Skip to content

Commit

Permalink
Improved test covereage of demo devices. Added composite device Tango…
Browse files Browse the repository at this point in the history
…Detector to demo which includes demonstrations of annotated subdevices.
  • Loading branch information
burkeds committed Sep 12, 2024
1 parent f66d403 commit e586a24
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 95 deletions.
13 changes: 6 additions & 7 deletions src/ophyd_async/tango/base_devices/_base_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/ophyd_async/tango/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
23 changes: 4 additions & 19 deletions src/ophyd_async/tango/demo/_counter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from dataclasses import dataclass
from typing import Optional

from ophyd_async.core import (
DEFAULT_TIMEOUT,
AsyncStatus,
Expand All @@ -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)
Expand All @@ -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)
39 changes: 39 additions & 0 deletions src/ophyd_async/tango/demo/_detector.py
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 0 additions & 19 deletions src/ophyd_async/tango/demo/_mover.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import asyncio
from dataclasses import dataclass
from typing import Optional

from bluesky.protocols import Movable, Reading, Stoppable

Expand All @@ -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):
Expand Down Expand Up @@ -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()
12 changes: 0 additions & 12 deletions src/ophyd_async/tango/demo/_tango/_servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,13 @@ 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

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()
Expand Down Expand Up @@ -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)
Expand Down
47 changes: 13 additions & 34 deletions tests/tango/test_base_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
from ophyd_async.tango.demo import (
DemoCounter,
DemoMover,
TangoCounter,
TangoMover,
TangoDetector,
)
from tango import (
AttrDataFormat,
Expand Down Expand Up @@ -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])
Expand Down

0 comments on commit e586a24

Please sign in to comment.