ophyd-async 0.4.0__py3-none-any.whl → 0.5.1__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 (95) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +86 -73
  3. ophyd_async/core/{detector.py → _detector.py} +42 -36
  4. ophyd_async/core/{device.py → _device.py} +1 -1
  5. ophyd_async/core/{device_save_loader.py → _device_save_loader.py} +3 -3
  6. ophyd_async/core/{flyer.py → _flyer.py} +6 -8
  7. ophyd_async/{epics/areadetector/writers/general_hdffile.py → core/_hdf_dataset.py} +4 -8
  8. ophyd_async/{log.py → core/_log.py} +11 -3
  9. ophyd_async/core/{mock_signal_backend.py → _mock_signal_backend.py} +3 -3
  10. ophyd_async/core/{mock_signal_utils.py → _mock_signal_utils.py} +7 -7
  11. ophyd_async/{protocols.py → core/_protocol.py} +1 -1
  12. ophyd_async/core/_providers.py +24 -37
  13. ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
  14. ophyd_async/core/{signal.py → _signal.py} +79 -35
  15. ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
  16. ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +3 -12
  17. ophyd_async/core/{async_status.py → _status.py} +3 -11
  18. ophyd_async/epics/adaravis/__init__.py +9 -0
  19. ophyd_async/epics/{areadetector/aravis.py → adaravis/_aravis.py} +9 -11
  20. ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +7 -10
  21. ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
  22. ophyd_async/epics/adcore/__init__.py +47 -0
  23. ophyd_async/epics/adcore/_core_io.py +138 -0
  24. ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +16 -52
  25. ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +54 -29
  26. ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
  27. ophyd_async/epics/adcore/_utils.py +132 -0
  28. ophyd_async/epics/adkinetix/__init__.py +9 -0
  29. ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +9 -11
  30. ophyd_async/epics/{areadetector/controllers/kinetix_controller.py → adkinetix/_kinetix_controller.py} +6 -9
  31. ophyd_async/epics/{areadetector/drivers/kinetix_driver.py → adkinetix/_kinetix_io.py} +9 -5
  32. ophyd_async/epics/adpilatus/__init__.py +11 -0
  33. ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +10 -14
  34. ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +15 -17
  35. ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +6 -4
  36. ophyd_async/epics/adsimdetector/__init__.py +7 -0
  37. ophyd_async/epics/{demo/demo_ad_sim_detector.py → adsimdetector/_sim.py} +10 -11
  38. ophyd_async/epics/{areadetector/controllers/ad_sim_controller.py → adsimdetector/_sim_controller.py} +8 -14
  39. ophyd_async/epics/advimba/__init__.py +9 -0
  40. ophyd_async/epics/{areadetector/vimba.py → advimba/_vimba.py} +9 -9
  41. ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +9 -17
  42. ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +11 -8
  43. ophyd_async/epics/demo/__init__.py +9 -132
  44. ophyd_async/epics/demo/_mover.py +97 -0
  45. ophyd_async/epics/demo/_sensor.py +36 -0
  46. ophyd_async/epics/{motion/motor.py → motor.py} +28 -14
  47. ophyd_async/epics/pvi/__init__.py +2 -2
  48. ophyd_async/epics/pvi/{pvi.py → _pvi.py} +17 -14
  49. ophyd_async/epics/signal/__init__.py +7 -1
  50. ophyd_async/epics/{_backend → signal}/_aioca.py +15 -7
  51. ophyd_async/epics/{_backend/common.py → signal/_common.py} +2 -2
  52. ophyd_async/epics/signal/_epics_transport.py +3 -3
  53. ophyd_async/epics/{_backend → signal}/_p4p.py +18 -14
  54. ophyd_async/epics/signal/{signal.py → _signal.py} +10 -9
  55. ophyd_async/fastcs/odin/__init__.py +0 -0
  56. ophyd_async/{panda → fastcs/panda}/__init__.py +20 -15
  57. ophyd_async/{panda/_common_blocks.py → fastcs/panda/_block.py} +5 -3
  58. ophyd_async/{panda/_panda_controller.py → fastcs/panda/_control.py} +2 -1
  59. ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +5 -10
  60. ophyd_async/{panda → fastcs/panda}/_trigger.py +3 -7
  61. ophyd_async/{panda/writers/_hdf_writer.py → fastcs/panda/_writer.py} +36 -28
  62. ophyd_async/plan_stubs/__init__.py +5 -2
  63. ophyd_async/plan_stubs/{ensure_connected.py → _ensure_connected.py} +1 -2
  64. ophyd_async/plan_stubs/{fly.py → _fly.py} +13 -9
  65. ophyd_async/plan_stubs/_nd_attributes.py +63 -0
  66. ophyd_async/sim/__init__.py +0 -11
  67. ophyd_async/sim/demo/__init__.py +18 -2
  68. ophyd_async/sim/demo/_pattern_detector/__init__.py +13 -0
  69. ophyd_async/sim/{sim_pattern_generator.py → demo/_pattern_detector/_pattern_detector.py} +8 -8
  70. ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +9 -7
  71. ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +4 -4
  72. ophyd_async/sim/{pattern_generator.py → demo/_pattern_detector/_pattern_generator.py} +13 -11
  73. ophyd_async/sim/demo/{sim_motor.py → _sim_motor.py} +7 -5
  74. ophyd_async/sim/testing/__init__.py +0 -0
  75. ophyd_async/tango/__init__.py +0 -0
  76. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/METADATA +46 -44
  77. ophyd_async-0.5.1.dist-info/RECORD +90 -0
  78. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/WHEEL +1 -1
  79. ophyd_async/epics/areadetector/__init__.py +0 -23
  80. ophyd_async/epics/areadetector/controllers/__init__.py +0 -5
  81. ophyd_async/epics/areadetector/drivers/__init__.py +0 -23
  82. ophyd_async/epics/areadetector/utils.py +0 -104
  83. ophyd_async/epics/areadetector/writers/__init__.py +0 -5
  84. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -43
  85. ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -68
  86. ophyd_async/epics/motion/__init__.py +0 -3
  87. ophyd_async/panda/writers/__init__.py +0 -3
  88. ophyd_async-0.4.0.dist-info/RECORD +0 -84
  89. /ophyd_async/core/{utils.py → _utils.py} +0 -0
  90. /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
  91. /ophyd_async/{panda → fastcs/panda}/_table.py +0 -0
  92. /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
  93. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/LICENSE +0 -0
  94. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/entry_points.txt +0 -0
  95. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,97 @@
1
+ import asyncio
2
+
3
+ import numpy as np
4
+ from bluesky.protocols import Movable, Stoppable
5
+
6
+ from ophyd_async.core import (
7
+ DEFAULT_TIMEOUT,
8
+ AsyncStatus,
9
+ CalculatableTimeout,
10
+ CalculateTimeout,
11
+ ConfigSignal,
12
+ Device,
13
+ HintedSignal,
14
+ StandardReadable,
15
+ WatchableAsyncStatus,
16
+ WatcherUpdate,
17
+ observe_value,
18
+ )
19
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
20
+
21
+
22
+ class Mover(StandardReadable, Movable, Stoppable):
23
+ """A demo movable that moves based on velocity"""
24
+
25
+ def __init__(self, prefix: str, name="") -> None:
26
+ # Define some signals
27
+ with self.add_children_as_readables(HintedSignal):
28
+ self.readback = epics_signal_r(float, prefix + "Readback")
29
+ with self.add_children_as_readables(ConfigSignal):
30
+ self.velocity = epics_signal_rw(float, prefix + "Velocity")
31
+ self.units = epics_signal_r(str, prefix + "Readback.EGU")
32
+ self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
33
+ self.precision = epics_signal_r(int, prefix + "Readback.PREC")
34
+ # Signals that collide with standard methods should have a trailing underscore
35
+ self.stop_ = epics_signal_x(prefix + "Stop.PROC")
36
+ # Whether set() should complete successfully or not
37
+ self._set_success = True
38
+
39
+ super().__init__(name=name)
40
+
41
+ def set_name(self, name: str):
42
+ super().set_name(name)
43
+ # Readback should be named the same as its parent in read()
44
+ self.readback.set_name(name)
45
+
46
+ @WatchableAsyncStatus.wrap
47
+ async def set(
48
+ self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
49
+ ):
50
+ self._set_success = True
51
+ old_position, units, precision, velocity = await asyncio.gather(
52
+ self.setpoint.get_value(),
53
+ self.units.get_value(),
54
+ self.precision.get_value(),
55
+ self.velocity.get_value(),
56
+ )
57
+ if timeout is CalculateTimeout:
58
+ assert velocity > 0, "Mover has zero velocity"
59
+ timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
60
+ # Make an Event that will be set on completion, and a Status that will
61
+ # error if not done in time
62
+ done = asyncio.Event()
63
+ done_status = AsyncStatus(asyncio.wait_for(done.wait(), timeout))
64
+ # Wait for the value to set, but don't wait for put completion callback
65
+ await self.setpoint.set(new_position, wait=False)
66
+ async for current_position in observe_value(
67
+ self.readback, done_status=done_status
68
+ ):
69
+ yield WatcherUpdate(
70
+ current=current_position,
71
+ initial=old_position,
72
+ target=new_position,
73
+ name=self.name,
74
+ unit=units,
75
+ precision=precision,
76
+ )
77
+ if np.isclose(current_position, new_position):
78
+ done.set()
79
+ break
80
+ if not self._set_success:
81
+ raise RuntimeError("Motor was stopped")
82
+
83
+ async def stop(self, success=True):
84
+ self._set_success = success
85
+ status = self.stop_.trigger()
86
+ await status
87
+
88
+
89
+ class SampleStage(Device):
90
+ """A demo sample stage with X and Y movables"""
91
+
92
+ def __init__(self, prefix: str, name="") -> None:
93
+ # Define some child Devices
94
+ self.x = Mover(prefix + "X:")
95
+ self.y = Mover(prefix + "Y:")
96
+ # Set name of device and child devices
97
+ super().__init__(name=name)
@@ -0,0 +1,36 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import ConfigSignal, DeviceVector, HintedSignal, StandardReadable
4
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
5
+
6
+
7
+ class EnergyMode(str, Enum):
8
+ """Energy mode for `Sensor`"""
9
+
10
+ #: Low energy mode
11
+ low = "Low Energy"
12
+ #: High energy mode
13
+ high = "High Energy"
14
+
15
+
16
+ class Sensor(StandardReadable):
17
+ """A demo sensor that produces a scalar value based on X and Y Movers"""
18
+
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)
27
+
28
+
29
+ class SensorGroup(StandardReadable):
30
+ def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
31
+ with self.add_children_as_readables():
32
+ self.sensors = DeviceVector(
33
+ {i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
34
+ )
35
+
36
+ super().__init__(name)
@@ -1,25 +1,28 @@
1
1
  import asyncio
2
2
  from typing import Optional
3
3
 
4
- from bluesky.protocols import Flyable, Movable, Preparable, Stoppable
4
+ from bluesky.protocols import (
5
+ Flyable,
6
+ Locatable,
7
+ Location,
8
+ Preparable,
9
+ Stoppable,
10
+ )
5
11
  from pydantic import BaseModel, Field
6
12
 
7
13
  from ophyd_async.core import (
14
+ DEFAULT_TIMEOUT,
15
+ AsyncStatus,
16
+ CalculatableTimeout,
17
+ CalculateTimeout,
8
18
  ConfigSignal,
9
19
  HintedSignal,
10
20
  StandardReadable,
11
21
  WatchableAsyncStatus,
12
- )
13
- from ophyd_async.core.async_status import AsyncStatus
14
- from ophyd_async.core.signal import observe_value
15
- from ophyd_async.core.utils import (
16
- DEFAULT_TIMEOUT,
17
- CalculatableTimeout,
18
- CalculateTimeout,
19
22
  WatcherUpdate,
23
+ observe_value,
20
24
  )
21
-
22
- from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
25
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
23
26
 
24
27
 
25
28
  class MotorLimitsException(Exception):
@@ -54,7 +57,7 @@ class FlyMotorInfo(BaseModel):
54
57
  timeout: CalculatableTimeout = Field(frozen=True, default=CalculateTimeout)
55
58
 
56
59
 
57
- class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
60
+ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
58
61
  """Device that moves a motor record"""
59
62
 
60
63
  def __init__(self, prefix: str, name="") -> None:
@@ -116,6 +119,7 @@ class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
116
119
  )
117
120
 
118
121
  await self.set(fly_prepared_position)
122
+ await self.velocity.set(fly_velocity)
119
123
 
120
124
  @AsyncStatus.wrap
121
125
  async def kickoff(self):
@@ -182,7 +186,7 @@ class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
182
186
  async def _prepare_velocity(
183
187
  self, start_position: float, end_position: float, time_for_move: float
184
188
  ) -> float:
185
- fly_velocity = (start_position - end_position) / time_for_move
189
+ fly_velocity = (end_position - start_position) / time_for_move
186
190
  max_speed, egu = await asyncio.gather(
187
191
  self.max_velocity.get_value(), self.motor_egu.get_value()
188
192
  )
@@ -191,15 +195,25 @@ class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
191
195
  f"Motor speed of {abs(fly_velocity)} {egu}/s was requested for a motor "
192
196
  f" with max speed of {max_speed} {egu}/s"
193
197
  )
194
- await self.velocity.set(abs(fly_velocity))
198
+ # move to prepare position at maximum velocity
199
+ await self.velocity.set(abs(max_speed))
195
200
  return fly_velocity
196
201
 
202
+ async def locate(self) -> Location[float]:
203
+ location: Location = {
204
+ "setpoint": await self.user_setpoint.get_value(),
205
+ "readback": await self.user_readback.get_value(),
206
+ }
207
+ return location
208
+
197
209
  async def _prepare_motor_path(
198
210
  self, fly_velocity: float, start_position: float, end_position: float
199
211
  ) -> float:
200
212
  # Distance required for motor to accelerate from stationary to fly_velocity, and
201
213
  # distance required for motor to decelerate from fly_velocity to stationary
202
- run_up_distance = (await self.acceleration_time.get_value()) * fly_velocity
214
+ run_up_distance = (
215
+ (await self.acceleration_time.get_value()) * fly_velocity * 0.5
216
+ )
203
217
 
204
218
  self._fly_completed_position = end_position + run_up_distance
205
219
 
@@ -1,3 +1,3 @@
1
- from .pvi import PVIEntry, create_children_from_annotations, fill_pvi_entries
1
+ from ._pvi import create_children_from_annotations, fill_pvi_entries
2
2
 
3
- __all__ = ["PVIEntry", "fill_pvi_entries", "create_children_from_annotations"]
3
+ __all__ = ["fill_pvi_entries", "create_children_from_annotations"]
@@ -10,25 +10,28 @@ from typing import (
10
10
  Optional,
11
11
  Tuple,
12
12
  Type,
13
- TypeVar,
14
13
  Union,
15
14
  get_args,
16
15
  get_origin,
17
16
  get_type_hints,
18
17
  )
19
18
 
20
- from ophyd_async.core import Device, DeviceVector, SoftSignalBackend
21
- from ophyd_async.core.signal import Signal
22
- from ophyd_async.core.utils import DEFAULT_TIMEOUT
23
- from ophyd_async.epics._backend._p4p import PvaSignalBackend
24
- from ophyd_async.epics.signal.signal import (
19
+ from ophyd_async.core import (
20
+ DEFAULT_TIMEOUT,
21
+ Device,
22
+ DeviceVector,
23
+ Signal,
24
+ SoftSignalBackend,
25
+ T,
26
+ )
27
+ from ophyd_async.epics.signal import (
28
+ PvaSignalBackend,
25
29
  epics_signal_r,
26
30
  epics_signal_rw,
27
31
  epics_signal_w,
28
32
  epics_signal_x,
29
33
  )
30
34
 
31
- T = TypeVar("T")
32
35
  Access = FrozenSet[
33
36
  Union[Literal["r"], Literal["w"], Literal["rw"], Literal["x"], Literal["d"]]
34
37
  ]
@@ -74,19 +77,19 @@ def _strip_device_vector(field: Union[Type[Device]]) -> Tuple[bool, Type[Device]
74
77
 
75
78
 
76
79
  @dataclass
77
- class PVIEntry:
80
+ class _PVIEntry:
78
81
  """
79
82
  A dataclass to represent a single entry in the PVI table.
80
83
  This could either be a signal or a sub-table.
81
84
  """
82
85
 
83
- sub_entries: Dict[str, Union[Dict[int, "PVIEntry"], "PVIEntry"]]
86
+ sub_entries: Dict[str, Union[Dict[int, "_PVIEntry"], "_PVIEntry"]]
84
87
  pvi_pv: Optional[str] = None
85
88
  device: Optional[Device] = None
86
89
  common_device_type: Optional[Type[Device]] = None
87
90
 
88
91
 
89
- def _verify_common_blocks(entry: PVIEntry, common_device: Type[Device]):
92
+ def _verify_common_blocks(entry: _PVIEntry, common_device: Type[Device]):
90
93
  if not entry.sub_entries:
91
94
  return
92
95
  common_sub_devices = get_type_hints(common_device)
@@ -205,7 +208,7 @@ def _mock_common_blocks(device: Device, stripped_type: Optional[Type] = None):
205
208
  sub_device.parent = device
206
209
 
207
210
 
208
- async def _get_pvi_entries(entry: PVIEntry, timeout=DEFAULT_TIMEOUT):
211
+ async def _get_pvi_entries(entry: _PVIEntry, timeout=DEFAULT_TIMEOUT):
209
212
  if not entry.pvi_pv or not entry.pvi_pv.endswith(":PVI"):
210
213
  raise RuntimeError("Top level entry must be a pvi table")
211
214
 
@@ -235,7 +238,7 @@ async def _get_pvi_entries(entry: PVIEntry, timeout=DEFAULT_TIMEOUT):
235
238
  else:
236
239
  device = getattr(entry.device, sub_name, device_type())
237
240
 
238
- sub_entry = PVIEntry(
241
+ sub_entry = _PVIEntry(
239
242
  device=device, common_device_type=device_type, sub_entries={}
240
243
  )
241
244
 
@@ -257,7 +260,7 @@ async def _get_pvi_entries(entry: PVIEntry, timeout=DEFAULT_TIMEOUT):
257
260
  _verify_common_blocks(entry, entry.common_device_type)
258
261
 
259
262
 
260
- def _set_device_attributes(entry: PVIEntry):
263
+ def _set_device_attributes(entry: _PVIEntry):
261
264
  for sub_name, sub_entry in entry.sub_entries.items():
262
265
  if isinstance(sub_entry, dict):
263
266
  sub_device = DeviceVector() # type: ignore
@@ -289,7 +292,7 @@ async def fill_pvi_entries(
289
292
  _mock_common_blocks(device)
290
293
  else:
291
294
  # check the pvi table for devices and fill the device with them
292
- root_entry = PVIEntry(
295
+ root_entry = _PVIEntry(
293
296
  pvi_pv=root_pv,
294
297
  device=device,
295
298
  common_device_type=type(device),
@@ -1,4 +1,6 @@
1
- from .signal import (
1
+ from ._common import LimitPair, Limits, get_supported_values
2
+ from ._p4p import PvaSignalBackend
3
+ from ._signal import (
2
4
  epics_signal_r,
3
5
  epics_signal_rw,
4
6
  epics_signal_rw_rbv,
@@ -7,6 +9,10 @@ from .signal import (
7
9
  )
8
10
 
9
11
  __all__ = [
12
+ "get_supported_values",
13
+ "LimitPair",
14
+ "Limits",
15
+ "PvaSignalBackend",
10
16
  "epics_signal_r",
11
17
  "epics_signal_rw",
12
18
  "epics_signal_rw_rbv",
@@ -21,6 +21,8 @@ from bluesky.protocols import DataKey, Dtype, Reading
21
21
  from epicscorelibs.ca import dbr
22
22
 
23
23
  from ophyd_async.core import (
24
+ DEFAULT_TIMEOUT,
25
+ NotConnected,
24
26
  ReadingValueCallback,
25
27
  SignalBackend,
26
28
  T,
@@ -28,9 +30,8 @@ from ophyd_async.core import (
28
30
  get_unique,
29
31
  wait_for_connection,
30
32
  )
31
- from ophyd_async.core.utils import DEFAULT_TIMEOUT, NotConnected
32
33
 
33
- from .common import LimitPair, Limits, common_meta, get_supported_values
34
+ from ._common import LimitPair, Limits, common_meta, get_supported_values
34
35
 
35
36
  dbr_to_dtype: Dict[Dbr, Dtype] = {
36
37
  dbr.DBR_STRING: "string",
@@ -46,7 +47,7 @@ def _data_key_from_augmented_value(
46
47
  value: AugmentedValue,
47
48
  *,
48
49
  choices: Optional[List[str]] = None,
49
- dtype: Optional[str] = None,
50
+ dtype: Optional[Dtype] = None,
50
51
  ) -> DataKey:
51
52
  """Use the return value of get with FORMAT_CTRL to construct a DataKey
52
53
  describing the signal. See docstring of AugmentedValue for expected
@@ -174,7 +175,7 @@ class CaBoolConverter(CaConverter):
174
175
  return bool(value)
175
176
 
176
177
  def get_datakey(self, value: AugmentedValue) -> DataKey:
177
- return _data_key_from_augmented_value(value, dtype="bool")
178
+ return _data_key_from_augmented_value(value, dtype="boolean")
178
179
 
179
180
 
180
181
  class DisconnectedCaConverter(CaConverter):
@@ -228,10 +229,17 @@ def make_converter(
228
229
  value = list(values.values())[0]
229
230
  # Done the dbr check, so enough to check one of the values
230
231
  if datatype and not isinstance(value, datatype):
231
- raise TypeError(
232
- f"{pv} has type {type(value).__name__.replace('ca_', '')} "
233
- + f"not {datatype.__name__}"
232
+ # Allow int signals to represent float records when prec is 0
233
+ is_prec_zero_float = (
234
+ isinstance(value, float)
235
+ and get_unique({k: v.precision for k, v in values.items()}, "precision")
236
+ == 0
234
237
  )
238
+ if not (datatype is int and is_prec_zero_float):
239
+ raise TypeError(
240
+ f"{pv} has type {type(value).__name__.replace('ca_', '')} "
241
+ + f"not {datatype.__name__}"
242
+ )
235
243
  return CaConverter(pv_dbr, None)
236
244
 
237
245
 
@@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple, Type
4
4
 
5
5
  from typing_extensions import TypedDict
6
6
 
7
- from ophyd_async.core.signal_backend import RuntimeSubsetEnum
7
+ from ophyd_async.core import RuntimeSubsetEnum
8
8
 
9
9
  common_meta = {
10
10
  "units",
@@ -55,7 +55,7 @@ def get_supported_values(
55
55
  f"which do not match {datatype}, which has {choices}."
56
56
  )
57
57
  return {x: datatype(x) if x else "_" for x in pv_choices}
58
- elif datatype is None:
58
+ elif datatype is None or datatype is str:
59
59
  return {x: x or "_" for x in pv_choices}
60
60
 
61
61
  raise TypeError(
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from enum import Enum
6
6
 
7
7
  try:
8
- from .._backend._aioca import CaSignalBackend
8
+ from ._aioca import CaSignalBackend
9
9
  except ImportError as ca_error:
10
10
 
11
11
  class CaSignalBackend: # type: ignore
@@ -14,7 +14,7 @@ except ImportError as ca_error:
14
14
 
15
15
 
16
16
  try:
17
- from .._backend._p4p import PvaSignalBackend
17
+ from ._p4p import PvaSignalBackend
18
18
  except ImportError as pva_error:
19
19
 
20
20
  class PvaSignalBackend: # type: ignore
@@ -22,7 +22,7 @@ except ImportError as pva_error:
22
22
  raise NotImplementedError("PVA support not available") from pva_error
23
23
 
24
24
 
25
- class EpicsTransport(Enum):
25
+ class _EpicsTransport(Enum):
26
26
  """The sorts of transport EPICS support"""
27
27
 
28
28
  #: Use Channel Access (using aioca library)
@@ -13,17 +13,18 @@ from p4p import Value
13
13
  from p4p.client.asyncio import Context, Subscription
14
14
 
15
15
  from ophyd_async.core import (
16
+ DEFAULT_TIMEOUT,
17
+ NotConnected,
16
18
  ReadingValueCallback,
19
+ RuntimeSubsetEnum,
17
20
  SignalBackend,
18
21
  T,
19
22
  get_dtype,
20
23
  get_unique,
21
24
  wait_for_connection,
22
25
  )
23
- from ophyd_async.core.signal_backend import RuntimeSubsetEnum
24
- from ophyd_async.core.utils import DEFAULT_TIMEOUT, NotConnected
25
26
 
26
- from .common import LimitPair, Limits, common_meta, get_supported_values
27
+ from ._common import LimitPair, Limits, common_meta, get_supported_values
27
28
 
28
29
  # https://mdavidsaver.github.io/p4p/values.html
29
30
  specifier_to_dtype: Dict[str, Dtype] = {
@@ -63,7 +64,7 @@ def _data_key_from_value(
63
64
  *,
64
65
  shape: Optional[list[int]] = None,
65
66
  choices: Optional[list[str]] = None,
66
- dtype: Optional[str] = None,
67
+ dtype: Optional[Dtype] = None,
67
68
  ) -> DataKey:
68
69
  """
69
70
  Args:
@@ -84,7 +85,7 @@ def _data_key_from_value(
84
85
  if isinstance(type_code, tuple):
85
86
  dtype_numpy = ""
86
87
  if type_code[1] == "enum_t":
87
- if dtype == "bool":
88
+ if dtype == "boolean":
88
89
  dtype_numpy = "<i2"
89
90
  else:
90
91
  for item in type_code[2]:
@@ -240,7 +241,7 @@ class PvaEmumBoolConverter(PvaConverter):
240
241
  return bool(value["value"]["index"])
241
242
 
242
243
  def get_datakey(self, source: str, value) -> DataKey:
243
- return _data_key_from_value(source, value, dtype="bool")
244
+ return _data_key_from_value(source, value, dtype="boolean")
244
245
 
245
246
 
246
247
  class PvaTableConverter(PvaConverter):
@@ -334,14 +335,17 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
334
335
  return PvaEnumConverter(
335
336
  get_supported_values(pv, datatype, datatype.choices)
336
337
  )
337
- elif (
338
- datatype
339
- and not issubclass(typ, datatype)
340
- and not (
341
- typ is float and datatype is int
342
- ) # Allow float -> int since prec can be 0
343
- ):
344
- raise TypeError(f"{pv} has type {typ.__name__} not {datatype.__name__}")
338
+ elif datatype and not issubclass(typ, datatype):
339
+ # Allow int signals to represent float records when prec is 0
340
+ is_prec_zero_float = typ is float and (
341
+ get_unique(
342
+ {k: v["display"]["precision"] for k, v in values.items()},
343
+ "precision",
344
+ )
345
+ == 0
346
+ )
347
+ if not (datatype is int and is_prec_zero_float):
348
+ raise TypeError(f"{pv} has type {typ.__name__} not {datatype.__name__}")
345
349
  return PvaConverter()
346
350
  elif "NTTable" in typeid:
347
351
  return PvaTableConverter()
@@ -14,26 +14,27 @@ from ophyd_async.core import (
14
14
  get_unique,
15
15
  )
16
16
 
17
- from ._epics_transport import EpicsTransport
17
+ from ._epics_transport import _EpicsTransport
18
18
 
19
- _default_epics_transport = EpicsTransport.ca
19
+ _default_epics_transport = _EpicsTransport.ca
20
20
 
21
21
 
22
- def _transport_pv(pv: str) -> Tuple[EpicsTransport, str]:
22
+ def _transport_pv(pv: str) -> Tuple[_EpicsTransport, str]:
23
23
  split = pv.split("://", 1)
24
24
  if len(split) > 1:
25
25
  # We got something like pva://mydevice, so use specified comms mode
26
26
  transport_str, pv = split
27
- transport = EpicsTransport[transport_str]
27
+ transport = _EpicsTransport[transport_str]
28
28
  else:
29
29
  # No comms mode specified, use the default
30
30
  transport = _default_epics_transport
31
31
  return transport, pv
32
32
 
33
33
 
34
- def _make_backend(
34
+ def _epics_signal_backend(
35
35
  datatype: Optional[Type[T]], read_pv: str, write_pv: str
36
36
  ) -> SignalBackend[T]:
37
+ """Create an epics signal backend."""
37
38
  r_transport, r_pv = _transport_pv(read_pv)
38
39
  w_transport, w_pv = _transport_pv(write_pv)
39
40
  transport = get_unique({read_pv: r_transport, write_pv: w_transport}, "transports")
@@ -54,7 +55,7 @@ def epics_signal_rw(
54
55
  write_pv:
55
56
  If given, use this PV to write to, otherwise use read_pv
56
57
  """
57
- backend = _make_backend(datatype, read_pv, write_pv or read_pv)
58
+ backend = _epics_signal_backend(datatype, read_pv, write_pv or read_pv)
58
59
  return SignalRW(backend, name=name)
59
60
 
60
61
 
@@ -85,7 +86,7 @@ def epics_signal_r(datatype: Type[T], read_pv: str, name: str = "") -> SignalR[T
85
86
  read_pv:
86
87
  The PV to read and monitor
87
88
  """
88
- backend = _make_backend(datatype, read_pv, read_pv)
89
+ backend = _epics_signal_backend(datatype, read_pv, read_pv)
89
90
  return SignalR(backend, name=name)
90
91
 
91
92
 
@@ -99,7 +100,7 @@ def epics_signal_w(datatype: Type[T], write_pv: str, name: str = "") -> SignalW[
99
100
  write_pv:
100
101
  The PV to write to
101
102
  """
102
- backend = _make_backend(datatype, write_pv, write_pv)
103
+ backend = _epics_signal_backend(datatype, write_pv, write_pv)
103
104
  return SignalW(backend, name=name)
104
105
 
105
106
 
@@ -111,5 +112,5 @@ def epics_signal_x(write_pv: str, name: str = "") -> SignalX:
111
112
  write_pv:
112
113
  The PV to write its initial value to on trigger
113
114
  """
114
- backend: SignalBackend = _make_backend(None, write_pv, write_pv)
115
+ backend: SignalBackend = _epics_signal_backend(None, write_pv, write_pv)
115
116
  return SignalX(backend, name=name)
File without changes
@@ -1,4 +1,4 @@
1
- from ._common_blocks import (
1
+ from ._block import (
2
2
  CommonPandaBlocks,
3
3
  DataBlock,
4
4
  EnableDisableOptions,
@@ -9,9 +9,11 @@ from ._common_blocks import (
9
9
  SeqBlock,
10
10
  TimeUnits,
11
11
  )
12
+ from ._control import PandaPcapController
12
13
  from ._hdf_panda import HDFPanda
13
- from ._panda_controller import PandaPcapController
14
14
  from ._table import (
15
+ DatasetTable,
16
+ PandaHdf5DatasetType,
15
17
  SeqTable,
16
18
  SeqTableRow,
17
19
  SeqTrigger,
@@ -25,28 +27,31 @@ from ._trigger import (
25
27
  StaticSeqTableTriggerLogic,
26
28
  )
27
29
  from ._utils import phase_sorter
30
+ from ._writer import PandaHDFWriter
28
31
 
29
32
  __all__ = [
30
33
  "CommonPandaBlocks",
31
- "HDFPanda",
32
- "PcompBlock",
33
- "PcompInfo",
34
- "PcompDirectionOptions",
34
+ "DataBlock",
35
35
  "EnableDisableOptions",
36
36
  "PcapBlock",
37
+ "PcompBlock",
38
+ "PcompDirectionOptions",
37
39
  "PulseBlock",
38
- "seq_table_from_arrays",
39
- "seq_table_from_rows",
40
40
  "SeqBlock",
41
- "SeqTableInfo",
41
+ "TimeUnits",
42
+ "HDFPanda",
43
+ "PandaHDFWriter",
44
+ "PandaPcapController",
45
+ "DatasetTable",
46
+ "PandaHdf5DatasetType",
42
47
  "SeqTable",
43
48
  "SeqTableRow",
44
49
  "SeqTrigger",
45
- "phase_sorter",
46
- "PandaPcapController",
47
- "TimeUnits",
48
- "DataBlock",
49
- "CommonPandABlocks",
50
- "StaticSeqTableTriggerLogic",
50
+ "seq_table_from_arrays",
51
+ "seq_table_from_rows",
52
+ "PcompInfo",
53
+ "SeqTableInfo",
51
54
  "StaticPcompTriggerLogic",
55
+ "StaticSeqTableTriggerLogic",
56
+ "phase_sorter",
52
57
  ]
@@ -2,9 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  from enum import Enum
4
4
 
5
- from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW
6
- from ophyd_async.core.signal_backend import SubsetEnum
7
- from ophyd_async.panda._table import DatasetTable, SeqTable
5
+ from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW, SubsetEnum
6
+
7
+ from ._table import DatasetTable, SeqTable
8
8
 
9
9
 
10
10
  class DataBlock(Device):
@@ -13,6 +13,8 @@ class DataBlock(Device):
13
13
  hdf_file_name: SignalRW[str]
14
14
  num_capture: SignalRW[int]
15
15
  num_captured: SignalR[int]
16
+ create_directory: SignalRW[int]
17
+ directory_exists: SignalR[bool]
16
18
  capture: SignalRW[bool]
17
19
  flush_period: SignalRW[float]
18
20
  datasets: SignalR[DatasetTable]