ophyd-async 0.2.0__py3-none-any.whl → 0.3.0__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/__init__.py +1 -4
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +52 -19
- ophyd_async/core/_providers.py +38 -5
- ophyd_async/core/async_status.py +86 -40
- ophyd_async/core/detector.py +214 -72
- ophyd_async/core/device.py +91 -50
- ophyd_async/core/device_save_loader.py +96 -23
- ophyd_async/core/flyer.py +32 -246
- ophyd_async/core/mock_signal_backend.py +82 -0
- ophyd_async/core/mock_signal_utils.py +145 -0
- ophyd_async/core/signal.py +225 -58
- ophyd_async/core/signal_backend.py +8 -5
- ophyd_async/core/{sim_signal_backend.py → soft_signal_backend.py} +51 -49
- ophyd_async/core/standard_readable.py +212 -23
- ophyd_async/core/utils.py +123 -30
- ophyd_async/epics/_backend/_aioca.py +42 -44
- ophyd_async/epics/_backend/_p4p.py +96 -52
- ophyd_async/epics/_backend/common.py +25 -0
- ophyd_async/epics/areadetector/__init__.py +8 -4
- ophyd_async/epics/areadetector/aravis.py +63 -0
- ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
- ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +1 -1
- ophyd_async/epics/areadetector/controllers/aravis_controller.py +78 -0
- ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
- ophyd_async/epics/areadetector/controllers/pilatus_controller.py +37 -25
- ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
- ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
- ophyd_async/epics/areadetector/drivers/ad_base.py +8 -12
- ophyd_async/epics/areadetector/drivers/aravis_driver.py +38 -0
- ophyd_async/epics/areadetector/drivers/kinetix_driver.py +27 -0
- ophyd_async/epics/areadetector/drivers/pilatus_driver.py +8 -5
- ophyd_async/epics/areadetector/drivers/vimba_driver.py +63 -0
- ophyd_async/epics/areadetector/kinetix.py +46 -0
- ophyd_async/epics/areadetector/pilatus.py +45 -0
- ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
- ophyd_async/epics/areadetector/utils.py +2 -12
- ophyd_async/epics/areadetector/vimba.py +43 -0
- ophyd_async/epics/areadetector/writers/_hdffile.py +21 -7
- ophyd_async/epics/areadetector/writers/hdf_writer.py +32 -17
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +19 -18
- ophyd_async/epics/areadetector/writers/nd_plugin.py +15 -7
- ophyd_async/epics/demo/__init__.py +75 -49
- ophyd_async/epics/motion/motor.py +67 -53
- ophyd_async/epics/pvi/__init__.py +3 -0
- ophyd_async/epics/pvi/pvi.py +318 -0
- ophyd_async/epics/signal/__init__.py +8 -3
- ophyd_async/epics/signal/signal.py +26 -9
- ophyd_async/log.py +130 -0
- ophyd_async/panda/__init__.py +21 -5
- ophyd_async/panda/_common_blocks.py +49 -0
- ophyd_async/panda/_hdf_panda.py +48 -0
- ophyd_async/panda/_panda_controller.py +37 -0
- ophyd_async/panda/_trigger.py +39 -0
- ophyd_async/panda/_utils.py +15 -0
- ophyd_async/panda/writers/__init__.py +3 -0
- ophyd_async/panda/writers/_hdf_writer.py +220 -0
- ophyd_async/panda/writers/_panda_hdf_file.py +58 -0
- ophyd_async/plan_stubs/__init__.py +13 -0
- ophyd_async/plan_stubs/ensure_connected.py +22 -0
- ophyd_async/plan_stubs/fly.py +149 -0
- ophyd_async/protocols.py +126 -0
- ophyd_async/sim/__init__.py +11 -0
- ophyd_async/sim/demo/__init__.py +3 -0
- ophyd_async/sim/demo/sim_motor.py +103 -0
- ophyd_async/sim/pattern_generator.py +318 -0
- ophyd_async/sim/sim_pattern_detector_control.py +55 -0
- ophyd_async/sim/sim_pattern_detector_writer.py +34 -0
- ophyd_async/sim/sim_pattern_generator.py +37 -0
- {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/METADATA +31 -70
- ophyd_async-0.3.0.dist-info/RECORD +86 -0
- {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/WHEEL +1 -1
- ophyd_async/epics/signal/pvi_get.py +0 -22
- ophyd_async/panda/panda.py +0 -294
- ophyd_async-0.2.0.dist-info/RECORD +0 -53
- /ophyd_async/panda/{table.py → _table.py} +0 -0
- {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import AsyncGenerator, AsyncIterator, Dict, List, Optional
|
|
3
4
|
|
|
4
|
-
from bluesky.protocols import
|
|
5
|
+
from bluesky.protocols import DataKey, Hints, StreamAsset
|
|
5
6
|
|
|
6
7
|
from ophyd_async.core import (
|
|
7
8
|
DEFAULT_TIMEOUT,
|
|
@@ -13,6 +14,7 @@ from ophyd_async.core import (
|
|
|
13
14
|
set_and_wait_for_value,
|
|
14
15
|
wait_for_value,
|
|
15
16
|
)
|
|
17
|
+
from ophyd_async.core.signal import observe_value
|
|
16
18
|
|
|
17
19
|
from ._hdfdataset import _HDFDataset
|
|
18
20
|
from ._hdffile import _HDFFile
|
|
@@ -38,22 +40,27 @@ class HDFWriter(DetectorWriter):
|
|
|
38
40
|
self._file: Optional[_HDFFile] = None
|
|
39
41
|
self._multiplier = 1
|
|
40
42
|
|
|
41
|
-
async def open(self, multiplier: int = 1) -> Dict[str,
|
|
43
|
+
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
|
|
42
44
|
self._file = None
|
|
43
45
|
info = self._directory_provider()
|
|
46
|
+
file_path = str(info.root / info.resource_dir)
|
|
44
47
|
await asyncio.gather(
|
|
45
48
|
self.hdf.num_extra_dims.set(0),
|
|
46
49
|
self.hdf.lazy_open.set(True),
|
|
47
50
|
self.hdf.swmr_mode.set(True),
|
|
48
|
-
|
|
49
|
-
self.hdf.
|
|
51
|
+
# See https://github.com/bluesky/ophyd-async/issues/122
|
|
52
|
+
self.hdf.file_path.set(file_path),
|
|
53
|
+
self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"),
|
|
50
54
|
self.hdf.file_template.set("%s/%s.h5"),
|
|
51
55
|
self.hdf.file_write_mode.set(FileWriteMode.stream),
|
|
56
|
+
# Never use custom xml layout file but use the one defined
|
|
57
|
+
# in the source code file NDFileHDF5LayoutXML.cpp
|
|
58
|
+
self.hdf.xml_file_name.set(""),
|
|
52
59
|
)
|
|
53
60
|
|
|
54
61
|
assert (
|
|
55
62
|
await self.hdf.file_path_exists.get_value()
|
|
56
|
-
), f"File path {
|
|
63
|
+
), f"File path {file_path} for hdf plugin does not exist"
|
|
57
64
|
|
|
58
65
|
# Overwrite num_capture to go forever
|
|
59
66
|
await self.hdf.num_capture.set(0)
|
|
@@ -78,7 +85,7 @@ class HDFWriter(DetectorWriter):
|
|
|
78
85
|
)
|
|
79
86
|
)
|
|
80
87
|
describe = {
|
|
81
|
-
ds.name:
|
|
88
|
+
ds.name: DataKey(
|
|
82
89
|
source=self.hdf.full_file_name.source,
|
|
83
90
|
shape=outer_shape + tuple(ds.shape),
|
|
84
91
|
dtype="array" if ds.shape else "number",
|
|
@@ -88,27 +95,35 @@ class HDFWriter(DetectorWriter):
|
|
|
88
95
|
}
|
|
89
96
|
return describe
|
|
90
97
|
|
|
91
|
-
async def
|
|
92
|
-
self,
|
|
93
|
-
):
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
matcher.__name__ = f"index_at_least_{index}"
|
|
98
|
-
await wait_for_value(self.hdf.num_captured, matcher, timeout=timeout)
|
|
98
|
+
async def observe_indices_written(
|
|
99
|
+
self, timeout=DEFAULT_TIMEOUT
|
|
100
|
+
) -> AsyncGenerator[int, None]:
|
|
101
|
+
"""Wait until a specific index is ready to be collected"""
|
|
102
|
+
async for num_captured in observe_value(self.hdf.num_captured, timeout):
|
|
103
|
+
yield num_captured // self._multiplier
|
|
99
104
|
|
|
100
105
|
async def get_indices_written(self) -> int:
|
|
101
106
|
num_captured = await self.hdf.num_captured.get_value()
|
|
102
107
|
return num_captured // self._multiplier
|
|
103
108
|
|
|
104
|
-
async def collect_stream_docs(
|
|
109
|
+
async def collect_stream_docs(
|
|
110
|
+
self, indices_written: int
|
|
111
|
+
) -> AsyncIterator[StreamAsset]:
|
|
105
112
|
# TODO: fail if we get dropped frames
|
|
106
113
|
await self.hdf.flush_now.set(True)
|
|
107
114
|
if indices_written:
|
|
108
115
|
if not self._file:
|
|
116
|
+
path = Path(await self.hdf.full_file_name.get_value())
|
|
109
117
|
self._file = _HDFFile(
|
|
110
|
-
|
|
118
|
+
self._directory_provider(),
|
|
119
|
+
# See https://github.com/bluesky/ophyd-async/issues/122
|
|
120
|
+
path,
|
|
121
|
+
self._datasets,
|
|
111
122
|
)
|
|
123
|
+
# stream resource says "here is a dataset",
|
|
124
|
+
# stream datum says "here are N frames in that stream resource",
|
|
125
|
+
# you get one stream resource and many stream datums per scan
|
|
126
|
+
|
|
112
127
|
for doc in self._file.stream_resources():
|
|
113
128
|
yield "stream_resource", doc
|
|
114
129
|
for doc in self._file.stream_data(indices_written):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
|
-
from ...signal.signal import epics_signal_rw
|
|
4
|
-
from ..utils import FileWriteMode
|
|
3
|
+
from ...signal.signal import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv
|
|
4
|
+
from ..utils import FileWriteMode
|
|
5
5
|
from .nd_plugin import NDPluginBase
|
|
6
6
|
|
|
7
7
|
|
|
@@ -19,21 +19,22 @@ class Compression(str, Enum):
|
|
|
19
19
|
class NDFileHDF(NDPluginBase):
|
|
20
20
|
def __init__(self, prefix: str, name="") -> None:
|
|
21
21
|
# Define some signals
|
|
22
|
-
self.position_mode =
|
|
23
|
-
self.compression =
|
|
24
|
-
self.num_extra_dims =
|
|
25
|
-
self.file_path =
|
|
26
|
-
self.file_name =
|
|
27
|
-
self.file_path_exists =
|
|
28
|
-
self.file_template =
|
|
29
|
-
self.full_file_name =
|
|
30
|
-
self.file_write_mode =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
22
|
+
self.position_mode = epics_signal_rw_rbv(bool, prefix + "PositionMode")
|
|
23
|
+
self.compression = epics_signal_rw_rbv(Compression, prefix + "Compression")
|
|
24
|
+
self.num_extra_dims = epics_signal_rw_rbv(int, prefix + "NumExtraDims")
|
|
25
|
+
self.file_path = epics_signal_rw_rbv(str, prefix + "FilePath")
|
|
26
|
+
self.file_name = epics_signal_rw_rbv(str, prefix + "FileName")
|
|
27
|
+
self.file_path_exists = epics_signal_r(bool, prefix + "FilePathExists_RBV")
|
|
28
|
+
self.file_template = epics_signal_rw_rbv(str, prefix + "FileTemplate")
|
|
29
|
+
self.full_file_name = epics_signal_r(str, prefix + "FullFileName_RBV")
|
|
30
|
+
self.file_write_mode = epics_signal_rw_rbv(
|
|
31
|
+
FileWriteMode, prefix + "FileWriteMode"
|
|
32
|
+
)
|
|
33
|
+
self.num_capture = epics_signal_rw_rbv(int, prefix + "NumCapture")
|
|
34
|
+
self.num_captured = epics_signal_r(int, prefix + "NumCaptured_RBV")
|
|
35
|
+
self.swmr_mode = epics_signal_rw_rbv(bool, prefix + "SWMRMode")
|
|
36
|
+
self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
|
|
37
|
+
self.capture = epics_signal_rw_rbv(bool, prefix + "Capture")
|
|
36
38
|
self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
|
|
37
|
-
self.
|
|
38
|
-
self.array_size1 = ad_r(int, prefix + "ArraySize1")
|
|
39
|
+
self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
|
|
39
40
|
super().__init__(prefix, name)
|
|
@@ -2,8 +2,7 @@ from enum import Enum
|
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import Device
|
|
4
4
|
from ophyd_async.epics.signal import epics_signal_rw
|
|
5
|
-
|
|
6
|
-
from ..utils import ad_r, ad_rw
|
|
5
|
+
from ophyd_async.epics.signal.signal import epics_signal_r, epics_signal_rw_rbv
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class Callback(str, Enum):
|
|
@@ -13,16 +12,25 @@ class Callback(str, Enum):
|
|
|
13
12
|
|
|
14
13
|
class NDArrayBase(Device):
|
|
15
14
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
16
|
-
self.unique_id =
|
|
15
|
+
self.unique_id = epics_signal_r(int, prefix + "UniqueId_RBV")
|
|
17
16
|
self.nd_attributes_file = epics_signal_rw(str, prefix + "NDAttributesFile")
|
|
18
|
-
|
|
17
|
+
self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
|
|
18
|
+
self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
|
|
19
|
+
self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
|
|
20
|
+
self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
|
|
21
|
+
# There is no _RBV for this one
|
|
22
|
+
self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
|
|
23
|
+
|
|
24
|
+
super().__init__(name=name)
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
class NDPluginBase(NDArrayBase):
|
|
22
28
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
23
|
-
self.nd_array_port =
|
|
24
|
-
self.enable_callback =
|
|
25
|
-
self.nd_array_address =
|
|
29
|
+
self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
|
|
30
|
+
self.enable_callback = epics_signal_rw_rbv(Callback, prefix + "EnableCallbacks")
|
|
31
|
+
self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
|
|
32
|
+
self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
|
|
33
|
+
self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
|
|
26
34
|
super().__init__(prefix, name)
|
|
27
35
|
|
|
28
36
|
|
|
@@ -6,15 +6,28 @@ import random
|
|
|
6
6
|
import string
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
|
-
import time
|
|
10
9
|
from enum import Enum
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
from typing import Callable, List, Optional
|
|
13
11
|
|
|
14
12
|
import numpy as np
|
|
15
13
|
from bluesky.protocols import Movable, Stoppable
|
|
16
14
|
|
|
17
|
-
from ophyd_async.core import
|
|
15
|
+
from ophyd_async.core import (
|
|
16
|
+
ConfigSignal,
|
|
17
|
+
Device,
|
|
18
|
+
DeviceVector,
|
|
19
|
+
HintedSignal,
|
|
20
|
+
StandardReadable,
|
|
21
|
+
WatchableAsyncStatus,
|
|
22
|
+
observe_value,
|
|
23
|
+
)
|
|
24
|
+
from ophyd_async.core.async_status import AsyncStatus
|
|
25
|
+
from ophyd_async.core.utils import (
|
|
26
|
+
DEFAULT_TIMEOUT,
|
|
27
|
+
CalculatableTimeout,
|
|
28
|
+
CalculateTimeout,
|
|
29
|
+
WatcherUpdate,
|
|
30
|
+
)
|
|
18
31
|
|
|
19
32
|
from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
20
33
|
|
|
@@ -33,35 +46,41 @@ class Sensor(StandardReadable):
|
|
|
33
46
|
|
|
34
47
|
def __init__(self, prefix: str, name="") -> None:
|
|
35
48
|
# Define some signals
|
|
36
|
-
self.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
config=[self.mode],
|
|
42
|
-
)
|
|
49
|
+
with self.add_children_as_readables(HintedSignal):
|
|
50
|
+
self.value = epics_signal_r(float, prefix + "Value")
|
|
51
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
52
|
+
self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
|
|
53
|
+
|
|
43
54
|
super().__init__(name=name)
|
|
44
55
|
|
|
45
56
|
|
|
57
|
+
class SensorGroup(StandardReadable):
|
|
58
|
+
def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
|
|
59
|
+
with self.add_children_as_readables():
|
|
60
|
+
self.sensors = DeviceVector(
|
|
61
|
+
{i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
super().__init__(name)
|
|
65
|
+
|
|
66
|
+
|
|
46
67
|
class Mover(StandardReadable, Movable, Stoppable):
|
|
47
68
|
"""A demo movable that moves based on velocity"""
|
|
48
69
|
|
|
49
70
|
def __init__(self, prefix: str, name="") -> None:
|
|
50
71
|
# Define some signals
|
|
72
|
+
with self.add_children_as_readables(HintedSignal):
|
|
73
|
+
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
74
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
75
|
+
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
76
|
+
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
51
77
|
self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
|
|
52
|
-
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
53
|
-
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
54
|
-
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
55
78
|
self.precision = epics_signal_r(int, prefix + "Readback.PREC")
|
|
56
79
|
# Signals that collide with standard methods should have a trailing underscore
|
|
57
80
|
self.stop_ = epics_signal_x(prefix + "Stop.PROC")
|
|
58
81
|
# Whether set() should complete successfully or not
|
|
59
82
|
self._set_success = True
|
|
60
|
-
|
|
61
|
-
self.set_readable_signals(
|
|
62
|
-
read=[self.readback],
|
|
63
|
-
config=[self.velocity, self.units],
|
|
64
|
-
)
|
|
83
|
+
|
|
65
84
|
super().__init__(name=name)
|
|
66
85
|
|
|
67
86
|
def set_name(self, name: str):
|
|
@@ -69,47 +88,43 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
69
88
|
# Readback should be named the same as its parent in read()
|
|
70
89
|
self.readback.set_name(name)
|
|
71
90
|
|
|
72
|
-
|
|
91
|
+
@WatchableAsyncStatus.wrap
|
|
92
|
+
async def set(
|
|
93
|
+
self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
|
|
94
|
+
):
|
|
73
95
|
self._set_success = True
|
|
74
|
-
|
|
75
|
-
start = time.monotonic()
|
|
76
|
-
old_position, units, precision = await asyncio.gather(
|
|
96
|
+
old_position, units, precision, velocity = await asyncio.gather(
|
|
77
97
|
self.setpoint.get_value(),
|
|
78
98
|
self.units.get_value(),
|
|
79
99
|
self.precision.get_value(),
|
|
100
|
+
self.velocity.get_value(),
|
|
80
101
|
)
|
|
102
|
+
if timeout is CalculateTimeout:
|
|
103
|
+
assert velocity > 0, "Mover has zero velocity"
|
|
104
|
+
timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
|
|
105
|
+
# Make an Event that will be set on completion, and a Status that will
|
|
106
|
+
# error if not done in time
|
|
107
|
+
done = asyncio.Event()
|
|
108
|
+
done_status = AsyncStatus(asyncio.wait_for(done.wait(), timeout))
|
|
81
109
|
# Wait for the value to set, but don't wait for put completion callback
|
|
82
110
|
await self.setpoint.set(new_position, wait=False)
|
|
83
|
-
async for current_position in observe_value(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
async for current_position in observe_value(
|
|
112
|
+
self.readback, done_status=done_status
|
|
113
|
+
):
|
|
114
|
+
yield WatcherUpdate(
|
|
115
|
+
current=current_position,
|
|
116
|
+
initial=old_position,
|
|
117
|
+
target=new_position,
|
|
118
|
+
name=self.name,
|
|
119
|
+
unit=units,
|
|
120
|
+
precision=precision,
|
|
121
|
+
)
|
|
94
122
|
if np.isclose(current_position, new_position):
|
|
123
|
+
done.set()
|
|
95
124
|
break
|
|
96
125
|
if not self._set_success:
|
|
97
126
|
raise RuntimeError("Motor was stopped")
|
|
98
127
|
|
|
99
|
-
def move(self, new_position: float, timeout: Optional[float] = None):
|
|
100
|
-
"""Commandline only synchronous move of a Motor"""
|
|
101
|
-
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
102
|
-
|
|
103
|
-
if in_bluesky_event_loop():
|
|
104
|
-
raise RuntimeError("Will deadlock run engine if run in a plan")
|
|
105
|
-
call_in_bluesky_event_loop(self._move(new_position), timeout) # type: ignore
|
|
106
|
-
|
|
107
|
-
# TODO: this fails if we call from the cli, but works if we "ipython await" it
|
|
108
|
-
def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStatus:
|
|
109
|
-
watchers: List[Callable] = []
|
|
110
|
-
coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
|
|
111
|
-
return AsyncStatus(coro, watchers)
|
|
112
|
-
|
|
113
128
|
async def stop(self, success=True):
|
|
114
129
|
self._set_success = success
|
|
115
130
|
status = self.stop_.trigger()
|
|
@@ -135,11 +150,22 @@ def start_ioc_subprocess() -> str:
|
|
|
135
150
|
pv_prefix = "".join(random.choice(string.ascii_uppercase) for _ in range(12)) + ":"
|
|
136
151
|
here = Path(__file__).absolute().parent
|
|
137
152
|
args = [sys.executable, "-m", "epicscorelibs.ioc"]
|
|
153
|
+
|
|
154
|
+
# Create standalone sensor
|
|
138
155
|
args += ["-m", f"P={pv_prefix}"]
|
|
139
156
|
args += ["-d", str(here / "sensor.db")]
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
|
|
158
|
+
# Create sensor group
|
|
159
|
+
for suffix in ["1", "2", "3"]:
|
|
160
|
+
args += ["-m", f"P={pv_prefix}{suffix}:"]
|
|
161
|
+
args += ["-d", str(here / "sensor.db")]
|
|
162
|
+
|
|
163
|
+
# Create X and Y motors
|
|
164
|
+
for suffix in ["X", "Y"]:
|
|
165
|
+
args += ["-m", f"P={pv_prefix}{suffix}:"]
|
|
142
166
|
args += ["-d", str(here / "mover.db")]
|
|
167
|
+
|
|
168
|
+
# Start IOC
|
|
143
169
|
process = subprocess.Popen(
|
|
144
170
|
args,
|
|
145
171
|
stdin=subprocess.PIPE,
|
|
@@ -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
|
|
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
|
|
|
@@ -14,72 +24,76 @@ class Motor(StandardReadable, Movable, Stoppable):
|
|
|
14
24
|
|
|
15
25
|
def __init__(self, prefix: str, name="") -> None:
|
|
16
26
|
# Define some signals
|
|
17
|
-
self.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
28
|
+
self.motor_egu = epics_signal_r(str, prefix + ".EGU")
|
|
29
|
+
self.velocity = epics_signal_rw(float, prefix + ".VELO")
|
|
30
|
+
|
|
31
|
+
with self.add_children_as_readables(HintedSignal):
|
|
32
|
+
self.user_readback = epics_signal_r(float, prefix + ".RBV")
|
|
33
|
+
|
|
34
|
+
self.user_setpoint = epics_signal_rw(float, prefix + ".VAL")
|
|
35
|
+
self.max_velocity = epics_signal_r(float, prefix + ".VMAX")
|
|
36
|
+
self.acceleration_time = epics_signal_rw(float, prefix + ".ACCL")
|
|
21
37
|
self.precision = epics_signal_r(int, prefix + ".PREC")
|
|
22
|
-
|
|
23
|
-
self.
|
|
38
|
+
self.deadband = epics_signal_r(float, prefix + ".RDBD")
|
|
39
|
+
self.motor_done_move = epics_signal_r(int, prefix + ".DMOV")
|
|
40
|
+
self.low_limit_travel = epics_signal_rw(float, prefix + ".LLM")
|
|
41
|
+
self.high_limit_travel = epics_signal_rw(float, prefix + ".HLM")
|
|
42
|
+
|
|
43
|
+
self.motor_stop = epics_signal_x(prefix + ".STOP")
|
|
24
44
|
# Whether set() should complete successfully or not
|
|
25
45
|
self._set_success = True
|
|
26
|
-
# Set name and signals for read() and read_configuration()
|
|
27
|
-
self.set_readable_signals(
|
|
28
|
-
read=[self.readback],
|
|
29
|
-
config=[self.velocity, self.units],
|
|
30
|
-
)
|
|
31
46
|
super().__init__(name=name)
|
|
32
47
|
|
|
33
48
|
def set_name(self, name: str):
|
|
34
49
|
super().set_name(name)
|
|
35
50
|
# Readback should be named the same as its parent in read()
|
|
36
|
-
self.
|
|
51
|
+
self.user_readback.set_name(name)
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
@WatchableAsyncStatus.wrap
|
|
54
|
+
async def set(
|
|
55
|
+
self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
|
|
56
|
+
):
|
|
39
57
|
self._set_success = True
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
(
|
|
59
|
+
old_position,
|
|
60
|
+
units,
|
|
61
|
+
precision,
|
|
62
|
+
velocity,
|
|
63
|
+
acceleration_time,
|
|
64
|
+
) = await asyncio.gather(
|
|
65
|
+
self.user_setpoint.get_value(),
|
|
66
|
+
self.motor_egu.get_value(),
|
|
44
67
|
self.precision.get_value(),
|
|
68
|
+
self.velocity.get_value(),
|
|
69
|
+
self.acceleration_time.get_value(),
|
|
45
70
|
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
)
|
|
64
90
|
if not self._set_success:
|
|
65
91
|
raise RuntimeError("Motor was stopped")
|
|
66
92
|
|
|
67
|
-
def move(self, new_position: float, timeout: Optional[float] = None):
|
|
68
|
-
"""Commandline only synchronous move of a Motor"""
|
|
69
|
-
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
70
|
-
|
|
71
|
-
if in_bluesky_event_loop():
|
|
72
|
-
raise RuntimeError("Will deadlock run engine if run in a plan")
|
|
73
|
-
call_in_bluesky_event_loop(self._move(new_position), timeout) # type: ignore
|
|
74
|
-
|
|
75
|
-
def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStatus:
|
|
76
|
-
watchers: List[Callable] = []
|
|
77
|
-
coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
|
|
78
|
-
return AsyncStatus(coro, watchers)
|
|
79
|
-
|
|
80
93
|
async def stop(self, success=False):
|
|
81
94
|
self._set_success = success
|
|
82
95
|
# Put with completion will never complete as we are waiting for completion on
|
|
83
96
|
# the move above, so need to pass wait=False
|
|
84
|
-
|
|
85
|
-
|
|
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())
|