Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Async Protocols #219

Merged
merged 8 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -131,7 +132,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
3 changes: 3 additions & 0 deletions src/ophyd_async/protocols/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .protocols import AsyncConfigurable, AsyncPausable, AsyncReadable

__all__ = ["AsyncReadable", "AsyncConfigurable", "AsyncPausable"]
73 changes: 73 additions & 0 deletions src/ophyd_async/protocols/protocols.py
callumforrester marked this conversation as resolved.
Show resolved Hide resolved
coretl marked this conversation as resolved.
Show resolved Hide resolved
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
coretl marked this conversation as resolved.
Show resolved Hide resolved
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)
Loading