Skip to content

Commit

Permalink
made a new class for the PandA hdf writer and adjusted PandAController
Browse files Browse the repository at this point in the history
Also made it possible to pre-intialise blocks so that the controller can have the same
pcap device as the post init panda.
  • Loading branch information
evalott100 committed Apr 11, 2024
1 parent e54c91c commit d78ffcb
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 114 deletions.
4 changes: 2 additions & 2 deletions src/ophyd_async/epics/pvi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .pvi import PVIEntry, fill_pvi_entries
from .pvi import PVIEntry, fill_pvi_entries, pre_initialize_blocks

__all__ = ["PVIEntry", "fill_pvi_entries"]
__all__ = ["PVIEntry", "fill_pvi_entries", "pre_initialize_blocks"]
25 changes: 23 additions & 2 deletions src/ophyd_async/epics/pvi/pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _sim_common_blocks(device: Device, stripped_type: Optional[Type] = None):
signal_type = args[0] if (args := get_args(sub_device_t)) else None
sub_device = sub_device_t(SimSignalBackend(signal_type, sub_name))
else:
sub_device = sub_device_t()
sub_device = getattr(device, sub_name, sub_device_t())

if not is_signal:
if is_device_vector:
Expand Down Expand Up @@ -226,7 +226,10 @@ async def _get_pvi_entries(entry: PVIEntry, timeout=DEFAULT_TIMEOUT):
if is_signal:
device = _pvi_mapping[frozenset(pva_entries.keys())](signal_dtype, *pvs)
else:
device = device_type()
if hasattr(entry.device, sub_name):
device = getattr(entry.device, sub_name)
else:
device = device_type()

sub_entry = PVIEntry(
device=device, common_device_type=device_type, sub_entries={}
Expand Down Expand Up @@ -296,3 +299,21 @@ async def fill_pvi_entries(
# We call set name now the parent field has been set in all of the
# introspect-initialized devices. This will recursively set the names.
device.set_name(device.name)


def pre_initialize_blocks(device: Device):
for name, device_type in get_type_hints(type(device)).items():
if name in ("_name", "parent"):
continue
device_type = _strip_union(device_type)
is_device_vector, device_type = _strip_device_vector(device_type)
if (
is_device_vector
or ((origin := get_origin(device_type)) and issubclass(origin, Signal))
or (isclass(device_type) and issubclass(device_type, Signal))
):
continue

sub_device = device_type()
setattr(device, name, sub_device)
pre_initialize_blocks(sub_device)
6 changes: 4 additions & 2 deletions src/ophyd_async/panda/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .panda import PandA, PcapBlock, PulseBlock, SeqBlock, SeqTable, TimeUnits
from .common_panda import CommonPandaBlocks, PcapBlock, PulseBlock, SeqBlock, TimeUnits
from .hdf_panda import HDFPandA
from .panda_controller import PandaPcapController
from .table import (
SeqTable,
Expand All @@ -10,7 +11,8 @@
from .utils import phase_sorter

__all__ = [
"PandA",
"CommonPandaBlocks",
"HDFPandA",
"PcapBlock",
"PulseBlock",
"seq_table_from_arrays",
Expand Down
38 changes: 38 additions & 0 deletions src/ophyd_async/panda/common_panda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

from enum import Enum

from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW
from ophyd_async.panda.table import SeqTable


class PulseBlock(Device):
delay: SignalRW[float]
width: SignalRW[float]


class TimeUnits(str, Enum):
min = "min"
s = "s"
ms = "ms"
us = "us"


class SeqBlock(Device):
table: SignalRW[SeqTable]
active: SignalRW[bool]
repeats: SignalRW[int]
prescale: SignalRW[float]
prescale_units: SignalRW[TimeUnits]
enable: SignalRW[str]


class PcapBlock(Device):
active: SignalR[bool]
arm: SignalRW[bool]


class CommonPandaBlocks(Device):
pulse: DeviceVector[PulseBlock]
seq: DeviceVector[SeqBlock]
pcap: PcapBlock
38 changes: 38 additions & 0 deletions src/ophyd_async/panda/hdf_panda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

from typing import Sequence

from ophyd_async.core import DEFAULT_TIMEOUT, SignalR, StandardDetector
from ophyd_async.epics.areadetector.writers import HDFWriter
from ophyd_async.epics.pvi import fill_pvi_entries, pre_initialize_blocks

from .common_panda import CommonPandaBlocks
from .panda_controller import PandaPcapController


class HDFPandA(CommonPandaBlocks, StandardDetector):
def __init__(
self,
prefix: str,
config_sigs: Sequence[SignalR] = (),
name: str = "",
):
self._prefix = prefix
pre_initialize_blocks(self)
super().__init__(
controller=PandaPcapController(pcap=self.pcap),
# TODO
writer=None, # type: ignore
config_sigs=config_sigs,
name=name,
writer_timeout=DEFAULT_TIMEOUT,
)

async def connect(
self, sim: bool = False, timeout: float = DEFAULT_TIMEOUT
) -> None:
await fill_pvi_entries(self, self._prefix + "PVI", timeout=timeout, sim=sim)
await super().connect(sim=sim, timeout=timeout)

async def stage(self):
pass
61 changes: 0 additions & 61 deletions src/ophyd_async/panda/panda.py

This file was deleted.

10 changes: 3 additions & 7 deletions src/ophyd_async/panda/panda_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
DetectorTrigger,
wait_for_value,
)

from .panda import PcapBlock
from ophyd_async.panda import PcapBlock


class PandaPcapController(DetectorControl):
def __init__(
self,
pcap: PcapBlock,
) -> None:
def __init__(self, pcap: PcapBlock) -> None:
self.pcap = pcap

def get_deadtime(self, exposure: float) -> float:
Expand All @@ -35,7 +31,7 @@ async def arm(
await wait_for_value(self.pcap.active, True, timeout=1)
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))

async def disarm(self):
async def disarm(self) -> AsyncStatus:
await asyncio.gather(self.pcap.arm.set(False))
await wait_for_value(self.pcap.active, False, timeout=1)
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
46 changes: 45 additions & 1 deletion tests/epics/test_pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
SignalRW,
SignalX,
)
from ophyd_async.epics.pvi import fill_pvi_entries
from ophyd_async.epics.pvi import fill_pvi_entries, pre_initialize_blocks


class Block1(Device):
Expand Down Expand Up @@ -94,3 +94,47 @@ async def test_fill_pvi_entries_sim_mode(pvi_test_device_t):

# top level signals are typed
assert test_device.signal_rw._backend.datatype is int


@pytest.fixture
def pvi_test_device_pre_initialize_blocks_t():
"""A fixture since pytest discourages init in test case classes"""

class TestDevice(Block3, Device):
def __init__(self, prefix: str, name: str = ""):
self._prefix = prefix
super().__init__(name)
pre_initialize_blocks(self)

async def connect(
self, sim: bool = False, timeout: float = DEFAULT_TIMEOUT
) -> None:
await fill_pvi_entries(self, self._prefix + "PVI", timeout=timeout, sim=sim)

await super().connect(sim)

yield TestDevice


async def test_device_pre_initialize_blocks(pvi_test_device_pre_initialize_blocks_t):
device = pvi_test_device_pre_initialize_blocks_t("PREFIX:")

block_2_device = device.device
block_1_device = device.device.device
top_block_1_device = device.signal_device

# The pre_initialize_blocks has only made blocks,
# not signals or device vectors
assert isinstance(block_2_device, Block2)
assert isinstance(block_1_device, Block1)
assert isinstance(top_block_1_device, Block1)
assert not hasattr(device, "signal_x")
assert not hasattr(device, "signal_rw")
assert not hasattr(top_block_1_device, "signal_rw")

await device.connect(sim=True)

# The memory addresses have not changed
assert device.device is block_2_device
assert device.device.device is block_1_device
assert device.signal_device is top_block_1_device
88 changes: 88 additions & 0 deletions tests/panda/test_hdf_panda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import time
from typing import AsyncGenerator, AsyncIterator, Dict, Optional, Sequence

import pytest
from bluesky.protocols import Descriptor, StreamAsset
from event_model import ComposeStreamResourceBundle, compose_stream_resource

from ophyd_async.core import (
DEFAULT_TIMEOUT,
DetectorWriter,
DeviceCollector,
SignalRW,
SimSignalBackend,
observe_value,
)
from ophyd_async.panda import HDFPandA


class DummyWriter(DetectorWriter):
def __init__(self, name: str, shape: Sequence[int]):
self.dummy_signal = SignalRW(backend=SimSignalBackend(int, source="test"))
self._shape = shape
self._name = name
self._file: Optional[ComposeStreamResourceBundle] = None
self._last_emitted = 0
self.index = 0

async def open(self, multiplier: int = 1) -> Dict[str, Descriptor]:
return {
self._name: Descriptor(
source="sim://some-source",
shape=self._shape,
dtype="number",
external="STREAM:",
)
}

async def observe_indices_written(
self, timeout=DEFAULT_TIMEOUT
) -> AsyncGenerator[int, None]:
num_captured: int
async for num_captured in observe_value(self.dummy_signal, timeout):
yield num_captured

async def get_indices_written(self) -> int:
return self.index

async def collect_stream_docs(
self, indices_written: int
) -> AsyncIterator[StreamAsset]:
if indices_written:
if not self._file:
self._file = compose_stream_resource(
spec="AD_HDF5_SWMR_SLICE",
root="/",
data_key=self._name,
resource_path="",
resource_kwargs={
"path": "",
"multiplier": 1,
"timestamps": "/entry/instrument/NDAttributes/NDArrayTimeStamp",
},
)
yield "stream_resource", self._file.stream_resource_doc

if indices_written >= self._last_emitted:
indices = dict(
start=self._last_emitted,
stop=indices_written,
)
self._last_emitted = indices_written
self._last_flush = time.monotonic()
yield "stream_datum", self._file.compose_stream_datum(indices)

async def close(self) -> None:
self._file = None


@pytest.fixture
async def sim_hdf_panda():
async with DeviceCollector(sim=True):
sim_hdf_panda = HDFPandA("HDFPANDA:")
yield sim_hdf_panda


async def test_hdf_panda_passes_blocks_to_controller(sim_hdf_panda: HDFPandA):
assert hasattr(sim_hdf_panda.controller, "pcap")
assert sim_hdf_panda.controller.pcap == sim_hdf_panda.pcap
Loading

0 comments on commit d78ffcb

Please sign in to comment.