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 15, 2024
1 parent 9c43101 commit a2ef6b0
Show file tree
Hide file tree
Showing 16 changed files with 354 additions and 156 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"]
57 changes: 45 additions & 12 deletions src/ophyd_async/epics/pvi/pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ def _strip_number_from_string(string: str) -> Tuple[str, Optional[int]]:


def _strip_union(field: Union[Union[T], T]) -> T:
is_optional = False
if get_origin(field) is Union:
args = get_args(field)
for arg in args:
if arg is not type(None):
return arg
return field
is_optional = type(None) in args
for field in args:
if field is not type(None):
break
return field, is_optional


def _strip_device_vector(field: Union[Type[Device]]) -> Tuple[bool, Type[Device]]:
Expand Down Expand Up @@ -80,10 +82,15 @@ def _verify_common_blocks(entry: PVIEntry, common_device: Type[Device]):
if sub_name in ("_name", "parent"):
continue
assert entry.sub_entries
if sub_name not in entry.sub_entries and get_origin(sub_device) is not Optional:
raise RuntimeError(
f"sub device `{sub_name}:{type(sub_device)}` was not provided by pvi"
)
device_t, is_optional = _strip_union(sub_device)
if sub_name not in entry.sub_entries:
if is_optional:
continue
else:
raise RuntimeError(
f"sub device `{sub_name}:{type(sub_device)}` "
"was not provided by pvi"
)
if isinstance(entry.sub_entries[sub_name], dict):
for sub_sub_entry in entry.sub_entries[sub_name].values(): # type: ignore
_verify_common_blocks(sub_sub_entry, sub_device) # type: ignore
Expand Down Expand Up @@ -115,7 +122,7 @@ def _parse_type(
):
if common_device_type:
# pre-defined type
device_type = _strip_union(common_device_type)
device_type, _ = _strip_union(common_device_type)
is_device_vector, device_type = _strip_device_vector(device_type)

if ((origin := get_origin(device_type)) and issubclass(origin, Signal)) or (
Expand Down Expand Up @@ -152,7 +159,7 @@ def _sim_common_blocks(device: Device, stripped_type: Optional[Type] = None):
continue

# we'll take the first type in the union which isn't NoneType
sub_device_t = _strip_union(sub_device_t)
sub_device_t, _ = _strip_union(sub_device_t)
is_device_vector, sub_device_t = _strip_device_vector(sub_device_t)
is_signal = (
(origin := get_origin(sub_device_t)) and issubclass(origin, Signal)
Expand Down Expand Up @@ -185,7 +192,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 +233,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 +306,26 @@ 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, included_optional_fields: Tuple[str, ...] = tuple()
):
"""For intializing blocks at __init__ of ``device``."""
for name, device_type in get_type_hints(type(device)).items():
if name in ("_name", "parent"):
continue
device_type, is_optional = _strip_union(device_type)
if is_optional and name not in included_optional_fields:
continue
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)
9 changes: 5 additions & 4 deletions src/ophyd_async/panda/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from .panda import (
CommonPandABlocks,
from .common_panda import (
CommonPandaBlocks,
DataBlock,
PandA,
PcapBlock,
PulseBlock,
SeqBlock,
TimeUnits,
)
from .hdf_panda import HDFPanda
from .panda_controller import PandaPcapController
from .table import (
SeqTable,
Expand All @@ -18,7 +18,8 @@
from .utils import phase_sorter

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

from enum import Enum
from typing import Optional

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


class DataBlock(Device):
hdf_directory: SignalRW[str]
hdf_file_name: SignalRW[str]
num_capture: SignalRW[int]
num_captured: SignalR[int]
capture: SignalRW[bool]
flush_period: SignalRW[float]


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

# In future we may decide not to have a datablock
data: Optional[DataBlock]
49 changes: 49 additions & 0 deletions src/ophyd_async/panda/hdf_panda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations

from typing import Sequence

from ophyd_async.core import (
DEFAULT_TIMEOUT,
DirectoryProvider,
SignalR,
StandardDetector,
)
from ophyd_async.epics.pvi import fill_pvi_entries, pre_initialize_blocks

from .common_panda import CommonPandaBlocks
from .panda_controller import PandaPcapController
from .writers.hdf_writer import PandaHDFWriter


class HDFPanda(CommonPandaBlocks, StandardDetector):
def __init__(
self,
prefix: str,
directory_provider: DirectoryProvider,
config_sigs: Sequence[SignalR] = (),
name: str = "",
):
self._prefix = prefix
self.set_name(name)

pre_initialize_blocks(self, included_optional_fields=("data"))
controller = PandaPcapController(pcap=self.pcap)
writer = PandaHDFWriter(
prefix=prefix,
directory_provider=directory_provider,
name_provider=lambda: name,
panda_device=self,
)
super().__init__(
controller=controller,
writer=writer,
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)
74 changes: 0 additions & 74 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))
4 changes: 2 additions & 2 deletions src/ophyd_async/panda/writers/hdf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
wait_for_value,
)
from ophyd_async.core.signal import observe_value
from ophyd_async.panda.panda import PandA
from ophyd_async.panda import CommonPandaBlocks

from .panda_hdf_file import _HDFDataset, _HDFFile

Expand Down Expand Up @@ -91,7 +91,7 @@ def __init__(
prefix: str,
directory_provider: DirectoryProvider,
name_provider: NameProvider,
panda_device: PandA,
panda_device: CommonPandaBlocks,
) -> None:
self.panda_device = panda_device
self._prefix = prefix
Expand Down
Loading

0 comments on commit a2ef6b0

Please sign in to comment.