ophyd-async 0.3a2__py3-none-any.whl → 0.3a3__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 +1 -1
- ophyd_async/core/__init__.py +11 -1
- ophyd_async/core/detector.py +8 -9
- ophyd_async/core/flyer.py +2 -2
- ophyd_async/core/signal.py +102 -7
- ophyd_async/core/signal_backend.py +2 -2
- ophyd_async/core/sim_signal_backend.py +6 -6
- ophyd_async/core/standard_readable.py +211 -24
- ophyd_async/epics/_backend/_aioca.py +6 -6
- ophyd_async/epics/_backend/_p4p.py +17 -11
- ophyd_async/epics/areadetector/__init__.py +4 -0
- ophyd_async/epics/areadetector/aravis.py +7 -9
- ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
- ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
- ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
- ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
- ophyd_async/epics/areadetector/drivers/kinetix_driver.py +24 -0
- ophyd_async/epics/areadetector/drivers/vimba_driver.py +58 -0
- ophyd_async/epics/areadetector/kinetix.py +46 -0
- ophyd_async/epics/areadetector/pilatus.py +7 -12
- ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
- ophyd_async/epics/areadetector/vimba.py +43 -0
- ophyd_async/epics/areadetector/writers/hdf_writer.py +6 -3
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +1 -0
- ophyd_async/epics/demo/__init__.py +19 -22
- ophyd_async/epics/motion/motor.py +11 -12
- ophyd_async/epics/signal/signal.py +1 -1
- ophyd_async/log.py +130 -0
- ophyd_async/panda/writers/_hdf_writer.py +3 -3
- ophyd_async/protocols.py +26 -3
- ophyd_async/sim/demo/sim_motor.py +11 -9
- ophyd_async/sim/pattern_generator.py +4 -4
- ophyd_async/sim/sim_pattern_detector_writer.py +2 -2
- ophyd_async/sim/sim_pattern_generator.py +2 -2
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/METADATA +20 -3
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/RECORD +40 -33
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/WHEEL +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -30,6 +30,10 @@ from .signal import (
|
|
|
30
30
|
SignalRW,
|
|
31
31
|
SignalW,
|
|
32
32
|
SignalX,
|
|
33
|
+
assert_configuration,
|
|
34
|
+
assert_emitted,
|
|
35
|
+
assert_reading,
|
|
36
|
+
assert_value,
|
|
33
37
|
observe_value,
|
|
34
38
|
set_and_wait_for_value,
|
|
35
39
|
set_sim_callback,
|
|
@@ -41,7 +45,7 @@ from .signal import (
|
|
|
41
45
|
)
|
|
42
46
|
from .signal_backend import SignalBackend
|
|
43
47
|
from .sim_signal_backend import SimSignalBackend
|
|
44
|
-
from .standard_readable import StandardReadable
|
|
48
|
+
from .standard_readable import ConfigSignal, HintedSignal, StandardReadable
|
|
45
49
|
from .utils import (
|
|
46
50
|
DEFAULT_TIMEOUT,
|
|
47
51
|
Callback,
|
|
@@ -84,6 +88,8 @@ __all__ = [
|
|
|
84
88
|
"ShapeProvider",
|
|
85
89
|
"StaticDirectoryProvider",
|
|
86
90
|
"StandardReadable",
|
|
91
|
+
"ConfigSignal",
|
|
92
|
+
"HintedSignal",
|
|
87
93
|
"TriggerInfo",
|
|
88
94
|
"TriggerLogic",
|
|
89
95
|
"HardwareTriggeredFlyable",
|
|
@@ -103,4 +109,8 @@ __all__ = [
|
|
|
103
109
|
"walk_rw_signals",
|
|
104
110
|
"load_device",
|
|
105
111
|
"save_device",
|
|
112
|
+
"assert_reading",
|
|
113
|
+
"assert_value",
|
|
114
|
+
"assert_configuration",
|
|
115
|
+
"assert_emitted",
|
|
106
116
|
]
|
ophyd_async/core/detector.py
CHANGED
|
@@ -19,7 +19,7 @@ from typing import (
|
|
|
19
19
|
|
|
20
20
|
from bluesky.protocols import (
|
|
21
21
|
Collectable,
|
|
22
|
-
|
|
22
|
+
DataKey,
|
|
23
23
|
Flyable,
|
|
24
24
|
Preparable,
|
|
25
25
|
Reading,
|
|
@@ -33,7 +33,6 @@ from ophyd_async.protocols import AsyncConfigurable, AsyncReadable
|
|
|
33
33
|
|
|
34
34
|
from .async_status import AsyncStatus
|
|
35
35
|
from .device import Device
|
|
36
|
-
from .signal import SignalR
|
|
37
36
|
from .utils import DEFAULT_TIMEOUT, merge_gathered_dicts
|
|
38
37
|
|
|
39
38
|
T = TypeVar("T")
|
|
@@ -110,7 +109,7 @@ class DetectorWriter(ABC):
|
|
|
110
109
|
(e.g. an HDF5 file)"""
|
|
111
110
|
|
|
112
111
|
@abstractmethod
|
|
113
|
-
async def open(self, multiplier: int = 1) -> Dict[str,
|
|
112
|
+
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
|
|
114
113
|
"""Open writer and wait for it to be ready for data.
|
|
115
114
|
|
|
116
115
|
Args:
|
|
@@ -161,7 +160,7 @@ class StandardDetector(
|
|
|
161
160
|
self,
|
|
162
161
|
controller: DetectorControl,
|
|
163
162
|
writer: DetectorWriter,
|
|
164
|
-
config_sigs: Sequence[
|
|
163
|
+
config_sigs: Sequence[AsyncReadable] = (),
|
|
165
164
|
name: str = "",
|
|
166
165
|
writer_timeout: float = DEFAULT_TIMEOUT,
|
|
167
166
|
) -> None:
|
|
@@ -181,7 +180,7 @@ class StandardDetector(
|
|
|
181
180
|
"""
|
|
182
181
|
self._controller = controller
|
|
183
182
|
self._writer = writer
|
|
184
|
-
self._describe: Dict[str,
|
|
183
|
+
self._describe: Dict[str, DataKey] = {}
|
|
185
184
|
self._config_sigs = list(config_sigs)
|
|
186
185
|
self._frame_writing_timeout = writer_timeout
|
|
187
186
|
# For prepare
|
|
@@ -214,7 +213,7 @@ class StandardDetector(
|
|
|
214
213
|
async def _check_config_sigs(self):
|
|
215
214
|
"""Checks configuration signals are named and connected."""
|
|
216
215
|
for signal in self._config_sigs:
|
|
217
|
-
if signal.
|
|
216
|
+
if signal.name == "":
|
|
218
217
|
raise Exception(
|
|
219
218
|
"config signal must be named before it is passed to the detector"
|
|
220
219
|
)
|
|
@@ -234,14 +233,14 @@ class StandardDetector(
|
|
|
234
233
|
async def read_configuration(self) -> Dict[str, Reading]:
|
|
235
234
|
return await merge_gathered_dicts(sig.read() for sig in self._config_sigs)
|
|
236
235
|
|
|
237
|
-
async def describe_configuration(self) -> Dict[str,
|
|
236
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
238
237
|
return await merge_gathered_dicts(sig.describe() for sig in self._config_sigs)
|
|
239
238
|
|
|
240
239
|
async def read(self) -> Dict[str, Reading]:
|
|
241
240
|
# All data is in StreamResources, not Events, so nothing to output here
|
|
242
241
|
return {}
|
|
243
242
|
|
|
244
|
-
async def describe(self) -> Dict[str,
|
|
243
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
245
244
|
return self._describe
|
|
246
245
|
|
|
247
246
|
@AsyncStatus.wrap
|
|
@@ -330,7 +329,7 @@ class StandardDetector(
|
|
|
330
329
|
assert self._fly_status, "Kickoff not run"
|
|
331
330
|
return await self._fly_status
|
|
332
331
|
|
|
333
|
-
async def describe_collect(self) -> Dict[str,
|
|
332
|
+
async def describe_collect(self) -> Dict[str, DataKey]:
|
|
334
333
|
return self._describe
|
|
335
334
|
|
|
336
335
|
async def collect_asset_docs(
|
ophyd_async/core/flyer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Dict, Generic, Sequence, TypeVar
|
|
3
3
|
|
|
4
|
-
from bluesky.protocols import
|
|
4
|
+
from bluesky.protocols import DataKey, Flyable, Preparable, Reading, Stageable
|
|
5
5
|
|
|
6
6
|
from .async_status import AsyncStatus
|
|
7
7
|
from .device import Device
|
|
@@ -74,7 +74,7 @@ class HardwareTriggeredFlyable(
|
|
|
74
74
|
async def complete(self) -> None:
|
|
75
75
|
await self._trigger_logic.complete()
|
|
76
76
|
|
|
77
|
-
async def describe_configuration(self) -> Dict[str,
|
|
77
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
78
78
|
return await merge_gathered_dicts(
|
|
79
79
|
[sig.describe() for sig in self._configuration_signals]
|
|
80
80
|
)
|
ophyd_async/core/signal.py
CHANGED
|
@@ -2,19 +2,29 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
AsyncGenerator,
|
|
8
|
+
Callable,
|
|
9
|
+
Dict,
|
|
10
|
+
Generic,
|
|
11
|
+
Mapping,
|
|
12
|
+
Optional,
|
|
13
|
+
Tuple,
|
|
14
|
+
Type,
|
|
15
|
+
Union,
|
|
16
|
+
)
|
|
6
17
|
|
|
7
18
|
from bluesky.protocols import (
|
|
8
|
-
|
|
19
|
+
DataKey,
|
|
9
20
|
Locatable,
|
|
10
21
|
Location,
|
|
11
22
|
Movable,
|
|
12
23
|
Reading,
|
|
13
|
-
Stageable,
|
|
14
24
|
Subscribable,
|
|
15
25
|
)
|
|
16
26
|
|
|
17
|
-
from ophyd_async.protocols import AsyncReadable
|
|
27
|
+
from ophyd_async.protocols import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
18
28
|
|
|
19
29
|
from .async_status import AsyncStatus
|
|
20
30
|
from .device import Device
|
|
@@ -128,7 +138,7 @@ class _SignalCache(Generic[T]):
|
|
|
128
138
|
return self._staged or bool(self._listeners)
|
|
129
139
|
|
|
130
140
|
|
|
131
|
-
class SignalR(Signal[T], AsyncReadable,
|
|
141
|
+
class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
132
142
|
"""Signal that can be read from and monitored"""
|
|
133
143
|
|
|
134
144
|
_cache: Optional[_SignalCache] = None
|
|
@@ -161,9 +171,9 @@ class SignalR(Signal[T], AsyncReadable, Stageable, Subscribable):
|
|
|
161
171
|
return {self.name: await self._backend_or_cache(cached).get_reading()}
|
|
162
172
|
|
|
163
173
|
@_add_timeout
|
|
164
|
-
async def describe(self) -> Dict[str,
|
|
174
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
165
175
|
"""Return a single item dict with the descriptor in it"""
|
|
166
|
-
return {self.name: await self._backend.
|
|
176
|
+
return {self.name: await self._backend.get_datakey(self.source)}
|
|
167
177
|
|
|
168
178
|
@_add_timeout
|
|
169
179
|
async def get_value(self, cached: Optional[bool] = None) -> T:
|
|
@@ -272,6 +282,91 @@ def soft_signal_r_and_backend(
|
|
|
272
282
|
return (signal, backend)
|
|
273
283
|
|
|
274
284
|
|
|
285
|
+
async def assert_value(signal: SignalR[T], value: Any) -> None:
|
|
286
|
+
"""Assert a signal's value and compare it an expected signal.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
signal:
|
|
291
|
+
signal with get_value.
|
|
292
|
+
value:
|
|
293
|
+
The expected value from the signal.
|
|
294
|
+
|
|
295
|
+
Notes
|
|
296
|
+
-----
|
|
297
|
+
Example usage::
|
|
298
|
+
await assert_value(signal, value)
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
assert await signal.get_value() == value
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
async def assert_reading(
|
|
305
|
+
readable: AsyncReadable, reading: Mapping[str, Reading]
|
|
306
|
+
) -> None:
|
|
307
|
+
"""Assert readings from readable.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
readable:
|
|
312
|
+
Callable with readable.read function that generate readings.
|
|
313
|
+
|
|
314
|
+
reading:
|
|
315
|
+
The expected readings from the readable.
|
|
316
|
+
|
|
317
|
+
Notes
|
|
318
|
+
-----
|
|
319
|
+
Example usage::
|
|
320
|
+
await assert_reading(readable, reading)
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
assert await readable.read() == reading
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
async def assert_configuration(
|
|
327
|
+
configurable: AsyncConfigurable,
|
|
328
|
+
configuration: Mapping[str, Reading],
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Assert readings from Configurable.
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
configurable:
|
|
335
|
+
Configurable with Configurable.read function that generate readings.
|
|
336
|
+
|
|
337
|
+
configuration:
|
|
338
|
+
The expected readings from configurable.
|
|
339
|
+
|
|
340
|
+
Notes
|
|
341
|
+
-----
|
|
342
|
+
Example usage::
|
|
343
|
+
await assert_configuration(configurable configuration)
|
|
344
|
+
|
|
345
|
+
"""
|
|
346
|
+
assert await configurable.read_configuration() == configuration
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
|
|
350
|
+
"""Assert emitted document generated by running a Bluesky plan
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
Doc:
|
|
355
|
+
A dictionary
|
|
356
|
+
|
|
357
|
+
numbers:
|
|
358
|
+
expected emission in kwarg from
|
|
359
|
+
|
|
360
|
+
Notes
|
|
361
|
+
-----
|
|
362
|
+
Example usage::
|
|
363
|
+
assert_emitted(docs, start=1, descriptor=1,
|
|
364
|
+
resource=1, datum=1, event=1, stop=1)
|
|
365
|
+
"""
|
|
366
|
+
assert list(docs) == list(numbers)
|
|
367
|
+
assert {name: len(d) for name, d in docs.items()} == numbers
|
|
368
|
+
|
|
369
|
+
|
|
275
370
|
async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, None]:
|
|
276
371
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
277
372
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
from typing import Generic, Optional, Type
|
|
3
3
|
|
|
4
|
-
from bluesky.protocols import
|
|
4
|
+
from bluesky.protocols import DataKey, Reading
|
|
5
5
|
|
|
6
6
|
from .utils import DEFAULT_TIMEOUT, ReadingValueCallback, T
|
|
7
7
|
|
|
@@ -27,7 +27,7 @@ class SignalBackend(Generic[T]):
|
|
|
27
27
|
"""Put a value to the PV, if wait then wait for completion for up to timeout"""
|
|
28
28
|
|
|
29
29
|
@abstractmethod
|
|
30
|
-
async def
|
|
30
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
31
31
|
"""Metadata like source, dtype, shape, precision, units"""
|
|
32
32
|
|
|
33
33
|
@abstractmethod
|
|
@@ -9,7 +9,7 @@ from enum import Enum
|
|
|
9
9
|
from typing import Any, Dict, Generic, Optional, Type, Union, cast, get_origin
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
|
-
from bluesky.protocols import
|
|
12
|
+
from bluesky.protocols import DataKey, Dtype, Reading
|
|
13
13
|
|
|
14
14
|
from .signal_backend import SignalBackend
|
|
15
15
|
from .utils import DEFAULT_TIMEOUT, ReadingValueCallback, T, get_dtype
|
|
@@ -36,7 +36,7 @@ class SimConverter(Generic[T]):
|
|
|
36
36
|
alarm_severity=-1 if severity > 2 else severity,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
40
40
|
dtype = type(value)
|
|
41
41
|
if np.issubdtype(dtype, np.integer):
|
|
42
42
|
dtype = int
|
|
@@ -56,7 +56,7 @@ class SimConverter(Generic[T]):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class SimArrayConverter(SimConverter):
|
|
59
|
-
def
|
|
59
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
60
60
|
return {"source": source, "dtype": "array", "shape": [len(value)]}
|
|
61
61
|
|
|
62
62
|
def make_initial_value(self, datatype: Optional[Type[T]]) -> T:
|
|
@@ -79,7 +79,7 @@ class SimEnumConverter(SimConverter):
|
|
|
79
79
|
else:
|
|
80
80
|
return self.enum_class(value)
|
|
81
81
|
|
|
82
|
-
def
|
|
82
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
83
83
|
choices = [e.value for e in self.enum_class]
|
|
84
84
|
return {"source": source, "dtype": "string", "shape": [], "choices": choices} # type: ignore
|
|
85
85
|
|
|
@@ -164,8 +164,8 @@ class SimSignalBackend(SignalBackend[T]):
|
|
|
164
164
|
if self.callback:
|
|
165
165
|
self.callback(reading, self._value)
|
|
166
166
|
|
|
167
|
-
async def
|
|
168
|
-
return self.converter.
|
|
167
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
168
|
+
return self.converter.get_datakey(source, self._value)
|
|
169
169
|
|
|
170
170
|
async def get_reading(self) -> Reading:
|
|
171
171
|
return self.converter.reading(self._value, self._timestamp, self._severity)
|
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
import warnings
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from typing import (
|
|
4
|
+
Callable,
|
|
5
|
+
Dict,
|
|
6
|
+
Generator,
|
|
7
|
+
Optional,
|
|
8
|
+
Sequence,
|
|
9
|
+
Tuple,
|
|
10
|
+
Type,
|
|
11
|
+
Union,
|
|
12
|
+
)
|
|
2
13
|
|
|
3
|
-
from bluesky.protocols import
|
|
14
|
+
from bluesky.protocols import DataKey, HasHints, Hints, Reading
|
|
4
15
|
|
|
5
|
-
from ophyd_async.protocols import AsyncConfigurable, AsyncReadable
|
|
16
|
+
from ophyd_async.protocols import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
6
17
|
|
|
7
18
|
from .async_status import AsyncStatus
|
|
8
|
-
from .device import Device
|
|
19
|
+
from .device import Device, DeviceVector
|
|
9
20
|
from .signal import SignalR
|
|
10
21
|
from .utils import merge_gathered_dicts
|
|
11
22
|
|
|
23
|
+
ReadableChild = Union[AsyncReadable, AsyncConfigurable, AsyncStageable, HasHints]
|
|
24
|
+
ReadableChildWrapper = Union[
|
|
25
|
+
Callable[[ReadableChild], ReadableChild], Type["ConfigSignal"], Type["HintedSignal"]
|
|
26
|
+
]
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
|
|
29
|
+
class StandardReadable(
|
|
30
|
+
Device, AsyncReadable, AsyncConfigurable, AsyncStageable, HasHints
|
|
31
|
+
):
|
|
14
32
|
"""Device that owns its children and provides useful default behavior.
|
|
15
33
|
|
|
16
34
|
- When its name is set it renames child Devices
|
|
@@ -18,9 +36,12 @@ class StandardReadable(Device, AsyncReadable, AsyncConfigurable, Stageable):
|
|
|
18
36
|
- These signals will be subscribed for read() between stage() and unstage()
|
|
19
37
|
"""
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
# These must be immutable types to avoid accidental sharing between
|
|
40
|
+
# different instances of the class
|
|
41
|
+
_readables: Tuple[AsyncReadable, ...] = ()
|
|
42
|
+
_configurables: Tuple[AsyncConfigurable, ...] = ()
|
|
43
|
+
_stageables: Tuple[AsyncStageable, ...] = ()
|
|
44
|
+
_has_hints: Tuple[HasHints, ...] = ()
|
|
24
45
|
|
|
25
46
|
def set_readable_signals(
|
|
26
47
|
self,
|
|
@@ -38,37 +59,203 @@ class StandardReadable(Device, AsyncReadable, AsyncConfigurable, Stageable):
|
|
|
38
59
|
read_uncached:
|
|
39
60
|
Signals to make up :meth:`~StandardReadable.read` that won't be cached
|
|
40
61
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
warnings.warn(
|
|
63
|
+
DeprecationWarning(
|
|
64
|
+
"Migrate to `add_children_as_readables` context manager or "
|
|
65
|
+
"`add_readables` method"
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
self.add_readables(read, wrapper=HintedSignal)
|
|
69
|
+
self.add_readables(config, wrapper=ConfigSignal)
|
|
70
|
+
self.add_readables(read_uncached, wrapper=HintedSignal.uncached)
|
|
44
71
|
|
|
45
72
|
@AsyncStatus.wrap
|
|
46
73
|
async def stage(self) -> None:
|
|
47
|
-
for sig in self.
|
|
74
|
+
for sig in self._stageables:
|
|
48
75
|
await sig.stage().task
|
|
49
76
|
|
|
50
77
|
@AsyncStatus.wrap
|
|
51
78
|
async def unstage(self) -> None:
|
|
52
|
-
for sig in self.
|
|
79
|
+
for sig in self._stageables:
|
|
53
80
|
await sig.unstage().task
|
|
54
81
|
|
|
55
|
-
async def describe_configuration(self) -> Dict[str,
|
|
82
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
56
83
|
return await merge_gathered_dicts(
|
|
57
|
-
[sig.
|
|
84
|
+
[sig.describe_configuration() for sig in self._configurables]
|
|
58
85
|
)
|
|
59
86
|
|
|
60
87
|
async def read_configuration(self) -> Dict[str, Reading]:
|
|
61
88
|
return await merge_gathered_dicts(
|
|
62
|
-
[sig.
|
|
89
|
+
[sig.read_configuration() for sig in self._configurables]
|
|
63
90
|
)
|
|
64
91
|
|
|
65
|
-
async def describe(self) -> Dict[str,
|
|
66
|
-
return await merge_gathered_dicts(
|
|
67
|
-
[sig.describe() for sig in self._read_signals + self._read_uncached_signals]
|
|
68
|
-
)
|
|
92
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
93
|
+
return await merge_gathered_dicts([sig.describe() for sig in self._readables])
|
|
69
94
|
|
|
70
95
|
async def read(self) -> Dict[str, Reading]:
|
|
71
|
-
return await merge_gathered_dicts(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
return await merge_gathered_dicts([sig.read() for sig in self._readables])
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def hints(self) -> Hints:
|
|
100
|
+
hints: Hints = {}
|
|
101
|
+
for new_hint in self._has_hints:
|
|
102
|
+
# Merge the existing and new hints, based on the type of the value.
|
|
103
|
+
# This avoids default dict merge behaviour that overrides the values;
|
|
104
|
+
# we want to combine them when they are Sequences, and ensure they are
|
|
105
|
+
# identical when string values.
|
|
106
|
+
for key, value in new_hint.hints.items():
|
|
107
|
+
if isinstance(value, str):
|
|
108
|
+
if key in hints:
|
|
109
|
+
assert (
|
|
110
|
+
hints[key] == value # type: ignore[literal-required]
|
|
111
|
+
), f"Hints key {key} value may not be overridden"
|
|
112
|
+
else:
|
|
113
|
+
hints[key] = value # type: ignore[literal-required]
|
|
114
|
+
elif isinstance(value, Sequence):
|
|
115
|
+
if key in hints:
|
|
116
|
+
for new_val in value:
|
|
117
|
+
assert (
|
|
118
|
+
new_val not in hints[key] # type: ignore[literal-required]
|
|
119
|
+
), f"Hint {key} {new_val} overrides existing hint"
|
|
120
|
+
hints[key] = ( # type: ignore[literal-required]
|
|
121
|
+
hints[key] + value # type: ignore[literal-required]
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
hints[key] = value # type: ignore[literal-required]
|
|
125
|
+
else:
|
|
126
|
+
raise TypeError(
|
|
127
|
+
f"{new_hint.name}: Unknown type for value '{value}' "
|
|
128
|
+
f" for key '{key}'"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return hints
|
|
132
|
+
|
|
133
|
+
@contextmanager
|
|
134
|
+
def add_children_as_readables(
|
|
135
|
+
self,
|
|
136
|
+
wrapper: Optional[ReadableChildWrapper] = None,
|
|
137
|
+
) -> Generator[None, None, None]:
|
|
138
|
+
"""Context manager to wrap adding Devices
|
|
139
|
+
|
|
140
|
+
Add Devices to this class instance inside the Context Manager to automatically
|
|
141
|
+
add them to the correct fields, based on the Device's interfaces.
|
|
142
|
+
|
|
143
|
+
The provided wrapper class will be applied to all Devices and can be used to
|
|
144
|
+
specify their behaviour.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
wrapper:
|
|
149
|
+
Wrapper class to apply to all Devices created inside the context manager.
|
|
150
|
+
|
|
151
|
+
See Also
|
|
152
|
+
--------
|
|
153
|
+
:func:`~StandardReadable.add_readables`
|
|
154
|
+
:class:`ConfigSignal`
|
|
155
|
+
:class:`HintedSignal`
|
|
156
|
+
:meth:`HintedSignal.uncached`
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
dict_copy = self.__dict__.copy()
|
|
160
|
+
|
|
161
|
+
yield
|
|
162
|
+
|
|
163
|
+
# Set symmetric difference operator gives all newly added keys
|
|
164
|
+
new_keys = dict_copy.keys() ^ self.__dict__.keys()
|
|
165
|
+
new_values = [self.__dict__[key] for key in new_keys]
|
|
166
|
+
|
|
167
|
+
flattened_values = []
|
|
168
|
+
for value in new_values:
|
|
169
|
+
if isinstance(value, DeviceVector):
|
|
170
|
+
children = value.children()
|
|
171
|
+
flattened_values.extend([x[1] for x in children])
|
|
172
|
+
else:
|
|
173
|
+
flattened_values.append(value)
|
|
174
|
+
|
|
175
|
+
new_devices = list(filter(lambda x: isinstance(x, Device), flattened_values))
|
|
176
|
+
self.add_readables(new_devices, wrapper)
|
|
177
|
+
|
|
178
|
+
def add_readables(
|
|
179
|
+
self,
|
|
180
|
+
devices: Sequence[Device],
|
|
181
|
+
wrapper: Optional[ReadableChildWrapper] = None,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Add the given devices to the lists of known Devices
|
|
184
|
+
|
|
185
|
+
Add the provided Devices to the relevant fields, based on the Signal's
|
|
186
|
+
interfaces.
|
|
187
|
+
|
|
188
|
+
The provided wrapper class will be applied to all Devices and can be used to
|
|
189
|
+
specify their behaviour.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
devices:
|
|
194
|
+
The devices to be added
|
|
195
|
+
wrapper:
|
|
196
|
+
Wrapper class to apply to all Devices created inside the context manager.
|
|
197
|
+
|
|
198
|
+
See Also
|
|
199
|
+
--------
|
|
200
|
+
:func:`~StandardReadable.add_children_as_readables`
|
|
201
|
+
:class:`ConfigSignal`
|
|
202
|
+
:class:`HintedSignal`
|
|
203
|
+
:meth:`HintedSignal.uncached`
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
for readable in devices:
|
|
207
|
+
obj = readable
|
|
208
|
+
if wrapper:
|
|
209
|
+
obj = wrapper(readable)
|
|
210
|
+
|
|
211
|
+
if isinstance(obj, AsyncReadable):
|
|
212
|
+
self._readables += (obj,)
|
|
213
|
+
|
|
214
|
+
if isinstance(obj, AsyncConfigurable):
|
|
215
|
+
self._configurables += (obj,)
|
|
216
|
+
|
|
217
|
+
if isinstance(obj, AsyncStageable):
|
|
218
|
+
self._stageables += (obj,)
|
|
219
|
+
|
|
220
|
+
if isinstance(obj, HasHints):
|
|
221
|
+
self._has_hints += (obj,)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ConfigSignal(AsyncConfigurable):
|
|
225
|
+
def __init__(self, signal: ReadableChild) -> None:
|
|
226
|
+
assert isinstance(signal, SignalR), f"Expected signal, got {signal}"
|
|
227
|
+
self.signal = signal
|
|
228
|
+
|
|
229
|
+
async def read_configuration(self) -> Dict[str, Reading]:
|
|
230
|
+
return await self.signal.read()
|
|
231
|
+
|
|
232
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
233
|
+
return await self.signal.describe()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class HintedSignal(HasHints, AsyncReadable):
|
|
237
|
+
def __init__(self, signal: ReadableChild, allow_cache: bool = True) -> None:
|
|
238
|
+
assert isinstance(signal, SignalR), f"Expected signal, got {signal}"
|
|
239
|
+
self.signal = signal
|
|
240
|
+
self.cached = None if allow_cache else allow_cache
|
|
241
|
+
if allow_cache:
|
|
242
|
+
self.stage = signal.stage
|
|
243
|
+
self.unstage = signal.unstage
|
|
244
|
+
|
|
245
|
+
async def read(self) -> Dict[str, Reading]:
|
|
246
|
+
return await self.signal.read(cached=self.cached)
|
|
247
|
+
|
|
248
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
249
|
+
return await self.signal.describe()
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def name(self) -> str:
|
|
253
|
+
return self.signal.name
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def hints(self) -> Hints:
|
|
257
|
+
return {"fields": [self.signal.name]}
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def uncached(cls, signal: ReadableChild) -> "HintedSignal":
|
|
261
|
+
return cls(signal, allow_cache=False)
|
|
@@ -15,7 +15,7 @@ from aioca import (
|
|
|
15
15
|
caput,
|
|
16
16
|
)
|
|
17
17
|
from aioca.types import AugmentedValue, Dbr, Format
|
|
18
|
-
from bluesky.protocols import
|
|
18
|
+
from bluesky.protocols import DataKey, Dtype, Reading
|
|
19
19
|
from epicscorelibs.ca import dbr
|
|
20
20
|
|
|
21
21
|
from ophyd_async.core import (
|
|
@@ -58,7 +58,7 @@ class CaConverter:
|
|
|
58
58
|
"alarm_severity": -1 if value.severity > 2 else value.severity,
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
def
|
|
61
|
+
def get_datakey(self, source: str, value: AugmentedValue) -> DataKey:
|
|
62
62
|
return {"source": source, "dtype": dbr_to_dtype[value.datatype], "shape": []}
|
|
63
63
|
|
|
64
64
|
|
|
@@ -73,7 +73,7 @@ class CaLongStrConverter(CaConverter):
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
class CaArrayConverter(CaConverter):
|
|
76
|
-
def
|
|
76
|
+
def get_datakey(self, source: str, value: AugmentedValue) -> DataKey:
|
|
77
77
|
return {"source": source, "dtype": "array", "shape": [len(value)]}
|
|
78
78
|
|
|
79
79
|
|
|
@@ -90,7 +90,7 @@ class CaEnumConverter(CaConverter):
|
|
|
90
90
|
def value(self, value: AugmentedValue):
|
|
91
91
|
return self.enum_class(value)
|
|
92
92
|
|
|
93
|
-
def
|
|
93
|
+
def get_datakey(self, source: str, value: AugmentedValue) -> DataKey:
|
|
94
94
|
choices = [e.value for e in self.enum_class]
|
|
95
95
|
return {"source": source, "dtype": "string", "shape": [], "choices": choices}
|
|
96
96
|
|
|
@@ -218,9 +218,9 @@ class CaSignalBackend(SignalBackend[T]):
|
|
|
218
218
|
timeout=None,
|
|
219
219
|
)
|
|
220
220
|
|
|
221
|
-
async def
|
|
221
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
222
222
|
value = await self._caget(FORMAT_CTRL)
|
|
223
|
-
return self.converter.
|
|
223
|
+
return self.converter.get_datakey(source, value)
|
|
224
224
|
|
|
225
225
|
async def get_reading(self) -> Reading:
|
|
226
226
|
value = await self._caget(FORMAT_TIME)
|