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.
- ophyd_async/_version.py +16 -3
- ophyd_async/core/__init__.py +2 -1
- ophyd_async/core/_detector.py +1 -1
- ophyd_async/core/_signal.py +16 -9
- ophyd_async/core/_utils.py +5 -4
- ophyd_async/epics/adandor/_andor.py +1 -2
- ophyd_async/epics/adcore/_core_detector.py +1 -2
- ophyd_async/epics/adcore/_core_io.py +1 -1
- ophyd_async/epics/adcore/_core_logic.py +2 -2
- ophyd_async/epics/adcore/_core_writer.py +9 -6
- ophyd_async/epics/adcore/_hdf_writer.py +6 -1
- ophyd_async/epics/adkinetix/_kinetix_io.py +4 -4
- ophyd_async/epics/adpilatus/_pilatus.py +2 -6
- ophyd_async/epics/advimba/_vimba_io.py +1 -1
- ophyd_async/epics/core/_epics_connector.py +14 -1
- ophyd_async/epics/core/_p4p.py +2 -3
- ophyd_async/epics/core/_pvi_connector.py +1 -1
- ophyd_async/epics/motor.py +21 -16
- ophyd_async/epics/{eiger → odin}/_odin_io.py +5 -3
- ophyd_async/epics/pmac/__init__.py +2 -0
- ophyd_async/epics/pmac/_pmac_io.py +2 -2
- ophyd_async/epics/pmac/_pmac_trajectory.py +116 -0
- ophyd_async/epics/pmac/_utils.py +671 -55
- ophyd_async/epics/testing/_example_ioc.py +1 -2
- ophyd_async/fastcs/eiger/_eiger.py +1 -1
- ophyd_async/fastcs/jungfrau/__init__.py +29 -0
- ophyd_async/fastcs/jungfrau/_controller.py +139 -0
- ophyd_async/fastcs/jungfrau/_jungfrau.py +30 -0
- ophyd_async/fastcs/jungfrau/_signals.py +94 -0
- ophyd_async/fastcs/jungfrau/_utils.py +79 -0
- ophyd_async/plan_stubs/_settings.py +1 -1
- ophyd_async/sim/_motor.py +11 -3
- ophyd_async/sim/_point_detector.py +6 -3
- ophyd_async/sim/_stage.py +14 -3
- ophyd_async/tango/core/_tango_transport.py +2 -2
- ophyd_async/testing/_assert.py +6 -6
- ophyd_async/testing/_one_of_everything.py +1 -1
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/METADATA +5 -4
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/RECORD +43 -37
- /ophyd_async/epics/{eiger → odin}/__init__.py +0 -0
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/WHEEL +0 -0
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/licenses/LICENSE +0 -0
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '0.13.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 13,
|
|
31
|
+
__version__ = version = '0.13.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 13, 2)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
ophyd_async/core/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ from ._derived_signal import (
|
|
|
6
6
|
derived_signal_rw,
|
|
7
7
|
derived_signal_w,
|
|
8
8
|
)
|
|
9
|
-
from ._derived_signal_backend import Transform
|
|
9
|
+
from ._derived_signal_backend import Transform, merge_gathered_dicts
|
|
10
10
|
from ._detector import (
|
|
11
11
|
DetectorController,
|
|
12
12
|
DetectorTrigger,
|
|
@@ -212,6 +212,7 @@ __all__ = [
|
|
|
212
212
|
"derived_signal_w",
|
|
213
213
|
"Transform",
|
|
214
214
|
"DerivedSignalFactory",
|
|
215
|
+
"merge_gathered_dicts",
|
|
215
216
|
# Back compat - delete before 1.0
|
|
216
217
|
"ConfigSignal",
|
|
217
218
|
"HintedSignal",
|
ophyd_async/core/_detector.py
CHANGED
|
@@ -84,7 +84,7 @@ class TriggerInfo(ConfinedModel):
|
|
|
84
84
|
A exposures_per_event > 1 can be useful to have exposures from a faster detector
|
|
85
85
|
able to be zipped with a single exposure from a slower detector. E.g. if
|
|
86
86
|
number_of_events=10 and exposures_per_event=5 then the detector will take
|
|
87
|
-
|
|
87
|
+
50 exposures, but publish 10 StreamDatum indices, and describe() will show a
|
|
88
88
|
shape of (5, h, w) for each.
|
|
89
89
|
Default is 1.
|
|
90
90
|
"""
|
ophyd_async/core/_signal.py
CHANGED
|
@@ -39,8 +39,8 @@ from ._utils import (
|
|
|
39
39
|
async def _wait_for(coro: Awaitable[T], timeout: float | None, source: str) -> T:
|
|
40
40
|
try:
|
|
41
41
|
return await asyncio.wait_for(coro, timeout)
|
|
42
|
-
except
|
|
43
|
-
raise
|
|
42
|
+
except TimeoutError as e:
|
|
43
|
+
raise TimeoutError(source) from e
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def _add_timeout(func):
|
|
@@ -121,6 +121,13 @@ class _SignalCache(Generic[SignalDatatypeT]):
|
|
|
121
121
|
self._valid = asyncio.Event()
|
|
122
122
|
self._reading: Reading[SignalDatatypeT] | None = None
|
|
123
123
|
self.backend: SignalBackend[SignalDatatypeT] = backend
|
|
124
|
+
try:
|
|
125
|
+
asyncio.get_running_loop()
|
|
126
|
+
except RuntimeError as e:
|
|
127
|
+
raise RuntimeError(
|
|
128
|
+
"Need a running event loop to subscribe to a signal, "
|
|
129
|
+
"are you trying to run subscribe outside a plan?"
|
|
130
|
+
) from e
|
|
124
131
|
signal.log.debug(f"Making subscription on source {signal.source}")
|
|
125
132
|
backend.set_callback(self._callback)
|
|
126
133
|
|
|
@@ -492,7 +499,7 @@ async def observe_signals_value(
|
|
|
492
499
|
last_item = ()
|
|
493
500
|
while True:
|
|
494
501
|
if overall_deadline and time.monotonic() >= overall_deadline:
|
|
495
|
-
raise
|
|
502
|
+
raise TimeoutError(
|
|
496
503
|
f"observe_value was still observing signals "
|
|
497
504
|
f"{[signal.source for signal in signals]} after "
|
|
498
505
|
f"timeout {done_timeout}s"
|
|
@@ -500,8 +507,8 @@ async def observe_signals_value(
|
|
|
500
507
|
iteration_timeout = _get_iteration_timeout(timeout, overall_deadline)
|
|
501
508
|
try:
|
|
502
509
|
item = await asyncio.wait_for(q.get(), iteration_timeout)
|
|
503
|
-
except
|
|
504
|
-
raise
|
|
510
|
+
except TimeoutError as exc:
|
|
511
|
+
raise TimeoutError(
|
|
505
512
|
f"Timeout Error while waiting {iteration_timeout}s to update "
|
|
506
513
|
f"{[signal.source for signal in signals]}. "
|
|
507
514
|
f"Last observed signal and value were {last_item}"
|
|
@@ -536,8 +543,8 @@ class _ValueChecker(Generic[SignalDatatypeT]):
|
|
|
536
543
|
):
|
|
537
544
|
try:
|
|
538
545
|
await asyncio.wait_for(self._wait_for_value(signal), timeout)
|
|
539
|
-
except
|
|
540
|
-
raise
|
|
546
|
+
except TimeoutError as e:
|
|
547
|
+
raise TimeoutError(
|
|
541
548
|
f"{signal.name} didn't match {self._matcher_name} in {timeout}s, "
|
|
542
549
|
f"last value {self._last_value!r}"
|
|
543
550
|
) from e
|
|
@@ -635,8 +642,8 @@ async def set_and_wait_for_other_value(
|
|
|
635
642
|
await asyncio.wait_for(_wait_for_value(), timeout)
|
|
636
643
|
if wait_for_set_completion:
|
|
637
644
|
await status
|
|
638
|
-
except
|
|
639
|
-
raise
|
|
645
|
+
except TimeoutError as e:
|
|
646
|
+
raise TimeoutError(
|
|
640
647
|
f"{match_signal.name} value didn't match value from"
|
|
641
648
|
f" {matcher.__name__}() in {timeout}s"
|
|
642
649
|
) from e
|
ophyd_async/core/_utils.py
CHANGED
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
import logging
|
|
5
5
|
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from enum import Enum, EnumMeta
|
|
7
|
+
from enum import Enum, EnumMeta, StrEnum
|
|
8
8
|
from typing import (
|
|
9
9
|
Any,
|
|
10
10
|
Generic,
|
|
@@ -59,15 +59,15 @@ class AnyStringUppercaseNameEnumMeta(UppercaseNameEnumMeta):
|
|
|
59
59
|
return super().__call__(value, *args, **kwargs)
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
class StrictEnum(
|
|
62
|
+
class StrictEnum(StrEnum, metaclass=UppercaseNameEnumMeta):
|
|
63
63
|
"""All members should exist in the Backend, and there will be no extras."""
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
class SubsetEnum(
|
|
66
|
+
class SubsetEnum(StrEnum, metaclass=AnyStringUppercaseNameEnumMeta):
|
|
67
67
|
"""All members should exist in the Backend, but there may be extras."""
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
class SupersetEnum(
|
|
70
|
+
class SupersetEnum(StrEnum, metaclass=UppercaseNameEnumMeta):
|
|
71
71
|
"""Some members should exist in the Backend, and there should be no extras."""
|
|
72
72
|
|
|
73
73
|
|
|
@@ -243,6 +243,7 @@ def get_enum_cls(datatype: type | None) -> type[EnumTypes] | None:
|
|
|
243
243
|
"""
|
|
244
244
|
if get_origin(datatype) is Sequence:
|
|
245
245
|
datatype = get_args(datatype)[0]
|
|
246
|
+
datatype = get_origin_class(datatype)
|
|
246
247
|
if datatype and issubclass(datatype, Enum):
|
|
247
248
|
if not issubclass(datatype, EnumTypes):
|
|
248
249
|
raise TypeError(
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider
|
|
4
|
-
from ophyd_async.core._signal import SignalR
|
|
3
|
+
from ophyd_async.core import PathProvider, SignalR
|
|
5
4
|
from ophyd_async.epics import adcore
|
|
6
5
|
|
|
7
6
|
from ._andor_controller import Andor2Controller
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import SignalR, StandardDetector
|
|
4
|
-
from ophyd_async.core._providers import PathProvider
|
|
3
|
+
from ophyd_async.core import PathProvider, SignalR, StandardDetector
|
|
5
4
|
|
|
6
5
|
from ._core_io import ADBaseIO, NDPluginBaseIO, NDPluginCBIO
|
|
7
6
|
from ._core_logic import ADBaseContAcqController, ADBaseControllerT
|
|
@@ -229,7 +229,7 @@ class NDFileHDFIO(NDFilePluginIO):
|
|
|
229
229
|
swmr_mode: A[SignalRW[bool], PvSuffix.rbv("SWMRMode")]
|
|
230
230
|
flush_now: A[SignalRW[bool], PvSuffix("FlushNow")]
|
|
231
231
|
xml_file_name: A[SignalRW[str], PvSuffix.rbv("XMLFileName")]
|
|
232
|
-
num_frames_chunks: A[
|
|
232
|
+
num_frames_chunks: A[SignalRW[int], PvSuffix.rbv("NumFramesChunks")]
|
|
233
233
|
chunk_size_auto: A[SignalRW[bool], PvSuffix.rbv("ChunkSizeAuto")]
|
|
234
234
|
lazy_open: A[SignalRW[bool], PvSuffix.rbv("LazyOpen")]
|
|
235
235
|
|
|
@@ -130,7 +130,7 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
130
130
|
):
|
|
131
131
|
if state in self.good_states:
|
|
132
132
|
return
|
|
133
|
-
except
|
|
133
|
+
except TimeoutError as exc:
|
|
134
134
|
if state is not None:
|
|
135
135
|
raise ValueError(
|
|
136
136
|
f"Final detector state {state.value} not in valid end "
|
|
@@ -138,7 +138,7 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
138
138
|
) from exc
|
|
139
139
|
else:
|
|
140
140
|
# No updates from the detector, something else is wrong
|
|
141
|
-
raise
|
|
141
|
+
raise TimeoutError(
|
|
142
142
|
"Could not monitor detector state: "
|
|
143
143
|
+ self.driver.detector_state.source
|
|
144
144
|
) from exc
|
|
@@ -11,15 +11,18 @@ from event_model import ( # type: ignore
|
|
|
11
11
|
)
|
|
12
12
|
from pydantic import PositiveInt
|
|
13
13
|
|
|
14
|
-
from ophyd_async.core
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
from ophyd_async.core import (
|
|
15
|
+
DEFAULT_TIMEOUT,
|
|
16
|
+
AsyncStatus,
|
|
17
|
+
DatasetDescriber,
|
|
18
|
+
DetectorWriter,
|
|
19
|
+
EnableDisable,
|
|
20
|
+
PathInfo,
|
|
21
|
+
PathProvider,
|
|
22
|
+
error_if_none,
|
|
18
23
|
observe_value,
|
|
19
24
|
set_and_wait_for_value,
|
|
20
25
|
)
|
|
21
|
-
from ophyd_async.core._status import AsyncStatus
|
|
22
|
-
from ophyd_async.core._utils import DEFAULT_TIMEOUT, error_if_none
|
|
23
26
|
from ophyd_async.epics.core import stop_busy_record
|
|
24
27
|
|
|
25
28
|
# from ophyd_async.epics.adcore._core_logic import ADBaseDatasetDescriber
|
|
@@ -75,8 +75,13 @@ class ADHDFWriter(ADWriter[NDFileHDFIO]):
|
|
|
75
75
|
# Used by the base class
|
|
76
76
|
self._exposures_per_event = exposures_per_event
|
|
77
77
|
|
|
78
|
-
# Determine number of frames that will be saved per HDF chunk
|
|
78
|
+
# Determine number of frames that will be saved per HDF chunk.
|
|
79
|
+
# On a fresh IOC startup, this is set to zero until the first capture,
|
|
80
|
+
# so if it is zero, set it to 1.
|
|
79
81
|
frames_per_chunk = await self.fileio.num_frames_chunks.get_value()
|
|
82
|
+
if frames_per_chunk == 0:
|
|
83
|
+
frames_per_chunk = 1
|
|
84
|
+
await self.fileio.num_frames_chunks.set(frames_per_chunk)
|
|
80
85
|
|
|
81
86
|
if not _is_fully_described(detector_shape):
|
|
82
87
|
# Questions:
|
|
@@ -16,10 +16,10 @@ class KinetixTriggerMode(StrictEnum):
|
|
|
16
16
|
class KinetixReadoutMode(StrictEnum):
|
|
17
17
|
"""Readout mode for ADKinetix detector."""
|
|
18
18
|
|
|
19
|
-
SENSITIVITY = 1
|
|
20
|
-
SPEED = 2
|
|
21
|
-
DYNAMIC_RANGE = 3
|
|
22
|
-
SUB_ELECTRON = 4
|
|
19
|
+
SENSITIVITY = "1"
|
|
20
|
+
SPEED = "2"
|
|
21
|
+
DYNAMIC_RANGE = "3"
|
|
22
|
+
SUB_ELECTRON = "4"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class KinetixDriverIO(adcore.ADBaseIO):
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider
|
|
4
|
-
from ophyd_async.
|
|
5
|
-
from ophyd_async.epics.adcore._core_detector import AreaDetector
|
|
6
|
-
from ophyd_async.epics.adcore._core_io import NDPluginBaseIO
|
|
7
|
-
from ophyd_async.epics.adcore._core_writer import ADWriter
|
|
8
|
-
from ophyd_async.epics.adcore._hdf_writer import ADHDFWriter
|
|
3
|
+
from ophyd_async.core import PathProvider, SignalR
|
|
4
|
+
from ophyd_async.epics.adcore import ADHDFWriter, ADWriter, AreaDetector, NDPluginBaseIO
|
|
9
5
|
|
|
10
6
|
from ._pilatus_controller import PilatusController, PilatusReadoutTime
|
|
11
7
|
from ._pilatus_io import PilatusDriverIO
|
|
@@ -10,7 +10,20 @@ from ._signal import EpicsSignalBackend, get_signal_backend_type, split_protocol
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
12
12
|
class PvSuffix:
|
|
13
|
-
"""Define the PV suffix to be appended to the device prefix.
|
|
13
|
+
"""Define the PV suffix to be appended to the device prefix.
|
|
14
|
+
|
|
15
|
+
For a SignalRW:
|
|
16
|
+
- If you use the same "Suffix" for the read and write PV then use PvSuffix("Suffix")
|
|
17
|
+
- If you have "Suffix" for the write PV and "Suffix_RBV" for the read PV then use
|
|
18
|
+
PvSuffix.rbv("Suffix")
|
|
19
|
+
- If you have "WriteSuffix" for the write PV and "ReadSuffix" for the read PV then
|
|
20
|
+
you use PvSuffix(read_suffix="ReadSuffix", write_suffix="WriteSuffix")
|
|
21
|
+
|
|
22
|
+
For a SignalR:
|
|
23
|
+
- If you have "Suffix" for the read PV then use PvSuffix("Suffix")
|
|
24
|
+
- If you have "Suffix_RBV" for the read PV then use PvSuffix("Suffix_RBV"), do not
|
|
25
|
+
use PvSuffix.rbv as that will try to connect to multiple PVs
|
|
26
|
+
"""
|
|
14
27
|
|
|
15
28
|
read_suffix: str
|
|
16
29
|
write_suffix: str | None = None
|
ophyd_async/epics/core/_p4p.py
CHANGED
|
@@ -22,7 +22,6 @@ from ophyd_async.core import (
|
|
|
22
22
|
SignalDatatype,
|
|
23
23
|
SignalDatatypeT,
|
|
24
24
|
SignalMetadata,
|
|
25
|
-
StrictEnum,
|
|
26
25
|
Table,
|
|
27
26
|
get_enum_cls,
|
|
28
27
|
get_unique,
|
|
@@ -82,7 +81,7 @@ def _metadata_from_value(datatype: type[SignalDatatype], value: Any) -> SignalMe
|
|
|
82
81
|
if (limits := _limits_from_value(value)) and specifier[-1] in _number_specifiers:
|
|
83
82
|
metadata["limits"] = limits
|
|
84
83
|
# Get choices from display or value
|
|
85
|
-
if datatype is str or
|
|
84
|
+
if datatype is str or get_enum_cls(datatype) is not None:
|
|
86
85
|
if hasattr(display_data, "choices"):
|
|
87
86
|
metadata["choices"] = display_data.choices
|
|
88
87
|
elif hasattr(value_data, "choices"):
|
|
@@ -327,7 +326,7 @@ def context() -> Context:
|
|
|
327
326
|
async def pvget_with_timeout(pv: str, timeout: float) -> Any:
|
|
328
327
|
try:
|
|
329
328
|
return await asyncio.wait_for(context().get(pv), timeout=timeout)
|
|
330
|
-
except
|
|
329
|
+
except TimeoutError as exc:
|
|
331
330
|
logger.debug(f"signal pva://{pv} timed out", exc_info=True)
|
|
332
331
|
raise NotConnected(f"pva://{pv}") from exc
|
|
333
332
|
|
|
@@ -6,13 +6,13 @@ from ophyd_async.core import (
|
|
|
6
6
|
Device,
|
|
7
7
|
DeviceConnector,
|
|
8
8
|
DeviceFiller,
|
|
9
|
+
LazyMock,
|
|
9
10
|
Signal,
|
|
10
11
|
SignalR,
|
|
11
12
|
SignalRW,
|
|
12
13
|
SignalW,
|
|
13
14
|
SignalX,
|
|
14
15
|
)
|
|
15
|
-
from ophyd_async.core._utils import LazyMock
|
|
16
16
|
|
|
17
17
|
from ._epics_connector import fill_backend_with_prefix
|
|
18
18
|
from ._signal import PvaSignalBackend, pvget_with_timeout
|
ophyd_async/epics/motor.py
CHANGED
|
@@ -106,6 +106,24 @@ class Motor(
|
|
|
106
106
|
# Readback should be named the same as its parent in read()
|
|
107
107
|
self.user_readback.set_name(name)
|
|
108
108
|
|
|
109
|
+
async def check_motor_limit(self, abs_start_pos: float, abs_end_pos: float):
|
|
110
|
+
"""Check the motor limit with the absolute positions."""
|
|
111
|
+
motor_lower_limit, motor_upper_limit, egu = await asyncio.gather(
|
|
112
|
+
self.low_limit_travel.get_value(),
|
|
113
|
+
self.high_limit_travel.get_value(),
|
|
114
|
+
self.motor_egu.get_value(),
|
|
115
|
+
)
|
|
116
|
+
if (
|
|
117
|
+
not motor_upper_limit >= abs_start_pos >= motor_lower_limit
|
|
118
|
+
or not motor_upper_limit >= abs_end_pos >= motor_lower_limit
|
|
119
|
+
):
|
|
120
|
+
raise MotorLimitsException(
|
|
121
|
+
f"Motor trajectory for requested fly/move is from "
|
|
122
|
+
f"{abs_start_pos}{egu} to "
|
|
123
|
+
f"{abs_end_pos}{egu} but motor limits are "
|
|
124
|
+
f"{motor_lower_limit}{egu} <= x <= {motor_upper_limit}{egu} "
|
|
125
|
+
)
|
|
126
|
+
|
|
109
127
|
@AsyncStatus.wrap
|
|
110
128
|
async def prepare(self, value: FlyMotorInfo):
|
|
111
129
|
"""Move to the beginning of a suitable run-up distance ready for a fly scan."""
|
|
@@ -126,22 +144,7 @@ class Motor(
|
|
|
126
144
|
ramp_up_start_pos = value.ramp_up_start_pos(acceleration_time)
|
|
127
145
|
ramp_down_end_pos = value.ramp_down_end_pos(acceleration_time)
|
|
128
146
|
|
|
129
|
-
|
|
130
|
-
self.low_limit_travel.get_value(),
|
|
131
|
-
self.high_limit_travel.get_value(),
|
|
132
|
-
self.motor_egu.get_value(),
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
not motor_upper_limit >= ramp_up_start_pos >= motor_lower_limit
|
|
137
|
-
or not motor_upper_limit >= ramp_down_end_pos >= motor_lower_limit
|
|
138
|
-
):
|
|
139
|
-
raise MotorLimitsException(
|
|
140
|
-
f"Motor trajectory for requested fly is from "
|
|
141
|
-
f"{ramp_up_start_pos}{egu} to "
|
|
142
|
-
f"{ramp_down_end_pos}{egu} but motor limits are "
|
|
143
|
-
f"{motor_lower_limit}{egu} <= x <= {motor_upper_limit}{egu} "
|
|
144
|
-
)
|
|
147
|
+
await self.check_motor_limit(ramp_up_start_pos, ramp_down_end_pos)
|
|
145
148
|
|
|
146
149
|
# move to prepare position at maximum velocity
|
|
147
150
|
await self.velocity.set(abs(max_speed))
|
|
@@ -199,6 +202,8 @@ class Motor(
|
|
|
199
202
|
msg = "Mover has zero velocity"
|
|
200
203
|
raise ValueError(msg) from error
|
|
201
204
|
|
|
205
|
+
await self.check_motor_limit(old_position, new_position)
|
|
206
|
+
|
|
202
207
|
move_status = self.user_setpoint.set(new_position, wait=True, timeout=timeout)
|
|
203
208
|
async for current_position in observe_value(
|
|
204
209
|
self.user_readback, done_status=move_status
|
|
@@ -90,11 +90,11 @@ class OdinWriter(DetectorWriter):
|
|
|
90
90
|
self,
|
|
91
91
|
path_provider: PathProvider,
|
|
92
92
|
odin_driver: Odin,
|
|
93
|
-
|
|
93
|
+
detector_bit_depth: SignalR[int],
|
|
94
94
|
) -> None:
|
|
95
95
|
self._drv = odin_driver
|
|
96
96
|
self._path_provider = path_provider
|
|
97
|
-
self.
|
|
97
|
+
self._detector_bit_depth = Reference(detector_bit_depth)
|
|
98
98
|
self._capture_status: AsyncStatus | None = None
|
|
99
99
|
super().__init__()
|
|
100
100
|
|
|
@@ -103,7 +103,9 @@ class OdinWriter(DetectorWriter):
|
|
|
103
103
|
self._exposures_per_event = exposures_per_event
|
|
104
104
|
|
|
105
105
|
await asyncio.gather(
|
|
106
|
-
self._drv.data_type.set(
|
|
106
|
+
self._drv.data_type.set(
|
|
107
|
+
f"UInt{await self._detector_bit_depth().get_value()}"
|
|
108
|
+
),
|
|
107
109
|
self._drv.num_to_capture.set(0),
|
|
108
110
|
self._drv.file_path.set(str(info.directory_path)),
|
|
109
111
|
self._drv.file_name.set(info.filename),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from ._pmac_io import PmacAxisAssignmentIO, PmacCoordIO, PmacIO, PmacTrajectoryIO
|
|
2
|
+
from ._pmac_trajectory import PmacTrajectoryTriggerLogic
|
|
2
3
|
|
|
3
4
|
__all__ = [
|
|
4
5
|
"PmacAxisAssignmentIO",
|
|
5
6
|
"PmacCoordIO",
|
|
6
7
|
"PmacIO",
|
|
7
8
|
"PmacTrajectoryIO",
|
|
9
|
+
"PmacTrajectoryTriggerLogic",
|
|
8
10
|
]
|
|
@@ -72,7 +72,7 @@ class PmacCoordIO(Device):
|
|
|
72
72
|
self.cs_port = epics_signal_r(str, f"{prefix}Port")
|
|
73
73
|
self.cs_axis_setpoint = DeviceVector(
|
|
74
74
|
{
|
|
75
|
-
i + 1: epics_signal_rw(
|
|
75
|
+
i + 1: epics_signal_rw(float, f"{prefix}M{i + 1}:DirectDemand")
|
|
76
76
|
for i in range(len(CS_LETTERS))
|
|
77
77
|
}
|
|
78
78
|
)
|
|
@@ -93,7 +93,7 @@ class PmacIO(Device):
|
|
|
93
93
|
|
|
94
94
|
self.assignment = DeviceVector(
|
|
95
95
|
{
|
|
96
|
-
i: PmacAxisAssignmentIO(motor_prefix)
|
|
96
|
+
i: PmacAxisAssignmentIO(motor_prefix + ":")
|
|
97
97
|
for i, motor_prefix in enumerate(motor_prefixes)
|
|
98
98
|
}
|
|
99
99
|
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scanspec.core import Path
|
|
5
|
+
from scanspec.specs import Spec
|
|
6
|
+
|
|
7
|
+
from ophyd_async.core import (
|
|
8
|
+
DEFAULT_TIMEOUT,
|
|
9
|
+
FlyerController,
|
|
10
|
+
set_and_wait_for_value,
|
|
11
|
+
wait_for_value,
|
|
12
|
+
)
|
|
13
|
+
from ophyd_async.epics.motor import Motor
|
|
14
|
+
from ophyd_async.epics.pmac import PmacIO
|
|
15
|
+
from ophyd_async.epics.pmac._pmac_io import CS_LETTERS
|
|
16
|
+
from ophyd_async.epics.pmac._utils import (
|
|
17
|
+
_PmacMotorInfo,
|
|
18
|
+
_Trajectory,
|
|
19
|
+
calculate_ramp_position_and_duration,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PmacTrajectoryTriggerLogic(FlyerController):
|
|
24
|
+
def __init__(self, pmac: PmacIO) -> None:
|
|
25
|
+
self.pmac = pmac
|
|
26
|
+
self.scantime: float | None = None
|
|
27
|
+
|
|
28
|
+
async def prepare(self, value: Spec[Motor]):
|
|
29
|
+
slice = Path(value.calculate()).consume()
|
|
30
|
+
motor_info = await _PmacMotorInfo.from_motors(self.pmac, slice.axes())
|
|
31
|
+
ramp_up_pos, ramp_up_time = calculate_ramp_position_and_duration(
|
|
32
|
+
slice, motor_info, True
|
|
33
|
+
)
|
|
34
|
+
ramp_down_pos, ramp_down_time = calculate_ramp_position_and_duration(
|
|
35
|
+
slice, motor_info, False
|
|
36
|
+
)
|
|
37
|
+
trajectory = _Trajectory.from_slice(slice, ramp_up_time, motor_info)
|
|
38
|
+
await asyncio.gather(
|
|
39
|
+
self._build_trajectory(
|
|
40
|
+
trajectory, motor_info, ramp_down_pos, ramp_down_time
|
|
41
|
+
),
|
|
42
|
+
self._move_to_start(motor_info, ramp_up_pos),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def kickoff(self):
|
|
46
|
+
if not self.scantime:
|
|
47
|
+
raise RuntimeError("Cannot kickoff. Must call prepare first.")
|
|
48
|
+
self.status = await self.pmac.trajectory.execute_profile.set(
|
|
49
|
+
True, timeout=self.scantime + 1
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def complete(self):
|
|
53
|
+
if not self.scantime:
|
|
54
|
+
raise RuntimeError("Cannot complete. Must call prepare first.")
|
|
55
|
+
await wait_for_value(
|
|
56
|
+
self.pmac.trajectory.execute_profile,
|
|
57
|
+
False,
|
|
58
|
+
timeout=self.scantime + DEFAULT_TIMEOUT,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async def stop(self):
|
|
62
|
+
await self.pmac.trajectory.abort_profile.set(True)
|
|
63
|
+
|
|
64
|
+
async def _build_trajectory(
|
|
65
|
+
self,
|
|
66
|
+
trajectory: _Trajectory,
|
|
67
|
+
motor_info: _PmacMotorInfo,
|
|
68
|
+
ramp_down_pos: dict[Motor, np.float64],
|
|
69
|
+
ramp_down_time: float,
|
|
70
|
+
):
|
|
71
|
+
trajectory = trajectory.append_ramp_down(ramp_down_pos, ramp_down_time, 0)
|
|
72
|
+
self.scantime = np.sum(trajectory.durations)
|
|
73
|
+
use_axis = {axis + 1: False for axis in range(len(CS_LETTERS))}
|
|
74
|
+
|
|
75
|
+
size = 0
|
|
76
|
+
for motor, number in motor_info.motor_cs_index.items():
|
|
77
|
+
use_axis[number + 1] = True
|
|
78
|
+
await self.pmac.trajectory.positions[number + 1].set(
|
|
79
|
+
trajectory.positions[motor]
|
|
80
|
+
)
|
|
81
|
+
await self.pmac.trajectory.velocities[number + 1].set(
|
|
82
|
+
trajectory.velocities[motor]
|
|
83
|
+
)
|
|
84
|
+
size += len(trajectory.positions[motor])
|
|
85
|
+
|
|
86
|
+
coros = [
|
|
87
|
+
self.pmac.trajectory.profile_cs_name.set(motor_info.cs_port),
|
|
88
|
+
self.pmac.trajectory.time_array.set(trajectory.durations),
|
|
89
|
+
self.pmac.trajectory.user_array.set(trajectory.user_programs),
|
|
90
|
+
self.pmac.trajectory.points_to_build.set(size),
|
|
91
|
+
self.pmac.trajectory.calculate_velocities.set(False),
|
|
92
|
+
] + [
|
|
93
|
+
self.pmac.trajectory.use_axis[number].set(use)
|
|
94
|
+
for number, use in use_axis.items()
|
|
95
|
+
]
|
|
96
|
+
await asyncio.gather(*coros)
|
|
97
|
+
await self.pmac.trajectory.build_profile.set(True)
|
|
98
|
+
|
|
99
|
+
async def _move_to_start(
|
|
100
|
+
self, motor_info: _PmacMotorInfo, ramp_up_position: dict[Motor, np.float64]
|
|
101
|
+
):
|
|
102
|
+
coord = self.pmac.coord[motor_info.cs_number]
|
|
103
|
+
coros = []
|
|
104
|
+
await coord.defer_moves.set(True)
|
|
105
|
+
for motor, position in ramp_up_position.items():
|
|
106
|
+
coros.append(
|
|
107
|
+
set_and_wait_for_value(
|
|
108
|
+
coord.cs_axis_setpoint[motor_info.motor_cs_index[motor] + 1],
|
|
109
|
+
position,
|
|
110
|
+
set_timeout=10,
|
|
111
|
+
wait_for_set_completion=False,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
statuses = await asyncio.gather(*coros)
|
|
115
|
+
await coord.defer_moves.set(False)
|
|
116
|
+
await asyncio.gather(*statuses)
|