ophyd-async 0.3a4__py3-none-any.whl → 0.3a6__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 (36) hide show
  1. ophyd_async/_version.py +1 -1
  2. ophyd_async/core/__init__.py +6 -1
  3. ophyd_async/core/async_status.py +83 -39
  4. ophyd_async/core/detector.py +23 -29
  5. ophyd_async/core/device.py +32 -11
  6. ophyd_async/core/flyer.py +1 -1
  7. ophyd_async/core/mock_signal_backend.py +14 -15
  8. ophyd_async/core/mock_signal_utils.py +9 -13
  9. ophyd_async/core/signal.py +71 -21
  10. ophyd_async/core/utils.py +30 -0
  11. ophyd_async/epics/areadetector/aravis.py +1 -5
  12. ophyd_async/epics/areadetector/controllers/aravis_controller.py +6 -1
  13. ophyd_async/epics/areadetector/drivers/ad_base.py +1 -7
  14. ophyd_async/epics/areadetector/drivers/aravis_driver.py +2 -120
  15. ophyd_async/epics/areadetector/writers/hdf_writer.py +3 -2
  16. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -2
  17. ophyd_async/epics/areadetector/writers/nd_plugin.py +9 -0
  18. ophyd_async/epics/demo/__init__.py +33 -34
  19. ophyd_async/epics/motion/motor.py +47 -42
  20. ophyd_async/epics/pvi/pvi.py +2 -2
  21. ophyd_async/epics/signal/__init__.py +8 -1
  22. ophyd_async/panda/__init__.py +2 -0
  23. ophyd_async/panda/writers/_hdf_writer.py +4 -4
  24. ophyd_async/plan_stubs/__init__.py +13 -0
  25. ophyd_async/plan_stubs/ensure_connected.py +22 -0
  26. ophyd_async/plan_stubs/fly.py +149 -0
  27. ophyd_async/protocols.py +32 -2
  28. ophyd_async/sim/demo/sim_motor.py +67 -82
  29. {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/METADATA +1 -1
  30. {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/RECORD +34 -33
  31. ophyd_async/planstubs/__init__.py +0 -5
  32. ophyd_async/planstubs/prepare_trigger_and_dets.py +0 -57
  33. {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/LICENSE +0 -0
  34. {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/WHEEL +0 -0
  35. {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/entry_points.txt +0 -0
  36. {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,20 @@
1
1
  import asyncio
2
- import time
3
- from typing import Callable, List, Optional
4
2
 
5
3
  from bluesky.protocols import Movable, Stoppable
6
4
 
7
- from ophyd_async.core import AsyncStatus, ConfigSignal, HintedSignal, StandardReadable
5
+ from ophyd_async.core import (
6
+ ConfigSignal,
7
+ HintedSignal,
8
+ StandardReadable,
9
+ WatchableAsyncStatus,
10
+ )
11
+ from ophyd_async.core.signal import observe_value
12
+ from ophyd_async.core.utils import (
13
+ DEFAULT_TIMEOUT,
14
+ CalculatableTimeout,
15
+ CalculateTimeout,
16
+ WatcherUpdate,
17
+ )
8
18
 
9
19
  from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
10
20
 
@@ -40,55 +50,50 @@ class Motor(StandardReadable, Movable, Stoppable):
40
50
  # Readback should be named the same as its parent in read()
41
51
  self.user_readback.set_name(name)
42
52
 
43
- async def _move(
44
- self, new_position: float, watchers: Optional[List[Callable]] = None
53
+ @WatchableAsyncStatus.wrap
54
+ async def set(
55
+ self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
45
56
  ):
46
- if watchers is None:
47
- watchers = []
48
57
  self._set_success = True
49
- start = time.monotonic()
50
- old_position, units, precision = await asyncio.gather(
58
+ (
59
+ old_position,
60
+ units,
61
+ precision,
62
+ velocity,
63
+ acceleration_time,
64
+ ) = await asyncio.gather(
51
65
  self.user_setpoint.get_value(),
52
66
  self.motor_egu.get_value(),
53
67
  self.precision.get_value(),
68
+ self.velocity.get_value(),
69
+ self.acceleration_time.get_value(),
54
70
  )
55
-
56
- def update_watchers(current_position: float):
57
- for watcher in watchers:
58
- watcher(
59
- name=self.name,
60
- current=current_position,
61
- initial=old_position,
62
- target=new_position,
63
- unit=units,
64
- precision=precision,
65
- time_elapsed=time.monotonic() - start,
66
- )
67
-
68
- self.user_readback.subscribe_value(update_watchers)
69
- try:
70
- await self.user_setpoint.set(new_position)
71
- finally:
72
- self.user_readback.clear_sub(update_watchers)
71
+ if timeout is CalculateTimeout:
72
+ assert velocity > 0, "Motor has zero velocity"
73
+ timeout = (
74
+ abs(new_position - old_position) / velocity
75
+ + 2 * acceleration_time
76
+ + DEFAULT_TIMEOUT
77
+ )
78
+ move_status = self.user_setpoint.set(new_position, wait=True, timeout=timeout)
79
+ async for current_position in observe_value(
80
+ self.user_readback, done_status=move_status
81
+ ):
82
+ yield WatcherUpdate(
83
+ current=current_position,
84
+ initial=old_position,
85
+ target=new_position,
86
+ name=self.name,
87
+ unit=units,
88
+ precision=precision,
89
+ )
73
90
  if not self._set_success:
74
91
  raise RuntimeError("Motor was stopped")
75
92
 
76
- def move(self, new_position: float, timeout: Optional[float] = None):
77
- """Commandline only synchronous move of a Motor"""
78
- from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
79
-
80
- if in_bluesky_event_loop():
81
- raise RuntimeError("Will deadlock run engine if run in a plan")
82
- call_in_bluesky_event_loop(self._move(new_position), timeout) # type: ignore
83
-
84
- def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStatus:
85
- watchers: List[Callable] = []
86
- coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
87
- return AsyncStatus(coro, watchers)
88
-
89
93
  async def stop(self, success=False):
90
94
  self._set_success = success
91
95
  # Put with completion will never complete as we are waiting for completion on
92
96
  # the move above, so need to pass wait=False
93
- status = self.motor_stop.trigger(wait=False)
94
- await status
97
+ await self.motor_stop.trigger(wait=False)
98
+ # Trigger any callbacks
99
+ await self.user_readback._backend.put(await self.user_readback.get_value())
@@ -91,7 +91,7 @@ def _verify_common_blocks(entry: PVIEntry, common_device: Type[Device]):
91
91
  return
92
92
  common_sub_devices = get_type_hints(common_device)
93
93
  for sub_name, sub_device in common_sub_devices.items():
94
- if sub_name in ("_name", "parent"):
94
+ if sub_name.startswith("_") or sub_name == "parent":
95
95
  continue
96
96
  assert entry.sub_entries
97
97
  device_t, is_optional = _strip_union(sub_device)
@@ -161,7 +161,7 @@ def _mock_common_blocks(device: Device, stripped_type: Optional[Type] = None):
161
161
  sub_devices = (
162
162
  (field, field_type)
163
163
  for field, field_type in get_type_hints(device_t).items()
164
- if field not in ("_name", "parent")
164
+ if not field.startswith("_") and field != "parent"
165
165
  )
166
166
 
167
167
  for device_name, device_cls in sub_devices:
@@ -1,8 +1,15 @@
1
- from .signal import epics_signal_r, epics_signal_rw, epics_signal_w, epics_signal_x
1
+ from .signal import (
2
+ epics_signal_r,
3
+ epics_signal_rw,
4
+ epics_signal_rw_rbv,
5
+ epics_signal_w,
6
+ epics_signal_x,
7
+ )
2
8
 
3
9
  __all__ = [
4
10
  "epics_signal_r",
5
11
  "epics_signal_rw",
12
+ "epics_signal_rw_rbv",
6
13
  "epics_signal_w",
7
14
  "epics_signal_x",
8
15
  ]
@@ -15,6 +15,7 @@ from ._table import (
15
15
  seq_table_from_arrays,
16
16
  seq_table_from_rows,
17
17
  )
18
+ from ._trigger import StaticSeqTableTriggerLogic
18
19
  from ._utils import phase_sorter
19
20
 
20
21
  __all__ = [
@@ -33,4 +34,5 @@ __all__ = [
33
34
  "TimeUnits",
34
35
  "DataBlock",
35
36
  "CommonPandABlocks",
37
+ "StaticSeqTableTriggerLogic",
36
38
  ]
@@ -79,10 +79,10 @@ async def get_signals_marked_for_capture(
79
79
  capture_signals.keys(), capture_signals.values(), signal_values
80
80
  ):
81
81
  signal_path = signal_path.replace("_capture", "")
82
- if (signal_value.value in iter(Capture)) and (signal_value.value != Capture.No):
82
+ if (signal_value in iter(Capture)) and (signal_value != Capture.No):
83
83
  signals_to_capture[signal_path] = CaptureSignalWrapper(
84
84
  signal_object,
85
- signal_value.value,
85
+ signal_value,
86
86
  )
87
87
 
88
88
  return signals_to_capture
@@ -126,7 +126,7 @@ class PandaHDFWriter(DetectorWriter):
126
126
  str(info.root / info.resource_dir)
127
127
  ),
128
128
  self.panda_device.data.hdf_file_name.set(
129
- f"{info.prefix}{self.panda_device.name}{info.suffix}",
129
+ f"{info.prefix}{self.panda_device.name}{info.suffix}.h5",
130
130
  ),
131
131
  self.panda_device.data.num_capture.set(0),
132
132
  )
@@ -149,7 +149,7 @@ class PandaHDFWriter(DetectorWriter):
149
149
  else split_path[-2]
150
150
  )
151
151
 
152
- for suffix in str(capture_signal.capture_type).split(" "):
152
+ for suffix in capture_signal.capture_type.split(" "):
153
153
  self._datasets.append(
154
154
  _HDFDataset(
155
155
  name,
@@ -0,0 +1,13 @@
1
+ from .ensure_connected import ensure_connected
2
+ from .fly import (
3
+ fly_and_collect,
4
+ prepare_static_seq_table_flyer_and_detectors_with_same_trigger,
5
+ time_resolved_fly_and_collect_with_static_seq_table,
6
+ )
7
+
8
+ __all__ = [
9
+ "fly_and_collect",
10
+ "prepare_static_seq_table_flyer_and_detectors_with_same_trigger",
11
+ "time_resolved_fly_and_collect_with_static_seq_table",
12
+ "ensure_connected",
13
+ ]
@@ -0,0 +1,22 @@
1
+ import bluesky.plan_stubs as bps
2
+
3
+ from ophyd_async.core.device import Device
4
+ from ophyd_async.core.utils import DEFAULT_TIMEOUT, wait_for_connection
5
+
6
+
7
+ def ensure_connected(
8
+ *devices: Device,
9
+ mock: bool = False,
10
+ timeout: float = DEFAULT_TIMEOUT,
11
+ force_reconnect=False,
12
+ ):
13
+ yield from bps.wait_for(
14
+ [
15
+ lambda: wait_for_connection(
16
+ **{
17
+ device.name: device.connect(mock, timeout, force_reconnect)
18
+ for device in devices
19
+ }
20
+ )
21
+ ]
22
+ )
@@ -0,0 +1,149 @@
1
+ from typing import List
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from bluesky.utils import short_uid
5
+
6
+ from ophyd_async.core.detector import DetectorTrigger, StandardDetector, TriggerInfo
7
+ from ophyd_async.core.flyer import HardwareTriggeredFlyable
8
+ from ophyd_async.core.utils import in_micros
9
+ from ophyd_async.panda._table import SeqTable, SeqTableRow, seq_table_from_rows
10
+ from ophyd_async.panda._trigger import SeqTableInfo
11
+
12
+
13
+ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
14
+ flyer: HardwareTriggeredFlyable[SeqTableInfo],
15
+ detectors: List[StandardDetector],
16
+ number_of_frames: int,
17
+ exposure: float,
18
+ shutter_time: float,
19
+ repeats: int = 1,
20
+ period: float = 0.0,
21
+ ):
22
+ """Prepare a hardware triggered flyable and one or more detectors.
23
+
24
+ Prepare a hardware triggered flyable and one or more detectors with the
25
+ same trigger. This method constructs TriggerInfo and a static sequence
26
+ table from required parameters. The table is required to prepare the flyer,
27
+ and the TriggerInfo is required to prepare the detector(s).
28
+
29
+ This prepares all supplied detectors with the same trigger.
30
+
31
+ """
32
+ if not detectors:
33
+ raise ValueError("No detectors provided. There must be at least one.")
34
+
35
+ deadtime = max(det.controller.get_deadtime(exposure) for det in detectors)
36
+
37
+ trigger_info = TriggerInfo(
38
+ num=number_of_frames * repeats,
39
+ trigger=DetectorTrigger.constant_gate,
40
+ deadtime=deadtime,
41
+ livetime=exposure,
42
+ )
43
+ trigger_time = number_of_frames * (exposure + deadtime)
44
+ pre_delay = max(period - 2 * shutter_time - trigger_time, 0)
45
+
46
+ table: SeqTable = seq_table_from_rows(
47
+ # Wait for pre-delay then open shutter
48
+ SeqTableRow(
49
+ time1=in_micros(pre_delay),
50
+ time2=in_micros(shutter_time),
51
+ outa2=True,
52
+ ),
53
+ # Keeping shutter open, do N triggers
54
+ SeqTableRow(
55
+ repeats=number_of_frames,
56
+ time1=in_micros(exposure),
57
+ outa1=True,
58
+ outb1=True,
59
+ time2=in_micros(deadtime),
60
+ outa2=True,
61
+ ),
62
+ # Add the shutter close
63
+ SeqTableRow(time2=in_micros(shutter_time)),
64
+ )
65
+
66
+ table_info = SeqTableInfo(table, repeats)
67
+
68
+ for det in detectors:
69
+ yield from bps.prepare(det, trigger_info, wait=False, group="prep")
70
+ yield from bps.prepare(flyer, table_info, wait=False, group="prep")
71
+ yield from bps.wait(group="prep")
72
+
73
+
74
+ def fly_and_collect(
75
+ stream_name: str,
76
+ flyer: HardwareTriggeredFlyable[SeqTableInfo],
77
+ detectors: List[StandardDetector],
78
+ ):
79
+ """Kickoff, complete and collect with a flyer and multiple detectors.
80
+
81
+ This stub takes a flyer and one or more detectors that have been prepared. It
82
+ declares a stream for the detectors, then kicks off the detectors and the flyer.
83
+ The detectors are collected until the flyer and detectors have completed.
84
+
85
+ """
86
+ yield from bps.declare_stream(*detectors, name=stream_name, collect=True)
87
+ yield from bps.kickoff(flyer, wait=True)
88
+ for detector in detectors:
89
+ yield from bps.kickoff(detector)
90
+
91
+ # collect_while_completing
92
+ group = short_uid(label="complete")
93
+
94
+ yield from bps.complete(flyer, wait=False, group=group)
95
+ for detector in detectors:
96
+ yield from bps.complete(detector, wait=False, group=group)
97
+
98
+ done = False
99
+ while not done:
100
+ try:
101
+ yield from bps.wait(group=group, timeout=0.5)
102
+ except TimeoutError:
103
+ pass
104
+ else:
105
+ done = True
106
+ yield from bps.collect(
107
+ *detectors,
108
+ return_payload=False,
109
+ name=stream_name,
110
+ )
111
+ yield from bps.wait(group=group)
112
+
113
+
114
+ def time_resolved_fly_and_collect_with_static_seq_table(
115
+ stream_name: str,
116
+ flyer: HardwareTriggeredFlyable[SeqTableInfo],
117
+ detectors: List[StandardDetector],
118
+ number_of_frames: int,
119
+ exposure: float,
120
+ shutter_time: float,
121
+ repeats: int = 1,
122
+ period: float = 0.0,
123
+ ):
124
+ """Run a scan wth a flyer and multiple detectors.
125
+
126
+ The stub demonstrates the standard basic flow for a flyscan:
127
+
128
+ - Prepare the flyer and detectors with a trigger
129
+ - Fly and collect:
130
+ - Declare the stream and kickoff the scan
131
+ - Collect while completing
132
+
133
+ This needs to be used in a plan that instantates detectors and a flyer,
134
+ stages/unstages the devices, and opens and closes the run.
135
+
136
+ """
137
+
138
+ # Set up scan and prepare trigger
139
+ yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
140
+ flyer,
141
+ detectors,
142
+ number_of_frames=number_of_frames,
143
+ exposure=exposure,
144
+ shutter_time=shutter_time,
145
+ repeats=repeats,
146
+ period=period,
147
+ )
148
+ # Run the fly scan
149
+ yield from fly_and_collect(stream_name, flyer, detectors)
ophyd_async/protocols.py CHANGED
@@ -1,9 +1,20 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import abstractmethod
2
- from typing import Dict, Protocol, runtime_checkable
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Any,
7
+ Dict,
8
+ Generic,
9
+ Protocol,
10
+ TypeVar,
11
+ runtime_checkable,
12
+ )
3
13
 
4
14
  from bluesky.protocols import DataKey, HasName, Reading
5
15
 
6
- from ophyd_async.core.async_status import AsyncStatus
16
+ if TYPE_CHECKING:
17
+ from ophyd_async.core.async_status import AsyncStatus
7
18
 
8
19
 
9
20
  @runtime_checkable
@@ -94,3 +105,22 @@ class AsyncStageable(Protocol):
94
105
  unstaging.
95
106
  """
96
107
  ...
108
+
109
+
110
+ C = TypeVar("C", contravariant=True)
111
+
112
+
113
+ class Watcher(Protocol, Generic[C]):
114
+ @staticmethod
115
+ def __call__(
116
+ *,
117
+ current: C,
118
+ initial: C,
119
+ target: C,
120
+ name: str | None,
121
+ unit: str | None,
122
+ precision: float | None,
123
+ fraction: float | None,
124
+ time_elapsed: float | None,
125
+ time_remaining: float | None,
126
+ ) -> Any: ...
@@ -1,13 +1,18 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import time
3
- from typing import Callable, List, Optional
4
4
 
5
5
  from bluesky.protocols import Movable, Stoppable
6
6
 
7
7
  from ophyd_async.core import StandardReadable
8
- from ophyd_async.core.async_status import AsyncStatus
9
- from ophyd_async.core.signal import soft_signal_r_and_setter, soft_signal_rw
8
+ from ophyd_async.core.async_status import AsyncStatus, WatchableAsyncStatus
9
+ from ophyd_async.core.signal import (
10
+ observe_value,
11
+ soft_signal_r_and_setter,
12
+ soft_signal_rw,
13
+ )
10
14
  from ophyd_async.core.standard_readable import ConfigSignal, HintedSignal
15
+ from ophyd_async.core.utils import WatcherUpdate
11
16
 
12
17
 
13
18
  class SimMotor(StandardReadable, Movable, Stoppable):
@@ -20,99 +25,79 @@ class SimMotor(StandardReadable, Movable, Stoppable):
20
25
  - name: str: name of device
21
26
  - instant: bool: whether to move instantly, or with a delay
22
27
  """
28
+ # Define some signals
23
29
  with self.add_children_as_readables(HintedSignal):
24
30
  self.user_readback, self._user_readback_set = soft_signal_r_and_setter(
25
31
  float, 0
26
32
  )
27
-
28
33
  with self.add_children_as_readables(ConfigSignal):
29
- self.velocity = soft_signal_rw(float, 1.0)
30
- self.egu = soft_signal_rw(float, "mm")
31
-
32
- self._instant = instant
33
- self._move_task: Optional[asyncio.Task] = None
34
-
35
- # Define some signals
34
+ self.velocity = soft_signal_rw(float, 0 if instant else 1.0)
35
+ self.units = soft_signal_rw(str, "mm")
36
36
  self.user_setpoint = soft_signal_rw(float, 0)
37
37
 
38
- super().__init__(name=name)
39
-
40
38
  # Whether set() should complete successfully or not
41
39
  self._set_success = True
40
+ self._move_status: AsyncStatus | None = None
42
41
 
43
- def stop(self, success=False):
44
- """
45
- Stop the motor if it is moving
46
- """
47
- if self._move_task:
48
- self._move_task.cancel()
49
- self._move_task = None
42
+ super().__init__(name=name)
50
43
 
51
- self._set_success = success
44
+ async def _move(self, old_position: float, new_position: float, move_time: float):
45
+ start = time.monotonic()
46
+ distance = abs(new_position - old_position)
47
+ while True:
48
+ time_elapsed = round(time.monotonic() - start, 2)
52
49
 
53
- def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStatus: # noqa: F821
54
- """
55
- Asynchronously move the motor to a new position.
56
- """
57
- watchers: List[Callable] = []
58
- coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
59
- return AsyncStatus(coro, watchers)
50
+ # update position based on time elapsed
51
+ if time_elapsed >= move_time:
52
+ # successfully reached our target position
53
+ self._user_readback_set(new_position)
54
+ break
55
+ else:
56
+ current_position = old_position + distance * time_elapsed / move_time
60
57
 
61
- async def _move(self, new_position: float, watchers: List[Callable] = []):
62
- """
63
- Start the motor moving to a new position.
58
+ self._user_readback_set(current_position)
64
59
 
65
- If the motor is already moving, it will stop first.
66
- If this is an instant motor the move will be instantaneous.
67
- """
68
- self.stop()
69
- start = time.monotonic()
70
-
71
- current_position = await self.user_readback.get_value()
72
- distance = abs(new_position - current_position)
73
- travel_time = 0 if self._instant else distance / await self.velocity.get_value()
60
+ # 10hz update loop
61
+ await asyncio.sleep(0.1)
74
62
 
75
- old_position, units = await asyncio.gather(
63
+ @WatchableAsyncStatus.wrap
64
+ async def set(self, new_position: float):
65
+ """
66
+ Asynchronously move the motor to a new position.
67
+ """
68
+ # Make sure any existing move tasks are stopped
69
+ await self.stop()
70
+ old_position, units, velocity = await asyncio.gather(
76
71
  self.user_setpoint.get_value(),
77
- self.egu.get_value(),
72
+ self.units.get_value(),
73
+ self.velocity.get_value(),
78
74
  )
79
-
80
- async def update_position():
81
- while True:
82
- time_elapsed = round(time.monotonic() - start, 2)
83
-
84
- # update position based on time elapsed
85
- if time_elapsed >= travel_time:
86
- # successfully reached our target position
87
- self._user_readback_set(new_position)
88
- self._set_success = True
89
- break
90
- else:
91
- current_position = (
92
- old_position + distance * time_elapsed / travel_time
93
- )
94
-
95
- self._user_readback_set(current_position)
96
-
97
- # notify watchers of the new position
98
- for watcher in watchers:
99
- watcher(
100
- name=self.name,
101
- current=current_position,
102
- initial=old_position,
103
- target=new_position,
104
- unit=units,
105
- time_elapsed=time.monotonic() - start,
106
- )
107
-
108
- # 10hz update loop
109
- await asyncio.sleep(0.1)
110
-
111
- # set up a task that updates the motor position at 10hz
112
- self._move_task = asyncio.create_task(update_position())
113
-
114
- try:
115
- await self._move_task
116
- finally:
117
- if not self._set_success:
118
- raise RuntimeError("Motor was stopped")
75
+ # If zero velocity, do instant move
76
+ move_time = abs(new_position - old_position) / velocity if velocity else 0
77
+ self._move_status = AsyncStatus(
78
+ self._move(old_position, new_position, move_time)
79
+ )
80
+ # If stop is called then this will raise a CancelledError, ignore it
81
+ with contextlib.suppress(asyncio.CancelledError):
82
+ async for current_position in observe_value(
83
+ self.user_readback, done_status=self._move_status
84
+ ):
85
+ yield WatcherUpdate(
86
+ current=current_position,
87
+ initial=old_position,
88
+ target=new_position,
89
+ name=self.name,
90
+ unit=units,
91
+ )
92
+ if not self._set_success:
93
+ raise RuntimeError("Motor was stopped")
94
+
95
+ async def stop(self, success=True):
96
+ """
97
+ Stop the motor if it is moving
98
+ """
99
+ self._set_success = success
100
+ if self._move_status:
101
+ self._move_status.task.cancel()
102
+ self._move_status = None
103
+ await self.user_setpoint.set(await self.user_readback.get_value())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ophyd-async
3
- Version: 0.3a4
3
+ Version: 0.3a6
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