Skip to content

Commit

Permalink
Add Async Protocols (#219)
Browse files Browse the repository at this point in the history
* Added AsyncReadable, AsyncConfigurable and AsyncPausable

Also replaced imports of bluesky.protocols for each with their new async counterparts.

* Fixed describe() in core/detector.py to be async

* Sorted imports

* Add tests

* Removed test_mypy

* Removed ophyd sim imports, replace with ophyd_async equivalents (requires make_detector)

* Move protocols.py into src/ophyd_async

---------

Co-authored-by: Oliver Copping <[email protected]>
Co-authored-by: Tom C (DLS) <[email protected]>
  • Loading branch information
3 people authored Apr 19, 2024
1 parent caecb59 commit 610d2d1
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 9 deletions.
10 changes: 5 additions & 5 deletions src/ophyd_async/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@

from bluesky.protocols import (
Collectable,
Configurable,
Descriptor,
Flyable,
Preparable,
Readable,
Reading,
Stageable,
StreamAsset,
Triggerable,
WritesStreamAssets,
)

from ophyd_async.protocols import AsyncConfigurable, AsyncReadable

from .async_status import AsyncStatus
from .device import Device
from .signal import SignalR
Expand Down Expand Up @@ -143,8 +143,8 @@ async def close(self) -> None:
class StandardDetector(
Device,
Stageable,
Configurable,
Readable,
AsyncConfigurable,
AsyncReadable,
Triggerable,
Preparable,
Flyable,
Expand Down Expand Up @@ -241,7 +241,7 @@ async def read(self) -> Dict[str, Reading]:
# All data is in StreamResources, not Events, so nothing to output here
return {}

def describe(self) -> Dict[str, Descriptor]:
async def describe(self) -> Dict[str, Descriptor]:
return self._describe

@AsyncStatus.wrap
Expand Down
5 changes: 3 additions & 2 deletions src/ophyd_async/core/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
Locatable,
Location,
Movable,
Readable,
Reading,
Stageable,
Subscribable,
)

from ophyd_async.protocols import AsyncReadable

from .async_status import AsyncStatus
from .device import Device
from .signal_backend import SignalBackend
Expand Down Expand Up @@ -127,7 +128,7 @@ def set_staged(self, staged: bool):
return self._staged or bool(self._listeners)


class SignalR(Signal[T], Readable, Stageable, Subscribable):
class SignalR(Signal[T], AsyncReadable, Stageable, Subscribable):
"""Signal that can be read from and monitored"""

_cache: Optional[_SignalCache] = None
Expand Down
6 changes: 4 additions & 2 deletions src/ophyd_async/core/standard_readable.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from typing import Dict, Sequence, Tuple

from bluesky.protocols import Configurable, Descriptor, Readable, Reading, Stageable
from bluesky.protocols import Descriptor, Reading, Stageable

from ophyd_async.protocols import AsyncConfigurable, AsyncReadable

from .async_status import AsyncStatus
from .device import Device
from .signal import SignalR
from .utils import merge_gathered_dicts


class StandardReadable(Device, Readable, Configurable, Stageable):
class StandardReadable(Device, AsyncReadable, AsyncConfigurable, Stageable):
"""Device that owns its children and provides useful default behavior.
- When its name is set it renames child Devices
Expand Down
73 changes: 73 additions & 0 deletions src/ophyd_async/protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from abc import abstractmethod
from typing import Dict, Protocol, runtime_checkable

from bluesky.protocols import Descriptor, HasName, Reading


@runtime_checkable
class AsyncReadable(HasName, Protocol):
@abstractmethod
async def read(self) -> Dict[str, Reading]:
"""Return an OrderedDict mapping string field name(s) to dictionaries
of values and timestamps and optional per-point metadata.
Example return value:
.. code-block:: python
OrderedDict(('channel1',
{'value': 5, 'timestamp': 1472493713.271991}),
('channel2',
{'value': 16, 'timestamp': 1472493713.539238}))
"""
...

@abstractmethod
async def describe(self) -> Dict[str, Descriptor]:
"""Return an OrderedDict with exactly the same keys as the ``read``
method, here mapped to per-scan metadata about each field.
Example return value:
.. code-block:: python
OrderedDict(('channel1',
{'source': 'XF23-ID:SOME_PV_NAME',
'dtype': 'number',
'shape': []}),
('channel2',
{'source': 'XF23-ID:SOME_PV_NAME',
'dtype': 'number',
'shape': []}))
"""
...


@runtime_checkable
class AsyncConfigurable(Protocol):
@abstractmethod
async def read_configuration(self) -> Dict[str, Reading]:
"""Same API as ``read`` but for slow-changing fields related to configuration.
e.g., exposure time. These will typically be read only once per run.
"""
...

@abstractmethod
async def describe_configuration(self) -> Dict[str, Descriptor]:
"""Same API as ``describe``, but corresponding to the keys in
``read_configuration``.
"""
...


@runtime_checkable
class AsyncPausable(Protocol):
@abstractmethod
async def pause(self) -> None:
"""Perform device-specific work when the RunEngine pauses."""
...

@abstractmethod
async def resume(self) -> None:
"""Perform device-specific work when the RunEngine resumes after a pause."""
...
42 changes: 42 additions & 0 deletions tests/protocols/test_protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path

from bluesky.utils import new_uid

from ophyd_async import protocols as bs_protocols
from ophyd_async.core import (
DeviceCollector,
StaticDirectoryProvider,
set_sim_callback,
set_sim_value,
)
from ophyd_async.core.flyer import HardwareTriggeredFlyable
from ophyd_async.epics.areadetector.drivers import ADBase
from ophyd_async.epics.areadetector.writers import NDFileHDF
from ophyd_async.epics.demo.demo_ad_sim_detector import DemoADSimDetector
from ophyd_async.sim.demo import SimMotor


async def make_detector(prefix: str, name: str, tmp_path: Path):
dp = StaticDirectoryProvider(tmp_path, f"test-{new_uid()}")

async with DeviceCollector(sim=True):
drv = ADBase(f"{prefix}DRV:")
hdf = NDFileHDF(f"{prefix}HDF:")
det = DemoADSimDetector(
drv, hdf, dp, config_sigs=[drv.acquire_time, drv.acquire], name=name
)

def _set_full_file_name(_, val):
set_sim_value(hdf.full_file_name, str(tmp_path / val))

set_sim_callback(hdf.file_name, _set_full_file_name)

return det


async def test_readable():
async with DeviceCollector(sim=True):
det = await make_detector("test", "test det", Path("/tmp"))
assert isinstance(SimMotor, bs_protocols.AsyncReadable)
assert isinstance(det, bs_protocols.AsyncReadable)
assert not isinstance(HardwareTriggeredFlyable, bs_protocols.AsyncReadable)

0 comments on commit 610d2d1

Please sign in to comment.