From a66614db51dd05607c048e3cdb1c1f330172163f Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Tue, 9 Apr 2024 13:26:54 +0000 Subject: [PATCH 1/7] added pimte area detector --- .../devices/areadetector/epics/__init__.py | 0 .../areadetector/epics/drivers/__init__.py | 0 .../epics/drivers/pimte1_driver.py | 28 ++++ .../areadetector/epics/pimte_controller.py | 66 ++++++++++ src/dodal/devices/areadetector/pimteAD.py | 45 +++++++ tests/devices/unit_tests/test_pimte.py | 80 ++++++++++++ tests/devices/unit_tests/test_pimte1Driver.py | 121 ++++++++++++++++++ .../unit_tests/test_pimteController.py | 55 ++++++++ 8 files changed, 395 insertions(+) create mode 100644 src/dodal/devices/areadetector/epics/__init__.py create mode 100644 src/dodal/devices/areadetector/epics/drivers/__init__.py create mode 100644 src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py create mode 100644 src/dodal/devices/areadetector/epics/pimte_controller.py create mode 100644 src/dodal/devices/areadetector/pimteAD.py create mode 100644 tests/devices/unit_tests/test_pimte.py create mode 100644 tests/devices/unit_tests/test_pimte1Driver.py create mode 100644 tests/devices/unit_tests/test_pimteController.py diff --git a/src/dodal/devices/areadetector/epics/__init__.py b/src/dodal/devices/areadetector/epics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/dodal/devices/areadetector/epics/drivers/__init__.py b/src/dodal/devices/areadetector/epics/drivers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py b/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py new file mode 100644 index 0000000000..074deb1263 --- /dev/null +++ b/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py @@ -0,0 +1,28 @@ +from enum import Enum + +from ophyd_async.epics.areadetector.drivers.ad_base import ADBase +from ophyd_async.epics.areadetector.utils import ad_r, ad_rw +from ophyd_async.epics.signal import epics_signal_rw + + +class Pimte1Driver(ADBase): + def __init__(self, prefix: str) -> None: + self.trigger_mode = ad_rw(Pimte1Driver.TriggerMode, prefix + "TriggerMode") + self.initialize = ad_rw(int, prefix + "Initialize") + self.set_temperture = epics_signal_rw(float, prefix + "SetTemperature") + self.read_backtemperture = ad_r(float, prefix + "MeasuredTemperature") + self.speed = ad_rw(Pimte1Driver.SpeedMode, prefix + "SpeedSelection") + super().__init__(prefix) + + class SpeedMode(str, Enum): + adc_50Khz = "0: 50 KHz - 20000 ns" + adc_100Khz = "1: 100 kHz - 10000 ns" + adc_200Khz = "2: 200 kHz - 5000 ns" + adc_500Khz = "3: 500 kHz - 2000 ns" + adc_1Mhz = "4: 1 MHz - 1000 ns" + adc_2Mhz = "5: 2 MHz - 500 ns" + + class TriggerMode(str, Enum): + internal = "Free Run" + ext_trigger = "Ext Trigger" + bulb_mode = "Bulb Mode" diff --git a/src/dodal/devices/areadetector/epics/pimte_controller.py b/src/dodal/devices/areadetector/epics/pimte_controller.py new file mode 100644 index 0000000000..e56433e33b --- /dev/null +++ b/src/dodal/devices/areadetector/epics/pimte_controller.py @@ -0,0 +1,66 @@ +import asyncio +from typing import Optional, Set # , Set + +from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger +from ophyd_async.epics.areadetector.drivers.ad_base import ( + DEFAULT_GOOD_STATES, + DetectorState, + start_acquiring_driver_and_ensure_status, +) +from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record + +from drivers.pimte1_driver import Pimte1Driver + +TRIGGER_MODE = { + DetectorTrigger.internal: Pimte1Driver.TriggerMode.internal, + DetectorTrigger.constant_gate: Pimte1Driver.TriggerMode.ext_trigger, + DetectorTrigger.variable_gate: Pimte1Driver.TriggerMode.ext_trigger, +} + + +class PimteController(DetectorControl): + def __init__( + self, + driver: Pimte1Driver, + good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), + ) -> None: + self.driver = driver + self.good_states = good_states + + def get_deadtime(self, exposure: float) -> float: + return exposure + 0.1 + + async def _process_setting(self) -> None: + await self.driver.initialize.set(1) + + async def set_temperature(self, temperature: float) -> None: + await self.driver.set_temperture.set(temperature) + await self._process_setting() + + async def set_speed(self, speed: Pimte1Driver.SpeedMode) -> None: + await self.driver.speed.set(speed) + await self._process_setting() + + async def arm( + self, + num: int = 1, + trigger: DetectorTrigger = DetectorTrigger.internal, + exposure: Optional[float] = None, + ) -> AsyncStatus: + + funcs = [ + self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.image_mode.set(ImageMode.multiple), + self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), + ] + if exposure is not None: + funcs.append(self.driver.acquire_time.set(exposure)) + + await asyncio.gather(*funcs) + await self._process_setting() + return await start_acquiring_driver_and_ensure_status( + self.driver, good_states=self.good_states + ) + + async def disarm(self): + await stop_busy_record(self.driver.acquire, False, timeout=1) diff --git a/src/dodal/devices/areadetector/pimteAD.py b/src/dodal/devices/areadetector/pimteAD.py new file mode 100644 index 0000000000..372ae39742 --- /dev/null +++ b/src/dodal/devices/areadetector/pimteAD.py @@ -0,0 +1,45 @@ +from typing import Sequence + +from bluesky.protocols import Hints +from ophyd_async.core import DirectoryProvider, SignalR, StandardDetector +from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider +from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF, NDPluginStats + +from epics.drivers.pimte1_driver import Pimte1Driver +from epics.pimte_controller import PimteController + + +class HDFStatsPimte(StandardDetector): + _controller: PimteController + _writer: HDFWriter + + def __init__( + self, + prefix: str, + directory_provider: DirectoryProvider, + name: str, + config_sigs: Sequence[SignalR] = (), + **scalar_sigs: str, + ): + self.drv = Pimte1Driver(prefix + "CAM:") + self.hdf = NDFileHDF(prefix + "HDF5:") + self.stats = NDPluginStats(prefix + "STAT:") + # taken from i22 but this does nothing atm + + super().__init__( + PimteController(self.drv), + HDFWriter( + self.hdf, + directory_provider, + lambda: self.name, + ADBaseShapeProvider(self.drv), + sum="StatsTotal", + **scalar_sigs, + ), + config_sigs=config_sigs, + name=name, + ) + + @property + def hints(self) -> Hints: + return self.writer.hints diff --git a/tests/devices/unit_tests/test_pimte.py b/tests/devices/unit_tests/test_pimte.py new file mode 100644 index 0000000000..6a88db1638 --- /dev/null +++ b/tests/devices/unit_tests/test_pimte.py @@ -0,0 +1,80 @@ +import bluesky.plan_stubs as bps +import pytest +from bluesky import RunEngine +from bluesky.utils import new_uid +from ophyd_async.core import DeviceCollector, StaticDirectoryProvider, set_sim_value + +from dodal.devices.areadetector.pimteAD import HDFStatsPimte + +CURRENT_DIRECTORY = "." # str(Path(__file__).parent) + + +async def make_detector(prefix: str = "") -> HDFStatsPimte: + dp = StaticDirectoryProvider(CURRENT_DIRECTORY, f"test-{new_uid()}") + + async with DeviceCollector(sim=True): + detector = HDFStatsPimte(prefix, dp, "pimte") + return detector + + +def count_sim(det: HDFStatsPimte, times: int = 1): + """Test plan to do the equivalent of bp.count for a sim detector.""" + + yield from bps.stage_all(det) + yield from bps.open_run() + yield from bps.declare_stream(det, name="primary", collect=False) + for _ in range(times): + read_value = yield from bps.rd(det._writer.hdf.num_captured) + yield from bps.trigger(det, wait=False, group="wait_for_trigger") + + yield from bps.sleep(0.001) + set_sim_value(det._writer.hdf.num_captured, read_value + 1) + + yield from bps.wait(group="wait_for_trigger") + yield from bps.create() + yield from bps.read(det) + yield from bps.save() + + yield from bps.close_run() + yield from bps.unstage_all(det) + + +@pytest.fixture +async def single_detector(RE: RunEngine) -> HDFStatsPimte: + detector = await make_detector(prefix="TEST") + + set_sim_value(detector._controller.driver.array_size_x, 10) + set_sim_value(detector._controller.driver.array_size_y, 20) + set_sim_value(detector.hdf.file_path_exists, True) + set_sim_value(detector._writer.hdf.num_captured, 0) + return detector + + +async def test_pimte(RE: RunEngine, single_detector: HDFStatsPimte): + names = [] + docs = [] + RE.subscribe(lambda name, _: names.append(name)) + RE.subscribe(lambda _, doc: docs.append(doc)) + + RE(count_sim(single_detector)) + writer = single_detector._writer + + assert ( + await writer.hdf.file_path.get_value() + == writer._directory_provider().root.as_posix() + ) + + assert (await writer.hdf.file_name.get_value()).startswith( + writer._directory_provider().prefix + ) + + assert names == [ + "start", + "descriptor", + "stream_resource", + "stream_resource", + "stream_datum", + "stream_datum", + "event", + "stop", + ] diff --git a/tests/devices/unit_tests/test_pimte1Driver.py b/tests/devices/unit_tests/test_pimte1Driver.py new file mode 100644 index 0000000000..3cd13c972c --- /dev/null +++ b/tests/devices/unit_tests/test_pimte1Driver.py @@ -0,0 +1,121 @@ +import pytest +from ophyd_async.core import DeviceCollector + +from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver + +# Long enough for multiple asyncio event loop cycles to run so +# all the tasks have a chance to run +A_BIT = 0.001 + + +@pytest.fixture +async def sim_pimte_driver(): + async with DeviceCollector(sim=True): + sim_pimte_driver = Pimte1Driver("BLxxI-MO-TABLE-01:X") + # Signals connected here + + assert sim_pimte_driver.name == "sim_pimte_driver" + yield sim_pimte_driver + + +async def test_sim_pimte_driver(sim_pimte_driver: Pimte1Driver) -> None: + pass + + +""" +async def test_motor_moving_well(sim_motor: motor.Motor) -> None: + set_sim_put_proceeds(sim_motor.setpoint, False) + s = sim_motor.set(0.55) + watcher = Mock() + s.watch(watcher) + done = Mock() + s.add_callback(done) + await asyncio.sleep(A_BIT) + assert watcher.call_count == 1 + assert watcher.call_args == call( + name="sim_motor", + current=0.0, + initial=0.0, + target=0.55, + unit="mm", + precision=3, + time_elapsed=pytest.approx(0.0, abs=0.05), + ) + watcher.reset_mock() + assert 0.55 == await sim_motor.setpoint.get_value() + assert not s.done + await asyncio.sleep(0.1) + set_sim_value(sim_motor.readback, 0.1) + assert watcher.call_count == 1 + assert watcher.call_args == call( + name="sim_motor", + current=0.1, + initial=0.0, + target=0.55, + unit="mm", + precision=3, + time_elapsed=pytest.approx(0.1, abs=0.05), + ) + set_sim_put_proceeds(sim_motor.setpoint, True) + await asyncio.sleep(A_BIT) + assert s.done + done.assert_called_once_with(s) + + +async def test_motor_moving_stopped(sim_motor: motor.Motor): + set_sim_put_proceeds(sim_motor.setpoint, False) + s = sim_motor.set(1.5) + s.add_callback(Mock()) + await asyncio.sleep(0.2) + assert not s.done + await sim_motor.stop() + set_sim_put_proceeds(sim_motor.setpoint, True) + await asyncio.sleep(A_BIT) + assert s.done + assert s.success is False + + +async def test_read_motor(sim_motor: motor.Motor): + sim_motor.stage() + assert (await sim_motor.read())["sim_motor"]["value"] == 0.0 + assert (await sim_motor.describe())["sim_motor"][ + "source" + ] == "sim://BLxxI-MO-TABLE-01:X.RBV" + assert (await sim_motor.read_configuration())["sim_motor-velocity"]["value"] == 1 + assert (await sim_motor.describe_configuration())["sim_motor-units"]["shape"] == [] + set_sim_value(sim_motor.readback, 0.5) + assert (await sim_motor.read())["sim_motor"]["value"] == 0.5 + sim_motor.unstage() + # Check we can still read and describe when not staged + set_sim_value(sim_motor.readback, 0.1) + assert (await sim_motor.read())["sim_motor"]["value"] == 0.1 + assert await sim_motor.describe() + + +async def test_set_velocity(sim_motor: motor.Motor) -> None: + v = sim_motor.velocity + assert (await v.describe())["sim_motor-velocity"][ + "source" + ] == "sim://BLxxI-MO-TABLE-01:X.VELO" + q: asyncio.Queue[Dict[str, Reading]] = asyncio.Queue() + v.subscribe(q.put_nowait) + assert (await q.get())["sim_motor-velocity"]["value"] == 1.0 + await v.set(2.0) + assert (await q.get())["sim_motor-velocity"]["value"] == 2.0 + v.clear_sub(q.put_nowait) + await v.set(3.0) + assert (await v.read())["sim_motor-velocity"]["value"] == 3.0 + assert q.empty() + + +def test_motor_in_re(sim_motor: motor.Motor, RE: RunEngine) -> None: + sim_motor.move(0) + + def my_plan(): + sim_motor.move(0) + return + yield + + with pytest.raises(RuntimeError, match="Will deadlock run engine if run in a plan"): + RE(my_plan()) +""" diff --git a/tests/devices/unit_tests/test_pimteController.py b/tests/devices/unit_tests/test_pimteController.py new file mode 100644 index 0000000000..12b161208b --- /dev/null +++ b/tests/devices/unit_tests/test_pimteController.py @@ -0,0 +1,55 @@ +from unittest.mock import patch + +import pytest +from ophyd_async.core import DetectorTrigger, DeviceCollector +from ophyd_async.epics.areadetector.controllers import ( + ADSimController, +) +from ophyd_async.epics.areadetector.drivers import ADBase +from ophyd_async.epics.areadetector.utils import ImageMode + +from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver +from dodal.devices.areadetector.epics.pimte_controller import PimteController + + +@pytest.fixture +async def pimte(RE) -> PimteController: + async with DeviceCollector(sim=True): + drv = Pimte1Driver("DRIVER:") + controller = PimteController(drv) + + return controller + + +@pytest.fixture +async def ad(RE) -> ADSimController: + async with DeviceCollector(sim=True): + drv = ADBase("DRIVER:") + controller = ADSimController(drv) + + return controller + + +async def test_pimte_controller(RE, pimte: PimteController): + with patch("ophyd_async.core.signal.wait_for_value", return_value=None): + await pimte.arm(num=1, exposure=0.002, trigger=DetectorTrigger.internal) + + driver = pimte.driver + + assert await driver.num_images.get_value() == 1 + assert await driver.image_mode.get_value() == ImageMode.multiple + assert await driver.trigger_mode.get_value() == Pimte1Driver.TriggerMode.internal + assert await driver.acquire.get_value() is True + assert await driver.acquire_time.get_value() == 0.002 + assert pimte.get_deadtime(2) == 2 + 0.1 + + with patch( + "ophyd_async.epics.areadetector.utils.wait_for_value", return_value=None + ): + await pimte.disarm() + await pimte.set_temperature(20) + await pimte.set_speed(driver.SpeedMode.adc_200Khz) + assert await driver.set_temperture.get_value() == 20 + assert await driver.speed.get_value() == driver.SpeedMode.adc_200Khz + + assert await driver.acquire.get_value() is False From c491103589dfbac06cb4e8428f194bdff37fda0a Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Tue, 9 Apr 2024 13:40:39 +0000 Subject: [PATCH 2/7] add pimte detector and test --- src/dodal/devices/areadetector/epics/pimte_controller.py | 3 +-- src/dodal/devices/areadetector/pimteAD.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/dodal/devices/areadetector/epics/pimte_controller.py b/src/dodal/devices/areadetector/epics/pimte_controller.py index e56433e33b..847ea60448 100644 --- a/src/dodal/devices/areadetector/epics/pimte_controller.py +++ b/src/dodal/devices/areadetector/epics/pimte_controller.py @@ -9,7 +9,7 @@ ) from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record -from drivers.pimte1_driver import Pimte1Driver +from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver TRIGGER_MODE = { DetectorTrigger.internal: Pimte1Driver.TriggerMode.internal, @@ -47,7 +47,6 @@ async def arm( trigger: DetectorTrigger = DetectorTrigger.internal, exposure: Optional[float] = None, ) -> AsyncStatus: - funcs = [ self.driver.num_images.set(999_999 if num == 0 else num), self.driver.image_mode.set(ImageMode.multiple), diff --git a/src/dodal/devices/areadetector/pimteAD.py b/src/dodal/devices/areadetector/pimteAD.py index 372ae39742..1b7432ed1f 100644 --- a/src/dodal/devices/areadetector/pimteAD.py +++ b/src/dodal/devices/areadetector/pimteAD.py @@ -5,8 +5,8 @@ from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF, NDPluginStats -from epics.drivers.pimte1_driver import Pimte1Driver -from epics.pimte_controller import PimteController +from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver +from dodal.devices.areadetector.epics.pimte_controller import PimteController class HDFStatsPimte(StandardDetector): From c4ca0e8ccf08effbdc38543fe4e3fa9ac1305b63 Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Tue, 9 Apr 2024 14:06:07 +0000 Subject: [PATCH 3/7] add test --- src/dodal/devices/areadetector/pimteAD.py | 3 +-- tests/devices/unit_tests/test_pimte.py | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/dodal/devices/areadetector/pimteAD.py b/src/dodal/devices/areadetector/pimteAD.py index 1b7432ed1f..9ece39ad89 100644 --- a/src/dodal/devices/areadetector/pimteAD.py +++ b/src/dodal/devices/areadetector/pimteAD.py @@ -1,5 +1,4 @@ from typing import Sequence - from bluesky.protocols import Hints from ophyd_async.core import DirectoryProvider, SignalR, StandardDetector from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider @@ -42,4 +41,4 @@ def __init__( @property def hints(self) -> Hints: - return self.writer.hints + return self._writer.hints diff --git a/tests/devices/unit_tests/test_pimte.py b/tests/devices/unit_tests/test_pimte.py index 6a88db1638..08cc09da0b 100644 --- a/tests/devices/unit_tests/test_pimte.py +++ b/tests/devices/unit_tests/test_pimte.py @@ -1,6 +1,6 @@ import bluesky.plan_stubs as bps import pytest -from bluesky import RunEngine +from bluesky.run_engine import RunEngine from bluesky.utils import new_uid from ophyd_async.core import DeviceCollector, StaticDirectoryProvider, set_sim_value @@ -64,10 +64,6 @@ async def test_pimte(RE: RunEngine, single_detector: HDFStatsPimte): == writer._directory_provider().root.as_posix() ) - assert (await writer.hdf.file_name.get_value()).startswith( - writer._directory_provider().prefix - ) - assert names == [ "start", "descriptor", From 104d0e8d070827468c2d007918f170b2ce35dfba Mon Sep 17 00:00:00 2001 From: Raymond Fan Date: Fri, 12 Apr 2024 10:55:05 +0100 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: DiamondJoseph <53935796+DiamondJoseph@users.noreply.github.com> --- src/dodal/devices/areadetector/pimteAD.py | 5 +---- tests/devices/unit_tests/test_pimte.py | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/dodal/devices/areadetector/pimteAD.py b/src/dodal/devices/areadetector/pimteAD.py index 9ece39ad89..1bd9329ecb 100644 --- a/src/dodal/devices/areadetector/pimteAD.py +++ b/src/dodal/devices/areadetector/pimteAD.py @@ -22,17 +22,14 @@ def __init__( ): self.drv = Pimte1Driver(prefix + "CAM:") self.hdf = NDFileHDF(prefix + "HDF5:") - self.stats = NDPluginStats(prefix + "STAT:") - # taken from i22 but this does nothing atm - super().__init__( + super().__init__( PimteController(self.drv), HDFWriter( self.hdf, directory_provider, lambda: self.name, ADBaseShapeProvider(self.drv), - sum="StatsTotal", **scalar_sigs, ), config_sigs=config_sigs, diff --git a/tests/devices/unit_tests/test_pimte.py b/tests/devices/unit_tests/test_pimte.py index 08cc09da0b..c5d56f9ab2 100644 --- a/tests/devices/unit_tests/test_pimte.py +++ b/tests/devices/unit_tests/test_pimte.py @@ -40,9 +40,9 @@ def count_sim(det: HDFStatsPimte, times: int = 1): @pytest.fixture -async def single_detector(RE: RunEngine) -> HDFStatsPimte: - detector = await make_detector(prefix="TEST") - +async def single_detector(RE: RunEngine, tmp_directory_provider: StaticDirectoryProvider) -> HDFStatsPimte: + async with DeviceCollector(sim=True): + detector = HDFStatsPimte(prefix, tmp_directory_provider, "pimte") set_sim_value(detector._controller.driver.array_size_x, 10) set_sim_value(detector._controller.driver.array_size_y, 20) set_sim_value(detector.hdf.file_path_exists, True) From 608e9bab59bc2517e96c5279be60ff7049626a16 Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Fri, 12 Apr 2024 10:05:47 +0000 Subject: [PATCH 5/7] fixing dead time --- .../epics/drivers/pimte1_driver.py | 30 +++++++++++-------- .../areadetector/epics/pimte_controller.py | 12 ++++---- tests/devices/unit_tests/test_pimte.py | 15 +++------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py b/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py index 074deb1263..3c1d3de7bc 100644 --- a/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py +++ b/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py @@ -3,26 +3,30 @@ from ophyd_async.epics.areadetector.drivers.ad_base import ADBase from ophyd_async.epics.areadetector.utils import ad_r, ad_rw from ophyd_async.epics.signal import epics_signal_rw +""" +Driver for pi-mite 3 CCD + +""" class Pimte1Driver(ADBase): def __init__(self, prefix: str) -> None: - self.trigger_mode = ad_rw(Pimte1Driver.TriggerMode, prefix + "TriggerMode") + self.trigger_mode = ad_rw(TriggerMode, prefix + "TriggerMode") self.initialize = ad_rw(int, prefix + "Initialize") self.set_temperture = epics_signal_rw(float, prefix + "SetTemperature") self.read_backtemperture = ad_r(float, prefix + "MeasuredTemperature") - self.speed = ad_rw(Pimte1Driver.SpeedMode, prefix + "SpeedSelection") + self.speed = ad_rw(SpeedMode, prefix + "SpeedSelection") super().__init__(prefix) - class SpeedMode(str, Enum): - adc_50Khz = "0: 50 KHz - 20000 ns" - adc_100Khz = "1: 100 kHz - 10000 ns" - adc_200Khz = "2: 200 kHz - 5000 ns" - adc_500Khz = "3: 500 kHz - 2000 ns" - adc_1Mhz = "4: 1 MHz - 1000 ns" - adc_2Mhz = "5: 2 MHz - 500 ns" +class SpeedMode(str, Enum): + adc_50Khz = "0: 50 KHz - 20000 ns" + adc_100Khz = "1: 100 kHz - 10000 ns" + adc_200Khz = "2: 200 kHz - 5000 ns" + adc_500Khz = "3: 500 kHz - 2000 ns" + adc_1Mhz = "4: 1 MHz - 1000 ns" + adc_2Mhz = "5: 2 MHz - 500 ns" - class TriggerMode(str, Enum): - internal = "Free Run" - ext_trigger = "Ext Trigger" - bulb_mode = "Bulb Mode" +class TriggerMode(str, Enum): + internal = "Free Run" + ext_trigger = "Ext Trigger" + bulb_mode = "Bulb Mode" diff --git a/src/dodal/devices/areadetector/epics/pimte_controller.py b/src/dodal/devices/areadetector/epics/pimte_controller.py index 847ea60448..4cfe2b5e66 100644 --- a/src/dodal/devices/areadetector/epics/pimte_controller.py +++ b/src/dodal/devices/areadetector/epics/pimte_controller.py @@ -9,12 +9,12 @@ ) from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record -from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver +from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver, TriggerMode, SpeedMode TRIGGER_MODE = { - DetectorTrigger.internal: Pimte1Driver.TriggerMode.internal, - DetectorTrigger.constant_gate: Pimte1Driver.TriggerMode.ext_trigger, - DetectorTrigger.variable_gate: Pimte1Driver.TriggerMode.ext_trigger, + DetectorTrigger.internal: TriggerMode.internal, + DetectorTrigger.constant_gate: TriggerMode.ext_trigger, + DetectorTrigger.variable_gate: TriggerMode.ext_trigger, } @@ -28,7 +28,7 @@ def __init__( self.good_states = good_states def get_deadtime(self, exposure: float) -> float: - return exposure + 0.1 + return 2.4e-5 async def _process_setting(self) -> None: await self.driver.initialize.set(1) @@ -37,7 +37,7 @@ async def set_temperature(self, temperature: float) -> None: await self.driver.set_temperture.set(temperature) await self._process_setting() - async def set_speed(self, speed: Pimte1Driver.SpeedMode) -> None: + async def set_speed(self, speed: SpeedMode) -> None: await self.driver.speed.set(speed) await self._process_setting() diff --git a/tests/devices/unit_tests/test_pimte.py b/tests/devices/unit_tests/test_pimte.py index 08cc09da0b..a7bf18bb66 100644 --- a/tests/devices/unit_tests/test_pimte.py +++ b/tests/devices/unit_tests/test_pimte.py @@ -3,19 +3,12 @@ from bluesky.run_engine import RunEngine from bluesky.utils import new_uid from ophyd_async.core import DeviceCollector, StaticDirectoryProvider, set_sim_value - +from pathlib import Path from dodal.devices.areadetector.pimteAD import HDFStatsPimte -CURRENT_DIRECTORY = "." # str(Path(__file__).parent) - - -async def make_detector(prefix: str = "") -> HDFStatsPimte: - dp = StaticDirectoryProvider(CURRENT_DIRECTORY, f"test-{new_uid()}") - - async with DeviceCollector(sim=True): - detector = HDFStatsPimte(prefix, dp, "pimte") - return detector - +@pytest.fixture +def tmp_directory_provider(tmp_path: Path) -> StaticDirectoryProvider: + return StaticDirectoryProvider(tmp_path) def count_sim(det: HDFStatsPimte, times: int = 1): """Test plan to do the equivalent of bp.count for a sim detector.""" From ef17e6046b06839dd204f4a98cdcb7848a42c61d Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Fri, 12 Apr 2024 10:19:01 +0000 Subject: [PATCH 6/7] bring trigger in line with standard and moved test tmp file dir to conftest --- tests/conftest.py | 7 ++ tests/devices/unit_tests/test_pimte.py | 8 +- tests/devices/unit_tests/test_pimte1Driver.py | 105 +----------------- .../unit_tests/test_pimteController.py | 8 +- 4 files changed, 16 insertions(+), 112 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 54ccd42836..5918846b9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,12 +10,15 @@ import pytest from bluesky.run_engine import RunEngine from ophyd.sim import make_fake_device +from ophyd_async.core import StaticDirectoryProvider from dodal.beamlines import beamline_utils, i03 from dodal.devices.focusing_mirror import VFMMirrorVoltages from dodal.log import LOGGER, GELFTCPHandler, set_up_all_logging_handlers from dodal.utils import make_all_devices +from pathlib import Path + MOCK_DAQ_CONFIG_PATH = "tests/devices/unit_tests/test_daq_configuration" mock_paths = [ ("DAQ_CONFIGURATION_PATH", MOCK_DAQ_CONFIG_PATH), @@ -35,6 +38,10 @@ def mock_beamline_module_filepaths(bl_name, bl_module): [bl_module.__setattr__(attr[0], attr[1]) for attr in mock_attributes] +@pytest.fixture +def tmp_directory_provider(tmp_path: Path) -> StaticDirectoryProvider: + return StaticDirectoryProvider(tmp_path) + @pytest.fixture(scope="function") def module_and_devices_for_beamline(request): beamline = request.param diff --git a/tests/devices/unit_tests/test_pimte.py b/tests/devices/unit_tests/test_pimte.py index 0cd8948e20..2e900bc75e 100644 --- a/tests/devices/unit_tests/test_pimte.py +++ b/tests/devices/unit_tests/test_pimte.py @@ -3,12 +3,9 @@ from bluesky.run_engine import RunEngine from bluesky.utils import new_uid from ophyd_async.core import DeviceCollector, StaticDirectoryProvider, set_sim_value -from pathlib import Path + from dodal.devices.areadetector.pimteAD import HDFStatsPimte -@pytest.fixture -def tmp_directory_provider(tmp_path: Path) -> StaticDirectoryProvider: - return StaticDirectoryProvider(tmp_path) def count_sim(det: HDFStatsPimte, times: int = 1): """Test plan to do the equivalent of bp.count for a sim detector.""" @@ -35,7 +32,8 @@ def count_sim(det: HDFStatsPimte, times: int = 1): @pytest.fixture async def single_detector(RE: RunEngine, tmp_directory_provider: StaticDirectoryProvider) -> HDFStatsPimte: async with DeviceCollector(sim=True): - detector = HDFStatsPimte(prefix, tmp_directory_provider, "pimte") + detector = HDFStatsPimte("prefix", tmp_directory_provider, "pimte") + set_sim_value(detector._controller.driver.array_size_x, 10) set_sim_value(detector._controller.driver.array_size_y, 20) set_sim_value(detector.hdf.file_path_exists, True) diff --git a/tests/devices/unit_tests/test_pimte1Driver.py b/tests/devices/unit_tests/test_pimte1Driver.py index 3cd13c972c..cbd8a3e034 100644 --- a/tests/devices/unit_tests/test_pimte1Driver.py +++ b/tests/devices/unit_tests/test_pimte1Driver.py @@ -11,111 +11,10 @@ @pytest.fixture async def sim_pimte_driver(): async with DeviceCollector(sim=True): - sim_pimte_driver = Pimte1Driver("BLxxI-MO-TABLE-01:X") + sim_pimte_driver = Pimte1Driver("BLxxI-A-DET-03:CAM") # Signals connected here - - assert sim_pimte_driver.name == "sim_pimte_driver" yield sim_pimte_driver async def test_sim_pimte_driver(sim_pimte_driver: Pimte1Driver) -> None: - pass - - -""" -async def test_motor_moving_well(sim_motor: motor.Motor) -> None: - set_sim_put_proceeds(sim_motor.setpoint, False) - s = sim_motor.set(0.55) - watcher = Mock() - s.watch(watcher) - done = Mock() - s.add_callback(done) - await asyncio.sleep(A_BIT) - assert watcher.call_count == 1 - assert watcher.call_args == call( - name="sim_motor", - current=0.0, - initial=0.0, - target=0.55, - unit="mm", - precision=3, - time_elapsed=pytest.approx(0.0, abs=0.05), - ) - watcher.reset_mock() - assert 0.55 == await sim_motor.setpoint.get_value() - assert not s.done - await asyncio.sleep(0.1) - set_sim_value(sim_motor.readback, 0.1) - assert watcher.call_count == 1 - assert watcher.call_args == call( - name="sim_motor", - current=0.1, - initial=0.0, - target=0.55, - unit="mm", - precision=3, - time_elapsed=pytest.approx(0.1, abs=0.05), - ) - set_sim_put_proceeds(sim_motor.setpoint, True) - await asyncio.sleep(A_BIT) - assert s.done - done.assert_called_once_with(s) - - -async def test_motor_moving_stopped(sim_motor: motor.Motor): - set_sim_put_proceeds(sim_motor.setpoint, False) - s = sim_motor.set(1.5) - s.add_callback(Mock()) - await asyncio.sleep(0.2) - assert not s.done - await sim_motor.stop() - set_sim_put_proceeds(sim_motor.setpoint, True) - await asyncio.sleep(A_BIT) - assert s.done - assert s.success is False - - -async def test_read_motor(sim_motor: motor.Motor): - sim_motor.stage() - assert (await sim_motor.read())["sim_motor"]["value"] == 0.0 - assert (await sim_motor.describe())["sim_motor"][ - "source" - ] == "sim://BLxxI-MO-TABLE-01:X.RBV" - assert (await sim_motor.read_configuration())["sim_motor-velocity"]["value"] == 1 - assert (await sim_motor.describe_configuration())["sim_motor-units"]["shape"] == [] - set_sim_value(sim_motor.readback, 0.5) - assert (await sim_motor.read())["sim_motor"]["value"] == 0.5 - sim_motor.unstage() - # Check we can still read and describe when not staged - set_sim_value(sim_motor.readback, 0.1) - assert (await sim_motor.read())["sim_motor"]["value"] == 0.1 - assert await sim_motor.describe() - - -async def test_set_velocity(sim_motor: motor.Motor) -> None: - v = sim_motor.velocity - assert (await v.describe())["sim_motor-velocity"][ - "source" - ] == "sim://BLxxI-MO-TABLE-01:X.VELO" - q: asyncio.Queue[Dict[str, Reading]] = asyncio.Queue() - v.subscribe(q.put_nowait) - assert (await q.get())["sim_motor-velocity"]["value"] == 1.0 - await v.set(2.0) - assert (await q.get())["sim_motor-velocity"]["value"] == 2.0 - v.clear_sub(q.put_nowait) - await v.set(3.0) - assert (await v.read())["sim_motor-velocity"]["value"] == 3.0 - assert q.empty() - - -def test_motor_in_re(sim_motor: motor.Motor, RE: RunEngine) -> None: - sim_motor.move(0) - - def my_plan(): - sim_motor.move(0) - return - yield - - with pytest.raises(RuntimeError, match="Will deadlock run engine if run in a plan"): - RE(my_plan()) -""" + assert sim_pimte_driver.name == "sim_pimte_driver" \ No newline at end of file diff --git a/tests/devices/unit_tests/test_pimteController.py b/tests/devices/unit_tests/test_pimteController.py index 12b161208b..c83ef8718b 100644 --- a/tests/devices/unit_tests/test_pimteController.py +++ b/tests/devices/unit_tests/test_pimteController.py @@ -8,7 +8,7 @@ from ophyd_async.epics.areadetector.drivers import ADBase from ophyd_async.epics.areadetector.utils import ImageMode -from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver +from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver, TriggerMode, SpeedMode from dodal.devices.areadetector.epics.pimte_controller import PimteController @@ -38,7 +38,7 @@ async def test_pimte_controller(RE, pimte: PimteController): assert await driver.num_images.get_value() == 1 assert await driver.image_mode.get_value() == ImageMode.multiple - assert await driver.trigger_mode.get_value() == Pimte1Driver.TriggerMode.internal + assert await driver.trigger_mode.get_value() == TriggerMode.internal assert await driver.acquire.get_value() is True assert await driver.acquire_time.get_value() == 0.002 assert pimte.get_deadtime(2) == 2 + 0.1 @@ -48,8 +48,8 @@ async def test_pimte_controller(RE, pimte: PimteController): ): await pimte.disarm() await pimte.set_temperature(20) - await pimte.set_speed(driver.SpeedMode.adc_200Khz) + await pimte.set_speed(SpeedMode.adc_200Khz) assert await driver.set_temperture.get_value() == 20 - assert await driver.speed.get_value() == driver.SpeedMode.adc_200Khz + assert await driver.speed.get_value() == SpeedMode.adc_200Khz assert await driver.acquire.get_value() is False From 6db204e604bf7b8b871edd2507b73e5def94c44b Mon Sep 17 00:00:00 2001 From: Relm-Arrowny Date: Fri, 12 Apr 2024 11:33:49 +0000 Subject: [PATCH 7/7] fixing a test issue where test path is not set correctly --- .../areadetector/epics/drivers/pimte1_driver.py | 7 +++++-- .../devices/areadetector/epics/pimte_controller.py | 6 +++++- src/dodal/devices/areadetector/pimteAD.py | 5 +++-- tests/conftest.py | 3 +-- tests/devices/unit_tests/test_pimte.py | 11 ++++++----- tests/devices/unit_tests/test_pimte1Driver.py | 2 +- tests/devices/unit_tests/test_pimteController.py | 8 ++++++-- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py b/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py index 3c1d3de7bc..a8b561164f 100644 --- a/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py +++ b/src/dodal/devices/areadetector/epics/drivers/pimte1_driver.py @@ -3,12 +3,13 @@ from ophyd_async.epics.areadetector.drivers.ad_base import ADBase from ophyd_async.epics.areadetector.utils import ad_r, ad_rw from ophyd_async.epics.signal import epics_signal_rw -""" -Driver for pi-mite 3 CCD +""" +Driver for pi-mite 3 CCD """ + class Pimte1Driver(ADBase): def __init__(self, prefix: str) -> None: self.trigger_mode = ad_rw(TriggerMode, prefix + "TriggerMode") @@ -18,6 +19,7 @@ def __init__(self, prefix: str) -> None: self.speed = ad_rw(SpeedMode, prefix + "SpeedSelection") super().__init__(prefix) + class SpeedMode(str, Enum): adc_50Khz = "0: 50 KHz - 20000 ns" adc_100Khz = "1: 100 kHz - 10000 ns" @@ -26,6 +28,7 @@ class SpeedMode(str, Enum): adc_1Mhz = "4: 1 MHz - 1000 ns" adc_2Mhz = "5: 2 MHz - 500 ns" + class TriggerMode(str, Enum): internal = "Free Run" ext_trigger = "Ext Trigger" diff --git a/src/dodal/devices/areadetector/epics/pimte_controller.py b/src/dodal/devices/areadetector/epics/pimte_controller.py index 4cfe2b5e66..349e201450 100644 --- a/src/dodal/devices/areadetector/epics/pimte_controller.py +++ b/src/dodal/devices/areadetector/epics/pimte_controller.py @@ -9,7 +9,11 @@ ) from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record -from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver, TriggerMode, SpeedMode +from dodal.devices.areadetector.epics.drivers.pimte1_driver import ( + Pimte1Driver, + SpeedMode, + TriggerMode, +) TRIGGER_MODE = { DetectorTrigger.internal: TriggerMode.internal, diff --git a/src/dodal/devices/areadetector/pimteAD.py b/src/dodal/devices/areadetector/pimteAD.py index 1bd9329ecb..0466bc6e25 100644 --- a/src/dodal/devices/areadetector/pimteAD.py +++ b/src/dodal/devices/areadetector/pimteAD.py @@ -1,8 +1,9 @@ from typing import Sequence + from bluesky.protocols import Hints from ophyd_async.core import DirectoryProvider, SignalR, StandardDetector from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider -from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF, NDPluginStats +from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver from dodal.devices.areadetector.epics.pimte_controller import PimteController @@ -23,7 +24,7 @@ def __init__( self.drv = Pimte1Driver(prefix + "CAM:") self.hdf = NDFileHDF(prefix + "HDF5:") - super().__init__( + super().__init__( PimteController(self.drv), HDFWriter( self.hdf, diff --git a/tests/conftest.py b/tests/conftest.py index 5918846b9c..661292d4c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,8 +17,6 @@ from dodal.log import LOGGER, GELFTCPHandler, set_up_all_logging_handlers from dodal.utils import make_all_devices -from pathlib import Path - MOCK_DAQ_CONFIG_PATH = "tests/devices/unit_tests/test_daq_configuration" mock_paths = [ ("DAQ_CONFIGURATION_PATH", MOCK_DAQ_CONFIG_PATH), @@ -42,6 +40,7 @@ def mock_beamline_module_filepaths(bl_name, bl_module): def tmp_directory_provider(tmp_path: Path) -> StaticDirectoryProvider: return StaticDirectoryProvider(tmp_path) + @pytest.fixture(scope="function") def module_and_devices_for_beamline(request): beamline = request.param diff --git a/tests/devices/unit_tests/test_pimte.py b/tests/devices/unit_tests/test_pimte.py index 2e900bc75e..26bd7701fe 100644 --- a/tests/devices/unit_tests/test_pimte.py +++ b/tests/devices/unit_tests/test_pimte.py @@ -1,7 +1,6 @@ import bluesky.plan_stubs as bps import pytest from bluesky.run_engine import RunEngine -from bluesky.utils import new_uid from ophyd_async.core import DeviceCollector, StaticDirectoryProvider, set_sim_value from dodal.devices.areadetector.pimteAD import HDFStatsPimte @@ -30,13 +29,17 @@ def count_sim(det: HDFStatsPimte, times: int = 1): @pytest.fixture -async def single_detector(RE: RunEngine, tmp_directory_provider: StaticDirectoryProvider) -> HDFStatsPimte: +async def single_detector( + RE: RunEngine, tmp_directory_provider: StaticDirectoryProvider +) -> HDFStatsPimte: + tempD = tmp_directory_provider async with DeviceCollector(sim=True): - detector = HDFStatsPimte("prefix", tmp_directory_provider, "pimte") + detector = HDFStatsPimte("prefix", tempD, "pimte") set_sim_value(detector._controller.driver.array_size_x, 10) set_sim_value(detector._controller.driver.array_size_y, 20) set_sim_value(detector.hdf.file_path_exists, True) + set_sim_value(detector.hdf.full_file_name, str(tempD().root.absolute())) set_sim_value(detector._writer.hdf.num_captured, 0) return detector @@ -59,8 +62,6 @@ async def test_pimte(RE: RunEngine, single_detector: HDFStatsPimte): "start", "descriptor", "stream_resource", - "stream_resource", - "stream_datum", "stream_datum", "event", "stop", diff --git a/tests/devices/unit_tests/test_pimte1Driver.py b/tests/devices/unit_tests/test_pimte1Driver.py index cbd8a3e034..c779cbcc0e 100644 --- a/tests/devices/unit_tests/test_pimte1Driver.py +++ b/tests/devices/unit_tests/test_pimte1Driver.py @@ -17,4 +17,4 @@ async def sim_pimte_driver(): async def test_sim_pimte_driver(sim_pimte_driver: Pimte1Driver) -> None: - assert sim_pimte_driver.name == "sim_pimte_driver" \ No newline at end of file + assert sim_pimte_driver.name == "sim_pimte_driver" diff --git a/tests/devices/unit_tests/test_pimteController.py b/tests/devices/unit_tests/test_pimteController.py index c83ef8718b..f4a5d2b387 100644 --- a/tests/devices/unit_tests/test_pimteController.py +++ b/tests/devices/unit_tests/test_pimteController.py @@ -8,7 +8,11 @@ from ophyd_async.epics.areadetector.drivers import ADBase from ophyd_async.epics.areadetector.utils import ImageMode -from dodal.devices.areadetector.epics.drivers.pimte1_driver import Pimte1Driver, TriggerMode, SpeedMode +from dodal.devices.areadetector.epics.drivers.pimte1_driver import ( + Pimte1Driver, + SpeedMode, + TriggerMode, +) from dodal.devices.areadetector.epics.pimte_controller import PimteController @@ -41,7 +45,7 @@ async def test_pimte_controller(RE, pimte: PimteController): assert await driver.trigger_mode.get_value() == TriggerMode.internal assert await driver.acquire.get_value() is True assert await driver.acquire_time.get_value() == 0.002 - assert pimte.get_deadtime(2) == 2 + 0.1 + assert pimte.get_deadtime(2) == 2.4e-5 with patch( "ophyd_async.epics.areadetector.utils.wait_for_value", return_value=None