ophyd-async 0.13.5__py3-none-any.whl → 0.13.7__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.
ophyd_async/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.13.5'
32
- __version_tuple__ = version_tuple = (0, 13, 5)
31
+ __version__ = version = '0.13.7'
32
+ __version_tuple__ = version_tuple = (0, 13, 7)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -41,7 +41,9 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
41
41
  @cached_property
42
42
  def put_mock(self) -> AsyncMock:
43
43
  """Return the mock that will track calls to `put()`."""
44
- put_mock = AsyncMock(name="put", spec=Callable)
44
+ put_mock = AsyncMock(
45
+ name="put", spec=Callable, side_effect=lambda *_, **__: None
46
+ )
45
47
  self.mock().attach_mock(put_mock, "put")
46
48
  return put_mock
47
49
 
@@ -66,8 +68,10 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
66
68
  return put_proceeds
67
69
 
68
70
  async def put(self, value: SignalDatatypeT | None, wait: bool):
69
- await self.put_mock(value, wait=wait)
70
- await self.soft_backend.put(value, wait=wait)
71
+ new_value = await self.put_mock(value, wait=wait)
72
+ if new_value is None:
73
+ new_value = value
74
+ await self.soft_backend.put(new_value, wait=wait)
71
75
  if wait:
72
76
  await self.put_proceeds.wait()
73
77
 
@@ -639,9 +639,10 @@ async def set_and_wait_for_other_value(
639
639
  if wait_for_set_completion:
640
640
  await status
641
641
  except TimeoutError as exc:
642
+ matcher_name = getattr(matcher, "__name__", f"<{type(matcher).__name__}>")
642
643
  raise TimeoutError(
643
644
  f"{match_signal.name} value didn't match value from"
644
- f" {matcher.__name__}() in {timeout}s"
645
+ f" {matcher_name}() in {timeout}s"
645
646
  ) from exc
646
647
 
647
648
  return status
@@ -98,9 +98,12 @@ class Motor(
98
98
  self.motor_done_move = epics_signal_r(int, prefix + ".DMOV")
99
99
  self.low_limit_travel = epics_signal_rw(float, prefix + ".LLM")
100
100
  self.high_limit_travel = epics_signal_rw(float, prefix + ".HLM")
101
+ self.dial_low_limit_travel = epics_signal_rw(float, prefix + ".DLLM")
102
+ self.dial_high_limit_travel = epics_signal_rw(float, prefix + ".DHLM")
101
103
  self.offset_freeze_switch = epics_signal_rw(OffsetMode, prefix + ".FOFF")
102
104
  self.high_limit_switch = epics_signal_r(int, prefix + ".HLS")
103
105
  self.low_limit_switch = epics_signal_r(int, prefix + ".LLS")
106
+ self.output_link = epics_signal_r(str, prefix + ".OUT")
104
107
  self.set_use_switch = epics_signal_rw(UseSetMode, prefix + ".SET")
105
108
 
106
109
  # Note:cannot use epics_signal_x here, as the motor record specifies that
@@ -131,16 +134,26 @@ class Motor(
131
134
  Will raise a MotorLimitsException if the given absolute positions will be
132
135
  outside the motor soft limits.
133
136
  """
134
- motor_lower_limit, motor_upper_limit, egu = await asyncio.gather(
137
+ (
138
+ motor_lower_limit,
139
+ motor_upper_limit,
140
+ egu,
141
+ dial_lower_limit,
142
+ dial_upper_limit,
143
+ ) = await asyncio.gather(
135
144
  self.low_limit_travel.get_value(),
136
145
  self.high_limit_travel.get_value(),
137
146
  self.motor_egu.get_value(),
147
+ self.dial_low_limit_travel.get_value(),
148
+ self.dial_high_limit_travel.get_value(),
138
149
  )
139
150
 
140
- # EPICS motor record treats limits of 0, 0 as no limit
141
- if motor_lower_limit == 0 and motor_upper_limit == 0:
151
+ # EPICS motor record treats dial limits of 0, 0 as no limit
152
+ # Use DLLM and DHLM to check
153
+ if dial_lower_limit == 0 and dial_upper_limit == 0:
142
154
  return
143
155
 
156
+ # Use real motor limit(i.e. HLM and LLM) to check if the move is permissible
144
157
  if (
145
158
  not motor_upper_limit >= abs_start_pos >= motor_lower_limit
146
159
  or not motor_upper_limit >= abs_end_pos >= motor_lower_limit
@@ -150,6 +163,8 @@ class Motor(
150
163
  f"{abs_start_pos}{egu} to "
151
164
  f"{abs_end_pos}{egu} but motor limits are "
152
165
  f"{motor_lower_limit}{egu} <= x <= {motor_upper_limit}{egu} "
166
+ f"dial limits are "
167
+ f"{dial_lower_limit}{egu} <= x <= {dial_upper_limit}"
153
168
  )
154
169
 
155
170
  @AsyncStatus.wrap
@@ -6,7 +6,8 @@ from ophyd_async.core import Array1D, Device, DeviceVector, StandardReadable
6
6
  from ophyd_async.epics import motor
7
7
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
8
8
 
9
- CS_LETTERS = "ABCUVWXYZ"
9
+ # Map the CS axis letters to their index (1 indexed)
10
+ CS_INDEX = {letter: index + 1 for index, letter in enumerate("ABCUVWXYZ")}
10
11
 
11
12
 
12
13
  class PmacTrajectoryIO(StandardReadable):
@@ -20,24 +21,20 @@ class PmacTrajectoryIO(StandardReadable):
20
21
  # 1 indexed CS axes so we can index into them from the compound motor input link
21
22
  self.positions = DeviceVector(
22
23
  {
23
- i + 1: epics_signal_rw(
24
- Array1D[np.float64], f"{prefix}{letter}:Positions"
25
- )
26
- for i, letter in enumerate(CS_LETTERS)
24
+ i: epics_signal_rw(Array1D[np.float64], f"{prefix}{letter}:Positions")
25
+ for letter, i in CS_INDEX.items()
27
26
  }
28
27
  )
29
28
  self.use_axis = DeviceVector(
30
29
  {
31
- i + 1: epics_signal_rw(bool, f"{prefix}{letter}:UseAxis")
32
- for i, letter in enumerate(CS_LETTERS)
30
+ i: epics_signal_rw(bool, f"{prefix}{letter}:UseAxis")
31
+ for letter, i in CS_INDEX.items()
33
32
  }
34
33
  )
35
34
  self.velocities = DeviceVector(
36
35
  {
37
- i + 1: epics_signal_rw(
38
- Array1D[np.float64], f"{prefix}{letter}:Velocities"
39
- )
40
- for i, letter in enumerate(CS_LETTERS)
36
+ i: epics_signal_rw(Array1D[np.float64], f"{prefix}{letter}:Velocities")
37
+ for letter, i in CS_INDEX.items()
41
38
  }
42
39
  )
43
40
  self.total_points = epics_signal_r(int, f"{prefix}TotalPoints_RBV")
@@ -76,8 +73,8 @@ class PmacCoordIO(Device):
76
73
  self.cs_port = epics_signal_r(str, f"{prefix}Port")
77
74
  self.cs_axis_setpoint = DeviceVector(
78
75
  {
79
- i + 1: epics_signal_rw(float, f"{prefix}M{i + 1}:DirectDemand")
80
- for i in range(len(CS_LETTERS))
76
+ i: epics_signal_rw(float, f"{prefix}M{i}:DirectDemand")
77
+ for i in CS_INDEX.values()
81
78
  }
82
79
  )
83
80
  super().__init__(name=name)
@@ -15,10 +15,10 @@ from ophyd_async.core import (
15
15
  wait_for_value,
16
16
  )
17
17
  from ophyd_async.epics.motor import Motor
18
- from ophyd_async.epics.pmac import PmacIO
19
- from ophyd_async.epics.pmac._pmac_io import CS_LETTERS
20
- from ophyd_async.epics.pmac._pmac_trajectory_generation import PVT, Trajectory
21
- from ophyd_async.epics.pmac._utils import (
18
+
19
+ from ._pmac_io import CS_INDEX, PmacIO
20
+ from ._pmac_trajectory_generation import PVT, Trajectory
21
+ from ._utils import (
22
22
  _PmacMotorInfo,
23
23
  calculate_ramp_position_and_duration,
24
24
  )
@@ -131,8 +131,7 @@ class PmacTrajectoryTriggerLogic(FlyerController):
131
131
  slice, path_length, motor_info, ramp_up_time
132
132
  )
133
133
  use_axis = {
134
- axis + 1: (axis in motor_info.motor_cs_index.values())
135
- for axis in range(len(CS_LETTERS))
134
+ i: (i in motor_info.motor_cs_index.values()) for i in CS_INDEX.values()
136
135
  }
137
136
 
138
137
  coros = [
@@ -177,14 +176,14 @@ class PmacTrajectoryTriggerLogic(FlyerController):
177
176
  self, trajectory: Trajectory, motor_info: _PmacMotorInfo
178
177
  ):
179
178
  coros = []
180
- for motor, number in motor_info.motor_cs_index.items():
179
+ for motor, cs_index in motor_info.motor_cs_index.items():
181
180
  coros.append(
182
- self.pmac.trajectory.positions[number + 1].set(
181
+ self.pmac.trajectory.positions[cs_index].set(
183
182
  trajectory.positions[motor]
184
183
  )
185
184
  )
186
185
  coros.append(
187
- self.pmac.trajectory.velocities[number + 1].set(
186
+ self.pmac.trajectory.velocities[cs_index].set(
188
187
  trajectory.velocities[motor]
189
188
  )
190
189
  )
@@ -206,7 +205,7 @@ class PmacTrajectoryTriggerLogic(FlyerController):
206
205
  for motor, position in ramp_up_position.items():
207
206
  coros.append(
208
207
  set_and_wait_for_value(
209
- coord.cs_axis_setpoint[motor_info.motor_cs_index[motor] + 1],
208
+ coord.cs_axis_setpoint[motor_info.motor_cs_index[motor]],
210
209
  position,
211
210
  set_timeout=10,
212
211
  wait_for_set_completion=False,
@@ -558,6 +558,9 @@ def _get_velocity_profile(
558
558
  # Check if all profiles have converged on min_time
559
559
  if np.isclose(new_min_time, min_time):
560
560
  for motor in motors:
561
+ # MIN_INTERVAL should be less than our convergence tolerance
562
+ # such that motors snap to the same point in the time grid
563
+ profiles[motor].quantize()
561
564
  time_arrays[motor], velocity_arrays[motor] = profiles[
562
565
  motor
563
566
  ].make_arrays()
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import re
4
5
  from collections.abc import Sequence
5
6
  from dataclasses import dataclass
6
7
 
@@ -10,7 +11,7 @@ from scanspec.core import Slice
10
11
  from ophyd_async.core import gather_dict
11
12
  from ophyd_async.epics.motor import Motor
12
13
 
13
- from ._pmac_io import CS_LETTERS, PmacIO
14
+ from ._pmac_io import CS_INDEX, PmacIO
14
15
 
15
16
  # PMAC durations are in milliseconds
16
17
  # We must convert from scanspec durations (seconds) to milliseconds
@@ -21,6 +22,11 @@ TICK_S = 0.000001
21
22
  MIN_TURNAROUND = 0.002
22
23
  MIN_INTERVAL = 0.002
23
24
 
25
+ # Regex to parse outlink strings of the form "@asyn(PMAC1CS2, 7)"
26
+ # returning PMAC1CS2 and 7
27
+ # https://regex101.com/r/Mu9XpO/1
28
+ OUTLINK_REGEX = re.compile(r"^\@asyn\(([^,]+),\s*(\d+)\)$")
29
+
24
30
 
25
31
  @dataclass
26
32
  class _PmacMotorInfo:
@@ -44,78 +50,115 @@ class _PmacMotorInfo:
44
50
  dictionaries of motor's to their unique CS index and accelerate rate
45
51
 
46
52
  """
47
- assignments = {
48
- motor: pmac.assignment[pmac.motor_assignment_index[motor]]
49
- for motor in motors
50
- }
51
-
52
- cs_ports, cs_numbers, cs_axes, velocities, accls = await asyncio.gather(
53
- gather_dict(
54
- {motor: assignments[motor].cs_port.get_value() for motor in motors}
55
- ),
56
- gather_dict(
57
- {motor: assignments[motor].cs_number.get_value() for motor in motors}
58
- ),
59
- gather_dict(
60
- {
61
- motor: assignments[motor].cs_axis_letter.get_value()
62
- for motor in motors
63
- }
64
- ),
65
- gather_dict({motor: motor.max_velocity.get_value() for motor in motors}),
66
- gather_dict(
67
- {motor: motor.acceleration_time.get_value() for motor in motors}
68
- ),
69
- )
53
+ is_raw_motor = [motor in pmac.motor_assignment_index for motor in motors]
54
+ if all(is_raw_motor):
55
+ # Get the CS port, number and axis letter from the PVs for the raw motor
56
+ assignments = {
57
+ motor: pmac.assignment[pmac.motor_assignment_index[motor]]
58
+ for motor in motors
59
+ }
60
+ cs_ports, cs_numbers, cs_axis_letters = await asyncio.gather(
61
+ gather_dict(
62
+ {motor: assignments[motor].cs_port.get_value() for motor in motors}
63
+ ),
64
+ gather_dict(
65
+ {
66
+ motor: assignments[motor].cs_number.get_value()
67
+ for motor in motors
68
+ }
69
+ ),
70
+ gather_dict(
71
+ {
72
+ motor: assignments[motor].cs_axis_letter.get_value()
73
+ for motor in motors
74
+ }
75
+ ),
76
+ )
77
+ # Translate axis letters to cs_index and check for duplicates
78
+ motor_cs_index: dict[Motor, int] = {}
79
+ for motor, cs_axis_letter in cs_axis_letters.items():
80
+ if not cs_axis_letter:
81
+ raise ValueError(
82
+ f"Motor {motor.name} does not have an axis assignment."
83
+ )
84
+ try:
85
+ # 1 indexed to match coord
86
+ index = CS_INDEX[cs_axis_letter]
87
+ except KeyError as err:
88
+ raise ValueError(
89
+ f"Motor {motor.name} assigned to '{cs_axis_letter}' "
90
+ f"but must be assigned to one of '{','.join(CS_INDEX)}'"
91
+ ) from err
92
+ if index in motor_cs_index.values():
93
+ raise ValueError(
94
+ f"Motor {motor.name} assigned to '{cs_axis_letter}' "
95
+ "but another motor is already assigned to this axis."
96
+ )
97
+ motor_cs_index[motor] = index
98
+ elif not any(is_raw_motor):
99
+ # Get CS numbers from all the cs ports and output links for the CS motors
100
+ output_links, cs_lookup = await asyncio.gather(
101
+ gather_dict({motor: motor.output_link.get_value() for motor in motors}),
102
+ gather_dict(
103
+ {
104
+ cs_number: cs.cs_port.get_value()
105
+ for cs_number, cs in pmac.coord.items()
106
+ }
107
+ ),
108
+ )
109
+ # Create a reverse lookup from cs_port to cs_number
110
+ cs_reverse_lookup = {
111
+ cs_port: cs_number for cs_number, cs_port in cs_lookup.items()
112
+ }
113
+ cs_ports: dict[Motor, str] = {}
114
+ cs_numbers: dict[Motor, int] = {}
115
+ motor_cs_index: dict[Motor, int] = {}
116
+ # Populate the cs_ports, cs_numbers and motor_cs_index dicts from outlinks
117
+ for motor, output_link in output_links.items():
118
+ match = OUTLINK_REGEX.match(output_link)
119
+ if not match:
120
+ raise ValueError(
121
+ f"Motor {motor.name} has invalid output link '{output_link}'"
122
+ )
123
+ cs_port, cs_index = match.groups()
124
+ cs_ports[motor] = cs_port
125
+ cs_numbers[motor] = cs_reverse_lookup[cs_port]
126
+ motor_cs_index[motor] = int(cs_index)
127
+ else:
128
+ raise ValueError("Unable to use raw motors and CS motors in the same scan")
70
129
 
71
130
  # check if the values in cs_port and cs_number are the same
72
- cs_ports = set(cs_ports.values())
73
-
74
- if len(cs_ports) != 1:
131
+ cs_ports_set = set(cs_ports.values())
132
+ if len(cs_ports_set) != 1:
75
133
  raise RuntimeError(
76
134
  "Failed to fetch common CS port."
77
135
  "Motors passed are assigned to multiple CS ports:"
78
- f"{list(cs_ports)}"
136
+ f"{list(cs_ports_set)}"
79
137
  )
80
-
81
- cs_port = cs_ports.pop()
82
-
83
- cs_numbers = set(cs_numbers.values())
84
- if len(cs_numbers) != 1:
138
+ cs_numbers_set = set(cs_numbers.values())
139
+ if len(cs_numbers_set) != 1:
85
140
  raise RuntimeError(
86
141
  "Failed to fetch common CS number."
87
142
  "Motors passed are assigned to multiple CS numbers:"
88
- f"{list(cs_numbers)}"
143
+ f"{list(cs_numbers_set)}"
89
144
  )
90
145
 
91
- cs_number = cs_numbers.pop()
92
-
93
- motor_cs_index = {}
94
- for motor in cs_axes:
95
- try:
96
- if not cs_axes[motor]:
97
- raise ValueError
98
- motor_cs_index[motor] = CS_LETTERS.index(cs_axes[motor])
99
- except ValueError as err:
100
- raise ValueError(
101
- "Failed to get motor CS index. "
102
- f"Motor {motor.name} assigned to '{cs_axes[motor]}' "
103
- f"but must be assignmed to '{CS_LETTERS}"
104
- ) from err
105
- if len(set(motor_cs_index.values())) != len(motor_cs_index.items()):
106
- raise RuntimeError(
107
- "Failed to fetch distinct CS Axes."
108
- "Motors passed are assigned to the same CS Axis"
109
- f"{list(motor_cs_index)}"
110
- )
111
-
146
+ # Get the velocities and acceleration rates for each motor
147
+ max_velocity, acceleration_time = await asyncio.gather(
148
+ gather_dict({motor: motor.max_velocity.get_value() for motor in motors}),
149
+ gather_dict(
150
+ {motor: motor.acceleration_time.get_value() for motor in motors}
151
+ ),
152
+ )
112
153
  motor_acceleration_rate = {
113
- motor: float(velocities[motor]) / float(accls[motor])
114
- for motor in velocities
154
+ motor: max_velocity[motor] / acceleration_time[motor] for motor in motors
115
155
  }
116
-
117
156
  return _PmacMotorInfo(
118
- cs_port, cs_number, motor_cs_index, motor_acceleration_rate, velocities
157
+ cs_port=cs_ports_set.pop(),
158
+ cs_number=cs_numbers_set.pop(),
159
+ motor_cs_index=motor_cs_index,
160
+ motor_acceleration_rate=motor_acceleration_rate,
161
+ motor_max_velocity=max_velocity,
119
162
  )
120
163
 
121
164
 
@@ -34,15 +34,6 @@ class DataBlock(Device):
34
34
  datasets: SignalR[DatasetTable]
35
35
 
36
36
 
37
- class PulseBlock(Device):
38
- """Used for configuring pulses in the PandA."""
39
-
40
- delay: SignalRW[float]
41
- pulses: SignalRW[int]
42
- step: SignalRW[float]
43
- width: SignalRW[float]
44
-
45
-
46
37
  class PandaPcompDirection(StrictEnum):
47
38
  """Direction options for position compare in the PandA."""
48
39
 
@@ -64,6 +55,16 @@ class PandaPosMux(SubsetEnum):
64
55
  ZERO = "ZERO"
65
56
 
66
57
 
58
+ class PulseBlock(Device):
59
+ """Used for configuring pulses in the PandA."""
60
+
61
+ enable: SignalRW[PandaBitMux]
62
+ delay: SignalRW[float]
63
+ pulses: SignalRW[int]
64
+ step: SignalRW[float]
65
+ width: SignalRW[float]
66
+
67
+
67
68
  class PcompBlock(Device):
68
69
  """Position compare block in the PandA."""
69
70
 
@@ -8,7 +8,6 @@ from ._signal import (
8
8
  tango_signal_w,
9
9
  tango_signal_x,
10
10
  )
11
- from ._tango_readable import TangoReadable
12
11
  from ._tango_transport import (
13
12
  AttributeProxy,
14
13
  CommandProxy,
@@ -50,7 +49,6 @@ __all__ = [
50
49
  "tango_signal_w",
51
50
  "tango_signal_x",
52
51
  "TangoDevice",
53
- "TangoReadable",
54
52
  "TangoPolling",
55
53
  "TangoDeviceConnector",
56
54
  "TangoLongStringTable",
@@ -29,7 +29,7 @@ class TangoDevice(Device):
29
29
 
30
30
  def __init__(
31
31
  self,
32
- trl: str | None,
32
+ trl: str = "",
33
33
  support_events: bool = False,
34
34
  name: str = "",
35
35
  auto_fill_signals: bool = True,
@@ -80,6 +80,9 @@ class TangoDeviceConnector(DeviceConnector):
80
80
  self._support_events = support_events
81
81
  self._auto_fill_signals = auto_fill_signals
82
82
 
83
+ def set_trl(self, trl: str):
84
+ self.trl = trl
85
+
83
86
  def create_children_from_annotations(self, device: Device):
84
87
  if not hasattr(self, "filler"):
85
88
  self.filler = DeviceFiller(
@@ -1,11 +1,18 @@
1
1
  from typing import Annotated as A
2
2
 
3
- from ophyd_async.core import DEFAULT_TIMEOUT, AsyncStatus, SignalR, SignalRW, SignalX
3
+ from ophyd_async.core import (
4
+ DEFAULT_TIMEOUT,
5
+ AsyncStatus,
6
+ SignalR,
7
+ SignalRW,
8
+ SignalX,
9
+ StandardReadable,
10
+ )
4
11
  from ophyd_async.core import StandardReadableFormat as Format
5
- from ophyd_async.tango.core import TangoPolling, TangoReadable
12
+ from ophyd_async.tango.core import TangoDevice, TangoPolling
6
13
 
7
14
 
8
- class TangoCounter(TangoReadable):
15
+ class TangoCounter(TangoDevice, StandardReadable):
9
16
  """Tango counting device."""
10
17
 
11
18
  # Enter the name and type of the signals you want to use
@@ -11,16 +11,17 @@ from ophyd_async.core import (
11
11
  SignalR,
12
12
  SignalRW,
13
13
  SignalX,
14
+ StandardReadable,
14
15
  WatchableAsyncStatus,
15
16
  WatcherUpdate,
16
17
  observe_value,
17
18
  wait_for_value,
18
19
  )
19
20
  from ophyd_async.core import StandardReadableFormat as Format
20
- from ophyd_async.tango.core import DevStateEnum, TangoPolling, TangoReadable
21
+ from ophyd_async.tango.core import DevStateEnum, TangoDevice, TangoPolling
21
22
 
22
23
 
23
- class TangoMover(TangoReadable, Movable, Stoppable):
24
+ class TangoMover(TangoDevice, StandardReadable, Movable, Stoppable):
24
25
  """Tango moving device."""
25
26
 
26
27
  # Enter the name and type of the signals you want to use
@@ -32,7 +33,7 @@ class TangoMover(TangoReadable, Movable, Stoppable):
32
33
  # If a tango name clashes with a bluesky verb, add a trailing underscore
33
34
  stop_: SignalX
34
35
 
35
- def __init__(self, trl: str | None = "", name=""):
36
+ def __init__(self, trl: str = "", name=""):
36
37
  super().__init__(trl, name=name)
37
38
  self.add_readables([self.position], Format.HINTED_SIGNAL)
38
39
  self.add_readables([self.velocity], Format.CONFIG_SIGNAL)
@@ -119,14 +119,17 @@ def _unset_side_effect_cm(put_mock: AsyncMock):
119
119
 
120
120
  def callback_on_mock_put(
121
121
  signal: Signal[SignalDatatypeT],
122
- callback: Callable[[SignalDatatypeT, bool], None]
123
- | Callable[[SignalDatatypeT, bool], Awaitable[None]],
122
+ callback: Callable[[SignalDatatypeT, bool], SignalDatatypeT | None]
123
+ | Callable[[SignalDatatypeT, bool], Awaitable[SignalDatatypeT | None]],
124
124
  ):
125
125
  """For setting a callback when a backend is put to.
126
126
 
127
127
  Can either be used in a context, with the callback being unset on exit, or
128
128
  as an ordinary function.
129
129
 
130
+ The value that the callback returns (if not None) will be set to the signal
131
+ readback. If None is returned then the readback will be set to the setpoint.
132
+
130
133
  :param signal: A signal with a `MockSignalBackend` backend.
131
134
  :param callback: The callback to call when the backend is put to during the
132
135
  context.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ophyd-async
3
- Version: 0.13.5
3
+ Version: 0.13.7
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
@@ -1,7 +1,7 @@
1
1
  ophyd_async/__init__.py,sha256=dcAA3qsj1nNIMe5l-v2tlduZ_ypwBmyuHe45Lsq4k4w,206
2
2
  ophyd_async/__main__.py,sha256=n_U4O9bgm97OuboUB_9eK7eFiwy8BZSgXJ0OzbE0DqU,481
3
3
  ophyd_async/_docs_parser.py,sha256=gPYrigfSbYCF7QoSf2UvE-cpQu4snSssl7ZWN-kKDzI,352
4
- ophyd_async/_version.py,sha256=s59flr3WuC_9oONlGKcmFdyemUNgE-R3uqabpBNb6YU,706
4
+ ophyd_async/_version.py,sha256=JHUU2H4LQG4g5yLVtdeoRqh4fKkn1TUpLvJEVN4qjr4,706
5
5
  ophyd_async/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  ophyd_async/core/__init__.py,sha256=yGbkVju5otO8DmA3KyerYB0gLtcX5sdxlq3ajyGha4M,5531
7
7
  ophyd_async/core/_derived_signal.py,sha256=TuZza_j3J1Bw4QSqBYB9Ta2FyQP5BycO3nSHVtJ890Q,13015
@@ -13,12 +13,12 @@ ophyd_async/core/_enums.py,sha256=2vh6x0rZ6SLiw2xxq1xVIn-GpbLDFc8wZoVdA55QiE8,37
13
13
  ophyd_async/core/_flyer.py,sha256=8zKyU5aQOr_t59GIUwsYeb8NSabdvBp0swwuRe4v5VQ,3457
14
14
  ophyd_async/core/_hdf_dataset.py,sha256=0bIX_ZbFSMdXqDwRtEvV-0avHnwXhjPddE5GVNmo7H8,2608
15
15
  ophyd_async/core/_log.py,sha256=DxKR4Nz3SgTaTzKBZWqt-w48yT8WUAr_3Qr223TEWRw,3587
16
- ophyd_async/core/_mock_signal_backend.py,sha256=SPdCbVWss6-iL9C3t9u0IvR_Ln9JeDypVd18WlivdjE,3156
16
+ ophyd_async/core/_mock_signal_backend.py,sha256=N6rzwBR3QwHZMcifIZg6sbno6-Ts-1jK0aFVt-3jHAg,3289
17
17
  ophyd_async/core/_protocol.py,sha256=wQ_snxhTprHqEjQb1HgFwBljwolMY6A8C3xgV1PXwdU,4051
18
18
  ophyd_async/core/_providers.py,sha256=WBht3QCgvGc0stNcwH6z4Zr6hAz3e01-88NjsYI2w6I,9740
19
19
  ophyd_async/core/_readable.py,sha256=iBo1YwA5bsAbzLbznvmSnzKDWUuGkLh850Br3BXsgeU,11707
20
20
  ophyd_async/core/_settings.py,sha256=_ZccbXKP7j5rG6-bMKk7aaLr8hChdRDAPY_YSR71XXM,4213
21
- ophyd_async/core/_signal.py,sha256=085vcyjhEyZECi4Svjq6DUM5kxrzZh8s5DoHR2LALOc,28195
21
+ ophyd_async/core/_signal.py,sha256=LVQ38-Txn6lIYFsIbRbZClOy9WXlLDc16FdaE1wVmII,28278
22
22
  ophyd_async/core/_signal_backend.py,sha256=F3ma45cIIJ3D702zsVZIqn4Jv7u05YzMQBQND70QCbQ,6987
23
23
  ophyd_async/core/_soft_signal_backend.py,sha256=NJUuyaCKtBZjggt8WKi7_lKQRHasToxviuQvl5xbhLU,6222
24
24
  ophyd_async/core/_status.py,sha256=a2IDvv_GvUcFuhjQA5bQzWm9ngR6zGc9PR4XcZiaeqk,6557
@@ -26,7 +26,7 @@ ophyd_async/core/_table.py,sha256=ryJ7AwJBglQUzwP9_aSjR8cu8EKvYXfo1q1byhke3Uc,72
26
26
  ophyd_async/core/_utils.py,sha256=gUewO4XPrBxsEWzObNumqAB2Q7Hwrd5F_Nc6B2XwQIM,12532
27
27
  ophyd_async/core/_yaml_settings.py,sha256=Qojhku9l5kPSkTnEylCRWTe0gpw6S_XP5av5dPpqFgQ,2089
28
28
  ophyd_async/epics/__init__.py,sha256=ou4yEaH9VZHz70e8oM614-arLMQvUfQyXhRJsnEpWn8,60
29
- ophyd_async/epics/motor.py,sha256=nS6Vx4-6vYso3b_QtpE8p8dlys5jRr2LK7prZCkZMc8,9558
29
+ ophyd_async/epics/motor.py,sha256=RDcyKC_zFR5zLpAcf_yAbuY-1ImE3L3uBAoUXDEiz-s,10224
30
30
  ophyd_async/epics/signal.py,sha256=0A-supp9ajr63O6aD7F9oG0-Q26YmRjk-ZGh57-jo1Y,239
31
31
  ophyd_async/epics/adandor/__init__.py,sha256=dlitllrAdhvh16PAcVMUSSEytTDNMu6_HuYk8KD1EoY,343
32
32
  ophyd_async/epics/adandor/_andor.py,sha256=TijGjNVxuH-P0X7UACPt9eLLQ449DwMyVhbn1kV7Le8,1245
@@ -83,10 +83,10 @@ ophyd_async/epics/demo/point_detector_channel.db,sha256=FZ9H6HjqplhcF2jgimv_dT1n
83
83
  ophyd_async/epics/odin/__init__.py,sha256=7kRqVzwoD8PVtp7Nj9iQWlgbLeoWE_8oiq-B0kixwTE,93
84
84
  ophyd_async/epics/odin/_odin_io.py,sha256=YDBrS15PnEKe5SHmz397Emh--lZSQEnbR3G7p8pbShY,6533
85
85
  ophyd_async/epics/pmac/__init__.py,sha256=GqJTiJudqE9pu050ZNED09F9tKRfazn0wBsojsMH2gg,273
86
- ophyd_async/epics/pmac/_pmac_io.py,sha256=E7tdaq9FAM6BeGQG1L8ALEYpXOlyqTXl_NLLSWzmCdk,4345
87
- ophyd_async/epics/pmac/_pmac_trajectory.py,sha256=7wTaetNNy9uj7C_skcs0VH5BthuY4Ec5zn-m4ROxd8k,7866
88
- ophyd_async/epics/pmac/_pmac_trajectory_generation.py,sha256=l-jFlJCs2-LowoF6vJn2ex0Evcon2OeYPMHWzEZnEgk,27136
89
- ophyd_async/epics/pmac/_utils.py,sha256=n4vh9n7hmaWe9g02FtguF2oDsYuVvsTgmK7fYEyGuIE,6092
86
+ ophyd_async/epics/pmac/_pmac_io.py,sha256=cbChieNrDWRzrr5Mdsqtm2Azp8sG0KHP9rGeJxmbYrA,4332
87
+ ophyd_async/epics/pmac/_pmac_trajectory.py,sha256=hzAcpLNmFoNceubsjk6mjsmg6-PgSjWUu1a8Exyvi6I,7729
88
+ ophyd_async/epics/pmac/_pmac_trajectory_generation.py,sha256=3IIxXa0r6-2uNnILKLGxp3xosOZx8MubKF-F_OM7uaw,27331
89
+ ophyd_async/epics/pmac/_utils.py,sha256=MfuY6NicT7wkwVIWAZkWoCu1ZoSzy6jda1wLK9XAOLA,8614
90
90
  ophyd_async/epics/testing/__init__.py,sha256=aTIv4D2DYrpnGco5RQF8QuLG1SfFkIlTyM2uYEKXltA,522
91
91
  ophyd_async/epics/testing/_example_ioc.py,sha256=zb4ZEUzuB2MrSw5ETPLIiHhf-2BRU1Bdxco6Kh4iI1I,3880
92
92
  ophyd_async/epics/testing/_utils.py,sha256=9gxpwaWX0HGtacu1LTupcw7viXN8G78RmuNciU_-cjs,1702
@@ -105,7 +105,7 @@ ophyd_async/fastcs/jungfrau/_signals.py,sha256=8seZCkKTb-xJL0IdB2el8VTEbWNaCvZIh
105
105
  ophyd_async/fastcs/jungfrau/_utils.py,sha256=QpdWbPT_31Jwyi7INFMRq9hncSZIK_4J3l6wpuppzn8,2875
106
106
  ophyd_async/fastcs/odin/__init__.py,sha256=da1PTClDMl-IBkrSvq6JC1lnS-K_BASzCvxVhNxN5Ls,13
107
107
  ophyd_async/fastcs/panda/__init__.py,sha256=GbnPqH_13wvyPK1CvRHGAViamKVWHY9n-sTmfAdcnMA,1229
108
- ophyd_async/fastcs/panda/_block.py,sha256=ejWajojXpP85kdWoB4zl6Z4s9S-Izs4brBdxosfjYbA,2645
108
+ ophyd_async/fastcs/panda/_block.py,sha256=Bffta9IkuSq_NSvidDvLyC1YUrfQCwMhppsu1Te7vec,2679
109
109
  ophyd_async/fastcs/panda/_control.py,sha256=xtW3dH_MLQoycgP-4vJtYx1M9alHjWo13iu9UFTgwzY,1306
110
110
  ophyd_async/fastcs/panda/_hdf_panda.py,sha256=tL_OWHxlMQcMZGq9sxHLSeag6hP9MRIbTPn1W0u0iNI,1237
111
111
  ophyd_async/fastcs/panda/_table.py,sha256=maKGoKypEuYqTSVWGgDO6GMEKOtlDm9Dn5YiYdBzu6c,2486
@@ -131,17 +131,16 @@ ophyd_async/sim/_pattern_generator.py,sha256=kuxvyX2gIxrywhQRhaO1g8YluBT7LBkE20I
131
131
  ophyd_async/sim/_point_detector.py,sha256=wMG_ncvm99WMCPihlFyuMEf3UknAxCpB1hpk3uKiENE,3024
132
132
  ophyd_async/sim/_stage.py,sha256=_SywbmSQwxf7JLx68qwo0RpiB3oIWlbTLmvRKxUoig0,1602
133
133
  ophyd_async/tango/__init__.py,sha256=g9xzjlzPpUAP12YI-kYwfAoLSYPAQdL1S11R2c-cius,60
134
- ophyd_async/tango/core/__init__.py,sha256=OOVdHu07cssK90F-caG0CY7qKpPYy0MSV421YNAI-_8,1413
135
- ophyd_async/tango/core/_base_device.py,sha256=e9oqSL-fDOj8r9nUUFZkbibhRGbI6HYtlnZjK5B_2fE,5033
134
+ ophyd_async/tango/core/__init__.py,sha256=dO2tG_y61zZFQRQh5L37Ps-IqNf-DGOT77Ov5Kobfhs,1349
135
+ ophyd_async/tango/core/_base_device.py,sha256=X5ncxaWKOfRhhqPyT8tmTBJGc3ldGthw1ZCe_j_M2Tg,5088
136
136
  ophyd_async/tango/core/_converters.py,sha256=xI_RhMR8dY6IVORUZVVCL9LdYnEE6TA6BBPX_lTu06w,2183
137
137
  ophyd_async/tango/core/_signal.py,sha256=8mIxRVEVjhDN33LDbbKZWGMUYn9Gl5ZMEIYw6GSBTUE,5569
138
- ophyd_async/tango/core/_tango_readable.py,sha256=ctR6YcBGGatW6Jp2kvddA1hVZ2v1CidPsF9FmJK9BYg,406
139
138
  ophyd_async/tango/core/_tango_transport.py,sha256=KxjhHqKADrOvzGi9tbOQXUWdsJ0NKGejWxHItxpUsjg,37401
140
139
  ophyd_async/tango/core/_utils.py,sha256=pwT7V1DNWSyPOSzvDZ6OsDZTjaV-pAeDLDlmgtHVcNM,1673
141
140
  ophyd_async/tango/demo/__init__.py,sha256=_j-UicTnckuIBp8PnieFMOMnLFGivnaKdmo9o0hYtzc,256
142
- ophyd_async/tango/demo/_counter.py,sha256=2J4SCHnBWLF0O5mFWlJdO4tmnElvlx5sRrk4op_AC9U,1139
141
+ ophyd_async/tango/demo/_counter.py,sha256=m6zxOJLbHgCEBAapVc1UiOOqKj5lvrlxjA6mXWMRMjo,1200
143
142
  ophyd_async/tango/demo/_detector.py,sha256=X5YWHAjukKZ7iYF1fBNle4CBDj1X5rvj0lnPMOcnRCU,1340
144
- ophyd_async/tango/demo/_mover.py,sha256=i-Tq5nDmYi4RcC4O6mOJoVeMEIIxuqyS_2AfjTpAcnk,2884
143
+ ophyd_async/tango/demo/_mover.py,sha256=FyG9g1TLaWoqjbLblqWK8inMuDcNVlioq0MIeD5npz4,2913
145
144
  ophyd_async/tango/demo/_tango/__init__.py,sha256=FfONT7vM49nNo3a1Lv-LcMZO9EHv6bv91yY-RnxIib4,85
146
145
  ophyd_async/tango/demo/_tango/_servers.py,sha256=putvERDyibibaTbhdWyqZB_axj2fURXqzDsZb9oSW14,2991
147
146
  ophyd_async/tango/testing/__init__.py,sha256=l52SmX9XuxZUBuLpOYJzHfskkWVYhx3RkSbGL_wUu5Y,199
@@ -150,13 +149,13 @@ ophyd_async/tango/testing/_test_config.py,sha256=i3t5d4wjUEtAvvSSZNz_bH_r5VEvUph
150
149
  ophyd_async/testing/__init__.py,sha256=jDBzUAHGDMfkhd-_9u0CJWEq0E0sPrIGGlLmVzEyxY8,1742
151
150
  ophyd_async/testing/__pytest_assert_rewrite.py,sha256=_SU2UfChPgEf7CFY7aYH2B7MLp-07_qYnVLyu6QtDL8,129
152
151
  ophyd_async/testing/_assert.py,sha256=Ss_XDToi1ymUfr0Z1r45A2Fmg7-9UOv9gYkJEBsZPv8,8795
153
- ophyd_async/testing/_mock_signal_utils.py,sha256=d-n_923ii59-ae9TbqVuIK9MAJpDmu0k47fzgJLj8t8,5195
152
+ ophyd_async/testing/_mock_signal_utils.py,sha256=GOjELaRFg9zJKcpeLFXjN7ViMT1AK2Hu52lkLMI5XUc,5393
154
153
  ophyd_async/testing/_one_of_everything.py,sha256=U9ui7B-iNHDM3H3hIWUuaCb8Gc2eLlUh0sBHUlQldT0,4741
155
154
  ophyd_async/testing/_single_derived.py,sha256=5-HOTzgePcZ354NK_ssVpyIbJoJmKyjVQCxSwQXUC-4,2730
156
155
  ophyd_async/testing/_utils.py,sha256=zClRo5ve8RGia7wQnby41W-Zprj-slOA5da1LfYnuhw,45
157
156
  ophyd_async/testing/_wait_for_pending.py,sha256=YZAR48n-CW0GsPey3zFRzMJ4byDAr3HvMIoawjmTrHw,732
158
- ophyd_async-0.13.5.dist-info/licenses/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
159
- ophyd_async-0.13.5.dist-info/METADATA,sha256=poy7PYBMjXEoQKIWeOyo9kcz_Vv5LhZSA9w9xFZRMR8,5703
160
- ophyd_async-0.13.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
161
- ophyd_async-0.13.5.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
162
- ophyd_async-0.13.5.dist-info/RECORD,,
157
+ ophyd_async-0.13.7.dist-info/licenses/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
158
+ ophyd_async-0.13.7.dist-info/METADATA,sha256=D28G3xy4fiF7xtLVoy10CqOiI7ZLDs2z1MO6DICwVdA,5703
159
+ ophyd_async-0.13.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
160
+ ophyd_async-0.13.7.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
161
+ ophyd_async-0.13.7.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from ophyd_async.core import StandardReadable
4
-
5
- from ._base_device import TangoDevice
6
-
7
-
8
- class TangoReadable(TangoDevice, StandardReadable):
9
- def __init__(
10
- self,
11
- trl: str | None = None,
12
- name: str = "",
13
- auto_fill_signals: bool = True,
14
- ) -> None:
15
- TangoDevice.__init__(self, trl, name=name, auto_fill_signals=auto_fill_signals)