ophyd-async 0.8.0a6__py3-none-any.whl → 0.9.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/_version.py +2 -2
- ophyd_async/core/__init__.py +15 -46
- ophyd_async/core/_detector.py +68 -44
- ophyd_async/core/_device.py +120 -79
- ophyd_async/core/_device_filler.py +17 -8
- ophyd_async/core/_flyer.py +2 -2
- ophyd_async/core/_protocol.py +0 -28
- ophyd_async/core/_readable.py +30 -23
- ophyd_async/core/_settings.py +104 -0
- ophyd_async/core/_signal.py +91 -151
- ophyd_async/core/_signal_backend.py +4 -1
- ophyd_async/core/_soft_signal_backend.py +2 -1
- ophyd_async/core/_table.py +18 -10
- ophyd_async/core/_utils.py +30 -5
- ophyd_async/core/_yaml_settings.py +64 -0
- ophyd_async/epics/adandor/__init__.py +9 -0
- ophyd_async/epics/adandor/_andor.py +45 -0
- ophyd_async/epics/adandor/_andor_controller.py +49 -0
- ophyd_async/epics/adandor/_andor_io.py +36 -0
- ophyd_async/epics/adaravis/__init__.py +3 -1
- ophyd_async/epics/adaravis/_aravis.py +23 -37
- ophyd_async/epics/adaravis/_aravis_controller.py +21 -30
- ophyd_async/epics/adaravis/_aravis_io.py +4 -4
- ophyd_async/epics/adcore/__init__.py +15 -8
- ophyd_async/epics/adcore/_core_detector.py +41 -0
- ophyd_async/epics/adcore/_core_io.py +56 -31
- ophyd_async/epics/adcore/_core_logic.py +99 -86
- ophyd_async/epics/adcore/_core_writer.py +219 -0
- ophyd_async/epics/adcore/_hdf_writer.py +33 -59
- ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
- ophyd_async/epics/adcore/_single_trigger.py +5 -4
- ophyd_async/epics/adcore/_tiff_writer.py +26 -0
- ophyd_async/epics/adcore/_utils.py +37 -36
- ophyd_async/epics/adkinetix/_kinetix.py +29 -24
- ophyd_async/epics/adkinetix/_kinetix_controller.py +15 -27
- ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
- ophyd_async/epics/adpilatus/__init__.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus.py +28 -40
- ophyd_async/epics/adpilatus/_pilatus_controller.py +47 -25
- ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
- ophyd_async/epics/adsimdetector/__init__.py +3 -3
- ophyd_async/epics/adsimdetector/_sim.py +33 -17
- ophyd_async/epics/advimba/_vimba.py +23 -23
- ophyd_async/epics/advimba/_vimba_controller.py +21 -35
- ophyd_async/epics/advimba/_vimba_io.py +23 -23
- ophyd_async/epics/core/_aioca.py +52 -21
- ophyd_async/epics/core/_p4p.py +59 -16
- ophyd_async/epics/core/_pvi_connector.py +4 -2
- ophyd_async/epics/core/_signal.py +9 -2
- ophyd_async/epics/core/_util.py +10 -1
- ophyd_async/epics/eiger/_eiger_controller.py +4 -4
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +26 -15
- ophyd_async/epics/sim/_ioc.py +29 -0
- ophyd_async/epics/{demo → sim}/_mover.py +12 -6
- ophyd_async/epics/{demo → sim}/_sensor.py +2 -2
- ophyd_async/epics/testing/__init__.py +14 -14
- ophyd_async/epics/testing/_example_ioc.py +53 -67
- ophyd_async/epics/testing/_utils.py +17 -45
- ophyd_async/epics/testing/test_records.db +22 -0
- ophyd_async/fastcs/core.py +2 -2
- ophyd_async/fastcs/panda/__init__.py +0 -2
- ophyd_async/fastcs/panda/_block.py +9 -9
- ophyd_async/fastcs/panda/_control.py +9 -4
- ophyd_async/fastcs/panda/_hdf_panda.py +7 -2
- ophyd_async/fastcs/panda/_table.py +4 -1
- ophyd_async/fastcs/panda/_trigger.py +7 -7
- ophyd_async/plan_stubs/__init__.py +14 -0
- ophyd_async/plan_stubs/_ensure_connected.py +11 -17
- ophyd_async/plan_stubs/_fly.py +2 -2
- ophyd_async/plan_stubs/_nd_attributes.py +7 -5
- ophyd_async/plan_stubs/_panda.py +13 -0
- ophyd_async/plan_stubs/_settings.py +125 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
- ophyd_async/sim/__init__.py +19 -0
- ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py +9 -2
- ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_generator.py +13 -6
- ophyd_async/sim/{demo/_sim_motor.py → _sim_motor.py} +34 -32
- ophyd_async/tango/core/_signal.py +3 -1
- ophyd_async/tango/core/_tango_transport.py +13 -15
- ophyd_async/tango/{demo → sim}/_mover.py +5 -2
- ophyd_async/testing/__init__.py +52 -0
- ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
- ophyd_async/testing/_assert.py +176 -0
- ophyd_async/{core → testing}/_mock_signal_utils.py +15 -11
- ophyd_async/testing/_one_of_everything.py +126 -0
- ophyd_async/testing/_wait_for_pending.py +22 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/METADATA +4 -2
- ophyd_async-0.9.0.dist-info/RECORD +129 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/WHEEL +1 -1
- ophyd_async/core/_device_save_loader.py +0 -274
- ophyd_async/epics/adsimdetector/_sim_controller.py +0 -51
- ophyd_async/fastcs/panda/_utils.py +0 -16
- ophyd_async/sim/demo/__init__.py +0 -19
- ophyd_async/sim/testing/__init__.py +0 -0
- ophyd_async-0.8.0a6.dist-info/RECORD +0 -116
- ophyd_async-0.8.0a6.dist-info/entry_points.txt +0 -2
- /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
- /ophyd_async/epics/{demo → sim}/mover.db +0 -0
- /ophyd_async/epics/{demo → sim}/sensor.db +0 -0
- /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/__init__.py +0 -0
- /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector.py +0 -0
- /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_writer.py +0 -0
- /ophyd_async/tango/{demo → sim}/__init__.py +0 -0
- /ophyd_async/tango/{demo → sim}/_counter.py +0 -0
- /ophyd_async/tango/{demo → sim}/_detector.py +0 -0
- /ophyd_async/tango/{demo → sim}/_tango/__init__.py +0 -0
- /ophyd_async/tango/{demo → sim}/_tango/_servers.py +0 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -50,11 +50,13 @@ def setup_ndattributes(
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def setup_ndstats_sum(detector: Device):
|
|
53
|
-
hdf = getattr(detector, "
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
hdf = getattr(detector, "fileio", None)
|
|
54
|
+
if not isinstance(hdf, NDFileHDFIO):
|
|
55
|
+
msg = (
|
|
56
|
+
f"Expected {detector.name} to have 'fileio' attribute that is an "
|
|
57
|
+
f"NDFileHDFIO, got {hdf}"
|
|
58
|
+
)
|
|
59
|
+
raise TypeError(msg)
|
|
58
60
|
yield from (
|
|
59
61
|
setup_ndattributes(
|
|
60
62
|
hdf,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from bluesky.utils import MsgGenerator, plan
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import Settings
|
|
4
|
+
from ophyd_async.fastcs import panda
|
|
5
|
+
|
|
6
|
+
from ._settings import apply_settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@plan
|
|
10
|
+
def apply_panda_settings(settings: Settings[panda.HDFPanda]) -> MsgGenerator[None]:
|
|
11
|
+
units, others = settings.partition(lambda signal: signal.name.endswith("_units"))
|
|
12
|
+
yield from apply_settings(units)
|
|
13
|
+
yield from apply_settings(others)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import bluesky.plan_stubs as bps
|
|
8
|
+
import numpy as np
|
|
9
|
+
from bluesky.utils import MsgGenerator, plan
|
|
10
|
+
|
|
11
|
+
from ophyd_async.core import (
|
|
12
|
+
Device,
|
|
13
|
+
Settings,
|
|
14
|
+
SettingsProvider,
|
|
15
|
+
SignalRW,
|
|
16
|
+
T,
|
|
17
|
+
walk_rw_signals,
|
|
18
|
+
)
|
|
19
|
+
from ophyd_async.core._table import Table
|
|
20
|
+
|
|
21
|
+
from ._wait_for_awaitable import wait_for_awaitable
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@plan
|
|
25
|
+
def _get_values_of_signals(
|
|
26
|
+
signals: Mapping[T, SignalRW],
|
|
27
|
+
) -> MsgGenerator[dict[T, Any]]:
|
|
28
|
+
coros = [sig.get_value() for sig in signals.values()]
|
|
29
|
+
values = yield from wait_for_awaitable(asyncio.gather(*coros))
|
|
30
|
+
named_values = dict(zip(signals, values, strict=True))
|
|
31
|
+
return named_values
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@plan
|
|
35
|
+
def get_current_settings(device: Device) -> MsgGenerator[Settings]:
|
|
36
|
+
signals = walk_rw_signals(device)
|
|
37
|
+
named_values = yield from _get_values_of_signals(signals)
|
|
38
|
+
signal_values = {signals[name]: value for name, value in named_values.items()}
|
|
39
|
+
return Settings(device, signal_values)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@plan
|
|
43
|
+
def store_settings(
|
|
44
|
+
provider: SettingsProvider, name: str, device: Device
|
|
45
|
+
) -> MsgGenerator[None]:
|
|
46
|
+
"""Walk a Device for SignalRWs and store their values with a provider associated
|
|
47
|
+
with the given name.
|
|
48
|
+
"""
|
|
49
|
+
signals = walk_rw_signals(device)
|
|
50
|
+
named_values = yield from _get_values_of_signals(signals)
|
|
51
|
+
yield from wait_for_awaitable(provider.store(name, named_values))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@plan
|
|
55
|
+
def retrieve_settings(
|
|
56
|
+
provider: SettingsProvider, name: str, device: Device
|
|
57
|
+
) -> MsgGenerator[Settings]:
|
|
58
|
+
"""Retrieve named Settings for a Device from a provider."""
|
|
59
|
+
named_values = yield from wait_for_awaitable(provider.retrieve(name))
|
|
60
|
+
signals = walk_rw_signals(device)
|
|
61
|
+
unknown_names = set(named_values) - set(signals)
|
|
62
|
+
if unknown_names:
|
|
63
|
+
raise NameError(f"Unknown signal names {sorted(unknown_names)}")
|
|
64
|
+
signal_values = {signals[name]: value for name, value in named_values.items()}
|
|
65
|
+
return Settings(device, signal_values)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@plan
|
|
69
|
+
def apply_settings(settings: Settings) -> MsgGenerator[None]:
|
|
70
|
+
"""Set every SignalRW to the given value in Settings. If value is None ignore it."""
|
|
71
|
+
signal_values = {
|
|
72
|
+
signal: value for signal, value in settings.items() if value is not None
|
|
73
|
+
}
|
|
74
|
+
if signal_values:
|
|
75
|
+
for signal, value in signal_values.items():
|
|
76
|
+
yield from bps.abs_set(signal, value, group="apply_settings")
|
|
77
|
+
yield from bps.wait("apply_settings")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@plan
|
|
81
|
+
def apply_settings_if_different(
|
|
82
|
+
settings: Settings,
|
|
83
|
+
apply_plan: Callable[[Settings], MsgGenerator[None]],
|
|
84
|
+
current_settings: Settings | None = None,
|
|
85
|
+
) -> MsgGenerator[None]:
|
|
86
|
+
"""Set every SignalRW in settings to its given value if it is different to the
|
|
87
|
+
current value.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
apply_plan:
|
|
92
|
+
A device specific plan which takes the Settings to apply and applies them to
|
|
93
|
+
the Device. Used to add device specific ordering to setting the signals.
|
|
94
|
+
current_settings:
|
|
95
|
+
If given, should be a superset of settings containing the current value of
|
|
96
|
+
the Settings in the Device. If not given it will be created by reading just
|
|
97
|
+
the signals given in settings.
|
|
98
|
+
"""
|
|
99
|
+
if current_settings is None:
|
|
100
|
+
# If we aren't give the current settings, then get the
|
|
101
|
+
# values of just the signals we were asked to change.
|
|
102
|
+
# This allows us to use this plan with Settings for a subset
|
|
103
|
+
# of signals in the Device without retrieving them all
|
|
104
|
+
signal_values = yield from _get_values_of_signals(
|
|
105
|
+
{sig: sig for sig in settings}
|
|
106
|
+
)
|
|
107
|
+
current_settings = Settings(settings.device, signal_values)
|
|
108
|
+
|
|
109
|
+
def _is_different(current, required) -> bool:
|
|
110
|
+
if isinstance(current, Table):
|
|
111
|
+
current = current.model_dump()
|
|
112
|
+
if isinstance(required, Table):
|
|
113
|
+
required = required.model_dump()
|
|
114
|
+
return current.keys() != required.keys() or any(
|
|
115
|
+
_is_different(current[k], required[k]) for k in current
|
|
116
|
+
)
|
|
117
|
+
elif isinstance(current, np.ndarray):
|
|
118
|
+
return not np.array_equal(current, required)
|
|
119
|
+
else:
|
|
120
|
+
return current != required
|
|
121
|
+
|
|
122
|
+
settings_to_change, _ = settings.partition(
|
|
123
|
+
lambda sig: _is_different(current_settings[sig], settings[sig])
|
|
124
|
+
)
|
|
125
|
+
yield from apply_plan(settings_to_change)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from collections.abc import Awaitable
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
from bluesky.utils import MsgGenerator, plan
|
|
5
|
+
|
|
6
|
+
from ophyd_async.core import T
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@plan
|
|
10
|
+
def wait_for_awaitable(coro: Awaitable[T]) -> MsgGenerator[T]:
|
|
11
|
+
"""Wait for a single awaitable to complete, and return the result."""
|
|
12
|
+
(task,) = yield from bps.wait_for([lambda: coro])
|
|
13
|
+
return task.result()
|
ophyd_async/sim/__init__.py
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ._pattern_detector import (
|
|
2
|
+
DATA_PATH,
|
|
3
|
+
SUM_PATH,
|
|
4
|
+
PatternDetector,
|
|
5
|
+
PatternDetectorController,
|
|
6
|
+
PatternDetectorWriter,
|
|
7
|
+
PatternGenerator,
|
|
8
|
+
)
|
|
9
|
+
from ._sim_motor import SimMotor
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DATA_PATH",
|
|
13
|
+
"SUM_PATH",
|
|
14
|
+
"PatternGenerator",
|
|
15
|
+
"PatternDetector",
|
|
16
|
+
"PatternDetectorController",
|
|
17
|
+
"PatternDetectorWriter",
|
|
18
|
+
"SimMotor",
|
|
19
|
+
]
|
ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py
RENAMED
|
@@ -27,8 +27,15 @@ class PatternDetectorController(DetectorController):
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
async def arm(self):
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if not hasattr(self, "_trigger_info"):
|
|
31
|
+
msg = "TriggerInfo information is missing, has 'prepare' been called?"
|
|
32
|
+
raise RuntimeError(msg)
|
|
33
|
+
if not self._trigger_info.livetime:
|
|
34
|
+
msg = "Livetime information is missing in trigger info"
|
|
35
|
+
raise ValueError(msg)
|
|
36
|
+
if not self.period:
|
|
37
|
+
msg = "Period is not set"
|
|
38
|
+
raise ValueError(msg)
|
|
32
39
|
self.task = asyncio.create_task(
|
|
33
40
|
self._coroutine_for_image_writing(
|
|
34
41
|
self._trigger_info.livetime,
|
|
@@ -67,11 +67,14 @@ class PatternGenerator:
|
|
|
67
67
|
|
|
68
68
|
def write_data_to_dataset(self, path: str, data_shape: tuple[int, ...], data):
|
|
69
69
|
"""Write data to named dataset, resizing to fit and flushing after."""
|
|
70
|
-
|
|
70
|
+
if not self._handle_for_h5_file:
|
|
71
|
+
msg = "No file has been opened!"
|
|
72
|
+
raise OSError(msg)
|
|
73
|
+
|
|
71
74
|
dset = self._handle_for_h5_file[path]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
if not isinstance(dset, h5py.Dataset):
|
|
76
|
+
msg = f"Expected {path} to be a dataset, got {type(dset).__name__}"
|
|
77
|
+
raise TypeError(msg)
|
|
75
78
|
dset.resize((self.image_counter + 1,) + data_shape)
|
|
76
79
|
dset[self.image_counter] = data
|
|
77
80
|
dset.flush()
|
|
@@ -114,7 +117,9 @@ class PatternGenerator:
|
|
|
114
117
|
|
|
115
118
|
self._handle_for_h5_file = h5py.File(self.target_path, "w", libver="latest")
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
if not self._handle_for_h5_file:
|
|
121
|
+
msg = f"Problem opening file {self.target_path}"
|
|
122
|
+
raise OSError(msg)
|
|
118
123
|
|
|
119
124
|
self._handle_for_h5_file.create_dataset(
|
|
120
125
|
name=DATA_PATH,
|
|
@@ -184,7 +189,9 @@ class PatternGenerator:
|
|
|
184
189
|
# cannot get the full filename the HDF writer will write
|
|
185
190
|
# until the first frame comes in
|
|
186
191
|
if not self._hdf_stream_provider:
|
|
187
|
-
|
|
192
|
+
if self.target_path is None:
|
|
193
|
+
msg = "open file has not been called"
|
|
194
|
+
raise RuntimeError(msg)
|
|
188
195
|
self._hdf_stream_provider = HDFFile(
|
|
189
196
|
self.target_path,
|
|
190
197
|
self._datasets,
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import contextlib
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
|
+
import numpy as np
|
|
5
6
|
from bluesky.protocols import Movable, Stoppable
|
|
6
7
|
|
|
7
8
|
from ophyd_async.core import (
|
|
@@ -44,22 +45,20 @@ class SimMotor(StandardReadable, Movable, Stoppable):
|
|
|
44
45
|
|
|
45
46
|
async def _move(self, old_position: float, new_position: float, move_time: float):
|
|
46
47
|
start = time.monotonic()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# 10hz update loop
|
|
62
|
-
await asyncio.sleep(0.1)
|
|
48
|
+
# Make an array of relative update times at 10Hz intervals
|
|
49
|
+
update_times = np.arange(0.1, move_time, 0.1)
|
|
50
|
+
# With the end position appended
|
|
51
|
+
update_times = np.concatenate((update_times, [move_time]))
|
|
52
|
+
# Interpolate the [old, new] position array with those update times
|
|
53
|
+
new_positions = np.interp(
|
|
54
|
+
update_times, [0, move_time], [old_position, new_position]
|
|
55
|
+
)
|
|
56
|
+
for update_time, new_position in zip(update_times, new_positions, strict=True):
|
|
57
|
+
# Calculate how long to wait to get there
|
|
58
|
+
relative_time = time.monotonic() - start
|
|
59
|
+
await asyncio.sleep(update_time - relative_time)
|
|
60
|
+
# Update the readback position
|
|
61
|
+
self._user_readback_set(new_position)
|
|
63
62
|
|
|
64
63
|
@WatchableAsyncStatus.wrap
|
|
65
64
|
async def set(self, value: float):
|
|
@@ -75,22 +74,25 @@ class SimMotor(StandardReadable, Movable, Stoppable):
|
|
|
75
74
|
self.velocity.get_value(),
|
|
76
75
|
)
|
|
77
76
|
# If zero velocity, do instant move
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
):
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
if velocity == 0:
|
|
78
|
+
self._user_readback_set(new_position)
|
|
79
|
+
else:
|
|
80
|
+
move_time = abs(new_position - old_position) / velocity
|
|
81
|
+
self._move_status = AsyncStatus(
|
|
82
|
+
self._move(old_position, new_position, move_time)
|
|
83
|
+
)
|
|
84
|
+
# If stop is called then this will raise a CancelledError, ignore it
|
|
85
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
86
|
+
async for current_position in observe_value(
|
|
87
|
+
self.user_readback, done_status=self._move_status
|
|
88
|
+
):
|
|
89
|
+
yield WatcherUpdate(
|
|
90
|
+
current=current_position,
|
|
91
|
+
initial=old_position,
|
|
92
|
+
target=new_position,
|
|
93
|
+
name=self.name,
|
|
94
|
+
unit=units,
|
|
95
|
+
)
|
|
94
96
|
if not self._set_success:
|
|
95
97
|
raise RuntimeError("Motor was stopped")
|
|
96
98
|
|
|
@@ -28,6 +28,8 @@ from tango.asyncio import DeviceProxy as AsyncDeviceProxy
|
|
|
28
28
|
|
|
29
29
|
from ._tango_transport import TangoSignalBackend, get_python_type
|
|
30
30
|
|
|
31
|
+
logger = logging.getLogger("ophyd_async")
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
def make_backend(
|
|
33
35
|
datatype: type[SignalDatatypeT] | None,
|
|
@@ -205,7 +207,7 @@ async def infer_signal_type(
|
|
|
205
207
|
if config.in_type == CmdArgType.DevVoid:
|
|
206
208
|
return SignalX
|
|
207
209
|
elif config.in_type != config.out_type:
|
|
208
|
-
|
|
210
|
+
logger.debug("Commands with different in and out dtypes are not supported")
|
|
209
211
|
return None
|
|
210
212
|
else:
|
|
211
213
|
return SignalRW
|
|
@@ -189,7 +189,7 @@ class AttributeProxy(TangoProxy):
|
|
|
189
189
|
raise TimeoutError(f"{self._name} attr put failed: Timeout") from te
|
|
190
190
|
except DevFailed as de:
|
|
191
191
|
raise RuntimeError(
|
|
192
|
-
f"{self._name} device
|
|
192
|
+
f"{self._name} device failure: {de.args[0].desc}"
|
|
193
193
|
) from de
|
|
194
194
|
|
|
195
195
|
else:
|
|
@@ -210,11 +210,11 @@ class AttributeProxy(TangoProxy):
|
|
|
210
210
|
await asyncio.sleep(A_BIT)
|
|
211
211
|
if to and (time.time() - start_time > to):
|
|
212
212
|
raise TimeoutError(
|
|
213
|
-
f"{self._name} attr put failed:
|
|
213
|
+
f"{self._name} attr put failed: Timeout"
|
|
214
214
|
) from exc
|
|
215
215
|
else:
|
|
216
216
|
raise RuntimeError(
|
|
217
|
-
f"{self._name} device failure:
|
|
217
|
+
f"{self._name} device failure: {exc.args[0].desc}"
|
|
218
218
|
) from exc
|
|
219
219
|
|
|
220
220
|
return AsyncStatus(wait_for_reply(rid, timeout))
|
|
@@ -422,7 +422,7 @@ class CommandProxy(TangoProxy):
|
|
|
422
422
|
raise TimeoutError(f"{self._name} command failed: Timeout") from te
|
|
423
423
|
except DevFailed as de:
|
|
424
424
|
raise RuntimeError(
|
|
425
|
-
f"{self._name} device
|
|
425
|
+
f"{self._name} device failure: {de.args[0].desc}"
|
|
426
426
|
) from de
|
|
427
427
|
|
|
428
428
|
else:
|
|
@@ -446,8 +446,7 @@ class CommandProxy(TangoProxy):
|
|
|
446
446
|
) from de_exc
|
|
447
447
|
else:
|
|
448
448
|
raise RuntimeError(
|
|
449
|
-
f"{self._name} device failure:"
|
|
450
|
-
f" {de_exc.args[0].desc}"
|
|
449
|
+
f"{self._name} device failure: {de_exc.args[0].desc}"
|
|
451
450
|
) from de_exc
|
|
452
451
|
|
|
453
452
|
return AsyncStatus(wait_for_reply(rid, timeout))
|
|
@@ -733,22 +732,21 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
733
732
|
" for which polling is disabled."
|
|
734
733
|
)
|
|
735
734
|
|
|
735
|
+
if callback and self.proxies[self.read_trl].has_subscription(): # type: ignore
|
|
736
|
+
msg = "Cannot set a callback when one is already set"
|
|
737
|
+
raise RuntimeError(msg)
|
|
738
|
+
|
|
739
|
+
if self.proxies[self.read_trl].has_subscription(): # type: ignore
|
|
740
|
+
self.proxies[self.read_trl].unsubscribe_callback() # type: ignore
|
|
741
|
+
|
|
736
742
|
if callback:
|
|
737
743
|
try:
|
|
738
|
-
assert not self.proxies[self.read_trl].has_subscription() # type: ignore
|
|
739
744
|
self.proxies[self.read_trl].subscribe_callback(callback) # type: ignore
|
|
740
|
-
except AssertionError as ae:
|
|
741
|
-
raise RuntimeError(
|
|
742
|
-
"Cannot set a callback when one" " is already set"
|
|
743
|
-
) from ae
|
|
744
745
|
except RuntimeError as exc:
|
|
745
746
|
raise RuntimeError(
|
|
746
|
-
f"Cannot set callback
|
|
747
|
+
f"Cannot set callback for {self.read_trl}. {exc}"
|
|
747
748
|
) from exc
|
|
748
749
|
|
|
749
|
-
else:
|
|
750
|
-
self.proxies[self.read_trl].unsubscribe_callback() # type: ignore
|
|
751
|
-
|
|
752
750
|
def set_polling(
|
|
753
751
|
self,
|
|
754
752
|
allow_polling: bool = True,
|
|
@@ -43,8 +43,11 @@ class TangoMover(TangoReadable, Movable, Stoppable):
|
|
|
43
43
|
(old_position, velocity) = await asyncio.gather(
|
|
44
44
|
self.position.get_value(), self.velocity.get_value()
|
|
45
45
|
)
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
# TODO: check whether Tango does work with negative velocity
|
|
47
|
+
if timeout is CALCULATE_TIMEOUT and velocity == 0:
|
|
48
|
+
msg = "Motor has zero velocity"
|
|
49
|
+
raise ValueError(msg)
|
|
50
|
+
else:
|
|
48
51
|
timeout = abs(value - old_position) / velocity + DEFAULT_TIMEOUT
|
|
49
52
|
|
|
50
53
|
if not (isinstance(timeout, float) or timeout is None):
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from . import __pytest_assert_rewrite # noqa: F401
|
|
2
|
+
from ._assert import (
|
|
3
|
+
ApproxTable,
|
|
4
|
+
MonitorQueue,
|
|
5
|
+
approx_value,
|
|
6
|
+
assert_configuration,
|
|
7
|
+
assert_describe_signal,
|
|
8
|
+
assert_emitted,
|
|
9
|
+
assert_reading,
|
|
10
|
+
assert_value,
|
|
11
|
+
)
|
|
12
|
+
from ._mock_signal_utils import (
|
|
13
|
+
callback_on_mock_put,
|
|
14
|
+
get_mock,
|
|
15
|
+
get_mock_put,
|
|
16
|
+
mock_puts_blocked,
|
|
17
|
+
reset_mock_put_calls,
|
|
18
|
+
set_mock_put_proceeds,
|
|
19
|
+
set_mock_value,
|
|
20
|
+
set_mock_values,
|
|
21
|
+
)
|
|
22
|
+
from ._one_of_everything import (
|
|
23
|
+
ExampleEnum,
|
|
24
|
+
ExampleTable,
|
|
25
|
+
OneOfEverythingDevice,
|
|
26
|
+
ParentOfEverythingDevice,
|
|
27
|
+
)
|
|
28
|
+
from ._wait_for_pending import wait_for_pending_wakeups
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"approx_value",
|
|
32
|
+
"assert_configuration",
|
|
33
|
+
"assert_describe_signal",
|
|
34
|
+
"assert_emitted",
|
|
35
|
+
"assert_reading",
|
|
36
|
+
"assert_value",
|
|
37
|
+
"callback_on_mock_put",
|
|
38
|
+
"get_mock",
|
|
39
|
+
"get_mock_put",
|
|
40
|
+
"mock_puts_blocked",
|
|
41
|
+
"reset_mock_put_calls",
|
|
42
|
+
"set_mock_put_proceeds",
|
|
43
|
+
"set_mock_value",
|
|
44
|
+
"set_mock_values",
|
|
45
|
+
"wait_for_pending_wakeups",
|
|
46
|
+
"ExampleEnum",
|
|
47
|
+
"ExampleTable",
|
|
48
|
+
"OneOfEverythingDevice",
|
|
49
|
+
"ParentOfEverythingDevice",
|
|
50
|
+
"MonitorQueue",
|
|
51
|
+
"ApproxTable",
|
|
52
|
+
]
|