From e586a2418a452d8ee93706c821e1467b4165cfdb Mon Sep 17 00:00:00 2001 From: Devin Burke Date: Thu, 12 Sep 2024 14:28:20 +0200 Subject: [PATCH] Improved test covereage of demo devices. Added composite device TangoDetector to demo which includes demonstrations of annotated subdevices. --- .../tango/base_devices/_base_device.py | 13 +++-- src/ophyd_async/tango/demo/__init__.py | 8 ++-- src/ophyd_async/tango/demo/_counter.py | 23 ++------- src/ophyd_async/tango/demo/_detector.py | 39 +++++++++++++++ src/ophyd_async/tango/demo/_mover.py | 19 -------- src/ophyd_async/tango/demo/_tango/_servers.py | 12 ----- tests/tango/test_base_device.py | 47 +++++-------------- 7 files changed, 66 insertions(+), 95 deletions(-) create mode 100644 src/ophyd_async/tango/demo/_detector.py diff --git a/src/ophyd_async/tango/base_devices/_base_device.py b/src/ophyd_async/tango/base_devices/_base_device.py index 3e03108597..473e63bb2f 100644 --- a/src/ophyd_async/tango/base_devices/_base_device.py +++ b/src/ophyd_async/tango/base_devices/_base_device.py @@ -55,9 +55,6 @@ def __init__( device_proxy: Optional[Union[AsyncDeviceProxy, SyncDeviceProxy]] = None, name: str = "", ) -> None: - if not trl and not device_proxy: - raise ValueError("Either 'trl' or 'device_proxy' must be provided.") - self.trl = trl if trl else "" self.proxy = device_proxy tango_create_children_from_annotations(self) @@ -77,12 +74,14 @@ async def closure(): raise RuntimeError("Could not connect to device proxy") from e return self - if self.trl in ["", None]: + if self.trl in ["", None] and self.proxy is not None: self.trl = self.proxy.name() - await closure() - self.register_signals() - await fill_proxy_entries(self) + if self.trl: + await closure() + if self.proxy is not None: + self.register_signals() + await fill_proxy_entries(self) # set_name should be called again to propagate the new signal names self.set_name(self.name) diff --git a/src/ophyd_async/tango/demo/__init__.py b/src/ophyd_async/tango/demo/__init__.py index 4334b401f6..78fffae2aa 100644 --- a/src/ophyd_async/tango/demo/__init__.py +++ b/src/ophyd_async/tango/demo/__init__.py @@ -1,12 +1,12 @@ -from ._counter import TangoCounter, TangoCounterConfig -from ._mover import TangoMover, TangoMoverConfig +from ._counter import TangoCounter +from ._detector import TangoDetector +from ._mover import TangoMover from ._tango import DemoCounter, DemoMover __all__ = [ "DemoCounter", "DemoMover", "TangoCounter", - "TangoCounterConfig", "TangoMover", - "TangoMoverConfig", + "TangoDetector", ] diff --git a/src/ophyd_async/tango/demo/_counter.py b/src/ophyd_async/tango/demo/_counter.py index 1a8ff7dc3d..4da1b2d17b 100644 --- a/src/ophyd_async/tango/demo/_counter.py +++ b/src/ophyd_async/tango/demo/_counter.py @@ -1,6 +1,3 @@ -from dataclasses import dataclass -from typing import Optional - from ophyd_async.core import ( DEFAULT_TIMEOUT, AsyncStatus, @@ -13,20 +10,16 @@ from ophyd_async.tango import TangoReadable, tango_polling -@dataclass -class TangoCounterConfig: - sample_time: Optional[float] = None - - # Enable device level polling, useful for servers that do not support events # Polling for individual signal can be enabled with a dict -@tango_polling((0.1, 0.1, 0.1), {"counts": (1.0, 0.1, 0.1)}) +@tango_polling({"counts": (1.0, 0.1, 0.1), "sample_time": (0.1, 0.1, 0.1)}) class TangoCounter(TangoReadable): # Enter the name and type of the signals you want to use # If type is None or Signal, the type will be inferred from the Tango device counts: SignalR[int] sample_time: SignalRW[float] start: SignalX + reset: SignalX def __init__(self, trl: str, name=""): super().__init__(trl, name=name) @@ -40,13 +33,5 @@ async def trigger(self) -> None: await self.start.trigger(wait=True, timeout=timeout) @AsyncStatus.wrap - async def prepare(self, value: TangoCounterConfig) -> None: - config = value.__dataclass_fields__ - for key in config: - v = getattr(value, key) - if v is not None: - if hasattr(self, key): - await getattr(self, key).set(v) - - def get_dataclass(self) -> TangoCounterConfig: - return TangoCounterConfig() + async def reset(self) -> None: + await self.reset.trigger(wait=True, timeout=DEFAULT_TIMEOUT) diff --git a/src/ophyd_async/tango/demo/_detector.py b/src/ophyd_async/tango/demo/_detector.py new file mode 100644 index 0000000000..089877ade3 --- /dev/null +++ b/src/ophyd_async/tango/demo/_detector.py @@ -0,0 +1,39 @@ +import asyncio + +from ophyd_async.core import ( + AsyncStatus, + DeviceVector, +) +from ophyd_async.tango import TangoReadable + +from . import TangoCounter, TangoMover + + +class TangoDetector(TangoReadable): + counters: DeviceVector[TangoCounter] + mover: TangoMover + + def __init__(self, *args, **kwargs): + if "counters_kwargs" in kwargs: + self._counters_kwargs = kwargs.pop("counters_kwargs") + if "mover_kwargs" in kwargs: + self._mover_kwargs = kwargs.pop("mover_kwargs") + super().__init__(*args, **kwargs) + self.add_readables([self.counters, self.mover]) + + def set(self, value): + return self.mover.set(value) + + def stop(self): + return self.mover.stop() + + @AsyncStatus.wrap + async def trigger(self): + statuses = [] + for counter in self.counters.values(): + statuses.append(counter.reset()) + await asyncio.gather(*statuses) + statuses.clear() + for counter in self.counters.values(): + statuses.append(counter.trigger()) + await asyncio.gather(*statuses) diff --git a/src/ophyd_async/tango/demo/_mover.py b/src/ophyd_async/tango/demo/_mover.py index bf54f6b014..8168093b2b 100644 --- a/src/ophyd_async/tango/demo/_mover.py +++ b/src/ophyd_async/tango/demo/_mover.py @@ -1,6 +1,4 @@ import asyncio -from dataclasses import dataclass -from typing import Optional from bluesky.protocols import Movable, Reading, Stoppable @@ -21,11 +19,6 @@ from tango import DevState -@dataclass -class TangoMoverConfig: - velocity: Optional[float] = None - - # Enable device level polling, useful for servers that do not support events @tango_polling((0.1, 0.1, 0.1)) class TangoMover(TangoReadable, Movable, Stoppable): @@ -88,15 +81,3 @@ def _wait(value: dict[str, Reading]): def stop(self, success: bool = True) -> AsyncStatus: self._set_success = success return self._stop.trigger() - - @AsyncStatus.wrap - async def prepare(self, value: TangoMoverConfig) -> None: - config = value.__dataclass_fields__ - for key in config: - v = getattr(value, key) - if v is not None: - if hasattr(self, key): - await getattr(self, key).set(v) - - def get_dataclass(self) -> TangoMoverConfig: - return TangoMoverConfig() diff --git a/src/ophyd_async/tango/demo/_tango/_servers.py b/src/ophyd_async/tango/demo/_tango/_servers.py index f253228908..d3332f881a 100644 --- a/src/ophyd_async/tango/demo/_tango/_servers.py +++ b/src/ophyd_async/tango/demo/_tango/_servers.py @@ -25,13 +25,6 @@ async def write_position(self, new_position): self._setpoint = new_position await self.move() - @attribute(dtype=float, access=AttrWriteType.READ_WRITE) - async def setpoint(self): - return self._setpoint - - async def write_setpoint(self, new_position): - self._setpoint = new_position - @attribute(dtype=float, access=AttrWriteType.READ_WRITE) async def velocity(self): return self._velocity @@ -39,9 +32,6 @@ async def velocity(self): async def write_velocity(self, value: float): self._velocity = value - async def write_precision(self, value: float): - self._precision = value - @attribute(dtype=DevState, access=AttrWriteType.READ) async def state(self): return self.get_state() @@ -88,8 +78,6 @@ async def sample_time(self): return self._sample_time async def write_sample_time(self, value: float): - if value < 0.0: - raise ValueError("Sample time must be a positive number") self._sample_time = value @attribute(dtype=DevState, access=AttrWriteType.READ) diff --git a/tests/tango/test_base_device.py b/tests/tango/test_base_device.py index d47d62e5e7..3071d60282 100644 --- a/tests/tango/test_base_device.py +++ b/tests/tango/test_base_device.py @@ -16,8 +16,7 @@ from ophyd_async.tango.demo import ( DemoCounter, DemoMover, - TangoCounter, - TangoMover, + TangoDetector, ) from tango import ( AttrDataFormat, @@ -363,43 +362,23 @@ async def test_with_bluesky(tango_test_device): @pytest.mark.asyncio async def test_tango_demo(demo_test_context): with demo_test_context: - motor1 = TangoMover( - trl=demo_test_context.get_device_access("demo/motor/1"), name="motor1" + detector = TangoDetector( + trl="", + name="detector", + counters_kwargs={"prefix": "demo/counter/", "count": 2}, + mover_kwargs={"trl": "demo/motor/1"}, ) - counter1 = TangoCounter( - trl=demo_test_context.get_device_access("demo/counter/1"), name="counter1" - ) - counter2 = TangoCounter( - trl=demo_test_context.get_device_access("demo/counter/2"), name="counter2" - ) - await motor1.connect() - await counter1.connect() - await counter2.connect() + await detector.connect() RE = RunEngine() - dc = motor1.get_dataclass() - dc.velocity = 1.0 - prepare_status = motor1.prepare(dc) - await prepare_status - assert all([prepare_status.done, prepare_status.success]) - - cc = counter1.get_dataclass() - cc.sample_time = 0.1 - prepare_status1 = counter1.prepare(cc) - prepare_status2 = counter2.prepare(cc) - await prepare_status1 - await prepare_status2 - assert all([prepare_status1.done, prepare_status1.success]) - assert all([prepare_status2.done, prepare_status2.success]) - - RE(bps.read(motor1.position)) - RE(bps.mv(motor1, 0)) - RE(bp.count([counter1, counter2])) - - set_status = motor1.set(1.0) + RE(bps.read(detector)) + RE(bps.mv(detector, 0)) + RE(bp.count(list(detector.counters.values()))) + + set_status = detector.set(1.0) await asyncio.sleep(0.1) - stop_status = motor1.stop() + stop_status = detector.stop() await set_status await stop_status assert all([set_status.done, stop_status.done])