ophyd-async 0.7.0a1__py3-none-any.whl → 0.8.0a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +30 -9
  3. ophyd_async/core/_detector.py +5 -10
  4. ophyd_async/core/_device.py +146 -67
  5. ophyd_async/core/_device_filler.py +269 -0
  6. ophyd_async/core/_device_save_loader.py +6 -7
  7. ophyd_async/core/_mock_signal_backend.py +32 -40
  8. ophyd_async/core/_mock_signal_utils.py +22 -16
  9. ophyd_async/core/_protocol.py +28 -8
  10. ophyd_async/core/_readable.py +133 -134
  11. ophyd_async/core/_signal.py +140 -152
  12. ophyd_async/core/_signal_backend.py +131 -64
  13. ophyd_async/core/_soft_signal_backend.py +125 -194
  14. ophyd_async/core/_status.py +22 -6
  15. ophyd_async/core/_table.py +97 -100
  16. ophyd_async/core/_utils.py +79 -18
  17. ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
  18. ophyd_async/epics/adaravis/_aravis_io.py +8 -6
  19. ophyd_async/epics/adcore/_core_io.py +5 -7
  20. ophyd_async/epics/adcore/_hdf_writer.py +2 -2
  21. ophyd_async/epics/adcore/_single_trigger.py +4 -9
  22. ophyd_async/epics/adcore/_utils.py +15 -10
  23. ophyd_async/epics/adkinetix/__init__.py +2 -1
  24. ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
  25. ophyd_async/epics/adkinetix/_kinetix_io.py +4 -5
  26. ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
  27. ophyd_async/epics/adpilatus/_pilatus_io.py +3 -4
  28. ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
  29. ophyd_async/epics/advimba/__init__.py +4 -1
  30. ophyd_async/epics/advimba/_vimba_controller.py +6 -3
  31. ophyd_async/epics/advimba/_vimba_io.py +8 -9
  32. ophyd_async/epics/core/__init__.py +26 -0
  33. ophyd_async/epics/core/_aioca.py +323 -0
  34. ophyd_async/epics/core/_epics_connector.py +53 -0
  35. ophyd_async/epics/core/_epics_device.py +13 -0
  36. ophyd_async/epics/core/_p4p.py +382 -0
  37. ophyd_async/epics/core/_pvi_connector.py +92 -0
  38. ophyd_async/epics/core/_signal.py +171 -0
  39. ophyd_async/epics/core/_util.py +61 -0
  40. ophyd_async/epics/demo/_mover.py +4 -5
  41. ophyd_async/epics/demo/_sensor.py +14 -13
  42. ophyd_async/epics/eiger/_eiger.py +1 -2
  43. ophyd_async/epics/eiger/_eiger_controller.py +1 -1
  44. ophyd_async/epics/eiger/_eiger_io.py +3 -5
  45. ophyd_async/epics/eiger/_odin_io.py +5 -5
  46. ophyd_async/epics/motor.py +4 -5
  47. ophyd_async/epics/signal.py +11 -0
  48. ophyd_async/fastcs/core.py +9 -0
  49. ophyd_async/fastcs/panda/__init__.py +4 -4
  50. ophyd_async/fastcs/panda/_block.py +23 -11
  51. ophyd_async/fastcs/panda/_control.py +3 -5
  52. ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
  53. ophyd_async/fastcs/panda/_table.py +29 -51
  54. ophyd_async/fastcs/panda/_trigger.py +8 -8
  55. ophyd_async/fastcs/panda/_writer.py +4 -7
  56. ophyd_async/plan_stubs/_ensure_connected.py +3 -1
  57. ophyd_async/plan_stubs/_fly.py +2 -2
  58. ophyd_async/plan_stubs/_nd_attributes.py +5 -4
  59. ophyd_async/py.typed +0 -0
  60. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
  61. ophyd_async/sim/demo/_sim_motor.py +3 -4
  62. ophyd_async/tango/__init__.py +2 -4
  63. ophyd_async/tango/base_devices/_base_device.py +76 -144
  64. ophyd_async/tango/demo/_counter.py +8 -18
  65. ophyd_async/tango/demo/_mover.py +5 -6
  66. ophyd_async/tango/signal/__init__.py +2 -4
  67. ophyd_async/tango/signal/_signal.py +29 -50
  68. ophyd_async/tango/signal/_tango_transport.py +38 -40
  69. {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/METADATA +8 -12
  70. ophyd_async-0.8.0a3.dist-info/RECORD +112 -0
  71. {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/WHEEL +1 -1
  72. ophyd_async/epics/pvi/__init__.py +0 -3
  73. ophyd_async/epics/pvi/_pvi.py +0 -338
  74. ophyd_async/epics/signal/__init__.py +0 -21
  75. ophyd_async/epics/signal/_aioca.py +0 -378
  76. ophyd_async/epics/signal/_common.py +0 -57
  77. ophyd_async/epics/signal/_epics_transport.py +0 -34
  78. ophyd_async/epics/signal/_p4p.py +0 -518
  79. ophyd_async/epics/signal/_signal.py +0 -114
  80. ophyd_async-0.7.0a1.dist-info/RECORD +0 -108
  81. {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/LICENSE +0 -0
  82. {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/entry_points.txt +0 -0
  83. {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
1
+ from collections.abc import Sequence
2
+ from typing import Any, get_args, get_origin
3
+
4
+ import numpy as np
5
+
6
+ from ophyd_async.core import (
7
+ SignalBackend,
8
+ SignalDatatypeT,
9
+ SubsetEnum,
10
+ get_dtype,
11
+ get_enum_cls,
12
+ )
13
+
14
+
15
+ def get_supported_values(
16
+ pv: str,
17
+ datatype: type,
18
+ pv_choices: Sequence[str],
19
+ ) -> dict[str, str]:
20
+ enum_cls = get_enum_cls(datatype)
21
+ if not enum_cls:
22
+ raise TypeError(f"{datatype} is not an Enum")
23
+ choices = [v.value for v in enum_cls]
24
+ error_msg = f"{pv} has choices {pv_choices}, but {datatype} requested {choices} "
25
+ if issubclass(enum_cls, SubsetEnum):
26
+ if not set(choices).issubset(pv_choices):
27
+ raise TypeError(error_msg + "to be a subset of them.")
28
+ else:
29
+ if set(choices) != set(pv_choices):
30
+ raise TypeError(error_msg + "to be strictly equal to them.")
31
+
32
+ # Take order from the pv choices
33
+ supported_values = {x: x for x in pv_choices}
34
+ # But override those that we specify via the datatype
35
+ for v in enum_cls:
36
+ supported_values[v.value] = v
37
+ return supported_values
38
+
39
+
40
+ def format_datatype(datatype: Any) -> str:
41
+ if get_origin(datatype) is np.ndarray and get_args(datatype)[0] == tuple[int]:
42
+ dtype = get_dtype(datatype)
43
+ return f"Array1D[np.{dtype.name}]"
44
+ elif get_origin(datatype) is Sequence:
45
+ return f"Sequence[{get_args(datatype)[0].__name__}]"
46
+ elif isinstance(datatype, type):
47
+ return datatype.__name__
48
+ else:
49
+ return str(datatype)
50
+
51
+
52
+ class EpicsSignalBackend(SignalBackend[SignalDatatypeT]):
53
+ def __init__(
54
+ self,
55
+ datatype: type[SignalDatatypeT] | None,
56
+ read_pv: str = "",
57
+ write_pv: str = "",
58
+ ):
59
+ self.read_pv = read_pv
60
+ self.write_pv = write_pv
61
+ super().__init__(datatype)
@@ -8,15 +8,14 @@ from ophyd_async.core import (
8
8
  DEFAULT_TIMEOUT,
9
9
  AsyncStatus,
10
10
  CalculatableTimeout,
11
- ConfigSignal,
12
11
  Device,
13
- HintedSignal,
14
12
  StandardReadable,
15
13
  WatchableAsyncStatus,
16
14
  WatcherUpdate,
17
15
  observe_value,
18
16
  )
19
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
17
+ from ophyd_async.core import StandardReadableFormat as Format
18
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
20
19
 
21
20
 
22
21
  class Mover(StandardReadable, Movable, Stoppable):
@@ -24,9 +23,9 @@ class Mover(StandardReadable, Movable, Stoppable):
24
23
 
25
24
  def __init__(self, prefix: str, name="") -> None:
26
25
  # Define some signals
27
- with self.add_children_as_readables(HintedSignal):
26
+ with self.add_children_as_readables(Format.HINTED_SIGNAL):
28
27
  self.readback = epics_signal_r(float, prefix + "Readback")
29
- with self.add_children_as_readables(ConfigSignal):
28
+ with self.add_children_as_readables(Format.CONFIG_SIGNAL):
30
29
  self.velocity = epics_signal_rw(float, prefix + "Velocity")
31
30
  self.units = epics_signal_r(str, prefix + "Readback.EGU")
32
31
  self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
@@ -1,10 +1,17 @@
1
- from enum import Enum
1
+ from typing import Annotated as A
2
2
 
3
- from ophyd_async.core import ConfigSignal, DeviceVector, HintedSignal, StandardReadable
4
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
3
+ from ophyd_async.core import (
4
+ DeviceVector,
5
+ SignalR,
6
+ SignalRW,
7
+ StandardReadable,
8
+ StrictEnum,
9
+ )
10
+ from ophyd_async.core import StandardReadableFormat as Format
11
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix
5
12
 
6
13
 
7
- class EnergyMode(str, Enum):
14
+ class EnergyMode(StrictEnum):
8
15
  """Energy mode for `Sensor`"""
9
16
 
10
17
  #: Low energy mode
@@ -13,17 +20,11 @@ class EnergyMode(str, Enum):
13
20
  high = "High Energy"
14
21
 
15
22
 
16
- class Sensor(StandardReadable):
23
+ class Sensor(StandardReadable, EpicsDevice):
17
24
  """A demo sensor that produces a scalar value based on X and Y Movers"""
18
25
 
19
- def __init__(self, prefix: str, name="") -> None:
20
- # Define some signals
21
- with self.add_children_as_readables(HintedSignal):
22
- self.value = epics_signal_r(float, prefix + "Value")
23
- with self.add_children_as_readables(ConfigSignal):
24
- self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
25
-
26
- super().__init__(name=name)
26
+ value: A[SignalR[float], PvSuffix("Value"), Format.HINTED_SIGNAL]
27
+ mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
27
28
 
28
29
 
29
30
  class SensorGroup(StandardReadable):
@@ -1,7 +1,6 @@
1
1
  from pydantic import Field
2
2
 
3
- from ophyd_async.core import AsyncStatus, PathProvider, StandardDetector
4
- from ophyd_async.core._detector import TriggerInfo
3
+ from ophyd_async.core import AsyncStatus, PathProvider, StandardDetector, TriggerInfo
5
4
 
6
5
  from ._eiger_controller import EigerController
7
6
  from ._eiger_io import EigerDriverIO
@@ -4,9 +4,9 @@ from ophyd_async.core import (
4
4
  DEFAULT_TIMEOUT,
5
5
  DetectorController,
6
6
  DetectorTrigger,
7
+ TriggerInfo,
7
8
  set_and_wait_for_other_value,
8
9
  )
9
- from ophyd_async.core._detector import TriggerInfo
10
10
 
11
11
  from ._eiger_io import EigerDriverIO, EigerTriggerMode
12
12
 
@@ -1,10 +1,8 @@
1
- from enum import Enum
1
+ from ophyd_async.core import Device, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv, epics_signal_w
2
3
 
3
- from ophyd_async.core import Device
4
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv, epics_signal_w
5
4
 
6
-
7
- class EigerTriggerMode(str, Enum):
5
+ class EigerTriggerMode(StrictEnum):
8
6
  internal = "ints"
9
7
  edge = "exts"
10
8
  gate = "exte"
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  from collections.abc import AsyncGenerator, AsyncIterator
3
- from enum import Enum
4
3
 
5
4
  from bluesky.protocols import StreamAsset
6
5
  from event_model import DataKey
@@ -12,17 +11,18 @@ from ophyd_async.core import (
12
11
  DeviceVector,
13
12
  NameProvider,
14
13
  PathProvider,
14
+ StrictEnum,
15
15
  observe_value,
16
16
  set_and_wait_for_value,
17
17
  )
18
- from ophyd_async.epics.signal import (
18
+ from ophyd_async.epics.core import (
19
19
  epics_signal_r,
20
20
  epics_signal_rw,
21
21
  epics_signal_rw_rbv,
22
22
  )
23
23
 
24
24
 
25
- class Writing(str, Enum):
25
+ class Writing(StrictEnum):
26
26
  ON = "ON"
27
27
  OFF = "OFF"
28
28
 
@@ -101,10 +101,10 @@ class OdinWriter(DetectorWriter):
101
101
  return {
102
102
  "data": DataKey(
103
103
  source=self._drv.file_name.source,
104
- shape=data_shape,
104
+ shape=list(data_shape),
105
105
  dtype="array",
106
106
  # TODO: Use correct type based on eiger https://github.com/bluesky/ophyd-async/issues/529
107
- dtype_numpy="<u2", # type: ignore
107
+ dtype_numpy="<u2",
108
108
  external="STREAM:",
109
109
  )
110
110
  }
@@ -14,14 +14,13 @@ from ophyd_async.core import (
14
14
  DEFAULT_TIMEOUT,
15
15
  AsyncStatus,
16
16
  CalculatableTimeout,
17
- ConfigSignal,
18
- HintedSignal,
19
17
  StandardReadable,
20
18
  WatchableAsyncStatus,
21
19
  WatcherUpdate,
22
20
  observe_value,
23
21
  )
24
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
22
+ from ophyd_async.core import StandardReadableFormat as Format
23
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
25
24
 
26
25
 
27
26
  class MotorLimitsException(Exception):
@@ -61,11 +60,11 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
61
60
 
62
61
  def __init__(self, prefix: str, name="") -> None:
63
62
  # Define some signals
64
- with self.add_children_as_readables(ConfigSignal):
63
+ with self.add_children_as_readables(Format.CONFIG_SIGNAL):
65
64
  self.motor_egu = epics_signal_r(str, prefix + ".EGU")
66
65
  self.velocity = epics_signal_rw(float, prefix + ".VELO")
67
66
 
68
- with self.add_children_as_readables(HintedSignal):
67
+ with self.add_children_as_readables(Format.HINTED_SIGNAL):
69
68
  self.user_readback = epics_signal_r(float, prefix + ".RBV")
70
69
 
71
70
  self.user_setpoint = epics_signal_rw(float, prefix + ".VAL")
@@ -0,0 +1,11 @@
1
+ # back compat
2
+ import warnings
3
+
4
+ from .core import * # noqa: F403
5
+
6
+ warnings.warn(
7
+ DeprecationWarning(
8
+ "Use `ophyd_async.epics.core` instead of `ophyd_async.epics.signal` and `pvi`"
9
+ ),
10
+ stacklevel=2,
11
+ )
@@ -0,0 +1,9 @@
1
+ from ophyd_async.core import Device, DeviceConnector
2
+ from ophyd_async.epics.core import PviDeviceConnector
3
+
4
+
5
+ def fastcs_connector(device: Device, uri: str) -> DeviceConnector:
6
+ # TODO: add Tango support based on uri scheme
7
+ connector = PviDeviceConnector(uri)
8
+ connector.create_children_from_annotations(device)
9
+ return connector
@@ -1,10 +1,10 @@
1
1
  from ._block import (
2
+ BitMux,
2
3
  CommonPandaBlocks,
3
4
  DataBlock,
4
- EnableDisableOptions,
5
5
  PcapBlock,
6
6
  PcompBlock,
7
- PcompDirectionOptions,
7
+ PcompDirection,
8
8
  PulseBlock,
9
9
  SeqBlock,
10
10
  TimeUnits,
@@ -29,10 +29,10 @@ from ._writer import PandaHDFWriter
29
29
  __all__ = [
30
30
  "CommonPandaBlocks",
31
31
  "DataBlock",
32
- "EnableDisableOptions",
32
+ "BitMux",
33
33
  "PcapBlock",
34
34
  "PcompBlock",
35
- "PcompDirectionOptions",
35
+ "PcompDirection",
36
36
  "PulseBlock",
37
37
  "SeqBlock",
38
38
  "TimeUnits",
@@ -1,10 +1,19 @@
1
- from __future__ import annotations
1
+ from ophyd_async.core import (
2
+ Device,
3
+ DeviceVector,
4
+ SignalR,
5
+ SignalRW,
6
+ StrictEnum,
7
+ SubsetEnum,
8
+ )
2
9
 
3
- from enum import Enum
10
+ from ._table import DatasetTable, SeqTable
4
11
 
5
- from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW, SubsetEnum
6
12
 
7
- from ._table import DatasetTable, SeqTable
13
+ class CaptureMode(StrictEnum):
14
+ FIRST_N = "FIRST_N"
15
+ LAST_N = "LAST_N"
16
+ FOREVER = "FOREVER"
8
17
 
9
18
 
10
19
  class DataBlock(Device):
@@ -15,6 +24,7 @@ class DataBlock(Device):
15
24
  num_captured: SignalR[int]
16
25
  create_directory: SignalRW[int]
17
26
  directory_exists: SignalR[bool]
27
+ capture_mode: SignalRW[CaptureMode]
18
28
  capture: SignalRW[bool]
19
29
  flush_period: SignalRW[float]
20
30
  datasets: SignalR[DatasetTable]
@@ -25,26 +35,28 @@ class PulseBlock(Device):
25
35
  width: SignalRW[float]
26
36
 
27
37
 
28
- class PcompDirectionOptions(str, Enum):
38
+ class PcompDirection(StrictEnum):
29
39
  positive = "Positive"
30
40
  negative = "Negative"
31
41
  either = "Either"
32
42
 
33
43
 
34
- EnableDisableOptions = SubsetEnum["ZERO", "ONE"]
44
+ class BitMux(SubsetEnum):
45
+ zero = "ZERO"
46
+ one = "ONE"
35
47
 
36
48
 
37
49
  class PcompBlock(Device):
38
50
  active: SignalR[bool]
39
- dir: SignalRW[PcompDirectionOptions]
40
- enable: SignalRW[EnableDisableOptions]
51
+ dir: SignalRW[PcompDirection]
52
+ enable: SignalRW[BitMux]
41
53
  pulses: SignalRW[int]
42
54
  start: SignalRW[int]
43
55
  step: SignalRW[int]
44
56
  width: SignalRW[int]
45
57
 
46
58
 
47
- class TimeUnits(str, Enum):
59
+ class TimeUnits(StrictEnum):
48
60
  min = "min"
49
61
  s = "s"
50
62
  ms = "ms"
@@ -53,11 +65,11 @@ class TimeUnits(str, Enum):
53
65
 
54
66
  class SeqBlock(Device):
55
67
  table: SignalRW[SeqTable]
56
- active: SignalRW[bool]
68
+ active: SignalR[bool]
57
69
  repeats: SignalRW[int]
58
70
  prescale: SignalRW[float]
59
71
  prescale_units: SignalRW[TimeUnits]
60
- enable: SignalRW[EnableDisableOptions]
72
+ enable: SignalRW[BitMux]
61
73
 
62
74
 
63
75
  class PcapBlock(Device):
@@ -1,12 +1,10 @@
1
- import asyncio
2
-
3
1
  from ophyd_async.core import (
2
+ AsyncStatus,
4
3
  DetectorController,
5
4
  DetectorTrigger,
5
+ TriggerInfo,
6
6
  wait_for_value,
7
7
  )
8
- from ophyd_async.core._detector import TriggerInfo
9
- from ophyd_async.core._status import AsyncStatus
10
8
 
11
9
  from ._block import PcapBlock
12
10
 
@@ -33,5 +31,5 @@ class PandaPcapController(DetectorController):
33
31
  pass
34
32
 
35
33
  async def disarm(self):
36
- await asyncio.gather(self.pcap.arm.set(False))
34
+ await self.pcap.arm.set(False)
37
35
  await wait_for_value(self.pcap.active, False, timeout=1)
@@ -2,8 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Sequence
4
4
 
5
- from ophyd_async.core import DEFAULT_TIMEOUT, PathProvider, SignalR, StandardDetector
6
- from ophyd_async.epics.pvi import create_children_from_annotations, fill_pvi_entries
5
+ from ophyd_async.core import PathProvider, SignalR, StandardDetector
6
+ from ophyd_async.fastcs.core import fastcs_connector
7
7
 
8
8
  from ._block import CommonPandaBlocks
9
9
  from ._control import PandaPcapController
@@ -18,12 +18,10 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
18
18
  config_sigs: Sequence[SignalR] = (),
19
19
  name: str = "",
20
20
  ):
21
- self._prefix = prefix
22
-
23
- create_children_from_annotations(self)
21
+ # This has to be first so we make self.pcap
22
+ connector = fastcs_connector(self, prefix)
24
23
  controller = PandaPcapController(pcap=self.pcap)
25
24
  writer = PandaHDFWriter(
26
- prefix=prefix,
27
25
  path_provider=path_provider,
28
26
  name_provider=lambda: name,
29
27
  panda_data_block=self.data,
@@ -33,17 +31,5 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
33
31
  writer=writer,
34
32
  config_sigs=config_sigs,
35
33
  name=name,
36
- )
37
-
38
- async def connect(
39
- self,
40
- mock: bool = False,
41
- timeout: float = DEFAULT_TIMEOUT,
42
- force_reconnect: bool = False,
43
- ):
44
- # TODO: this doesn't support caching
45
- # https://github.com/bluesky/ophyd-async/issues/472
46
- await fill_pvi_entries(self, self._prefix + "PVI", timeout=timeout, mock=mock)
47
- await super().connect(
48
- mock=mock, timeout=timeout, force_reconnect=force_reconnect
34
+ connector=connector,
49
35
  )
@@ -1,27 +1,22 @@
1
1
  from collections.abc import Sequence
2
- from enum import Enum
3
- from typing import Annotated
4
2
 
5
3
  import numpy as np
6
- import numpy.typing as npt
7
- from pydantic import Field, model_validator
8
- from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation
9
- from typing_extensions import TypedDict
4
+ from pydantic import model_validator
10
5
 
11
- from ophyd_async.core import Table
6
+ from ophyd_async.core import Array1D, StrictEnum, Table
12
7
 
13
8
 
14
- class PandaHdf5DatasetType(str, Enum):
9
+ class PandaHdf5DatasetType(StrictEnum):
15
10
  FLOAT_64 = "float64"
16
11
  UINT_32 = "uint32"
17
12
 
18
13
 
19
- class DatasetTable(TypedDict):
20
- name: npt.NDArray[np.str_]
14
+ class DatasetTable(Table):
15
+ name: Sequence[str]
21
16
  hdf5_type: Sequence[PandaHdf5DatasetType]
22
17
 
23
18
 
24
- class SeqTrigger(str, Enum):
19
+ class SeqTrigger(StrictEnum):
25
20
  IMMEDIATE = "Immediate"
26
21
  BITA_0 = "BITA=0"
27
22
  BITA_1 = "BITA=1"
@@ -37,45 +32,27 @@ class SeqTrigger(str, Enum):
37
32
  POSC_LT = "POSC<=POSITION"
38
33
 
39
34
 
40
- PydanticNp1DArrayInt32 = Annotated[
41
- np.ndarray[tuple[int], np.dtype[np.int32]],
42
- NpArrayPydanticAnnotation.factory(
43
- data_type=np.int32, dimensions=1, strict_data_typing=False
44
- ),
45
- Field(default_factory=lambda: np.array([], np.int32)),
46
- ]
47
- PydanticNp1DArrayBool = Annotated[
48
- np.ndarray[tuple[int], np.dtype[np.bool_]],
49
- NpArrayPydanticAnnotation.factory(
50
- data_type=np.bool_, dimensions=1, strict_data_typing=False
51
- ),
52
- Field(default_factory=lambda: np.array([], dtype=np.bool_)),
53
- ]
54
- TriggerStr = Annotated[Sequence[SeqTrigger], Field(default_factory=list)]
55
-
56
-
57
35
  class SeqTable(Table):
58
- repeats: PydanticNp1DArrayInt32
59
- trigger: TriggerStr
60
- position: PydanticNp1DArrayInt32
61
- time1: PydanticNp1DArrayInt32
62
- outa1: PydanticNp1DArrayBool
63
- outb1: PydanticNp1DArrayBool
64
- outc1: PydanticNp1DArrayBool
65
- outd1: PydanticNp1DArrayBool
66
- oute1: PydanticNp1DArrayBool
67
- outf1: PydanticNp1DArrayBool
68
- time2: PydanticNp1DArrayInt32
69
- outa2: PydanticNp1DArrayBool
70
- outb2: PydanticNp1DArrayBool
71
- outc2: PydanticNp1DArrayBool
72
- outd2: PydanticNp1DArrayBool
73
- oute2: PydanticNp1DArrayBool
74
- outf2: PydanticNp1DArrayBool
36
+ repeats: Array1D[np.uint16]
37
+ trigger: Sequence[SeqTrigger]
38
+ position: Array1D[np.int32]
39
+ time1: Array1D[np.uint32]
40
+ outa1: Array1D[np.bool_]
41
+ outb1: Array1D[np.bool_]
42
+ outc1: Array1D[np.bool_]
43
+ outd1: Array1D[np.bool_]
44
+ oute1: Array1D[np.bool_]
45
+ outf1: Array1D[np.bool_]
46
+ time2: Array1D[np.uint32]
47
+ outa2: Array1D[np.bool_]
48
+ outb2: Array1D[np.bool_]
49
+ outc2: Array1D[np.bool_]
50
+ outd2: Array1D[np.bool_]
51
+ oute2: Array1D[np.bool_]
52
+ outf2: Array1D[np.bool_]
75
53
 
76
- @classmethod
77
- def row( # type: ignore
78
- cls,
54
+ @staticmethod
55
+ def row(
79
56
  *,
80
57
  repeats: int = 1,
81
58
  trigger: str = SeqTrigger.IMMEDIATE,
@@ -95,7 +72,8 @@ class SeqTable(Table):
95
72
  oute2: bool = False,
96
73
  outf2: bool = False,
97
74
  ) -> "SeqTable":
98
- return Table.row(**locals())
75
+ # Let pydantic do the conversions for us
76
+ return SeqTable(**{k: [v] for k, v in locals().items()}) # type: ignore
99
77
 
100
78
  @model_validator(mode="after")
101
79
  def validate_max_length(self) -> "SeqTable":
@@ -104,6 +82,6 @@ class SeqTable(Table):
104
82
  the pydantic field doesn't work
105
83
  """
106
84
 
107
- first_length = len(next(iter(self))[1])
108
- assert 0 <= first_length < 4096, f"Length {first_length} not in range."
85
+ first_length = len(self)
86
+ assert first_length <= 4096, f"Length {first_length} is too long"
109
87
  return self
@@ -4,7 +4,7 @@ from pydantic import BaseModel, Field
4
4
 
5
5
  from ophyd_async.core import FlyerController, wait_for_value
6
6
 
7
- from ._block import PcompBlock, PcompDirectionOptions, SeqBlock, TimeUnits
7
+ from ._block import BitMux, PcompBlock, PcompDirection, SeqBlock, TimeUnits
8
8
  from ._table import SeqTable
9
9
 
10
10
 
@@ -21,7 +21,7 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
21
21
  async def prepare(self, value: SeqTableInfo):
22
22
  await asyncio.gather(
23
23
  self.seq.prescale_units.set(TimeUnits.us),
24
- self.seq.enable.set("ZERO"),
24
+ self.seq.enable.set(BitMux.zero),
25
25
  )
26
26
  await asyncio.gather(
27
27
  self.seq.prescale.set(value.prescale_as_us),
@@ -30,14 +30,14 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
30
30
  )
31
31
 
32
32
  async def kickoff(self) -> None:
33
- await self.seq.enable.set("ONE")
33
+ await self.seq.enable.set(BitMux.one)
34
34
  await wait_for_value(self.seq.active, True, timeout=1)
35
35
 
36
36
  async def complete(self) -> None:
37
37
  await wait_for_value(self.seq.active, False, timeout=None)
38
38
 
39
39
  async def stop(self):
40
- await self.seq.enable.set("ZERO")
40
+ await self.seq.enable.set(BitMux.zero)
41
41
  await wait_for_value(self.seq.active, False, timeout=1)
42
42
 
43
43
 
@@ -54,7 +54,7 @@ class PcompInfo(BaseModel):
54
54
  ),
55
55
  ge=0,
56
56
  )
57
- direction: PcompDirectionOptions = Field(
57
+ direction: PcompDirection = Field(
58
58
  description=(
59
59
  "Specifies which direction the motor counts should be "
60
60
  "moving. Pulses won't be sent unless the values are moving in "
@@ -68,7 +68,7 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
68
68
  self.pcomp = pcomp
69
69
 
70
70
  async def prepare(self, value: PcompInfo):
71
- await self.pcomp.enable.set("ZERO")
71
+ await self.pcomp.enable.set(BitMux.zero)
72
72
  await asyncio.gather(
73
73
  self.pcomp.start.set(value.start_postion),
74
74
  self.pcomp.width.set(value.pulse_width),
@@ -78,12 +78,12 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
78
78
  )
79
79
 
80
80
  async def kickoff(self) -> None:
81
- await self.pcomp.enable.set("ONE")
81
+ await self.pcomp.enable.set(BitMux.one)
82
82
  await wait_for_value(self.pcomp.active, True, timeout=1)
83
83
 
84
84
  async def complete(self, timeout: float | None = None) -> None:
85
85
  await wait_for_value(self.pcomp.active, False, timeout=timeout)
86
86
 
87
87
  async def stop(self):
88
- await self.pcomp.enable.set("ZERO")
88
+ await self.pcomp.enable.set(BitMux.zero)
89
89
  await wait_for_value(self.pcomp.active, False, timeout=1)
@@ -17,7 +17,7 @@ from ophyd_async.core import (
17
17
  wait_for_value,
18
18
  )
19
19
 
20
- from ._block import DataBlock
20
+ from ._block import CaptureMode, DataBlock
21
21
 
22
22
 
23
23
  class PandaHDFWriter(DetectorWriter):
@@ -25,13 +25,11 @@ class PandaHDFWriter(DetectorWriter):
25
25
 
26
26
  def __init__(
27
27
  self,
28
- prefix: str,
29
28
  path_provider: PathProvider,
30
29
  name_provider: NameProvider,
31
30
  panda_data_block: DataBlock,
32
31
  ) -> None:
33
32
  self.panda_data_block = panda_data_block
34
- self._prefix = prefix
35
33
  self._path_provider = path_provider
36
34
  self._name_provider = name_provider
37
35
  self._datasets: list[HDFDataset] = []
@@ -58,7 +56,7 @@ class PandaHDFWriter(DetectorWriter):
58
56
  self.panda_data_block.hdf_file_name.set(
59
57
  f"{info.filename}.h5",
60
58
  ),
61
- self.panda_data_block.num_capture.set(0),
59
+ self.panda_data_block.capture_mode.set(CaptureMode.FOREVER),
62
60
  )
63
61
 
64
62
  # Make sure that directory exists or has been created.
@@ -89,8 +87,7 @@ class PandaHDFWriter(DetectorWriter):
89
87
  shape=list(ds.shape),
90
88
  dtype="array" if ds.shape != [1] else "number",
91
89
  # PandA data should always be written as Float64
92
- # Ignore type check until https://github.com/bluesky/event-model/issues/308
93
- dtype_numpy="<f8", # type: ignore
90
+ dtype_numpy="<f8",
94
91
  external="STREAM:",
95
92
  )
96
93
  for ds in self._datasets
@@ -110,7 +107,7 @@ class PandaHDFWriter(DetectorWriter):
110
107
  HDFDataset(
111
108
  dataset_name, "/" + dataset_name, [1], multiplier=1, chunk_shape=(1024,)
112
109
  )
113
- for dataset_name in capture_table["name"]
110
+ for dataset_name in capture_table.name
114
111
  ]
115
112
 
116
113
  # Warn user if dataset table is empty in PandA