ophyd-async 0.9.0a1__py3-none-any.whl → 0.9.0a2__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 +13 -20
- ophyd_async/core/_detector.py +61 -37
- ophyd_async/core/_device.py +102 -80
- ophyd_async/core/_device_filler.py +17 -8
- ophyd_async/core/_flyer.py +2 -2
- ophyd_async/core/_readable.py +30 -23
- ophyd_async/core/_settings.py +104 -0
- ophyd_async/core/_signal.py +55 -17
- 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 +5 -3
- 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 +13 -22
- ophyd_async/epics/adcore/__init__.py +15 -8
- ophyd_async/epics/adcore/_core_detector.py +41 -0
- ophyd_async/epics/adcore/_core_io.py +35 -10
- ophyd_async/epics/adcore/_core_logic.py +98 -86
- ophyd_async/epics/adcore/_core_writer.py +219 -0
- ophyd_async/epics/adcore/_hdf_writer.py +38 -62
- ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
- ophyd_async/epics/adcore/_single_trigger.py +4 -3
- ophyd_async/epics/adcore/_tiff_writer.py +26 -0
- ophyd_async/epics/adcore/_utils.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +29 -24
- ophyd_async/epics/adkinetix/_kinetix_controller.py +9 -21
- ophyd_async/epics/adpilatus/__init__.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus.py +27 -39
- ophyd_async/epics/adpilatus/_pilatus_controller.py +44 -22
- 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 +10 -24
- ophyd_async/epics/core/_aioca.py +31 -14
- ophyd_async/epics/core/_p4p.py +40 -16
- ophyd_async/epics/core/_util.py +1 -1
- ophyd_async/epics/motor.py +18 -10
- ophyd_async/epics/sim/_ioc.py +29 -0
- ophyd_async/epics/{demo → sim}/_mover.py +10 -4
- ophyd_async/epics/testing/__init__.py +14 -14
- ophyd_async/epics/testing/_example_ioc.py +48 -65
- ophyd_async/epics/testing/_utils.py +17 -45
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/fastcs/panda/__init__.py +0 -2
- ophyd_async/fastcs/panda/_control.py +7 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +3 -1
- ophyd_async/fastcs/panda/_table.py +4 -1
- ophyd_async/plan_stubs/__init__.py +14 -0
- ophyd_async/plan_stubs/_ensure_connected.py +11 -17
- ophyd_async/plan_stubs/_fly.py +1 -1
- 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/tango/core/_signal.py +3 -1
- ophyd_async/tango/core/_tango_transport.py +12 -14
- ophyd_async/tango/{demo → sim}/_mover.py +5 -2
- ophyd_async/testing/__init__.py +19 -0
- ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
- ophyd_async/testing/_assert.py +88 -40
- ophyd_async/testing/_mock_signal_utils.py +3 -3
- ophyd_async/testing/_one_of_everything.py +126 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/METADATA +2 -2
- ophyd_async-0.9.0a2.dist-info/RECORD +129 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.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.9.0a1.dist-info/RECORD +0 -119
- ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
- /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
- /ophyd_async/epics/{demo → sim}/_sensor.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/sim/{demo/_sim_motor.py → _sim_motor.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.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/LICENSE +0 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/top_level.txt +0 -0
|
@@ -1,58 +1,46 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from ophyd_async.core import PathProvider
|
|
4
|
+
from ophyd_async.core._signal import SignalR
|
|
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
|
|
4
9
|
|
|
5
|
-
from
|
|
6
|
-
from ophyd_async.epics import adcore
|
|
7
|
-
|
|
8
|
-
from ._pilatus_controller import PilatusController
|
|
10
|
+
from ._pilatus_controller import PilatusController, PilatusReadoutTime
|
|
9
11
|
from ._pilatus_io import PilatusDriverIO
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
#: The required minimum time difference between ExpPeriod and ExpTime
|
|
14
|
-
#: (readout time) is 2.28 ms
|
|
15
|
-
#: We provide an option to override for newer Pilatus models
|
|
16
|
-
class PilatusReadoutTime(float, Enum):
|
|
17
|
-
"""Pilatus readout time per model in ms"""
|
|
18
|
-
|
|
19
|
-
# Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
|
|
20
|
-
PILATUS2 = 2.28e-3
|
|
21
|
-
|
|
22
|
-
# Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
|
|
23
|
-
PILATUS3 = 0.95e-3
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class PilatusDetector(StandardDetector):
|
|
14
|
+
class PilatusDetector(AreaDetector[PilatusController]):
|
|
27
15
|
"""A Pilatus StandardDetector writing HDF files"""
|
|
28
16
|
|
|
29
|
-
_controller: PilatusController
|
|
30
|
-
_writer: adcore.ADHDFWriter
|
|
31
|
-
|
|
32
17
|
def __init__(
|
|
33
18
|
self,
|
|
34
19
|
prefix: str,
|
|
35
20
|
path_provider: PathProvider,
|
|
36
21
|
readout_time: PilatusReadoutTime = PilatusReadoutTime.PILATUS3,
|
|
37
22
|
drv_suffix: str = "cam1:",
|
|
38
|
-
|
|
23
|
+
writer_cls: type[ADWriter] = ADHDFWriter,
|
|
24
|
+
fileio_suffix: str | None = None,
|
|
39
25
|
name: str = "",
|
|
26
|
+
plugins: dict[str, NDPluginBaseIO] | None = None,
|
|
27
|
+
config_sigs: Sequence[SignalR] = (),
|
|
40
28
|
):
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
driver = PilatusDriverIO(prefix + drv_suffix)
|
|
30
|
+
controller = PilatusController(driver)
|
|
31
|
+
|
|
32
|
+
writer = writer_cls.with_io(
|
|
33
|
+
prefix,
|
|
34
|
+
path_provider,
|
|
35
|
+
dataset_source=driver,
|
|
36
|
+
fileio_suffix=fileio_suffix,
|
|
37
|
+
plugins=plugins,
|
|
38
|
+
)
|
|
43
39
|
|
|
44
40
|
super().__init__(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
path_provider,
|
|
49
|
-
lambda: self.name,
|
|
50
|
-
adcore.ADBaseDatasetDescriber(self.drv),
|
|
51
|
-
),
|
|
52
|
-
config_sigs=(self.drv.acquire_time,),
|
|
41
|
+
controller=controller,
|
|
42
|
+
writer=writer,
|
|
43
|
+
plugins=plugins,
|
|
53
44
|
name=name,
|
|
45
|
+
config_sigs=config_sigs,
|
|
54
46
|
)
|
|
55
|
-
|
|
56
|
-
@property
|
|
57
|
-
def hints(self) -> Hints:
|
|
58
|
-
return self._writer.hints
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import TypeVar, get_args
|
|
2
4
|
|
|
3
5
|
from ophyd_async.core import (
|
|
4
6
|
DEFAULT_TIMEOUT,
|
|
5
|
-
AsyncStatus,
|
|
6
|
-
DetectorController,
|
|
7
7
|
DetectorTrigger,
|
|
8
8
|
TriggerInfo,
|
|
9
9
|
wait_for_value,
|
|
@@ -13,7 +13,24 @@ from ophyd_async.epics import adcore
|
|
|
13
13
|
from ._pilatus_io import PilatusDriverIO, PilatusTriggerMode
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
#: Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
|
|
17
|
+
#: The required minimum time difference between ExpPeriod and ExpTime
|
|
18
|
+
#: (readout time) is 2.28 ms
|
|
19
|
+
#: We provide an option to override for newer Pilatus models
|
|
20
|
+
class PilatusReadoutTime(float, Enum):
|
|
21
|
+
"""Pilatus readout time per model in ms"""
|
|
22
|
+
|
|
23
|
+
# Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
|
|
24
|
+
PILATUS2 = 2.28e-3
|
|
25
|
+
|
|
26
|
+
# Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
|
|
27
|
+
PILATUS3 = 0.95e-3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
PilatusControllerT = TypeVar("PilatusControllerT", bound="PilatusController")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PilatusController(adcore.ADBaseController[PilatusDriverIO]):
|
|
17
34
|
_supported_trigger_types = {
|
|
18
35
|
DetectorTrigger.INTERNAL: PilatusTriggerMode.INTERNAL,
|
|
19
36
|
DetectorTrigger.CONSTANT_GATE: PilatusTriggerMode.EXT_ENABLE,
|
|
@@ -23,48 +40,56 @@ class PilatusController(DetectorController):
|
|
|
23
40
|
def __init__(
|
|
24
41
|
self,
|
|
25
42
|
driver: PilatusDriverIO,
|
|
26
|
-
|
|
43
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
44
|
+
readout_time: float = PilatusReadoutTime.PILATUS3,
|
|
27
45
|
) -> None:
|
|
28
|
-
|
|
46
|
+
super().__init__(driver, good_states=good_states)
|
|
29
47
|
self._readout_time = readout_time
|
|
30
|
-
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def controller_and_drv(
|
|
51
|
+
cls: type[PilatusControllerT],
|
|
52
|
+
prefix: str,
|
|
53
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
54
|
+
name: str = "",
|
|
55
|
+
readout_time: float = PilatusReadoutTime.PILATUS3,
|
|
56
|
+
) -> tuple[PilatusControllerT, PilatusDriverIO]:
|
|
57
|
+
driver_cls = get_args(cls.__orig_bases__[0])[0] # type: ignore
|
|
58
|
+
driver = driver_cls(prefix, name=name)
|
|
59
|
+
controller = cls(driver, good_states=good_states, readout_time=readout_time)
|
|
60
|
+
return controller, driver
|
|
31
61
|
|
|
32
62
|
def get_deadtime(self, exposure: float | None) -> float:
|
|
33
63
|
return self._readout_time
|
|
34
64
|
|
|
35
65
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
36
66
|
if trigger_info.livetime is not None:
|
|
37
|
-
await
|
|
38
|
-
|
|
67
|
+
await self.set_exposure_time_and_acquire_period_if_supplied(
|
|
68
|
+
trigger_info.livetime
|
|
39
69
|
)
|
|
40
70
|
await asyncio.gather(
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
71
|
+
self.driver.trigger_mode.set(self._get_trigger_mode(trigger_info.trigger)),
|
|
72
|
+
self.driver.num_images.set(
|
|
43
73
|
999_999
|
|
44
74
|
if trigger_info.total_number_of_triggers == 0
|
|
45
75
|
else trigger_info.total_number_of_triggers
|
|
46
76
|
),
|
|
47
|
-
self.
|
|
77
|
+
self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
48
78
|
)
|
|
49
79
|
|
|
50
80
|
async def arm(self):
|
|
51
81
|
# Standard arm the detector and wait for the acquire PV to be True
|
|
52
|
-
self._arm_status = await
|
|
53
|
-
|
|
54
|
-
)
|
|
82
|
+
self._arm_status = await self.start_acquiring_driver_and_ensure_status()
|
|
83
|
+
|
|
55
84
|
# The pilatus has an additional PV that goes True when the camserver
|
|
56
85
|
# is actually ready. Should wait for that too or we risk dropping
|
|
57
86
|
# a frame
|
|
58
87
|
await wait_for_value(
|
|
59
|
-
self.
|
|
88
|
+
self.driver.armed,
|
|
60
89
|
True,
|
|
61
90
|
timeout=DEFAULT_TIMEOUT,
|
|
62
91
|
)
|
|
63
92
|
|
|
64
|
-
async def wait_for_idle(self):
|
|
65
|
-
if self._arm_status:
|
|
66
|
-
await self._arm_status
|
|
67
|
-
|
|
68
93
|
@classmethod
|
|
69
94
|
def _get_trigger_mode(cls, trigger: DetectorTrigger) -> PilatusTriggerMode:
|
|
70
95
|
if trigger not in cls._supported_trigger_types.keys():
|
|
@@ -74,6 +99,3 @@ class PilatusController(DetectorController):
|
|
|
74
99
|
f"use {trigger}"
|
|
75
100
|
)
|
|
76
101
|
return cls._supported_trigger_types[trigger]
|
|
77
|
-
|
|
78
|
-
async def disarm(self):
|
|
79
|
-
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
@@ -1,35 +1,51 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider, SignalR
|
|
3
|
+
from ophyd_async.core import PathProvider, SignalR
|
|
4
4
|
from ophyd_async.epics import adcore
|
|
5
5
|
|
|
6
|
-
from ._sim_controller import SimController
|
|
7
6
|
|
|
7
|
+
class SimDriverIO(adcore.ADBaseIO): ...
|
|
8
8
|
|
|
9
|
-
class SimDetector(StandardDetector):
|
|
10
|
-
_controller: SimController
|
|
11
|
-
_writer: adcore.ADHDFWriter
|
|
12
9
|
|
|
10
|
+
class SimController(adcore.ADBaseController[SimDriverIO]):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
driver: SimDriverIO,
|
|
14
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
15
|
+
) -> None:
|
|
16
|
+
super().__init__(driver, good_states=good_states)
|
|
17
|
+
|
|
18
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
19
|
+
return 0.001
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SimDetector(adcore.AreaDetector[SimController]):
|
|
13
23
|
def __init__(
|
|
14
24
|
self,
|
|
15
25
|
prefix: str,
|
|
16
26
|
path_provider: PathProvider,
|
|
17
27
|
drv_suffix="cam1:",
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter,
|
|
29
|
+
fileio_suffix: str | None = None,
|
|
30
|
+
name="",
|
|
20
31
|
config_sigs: Sequence[SignalR] = (),
|
|
32
|
+
plugins: dict[str, adcore.NDPluginBaseIO] | None = None,
|
|
21
33
|
):
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
driver = SimDriverIO(prefix + drv_suffix)
|
|
35
|
+
controller = SimController(driver)
|
|
36
|
+
|
|
37
|
+
writer = writer_cls.with_io(
|
|
38
|
+
prefix,
|
|
39
|
+
path_provider,
|
|
40
|
+
dataset_source=driver,
|
|
41
|
+
fileio_suffix=fileio_suffix,
|
|
42
|
+
plugins=plugins,
|
|
43
|
+
)
|
|
24
44
|
|
|
25
45
|
super().__init__(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
path_provider,
|
|
30
|
-
lambda: self.name,
|
|
31
|
-
adcore.ADBaseDatasetDescriber(self.drv),
|
|
32
|
-
),
|
|
33
|
-
config_sigs=(self.drv.acquire_period, self.drv.acquire_time, *config_sigs),
|
|
46
|
+
controller=controller,
|
|
47
|
+
writer=writer,
|
|
48
|
+
plugins=plugins,
|
|
34
49
|
name=name,
|
|
50
|
+
config_sigs=config_sigs,
|
|
35
51
|
)
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider,
|
|
3
|
+
from ophyd_async.core import PathProvider, SignalR
|
|
4
4
|
from ophyd_async.epics import adcore
|
|
5
5
|
|
|
6
6
|
from ._vimba_controller import VimbaController
|
|
7
7
|
from ._vimba_io import VimbaDriverIO
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class VimbaDetector(
|
|
10
|
+
class VimbaDetector(adcore.AreaDetector[VimbaController]):
|
|
11
11
|
"""
|
|
12
12
|
Ophyd-async implementation of an ADVimba Detector.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
_controller: VimbaController
|
|
16
|
-
_writer: adcore.ADHDFWriter
|
|
17
|
-
|
|
18
15
|
def __init__(
|
|
19
16
|
self,
|
|
20
17
|
prefix: str,
|
|
21
18
|
path_provider: PathProvider,
|
|
22
|
-
drv_suffix="cam1:",
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
drv_suffix: str = "cam1:",
|
|
20
|
+
writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter,
|
|
21
|
+
fileio_suffix: str | None = None,
|
|
22
|
+
name: str = "",
|
|
23
|
+
plugins: dict[str, adcore.NDPluginBaseIO] | None = None,
|
|
24
|
+
config_sigs: Sequence[SignalR] = (),
|
|
25
25
|
):
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
driver = VimbaDriverIO(prefix + drv_suffix)
|
|
27
|
+
controller = VimbaController(driver)
|
|
28
|
+
|
|
29
|
+
writer = writer_cls.with_io(
|
|
30
|
+
prefix,
|
|
31
|
+
path_provider,
|
|
32
|
+
dataset_source=driver,
|
|
33
|
+
fileio_suffix=fileio_suffix,
|
|
34
|
+
plugins=plugins,
|
|
35
|
+
)
|
|
28
36
|
|
|
29
37
|
super().__init__(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
path_provider,
|
|
34
|
-
lambda: self.name,
|
|
35
|
-
adcore.ADBaseDatasetDescriber(self.drv),
|
|
36
|
-
),
|
|
37
|
-
config_sigs=(self.drv.acquire_time,),
|
|
38
|
+
controller=controller,
|
|
39
|
+
writer=writer,
|
|
40
|
+
plugins=plugins,
|
|
38
41
|
name=name,
|
|
42
|
+
config_sigs=config_sigs,
|
|
39
43
|
)
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
def hints(self) -> Hints:
|
|
43
|
-
return self._writer.hints
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import (
|
|
4
|
-
AsyncStatus,
|
|
5
|
-
DetectorController,
|
|
6
4
|
DetectorTrigger,
|
|
7
5
|
TriggerInfo,
|
|
8
6
|
)
|
|
@@ -25,42 +23,30 @@ EXPOSE_OUT_MODE = {
|
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
|
|
28
|
-
class VimbaController(
|
|
26
|
+
class VimbaController(adcore.ADBaseController[VimbaDriverIO]):
|
|
29
27
|
def __init__(
|
|
30
28
|
self,
|
|
31
29
|
driver: VimbaDriverIO,
|
|
30
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
32
31
|
) -> None:
|
|
33
|
-
|
|
34
|
-
self._arm_status: AsyncStatus | None = None
|
|
32
|
+
super().__init__(driver, good_states=good_states)
|
|
35
33
|
|
|
36
34
|
def get_deadtime(self, exposure: float | None) -> float:
|
|
37
35
|
return 0.001
|
|
38
36
|
|
|
39
37
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
40
38
|
await asyncio.gather(
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
39
|
+
self.driver.trigger_mode.set(TRIGGER_MODE[trigger_info.trigger]),
|
|
40
|
+
self.driver.exposure_mode.set(EXPOSE_OUT_MODE[trigger_info.trigger]),
|
|
41
|
+
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
42
|
+
self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
45
43
|
)
|
|
46
44
|
if trigger_info.livetime is not None and trigger_info.trigger not in [
|
|
47
45
|
DetectorTrigger.VARIABLE_GATE,
|
|
48
46
|
DetectorTrigger.CONSTANT_GATE,
|
|
49
47
|
]:
|
|
50
|
-
await self.
|
|
48
|
+
await self.driver.acquire_time.set(trigger_info.livetime)
|
|
51
49
|
if trigger_info.trigger != DetectorTrigger.INTERNAL:
|
|
52
|
-
self.
|
|
50
|
+
self.driver.trigger_source.set(VimbaTriggerSource.LINE1)
|
|
53
51
|
else:
|
|
54
|
-
self.
|
|
55
|
-
|
|
56
|
-
async def arm(self):
|
|
57
|
-
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
58
|
-
self._drv
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
async def wait_for_idle(self):
|
|
62
|
-
if self._arm_status:
|
|
63
|
-
await self._arm_status
|
|
64
|
-
|
|
65
|
-
async def disarm(self):
|
|
66
|
-
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
52
|
+
self.driver.trigger_source.set(VimbaTriggerSource.FREERUN)
|
ophyd_async/epics/core/_aioca.py
CHANGED
|
@@ -35,12 +35,14 @@ from ophyd_async.core import (
|
|
|
35
35
|
|
|
36
36
|
from ._util import EpicsSignalBackend, format_datatype, get_supported_values
|
|
37
37
|
|
|
38
|
+
logger = logging.getLogger("ophyd_async")
|
|
39
|
+
|
|
38
40
|
|
|
39
41
|
def _limits_from_augmented_value(value: AugmentedValue) -> Limits:
|
|
40
42
|
def get_limits(limit: str) -> LimitsRange | None:
|
|
41
43
|
low = getattr(value, f"lower_{limit}_limit", nan)
|
|
42
44
|
high = getattr(value, f"upper_{limit}_limit", nan)
|
|
43
|
-
if not (isnan(low) and isnan(high)):
|
|
45
|
+
if not (isnan(low) and isnan(high)) and not high == low == 0:
|
|
44
46
|
return LimitsRange(
|
|
45
47
|
low=None if isnan(low) else low,
|
|
46
48
|
high=None if isnan(high) else high,
|
|
@@ -59,14 +61,20 @@ def _limits_from_augmented_value(value: AugmentedValue) -> Limits:
|
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
def _metadata_from_augmented_value(
|
|
62
|
-
|
|
64
|
+
datatype: type[SignalDatatypeT] | None,
|
|
65
|
+
value: AugmentedValue,
|
|
66
|
+
metadata: SignalMetadata,
|
|
63
67
|
) -> SignalMetadata:
|
|
64
68
|
metadata = metadata.copy()
|
|
65
|
-
if hasattr(value, "units"):
|
|
69
|
+
if hasattr(value, "units") and datatype not in (str, bool):
|
|
66
70
|
metadata["units"] = value.units
|
|
67
|
-
if
|
|
71
|
+
if (
|
|
72
|
+
hasattr(value, "precision")
|
|
73
|
+
and not isnan(value.precision)
|
|
74
|
+
and datatype is not int
|
|
75
|
+
):
|
|
68
76
|
metadata["precision"] = value.precision
|
|
69
|
-
if limits := _limits_from_augmented_value(value):
|
|
77
|
+
if (limits := _limits_from_augmented_value(value)) and datatype is not bool:
|
|
70
78
|
metadata["limits"] = limits
|
|
71
79
|
return metadata
|
|
72
80
|
|
|
@@ -100,6 +108,11 @@ class DisconnectedCaConverter(CaConverter):
|
|
|
100
108
|
raise NotImplementedError("No PV has been set as connect() has not been called")
|
|
101
109
|
|
|
102
110
|
|
|
111
|
+
class CaIntConverter(CaConverter[int]):
|
|
112
|
+
def value(self, value: AugmentedValue) -> int:
|
|
113
|
+
return int(value) # type: ignore
|
|
114
|
+
|
|
115
|
+
|
|
103
116
|
class CaArrayConverter(CaConverter[np.ndarray]):
|
|
104
117
|
def value(self, value: AugmentedValue) -> np.ndarray:
|
|
105
118
|
# A less expensive conversion
|
|
@@ -202,7 +215,7 @@ def make_converter(
|
|
|
202
215
|
and get_unique({k: v.precision for k, v in values.items()}, "precision") == 0
|
|
203
216
|
):
|
|
204
217
|
# Allow int signals to represent float records when prec is 0
|
|
205
|
-
return
|
|
218
|
+
return CaIntConverter(int, pv_dbr)
|
|
206
219
|
elif datatype in (None, inferred_datatype):
|
|
207
220
|
# If datatype matches what we are given then allow it and use inferred converter
|
|
208
221
|
return converter_cls(inferred_datatype, pv_dbr)
|
|
@@ -247,7 +260,7 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
247
260
|
pv, format=FORMAT_CTRL, timeout=timeout
|
|
248
261
|
)
|
|
249
262
|
except CANothing as exc:
|
|
250
|
-
|
|
263
|
+
logger.debug(f"signal ca://{pv} timed out")
|
|
251
264
|
raise NotConnected(f"ca://{pv}") from exc
|
|
252
265
|
|
|
253
266
|
async def connect(self, timeout: float):
|
|
@@ -290,7 +303,9 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
290
303
|
|
|
291
304
|
async def get_datakey(self, source: str) -> DataKey:
|
|
292
305
|
value = await self._caget(self.read_pv, FORMAT_CTRL)
|
|
293
|
-
metadata = _metadata_from_augmented_value(
|
|
306
|
+
metadata = _metadata_from_augmented_value(
|
|
307
|
+
self.datatype, value, self.converter.metadata
|
|
308
|
+
)
|
|
294
309
|
return make_datakey(
|
|
295
310
|
self.converter.datatype, self.converter.value(value), source, metadata
|
|
296
311
|
)
|
|
@@ -308,16 +323,18 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
308
323
|
return self.converter.value(value)
|
|
309
324
|
|
|
310
325
|
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
|
|
326
|
+
if callback and self.subscription:
|
|
327
|
+
msg = "Cannot set a callback when one is already set"
|
|
328
|
+
raise RuntimeError(msg)
|
|
329
|
+
|
|
330
|
+
if self.subscription:
|
|
331
|
+
self.subscription.close()
|
|
332
|
+
self.subscription = None
|
|
333
|
+
|
|
311
334
|
if callback:
|
|
312
|
-
assert (
|
|
313
|
-
not self.subscription
|
|
314
|
-
), "Cannot set a callback when one is already set"
|
|
315
335
|
self.subscription = camonitor(
|
|
316
336
|
self.read_pv,
|
|
317
337
|
lambda v: callback(self._make_reading(v)),
|
|
318
338
|
datatype=self.converter.read_dbr,
|
|
319
339
|
format=FORMAT_TIME,
|
|
320
340
|
)
|
|
321
|
-
elif self.subscription:
|
|
322
|
-
self.subscription.close()
|
|
323
|
-
self.subscription = None
|
ophyd_async/epics/core/_p4p.py
CHANGED
|
@@ -31,6 +31,8 @@ from ophyd_async.core import (
|
|
|
31
31
|
|
|
32
32
|
from ._util import EpicsSignalBackend, format_datatype, get_supported_values
|
|
33
33
|
|
|
34
|
+
logger = logging.getLogger("ophyd_async")
|
|
35
|
+
|
|
34
36
|
|
|
35
37
|
def _limits_from_value(value: Any) -> Limits:
|
|
36
38
|
def get_limits(
|
|
@@ -39,7 +41,7 @@ def _limits_from_value(value: Any) -> Limits:
|
|
|
39
41
|
substructure = getattr(value, substucture_name, None)
|
|
40
42
|
low = getattr(substructure, low_name, nan)
|
|
41
43
|
high = getattr(substructure, high_name, nan)
|
|
42
|
-
if not (isnan(low) and isnan(high)):
|
|
44
|
+
if not (isnan(low) and isnan(high)) and not low == high == 0:
|
|
43
45
|
return LimitsRange(
|
|
44
46
|
low=None if isnan(low) else low,
|
|
45
47
|
high=None if isnan(high) else high,
|
|
@@ -60,12 +62,22 @@ def _limits_from_value(value: Any) -> Limits:
|
|
|
60
62
|
def _metadata_from_value(datatype: type[SignalDatatype], value: Any) -> SignalMetadata:
|
|
61
63
|
metadata = SignalMetadata()
|
|
62
64
|
value_data: Any = getattr(value, "value", None)
|
|
65
|
+
specifier = _get_specifier(value)
|
|
63
66
|
display_data: Any = getattr(value, "display", None)
|
|
64
|
-
if
|
|
67
|
+
if (
|
|
68
|
+
hasattr(display_data, "units")
|
|
69
|
+
and specifier[-1] in _number_specifiers
|
|
70
|
+
and datatype is not str
|
|
71
|
+
):
|
|
65
72
|
metadata["units"] = display_data.units
|
|
66
|
-
if
|
|
73
|
+
if (
|
|
74
|
+
hasattr(display_data, "precision")
|
|
75
|
+
and not isnan(display_data.precision)
|
|
76
|
+
and specifier[-1] in _float_specifiers
|
|
77
|
+
and datatype is not int
|
|
78
|
+
):
|
|
67
79
|
metadata["precision"] = display_data.precision
|
|
68
|
-
if limits := _limits_from_value(value):
|
|
80
|
+
if (limits := _limits_from_value(value)) and specifier[-1] in _number_specifiers:
|
|
69
81
|
metadata["limits"] = limits
|
|
70
82
|
# Get choices from display or value
|
|
71
83
|
if datatype is str or issubclass(datatype, StrictEnum):
|
|
@@ -84,9 +96,7 @@ class PvaConverter(Generic[SignalDatatypeT]):
|
|
|
84
96
|
self.datatype = datatype
|
|
85
97
|
|
|
86
98
|
def value(self, value: Any) -> SignalDatatypeT:
|
|
87
|
-
#
|
|
88
|
-
# invokes __pos__ operator to return an instance of
|
|
89
|
-
# the builtin base class
|
|
99
|
+
# Normally the value will be of the correct python type
|
|
90
100
|
return value["value"]
|
|
91
101
|
|
|
92
102
|
def write_value(self, value: Any) -> Any:
|
|
@@ -94,6 +104,15 @@ class PvaConverter(Generic[SignalDatatypeT]):
|
|
|
94
104
|
return value
|
|
95
105
|
|
|
96
106
|
|
|
107
|
+
class PvaIntConverter(PvaConverter[int]):
|
|
108
|
+
def __init__(self):
|
|
109
|
+
super().__init__(int)
|
|
110
|
+
|
|
111
|
+
def value(self, value: Any) -> int:
|
|
112
|
+
# Convert to an int
|
|
113
|
+
return int(value["value"])
|
|
114
|
+
|
|
115
|
+
|
|
97
116
|
class PvaLongStringConverter(PvaConverter[str]):
|
|
98
117
|
def __init__(self):
|
|
99
118
|
super().__init__(str)
|
|
@@ -174,6 +193,9 @@ class PvaTableConverter(PvaConverter[Table]):
|
|
|
174
193
|
|
|
175
194
|
|
|
176
195
|
# https://mdavidsaver.github.io/p4p/values.html
|
|
196
|
+
_float_specifiers = {"f", "d"}
|
|
197
|
+
_int_specifiers = {"b", "B", "h", "H", "i", "I", "l", "L"}
|
|
198
|
+
_number_specifiers = _float_specifiers.union(_int_specifiers)
|
|
177
199
|
_datatype_converter_from_typeid: dict[
|
|
178
200
|
tuple[str, str], tuple[type[SignalDatatype], type[PvaConverter]]
|
|
179
201
|
] = {
|
|
@@ -208,7 +230,7 @@ _datatype_converter_from_typeid: dict[
|
|
|
208
230
|
}
|
|
209
231
|
|
|
210
232
|
|
|
211
|
-
def _get_specifier(value: Value):
|
|
233
|
+
def _get_specifier(value: Value) -> str:
|
|
212
234
|
typ = value.type("value").aspy()
|
|
213
235
|
if isinstance(typ, tuple):
|
|
214
236
|
return typ[0]
|
|
@@ -258,7 +280,7 @@ def make_converter(datatype: type | None, values: dict[str, Any]) -> PvaConverte
|
|
|
258
280
|
== 0
|
|
259
281
|
):
|
|
260
282
|
# Allow int signals to represent float records when prec is 0
|
|
261
|
-
return
|
|
283
|
+
return PvaIntConverter()
|
|
262
284
|
elif inferred_datatype is str and (enum_cls := get_enum_cls(datatype)):
|
|
263
285
|
# Allow strings to be used as enums until QSRV supports this
|
|
264
286
|
return PvaConverter(str)
|
|
@@ -301,7 +323,7 @@ async def pvget_with_timeout(pv: str, timeout: float) -> Any:
|
|
|
301
323
|
try:
|
|
302
324
|
return await asyncio.wait_for(context().get(pv), timeout=timeout)
|
|
303
325
|
except asyncio.TimeoutError as exc:
|
|
304
|
-
|
|
326
|
+
logger.debug(f"signal pva://{pv} timed out", exc_info=True)
|
|
305
327
|
raise NotConnected(f"pva://{pv}") from exc
|
|
306
328
|
|
|
307
329
|
|
|
@@ -383,10 +405,15 @@ class PvaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
383
405
|
return self.converter.value(value)
|
|
384
406
|
|
|
385
407
|
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
|
|
408
|
+
if callback and self.subscription:
|
|
409
|
+
msg = "Cannot set a callback when one is already set"
|
|
410
|
+
raise RuntimeError(msg)
|
|
411
|
+
|
|
412
|
+
if self.subscription:
|
|
413
|
+
self.subscription.close()
|
|
414
|
+
self.subscription = None
|
|
415
|
+
|
|
386
416
|
if callback:
|
|
387
|
-
assert (
|
|
388
|
-
not self.subscription
|
|
389
|
-
), "Cannot set a callback when one is already set"
|
|
390
417
|
|
|
391
418
|
async def async_callback(v):
|
|
392
419
|
callback(self._make_reading(v))
|
|
@@ -397,6 +424,3 @@ class PvaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
397
424
|
self.subscription = context().monitor(
|
|
398
425
|
self.read_pv, async_callback, request=request
|
|
399
426
|
)
|
|
400
|
-
elif self.subscription:
|
|
401
|
-
self.subscription.close()
|
|
402
|
-
self.subscription = None
|
ophyd_async/epics/core/_util.py
CHANGED
|
@@ -47,7 +47,7 @@ def get_supported_values(
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def format_datatype(datatype: Any) -> str:
|
|
50
|
-
if get_origin(datatype) is np.ndarray and get_args(datatype)
|
|
50
|
+
if get_origin(datatype) is np.ndarray and get_args(datatype):
|
|
51
51
|
dtype = get_dtype(datatype)
|
|
52
52
|
return f"Array1D[np.{dtype.name}]"
|
|
53
53
|
elif get_origin(datatype) is Sequence:
|