ophyd-async 0.13.0__py3-none-any.whl → 0.13.2__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 (43) hide show
  1. ophyd_async/_version.py +16 -3
  2. ophyd_async/core/__init__.py +2 -1
  3. ophyd_async/core/_detector.py +1 -1
  4. ophyd_async/core/_signal.py +16 -9
  5. ophyd_async/core/_utils.py +5 -4
  6. ophyd_async/epics/adandor/_andor.py +1 -2
  7. ophyd_async/epics/adcore/_core_detector.py +1 -2
  8. ophyd_async/epics/adcore/_core_io.py +1 -1
  9. ophyd_async/epics/adcore/_core_logic.py +2 -2
  10. ophyd_async/epics/adcore/_core_writer.py +9 -6
  11. ophyd_async/epics/adcore/_hdf_writer.py +6 -1
  12. ophyd_async/epics/adkinetix/_kinetix_io.py +4 -4
  13. ophyd_async/epics/adpilatus/_pilatus.py +2 -6
  14. ophyd_async/epics/advimba/_vimba_io.py +1 -1
  15. ophyd_async/epics/core/_epics_connector.py +14 -1
  16. ophyd_async/epics/core/_p4p.py +2 -3
  17. ophyd_async/epics/core/_pvi_connector.py +1 -1
  18. ophyd_async/epics/motor.py +21 -16
  19. ophyd_async/epics/{eiger → odin}/_odin_io.py +5 -3
  20. ophyd_async/epics/pmac/__init__.py +2 -0
  21. ophyd_async/epics/pmac/_pmac_io.py +2 -2
  22. ophyd_async/epics/pmac/_pmac_trajectory.py +116 -0
  23. ophyd_async/epics/pmac/_utils.py +671 -55
  24. ophyd_async/epics/testing/_example_ioc.py +1 -2
  25. ophyd_async/fastcs/eiger/_eiger.py +1 -1
  26. ophyd_async/fastcs/jungfrau/__init__.py +29 -0
  27. ophyd_async/fastcs/jungfrau/_controller.py +139 -0
  28. ophyd_async/fastcs/jungfrau/_jungfrau.py +30 -0
  29. ophyd_async/fastcs/jungfrau/_signals.py +94 -0
  30. ophyd_async/fastcs/jungfrau/_utils.py +79 -0
  31. ophyd_async/plan_stubs/_settings.py +1 -1
  32. ophyd_async/sim/_motor.py +11 -3
  33. ophyd_async/sim/_point_detector.py +6 -3
  34. ophyd_async/sim/_stage.py +14 -3
  35. ophyd_async/tango/core/_tango_transport.py +2 -2
  36. ophyd_async/testing/_assert.py +6 -6
  37. ophyd_async/testing/_one_of_everything.py +1 -1
  38. {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/METADATA +5 -4
  39. {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/RECORD +43 -37
  40. /ophyd_async/epics/{eiger → odin}/__init__.py +0 -0
  41. {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/WHEEL +0 -0
  42. {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/licenses/LICENSE +0 -0
  43. {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/top_level.txt +0 -0
@@ -4,8 +4,7 @@ from typing import Annotated as A
4
4
 
5
5
  import numpy as np
6
6
 
7
- from ophyd_async.core import Array1D, SignalR, SignalRW, StrictEnum, Table
8
- from ophyd_async.core._utils import SubsetEnum
7
+ from ophyd_async.core import Array1D, SignalR, SignalRW, StrictEnum, SubsetEnum, Table
9
8
  from ophyd_async.epics.core import EpicsDevice, PvSuffix
10
9
 
11
10
  from ._utils import TestingIOC, generate_random_pv_prefix
@@ -4,7 +4,7 @@ from ophyd_async.core import (
4
4
  StandardDetector,
5
5
  TriggerInfo,
6
6
  )
7
- from ophyd_async.epics.eiger import Odin, OdinWriter
7
+ from ophyd_async.epics.odin import Odin, OdinWriter
8
8
 
9
9
  from ._eiger_controller import EigerController
10
10
  from ._eiger_io import EigerDriverIO
@@ -0,0 +1,29 @@
1
+ from ._controller import JungfrauController
2
+ from ._jungfrau import Jungfrau
3
+ from ._signals import (
4
+ AcquisitionType,
5
+ DetectorStatus,
6
+ GainMode,
7
+ JungfrauDriverIO,
8
+ JungfrauTriggerMode,
9
+ PedestalMode,
10
+ )
11
+ from ._utils import (
12
+ create_jungfrau_external_triggering_info,
13
+ create_jungfrau_internal_triggering_info,
14
+ create_jungfrau_pedestal_triggering_info,
15
+ )
16
+
17
+ __all__ = [
18
+ "Jungfrau",
19
+ "DetectorStatus",
20
+ "create_jungfrau_external_triggering_info",
21
+ "create_jungfrau_internal_triggering_info",
22
+ "create_jungfrau_pedestal_triggering_info",
23
+ "JungfrauController",
24
+ "JungfrauDriverIO",
25
+ "JungfrauTriggerMode",
26
+ "AcquisitionType",
27
+ "GainMode",
28
+ "PedestalMode",
29
+ ]
@@ -0,0 +1,139 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from ophyd_async.core import (
5
+ DEFAULT_TIMEOUT,
6
+ DetectorController,
7
+ DetectorTrigger,
8
+ TriggerInfo,
9
+ wait_for_value,
10
+ )
11
+
12
+ from ._signals import (
13
+ JUNGFRAU_TRIGGER_MODE_MAP,
14
+ AcquisitionType,
15
+ DetectorStatus,
16
+ JungfrauDriverIO,
17
+ PedestalMode,
18
+ )
19
+
20
+ # Deadtime is dependant on a wide combination of settings and on trigger mode
21
+ # but this is safe upper-limit
22
+ JUNGFRAU_DEADTIME_S = 2e-5
23
+
24
+ logger = logging.getLogger("ophyd_async")
25
+
26
+
27
+ class JungfrauController(DetectorController):
28
+ def __init__(self, driver: JungfrauDriverIO):
29
+ self._driver = driver
30
+
31
+ def get_deadtime(self, exposure: float | None = None) -> float:
32
+ return JUNGFRAU_DEADTIME_S
33
+
34
+ async def prepare(self, trigger_info: TriggerInfo) -> None:
35
+ # ValueErrors and warnings in this function come from the jungfrau operation
36
+ # docs: https://rtd.xfel.eu/docs/jungfrau-detector-documentation/en/latest/operation.html
37
+
38
+ # Deadtime here is really used as "time between frames"
39
+
40
+ acquisition_type = await self._driver.acquisition_type.get_value()
41
+ logger.info(f"Preparing Jungfrau in {acquisition_type} mode.")
42
+
43
+ if trigger_info.trigger not in (
44
+ DetectorTrigger.INTERNAL,
45
+ DetectorTrigger.EDGE_TRIGGER,
46
+ ):
47
+ raise ValueError(
48
+ "The trigger method can only be called with internal or edge triggering"
49
+ )
50
+ if (
51
+ acquisition_type == AcquisitionType.PEDESTAL
52
+ and trigger_info.trigger != DetectorTrigger.INTERNAL
53
+ ):
54
+ raise ValueError(
55
+ "Jungfrau must be triggered internally while in pedestal mode."
56
+ )
57
+
58
+ if not isinstance(trigger_info.number_of_events, int):
59
+ raise TypeError("Number of events must be an integer")
60
+
61
+ if acquisition_type != AcquisitionType.PEDESTAL:
62
+ if (
63
+ trigger_info.trigger == DetectorTrigger.INTERNAL
64
+ and trigger_info.number_of_events != 1
65
+ ):
66
+ raise ValueError(
67
+ "Number of events must be set to 1 in internal trigger mode during "
68
+ "standard acquisitions."
69
+ )
70
+
71
+ if (
72
+ trigger_info.trigger == DetectorTrigger.EDGE_TRIGGER
73
+ and trigger_info.exposures_per_event != 1
74
+ ):
75
+ raise ValueError(
76
+ "Exposures per event must be set to 1 in edge trigger mode "
77
+ "during standard acquisitions."
78
+ )
79
+
80
+ if not trigger_info.livetime:
81
+ raise ValueError("Must set TriggerInfo.livetime")
82
+
83
+ if trigger_info.livetime < 2e-6:
84
+ logger.warning("Exposure time shorter than 2μs is not recommended")
85
+
86
+ period_between_frames = trigger_info.livetime + trigger_info.deadtime
87
+
88
+ if period_between_frames < self.get_deadtime():
89
+ raise ValueError(
90
+ f"Period between frames (exposure time - deadtime) = "
91
+ f"{period_between_frames}s cannot be lower than minimum detector "
92
+ f"deadtime {self.get_deadtime()}"
93
+ )
94
+
95
+ coros = [
96
+ self._driver.trigger_mode.set(
97
+ JUNGFRAU_TRIGGER_MODE_MAP[trigger_info.trigger]
98
+ ),
99
+ self._driver.period_between_frames.set(period_between_frames),
100
+ self._driver.exposure_time.set(trigger_info.livetime),
101
+ ]
102
+
103
+ match acquisition_type:
104
+ case AcquisitionType.STANDARD:
105
+ frames_signal = (
106
+ trigger_info.exposures_per_event
107
+ if trigger_info.trigger is DetectorTrigger.INTERNAL
108
+ else trigger_info.number_of_events
109
+ )
110
+ coros.extend(
111
+ [
112
+ self._driver.frames_per_acq.set(frames_signal),
113
+ ]
114
+ )
115
+ case AcquisitionType.PEDESTAL:
116
+ coros.extend(
117
+ [
118
+ self._driver.pedestal_mode_frames.set(
119
+ trigger_info.exposures_per_event
120
+ ),
121
+ self._driver.pedestal_mode_loops.set(
122
+ trigger_info.number_of_events
123
+ ),
124
+ self._driver.pedestal_mode_state.set(PedestalMode.ON),
125
+ ]
126
+ )
127
+
128
+ await asyncio.gather(*coros)
129
+
130
+ async def arm(self):
131
+ await self._driver.acquisition_start.trigger()
132
+
133
+ async def wait_for_idle(self):
134
+ await wait_for_value(
135
+ self._driver.detector_status, DetectorStatus.IDLE, timeout=DEFAULT_TIMEOUT
136
+ )
137
+
138
+ async def disarm(self):
139
+ await self._driver.acquisition_stop.trigger()
@@ -0,0 +1,30 @@
1
+ from ophyd_async.core import (
2
+ PathProvider,
3
+ StandardDetector,
4
+ )
5
+ from ophyd_async.epics.odin import Odin, OdinWriter
6
+ from ophyd_async.fastcs.jungfrau._controller import JungfrauController
7
+ from ophyd_async.fastcs.jungfrau._signals import JungfrauDriverIO
8
+
9
+
10
+ class Jungfrau(StandardDetector[JungfrauController, OdinWriter]):
11
+ """Ophyd-async implementation of a Jungfrau Detector."""
12
+
13
+ def __init__(
14
+ self,
15
+ prefix: str,
16
+ path_provider: PathProvider,
17
+ drv_suffix: str,
18
+ hdf_suffix: str,
19
+ odin_nodes: int,
20
+ name="",
21
+ ):
22
+ self.drv = JungfrauDriverIO(prefix + drv_suffix)
23
+ self.odin = Odin(prefix + hdf_suffix, nodes=odin_nodes)
24
+ writer = OdinWriter(
25
+ path_provider,
26
+ self.odin,
27
+ self.drv.bit_depth,
28
+ )
29
+ controller = JungfrauController(self.drv)
30
+ super().__init__(controller, writer, name=name)
@@ -0,0 +1,94 @@
1
+ from pydantic import NonNegativeInt
2
+
3
+ from ophyd_async.core import (
4
+ DetectorTrigger,
5
+ Device,
6
+ SignalR,
7
+ SignalRW,
8
+ SignalX,
9
+ StrictEnum,
10
+ soft_signal_rw,
11
+ )
12
+ from ophyd_async.fastcs.core import fastcs_connector
13
+
14
+
15
+ class JungfrauTriggerMode(StrictEnum):
16
+ INTERNAL = "Internal"
17
+
18
+ # Detector waits for external trigger to start frame series, but still
19
+ # controls exposure time and frame period internally
20
+ EXTERNAL = "External"
21
+
22
+
23
+ class DetectorStatus(StrictEnum):
24
+ IDLE = "Idle"
25
+ ERROR = "Error"
26
+ WAITING = "Waiting"
27
+ RUN_FINISHED = "RunFinished"
28
+ TRANSMITTING = "Transmitting"
29
+ RUNNING = "Running"
30
+ STOPPED = "Stopped"
31
+
32
+
33
+ class GainMode(StrictEnum):
34
+ DYNAMIC = "Dynamic"
35
+ FORCE_SWITCH_G1 = "ForceSwitchG1"
36
+ FORCE_SWITCH_G2 = "ForceSwitchG2"
37
+ FIX_G1 = "FixG1"
38
+ FIX_G2 = "FixG2"
39
+
40
+ # Use with caution - this may damage the detector
41
+ FIX_G0 = "FixG0"
42
+
43
+
44
+ class PedestalMode(StrictEnum):
45
+ ON = "On"
46
+ OFF = "Off"
47
+
48
+
49
+ class AcquisitionType(StrictEnum):
50
+ STANDARD = "Standard"
51
+ PEDESTAL = "Pedestal"
52
+
53
+
54
+ JUNGFRAU_TRIGGER_MODE_MAP = {
55
+ DetectorTrigger.EDGE_TRIGGER: JungfrauTriggerMode.EXTERNAL,
56
+ DetectorTrigger.INTERNAL: JungfrauTriggerMode.INTERNAL,
57
+ }
58
+
59
+
60
+ class JungfrauDriverIO(Device):
61
+ """Contains signals for handling IO on the Jungfrau detector."""
62
+
63
+ exposure_time: SignalRW[float] # in s
64
+
65
+ # Includes deadtime
66
+ period_between_frames: SignalRW[float] # in s
67
+
68
+ # Sets the delay for the beginning of the exposure time after
69
+ # trigger input
70
+ delay_after_trigger: SignalRW[float] # in s
71
+
72
+ # In internal trigger mode, this is frames per trigger. In external trigger mode,
73
+ # this is frames per overall acquisition. In pedestal mode, this signal is not set.
74
+ frames_per_acq: SignalRW[NonNegativeInt]
75
+
76
+ pedestal_mode_state: SignalRW[PedestalMode]
77
+ pedestal_mode_frames: SignalRW[NonNegativeInt]
78
+ pedestal_mode_loops: SignalRW[NonNegativeInt]
79
+
80
+ gain_mode: SignalRW[GainMode]
81
+
82
+ acquisition_start: SignalX
83
+
84
+ acquisition_stop: SignalX
85
+ bit_depth: SignalR[int]
86
+ trigger_mode: SignalRW[JungfrauTriggerMode]
87
+ detector_status: SignalR[DetectorStatus]
88
+
89
+ def __init__(self, uri: str, name: str = ""):
90
+ # Determines how the TriggerInfo gets mapped to the Jungfrau during prepare
91
+ self.acquisition_type = soft_signal_rw(
92
+ AcquisitionType, AcquisitionType.STANDARD
93
+ )
94
+ super().__init__(name=name, connector=fastcs_connector(self, uri))
@@ -0,0 +1,79 @@
1
+ from pydantic import PositiveInt
2
+
3
+ from ophyd_async.core import DetectorTrigger, TriggerInfo
4
+
5
+
6
+ def create_jungfrau_external_triggering_info(
7
+ total_triggers: PositiveInt,
8
+ exposure_time_s: float,
9
+ ) -> TriggerInfo:
10
+ """Create safe Jungfrau TriggerInfo for external triggering.
11
+
12
+ Uses parameters which more closely-align with Jungfrau terminology
13
+ to create TriggerInfo. This device currently only supports one frame per trigger
14
+ when being externally triggered, but support for this can be added if needed
15
+
16
+ Args:
17
+ total_triggers: Total external triggers expected before ending acquisition.
18
+ exposure_time_s: How long to expose the detector for each of its frames.
19
+
20
+ Returns:
21
+ `TriggerInfo`
22
+ """
23
+ return TriggerInfo(
24
+ number_of_events=total_triggers,
25
+ trigger=DetectorTrigger.EDGE_TRIGGER,
26
+ livetime=exposure_time_s,
27
+ )
28
+
29
+
30
+ def create_jungfrau_internal_triggering_info(
31
+ number_of_frames: PositiveInt, exposure_time_s: float
32
+ ) -> TriggerInfo:
33
+ """Create safe Jungfrau TriggerInfo for internal triggering.
34
+
35
+ Uses parameters which more closely-align with Jungfrau terminology
36
+ to create TriggerInfo.
37
+
38
+ Args:
39
+ number_of_frames: Total frames taken after starting acquisition.
40
+ exposure_time_s: How long to expose the detector for each of its frames.
41
+
42
+ Returns:
43
+ `TriggerInfo`
44
+ """
45
+ return TriggerInfo(
46
+ number_of_events=1,
47
+ trigger=DetectorTrigger.INTERNAL,
48
+ livetime=exposure_time_s,
49
+ exposures_per_event=number_of_frames,
50
+ )
51
+
52
+
53
+ def create_jungfrau_pedestal_triggering_info(
54
+ exposure_time_s: float,
55
+ pedestal_frames: PositiveInt,
56
+ pedestal_loops: PositiveInt,
57
+ ):
58
+ """Create safe Jungfrau TriggerInfo for pedestal triggering.
59
+
60
+ Uses parameters which more closely-align with Jungfrau terminology
61
+ to create TriggerInfo.
62
+
63
+ NOTE: To trigger the jungfrau in pedestal mode, you must first set the
64
+ jungfrau acquisition_type signal to AcquisitionType.PEDESTAL!
65
+
66
+ Args:
67
+ exposure_time_s: How long to expose the detector for each of its frames.
68
+ pedestal_frames: Number of frames taken once triggering begins
69
+ pedestal_loops: Number of repeats of the pedestal scan before detector disarms.
70
+
71
+ Returns:
72
+ `TriggerInfo`
73
+ """
74
+ return TriggerInfo(
75
+ number_of_events=pedestal_loops,
76
+ exposures_per_event=pedestal_frames,
77
+ trigger=DetectorTrigger.INTERNAL,
78
+ livetime=exposure_time_s,
79
+ )
@@ -13,10 +13,10 @@ from ophyd_async.core import (
13
13
  Settings,
14
14
  SettingsProvider,
15
15
  SignalRW,
16
+ Table,
16
17
  walk_config_signals,
17
18
  walk_rw_signals,
18
19
  )
19
- from ophyd_async.core._table import Table
20
20
 
21
21
  from ._utils import T
22
22
  from ._wait_for_awaitable import wait_for_awaitable
ophyd_async/sim/_motor.py CHANGED
@@ -23,11 +23,19 @@ from ophyd_async.core import StandardReadableFormat as Format
23
23
  class SimMotor(StandardReadable, Stoppable, Subscribable[float], Locatable[float]):
24
24
  """For usage when simulating a motor."""
25
25
 
26
- def __init__(self, name="", instant=True) -> None:
26
+ def __init__(
27
+ self,
28
+ name: str = "",
29
+ instant: bool = True,
30
+ initial_value: float = 0.0,
31
+ units: str = "mm",
32
+ ) -> None:
27
33
  """Simulate a motor, with optional velocity.
28
34
 
29
35
  :param name: name of device
30
36
  :param instant: whether to move instantly or calculate move time using velocity
37
+ :param initial_value: initial position of the motor
38
+ :param units: units of the motor position
31
39
  """
32
40
  # Define some signals
33
41
  with self.add_children_as_readables(Format.HINTED_SIGNAL):
@@ -37,8 +45,8 @@ class SimMotor(StandardReadable, Stoppable, Subscribable[float], Locatable[float
37
45
  with self.add_children_as_readables(Format.CONFIG_SIGNAL):
38
46
  self.velocity = soft_signal_rw(float, 0 if instant else 1.0)
39
47
  self.acceleration_time = soft_signal_rw(float, 0.5)
40
- self.units = soft_signal_rw(str, "mm")
41
- self.user_setpoint = soft_signal_rw(float, 0)
48
+ self.units = soft_signal_rw(str, units)
49
+ self.user_setpoint = soft_signal_rw(float, initial_value)
42
50
 
43
51
  # Whether set() should complete successfully or not
44
52
  self._set_success = True
@@ -41,9 +41,12 @@ class SimPointDetector(StandardReadable):
41
41
  """Simalutes a point detector with multiple channels."""
42
42
 
43
43
  def __init__(
44
- self, generator: PatternGenerator, num_channels: int = 3, name: str = ""
44
+ self,
45
+ pattern_generator: PatternGenerator | None,
46
+ num_channels: int = 3,
47
+ name: str = "",
45
48
  ) -> None:
46
- self._generator = generator
49
+ self.pattern_generator = pattern_generator or PatternGenerator()
47
50
  self.acquire_time = soft_signal_rw(float, 0.1)
48
51
  self.acquiring, self._set_acquiring = soft_signal_r_and_setter(bool)
49
52
  self._value_signals = dict(
@@ -75,7 +78,7 @@ class SimPointDetector(StandardReadable):
75
78
  # Update the channel value
76
79
  for i, channel in self.channel.items():
77
80
  high_energy = modes[channel] == EnergyMode.HIGH
78
- point = self._generator.generate_point(i, high_energy)
81
+ point = self.pattern_generator.generate_point(i, high_energy)
79
82
  setter = self._value_signals[channel.value]
80
83
  setter(int(point * 10000 * update_time))
81
84
 
ophyd_async/sim/_stage.py CHANGED
@@ -8,12 +8,23 @@ class SimStage(StandardReadable):
8
8
  """A simulated sample stage with X and Y movables."""
9
9
 
10
10
  def __init__(self, pattern_generator: PatternGenerator, name="") -> None:
11
+ self.pattern_generator = pattern_generator
11
12
  # Define some child Devices
12
13
  with self.add_children_as_readables():
13
14
  self.x = SimMotor(instant=False)
14
15
  self.y = SimMotor(instant=False)
15
- # Tell the pattern generator about the motor positions
16
- self.x.user_readback.subscribe_value(pattern_generator.set_x)
17
- self.y.user_readback.subscribe_value(pattern_generator.set_y)
18
16
  # Set name of device and child devices
19
17
  super().__init__(name=name)
18
+
19
+ def stage(self):
20
+ """Stage the motors and report the position to the pattern generator."""
21
+ # Tell the pattern generator about the motor positions
22
+ self.x.user_readback.subscribe_value(self.pattern_generator.set_x)
23
+ self.y.user_readback.subscribe_value(self.pattern_generator.set_y)
24
+ return super().stage()
25
+
26
+ def unstage(self):
27
+ """Unstage the motors and remove the position subscription."""
28
+ self.x.user_readback.clear_sub(self.pattern_generator.set_x)
29
+ self.y.user_readback.clear_sub(self.pattern_generator.set_y)
30
+ return super().unstage()
@@ -212,7 +212,7 @@ class AttributeProxy(TangoProxy):
212
212
 
213
213
  task = asyncio.create_task(_write())
214
214
  await asyncio.wait_for(task, timeout)
215
- except asyncio.TimeoutError as te:
215
+ except TimeoutError as te:
216
216
  raise TimeoutError(f"{self._name} attr put failed: Timeout") from te
217
217
  except DevFailed as de:
218
218
  raise RuntimeError(
@@ -451,7 +451,7 @@ class CommandProxy(TangoProxy):
451
451
  timestamp=time.time(),
452
452
  alarm_severity=0,
453
453
  )
454
- except asyncio.TimeoutError as te:
454
+ except TimeoutError as te:
455
455
  raise TimeoutError(f"{self._name} command failed: Timeout") from te
456
456
  except DevFailed as de:
457
457
  raise RuntimeError(
@@ -21,11 +21,11 @@ from ophyd_async.core import (
21
21
  from ._utils import T
22
22
 
23
23
 
24
- def partial_reading(val: Any) -> dict[str, Any]:
25
- """Helper function for building expected reading or configuration dicts.
24
+ def partial_reading(val: Any) -> Mapping[str, Any]:
25
+ """Helper function for building expected reading or configuration mapping.
26
26
 
27
- :param val: Value to be wrapped in dict with "value" as the key.
28
- :return: The dict that has wrapped the val with key "value".
27
+ :param val: Value to be wrapped in mapping with "value" as the key.
28
+ :return: The mapping that has wrapped the val with key "value".
29
29
  """
30
30
  return {"value": val}
31
31
 
@@ -100,7 +100,7 @@ def _assert_readings_approx_equal(
100
100
 
101
101
  async def assert_configuration(
102
102
  configurable: AsyncConfigurable,
103
- expected_configuration: dict[str, dict[str, Any]],
103
+ expected_configuration: Mapping[str, Mapping[str, Any]],
104
104
  full_match: bool = True,
105
105
  ) -> None:
106
106
  """Assert that a configurable Device has the given configuration.
@@ -108,7 +108,7 @@ async def assert_configuration(
108
108
  :param configurable:
109
109
  Device with an async ``read_configuration()`` method to get the
110
110
  configuration from.
111
- :param configuration: The expected configuration from the configurable.
111
+ :param expected_configuration: The expected configuration from the configurable.
112
112
  :param full_match: if expected_reading keys set is same as actual keys set.
113
113
  true: exact match
114
114
  false: expected_reading keys is subset of actual reading keys
@@ -6,6 +6,7 @@ import numpy as np
6
6
  from ophyd_async.core import (
7
7
  Array1D,
8
8
  Device,
9
+ DeviceVector,
9
10
  DTypeScalar_co,
10
11
  SignalRW,
11
12
  StandardReadable,
@@ -15,7 +16,6 @@ from ophyd_async.core import (
15
16
  soft_signal_rw,
16
17
  )
17
18
  from ophyd_async.core import StandardReadableFormat as Format
18
- from ophyd_async.core._device import DeviceVector
19
19
 
20
20
 
21
21
  class ExampleEnum(StrictEnum):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ophyd-async
3
- Version: 0.13.0
3
+ Version: 0.13.2
4
4
  Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
5
5
  Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
6
6
  License: BSD 3-Clause License
@@ -35,10 +35,10 @@ License: BSD 3-Clause License
35
35
  Project-URL: GitHub, https://github.com/bluesky/ophyd-async
36
36
  Classifier: Development Status :: 3 - Alpha
37
37
  Classifier: License :: OSI Approved :: BSD License
38
- Classifier: Programming Language :: Python :: 3.10
39
38
  Classifier: Programming Language :: Python :: 3.11
40
39
  Classifier: Programming Language :: Python :: 3.12
41
- Requires-Python: >=3.10
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Requires-Python: >=3.11
42
42
  Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
44
  Requires-Dist: numpy
@@ -50,6 +50,7 @@ Requires-Dist: pydantic>=2.0
50
50
  Requires-Dist: pydantic-numpy
51
51
  Requires-Dist: stamina>=23.1.0
52
52
  Requires-Dist: scanspec>=1.0a1
53
+ Requires-Dist: velocity-profile
53
54
  Provides-Extra: sim
54
55
  Requires-Dist: h5py; extra == "sim"
55
56
  Provides-Extra: ca
@@ -57,7 +58,7 @@ Requires-Dist: aioca>=2.0a4; extra == "ca"
57
58
  Provides-Extra: pva
58
59
  Requires-Dist: p4p>=4.2.0; extra == "pva"
59
60
  Provides-Extra: tango
60
- Requires-Dist: pytango==10.0.0; extra == "tango"
61
+ Requires-Dist: pytango==10.0.2; extra == "tango"
61
62
  Provides-Extra: demo
62
63
  Requires-Dist: ipython; extra == "demo"
63
64
  Requires-Dist: matplotlib; extra == "demo"