From 4ed70eccac39ac5ab396522205890d3410cf852d Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 16 Jan 2026 15:21:03 +0000 Subject: [PATCH 01/19] Implement concrete analyser classes for beamlines to remove generics --- src/dodal/beamlines/b07.py | 16 +++--------- src/dodal/beamlines/b07_1.py | 21 ++++----------- src/dodal/beamlines/i09.py | 14 +++------- src/dodal/beamlines/p60.py | 19 +++----------- src/dodal/devices/b07/__init__.py | 5 ++-- src/dodal/devices/b07/analyser.py | 16 ++++++++++++ src/dodal/devices/b07/enums.py | 12 --------- src/dodal/devices/b07_1/__init__.py | 9 +++---- src/dodal/devices/b07_1/analyser.py | 16 ++++++++++++ src/dodal/devices/b07_shared/__init__.py | 3 +++ src/dodal/devices/b07_shared/enums.py | 13 ++++++++++ src/dodal/devices/i09/__init__.py | 3 ++- src/dodal/devices/i09/analyser.py | 26 +++++++++++++++++++ src/dodal/devices/p60/__init__.py | 2 ++ src/dodal/devices/p60/analyser.py | 22 ++++++++++++++++ .../base/test_base_controller.py | 4 +-- .../base/test_base_detector.py | 11 ++++---- .../base/test_base_driver_io.py | 4 +-- .../base/test_base_region.py | 6 ++--- .../specs/test_specs_detector.py | 3 ++- .../specs/test_specs_driver_io.py | 3 ++- .../specs/test_specs_region.py | 3 ++- 22 files changed, 141 insertions(+), 90 deletions(-) create mode 100644 src/dodal/devices/b07/analyser.py create mode 100644 src/dodal/devices/b07_1/analyser.py create mode 100644 src/dodal/devices/b07_shared/__init__.py create mode 100644 src/dodal/devices/b07_shared/enums.py create mode 100644 src/dodal/devices/i09/analyser.py create mode 100644 src/dodal/devices/p60/analyser.py diff --git a/src/dodal/beamlines/b07.py b/src/dodal/beamlines/b07.py index 8fdba8cc473..d0e44814dd3 100644 --- a/src/dodal/beamlines/b07.py +++ b/src/dodal/beamlines/b07.py @@ -1,10 +1,7 @@ -from dodal.common.beamlines.beamline_utils import ( - device_factory, -) +from dodal.common.beamlines.beamline_utils import device_factory from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline -from dodal.devices.b07 import Grating, LensMode, PsuMode +from dodal.devices.b07 import Grating, Specs2DCMOS from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.synchrotron import Synchrotron from dodal.log import set_beamline as set_log_beamline @@ -37,10 +34,5 @@ def energy_source() -> EnergySource: # Connect will work again after this work completed # https://jira.diamond.ac.uk/browse/B07-1104 @device_factory() -def analyser() -> SpecsDetector[LensMode, PsuMode]: - return SpecsDetector[LensMode, PsuMode]( - prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - energy_source=energy_source(), - ) +def analyser() -> Specs2DCMOS: + return Specs2DCMOS(f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", energy_source()) diff --git a/src/dodal/beamlines/b07_1.py b/src/dodal/beamlines/b07_1.py index fffc534785f..2c582a4d6f8 100644 --- a/src/dodal/beamlines/b07_1.py +++ b/src/dodal/beamlines/b07_1.py @@ -1,13 +1,7 @@ from dodal.common.beamlines.beamline_utils import device_factory from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline -from dodal.devices.b07 import PsuMode -from dodal.devices.b07_1 import ( - ChannelCutMonochromator, - Grating, - LensMode, -) +from dodal.devices.b07_1 import ChannelCutMonochromator, Grating, SpecsPhoibos from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.synchrotron import Synchrotron from dodal.log import set_beamline as set_log_beamline @@ -32,8 +26,6 @@ def pgm() -> PlaneGratingMonochromator: ) -# Connect will work again after this work completed -# https://jira.diamond.ac.uk/browse/B07-1104 @device_factory() def ccmc() -> ChannelCutMonochromator: return ChannelCutMonochromator(prefix=f"{PREFIX.beamline_prefix}-OP-CCM-01:") @@ -44,11 +36,8 @@ def energy_source() -> EnergySource: return EnergySource(pgm().energy.user_readback) +# Connect will work again after this work completed +# https://jira.diamond.ac.uk/browse/B07-1104 @device_factory() -def analyser() -> SpecsDetector[LensMode, PsuMode]: - return SpecsDetector[LensMode, PsuMode]( - prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - energy_source=energy_source(), - ) +def analyser() -> SpecsPhoibos: + return SpecsPhoibos(f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", energy_source()) diff --git a/src/dodal/beamlines/i09.py b/src/dodal/beamlines/i09.py index 880be616776..b4251492e3f 100644 --- a/src/dodal/beamlines/i09.py +++ b/src/dodal/beamlines/i09.py @@ -12,15 +12,12 @@ 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.i09 import Grating, LensMode, PassEnergy, PsuMode +from dodal.devices.i09 import EW4000, Grating from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.selectable_source import SourceSelector from dodal.devices.synchrotron import Synchrotron -from dodal.devices.temperture_controller import ( - Lakeshore336, -) +from dodal.devices.temperture_controller import Lakeshore336 from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name @@ -86,12 +83,9 @@ def dual_fast_shutter() -> DualFastShutter[InOut]: # Connect will work again after this work completed # https://jira.diamond.ac.uk/browse/I09-651 @device_factory() -def ew4000() -> VGScientaDetector[LensMode, PsuMode, PassEnergy]: - return VGScientaDetector[LensMode, PsuMode, PassEnergy]( +def ew4000() -> EW4000: + return EW4000( 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(), diff --git a/src/dodal/beamlines/p60.py b/src/dodal/beamlines/p60.py index 3cf0dc9304b..2b218d83281 100644 --- a/src/dodal/beamlines/p60.py +++ b/src/dodal/beamlines/p60.py @@ -3,14 +3,7 @@ ) from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.devices.electron_analyser.base import DualEnergySource -from dodal.devices.electron_analyser.vgscienta import VGScientaDetector -from dodal.devices.p60 import ( - LabXraySource, - LabXraySourceReadable, - LensMode, - PassEnergy, - PsuMode, -) +from dodal.devices.p60 import R4000, LabXraySource, LabXraySourceReadable 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 @@ -48,11 +41,5 @@ def energy_source() -> DualEnergySource: # Connect will work again after this work completed # https://jira.diamond.ac.uk/browse/P60-13 @device_factory() -def r4000() -> 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() -> R4000: + return R4000(f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", energy_source()) diff --git a/src/dodal/devices/b07/__init__.py b/src/dodal/devices/b07/__init__.py index 15895a410d8..a294200bba4 100644 --- a/src/dodal/devices/b07/__init__.py +++ b/src/dodal/devices/b07/__init__.py @@ -1,3 +1,4 @@ -from dodal.devices.b07.enums import Grating, LensMode, PsuMode +from .analyser import Specs2DCMOS +from .enums import Grating, LensMode -__all__ = ["Grating", "LensMode", "PsuMode"] +__all__ = ["Specs2DCMOS", "Grating", "LensMode"] diff --git a/src/dodal/devices/b07/analyser.py b/src/dodal/devices/b07/analyser.py new file mode 100644 index 00000000000..9ab654d60cd --- /dev/null +++ b/src/dodal/devices/b07/analyser.py @@ -0,0 +1,16 @@ +from dodal.devices.b07.enums import LensMode +from dodal.devices.b07_shared.enums import PsuMode +from dodal.devices.electron_analyser.base.energy_sources import EnergySource +from dodal.devices.electron_analyser.specs import SpecsDetector +from dodal.devices.fast_shutter import FastShutter + + +class Specs2DCMOS(SpecsDetector[LensMode, PsuMode]): + def __init__( + self, + prefix: str, + energy_source: EnergySource, + shutter: FastShutter | None = None, + name: str = "", + ): + super().__init__(prefix, LensMode, PsuMode, energy_source, shutter, None, name) diff --git a/src/dodal/devices/b07/enums.py b/src/dodal/devices/b07/enums.py index 58c826bb372..2e27db999d4 100644 --- a/src/dodal/devices/b07/enums.py +++ b/src/dodal/devices/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/b07_1/__init__.py b/src/dodal/devices/b07_1/__init__.py index 4573de4ab9d..a9578cd56e4 100644 --- a/src/dodal/devices/b07_1/__init__.py +++ b/src/dodal/devices/b07_1/__init__.py @@ -1,10 +1,9 @@ -from dodal.devices.b07_1.ccmc import ( - ChannelCutMonochromator, - ChannelCutMonochromatorPositions, -) -from dodal.devices.b07_1.enums import Grating, LensMode +from .analyser import SpecsPhoibos +from .ccmc import ChannelCutMonochromator, ChannelCutMonochromatorPositions +from .enums import Grating, LensMode __all__ = [ + "SpecsPhoibos", "Grating", "LensMode", "ChannelCutMonochromator", diff --git a/src/dodal/devices/b07_1/analyser.py b/src/dodal/devices/b07_1/analyser.py new file mode 100644 index 00000000000..be59d031c26 --- /dev/null +++ b/src/dodal/devices/b07_1/analyser.py @@ -0,0 +1,16 @@ +from dodal.devices.b07_1.enums import LensMode +from dodal.devices.b07_shared.enums import PsuMode +from dodal.devices.electron_analyser.base.energy_sources import EnergySource +from dodal.devices.electron_analyser.specs import SpecsDetector +from dodal.devices.fast_shutter import FastShutter + + +class SpecsPhoibos(SpecsDetector[LensMode, PsuMode]): + def __init__( + self, + prefix: str, + energy_source: EnergySource, + shutter: FastShutter | None = None, + name: str = "", + ): + super().__init__(prefix, LensMode, PsuMode, energy_source, shutter, None, name) diff --git a/src/dodal/devices/b07_shared/__init__.py b/src/dodal/devices/b07_shared/__init__.py new file mode 100644 index 00000000000..9fa24dbb0f4 --- /dev/null +++ b/src/dodal/devices/b07_shared/__init__.py @@ -0,0 +1,3 @@ +from .enums import PsuMode + +__all__ = ["PsuMode"] diff --git a/src/dodal/devices/b07_shared/enums.py b/src/dodal/devices/b07_shared/enums.py new file mode 100644 index 00000000000..0c6bbba3a8d --- /dev/null +++ b/src/dodal/devices/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/i09/__init__.py b/src/dodal/devices/i09/__init__.py index 012f6c53259..a4c08c18da2 100644 --- a/src/dodal/devices/i09/__init__.py +++ b/src/dodal/devices/i09/__init__.py @@ -1,3 +1,4 @@ +from dodal.devices.i09.analyser import EW4000 from dodal.devices.i09.enums import Grating, LensMode, PassEnergy, PsuMode -__all__ = ["Grating", "LensMode", "PsuMode", "PassEnergy"] +__all__ = ["EW4000", "Grating", "LensMode", "PsuMode", "PassEnergy"] diff --git a/src/dodal/devices/i09/analyser.py b/src/dodal/devices/i09/analyser.py new file mode 100644 index 00000000000..533ce088253 --- /dev/null +++ b/src/dodal/devices/i09/analyser.py @@ -0,0 +1,26 @@ +from dodal.devices.electron_analyser.base import AbstractEnergySource +from dodal.devices.electron_analyser.vgscienta import VGScientaDetector +from dodal.devices.fast_shutter import FastShutter +from dodal.devices.i09.enums import LensMode, PassEnergy, PsuMode +from dodal.devices.selectable_source import SourceSelector + + +class EW4000(VGScientaDetector[LensMode, PsuMode, PassEnergy]): + def __init__( + self, + prefix: str, + energy_source: AbstractEnergySource, + shutter: FastShutter | None = None, + source_selector: SourceSelector | None = None, + name: str = "", + ): + super().__init__( + prefix, + LensMode, + PsuMode, + PassEnergy, + energy_source, + shutter, + source_selector, + name, + ) diff --git a/src/dodal/devices/p60/__init__.py b/src/dodal/devices/p60/__init__.py index 6a25e7d0ba3..42d52a2d03c 100644 --- a/src/dodal/devices/p60/__init__.py +++ b/src/dodal/devices/p60/__init__.py @@ -1,7 +1,9 @@ +from .analyser import R4000 from .enums import LensMode, PassEnergy, PsuMode from .lab_xray_source import LabXraySource, LabXraySourceReadable __all__ = [ + "R4000", "LensMode", "PsuMode", "PassEnergy", diff --git a/src/dodal/devices/p60/analyser.py b/src/dodal/devices/p60/analyser.py new file mode 100644 index 00000000000..125f8a05661 --- /dev/null +++ b/src/dodal/devices/p60/analyser.py @@ -0,0 +1,22 @@ +from dodal.devices.electron_analyser.base import AbstractEnergySource +from dodal.devices.electron_analyser.vgscienta import VGScientaDetector +from dodal.devices.p60.enums import LensMode, PassEnergy, PsuMode + + +class R4000(VGScientaDetector[LensMode, PsuMode, PassEnergy]): + def __init__( + self, + prefix: str, + energy_source: AbstractEnergySource, + name: str = "", + ): + super().__init__( + prefix, + LensMode, + PsuMode, + PassEnergy, + energy_source, + None, + None, + name, + ) diff --git a/tests/devices/electron_analyser/base/test_base_controller.py b/tests/devices/electron_analyser/base/test_base_controller.py index aa899c66fc1..d9c8a9f260d 100644 --- a/tests/devices/electron_analyser/base/test_base_controller.py +++ b/tests/devices/electron_analyser/base/test_base_controller.py @@ -3,7 +3,7 @@ import pytest from ophyd_async.core import InOut, TriggerInfo, get_mock_put, init_devices -from dodal.beamlines import b07, i09 +from dodal.devices import b07, b07_shared, i09 from dodal.devices.electron_analyser.base import ( AbstractAnalyserDriverIO, AbstractBaseRegion, @@ -28,7 +28,7 @@ @pytest.fixture( params=[ - SpecsAnalyserDriverIO[b07.LensMode, b07.PsuMode], + SpecsAnalyserDriverIO[b07.LensMode, b07_shared.PsuMode], VGScientaAnalyserDriverIO[i09.LensMode, i09.PsuMode, i09.PassEnergy], ] ) diff --git a/tests/devices/electron_analyser/base/test_base_detector.py b/tests/devices/electron_analyser/base/test_base_detector.py index 68191af0c75..cc8e85d0cdb 100644 --- a/tests/devices/electron_analyser/base/test_base_detector.py +++ b/tests/devices/electron_analyser/base/test_base_detector.py @@ -10,6 +10,7 @@ ) import dodal.devices.b07 as b07 +import dodal.devices.b07_shared as b07_shared import dodal.devices.i09 as i09 from dodal.devices.electron_analyser.base import ( EnergySource, @@ -22,13 +23,11 @@ from dodal.testing.electron_analyser import create_detector from tests.devices.electron_analyser.helper_util import get_test_sequence +VGScientaDetector = VGScientaDetector[i09.LensMode, i09.PsuMode, i09.PassEnergy] +SpecsDetector = SpecsDetector[b07.LensMode, b07_shared.PsuMode] -@pytest.fixture( - params=[ - VGScientaDetector[i09.LensMode, i09.PsuMode, i09.PassEnergy], - SpecsDetector[b07.LensMode, b07.PsuMode], - ] -) + +@pytest.fixture(params=[VGScientaDetector, SpecsDetector]) async def sim_detector( request: pytest.FixtureRequest, single_energy_source: EnergySource, 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 36dd31a3435..c991d641643 100644 --- a/tests/devices/electron_analyser/base/test_base_driver_io.py +++ b/tests/devices/electron_analyser/base/test_base_driver_io.py @@ -4,7 +4,7 @@ from bluesky.utils import FailedStatus from ophyd_async.core import StrictEnum, init_devices -from dodal.devices import b07, i09 +from dodal.devices import b07, b07_shared, i09 from dodal.devices.electron_analyser.base import GenericAnalyserDriverIO from dodal.devices.electron_analyser.specs import ( SpecsAnalyserDriverIO, @@ -18,7 +18,7 @@ @pytest.fixture( params=[ VGScientaAnalyserDriverIO[i09.LensMode, i09.PsuMode, i09.PassEnergy], - SpecsAnalyserDriverIO[b07.LensMode, b07.PsuMode], + SpecsAnalyserDriverIO[b07.LensMode, b07_shared.PsuMode], ] ) async def sim_driver( diff --git a/tests/devices/electron_analyser/base/test_base_region.py b/tests/devices/electron_analyser/base/test_base_region.py index faea3fdbcfa..323d854182f 100644 --- a/tests/devices/electron_analyser/base/test_base_region.py +++ b/tests/devices/electron_analyser/base/test_base_region.py @@ -3,7 +3,7 @@ import pytest from dodal.common.data_util import load_json_file_to_class -from dodal.devices import b07, i09 +from dodal.devices import b07, b07_shared, i09 from dodal.devices.electron_analyser.base import ( AbstractBaseRegion, EnergyMode, @@ -26,7 +26,7 @@ @pytest.fixture( params=[ - SpecsSequence[b07.LensMode, b07.PsuMode], + SpecsSequence[b07.LensMode, b07_shared.PsuMode], VGScientaSequence[i09.LensMode, i09.PsuMode, i09.PassEnergy], ] ) @@ -39,7 +39,7 @@ def expected_region_class( sequence: GenericSequence, ) -> type[AbstractBaseRegion]: if isinstance(sequence, SpecsSequence): - return SpecsRegion[b07.LensMode, b07.PsuMode] + return SpecsRegion[b07.LensMode, b07_shared.PsuMode] elif isinstance(sequence, VGScientaSequence): return VGScientaRegion[i09.LensMode, i09.PassEnergy] raise TypeError(f"Unknown sequence type {type(sequence)}") diff --git a/tests/devices/electron_analyser/specs/test_specs_detector.py b/tests/devices/electron_analyser/specs/test_specs_detector.py index 2efb409ed39..043d8b4ee67 100644 --- a/tests/devices/electron_analyser/specs/test_specs_detector.py +++ b/tests/devices/electron_analyser/specs/test_specs_detector.py @@ -1,7 +1,8 @@ import pytest from ophyd_async.core import init_devices, set_mock_value -from dodal.devices.b07 import LensMode, PsuMode +from dodal.devices.b07 import LensMode +from dodal.devices.b07_shared import 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 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 898132bb6e3..455e9a712e0 100644 --- a/tests/devices/electron_analyser/specs/test_specs_driver_io.py +++ b/tests/devices/electron_analyser/specs/test_specs_driver_io.py @@ -12,7 +12,8 @@ partial_reading, ) -from dodal.devices.b07 import LensMode, PsuMode +from dodal.devices.b07 import LensMode +from dodal.devices.b07_shared import PsuMode 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 ( diff --git a/tests/devices/electron_analyser/specs/test_specs_region.py b/tests/devices/electron_analyser/specs/test_specs_region.py index f06fdb00846..992b3eb9da3 100644 --- a/tests/devices/electron_analyser/specs/test_specs_region.py +++ b/tests/devices/electron_analyser/specs/test_specs_region.py @@ -3,7 +3,8 @@ import pytest from dodal.common.data_util import load_json_file_to_class -from dodal.devices.b07 import LensMode, PsuMode +from dodal.devices.b07 import LensMode +from dodal.devices.b07_shared import PsuMode from dodal.devices.electron_analyser.base import EnergyMode from dodal.devices.electron_analyser.specs import ( AcquisitionMode, From 7b8b8115a25b8b59e650605f6c930da0d78eccc1 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Mon, 19 Jan 2026 11:13:39 +0000 Subject: [PATCH 02/19] Add i09_1 specs model --- src/dodal/beamlines/i09_1.py | 12 +++--------- src/dodal/devices/i09_1/__init__.py | 3 ++- src/dodal/devices/i09_1/analyser.py | 15 +++++++++++++++ src/dodal/devices/p60/analyser.py | 4 ++++ 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 src/dodal/devices/i09_1/analyser.py diff --git a/src/dodal/beamlines/i09_1.py b/src/dodal/beamlines/i09_1.py index c5321ba3bbb..d1c63a5b2b9 100644 --- a/src/dodal/beamlines/i09_1.py +++ b/src/dodal/beamlines/i09_1.py @@ -8,8 +8,7 @@ StationaryCrystal, ) from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector -from dodal.devices.i09_1 import LensMode, PsuMode +from dodal.devices.i09_1 import SpecsPhoibos225 from dodal.devices.i09_1_shared.hard_energy import HardEnergy, HardInsertionDeviceEnergy from dodal.devices.i09_1_shared.hard_undulator_functions import ( calculate_energy_i09_hu, @@ -49,13 +48,8 @@ def energy_source() -> EnergySource: # Connect will work again after this work completed # https://jira.diamond.ac.uk/browse/I09-651 @device_factory(skip=True) -def analyser() -> 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() -> SpecsPhoibos225: + return SpecsPhoibos225(f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:", energy_source()) @device_factory() diff --git a/src/dodal/devices/i09_1/__init__.py b/src/dodal/devices/i09_1/__init__.py index b54345b1141..3577fd23d9d 100644 --- a/src/dodal/devices/i09_1/__init__.py +++ b/src/dodal/devices/i09_1/__init__.py @@ -1,3 +1,4 @@ +from .analyser import SpecsPhoibos225 from .enums import LensMode, PsuMode -__all__ = ["LensMode", "PsuMode"] +__all__ = ["SpecsPhoibos225", "LensMode", "PsuMode"] diff --git a/src/dodal/devices/i09_1/analyser.py b/src/dodal/devices/i09_1/analyser.py new file mode 100644 index 00000000000..cb1a15529d1 --- /dev/null +++ b/src/dodal/devices/i09_1/analyser.py @@ -0,0 +1,15 @@ +from dodal.devices.electron_analyser.base.energy_sources import EnergySource +from dodal.devices.electron_analyser.specs import SpecsDetector +from dodal.devices.fast_shutter import FastShutter +from dodal.devices.i09_1.enums import LensMode, PsuMode + + +class SpecsPhoibos225(SpecsDetector[LensMode, PsuMode]): + def __init__( + self, + prefix: str, + energy_source: EnergySource, + shutter: FastShutter | None = None, + name: str = "", + ): + super().__init__(prefix, LensMode, PsuMode, energy_source, shutter, None, name) diff --git a/src/dodal/devices/p60/analyser.py b/src/dodal/devices/p60/analyser.py index 125f8a05661..3ad46b0d787 100644 --- a/src/dodal/devices/p60/analyser.py +++ b/src/dodal/devices/p60/analyser.py @@ -4,6 +4,10 @@ class R4000(VGScientaDetector[LensMode, PsuMode, PassEnergy]): + """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, From fd65ea620c820ce8d09d9a7225eac0c6513a69de Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Mon, 19 Jan 2026 11:20:18 +0000 Subject: [PATCH 03/19] Add device args --- src/dodal/devices/i09/analyser.py | 16 ++++++++-------- src/dodal/devices/p60/analyser.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/dodal/devices/i09/analyser.py b/src/dodal/devices/i09/analyser.py index 533ce088253..e3dbe597377 100644 --- a/src/dodal/devices/i09/analyser.py +++ b/src/dodal/devices/i09/analyser.py @@ -15,12 +15,12 @@ def __init__( name: str = "", ): super().__init__( - prefix, - LensMode, - PsuMode, - PassEnergy, - energy_source, - shutter, - source_selector, - name, + prefix=prefix, + lens_mode_type=LensMode, + psu_mode_type=PsuMode, + pass_energy_type=PassEnergy, + energy_source=energy_source, + shutter=shutter, + source_selector=source_selector, + name=name, ) diff --git a/src/dodal/devices/p60/analyser.py b/src/dodal/devices/p60/analyser.py index 3ad46b0d787..2fd306af5b0 100644 --- a/src/dodal/devices/p60/analyser.py +++ b/src/dodal/devices/p60/analyser.py @@ -15,12 +15,12 @@ def __init__( name: str = "", ): super().__init__( - prefix, - LensMode, - PsuMode, - PassEnergy, - energy_source, - None, - None, - name, + prefix=prefix, + lens_mode_type=LensMode, + psu_mode_type=PsuMode, + pass_energy_type=PassEnergy, + energy_source=energy_source, + shutter=None, + source_selector=None, + name=name, ) From 6a9465d225abc1ab2a9b340e0fd76bc1c09c1e67 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Thu, 22 Jan 2026 16:29:59 +0000 Subject: [PATCH 04/19] Fix lint --- src/dodal/beamlines/p60.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dodal/beamlines/p60.py b/src/dodal/beamlines/p60.py index 89612420b1f..32e67d124e6 100644 --- a/src/dodal/beamlines/p60.py +++ b/src/dodal/beamlines/p60.py @@ -30,7 +30,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, @@ -45,7 +45,5 @@ 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) -> R4000: - return R4000(f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", energy_source) - - +def r4000(dual_energy_source: DualEnergySource) -> R4000: + return R4000(f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", dual_energy_source) From 146163cc7cf519c09a740715f61295c84fe9932e Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Thu, 22 Jan 2026 16:33:14 +0000 Subject: [PATCH 05/19] Reorder DeviceManager placement for P60 --- src/dodal/beamlines/p60.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dodal/beamlines/p60.py b/src/dodal/beamlines/p60.py index 32e67d124e6..a897bd3b83c 100644 --- a/src/dodal/beamlines/p60.py +++ b/src/dodal/beamlines/p60.py @@ -6,13 +6,13 @@ from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name -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: From 04a03710bf16d0882d9877f043b32f882b0b821b Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Mon, 26 Jan 2026 17:25:57 +0000 Subject: [PATCH 06/19] Removed VGScienta and Specs Detector. Moved this to beamline implementation instead. --- src/dodal/beamlines/i09.py | 14 +- src/dodal/devices/b07/__init__.py | 18 ++- src/dodal/devices/b07/analyser.py | 29 ++++- src/dodal/devices/b07_1/__init__.py | 12 +- src/dodal/devices/b07_1/analyser.py | 31 ++++- .../electron_analyser/base/base_detector.py | 39 ++---- .../electron_analyser/specs/__init__.py | 2 - .../electron_analyser/specs/specs_detector.py | 47 ------- .../electron_analyser/vgscienta/__init__.py | 2 - .../vgscienta/vgscienta_detector.py | 53 -------- src/dodal/devices/i09/__init__.py | 18 ++- src/dodal/devices/i09/analyser.py | 61 ++++++--- src/dodal/devices/i09_1/analyser.py | 31 ++++- src/dodal/devices/p60/analyser.py | 46 +++++-- .../testing/electron_analyser/__init__.py | 6 - .../electron_analyser/device_factory.py | 59 --------- .../base/test_base_controller.py | 122 +++++------------- .../base/test_base_detector.py | 94 +++++++------- .../base/test_base_region.py | 24 ++-- tests/devices/electron_analyser/conftest.py | 73 ++++++----- .../electron_analyser/helper_util/__init__.py | 11 +- .../electron_analyser/helper_util/sequence.py | 42 +++--- .../specs/test_specs_detector.py | 47 ------- .../specs/test_specs_driver_io.py | 9 +- .../specs/test_specs_region.py | 18 +-- .../vgscienta/test_vgscienta_detector.py | 41 ------ .../vgscienta/test_vgscienta_driver_io.py | 34 +++-- .../vgscienta/test_vgsicenta_region.py | 21 ++- 28 files changed, 414 insertions(+), 590 deletions(-) delete mode 100644 src/dodal/devices/electron_analyser/specs/specs_detector.py delete mode 100644 src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py delete mode 100644 src/dodal/testing/electron_analyser/__init__.py delete mode 100644 src/dodal/testing/electron_analyser/device_factory.py delete mode 100644 tests/devices/electron_analyser/specs/test_specs_detector.py delete mode 100644 tests/devices/electron_analyser/vgscienta/test_vgscienta_detector.py diff --git a/src/dodal/beamlines/i09.py b/src/dodal/beamlines/i09.py index 944e71c1a45..2ab46ec1cf7 100644 --- a/src/dodal/beamlines/i09.py +++ b/src/dodal/beamlines/i09.py @@ -5,9 +5,7 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager from dodal.devices.common_dcm import DoubleCrystalMonochromatorWithDSpacing -from dodal.devices.electron_analyser.base import ( - DualEnergySource, -) +from dodal.devices.electron_analyser.base import DualEnergySource from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter from dodal.devices.i09 import EW4000 from dodal.devices.pgm import PlaneGratingMonochromator @@ -80,15 +78,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, ) -> EW4000: return EW4000( - prefix=f"{I_PREFIX.beamline_prefix}-EA-DET-01:CAM:", - energy_source=dual_energy_source, - shutter=dual_fast_shutter, - source_selector=source_selector, + f"{I_PREFIX.beamline_prefix}-EA-DET-01:CAM:", + dual_energy_source, + dual_fast_shutter, + source_selector, ) diff --git a/src/dodal/devices/b07/__init__.py b/src/dodal/devices/b07/__init__.py index a294200bba4..8c7f6c984a8 100644 --- a/src/dodal/devices/b07/__init__.py +++ b/src/dodal/devices/b07/__init__.py @@ -1,4 +1,18 @@ -from .analyser import Specs2DCMOS +from .analyser import ( + B07ElectronAnalyserController, + B07SpecsAnalyserDriverIO, + B07SpecsRegion, + B07SpecsSequence, + Specs2DCMOS, +) from .enums import Grating, LensMode -__all__ = ["Specs2DCMOS", "Grating", "LensMode"] +__all__ = [ + "B07ElectronAnalyserController", + "B07SpecsAnalyserDriverIO", + "B07SpecsRegion", + "B07SpecsSequence", + "Specs2DCMOS", + "Grating", + "LensMode", +] diff --git a/src/dodal/devices/b07/analyser.py b/src/dodal/devices/b07/analyser.py index 9ab654d60cd..945a610a5c9 100644 --- a/src/dodal/devices/b07/analyser.py +++ b/src/dodal/devices/b07/analyser.py @@ -1,11 +1,32 @@ from dodal.devices.b07.enums import LensMode from dodal.devices.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 SpecsDetector +from dodal.devices.electron_analyser.specs import ( + SpecsAnalyserDriverIO, + SpecsRegion, + SpecsSequence, +) from dodal.devices.fast_shutter import FastShutter +B07SpecsRegion = SpecsRegion[LensMode, PsuMode] +B07SpecsSequence = SpecsSequence[LensMode, PsuMode] -class Specs2DCMOS(SpecsDetector[LensMode, PsuMode]): + +class B07SpecsAnalyserDriverIO(SpecsAnalyserDriverIO): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, name) + + +B07ElectronAnalyserController = ElectronAnalyserController[ + B07SpecsAnalyserDriverIO, B07SpecsRegion +] + + +class Specs2DCMOS(ElectronAnalyserDetector[B07SpecsAnalyserDriverIO, B07SpecsRegion]): def __init__( self, prefix: str, @@ -13,4 +34,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - super().__init__(prefix, LensMode, PsuMode, energy_source, shutter, None, name) + driver = B07SpecsAnalyserDriverIO(prefix) + controller = B07ElectronAnalyserController(driver, energy_source, shutter) + super().__init__(controller, name) diff --git a/src/dodal/devices/b07_1/__init__.py b/src/dodal/devices/b07_1/__init__.py index a9578cd56e4..8158354ada5 100644 --- a/src/dodal/devices/b07_1/__init__.py +++ b/src/dodal/devices/b07_1/__init__.py @@ -1,8 +1,18 @@ -from .analyser import SpecsPhoibos +from .analyser import ( + B071ElectronAnalyserController, + B071SpecsAnalyserDriverIO, + B071SpecsRegion, + B071SpecsSequence, + SpecsPhoibos, +) from .ccmc import ChannelCutMonochromator, ChannelCutMonochromatorPositions from .enums import Grating, LensMode __all__ = [ + "B071ElectronAnalyserController", + "B071SpecsAnalyserDriverIO", + "B071SpecsRegion", + "B071SpecsSequence", "SpecsPhoibos", "Grating", "LensMode", diff --git a/src/dodal/devices/b07_1/analyser.py b/src/dodal/devices/b07_1/analyser.py index be59d031c26..ce92b07689e 100644 --- a/src/dodal/devices/b07_1/analyser.py +++ b/src/dodal/devices/b07_1/analyser.py @@ -1,11 +1,34 @@ from dodal.devices.b07_1.enums import LensMode from dodal.devices.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 SpecsDetector +from dodal.devices.electron_analyser.specs import ( + SpecsAnalyserDriverIO, + SpecsRegion, + SpecsSequence, +) from dodal.devices.fast_shutter import FastShutter +B071SpecsRegion = SpecsRegion[LensMode, PsuMode] +B071SpecsSequence = SpecsSequence[LensMode, PsuMode] -class SpecsPhoibos(SpecsDetector[LensMode, PsuMode]): + +class B071SpecsAnalyserDriverIO(SpecsAnalyserDriverIO): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, name) + + +B071ElectronAnalyserController = ElectronAnalyserController[ + B071SpecsAnalyserDriverIO, B071SpecsRegion +] + + +class SpecsPhoibos( + ElectronAnalyserDetector[B071SpecsAnalyserDriverIO, B071SpecsRegion] +): def __init__( self, prefix: str, @@ -13,4 +36,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - super().__init__(prefix, LensMode, PsuMode, energy_source, shutter, None, name) + driver = B071SpecsAnalyserDriverIO(prefix) + controller = B071ElectronAnalyserController(driver, energy_source, shutter) + 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 bcd68e4fc9d..24de42b4459 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, ) @@ -125,7 +122,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 @@ -135,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 @@ -164,39 +161,25 @@ 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: 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: Path to the sequence file containing the region data. - enabled_only: 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. """ - seq = self.load_sequence(filename) - regions: list[TAbstractBaseRegion] = ( - seq.get_enabled_regions() if enabled_only else seq.regions - ) return [ ElectronAnalyserRegionDetector[ TAbstractAnalyserDriverIO, TAbstractBaseRegion @@ -206,7 +189,7 @@ def create_region_detector_list( GenericElectronAnalyserDetector = ElectronAnalyserDetector[ - GenericSequence, GenericAnalyserDriverIO, GenericRegion + GenericAnalyserDriverIO, GenericRegion ] TElectronAnalyserDetector = TypeVar( "TElectronAnalyserDetector", diff --git a/src/dodal/devices/electron_analyser/specs/__init__.py b/src/dodal/devices/electron_analyser/specs/__init__.py index 64819341c05..ca5ce2686cd 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 fb54f47c49f..00000000000 --- 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 c344269cc19..3b746193df0 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 6e508fec5f0..00000000000 --- 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/i09/__init__.py b/src/dodal/devices/i09/__init__.py index a4c08c18da2..85bbe45d093 100644 --- a/src/dodal/devices/i09/__init__.py +++ b/src/dodal/devices/i09/__init__.py @@ -1,4 +1,18 @@ -from dodal.devices.i09.analyser import EW4000 +from dodal.devices.i09.analyser import ( + EW4000, + I09VGScientaAnalyserDriverIO, + I09VGScientaRegion, + I09VGScientaSequence, +) from dodal.devices.i09.enums import Grating, LensMode, PassEnergy, PsuMode -__all__ = ["EW4000", "Grating", "LensMode", "PsuMode", "PassEnergy"] +__all__ = [ + "EW4000", + "I09VGScientaAnalyserDriverIO", + "I09VGScientaRegion", + "I09VGScientaSequence", + "Grating", + "LensMode", + "PsuMode", + "PassEnergy", +] diff --git a/src/dodal/devices/i09/analyser.py b/src/dodal/devices/i09/analyser.py index e3dbe597377..33d75caa78a 100644 --- a/src/dodal/devices/i09/analyser.py +++ b/src/dodal/devices/i09/analyser.py @@ -1,26 +1,55 @@ -from dodal.devices.electron_analyser.base import AbstractEnergySource -from dodal.devices.electron_analyser.vgscienta import VGScientaDetector -from dodal.devices.fast_shutter import FastShutter +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.i09.enums import LensMode, PassEnergy, PsuMode 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, name) + + +I09Controller = ElectronAnalyserController[ + I09VGScientaAnalyserDriverIO, I09VGScientaRegion +] + + +class EW4000( + ElectronAnalyserDetector[I09VGScientaAnalyserDriverIO, I09VGScientaRegion] +): + """ """ -class EW4000(VGScientaDetector[LensMode, PsuMode, PassEnergy]): def __init__( self, prefix: str, - energy_source: AbstractEnergySource, - shutter: FastShutter | None = None, - source_selector: SourceSelector | None = None, + dual_energy_source: DualEnergySource, + dual_fast_shutter: DualFastShutter, + source_selector: SourceSelector, name: str = "", ): - super().__init__( - prefix=prefix, - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - pass_energy_type=PassEnergy, - energy_source=energy_source, - shutter=shutter, - source_selector=source_selector, - name=name, + driver = I09VGScientaAnalyserDriverIO(prefix) + controller = I09Controller( + driver, dual_energy_source, dual_fast_shutter, source_selector ) + super().__init__(controller, name) diff --git a/src/dodal/devices/i09_1/analyser.py b/src/dodal/devices/i09_1/analyser.py index cb1a15529d1..c432235122b 100644 --- a/src/dodal/devices/i09_1/analyser.py +++ b/src/dodal/devices/i09_1/analyser.py @@ -1,10 +1,33 @@ +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 SpecsDetector +from dodal.devices.electron_analyser.specs import ( + SpecsAnalyserDriverIO, + SpecsRegion, + SpecsSequence, +) from dodal.devices.fast_shutter import FastShutter from dodal.devices.i09_1.enums import LensMode, PsuMode +I091SpecsRegion = SpecsRegion[LensMode, PsuMode] +I091SpecsSequence = SpecsSequence[LensMode, PsuMode] -class SpecsPhoibos225(SpecsDetector[LensMode, PsuMode]): + +class I091SpecsAnalyserDriverIO(SpecsAnalyserDriverIO): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, name) + + +I091ElectronAnalyserController = ElectronAnalyserController[ + I091SpecsAnalyserDriverIO, I091SpecsRegion +] + + +class SpecsPhoibos225( + ElectronAnalyserDetector[I091SpecsAnalyserDriverIO, I091SpecsRegion] +): def __init__( self, prefix: str, @@ -12,4 +35,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - super().__init__(prefix, LensMode, PsuMode, energy_source, shutter, None, name) + driver = I091SpecsAnalyserDriverIO(prefix) + controller = I091ElectronAnalyserController(driver, energy_source, shutter) + super().__init__(controller, name) diff --git a/src/dodal/devices/p60/analyser.py b/src/dodal/devices/p60/analyser.py index 2fd306af5b0..be45e4bada0 100644 --- a/src/dodal/devices/p60/analyser.py +++ b/src/dodal/devices/p60/analyser.py @@ -1,9 +1,41 @@ from dodal.devices.electron_analyser.base import AbstractEnergySource -from dodal.devices.electron_analyser.vgscienta import VGScientaDetector +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.p60.enums import LensMode, PassEnergy, PsuMode +P60VGScientnaRegion = VGScientaRegion[LensMode, PassEnergy] +P60VGScientaSequence = VGScientaSequence[LensMode, PsuMode, PassEnergy] -class R4000(VGScientaDetector[LensMode, PsuMode, PassEnergy]): + +class P60VGScientaAnalyserDriverIO( + VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy] +): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, PassEnergy, name) + + +P60ElectronAnalyserController = ElectronAnalyserController[ + P60VGScientaAnalyserDriverIO, P60VGScientnaRegion +] + + +class R4000( + 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.""" @@ -14,13 +46,11 @@ def __init__( energy_source: AbstractEnergySource, name: str = "", ): - super().__init__( - prefix=prefix, - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - pass_energy_type=PassEnergy, + driver = P60VGScientaAnalyserDriverIO(prefix) + controller = P60ElectronAnalyserController( + driver, energy_source=energy_source, shutter=None, source_selector=None, - name=name, ) + super().__init__(controller, name) diff --git a/src/dodal/testing/electron_analyser/__init__.py b/src/dodal/testing/electron_analyser/__init__.py deleted file mode 100644 index dffbe8d4923..00000000000 --- 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 2d219fcc426..00000000000 --- a/src/dodal/testing/electron_analyser/device_factory.py +++ /dev/null @@ -1,59 +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 d9c8a9f260d..2cc24a51545 100644 --- a/tests/devices/electron_analyser/base/test_base_controller.py +++ b/tests/devices/electron_analyser/base/test_base_controller.py @@ -1,9 +1,9 @@ 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, init_devices -from dodal.devices import b07, b07_shared, i09 +from dodal.devices.b07.analyser import Specs2DCMOS from dodal.devices.electron_analyser.base import ( AbstractAnalyserDriverIO, AbstractBaseRegion, @@ -13,109 +13,49 @@ from dodal.devices.electron_analyser.base.base_controller import ( ElectronAnalyserController, ) -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.fast_shutter import DualFastShutter +from dodal.devices.i09.analyser import EW4000 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_shared.PsuMode], - VGScientaAnalyserDriverIO[i09.LensMode, i09.PsuMode, i09.PassEnergy], - ] -) -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)) +from tests.devices.electron_analyser.helper_util import TEST_SEQUENCE_REGION_NAMES @pytest.fixture -def shutter1() -> GenericFastShutter[InOut]: +def ew4000( + dual_energy_source: DualEnergySource, + dual_fast_shutter: DualFastShutter, + source_selector: SourceSelector, +) -> EW4000: with init_devices(mock=True): - shutter1 = GenericFastShutter[InOut]( - pv="TEST:", - open_state=InOut.OUT, - close_state=InOut.IN, - ) - return shutter1 + ew4000 = EW4000("TEST:", dual_energy_source, dual_fast_shutter, source_selector) + return ew4000 @pytest.fixture -def shutter2() -> GenericFastShutter[InOut]: +def specs_2dcmos(single_energy_source: EnergySource) -> Specs2DCMOS: with init_devices(mock=True): - shutter2 = GenericFastShutter[InOut]( - pv="TEST:", - open_state=InOut.OUT, - close_state=InOut.IN, - ) - return shutter2 + specs_2dcmos = Specs2DCMOS("TEST:", single_energy_source) + return specs_2dcmos -@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 +@pytest.fixture(params=["ew4000", "specs_2dcmos"]) +def analyser_controller( + request: pytest.FixtureRequest, ew4000: EW4000, specs_2dcmos: Specs2DCMOS +): + detectors = [ew4000, specs_2dcmos] + for detector in detectors: + if detector.name == request.param: + return detector._controller + raise ValueError(f"Detector with name '{request.param}' not found") -@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 +@pytest.fixture +def sim_driver( + analyser_controller: ElectronAnalyserController[ + AbstractAnalyserDriverIO, AbstractBaseRegion + ], +) -> AbstractAnalyserDriverIO: + return analyser_controller.driver 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 cc8e85d0cdb..755f584d029 100644 --- a/tests/devices/electron_analyser/base/test_base_detector.py +++ b/tests/devices/electron_analyser/base/test_base_detector.py @@ -9,38 +9,51 @@ assert_reading, ) -import dodal.devices.b07 as b07 -import dodal.devices.b07_shared as b07_shared -import dodal.devices.i09 as i09 +from dodal.devices.b07.analyser import Specs2DCMOS from dodal.devices.electron_analyser.base import ( + DualEnergySource, EnergySource, + GenericAnalyserDriverIO, 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 dodal.devices.fast_shutter import DualFastShutter +from dodal.devices.i09.analyser import EW4000 +from dodal.devices.selectable_source import SourceSelector -VGScientaDetector = VGScientaDetector[i09.LensMode, i09.PsuMode, i09.PassEnergy] -SpecsDetector = SpecsDetector[b07.LensMode, b07_shared.PsuMode] +@pytest.fixture +def ew4000( + dual_energy_source: DualEnergySource, + dual_fast_shutter: DualFastShutter, + source_selector: SourceSelector, +) -> EW4000: + with init_devices(mock=True): + ew4000 = EW4000("TEST:", dual_energy_source, dual_fast_shutter, source_selector) + return ew4000 -@pytest.fixture(params=[VGScientaDetector, SpecsDetector]) -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, - ) + +@pytest.fixture +def specs_2dcmos(single_energy_source: EnergySource) -> Specs2DCMOS: + with init_devices(mock=True): + specs_2dcmos = Specs2DCMOS("TEST:", 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 + set_mock_value(specs_2dcmos.driver.slices, 1) + return specs_2dcmos + + +@pytest.fixture(params=["ew4000", "specs_2dcmos"]) +def sim_detector( + request: pytest.FixtureRequest, ew4000: EW4000, specs_2dcmos: Specs2DCMOS +) -> GenericElectronAnalyserDetector: + detectors = [ew4000, specs_2dcmos] + for detector in detectors: + if detector.name == request.param: + return detector + + raise ValueError(f"Detector with name '{request.param}' not found") def test_base_analyser_detector_trigger( @@ -93,18 +106,10 @@ async def test_base_analyser_detector_describe_configuration( @pytest.fixture -def sequence_file_path( - sim_detector: GenericElectronAnalyserDetector, -) -> str: - return get_test_sequence(type(sim_detector)) - - -def test_analyser_detector_loads_sequence_correctly( +def sim_driver( sim_detector: GenericElectronAnalyserDetector, - sequence_file_path: str, -) -> None: - seq = sim_detector.load_sequence(sequence_file_path) - assert seq is not None +) -> GenericAnalyserDriverIO: + return sim_detector.driver async def test_analyser_detector_stage( @@ -129,20 +134,18 @@ 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) + region_detectors = sim_detector.create_region_detector_list(sequence.regions) - assert len(region_detectors) == len(seq.get_enabled_regions()) + assert len(region_detectors) == len(sequence.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 @@ -153,7 +156,7 @@ 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.regions) for det in region_detectors: assert det._child_devices.get(driver_name) is None @@ -177,11 +180,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) @@ -189,12 +191,10 @@ 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 - ) + region_detectors = sim_detector.create_region_detector_list(sequence.regions) trigger_info = TriggerInfo() for reg_det in region_detectors: diff --git a/tests/devices/electron_analyser/base/test_base_region.py b/tests/devices/electron_analyser/base/test_base_region.py index 323d854182f..8455f7709fb 100644 --- a/tests/devices/electron_analyser/base/test_base_region.py +++ b/tests/devices/electron_analyser/base/test_base_region.py @@ -2,8 +2,7 @@ import pytest -from dodal.common.data_util import load_json_file_to_class -from dodal.devices import b07, b07_shared, i09 +from dodal.devices.b07 import B07SpecsRegion from dodal.devices.electron_analyser.base import ( AbstractBaseRegion, EnergyMode, @@ -13,25 +12,24 @@ 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 dodal.devices.i09 import I09VGScientaRegion from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, - get_test_sequence, + b07_specs_sequence_loader, + i09_vgscienta_sequence_loader, ) @pytest.fixture( params=[ - SpecsSequence[b07.LensMode, b07_shared.PsuMode], - VGScientaSequence[i09.LensMode, i09.PsuMode, i09.PassEnergy], + b07_specs_sequence_loader(), + i09_vgscienta_sequence_loader(), ] ) def sequence(request: pytest.FixtureRequest) -> GenericSequence: - return load_json_file_to_class(request.param, get_test_sequence(request.param)) + return request.param @pytest.fixture @@ -39,9 +37,9 @@ def expected_region_class( sequence: GenericSequence, ) -> type[AbstractBaseRegion]: if isinstance(sequence, SpecsSequence): - return SpecsRegion[b07.LensMode, b07_shared.PsuMode] + return B07SpecsRegion 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 5ba46737e29..3885984ef4e 100644 --- a/tests/devices/electron_analyser/conftest.py +++ b/tests/devices/electron_analyser/conftest.py @@ -1,7 +1,7 @@ from typing import Any import pytest -from ophyd_async.core import init_devices +from ophyd_async.core import InOut, init_devices from dodal.devices.common_dcm import ( DoubleCrystalMonochromatorWithDSpacing, @@ -13,23 +13,13 @@ 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, ) +from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter from dodal.devices.i09 import Grating from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.selectable_source import SourceSelector -from tests.devices.electron_analyser.helper_util import get_test_sequence +from tests.devices.electron_analyser.helper_util import DRIVER_TO_TEST_SEQUENCE @pytest.fixture @@ -70,35 +60,48 @@ 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 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 dual_fast_shutter( + source_selector: SourceSelector, +) -> DualFastShutter[InOut]: + with init_devices(mock=True): + shutter1 = GenericFastShutter[InOut]( + pv="TEST:", + open_state=InOut.OUT, + close_state=InOut.IN, + ) + + with init_devices(mock=True): + shutter2 = GenericFastShutter[InOut]( + pv="TEST:", + open_state=InOut.OUT, + close_state=InOut.IN, + ) + + with init_devices(mock=True): + dual_fast_shutter = DualFastShutter[InOut]( + shutter1, + shutter2, + source_selector.selected_source, + ) + return dual_fast_shutter @pytest.fixture def sequence( sim_driver: AbstractAnalyserDriverIO, - sequence_class: type[TAbstractBaseSequence], - 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))) + return DRIVER_TO_TEST_SEQUENCE[type(sim_driver)] @pytest.fixture diff --git a/tests/devices/electron_analyser/helper_util/__init__.py b/tests/devices/electron_analyser/helper_util/__init__.py index c3026b1e71d..ec5e2125610 100644 --- a/tests/devices/electron_analyser/helper_util/__init__.py +++ b/tests/devices/electron_analyser/helper_util/__init__.py @@ -1,8 +1,15 @@ from .assert_func import assert_region_has_expected_values -from .sequence import TEST_SEQUENCE_REGION_NAMES, get_test_sequence +from .sequence import ( + DRIVER_TO_TEST_SEQUENCE, + TEST_SEQUENCE_REGION_NAMES, + b07_specs_sequence_loader, + i09_vgscienta_sequence_loader, +) __all__ = [ "assert_region_has_expected_values", - "get_test_sequence", + "DRIVER_TO_TEST_SEQUENCE", "TEST_SEQUENCE_REGION_NAMES", + "b07_specs_sequence_loader", + "i09_vgscienta_sequence_loader", ] diff --git a/tests/devices/electron_analyser/helper_util/sequence.py b/tests/devices/electron_analyser/helper_util/sequence.py index 85d47a8f56d..83d20760c90 100644 --- a/tests/devices/electron_analyser/helper_util/sequence.py +++ b/tests/devices/electron_analyser/helper_util/sequence.py @@ -1,34 +1,30 @@ -from dodal.devices.electron_analyser.specs import ( - SpecsAnalyserDriverIO, - SpecsDetector, - SpecsSequence, +from dodal.common.data_util import load_json_file_to_class +from dodal.devices.b07.analyser import ( + B07SpecsAnalyserDriverIO, + B07SpecsSequence, ) -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, - VGScientaDetector, - VGScientaSequence, +from dodal.devices.i09.analyser import ( + I09VGScientaAnalyserDriverIO, + I09VGScientaSequence, ) from tests.devices.electron_analyser.test_data import ( TEST_SPECS_SEQUENCE, TEST_VGSCIENTA_SEQUENCE, ) -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, -} +TEST_SEQUENCE_REGION_NAMES = ["New_Region", "New_Region1", "New_Region2"] -def get_test_sequence(key: type) -> str: - for cls in key.__mro__: - # Check for unscripted class only - if cls in TEST_SEQUENCES: - return TEST_SEQUENCES[cls] - raise KeyError(f"Found no match with type {key}") +def b07_specs_sequence_loader() -> B07SpecsSequence: + return load_json_file_to_class(B07SpecsSequence, TEST_SPECS_SEQUENCE) -TEST_SEQUENCE_REGION_NAMES = ["New_Region", "New_Region1", "New_Region2"] +def i09_vgscienta_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. +DRIVER_TO_TEST_SEQUENCE = { + B07SpecsAnalyserDriverIO: b07_specs_sequence_loader(), + I09VGScientaAnalyserDriverIO: i09_vgscienta_sequence_loader(), +} 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 043d8b4ee67..00000000000 --- a/tests/devices/electron_analyser/specs/test_specs_detector.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest -from ophyd_async.core import init_devices, set_mock_value - -from dodal.devices.b07 import LensMode -from dodal.devices.b07_shared import 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 455e9a712e0..2623e0fe297 100644 --- a/tests/devices/electron_analyser/specs/test_specs_driver_io.py +++ b/tests/devices/electron_analyser/specs/test_specs_driver_io.py @@ -13,6 +13,7 @@ ) from dodal.devices.b07 import LensMode +from dodal.devices.b07.analyser import B07SpecsAnalyserDriverIO from dodal.devices.b07_shared import PsuMode from dodal.devices.electron_analyser.base import EnergyMode from dodal.devices.electron_analyser.base.base_enums import EnergyMode @@ -21,19 +22,15 @@ SpecsAnalyserDriverIO, SpecsRegion, ) -from dodal.testing.electron_analyser import create_driver from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, ) @pytest.fixture -async def sim_driver() -> SpecsAnalyserDriverIO[LensMode, PsuMode]: +async def sim_driver() -> B07SpecsAnalyserDriverIO: async with init_devices(mock=True): - sim_driver = create_driver( - SpecsAnalyserDriverIO[LensMode, PsuMode], - prefix="TEST:", - ) + sim_driver = B07SpecsAnalyserDriverIO(prefix="TEST:") return sim_driver diff --git a/tests/devices/electron_analyser/specs/test_specs_region.py b/tests/devices/electron_analyser/specs/test_specs_region.py index 992b3eb9da3..c5ef29290a3 100644 --- a/tests/devices/electron_analyser/specs/test_specs_region.py +++ b/tests/devices/electron_analyser/specs/test_specs_region.py @@ -2,25 +2,21 @@ import pytest -from dodal.common.data_util import load_json_file_to_class from dodal.devices.b07 import LensMode +from dodal.devices.b07.analyser import B07SpecsSequence from dodal.devices.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 ( assert_region_has_expected_values, - get_test_sequence, + b07_specs_sequence_loader, ) @pytest.fixture -def sequence() -> SpecsSequence[LensMode, PsuMode]: - seq = SpecsSequence[LensMode, PsuMode] - return load_json_file_to_class(seq, get_test_sequence(seq)) +def sequence() -> B07SpecsSequence: + return b07_specs_sequence_loader() @pytest.fixture @@ -87,7 +83,7 @@ def expected_region_values() -> list[dict[str, Any]]: def test_sequence_get_expected_enabled_region_names( - sequence: SpecsSequence[LensMode, PsuMode], + sequence: B07SpecsSequence, expected_enabled_region_names: list[str], ) -> None: assert sequence.get_enabled_region_names() == expected_enabled_region_names @@ -96,7 +92,7 @@ def test_sequence_get_expected_enabled_region_names( def test_file_loads_into_class_with_expected_values( - sequence: SpecsSequence[LensMode, PsuMode], + sequence: B07SpecsSequence, 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 b7d771bca5f..00000000000 --- 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.electron_analyser.base import DualEnergySource -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaDetector, -) -from dodal.devices.i09 import LensMode, PassEnergy, PsuMode -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 61f1c381db5..c63939b6fb2 100644 --- a/tests/devices/electron_analyser/vgscienta/test_vgscienta_driver_io.py +++ b/tests/devices/electron_analyser/vgscienta/test_vgscienta_driver_io.py @@ -14,30 +14,26 @@ ) from dodal.devices.electron_analyser.base import EnergyMode -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, - VGScientaRegion, +from dodal.devices.i09.analyser import ( + I09VGScientaAnalyserDriverIO, + I09VGScientaRegion, ) -from dodal.devices.i09 import LensMode, PassEnergy, PsuMode -from dodal.testing.electron_analyser import create_driver from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, ) @pytest.fixture -async def sim_driver() -> VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy]: +async def sim_driver() -> I09VGScientaAnalyserDriverIO: async with init_devices(mock=True): - sim_driver = create_driver( - VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], prefix="TEST:" - ) + sim_driver = I09VGScientaAnalyserDriverIO(prefix="TEST:") return 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 +90,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 +132,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 +156,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 +179,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 +196,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 55df78dfafc..a2f0904dde4 100644 --- a/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py +++ b/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py @@ -2,31 +2,28 @@ import pytest -from dodal.common.data_util import load_json_file_to_class from dodal.devices.electron_analyser.base import EnergyMode from dodal.devices.electron_analyser.vgscienta import ( AcquisitionMode, DetectorMode, - VGScientaRegion, - VGScientaSequence, ) -from dodal.devices.i09 import LensMode, PassEnergy, PsuMode +from dodal.devices.i09 import LensMode, PassEnergy +from dodal.devices.i09.analyser import I09VGScientaRegion, I09VGScientaSequence from dodal.devices.selectable_source import SelectedSource from tests.devices.electron_analyser.helper_util import ( assert_region_has_expected_values, - get_test_sequence, + i09_vgscienta_sequence_loader, ) @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 i09_vgscienta_sequence_loader() @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 +105,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 +114,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) From e31edfe9bd25113821e2de99269be0d9396db4b7 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 27 Jan 2026 09:42:03 +0000 Subject: [PATCH 07/19] Fix tests --- .../base/test_base_driver_io.py | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) 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 c991d641643..da7a1ab2ffe 100644 --- a/tests/devices/electron_analyser/base/test_base_driver_io.py +++ b/tests/devices/electron_analyser/base/test_base_driver_io.py @@ -4,28 +4,15 @@ from bluesky.utils import FailedStatus from ophyd_async.core import StrictEnum, init_devices -from dodal.devices import b07, b07_shared, i09 +from dodal.devices.b07.analyser import B07SpecsAnalyserDriverIO from dodal.devices.electron_analyser.base import GenericAnalyserDriverIO -from dodal.devices.electron_analyser.specs import ( - SpecsAnalyserDriverIO, -) -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, -) -from dodal.testing.electron_analyser import create_driver +from dodal.devices.i09.analyser import I09VGScientaAnalyserDriverIO -@pytest.fixture( - params=[ - VGScientaAnalyserDriverIO[i09.LensMode, i09.PsuMode, i09.PassEnergy], - SpecsAnalyserDriverIO[b07.LensMode, b07_shared.PsuMode], - ] -) -async def sim_driver( - request: pytest.FixtureRequest, -) -> GenericAnalyserDriverIO: +@pytest.fixture(params=[I09VGScientaAnalyserDriverIO, B07SpecsAnalyserDriverIO]) +async def sim_driver(request: pytest.FixtureRequest) -> GenericAnalyserDriverIO: async with init_devices(mock=True): - sim_driver = create_driver(request.param, prefix="TEST:") + sim_driver = request.param(prefix="TEST:") return sim_driver From c5063d0771f97adeddc94158727eadba9c19a8ef Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 27 Jan 2026 10:30:24 +0000 Subject: [PATCH 08/19] Add doc string to EW4000 --- src/dodal/devices/i09/analyser.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dodal/devices/i09/analyser.py b/src/dodal/devices/i09/analyser.py index 33d75caa78a..ca880d6c2c4 100644 --- a/src/dodal/devices/i09/analyser.py +++ b/src/dodal/devices/i09/analyser.py @@ -30,7 +30,7 @@ def __init__(self, prefix: str, name: str = ""): super().__init__(prefix, LensMode, PsuMode, PassEnergy, name) -I09Controller = ElectronAnalyserController[ +I09ElectronAnalyserController = ElectronAnalyserController[ I09VGScientaAnalyserDriverIO, I09VGScientaRegion ] @@ -38,7 +38,9 @@ def __init__(self, prefix: str, name: str = ""): class EW4000( 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, @@ -49,7 +51,7 @@ def __init__( name: str = "", ): driver = I09VGScientaAnalyserDriverIO(prefix) - controller = I09Controller( + controller = I09ElectronAnalyserController( driver, dual_energy_source, dual_fast_shutter, source_selector ) super().__init__(controller, name) From 66ce50b31e91505a21be77bd509d6dbc7abb7cf3 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 27 Jan 2026 10:30:37 +0000 Subject: [PATCH 09/19] Add missing classes from __init__ --- src/dodal/devices/i09/__init__.py | 2 ++ src/dodal/devices/i09_1/__init__.py | 18 ++++++++++++++++-- src/dodal/devices/p60/__init__.py | 12 +++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/dodal/devices/i09/__init__.py b/src/dodal/devices/i09/__init__.py index 85bbe45d093..8516e467d45 100644 --- a/src/dodal/devices/i09/__init__.py +++ b/src/dodal/devices/i09/__init__.py @@ -1,5 +1,6 @@ from dodal.devices.i09.analyser import ( EW4000, + I09ElectronAnalyserController, I09VGScientaAnalyserDriverIO, I09VGScientaRegion, I09VGScientaSequence, @@ -8,6 +9,7 @@ __all__ = [ "EW4000", + "I09ElectronAnalyserController", "I09VGScientaAnalyserDriverIO", "I09VGScientaRegion", "I09VGScientaSequence", diff --git a/src/dodal/devices/i09_1/__init__.py b/src/dodal/devices/i09_1/__init__.py index 3577fd23d9d..7e509f537bc 100644 --- a/src/dodal/devices/i09_1/__init__.py +++ b/src/dodal/devices/i09_1/__init__.py @@ -1,4 +1,18 @@ -from .analyser import SpecsPhoibos225 +from .analyser import ( + I091ElectronAnalyserController, + I091SpecsAnalyserDriverIO, + I091SpecsRegion, + I091SpecsSequence, + SpecsPhoibos225, +) from .enums import LensMode, PsuMode -__all__ = ["SpecsPhoibos225", "LensMode", "PsuMode"] +__all__ = [ + "I091ElectronAnalyserController", + "I091SpecsAnalyserDriverIO", + "I091SpecsRegion", + "I091SpecsSequence", + "SpecsPhoibos225", + "LensMode", + "PsuMode", +] diff --git a/src/dodal/devices/p60/__init__.py b/src/dodal/devices/p60/__init__.py index 42d52a2d03c..136ecb09ba8 100644 --- a/src/dodal/devices/p60/__init__.py +++ b/src/dodal/devices/p60/__init__.py @@ -1,9 +1,19 @@ -from .analyser import R4000 +from .analyser import ( + R4000, + P60ElectronAnalyserController, + P60VGScientaAnalyserDriverIO, + P60VGScientaSequence, + P60VGScientnaRegion, +) from .enums import LensMode, PassEnergy, PsuMode from .lab_xray_source import LabXraySource, LabXraySourceReadable __all__ = [ "R4000", + "P60ElectronAnalyserController", + "P60VGScientaAnalyserDriverIO", + "P60VGScientaSequence", + "P60VGScientnaRegion", "LensMode", "PsuMode", "PassEnergy", From ec6d8178ab7d8f36ae1521053216e09f51276acd Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 27 Jan 2026 10:58:26 +0000 Subject: [PATCH 10/19] Add psu_mode_suffix option to driver to fix i09 and p60 connection fail --- src/dodal/devices/electron_analyser/base/base_driver_io.py | 3 ++- .../devices/electron_analyser/vgscienta/vgscienta_driver_io.py | 2 ++ src/dodal/devices/i09/analyser.py | 2 +- src/dodal/devices/p60/analyser.py | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) 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 817949fb0ec..615c8124fb1 100644 --- a/src/dodal/devices/electron_analyser/base/base_driver_io.py +++ b/src/dodal/devices/electron_analyser/base/base_driver_io.py @@ -54,6 +54,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: """ @@ -112,7 +113,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/vgscienta/vgscienta_driver_io.py b/src/dodal/devices/electron_analyser/vgscienta/vgscienta_driver_io.py index 3d7db84aa93..2563f0a7e37 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/devices/i09/analyser.py b/src/dodal/devices/i09/analyser.py index ca880d6c2c4..ee5cb43d7d6 100644 --- a/src/dodal/devices/i09/analyser.py +++ b/src/dodal/devices/i09/analyser.py @@ -27,7 +27,7 @@ class I09VGScientaAnalyserDriverIO( VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy] ): def __init__(self, prefix: str, name: str = ""): - super().__init__(prefix, LensMode, PsuMode, PassEnergy, name) + super().__init__(prefix, LensMode, PsuMode, PassEnergy, "ELEMENT_SET", name) I09ElectronAnalyserController = ElectronAnalyserController[ diff --git a/src/dodal/devices/p60/analyser.py b/src/dodal/devices/p60/analyser.py index be45e4bada0..0c530bacf9c 100644 --- a/src/dodal/devices/p60/analyser.py +++ b/src/dodal/devices/p60/analyser.py @@ -25,7 +25,7 @@ class P60VGScientaAnalyserDriverIO( VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy] ): def __init__(self, prefix: str, name: str = ""): - super().__init__(prefix, LensMode, PsuMode, PassEnergy, name) + super().__init__(prefix, LensMode, PsuMode, PassEnergy, "ELEMENT_SET", name) P60ElectronAnalyserController = ElectronAnalyserController[ From 60804b98a6cef0b7ed003de2bfadb43fc9b04792 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 27 Jan 2026 17:35:59 +0000 Subject: [PATCH 11/19] Renamed sequence functions --- tests/devices/electron_analyser/base/test_base_region.py | 8 ++++---- tests/devices/electron_analyser/helper_util/__init__.py | 8 ++++---- tests/devices/electron_analyser/helper_util/sequence.py | 8 ++++---- .../devices/electron_analyser/specs/test_specs_region.py | 4 ++-- .../electron_analyser/vgscienta/test_vgsicenta_region.py | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/devices/electron_analyser/base/test_base_region.py b/tests/devices/electron_analyser/base/test_base_region.py index 8455f7709fb..3dba0789101 100644 --- a/tests/devices/electron_analyser/base/test_base_region.py +++ b/tests/devices/electron_analyser/base/test_base_region.py @@ -17,15 +17,15 @@ from dodal.devices.i09 import I09VGScientaRegion from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, - b07_specs_sequence_loader, - i09_vgscienta_sequence_loader, + b07_specs_test_sequence_loader, + i09_vgscienta_test_sequence_loader, ) @pytest.fixture( params=[ - b07_specs_sequence_loader(), - i09_vgscienta_sequence_loader(), + b07_specs_test_sequence_loader(), + i09_vgscienta_test_sequence_loader(), ] ) def sequence(request: pytest.FixtureRequest) -> GenericSequence: diff --git a/tests/devices/electron_analyser/helper_util/__init__.py b/tests/devices/electron_analyser/helper_util/__init__.py index ec5e2125610..ed737315250 100644 --- a/tests/devices/electron_analyser/helper_util/__init__.py +++ b/tests/devices/electron_analyser/helper_util/__init__.py @@ -2,14 +2,14 @@ from .sequence import ( DRIVER_TO_TEST_SEQUENCE, TEST_SEQUENCE_REGION_NAMES, - b07_specs_sequence_loader, - i09_vgscienta_sequence_loader, + b07_specs_test_sequence_loader, + i09_vgscienta_test_sequence_loader, ) __all__ = [ "assert_region_has_expected_values", "DRIVER_TO_TEST_SEQUENCE", "TEST_SEQUENCE_REGION_NAMES", - "b07_specs_sequence_loader", - "i09_vgscienta_sequence_loader", + "b07_specs_test_sequence_loader", + "i09_vgscienta_test_sequence_loader", ] diff --git a/tests/devices/electron_analyser/helper_util/sequence.py b/tests/devices/electron_analyser/helper_util/sequence.py index 83d20760c90..1169228d5bc 100644 --- a/tests/devices/electron_analyser/helper_util/sequence.py +++ b/tests/devices/electron_analyser/helper_util/sequence.py @@ -15,16 +15,16 @@ TEST_SEQUENCE_REGION_NAMES = ["New_Region", "New_Region1", "New_Region2"] -def b07_specs_sequence_loader() -> B07SpecsSequence: +def b07_specs_test_sequence_loader() -> B07SpecsSequence: return load_json_file_to_class(B07SpecsSequence, TEST_SPECS_SEQUENCE) -def i09_vgscienta_sequence_loader() -> I09VGScientaSequence: +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. DRIVER_TO_TEST_SEQUENCE = { - B07SpecsAnalyserDriverIO: b07_specs_sequence_loader(), - I09VGScientaAnalyserDriverIO: i09_vgscienta_sequence_loader(), + B07SpecsAnalyserDriverIO: b07_specs_test_sequence_loader(), + I09VGScientaAnalyserDriverIO: i09_vgscienta_test_sequence_loader(), } diff --git a/tests/devices/electron_analyser/specs/test_specs_region.py b/tests/devices/electron_analyser/specs/test_specs_region.py index c5ef29290a3..ad52bd6ba5d 100644 --- a/tests/devices/electron_analyser/specs/test_specs_region.py +++ b/tests/devices/electron_analyser/specs/test_specs_region.py @@ -10,13 +10,13 @@ from dodal.devices.selectable_source import SelectedSource from tests.devices.electron_analyser.helper_util import ( assert_region_has_expected_values, - b07_specs_sequence_loader, + b07_specs_test_sequence_loader, ) @pytest.fixture def sequence() -> B07SpecsSequence: - return b07_specs_sequence_loader() + return b07_specs_test_sequence_loader() @pytest.fixture diff --git a/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py b/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py index a2f0904dde4..d9e97b8129c 100644 --- a/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py +++ b/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py @@ -12,13 +12,13 @@ from dodal.devices.selectable_source import SelectedSource from tests.devices.electron_analyser.helper_util import ( assert_region_has_expected_values, - i09_vgscienta_sequence_loader, + i09_vgscienta_test_sequence_loader, ) @pytest.fixture def sequence() -> I09VGScientaSequence: - return i09_vgscienta_sequence_loader() + return i09_vgscienta_test_sequence_loader() @pytest.fixture From 1264e9ac23a6ba8716dcc930991061223e72b70a Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Wed, 28 Jan 2026 16:37:08 +0000 Subject: [PATCH 12/19] Add comment linking bluesky issue --- src/dodal/devices/electron_analyser/base/base_detector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dodal/devices/electron_analyser/base/base_detector.py b/src/dodal/devices/electron_analyser/base/base_detector.py index 24de42b4459..9d6f732ac73 100644 --- a/src/dodal/devices/electron_analyser/base/base_detector.py +++ b/src/dodal/devices/electron_analyser/base/base_detector.py @@ -110,6 +110,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 ] From 7db944a8a7472f5ac38d3452e9e1b9667a049e6d Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 30 Jan 2026 13:42:57 +0000 Subject: [PATCH 13/19] Update outdated doc string --- src/dodal/devices/electron_analyser/base/base_driver_io.py | 2 -- 1 file changed, 2 deletions(-) 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 cf5ece6482b..25a3704d073 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. """ From 9e1108e270e2d68df690eba78e6e24847419f736 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Wed, 4 Feb 2026 13:57:04 +0000 Subject: [PATCH 14/19] Merge into main --- src/dodal/devices/beamlines/b07/__init__.py | 19 +++++++++++++++-- .../{ => beamlines}/b07_shared/__init__.py | 0 .../{ => beamlines}/b07_shared/enums.py | 0 src/dodal/devices/beamlines/i09/__init__.py | 21 +++++++++++++++++-- .../base/test_base_controller.py | 4 ++-- .../electron_analyser/helper_util/sequence.py | 4 ++-- 6 files changed, 40 insertions(+), 8 deletions(-) rename src/dodal/devices/{ => beamlines}/b07_shared/__init__.py (100%) rename src/dodal/devices/{ => beamlines}/b07_shared/enums.py (100%) diff --git a/src/dodal/devices/beamlines/b07/__init__.py b/src/dodal/devices/beamlines/b07/__init__.py index dd31225628c..8c7f6c984a8 100644 --- a/src/dodal/devices/beamlines/b07/__init__.py +++ b/src/dodal/devices/beamlines/b07/__init__.py @@ -1,3 +1,18 @@ -from dodal.devices.beamlines.b07.enums import Grating, LensMode, PsuMode +from .analyser import ( + B07ElectronAnalyserController, + B07SpecsAnalyserDriverIO, + B07SpecsRegion, + B07SpecsSequence, + Specs2DCMOS, +) +from .enums import Grating, LensMode -__all__ = ["Grating", "LensMode", "PsuMode"] +__all__ = [ + "B07ElectronAnalyserController", + "B07SpecsAnalyserDriverIO", + "B07SpecsRegion", + "B07SpecsSequence", + "Specs2DCMOS", + "Grating", + "LensMode", +] diff --git a/src/dodal/devices/b07_shared/__init__.py b/src/dodal/devices/beamlines/b07_shared/__init__.py similarity index 100% rename from src/dodal/devices/b07_shared/__init__.py rename to src/dodal/devices/beamlines/b07_shared/__init__.py diff --git a/src/dodal/devices/b07_shared/enums.py b/src/dodal/devices/beamlines/b07_shared/enums.py similarity index 100% rename from src/dodal/devices/b07_shared/enums.py rename to src/dodal/devices/beamlines/b07_shared/enums.py diff --git a/src/dodal/devices/beamlines/i09/__init__.py b/src/dodal/devices/beamlines/i09/__init__.py index cfb2a43d07d..ea53440d01a 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 ( + EW4000, + I09ElectronAnalyserController, + I09VGScientaAnalyserDriverIO, + I09VGScientaRegion, + I09VGScientaSequence, +) +from .enums import Grating, LensMode, PassEnergy, PsuMode -__all__ = ["Grating", "LensMode", "PsuMode", "PassEnergy"] +__all__ = [ + "EW4000", + "I09ElectronAnalyserController", + "I09VGScientaAnalyserDriverIO", + "I09VGScientaRegion", + "I09VGScientaSequence", + "Grating", + "LensMode", + "PsuMode", + "PassEnergy", +] diff --git a/tests/devices/electron_analyser/base/test_base_controller.py b/tests/devices/electron_analyser/base/test_base_controller.py index 2cc24a51545..94588b949d2 100644 --- a/tests/devices/electron_analyser/base/test_base_controller.py +++ b/tests/devices/electron_analyser/base/test_base_controller.py @@ -3,7 +3,8 @@ import pytest from ophyd_async.core import TriggerInfo, get_mock_put, init_devices -from dodal.devices.b07.analyser import Specs2DCMOS +from dodal.devices.beamlines.b07.analyser import Specs2DCMOS +from dodal.devices.beamlines.i09.analyser import EW4000 from dodal.devices.electron_analyser.base import ( AbstractAnalyserDriverIO, AbstractBaseRegion, @@ -14,7 +15,6 @@ ElectronAnalyserController, ) from dodal.devices.fast_shutter import DualFastShutter -from dodal.devices.i09.analyser import EW4000 from dodal.devices.selectable_source import SourceSelector from tests.devices.electron_analyser.helper_util import TEST_SEQUENCE_REGION_NAMES diff --git a/tests/devices/electron_analyser/helper_util/sequence.py b/tests/devices/electron_analyser/helper_util/sequence.py index 1169228d5bc..0245c0435bd 100644 --- a/tests/devices/electron_analyser/helper_util/sequence.py +++ b/tests/devices/electron_analyser/helper_util/sequence.py @@ -1,9 +1,9 @@ from dodal.common.data_util import load_json_file_to_class -from dodal.devices.b07.analyser import ( +from dodal.devices.beamlines.b07.analyser import ( B07SpecsAnalyserDriverIO, B07SpecsSequence, ) -from dodal.devices.i09.analyser import ( +from dodal.devices.beamlines.i09.analyser import ( I09VGScientaAnalyserDriverIO, I09VGScientaSequence, ) From e470d24734959bb4a065e029c3206cd151c2dfef Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 10 Feb 2026 12:08:39 +0000 Subject: [PATCH 15/19] Rename driver to drv --- src/dodal/devices/beamlines/b07/analyser.py | 4 ++-- src/dodal/devices/beamlines/b07_1/analyser.py | 4 ++-- src/dodal/devices/beamlines/i09/analyser.py | 4 ++-- src/dodal/devices/beamlines/i09_1/analyser.py | 4 ++-- src/dodal/devices/beamlines/p60/analyser.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dodal/devices/beamlines/b07/analyser.py b/src/dodal/devices/beamlines/b07/analyser.py index 46997bc5832..80fe3b491a3 100644 --- a/src/dodal/devices/beamlines/b07/analyser.py +++ b/src/dodal/devices/beamlines/b07/analyser.py @@ -34,6 +34,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - driver = B07SpecsAnalyserDriverIO(prefix) - controller = B07ElectronAnalyserController(driver, energy_source, shutter) + drv = B07SpecsAnalyserDriverIO(prefix) + controller = B07ElectronAnalyserController(drv, energy_source, shutter) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/b07_1/analyser.py b/src/dodal/devices/beamlines/b07_1/analyser.py index 42e95bc4fd9..dcb03873a8e 100644 --- a/src/dodal/devices/beamlines/b07_1/analyser.py +++ b/src/dodal/devices/beamlines/b07_1/analyser.py @@ -36,6 +36,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - driver = B071SpecsAnalyserDriverIO(prefix) - controller = B071ElectronAnalyserController(driver, energy_source, shutter) + drv = B071SpecsAnalyserDriverIO(prefix) + controller = B071ElectronAnalyserController(drv, energy_source, shutter) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/i09/analyser.py b/src/dodal/devices/beamlines/i09/analyser.py index 7a7359d46cd..e09064f71e0 100644 --- a/src/dodal/devices/beamlines/i09/analyser.py +++ b/src/dodal/devices/beamlines/i09/analyser.py @@ -51,8 +51,8 @@ def __init__( source_selector: SourceSelector, name: str = "", ): - driver = I09VGScientaAnalyserDriverIO(prefix) + drv = I09VGScientaAnalyserDriverIO(prefix) controller = I09ElectronAnalyserController( - driver, dual_energy_source, dual_fast_shutter, source_selector + drv, dual_energy_source, dual_fast_shutter, source_selector ) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/i09_1/analyser.py b/src/dodal/devices/beamlines/i09_1/analyser.py index d57016d61ac..3f6bd55904d 100644 --- a/src/dodal/devices/beamlines/i09_1/analyser.py +++ b/src/dodal/devices/beamlines/i09_1/analyser.py @@ -35,6 +35,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - driver = I091SpecsAnalyserDriverIO(prefix) - controller = I091ElectronAnalyserController(driver, energy_source, shutter) + drv = I091SpecsAnalyserDriverIO(prefix) + controller = I091ElectronAnalyserController(drv, energy_source, shutter) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/p60/analyser.py b/src/dodal/devices/beamlines/p60/analyser.py index 41c610c23b2..e9ce7d69e07 100644 --- a/src/dodal/devices/beamlines/p60/analyser.py +++ b/src/dodal/devices/beamlines/p60/analyser.py @@ -47,9 +47,9 @@ def __init__( energy_source: AbstractEnergySource, name: str = "", ): - driver = P60VGScientaAnalyserDriverIO(prefix) + drv = P60VGScientaAnalyserDriverIO(prefix) controller = P60ElectronAnalyserController( - driver, + drv, energy_source=energy_source, shutter=None, source_selector=None, From 95aae829ebceb888ba9601909a2de066249435ec Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 13 Feb 2026 15:55:06 +0000 Subject: [PATCH 16/19] Update the device names --- src/dodal/beamlines/b07.py | 6 +++--- src/dodal/beamlines/b07_1.py | 6 +++--- src/dodal/beamlines/i09.py | 6 +++--- src/dodal/beamlines/i09_1.py | 8 +++++--- src/dodal/beamlines/p60.py | 12 ++++++++--- src/dodal/devices/beamlines/b07/__init__.py | 20 +++++++++---------- src/dodal/devices/beamlines/b07/analyser.py | 18 +++++++++-------- src/dodal/devices/beamlines/b07_1/__init__.py | 20 +++++++++---------- src/dodal/devices/beamlines/b07_1/analyser.py | 18 ++++++++--------- src/dodal/devices/beamlines/i09/__init__.py | 4 ++-- src/dodal/devices/beamlines/i09/analyser.py | 2 +- src/dodal/devices/beamlines/i09_1/__init__.py | 4 ++-- src/dodal/devices/beamlines/i09_1/analyser.py | 2 +- src/dodal/devices/beamlines/p60/__init__.py | 4 ++-- src/dodal/devices/beamlines/p60/analyser.py | 2 +- 15 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/dodal/beamlines/b07.py b/src/dodal/beamlines/b07.py index ce350b4b732..cc5937c4520 100644 --- a/src/dodal/beamlines/b07.py +++ b/src/dodal/beamlines/b07.py @@ -1,7 +1,7 @@ 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 B07SampleManipulator52B, Grating, Specs2DCMOS +from dodal.devices.beamlines.b07 import B07BSpecs150, B07SampleManipulator52B, Grating from dodal.devices.electron_analyser.base import EnergySource from dodal.devices.motors import XYZPolarStage from dodal.devices.pgm import PlaneGratingMonochromator @@ -33,8 +33,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) -> Specs2DCMOS: - return Specs2DCMOS(f"{B_PREFIX.beamline_prefix}-EA-DET-01:CAM:", 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 5ba4c85290f..a5c76ae93e3 100644 --- a/src/dodal/beamlines/b07_1.py +++ b/src/dodal/beamlines/b07_1.py @@ -2,9 +2,9 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager from dodal.devices.beamlines.b07_1 import ( + B07CSpecs150, ChannelCutMonochromator, Grating, - SpecsPhoibos, ) from dodal.devices.electron_analyser.base import EnergySource from dodal.devices.motors import XYZPolarAzimuthStage @@ -42,8 +42,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) -> SpecsPhoibos: - return SpecsPhoibos( +def analyser(energy_source: EnergySource) -> B07CSpecs150: + return B07CSpecs150( prefix=f"{C_PREFIX.beamline_prefix}-EA-DET-01:CAM:", energy_source=energy_source, ) diff --git a/src/dodal/beamlines/i09.py b/src/dodal/beamlines/i09.py index 2f39974b080..792317ede93 100644 --- a/src/dodal/beamlines/i09.py +++ b/src/dodal/beamlines/i09.py @@ -4,7 +4,7 @@ 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 import EW4000 +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.fast_shutter import DualFastShutter, GenericFastShutter @@ -82,8 +82,8 @@ def ew4000( dual_energy_source: DualEnergySource, dual_fast_shutter: DualFastShutter, source_selector: SourceSelector, -) -> EW4000: - return EW4000( +) -> I09VGScientaEW4000: + return I09VGScientaEW4000( f"{I_PREFIX.beamline_prefix}-EA-DET-01:CAM:", dual_energy_source, dual_fast_shutter, diff --git a/src/dodal/beamlines/i09_1.py b/src/dodal/beamlines/i09_1.py index ba6de32e3cb..d42cc7fddde 100644 --- a/src/dodal/beamlines/i09_1.py +++ b/src/dodal/beamlines/i09_1.py @@ -1,7 +1,7 @@ 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 SpecsPhoibos225 +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.motors import XYZPolarAzimuthTiltStage @@ -32,8 +32,10 @@ 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) -> SpecsPhoibos225: - return SpecsPhoibos225(f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:", energy_source) +def analyser(energy_source: EnergySource) -> I091SpecsPhoibos225: + return I091SpecsPhoibos225( + f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:", energy_source + ) @devices.factory() diff --git a/src/dodal/beamlines/p60.py b/src/dodal/beamlines/p60.py index f034eebe324..76b0318bb73 100644 --- a/src/dodal/beamlines/p60.py +++ b/src/dodal/beamlines/p60.py @@ -1,6 +1,10 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager -from dodal.devices.beamlines.p60 import R4000, LabXraySource, LabXraySourceReadable +from dodal.devices.beamlines.p60 import ( + LabXraySource, + LabXraySourceReadable, + P60VGScientaR4000, +) from dodal.devices.electron_analyser.base import DualEnergySource from dodal.devices.selectable_source import SourceSelector from dodal.log import set_beamline as set_log_beamline @@ -50,5 +54,7 @@ def dual_energy_source( # Connect will work again after this work completed # https://jira.diamond.ac.uk/browse/P60-13 @devices.factory() -def r4000(dual_energy_source: DualEnergySource) -> R4000: - return R4000(f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", dual_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 e3a59cd2ec2..3f4f53514cb 100644 --- a/src/dodal/devices/beamlines/b07/__init__.py +++ b/src/dodal/devices/beamlines/b07/__init__.py @@ -1,19 +1,19 @@ from .analyser import ( - B07ElectronAnalyserController, - B07SpecsAnalyserDriverIO, - B07SpecsRegion, - B07SpecsSequence, - Specs2DCMOS, + B07BElectronAnalyserController, + B07BSpecs150, + B07BSpecsAnalyserDriverIO, + B07BSpecsRegion, + B07BSpecsSequence, ) from .b07_motors import B07SampleManipulator52B from .enums import Grating, LensMode __all__ = [ - "B07ElectronAnalyserController", - "B07SpecsAnalyserDriverIO", - "B07SpecsRegion", - "B07SpecsSequence", - "Specs2DCMOS", + "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 index 80fe3b491a3..6b5ba860e47 100644 --- a/src/dodal/devices/beamlines/b07/analyser.py +++ b/src/dodal/devices/beamlines/b07/analyser.py @@ -12,21 +12,23 @@ ) from dodal.devices.fast_shutter import FastShutter -B07SpecsRegion = SpecsRegion[LensMode, PsuMode] -B07SpecsSequence = SpecsSequence[LensMode, PsuMode] +B07BSpecsRegion = SpecsRegion[LensMode, PsuMode] +B07BSpecsSequence = SpecsSequence[LensMode, PsuMode] -class B07SpecsAnalyserDriverIO(SpecsAnalyserDriverIO): +class B07BSpecsAnalyserDriverIO(SpecsAnalyserDriverIO): def __init__(self, prefix: str, name: str = ""): super().__init__(prefix, LensMode, PsuMode, name) -B07ElectronAnalyserController = ElectronAnalyserController[ - B07SpecsAnalyserDriverIO, B07SpecsRegion +B07BElectronAnalyserController = ElectronAnalyserController[ + B07BSpecsAnalyserDriverIO, B07BSpecsRegion ] -class Specs2DCMOS(ElectronAnalyserDetector[B07SpecsAnalyserDriverIO, B07SpecsRegion]): +class B07BSpecs150( + ElectronAnalyserDetector[B07BSpecsAnalyserDriverIO, B07BSpecsRegion] +): def __init__( self, prefix: str, @@ -34,6 +36,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - drv = B07SpecsAnalyserDriverIO(prefix) - controller = B07ElectronAnalyserController(drv, energy_source, shutter) + drv = B07BSpecsAnalyserDriverIO(prefix) + controller = B07BElectronAnalyserController(drv, energy_source, shutter) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/b07_1/__init__.py b/src/dodal/devices/beamlines/b07_1/__init__.py index 3bb90c94c48..72fac821b2a 100644 --- a/src/dodal/devices/beamlines/b07_1/__init__.py +++ b/src/dodal/devices/beamlines/b07_1/__init__.py @@ -1,9 +1,9 @@ from .analyser import ( - B071ElectronAnalyserController, - B071SpecsAnalyserDriverIO, - B071SpecsRegion, - B071SpecsSequence, - SpecsPhoibos, + B07CElectronAnalyserController, + B07CSpecs150, + B07CSpecsAnalyserDriverIO, + B07CSpecsRegion, + B07CSpecsSequence, ) from .ccmc import ( ChannelCutMonochromator, @@ -12,11 +12,11 @@ from .enums import Grating, LensMode __all__ = [ - "B071ElectronAnalyserController", - "B071SpecsAnalyserDriverIO", - "B071SpecsRegion", - "B071SpecsSequence", - "SpecsPhoibos", + "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 index dcb03873a8e..e0436404c4a 100644 --- a/src/dodal/devices/beamlines/b07_1/analyser.py +++ b/src/dodal/devices/beamlines/b07_1/analyser.py @@ -12,22 +12,22 @@ ) from dodal.devices.fast_shutter import FastShutter -B071SpecsRegion = SpecsRegion[LensMode, PsuMode] -B071SpecsSequence = SpecsSequence[LensMode, PsuMode] +B07CSpecsRegion = SpecsRegion[LensMode, PsuMode] +B07CSpecsSequence = SpecsSequence[LensMode, PsuMode] -class B071SpecsAnalyserDriverIO(SpecsAnalyserDriverIO): +class B07CSpecsAnalyserDriverIO(SpecsAnalyserDriverIO): def __init__(self, prefix: str, name: str = ""): super().__init__(prefix, LensMode, PsuMode, name) -B071ElectronAnalyserController = ElectronAnalyserController[ - B071SpecsAnalyserDriverIO, B071SpecsRegion +B07CElectronAnalyserController = ElectronAnalyserController[ + B07CSpecsAnalyserDriverIO, B07CSpecsRegion ] -class SpecsPhoibos( - ElectronAnalyserDetector[B071SpecsAnalyserDriverIO, B071SpecsRegion] +class B07CSpecs150( + ElectronAnalyserDetector[B07CSpecsAnalyserDriverIO, B07CSpecsRegion] ): def __init__( self, @@ -36,6 +36,6 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - drv = B071SpecsAnalyserDriverIO(prefix) - controller = B071ElectronAnalyserController(drv, energy_source, shutter) + drv = B07CSpecsAnalyserDriverIO(prefix) + controller = B07CElectronAnalyserController(drv, energy_source, shutter) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/i09/__init__.py b/src/dodal/devices/beamlines/i09/__init__.py index ea53440d01a..458ffdd0790 100644 --- a/src/dodal/devices/beamlines/i09/__init__.py +++ b/src/dodal/devices/beamlines/i09/__init__.py @@ -1,16 +1,16 @@ from .analyser import ( - EW4000, I09ElectronAnalyserController, I09VGScientaAnalyserDriverIO, + I09VGScientaEW4000, I09VGScientaRegion, I09VGScientaSequence, ) from .enums import Grating, LensMode, PassEnergy, PsuMode __all__ = [ - "EW4000", "I09ElectronAnalyserController", "I09VGScientaAnalyserDriverIO", + "I09VGScientaEW4000", "I09VGScientaRegion", "I09VGScientaSequence", "Grating", diff --git a/src/dodal/devices/beamlines/i09/analyser.py b/src/dodal/devices/beamlines/i09/analyser.py index e09064f71e0..689fe017285 100644 --- a/src/dodal/devices/beamlines/i09/analyser.py +++ b/src/dodal/devices/beamlines/i09/analyser.py @@ -35,7 +35,7 @@ def __init__(self, prefix: str, name: str = ""): ] -class EW4000( +class I09VGScientaEW4000( ElectronAnalyserDetector[I09VGScientaAnalyserDriverIO, I09VGScientaRegion] ): """Implementation of VGScienta Electron Analyser. This model is unique for i09 diff --git a/src/dodal/devices/beamlines/i09_1/__init__.py b/src/dodal/devices/beamlines/i09_1/__init__.py index 7e509f537bc..fc3d25d1765 100644 --- a/src/dodal/devices/beamlines/i09_1/__init__.py +++ b/src/dodal/devices/beamlines/i09_1/__init__.py @@ -1,18 +1,18 @@ from .analyser import ( I091ElectronAnalyserController, I091SpecsAnalyserDriverIO, + I091SpecsPhoibos225, I091SpecsRegion, I091SpecsSequence, - SpecsPhoibos225, ) from .enums import LensMode, PsuMode __all__ = [ "I091ElectronAnalyserController", "I091SpecsAnalyserDriverIO", + "I091SpecsPhoibos225", "I091SpecsRegion", "I091SpecsSequence", - "SpecsPhoibos225", "LensMode", "PsuMode", ] diff --git a/src/dodal/devices/beamlines/i09_1/analyser.py b/src/dodal/devices/beamlines/i09_1/analyser.py index 3f6bd55904d..bbbafb4c223 100644 --- a/src/dodal/devices/beamlines/i09_1/analyser.py +++ b/src/dodal/devices/beamlines/i09_1/analyser.py @@ -25,7 +25,7 @@ def __init__(self, prefix: str, name: str = ""): ] -class SpecsPhoibos225( +class I091SpecsPhoibos225( ElectronAnalyserDetector[I091SpecsAnalyserDriverIO, I091SpecsRegion] ): def __init__( diff --git a/src/dodal/devices/beamlines/p60/__init__.py b/src/dodal/devices/beamlines/p60/__init__.py index 136ecb09ba8..0ccdeeaa15d 100644 --- a/src/dodal/devices/beamlines/p60/__init__.py +++ b/src/dodal/devices/beamlines/p60/__init__.py @@ -1,7 +1,7 @@ from .analyser import ( - R4000, P60ElectronAnalyserController, P60VGScientaAnalyserDriverIO, + P60VGScientaR4000, P60VGScientaSequence, P60VGScientnaRegion, ) @@ -9,9 +9,9 @@ from .lab_xray_source import LabXraySource, LabXraySourceReadable __all__ = [ - "R4000", "P60ElectronAnalyserController", "P60VGScientaAnalyserDriverIO", + "P60VGScientaR4000", "P60VGScientaSequence", "P60VGScientnaRegion", "LensMode", diff --git a/src/dodal/devices/beamlines/p60/analyser.py b/src/dodal/devices/beamlines/p60/analyser.py index e9ce7d69e07..e656becf990 100644 --- a/src/dodal/devices/beamlines/p60/analyser.py +++ b/src/dodal/devices/beamlines/p60/analyser.py @@ -33,7 +33,7 @@ def __init__(self, prefix: str, name: str = ""): ] -class R4000( +class P60VGScientaR4000( ElectronAnalyserDetector[P60VGScientaAnalyserDriverIO, P60VGScientnaRegion] ): """Lab specific analyser for P60 lab. It does not have any shutters connected so From 360419cf2418fc7c6c4af8ce17604834d1101583 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 13 Feb 2026 16:06:12 +0000 Subject: [PATCH 17/19] Fix tests --- .../base/test_base_controller.py | 24 +++++++++++-------- .../base/test_base_detector.py | 24 ++++++++++--------- .../base/test_base_driver_io.py | 4 ++-- .../base/test_base_region.py | 4 ++-- .../electron_analyser/helper_util/sequence.py | 10 ++++---- .../specs/test_specs_driver_io.py | 6 ++--- .../specs/test_specs_region.py | 8 +++---- 7 files changed, 43 insertions(+), 37 deletions(-) diff --git a/tests/devices/electron_analyser/base/test_base_controller.py b/tests/devices/electron_analyser/base/test_base_controller.py index 94588b949d2..839efedfa6f 100644 --- a/tests/devices/electron_analyser/base/test_base_controller.py +++ b/tests/devices/electron_analyser/base/test_base_controller.py @@ -3,8 +3,8 @@ import pytest from ophyd_async.core import TriggerInfo, get_mock_put, init_devices -from dodal.devices.beamlines.b07.analyser import Specs2DCMOS -from dodal.devices.beamlines.i09.analyser import EW4000 +from dodal.devices.beamlines.b07.analyser import B07BSpecs150 +from dodal.devices.beamlines.i09.analyser import I09VGScientaEW4000 from dodal.devices.electron_analyser.base import ( AbstractAnalyserDriverIO, AbstractBaseRegion, @@ -24,24 +24,28 @@ def ew4000( dual_energy_source: DualEnergySource, dual_fast_shutter: DualFastShutter, source_selector: SourceSelector, -) -> EW4000: +) -> I09VGScientaEW4000: with init_devices(mock=True): - ew4000 = EW4000("TEST:", dual_energy_source, dual_fast_shutter, source_selector) + ew4000 = I09VGScientaEW4000( + "TEST:", dual_energy_source, dual_fast_shutter, source_selector + ) return ew4000 @pytest.fixture -def specs_2dcmos(single_energy_source: EnergySource) -> Specs2DCMOS: +def specs150(single_energy_source: EnergySource) -> B07BSpecs150: with init_devices(mock=True): - specs_2dcmos = Specs2DCMOS("TEST:", single_energy_source) - return specs_2dcmos + specs150 = B07BSpecs150("TEST:", single_energy_source) + return specs150 -@pytest.fixture(params=["ew4000", "specs_2dcmos"]) +@pytest.fixture(params=["ew4000", "specs150"]) def analyser_controller( - request: pytest.FixtureRequest, ew4000: EW4000, specs_2dcmos: Specs2DCMOS + request: pytest.FixtureRequest, + ew4000: I09VGScientaEW4000, + specs150: B07BSpecs150, ): - detectors = [ew4000, specs_2dcmos] + detectors = [ew4000, specs150] for detector in detectors: if detector.name == request.param: return detector._controller diff --git a/tests/devices/electron_analyser/base/test_base_detector.py b/tests/devices/electron_analyser/base/test_base_detector.py index bc802c026db..bcd99120575 100644 --- a/tests/devices/electron_analyser/base/test_base_detector.py +++ b/tests/devices/electron_analyser/base/test_base_detector.py @@ -9,8 +9,8 @@ assert_reading, ) -from dodal.devices.beamlines.b07.analyser import Specs2DCMOS -from dodal.devices.beamlines.i09.analyser import EW4000 +from dodal.devices.beamlines.b07.analyser import B07BSpecs150 +from dodal.devices.beamlines.i09.analyser import I09VGScientaEW4000 from dodal.devices.electron_analyser.base import ( DualEnergySource, EnergySource, @@ -29,26 +29,28 @@ def ew4000( dual_energy_source: DualEnergySource, dual_fast_shutter: DualFastShutter, source_selector: SourceSelector, -) -> EW4000: +) -> I09VGScientaEW4000: with init_devices(mock=True): - ew4000 = EW4000("TEST:", dual_energy_source, dual_fast_shutter, source_selector) + ew4000 = I09VGScientaEW4000( + "TEST:", dual_energy_source, dual_fast_shutter, source_selector + ) return ew4000 @pytest.fixture -def specs_2dcmos(single_energy_source: EnergySource) -> Specs2DCMOS: +def specs150(single_energy_source: EnergySource) -> B07BSpecs150: with init_devices(mock=True): - specs_2dcmos = Specs2DCMOS("TEST:", single_energy_source) + specs150 = B07BSpecs150("TEST:", single_energy_source) # Needed for specs so we don't get division by zero error. - set_mock_value(specs_2dcmos.driver.slices, 1) - return specs_2dcmos + set_mock_value(specs150.driver.slices, 1) + return specs150 -@pytest.fixture(params=["ew4000", "specs_2dcmos"]) +@pytest.fixture(params=["ew4000", "specs150"]) def sim_detector( - request: pytest.FixtureRequest, ew4000: EW4000, specs_2dcmos: Specs2DCMOS + request: pytest.FixtureRequest, ew4000: I09VGScientaEW4000, specs150: B07BSpecs150 ) -> GenericElectronAnalyserDetector: - detectors = [ew4000, specs_2dcmos] + detectors = [ew4000, specs150] for detector in detectors: if detector.name == request.param: return detector 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 4a27bbfbf72..aff192b972f 100644 --- a/tests/devices/electron_analyser/base/test_base_driver_io.py +++ b/tests/devices/electron_analyser/base/test_base_driver_io.py @@ -4,12 +4,12 @@ from bluesky.utils import FailedStatus from ophyd_async.core import StrictEnum, init_devices -from dodal.devices.beamlines.b07.analyser import B07SpecsAnalyserDriverIO +from dodal.devices.beamlines.b07.analyser import B07BSpecsAnalyserDriverIO from dodal.devices.beamlines.i09.analyser import I09VGScientaAnalyserDriverIO from dodal.devices.electron_analyser.base import GenericAnalyserDriverIO -@pytest.fixture(params=[I09VGScientaAnalyserDriverIO, B07SpecsAnalyserDriverIO]) +@pytest.fixture(params=[I09VGScientaAnalyserDriverIO, B07BSpecsAnalyserDriverIO]) async def sim_driver(request: pytest.FixtureRequest) -> GenericAnalyserDriverIO: async with init_devices(mock=True): sim_driver = request.param(prefix="TEST:") diff --git a/tests/devices/electron_analyser/base/test_base_region.py b/tests/devices/electron_analyser/base/test_base_region.py index 198d6974d85..e6467e1e242 100644 --- a/tests/devices/electron_analyser/base/test_base_region.py +++ b/tests/devices/electron_analyser/base/test_base_region.py @@ -2,7 +2,7 @@ import pytest -from dodal.devices.beamlines.b07 import B07SpecsRegion +from dodal.devices.beamlines.b07 import B07BSpecsRegion from dodal.devices.beamlines.i09 import I09VGScientaRegion from dodal.devices.electron_analyser.base import ( AbstractBaseRegion, @@ -37,7 +37,7 @@ def expected_region_class( sequence: GenericSequence, ) -> type[AbstractBaseRegion]: if isinstance(sequence, SpecsSequence): - return B07SpecsRegion + return B07BSpecsRegion elif isinstance(sequence, VGScientaSequence): return I09VGScientaRegion raise TypeError(f"Unknown sequence type {type(sequence)}") diff --git a/tests/devices/electron_analyser/helper_util/sequence.py b/tests/devices/electron_analyser/helper_util/sequence.py index 0245c0435bd..73a5e2aab85 100644 --- a/tests/devices/electron_analyser/helper_util/sequence.py +++ b/tests/devices/electron_analyser/helper_util/sequence.py @@ -1,7 +1,7 @@ from dodal.common.data_util import load_json_file_to_class from dodal.devices.beamlines.b07.analyser import ( - B07SpecsAnalyserDriverIO, - B07SpecsSequence, + B07BSpecsAnalyserDriverIO, + B07BSpecsSequence, ) from dodal.devices.beamlines.i09.analyser import ( I09VGScientaAnalyserDriverIO, @@ -15,8 +15,8 @@ TEST_SEQUENCE_REGION_NAMES = ["New_Region", "New_Region1", "New_Region2"] -def b07_specs_test_sequence_loader() -> B07SpecsSequence: - return load_json_file_to_class(B07SpecsSequence, TEST_SPECS_SEQUENCE) +def b07_specs_test_sequence_loader() -> B07BSpecsSequence: + return load_json_file_to_class(B07BSpecsSequence, TEST_SPECS_SEQUENCE) def i09_vgscienta_test_sequence_loader() -> I09VGScientaSequence: @@ -25,6 +25,6 @@ def i09_vgscienta_test_sequence_loader() -> I09VGScientaSequence: # Map to know what function to load in sequence an analyser driver should use. DRIVER_TO_TEST_SEQUENCE = { - B07SpecsAnalyserDriverIO: b07_specs_test_sequence_loader(), + B07BSpecsAnalyserDriverIO: b07_specs_test_sequence_loader(), I09VGScientaAnalyserDriverIO: i09_vgscienta_test_sequence_loader(), } 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 f4e6cba04ac..223cf2434f9 100644 --- a/tests/devices/electron_analyser/specs/test_specs_driver_io.py +++ b/tests/devices/electron_analyser/specs/test_specs_driver_io.py @@ -13,7 +13,7 @@ ) from dodal.devices.beamlines.b07 import LensMode -from dodal.devices.beamlines.b07.analyser import B07SpecsAnalyserDriverIO +from dodal.devices.beamlines.b07.analyser import B07BSpecsAnalyserDriverIO from dodal.devices.beamlines.b07_shared import PsuMode from dodal.devices.electron_analyser.base import EnergyMode from dodal.devices.electron_analyser.base.base_enums import EnergyMode @@ -28,9 +28,9 @@ @pytest.fixture -async def sim_driver() -> B07SpecsAnalyserDriverIO: +async def sim_driver() -> B07BSpecsAnalyserDriverIO: async with init_devices(mock=True): - sim_driver = B07SpecsAnalyserDriverIO(prefix="TEST:") + sim_driver = B07BSpecsAnalyserDriverIO(prefix="TEST:") return sim_driver diff --git a/tests/devices/electron_analyser/specs/test_specs_region.py b/tests/devices/electron_analyser/specs/test_specs_region.py index 34e6656f7e6..bb3617e6610 100644 --- a/tests/devices/electron_analyser/specs/test_specs_region.py +++ b/tests/devices/electron_analyser/specs/test_specs_region.py @@ -2,7 +2,7 @@ import pytest -from dodal.devices.beamlines.b07 import B07SpecsSequence, LensMode +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 @@ -14,7 +14,7 @@ @pytest.fixture -def sequence() -> B07SpecsSequence: +def sequence() -> B07BSpecsSequence: return b07_specs_test_sequence_loader() @@ -82,7 +82,7 @@ def expected_region_values() -> list[dict[str, Any]]: def test_sequence_get_expected_enabled_region_names( - sequence: B07SpecsSequence, + sequence: B07BSpecsSequence, expected_enabled_region_names: list[str], ) -> None: assert sequence.get_enabled_region_names() == expected_enabled_region_names @@ -91,7 +91,7 @@ def test_sequence_get_expected_enabled_region_names( def test_file_loads_into_class_with_expected_values( - sequence: B07SpecsSequence, + sequence: B07BSpecsSequence, expected_region_values: list[dict[str, Any]], ) -> None: assert len(sequence.regions) == len(expected_region_values) From b3582661bfb3dd9f1a5d7411f5c701e7cb9e3408 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 13 Feb 2026 16:14:11 +0000 Subject: [PATCH 18/19] Remove temp drv variable from analyser --- src/dodal/devices/beamlines/b07/analyser.py | 5 +++-- src/dodal/devices/beamlines/b07_1/analyser.py | 5 +++-- src/dodal/devices/beamlines/i09/analyser.py | 6 ++++-- src/dodal/devices/beamlines/i09_1/analyser.py | 5 +++-- src/dodal/devices/beamlines/p60/analyser.py | 3 +-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/dodal/devices/beamlines/b07/analyser.py b/src/dodal/devices/beamlines/b07/analyser.py index 6b5ba860e47..93dbc5457f3 100644 --- a/src/dodal/devices/beamlines/b07/analyser.py +++ b/src/dodal/devices/beamlines/b07/analyser.py @@ -36,6 +36,7 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - drv = B07BSpecsAnalyserDriverIO(prefix) - controller = B07BElectronAnalyserController(drv, energy_source, shutter) + controller = B07BElectronAnalyserController( + B07BSpecsAnalyserDriverIO(prefix), energy_source, shutter + ) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/b07_1/analyser.py b/src/dodal/devices/beamlines/b07_1/analyser.py index e0436404c4a..bf36c0b9e8d 100644 --- a/src/dodal/devices/beamlines/b07_1/analyser.py +++ b/src/dodal/devices/beamlines/b07_1/analyser.py @@ -36,6 +36,7 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - drv = B07CSpecsAnalyserDriverIO(prefix) - controller = B07CElectronAnalyserController(drv, energy_source, shutter) + controller = B07CElectronAnalyserController( + B07CSpecsAnalyserDriverIO(prefix), energy_source, shutter + ) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/i09/analyser.py b/src/dodal/devices/beamlines/i09/analyser.py index 689fe017285..748933c24d2 100644 --- a/src/dodal/devices/beamlines/i09/analyser.py +++ b/src/dodal/devices/beamlines/i09/analyser.py @@ -51,8 +51,10 @@ def __init__( source_selector: SourceSelector, name: str = "", ): - drv = I09VGScientaAnalyserDriverIO(prefix) controller = I09ElectronAnalyserController( - drv, dual_energy_source, dual_fast_shutter, source_selector + I09VGScientaAnalyserDriverIO(prefix), + dual_energy_source, + dual_fast_shutter, + source_selector, ) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/i09_1/analyser.py b/src/dodal/devices/beamlines/i09_1/analyser.py index bbbafb4c223..bc7af96ae86 100644 --- a/src/dodal/devices/beamlines/i09_1/analyser.py +++ b/src/dodal/devices/beamlines/i09_1/analyser.py @@ -35,6 +35,7 @@ def __init__( shutter: FastShutter | None = None, name: str = "", ): - drv = I091SpecsAnalyserDriverIO(prefix) - controller = I091ElectronAnalyserController(drv, energy_source, shutter) + controller = I091ElectronAnalyserController( + I091SpecsAnalyserDriverIO(prefix), energy_source, shutter + ) super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/p60/analyser.py b/src/dodal/devices/beamlines/p60/analyser.py index e656becf990..a459a76c984 100644 --- a/src/dodal/devices/beamlines/p60/analyser.py +++ b/src/dodal/devices/beamlines/p60/analyser.py @@ -47,9 +47,8 @@ def __init__( energy_source: AbstractEnergySource, name: str = "", ): - drv = P60VGScientaAnalyserDriverIO(prefix) controller = P60ElectronAnalyserController( - drv, + P60VGScientaAnalyserDriverIO(prefix), energy_source=energy_source, shutter=None, source_selector=None, From ef6b7d342f467c63418e5d54688f4caa96f8786a Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Tue, 17 Feb 2026 14:21:40 +0000 Subject: [PATCH 19/19] Fix tests --- tests/devices/electron_analyser/base/test_base_region.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/devices/electron_analyser/base/test_base_region.py b/tests/devices/electron_analyser/base/test_base_region.py index eb1388488e3..a3c1a70705b 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.devices.beamlines.b07 import B07BSpecsRegion -from dodal.devices.beamlines.i09 import I09VGScientaRegion +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, @@ -21,7 +21,7 @@ ) -@pytest.fixture(params=[B07BSpecsRegion, I09VGScientaRegion]) +@pytest.fixture(params=[B07BSpecsSequence, I09VGScientaSequence]) def sequence(request: pytest.FixtureRequest) -> GenericSequence: return TEST_SEQUENCES[request.param]()