ophyd-async 0.5.2__py3-none-any.whl → 0.7.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 +10 -1
- ophyd_async/__main__.py +12 -4
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +15 -7
- ophyd_async/core/_detector.py +133 -87
- ophyd_async/core/_device.py +19 -16
- ophyd_async/core/_device_save_loader.py +30 -19
- ophyd_async/core/_flyer.py +6 -19
- ophyd_async/core/_hdf_dataset.py +8 -9
- ophyd_async/core/_log.py +3 -1
- ophyd_async/core/_mock_signal_backend.py +11 -9
- ophyd_async/core/_mock_signal_utils.py +8 -5
- ophyd_async/core/_protocol.py +7 -7
- ophyd_async/core/_providers.py +11 -11
- ophyd_async/core/_readable.py +30 -22
- ophyd_async/core/_signal.py +52 -51
- ophyd_async/core/_signal_backend.py +20 -7
- ophyd_async/core/_soft_signal_backend.py +62 -32
- ophyd_async/core/_status.py +7 -9
- ophyd_async/core/_table.py +146 -0
- ophyd_async/core/_utils.py +24 -28
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -19
- ophyd_async/epics/adaravis/_aravis_io.py +2 -1
- ophyd_async/epics/adcore/_core_io.py +2 -0
- ophyd_async/epics/adcore/_core_logic.py +4 -5
- ophyd_async/epics/adcore/_hdf_writer.py +19 -8
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +5 -6
- ophyd_async/epics/adkinetix/_kinetix_controller.py +20 -15
- ophyd_async/epics/adpilatus/_pilatus_controller.py +22 -18
- ophyd_async/epics/adsimdetector/_sim.py +7 -6
- ophyd_async/epics/adsimdetector/_sim_controller.py +22 -17
- ophyd_async/epics/advimba/_vimba_controller.py +22 -17
- ophyd_async/epics/demo/_mover.py +4 -5
- ophyd_async/epics/demo/sensor.db +0 -1
- ophyd_async/epics/eiger/_eiger.py +1 -1
- ophyd_async/epics/eiger/_eiger_controller.py +18 -18
- ophyd_async/epics/eiger/_odin_io.py +6 -5
- ophyd_async/epics/motor.py +8 -10
- ophyd_async/epics/pvi/_pvi.py +30 -33
- ophyd_async/epics/signal/_aioca.py +55 -25
- ophyd_async/epics/signal/_common.py +3 -10
- ophyd_async/epics/signal/_epics_transport.py +11 -8
- ophyd_async/epics/signal/_p4p.py +79 -30
- ophyd_async/epics/signal/_signal.py +6 -8
- ophyd_async/fastcs/panda/__init__.py +0 -6
- ophyd_async/fastcs/panda/_block.py +7 -0
- ophyd_async/fastcs/panda/_control.py +16 -17
- ophyd_async/fastcs/panda/_hdf_panda.py +11 -4
- ophyd_async/fastcs/panda/_table.py +77 -138
- ophyd_async/fastcs/panda/_trigger.py +4 -5
- ophyd_async/fastcs/panda/_utils.py +3 -2
- ophyd_async/fastcs/panda/_writer.py +30 -15
- ophyd_async/plan_stubs/_fly.py +15 -17
- ophyd_async/plan_stubs/_nd_attributes.py +12 -6
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +3 -3
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +27 -21
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +9 -6
- ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +21 -23
- ophyd_async/sim/demo/_sim_motor.py +2 -1
- ophyd_async/tango/__init__.py +45 -0
- ophyd_async/tango/base_devices/__init__.py +4 -0
- ophyd_async/tango/base_devices/_base_device.py +225 -0
- ophyd_async/tango/base_devices/_tango_readable.py +33 -0
- ophyd_async/tango/demo/__init__.py +12 -0
- ophyd_async/tango/demo/_counter.py +37 -0
- ophyd_async/tango/demo/_detector.py +42 -0
- ophyd_async/tango/demo/_mover.py +77 -0
- ophyd_async/tango/demo/_tango/__init__.py +3 -0
- ophyd_async/tango/demo/_tango/_servers.py +108 -0
- ophyd_async/tango/signal/__init__.py +39 -0
- ophyd_async/tango/signal/_signal.py +223 -0
- ophyd_async/tango/signal/_tango_transport.py +764 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0.dist-info}/METADATA +50 -45
- ophyd_async-0.7.0.dist-info/RECORD +108 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0.dist-info}/WHEEL +1 -1
- ophyd_async-0.5.2.dist-info/RECORD +0 -95
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import PathProvider, SignalR, StandardDetector
|
|
4
4
|
from ophyd_async.epics import adcore
|
|
@@ -12,14 +12,15 @@ class SimDetector(StandardDetector):
|
|
|
12
12
|
|
|
13
13
|
def __init__(
|
|
14
14
|
self,
|
|
15
|
-
|
|
16
|
-
hdf: adcore.NDFileHDFIO,
|
|
15
|
+
prefix: str,
|
|
17
16
|
path_provider: PathProvider,
|
|
17
|
+
drv_suffix="cam1:",
|
|
18
|
+
hdf_suffix="HDF1:",
|
|
18
19
|
name: str = "",
|
|
19
20
|
config_sigs: Sequence[SignalR] = (),
|
|
20
21
|
):
|
|
21
|
-
self.drv =
|
|
22
|
-
self.hdf =
|
|
22
|
+
self.drv = adcore.ADBaseIO(prefix + drv_suffix)
|
|
23
|
+
self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
|
|
23
24
|
|
|
24
25
|
super().__init__(
|
|
25
26
|
SimController(self.drv),
|
|
@@ -29,6 +30,6 @@ class SimDetector(StandardDetector):
|
|
|
29
30
|
lambda: self.name,
|
|
30
31
|
adcore.ADBaseDatasetDescriber(self.drv),
|
|
31
32
|
),
|
|
32
|
-
config_sigs=config_sigs,
|
|
33
|
+
config_sigs=(self.drv.acquire_period, self.drv.acquire_time, *config_sigs),
|
|
33
34
|
name=name,
|
|
34
35
|
)
|
|
@@ -1,45 +1,50 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional, Set
|
|
3
2
|
|
|
4
3
|
from ophyd_async.core import (
|
|
5
4
|
DEFAULT_TIMEOUT,
|
|
6
|
-
|
|
7
|
-
DetectorControl,
|
|
5
|
+
DetectorController,
|
|
8
6
|
DetectorTrigger,
|
|
9
7
|
)
|
|
8
|
+
from ophyd_async.core._detector import TriggerInfo
|
|
9
|
+
from ophyd_async.core._status import AsyncStatus
|
|
10
10
|
from ophyd_async.epics import adcore
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class SimController(
|
|
13
|
+
class SimController(DetectorController):
|
|
14
14
|
def __init__(
|
|
15
15
|
self,
|
|
16
16
|
driver: adcore.ADBaseIO,
|
|
17
|
-
good_states:
|
|
17
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
18
18
|
) -> None:
|
|
19
19
|
self.driver = driver
|
|
20
20
|
self.good_states = good_states
|
|
21
|
+
self.frame_timeout: float
|
|
22
|
+
self._arm_status: AsyncStatus | None = None
|
|
21
23
|
|
|
22
|
-
def get_deadtime(self, exposure: float) -> float:
|
|
24
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
23
25
|
return 0.002
|
|
24
26
|
|
|
25
|
-
async def
|
|
26
|
-
self,
|
|
27
|
-
num: int,
|
|
28
|
-
trigger: DetectorTrigger = DetectorTrigger.internal,
|
|
29
|
-
exposure: Optional[float] = None,
|
|
30
|
-
) -> AsyncStatus:
|
|
27
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
31
28
|
assert (
|
|
32
|
-
trigger == DetectorTrigger.internal
|
|
29
|
+
trigger_info.trigger == DetectorTrigger.internal
|
|
33
30
|
), "fly scanning (i.e. external triggering) is not supported for this device"
|
|
34
|
-
frame_timeout =
|
|
31
|
+
self.frame_timeout = (
|
|
32
|
+
DEFAULT_TIMEOUT + await self.driver.acquire_time.get_value()
|
|
33
|
+
)
|
|
35
34
|
await asyncio.gather(
|
|
36
|
-
self.driver.num_images.set(
|
|
35
|
+
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
37
36
|
self.driver.image_mode.set(adcore.ImageMode.multiple),
|
|
38
37
|
)
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
|
|
39
|
+
async def arm(self):
|
|
40
|
+
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
41
|
+
self.driver, good_states=self.good_states, timeout=self.frame_timeout
|
|
41
42
|
)
|
|
42
43
|
|
|
44
|
+
async def wait_for_idle(self):
|
|
45
|
+
if self._arm_status:
|
|
46
|
+
await self._arm_status
|
|
47
|
+
|
|
43
48
|
async def disarm(self):
|
|
44
49
|
# We can't use caput callback as we already used it in arm() and we can't have
|
|
45
50
|
# 2 or they will deadlock
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
|
-
from ophyd_async.core import
|
|
3
|
+
from ophyd_async.core import DetectorController, DetectorTrigger
|
|
4
|
+
from ophyd_async.core._detector import TriggerInfo
|
|
5
|
+
from ophyd_async.core._status import AsyncStatus
|
|
5
6
|
from ophyd_async.epics import adcore
|
|
6
7
|
|
|
7
8
|
from ._vimba_io import VimbaDriverIO, VimbaExposeOutMode, VimbaOnOff, VimbaTriggerSource
|
|
@@ -21,38 +22,42 @@ EXPOSE_OUT_MODE = {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
class VimbaController(
|
|
25
|
+
class VimbaController(DetectorController):
|
|
25
26
|
def __init__(
|
|
26
27
|
self,
|
|
27
28
|
driver: VimbaDriverIO,
|
|
28
29
|
) -> None:
|
|
29
30
|
self._drv = driver
|
|
31
|
+
self._arm_status: AsyncStatus | None = None
|
|
30
32
|
|
|
31
|
-
def get_deadtime(self, exposure: float) -> float:
|
|
33
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
32
34
|
return 0.001
|
|
33
35
|
|
|
34
|
-
async def
|
|
35
|
-
self,
|
|
36
|
-
num: int,
|
|
37
|
-
trigger: DetectorTrigger = DetectorTrigger.internal,
|
|
38
|
-
exposure: Optional[float] = None,
|
|
39
|
-
) -> AsyncStatus:
|
|
36
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
40
37
|
await asyncio.gather(
|
|
41
|
-
self._drv.trigger_mode.set(TRIGGER_MODE[trigger]),
|
|
42
|
-
self._drv.exposure_mode.set(EXPOSE_OUT_MODE[trigger]),
|
|
43
|
-
self._drv.num_images.set(
|
|
38
|
+
self._drv.trigger_mode.set(TRIGGER_MODE[trigger_info.trigger]),
|
|
39
|
+
self._drv.exposure_mode.set(EXPOSE_OUT_MODE[trigger_info.trigger]),
|
|
40
|
+
self._drv.num_images.set(trigger_info.total_number_of_triggers),
|
|
44
41
|
self._drv.image_mode.set(adcore.ImageMode.multiple),
|
|
45
42
|
)
|
|
46
|
-
if
|
|
43
|
+
if trigger_info.livetime is not None and trigger_info.trigger not in [
|
|
47
44
|
DetectorTrigger.variable_gate,
|
|
48
45
|
DetectorTrigger.constant_gate,
|
|
49
46
|
]:
|
|
50
|
-
await self._drv.acquire_time.set(
|
|
51
|
-
if trigger != DetectorTrigger.internal:
|
|
47
|
+
await self._drv.acquire_time.set(trigger_info.livetime)
|
|
48
|
+
if trigger_info.trigger != DetectorTrigger.internal:
|
|
52
49
|
self._drv.trigger_source.set(VimbaTriggerSource.line1)
|
|
53
50
|
else:
|
|
54
51
|
self._drv.trigger_source.set(VimbaTriggerSource.freerun)
|
|
55
|
-
|
|
52
|
+
|
|
53
|
+
async def arm(self):
|
|
54
|
+
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
55
|
+
self._drv
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def wait_for_idle(self):
|
|
59
|
+
if self._arm_status:
|
|
60
|
+
await self._arm_status
|
|
56
61
|
|
|
57
62
|
async def disarm(self):
|
|
58
63
|
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
ophyd_async/epics/demo/_mover.py
CHANGED
|
@@ -4,10 +4,10 @@ import numpy as np
|
|
|
4
4
|
from bluesky.protocols import Movable, Stoppable
|
|
5
5
|
|
|
6
6
|
from ophyd_async.core import (
|
|
7
|
+
CALCULATE_TIMEOUT,
|
|
7
8
|
DEFAULT_TIMEOUT,
|
|
8
9
|
AsyncStatus,
|
|
9
10
|
CalculatableTimeout,
|
|
10
|
-
CalculateTimeout,
|
|
11
11
|
ConfigSignal,
|
|
12
12
|
Device,
|
|
13
13
|
HintedSignal,
|
|
@@ -44,9 +44,8 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
44
44
|
self.readback.set_name(name)
|
|
45
45
|
|
|
46
46
|
@WatchableAsyncStatus.wrap
|
|
47
|
-
async def set(
|
|
48
|
-
|
|
49
|
-
):
|
|
47
|
+
async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEOUT):
|
|
48
|
+
new_position = value
|
|
50
49
|
self._set_success = True
|
|
51
50
|
old_position, units, precision, velocity = await asyncio.gather(
|
|
52
51
|
self.setpoint.get_value(),
|
|
@@ -54,7 +53,7 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
54
53
|
self.precision.get_value(),
|
|
55
54
|
self.velocity.get_value(),
|
|
56
55
|
)
|
|
57
|
-
if timeout
|
|
56
|
+
if timeout == CALCULATE_TIMEOUT:
|
|
58
57
|
assert velocity > 0, "Mover has zero velocity"
|
|
59
58
|
timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
|
|
60
59
|
# Make an Event that will be set on completion, and a Status that will
|
ophyd_async/epics/demo/sensor.db
CHANGED
|
@@ -38,6 +38,6 @@ class EigerDetector(StandardDetector):
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
@AsyncStatus.wrap
|
|
41
|
-
async def prepare(self, value: EigerTriggerInfo) -> None:
|
|
41
|
+
async def prepare(self, value: EigerTriggerInfo) -> None: # type: ignore
|
|
42
42
|
await self._controller.set_energy(value.energy_ev)
|
|
43
43
|
await super().prepare(value)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from ophyd_async.core import (
|
|
5
4
|
DEFAULT_TIMEOUT,
|
|
6
|
-
|
|
7
|
-
DetectorControl,
|
|
5
|
+
DetectorController,
|
|
8
6
|
DetectorTrigger,
|
|
9
7
|
set_and_wait_for_other_value,
|
|
10
8
|
)
|
|
9
|
+
from ophyd_async.core._detector import TriggerInfo
|
|
11
10
|
|
|
12
11
|
from ._eiger_io import EigerDriverIO, EigerTriggerMode
|
|
13
12
|
|
|
@@ -19,14 +18,14 @@ EIGER_TRIGGER_MODE_MAP = {
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
class EigerController(
|
|
21
|
+
class EigerController(DetectorController):
|
|
23
22
|
def __init__(
|
|
24
23
|
self,
|
|
25
24
|
driver: EigerDriverIO,
|
|
26
25
|
) -> None:
|
|
27
26
|
self._drv = driver
|
|
28
27
|
|
|
29
|
-
def get_deadtime(self, exposure: float) -> float:
|
|
28
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
30
29
|
# See https://media.dectris.com/filer_public/30/14/3014704e-5f3b-43ba-8ccf-8ef720e60d2a/240202_usermanual_eiger2.pdf
|
|
31
30
|
return 0.0001
|
|
32
31
|
|
|
@@ -37,30 +36,31 @@ class EigerController(DetectorControl):
|
|
|
37
36
|
if abs(current_energy - energy) > tolerance:
|
|
38
37
|
await self._drv.photon_energy.set(energy)
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
async def arm(
|
|
42
|
-
self,
|
|
43
|
-
num: int,
|
|
44
|
-
trigger: DetectorTrigger = DetectorTrigger.internal,
|
|
45
|
-
exposure: Optional[float] = None,
|
|
46
|
-
):
|
|
39
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
47
40
|
coros = [
|
|
48
|
-
self._drv.trigger_mode.set(
|
|
49
|
-
|
|
41
|
+
self._drv.trigger_mode.set(
|
|
42
|
+
EIGER_TRIGGER_MODE_MAP[trigger_info.trigger].value
|
|
43
|
+
),
|
|
44
|
+
self._drv.num_images.set(trigger_info.total_number_of_triggers),
|
|
50
45
|
]
|
|
51
|
-
if
|
|
46
|
+
if trigger_info.livetime is not None:
|
|
52
47
|
coros.extend(
|
|
53
48
|
[
|
|
54
|
-
self._drv.acquire_time.set(
|
|
55
|
-
self._drv.acquire_period.set(
|
|
49
|
+
self._drv.acquire_time.set(trigger_info.livetime),
|
|
50
|
+
self._drv.acquire_period.set(trigger_info.livetime),
|
|
56
51
|
]
|
|
57
52
|
)
|
|
58
53
|
await asyncio.gather(*coros)
|
|
59
54
|
|
|
55
|
+
async def arm(self):
|
|
60
56
|
# TODO: Detector state should be an enum see https://github.com/DiamondLightSource/eiger-fastcs/issues/43
|
|
61
|
-
|
|
57
|
+
self._arm_status = set_and_wait_for_other_value(
|
|
62
58
|
self._drv.arm, 1, self._drv.state, "ready", timeout=DEFAULT_TIMEOUT
|
|
63
59
|
)
|
|
64
60
|
|
|
61
|
+
async def wait_for_idle(self):
|
|
62
|
+
if self._arm_status:
|
|
63
|
+
await self._arm_status
|
|
64
|
+
|
|
65
65
|
async def disarm(self):
|
|
66
66
|
await self._drv.disarm.set(1)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
|
2
3
|
from enum import Enum
|
|
3
|
-
from typing import AsyncGenerator, AsyncIterator, Dict
|
|
4
4
|
|
|
5
5
|
from bluesky.protocols import StreamAsset
|
|
6
|
-
from event_model
|
|
6
|
+
from event_model import DataKey
|
|
7
7
|
|
|
8
8
|
from ophyd_async.core import (
|
|
9
9
|
DEFAULT_TIMEOUT,
|
|
@@ -77,7 +77,7 @@ class OdinWriter(DetectorWriter):
|
|
|
77
77
|
self._name_provider = name_provider
|
|
78
78
|
super().__init__()
|
|
79
79
|
|
|
80
|
-
async def open(self, multiplier: int = 1) ->
|
|
80
|
+
async def open(self, multiplier: int = 1) -> dict[str, DataKey]:
|
|
81
81
|
info = self._path_provider(device_name=self._name_provider())
|
|
82
82
|
|
|
83
83
|
await asyncio.gather(
|
|
@@ -93,7 +93,7 @@ class OdinWriter(DetectorWriter):
|
|
|
93
93
|
|
|
94
94
|
return await self._describe()
|
|
95
95
|
|
|
96
|
-
async def _describe(self) ->
|
|
96
|
+
async def _describe(self) -> dict[str, DataKey]:
|
|
97
97
|
data_shape = await asyncio.gather(
|
|
98
98
|
self._drv.image_height.get_value(), self._drv.image_width.get_value()
|
|
99
99
|
)
|
|
@@ -103,7 +103,8 @@ class OdinWriter(DetectorWriter):
|
|
|
103
103
|
source=self._drv.file_name.source,
|
|
104
104
|
shape=data_shape,
|
|
105
105
|
dtype="array",
|
|
106
|
-
|
|
106
|
+
# TODO: Use correct type based on eiger https://github.com/bluesky/ophyd-async/issues/529
|
|
107
|
+
dtype_numpy="<u2", # type: ignore
|
|
107
108
|
external="STREAM:",
|
|
108
109
|
)
|
|
109
110
|
}
|
ophyd_async/epics/motor.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from bluesky.protocols import (
|
|
5
4
|
Flyable,
|
|
@@ -11,10 +10,10 @@ from bluesky.protocols import (
|
|
|
11
10
|
from pydantic import BaseModel, Field
|
|
12
11
|
|
|
13
12
|
from ophyd_async.core import (
|
|
13
|
+
CALCULATE_TIMEOUT,
|
|
14
14
|
DEFAULT_TIMEOUT,
|
|
15
15
|
AsyncStatus,
|
|
16
16
|
CalculatableTimeout,
|
|
17
|
-
CalculateTimeout,
|
|
18
17
|
ConfigSignal,
|
|
19
18
|
HintedSignal,
|
|
20
19
|
StandardReadable,
|
|
@@ -54,7 +53,7 @@ class FlyMotorInfo(BaseModel):
|
|
|
54
53
|
|
|
55
54
|
#: Maximum time for the complete motor move, including run up and run down.
|
|
56
55
|
#: Defaults to `time_for_move` + run up and run down times + 10s.
|
|
57
|
-
timeout: CalculatableTimeout = Field(frozen=True, default=
|
|
56
|
+
timeout: CalculatableTimeout = Field(frozen=True, default=CALCULATE_TIMEOUT)
|
|
58
57
|
|
|
59
58
|
|
|
60
59
|
class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
@@ -83,13 +82,13 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
83
82
|
self._set_success = True
|
|
84
83
|
|
|
85
84
|
# end_position of a fly move, with run_up_distance added on.
|
|
86
|
-
self._fly_completed_position:
|
|
85
|
+
self._fly_completed_position: float | None = None
|
|
87
86
|
|
|
88
87
|
# Set on kickoff(), complete when motor reaches self._fly_completed_position
|
|
89
|
-
self._fly_status:
|
|
88
|
+
self._fly_status: WatchableAsyncStatus | None = None
|
|
90
89
|
|
|
91
90
|
# Set during prepare
|
|
92
|
-
self._fly_timeout:
|
|
91
|
+
self._fly_timeout: CalculatableTimeout | None = CALCULATE_TIMEOUT
|
|
93
92
|
|
|
94
93
|
super().__init__(name=name)
|
|
95
94
|
|
|
@@ -138,9 +137,8 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
138
137
|
return self._fly_status
|
|
139
138
|
|
|
140
139
|
@WatchableAsyncStatus.wrap
|
|
141
|
-
async def set(
|
|
142
|
-
|
|
143
|
-
):
|
|
140
|
+
async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEOUT):
|
|
141
|
+
new_position = value
|
|
144
142
|
self._set_success = True
|
|
145
143
|
(
|
|
146
144
|
old_position,
|
|
@@ -155,7 +153,7 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
155
153
|
self.velocity.get_value(),
|
|
156
154
|
self.acceleration_time.get_value(),
|
|
157
155
|
)
|
|
158
|
-
if timeout is
|
|
156
|
+
if timeout is CALCULATE_TIMEOUT:
|
|
159
157
|
assert velocity > 0, "Motor has zero velocity"
|
|
160
158
|
timeout = (
|
|
161
159
|
abs(new_position - old_position) / velocity
|
ophyd_async/epics/pvi/_pvi.py
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import types
|
|
3
|
+
from collections.abc import Callable
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from inspect import isclass
|
|
4
6
|
from typing import (
|
|
5
7
|
Any,
|
|
6
|
-
Callable,
|
|
7
|
-
Dict,
|
|
8
|
-
FrozenSet,
|
|
9
8
|
Literal,
|
|
10
|
-
Optional,
|
|
11
|
-
Tuple,
|
|
12
|
-
Type,
|
|
13
9
|
Union,
|
|
14
10
|
get_args,
|
|
15
11
|
get_origin,
|
|
@@ -32,23 +28,24 @@ from ophyd_async.epics.signal import (
|
|
|
32
28
|
epics_signal_x,
|
|
33
29
|
)
|
|
34
30
|
|
|
35
|
-
Access =
|
|
36
|
-
|
|
31
|
+
Access = frozenset[
|
|
32
|
+
Literal["r"] | Literal["w"] | Literal["rw"] | Literal["x"] | Literal["d"]
|
|
37
33
|
]
|
|
38
34
|
|
|
39
35
|
|
|
40
|
-
def _strip_number_from_string(string: str) ->
|
|
36
|
+
def _strip_number_from_string(string: str) -> tuple[str, int | None]:
|
|
41
37
|
match = re.match(r"(.*?)(\d*)$", string)
|
|
42
38
|
assert match
|
|
43
39
|
|
|
44
40
|
name = match.group(1)
|
|
45
41
|
number = match.group(2) or None
|
|
46
|
-
if number:
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
if number is None:
|
|
43
|
+
return name, None
|
|
44
|
+
else:
|
|
45
|
+
return name, int(number)
|
|
49
46
|
|
|
50
47
|
|
|
51
|
-
def _split_subscript(tp: T) ->
|
|
48
|
+
def _split_subscript(tp: T) -> tuple[Any, tuple[Any]] | tuple[T, None]:
|
|
52
49
|
"""Split a subscripted type into the its origin and args.
|
|
53
50
|
|
|
54
51
|
If `tp` is not a subscripted type, then just return the type and None as args.
|
|
@@ -60,8 +57,8 @@ def _split_subscript(tp: T) -> Union[Tuple[Any, Tuple[Any]], Tuple[T, None]]:
|
|
|
60
57
|
return tp, None
|
|
61
58
|
|
|
62
59
|
|
|
63
|
-
def _strip_union(field:
|
|
64
|
-
if get_origin(field)
|
|
60
|
+
def _strip_union(field: T | T) -> tuple[T, bool]:
|
|
61
|
+
if get_origin(field) in [Union, types.UnionType]:
|
|
65
62
|
args = get_args(field)
|
|
66
63
|
is_optional = type(None) in args
|
|
67
64
|
for arg in args:
|
|
@@ -70,7 +67,7 @@ def _strip_union(field: Union[Union[T], T]) -> Tuple[T, bool]:
|
|
|
70
67
|
return field, False
|
|
71
68
|
|
|
72
69
|
|
|
73
|
-
def _strip_device_vector(field:
|
|
70
|
+
def _strip_device_vector(field: type[Device]) -> tuple[bool, type[Device]]:
|
|
74
71
|
if get_origin(field) is DeviceVector:
|
|
75
72
|
return True, get_args(field)[0]
|
|
76
73
|
return False, field
|
|
@@ -83,13 +80,13 @@ class _PVIEntry:
|
|
|
83
80
|
This could either be a signal or a sub-table.
|
|
84
81
|
"""
|
|
85
82
|
|
|
86
|
-
sub_entries:
|
|
87
|
-
pvi_pv:
|
|
88
|
-
device:
|
|
89
|
-
common_device_type:
|
|
83
|
+
sub_entries: dict[str, Union[dict[int, "_PVIEntry"], "_PVIEntry"]]
|
|
84
|
+
pvi_pv: str | None = None
|
|
85
|
+
device: Device | None = None
|
|
86
|
+
common_device_type: type[Device] | None = None
|
|
90
87
|
|
|
91
88
|
|
|
92
|
-
def _verify_common_blocks(entry: _PVIEntry, common_device:
|
|
89
|
+
def _verify_common_blocks(entry: _PVIEntry, common_device: type[Device]):
|
|
93
90
|
if not entry.sub_entries:
|
|
94
91
|
return
|
|
95
92
|
common_sub_devices = get_type_hints(common_device)
|
|
@@ -107,12 +104,12 @@ def _verify_common_blocks(entry: _PVIEntry, common_device: Type[Device]):
|
|
|
107
104
|
_verify_common_blocks(sub_sub_entry, sub_device) # type: ignore
|
|
108
105
|
else:
|
|
109
106
|
_verify_common_blocks(
|
|
110
|
-
entry.sub_entries[sub_name],
|
|
107
|
+
entry.sub_entries[sub_name], # type: ignore
|
|
111
108
|
sub_device, # type: ignore
|
|
112
109
|
)
|
|
113
110
|
|
|
114
111
|
|
|
115
|
-
_pvi_mapping:
|
|
112
|
+
_pvi_mapping: dict[frozenset[str], Callable[..., Signal]] = {
|
|
116
113
|
frozenset({"r", "w"}): lambda dtype, read_pv, write_pv: epics_signal_rw(
|
|
117
114
|
dtype, "pva://" + read_pv, "pva://" + write_pv
|
|
118
115
|
),
|
|
@@ -129,8 +126,8 @@ _pvi_mapping: Dict[FrozenSet[str], Callable[..., Signal]] = {
|
|
|
129
126
|
|
|
130
127
|
def _parse_type(
|
|
131
128
|
is_pvi_table: bool,
|
|
132
|
-
number_suffix:
|
|
133
|
-
common_device_type:
|
|
129
|
+
number_suffix: int | None,
|
|
130
|
+
common_device_type: type[Device] | None,
|
|
134
131
|
):
|
|
135
132
|
if common_device_type:
|
|
136
133
|
# pre-defined type
|
|
@@ -159,7 +156,7 @@ def _parse_type(
|
|
|
159
156
|
return is_device_vector, is_signal, signal_dtype, device_cls
|
|
160
157
|
|
|
161
158
|
|
|
162
|
-
def _mock_common_blocks(device: Device, stripped_type:
|
|
159
|
+
def _mock_common_blocks(device: Device, stripped_type: type | None = None):
|
|
163
160
|
device_t = stripped_type or type(device)
|
|
164
161
|
sub_devices = (
|
|
165
162
|
(field, field_type)
|
|
@@ -173,11 +170,10 @@ def _mock_common_blocks(device: Device, stripped_type: Optional[Type] = None):
|
|
|
173
170
|
device_cls, device_args = _split_subscript(device_cls)
|
|
174
171
|
assert issubclass(device_cls, Device)
|
|
175
172
|
|
|
176
|
-
is_signal = issubclass(device_cls, Signal)
|
|
177
173
|
signal_dtype = device_args[0] if device_args is not None else None
|
|
178
174
|
|
|
179
175
|
if is_device_vector:
|
|
180
|
-
if
|
|
176
|
+
if issubclass(device_cls, Signal):
|
|
181
177
|
sub_device_1 = device_cls(SoftSignalBackend(signal_dtype))
|
|
182
178
|
sub_device_2 = device_cls(SoftSignalBackend(signal_dtype))
|
|
183
179
|
sub_device = DeviceVector({1: sub_device_1, 2: sub_device_2})
|
|
@@ -198,7 +194,7 @@ def _mock_common_blocks(device: Device, stripped_type: Optional[Type] = None):
|
|
|
198
194
|
for value in sub_device.values():
|
|
199
195
|
value.parent = sub_device
|
|
200
196
|
else:
|
|
201
|
-
if
|
|
197
|
+
if issubclass(device_cls, Signal):
|
|
202
198
|
sub_device = device_cls(SoftSignalBackend(signal_dtype))
|
|
203
199
|
else:
|
|
204
200
|
sub_device = getattr(device, device_name, device_cls())
|
|
@@ -271,7 +267,8 @@ def _set_device_attributes(entry: _PVIEntry):
|
|
|
271
267
|
# Set the device vector entry to have the device vector as a parent
|
|
272
268
|
device_vector_sub_entry.device.parent = sub_device # type: ignore
|
|
273
269
|
else:
|
|
274
|
-
sub_device = sub_entry.device
|
|
270
|
+
sub_device = sub_entry.device
|
|
271
|
+
assert sub_device, f"Device of {sub_entry} is None"
|
|
275
272
|
if sub_entry.pvi_pv:
|
|
276
273
|
_set_device_attributes(sub_entry)
|
|
277
274
|
|
|
@@ -308,8 +305,8 @@ async def fill_pvi_entries(
|
|
|
308
305
|
|
|
309
306
|
def create_children_from_annotations(
|
|
310
307
|
device: Device,
|
|
311
|
-
included_optional_fields:
|
|
312
|
-
device_vectors:
|
|
308
|
+
included_optional_fields: tuple[str, ...] = (),
|
|
309
|
+
device_vectors: dict[str, int] | None = None,
|
|
313
310
|
):
|
|
314
311
|
"""For intializing blocks at __init__ of ``device``."""
|
|
315
312
|
for name, device_type in get_type_hints(type(device)).items():
|
|
@@ -328,7 +325,7 @@ def create_children_from_annotations(
|
|
|
328
325
|
|
|
329
326
|
if is_device_vector:
|
|
330
327
|
n_device_vector = DeviceVector(
|
|
331
|
-
{i: device_type() for i in range(1, device_vectors[name] + 1)}
|
|
328
|
+
{i: device_type() for i in range(1, device_vectors[name] + 1)} # type: ignore
|
|
332
329
|
)
|
|
333
330
|
setattr(device, name, n_device_vector)
|
|
334
331
|
for sub_device in n_device_vector.values():
|