ophyd-async 0.13.0__py3-none-any.whl → 0.13.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ophyd_async/_version.py +16 -3
- ophyd_async/core/__init__.py +2 -1
- ophyd_async/core/_detector.py +1 -1
- ophyd_async/core/_signal.py +16 -9
- ophyd_async/core/_utils.py +5 -4
- ophyd_async/epics/adandor/_andor.py +1 -2
- ophyd_async/epics/adcore/_core_detector.py +1 -2
- ophyd_async/epics/adcore/_core_io.py +1 -1
- ophyd_async/epics/adcore/_core_logic.py +2 -2
- ophyd_async/epics/adcore/_core_writer.py +9 -6
- ophyd_async/epics/adcore/_hdf_writer.py +6 -1
- ophyd_async/epics/adkinetix/_kinetix_io.py +4 -4
- ophyd_async/epics/adpilatus/_pilatus.py +2 -6
- ophyd_async/epics/advimba/_vimba_io.py +1 -1
- ophyd_async/epics/core/_epics_connector.py +14 -1
- ophyd_async/epics/core/_p4p.py +2 -3
- ophyd_async/epics/core/_pvi_connector.py +1 -1
- ophyd_async/epics/motor.py +21 -16
- ophyd_async/epics/{eiger → odin}/_odin_io.py +5 -3
- ophyd_async/epics/pmac/__init__.py +2 -0
- ophyd_async/epics/pmac/_pmac_io.py +2 -2
- ophyd_async/epics/pmac/_pmac_trajectory.py +116 -0
- ophyd_async/epics/pmac/_utils.py +671 -55
- ophyd_async/epics/testing/_example_ioc.py +1 -2
- ophyd_async/fastcs/eiger/_eiger.py +1 -1
- ophyd_async/fastcs/jungfrau/__init__.py +29 -0
- ophyd_async/fastcs/jungfrau/_controller.py +139 -0
- ophyd_async/fastcs/jungfrau/_jungfrau.py +30 -0
- ophyd_async/fastcs/jungfrau/_signals.py +94 -0
- ophyd_async/fastcs/jungfrau/_utils.py +79 -0
- ophyd_async/plan_stubs/_settings.py +1 -1
- ophyd_async/sim/_motor.py +11 -3
- ophyd_async/sim/_point_detector.py +6 -3
- ophyd_async/sim/_stage.py +14 -3
- ophyd_async/tango/core/_tango_transport.py +2 -2
- ophyd_async/testing/_assert.py +6 -6
- ophyd_async/testing/_one_of_everything.py +1 -1
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/METADATA +5 -4
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/RECORD +43 -37
- /ophyd_async/epics/{eiger → odin}/__init__.py +0 -0
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/WHEEL +0 -0
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/licenses/LICENSE +0 -0
- {ophyd_async-0.13.0.dist-info → ophyd_async-0.13.2.dist-info}/top_level.txt +0 -0
|
@@ -4,8 +4,7 @@ from typing import Annotated as A
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
-
from ophyd_async.core import Array1D, SignalR, SignalRW, StrictEnum, Table
|
|
8
|
-
from ophyd_async.core._utils import SubsetEnum
|
|
7
|
+
from ophyd_async.core import Array1D, SignalR, SignalRW, StrictEnum, SubsetEnum, Table
|
|
9
8
|
from ophyd_async.epics.core import EpicsDevice, PvSuffix
|
|
10
9
|
|
|
11
10
|
from ._utils import TestingIOC, generate_random_pv_prefix
|
|
@@ -4,7 +4,7 @@ from ophyd_async.core import (
|
|
|
4
4
|
StandardDetector,
|
|
5
5
|
TriggerInfo,
|
|
6
6
|
)
|
|
7
|
-
from ophyd_async.epics.
|
|
7
|
+
from ophyd_async.epics.odin import Odin, OdinWriter
|
|
8
8
|
|
|
9
9
|
from ._eiger_controller import EigerController
|
|
10
10
|
from ._eiger_io import EigerDriverIO
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from ._controller import JungfrauController
|
|
2
|
+
from ._jungfrau import Jungfrau
|
|
3
|
+
from ._signals import (
|
|
4
|
+
AcquisitionType,
|
|
5
|
+
DetectorStatus,
|
|
6
|
+
GainMode,
|
|
7
|
+
JungfrauDriverIO,
|
|
8
|
+
JungfrauTriggerMode,
|
|
9
|
+
PedestalMode,
|
|
10
|
+
)
|
|
11
|
+
from ._utils import (
|
|
12
|
+
create_jungfrau_external_triggering_info,
|
|
13
|
+
create_jungfrau_internal_triggering_info,
|
|
14
|
+
create_jungfrau_pedestal_triggering_info,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Jungfrau",
|
|
19
|
+
"DetectorStatus",
|
|
20
|
+
"create_jungfrau_external_triggering_info",
|
|
21
|
+
"create_jungfrau_internal_triggering_info",
|
|
22
|
+
"create_jungfrau_pedestal_triggering_info",
|
|
23
|
+
"JungfrauController",
|
|
24
|
+
"JungfrauDriverIO",
|
|
25
|
+
"JungfrauTriggerMode",
|
|
26
|
+
"AcquisitionType",
|
|
27
|
+
"GainMode",
|
|
28
|
+
"PedestalMode",
|
|
29
|
+
]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
DEFAULT_TIMEOUT,
|
|
6
|
+
DetectorController,
|
|
7
|
+
DetectorTrigger,
|
|
8
|
+
TriggerInfo,
|
|
9
|
+
wait_for_value,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from ._signals import (
|
|
13
|
+
JUNGFRAU_TRIGGER_MODE_MAP,
|
|
14
|
+
AcquisitionType,
|
|
15
|
+
DetectorStatus,
|
|
16
|
+
JungfrauDriverIO,
|
|
17
|
+
PedestalMode,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Deadtime is dependant on a wide combination of settings and on trigger mode
|
|
21
|
+
# but this is safe upper-limit
|
|
22
|
+
JUNGFRAU_DEADTIME_S = 2e-5
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("ophyd_async")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class JungfrauController(DetectorController):
|
|
28
|
+
def __init__(self, driver: JungfrauDriverIO):
|
|
29
|
+
self._driver = driver
|
|
30
|
+
|
|
31
|
+
def get_deadtime(self, exposure: float | None = None) -> float:
|
|
32
|
+
return JUNGFRAU_DEADTIME_S
|
|
33
|
+
|
|
34
|
+
async def prepare(self, trigger_info: TriggerInfo) -> None:
|
|
35
|
+
# ValueErrors and warnings in this function come from the jungfrau operation
|
|
36
|
+
# docs: https://rtd.xfel.eu/docs/jungfrau-detector-documentation/en/latest/operation.html
|
|
37
|
+
|
|
38
|
+
# Deadtime here is really used as "time between frames"
|
|
39
|
+
|
|
40
|
+
acquisition_type = await self._driver.acquisition_type.get_value()
|
|
41
|
+
logger.info(f"Preparing Jungfrau in {acquisition_type} mode.")
|
|
42
|
+
|
|
43
|
+
if trigger_info.trigger not in (
|
|
44
|
+
DetectorTrigger.INTERNAL,
|
|
45
|
+
DetectorTrigger.EDGE_TRIGGER,
|
|
46
|
+
):
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"The trigger method can only be called with internal or edge triggering"
|
|
49
|
+
)
|
|
50
|
+
if (
|
|
51
|
+
acquisition_type == AcquisitionType.PEDESTAL
|
|
52
|
+
and trigger_info.trigger != DetectorTrigger.INTERNAL
|
|
53
|
+
):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"Jungfrau must be triggered internally while in pedestal mode."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if not isinstance(trigger_info.number_of_events, int):
|
|
59
|
+
raise TypeError("Number of events must be an integer")
|
|
60
|
+
|
|
61
|
+
if acquisition_type != AcquisitionType.PEDESTAL:
|
|
62
|
+
if (
|
|
63
|
+
trigger_info.trigger == DetectorTrigger.INTERNAL
|
|
64
|
+
and trigger_info.number_of_events != 1
|
|
65
|
+
):
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"Number of events must be set to 1 in internal trigger mode during "
|
|
68
|
+
"standard acquisitions."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
trigger_info.trigger == DetectorTrigger.EDGE_TRIGGER
|
|
73
|
+
and trigger_info.exposures_per_event != 1
|
|
74
|
+
):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"Exposures per event must be set to 1 in edge trigger mode "
|
|
77
|
+
"during standard acquisitions."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if not trigger_info.livetime:
|
|
81
|
+
raise ValueError("Must set TriggerInfo.livetime")
|
|
82
|
+
|
|
83
|
+
if trigger_info.livetime < 2e-6:
|
|
84
|
+
logger.warning("Exposure time shorter than 2μs is not recommended")
|
|
85
|
+
|
|
86
|
+
period_between_frames = trigger_info.livetime + trigger_info.deadtime
|
|
87
|
+
|
|
88
|
+
if period_between_frames < self.get_deadtime():
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Period between frames (exposure time - deadtime) = "
|
|
91
|
+
f"{period_between_frames}s cannot be lower than minimum detector "
|
|
92
|
+
f"deadtime {self.get_deadtime()}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
coros = [
|
|
96
|
+
self._driver.trigger_mode.set(
|
|
97
|
+
JUNGFRAU_TRIGGER_MODE_MAP[trigger_info.trigger]
|
|
98
|
+
),
|
|
99
|
+
self._driver.period_between_frames.set(period_between_frames),
|
|
100
|
+
self._driver.exposure_time.set(trigger_info.livetime),
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
match acquisition_type:
|
|
104
|
+
case AcquisitionType.STANDARD:
|
|
105
|
+
frames_signal = (
|
|
106
|
+
trigger_info.exposures_per_event
|
|
107
|
+
if trigger_info.trigger is DetectorTrigger.INTERNAL
|
|
108
|
+
else trigger_info.number_of_events
|
|
109
|
+
)
|
|
110
|
+
coros.extend(
|
|
111
|
+
[
|
|
112
|
+
self._driver.frames_per_acq.set(frames_signal),
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
case AcquisitionType.PEDESTAL:
|
|
116
|
+
coros.extend(
|
|
117
|
+
[
|
|
118
|
+
self._driver.pedestal_mode_frames.set(
|
|
119
|
+
trigger_info.exposures_per_event
|
|
120
|
+
),
|
|
121
|
+
self._driver.pedestal_mode_loops.set(
|
|
122
|
+
trigger_info.number_of_events
|
|
123
|
+
),
|
|
124
|
+
self._driver.pedestal_mode_state.set(PedestalMode.ON),
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
await asyncio.gather(*coros)
|
|
129
|
+
|
|
130
|
+
async def arm(self):
|
|
131
|
+
await self._driver.acquisition_start.trigger()
|
|
132
|
+
|
|
133
|
+
async def wait_for_idle(self):
|
|
134
|
+
await wait_for_value(
|
|
135
|
+
self._driver.detector_status, DetectorStatus.IDLE, timeout=DEFAULT_TIMEOUT
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def disarm(self):
|
|
139
|
+
await self._driver.acquisition_stop.trigger()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
PathProvider,
|
|
3
|
+
StandardDetector,
|
|
4
|
+
)
|
|
5
|
+
from ophyd_async.epics.odin import Odin, OdinWriter
|
|
6
|
+
from ophyd_async.fastcs.jungfrau._controller import JungfrauController
|
|
7
|
+
from ophyd_async.fastcs.jungfrau._signals import JungfrauDriverIO
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Jungfrau(StandardDetector[JungfrauController, OdinWriter]):
|
|
11
|
+
"""Ophyd-async implementation of a Jungfrau Detector."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
prefix: str,
|
|
16
|
+
path_provider: PathProvider,
|
|
17
|
+
drv_suffix: str,
|
|
18
|
+
hdf_suffix: str,
|
|
19
|
+
odin_nodes: int,
|
|
20
|
+
name="",
|
|
21
|
+
):
|
|
22
|
+
self.drv = JungfrauDriverIO(prefix + drv_suffix)
|
|
23
|
+
self.odin = Odin(prefix + hdf_suffix, nodes=odin_nodes)
|
|
24
|
+
writer = OdinWriter(
|
|
25
|
+
path_provider,
|
|
26
|
+
self.odin,
|
|
27
|
+
self.drv.bit_depth,
|
|
28
|
+
)
|
|
29
|
+
controller = JungfrauController(self.drv)
|
|
30
|
+
super().__init__(controller, writer, name=name)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from pydantic import NonNegativeInt
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import (
|
|
4
|
+
DetectorTrigger,
|
|
5
|
+
Device,
|
|
6
|
+
SignalR,
|
|
7
|
+
SignalRW,
|
|
8
|
+
SignalX,
|
|
9
|
+
StrictEnum,
|
|
10
|
+
soft_signal_rw,
|
|
11
|
+
)
|
|
12
|
+
from ophyd_async.fastcs.core import fastcs_connector
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class JungfrauTriggerMode(StrictEnum):
|
|
16
|
+
INTERNAL = "Internal"
|
|
17
|
+
|
|
18
|
+
# Detector waits for external trigger to start frame series, but still
|
|
19
|
+
# controls exposure time and frame period internally
|
|
20
|
+
EXTERNAL = "External"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DetectorStatus(StrictEnum):
|
|
24
|
+
IDLE = "Idle"
|
|
25
|
+
ERROR = "Error"
|
|
26
|
+
WAITING = "Waiting"
|
|
27
|
+
RUN_FINISHED = "RunFinished"
|
|
28
|
+
TRANSMITTING = "Transmitting"
|
|
29
|
+
RUNNING = "Running"
|
|
30
|
+
STOPPED = "Stopped"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GainMode(StrictEnum):
|
|
34
|
+
DYNAMIC = "Dynamic"
|
|
35
|
+
FORCE_SWITCH_G1 = "ForceSwitchG1"
|
|
36
|
+
FORCE_SWITCH_G2 = "ForceSwitchG2"
|
|
37
|
+
FIX_G1 = "FixG1"
|
|
38
|
+
FIX_G2 = "FixG2"
|
|
39
|
+
|
|
40
|
+
# Use with caution - this may damage the detector
|
|
41
|
+
FIX_G0 = "FixG0"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PedestalMode(StrictEnum):
|
|
45
|
+
ON = "On"
|
|
46
|
+
OFF = "Off"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AcquisitionType(StrictEnum):
|
|
50
|
+
STANDARD = "Standard"
|
|
51
|
+
PEDESTAL = "Pedestal"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
JUNGFRAU_TRIGGER_MODE_MAP = {
|
|
55
|
+
DetectorTrigger.EDGE_TRIGGER: JungfrauTriggerMode.EXTERNAL,
|
|
56
|
+
DetectorTrigger.INTERNAL: JungfrauTriggerMode.INTERNAL,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class JungfrauDriverIO(Device):
|
|
61
|
+
"""Contains signals for handling IO on the Jungfrau detector."""
|
|
62
|
+
|
|
63
|
+
exposure_time: SignalRW[float] # in s
|
|
64
|
+
|
|
65
|
+
# Includes deadtime
|
|
66
|
+
period_between_frames: SignalRW[float] # in s
|
|
67
|
+
|
|
68
|
+
# Sets the delay for the beginning of the exposure time after
|
|
69
|
+
# trigger input
|
|
70
|
+
delay_after_trigger: SignalRW[float] # in s
|
|
71
|
+
|
|
72
|
+
# In internal trigger mode, this is frames per trigger. In external trigger mode,
|
|
73
|
+
# this is frames per overall acquisition. In pedestal mode, this signal is not set.
|
|
74
|
+
frames_per_acq: SignalRW[NonNegativeInt]
|
|
75
|
+
|
|
76
|
+
pedestal_mode_state: SignalRW[PedestalMode]
|
|
77
|
+
pedestal_mode_frames: SignalRW[NonNegativeInt]
|
|
78
|
+
pedestal_mode_loops: SignalRW[NonNegativeInt]
|
|
79
|
+
|
|
80
|
+
gain_mode: SignalRW[GainMode]
|
|
81
|
+
|
|
82
|
+
acquisition_start: SignalX
|
|
83
|
+
|
|
84
|
+
acquisition_stop: SignalX
|
|
85
|
+
bit_depth: SignalR[int]
|
|
86
|
+
trigger_mode: SignalRW[JungfrauTriggerMode]
|
|
87
|
+
detector_status: SignalR[DetectorStatus]
|
|
88
|
+
|
|
89
|
+
def __init__(self, uri: str, name: str = ""):
|
|
90
|
+
# Determines how the TriggerInfo gets mapped to the Jungfrau during prepare
|
|
91
|
+
self.acquisition_type = soft_signal_rw(
|
|
92
|
+
AcquisitionType, AcquisitionType.STANDARD
|
|
93
|
+
)
|
|
94
|
+
super().__init__(name=name, connector=fastcs_connector(self, uri))
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from pydantic import PositiveInt
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import DetectorTrigger, TriggerInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_jungfrau_external_triggering_info(
|
|
7
|
+
total_triggers: PositiveInt,
|
|
8
|
+
exposure_time_s: float,
|
|
9
|
+
) -> TriggerInfo:
|
|
10
|
+
"""Create safe Jungfrau TriggerInfo for external triggering.
|
|
11
|
+
|
|
12
|
+
Uses parameters which more closely-align with Jungfrau terminology
|
|
13
|
+
to create TriggerInfo. This device currently only supports one frame per trigger
|
|
14
|
+
when being externally triggered, but support for this can be added if needed
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
total_triggers: Total external triggers expected before ending acquisition.
|
|
18
|
+
exposure_time_s: How long to expose the detector for each of its frames.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
`TriggerInfo`
|
|
22
|
+
"""
|
|
23
|
+
return TriggerInfo(
|
|
24
|
+
number_of_events=total_triggers,
|
|
25
|
+
trigger=DetectorTrigger.EDGE_TRIGGER,
|
|
26
|
+
livetime=exposure_time_s,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_jungfrau_internal_triggering_info(
|
|
31
|
+
number_of_frames: PositiveInt, exposure_time_s: float
|
|
32
|
+
) -> TriggerInfo:
|
|
33
|
+
"""Create safe Jungfrau TriggerInfo for internal triggering.
|
|
34
|
+
|
|
35
|
+
Uses parameters which more closely-align with Jungfrau terminology
|
|
36
|
+
to create TriggerInfo.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
number_of_frames: Total frames taken after starting acquisition.
|
|
40
|
+
exposure_time_s: How long to expose the detector for each of its frames.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
`TriggerInfo`
|
|
44
|
+
"""
|
|
45
|
+
return TriggerInfo(
|
|
46
|
+
number_of_events=1,
|
|
47
|
+
trigger=DetectorTrigger.INTERNAL,
|
|
48
|
+
livetime=exposure_time_s,
|
|
49
|
+
exposures_per_event=number_of_frames,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def create_jungfrau_pedestal_triggering_info(
|
|
54
|
+
exposure_time_s: float,
|
|
55
|
+
pedestal_frames: PositiveInt,
|
|
56
|
+
pedestal_loops: PositiveInt,
|
|
57
|
+
):
|
|
58
|
+
"""Create safe Jungfrau TriggerInfo for pedestal triggering.
|
|
59
|
+
|
|
60
|
+
Uses parameters which more closely-align with Jungfrau terminology
|
|
61
|
+
to create TriggerInfo.
|
|
62
|
+
|
|
63
|
+
NOTE: To trigger the jungfrau in pedestal mode, you must first set the
|
|
64
|
+
jungfrau acquisition_type signal to AcquisitionType.PEDESTAL!
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
exposure_time_s: How long to expose the detector for each of its frames.
|
|
68
|
+
pedestal_frames: Number of frames taken once triggering begins
|
|
69
|
+
pedestal_loops: Number of repeats of the pedestal scan before detector disarms.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
`TriggerInfo`
|
|
73
|
+
"""
|
|
74
|
+
return TriggerInfo(
|
|
75
|
+
number_of_events=pedestal_loops,
|
|
76
|
+
exposures_per_event=pedestal_frames,
|
|
77
|
+
trigger=DetectorTrigger.INTERNAL,
|
|
78
|
+
livetime=exposure_time_s,
|
|
79
|
+
)
|
|
@@ -13,10 +13,10 @@ from ophyd_async.core import (
|
|
|
13
13
|
Settings,
|
|
14
14
|
SettingsProvider,
|
|
15
15
|
SignalRW,
|
|
16
|
+
Table,
|
|
16
17
|
walk_config_signals,
|
|
17
18
|
walk_rw_signals,
|
|
18
19
|
)
|
|
19
|
-
from ophyd_async.core._table import Table
|
|
20
20
|
|
|
21
21
|
from ._utils import T
|
|
22
22
|
from ._wait_for_awaitable import wait_for_awaitable
|
ophyd_async/sim/_motor.py
CHANGED
|
@@ -23,11 +23,19 @@ from ophyd_async.core import StandardReadableFormat as Format
|
|
|
23
23
|
class SimMotor(StandardReadable, Stoppable, Subscribable[float], Locatable[float]):
|
|
24
24
|
"""For usage when simulating a motor."""
|
|
25
25
|
|
|
26
|
-
def __init__(
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
name: str = "",
|
|
29
|
+
instant: bool = True,
|
|
30
|
+
initial_value: float = 0.0,
|
|
31
|
+
units: str = "mm",
|
|
32
|
+
) -> None:
|
|
27
33
|
"""Simulate a motor, with optional velocity.
|
|
28
34
|
|
|
29
35
|
:param name: name of device
|
|
30
36
|
:param instant: whether to move instantly or calculate move time using velocity
|
|
37
|
+
:param initial_value: initial position of the motor
|
|
38
|
+
:param units: units of the motor position
|
|
31
39
|
"""
|
|
32
40
|
# Define some signals
|
|
33
41
|
with self.add_children_as_readables(Format.HINTED_SIGNAL):
|
|
@@ -37,8 +45,8 @@ class SimMotor(StandardReadable, Stoppable, Subscribable[float], Locatable[float
|
|
|
37
45
|
with self.add_children_as_readables(Format.CONFIG_SIGNAL):
|
|
38
46
|
self.velocity = soft_signal_rw(float, 0 if instant else 1.0)
|
|
39
47
|
self.acceleration_time = soft_signal_rw(float, 0.5)
|
|
40
|
-
self.units = soft_signal_rw(str,
|
|
41
|
-
self.user_setpoint = soft_signal_rw(float,
|
|
48
|
+
self.units = soft_signal_rw(str, units)
|
|
49
|
+
self.user_setpoint = soft_signal_rw(float, initial_value)
|
|
42
50
|
|
|
43
51
|
# Whether set() should complete successfully or not
|
|
44
52
|
self._set_success = True
|
|
@@ -41,9 +41,12 @@ class SimPointDetector(StandardReadable):
|
|
|
41
41
|
"""Simalutes a point detector with multiple channels."""
|
|
42
42
|
|
|
43
43
|
def __init__(
|
|
44
|
-
self,
|
|
44
|
+
self,
|
|
45
|
+
pattern_generator: PatternGenerator | None,
|
|
46
|
+
num_channels: int = 3,
|
|
47
|
+
name: str = "",
|
|
45
48
|
) -> None:
|
|
46
|
-
self.
|
|
49
|
+
self.pattern_generator = pattern_generator or PatternGenerator()
|
|
47
50
|
self.acquire_time = soft_signal_rw(float, 0.1)
|
|
48
51
|
self.acquiring, self._set_acquiring = soft_signal_r_and_setter(bool)
|
|
49
52
|
self._value_signals = dict(
|
|
@@ -75,7 +78,7 @@ class SimPointDetector(StandardReadable):
|
|
|
75
78
|
# Update the channel value
|
|
76
79
|
for i, channel in self.channel.items():
|
|
77
80
|
high_energy = modes[channel] == EnergyMode.HIGH
|
|
78
|
-
point = self.
|
|
81
|
+
point = self.pattern_generator.generate_point(i, high_energy)
|
|
79
82
|
setter = self._value_signals[channel.value]
|
|
80
83
|
setter(int(point * 10000 * update_time))
|
|
81
84
|
|
ophyd_async/sim/_stage.py
CHANGED
|
@@ -8,12 +8,23 @@ class SimStage(StandardReadable):
|
|
|
8
8
|
"""A simulated sample stage with X and Y movables."""
|
|
9
9
|
|
|
10
10
|
def __init__(self, pattern_generator: PatternGenerator, name="") -> None:
|
|
11
|
+
self.pattern_generator = pattern_generator
|
|
11
12
|
# Define some child Devices
|
|
12
13
|
with self.add_children_as_readables():
|
|
13
14
|
self.x = SimMotor(instant=False)
|
|
14
15
|
self.y = SimMotor(instant=False)
|
|
15
|
-
# Tell the pattern generator about the motor positions
|
|
16
|
-
self.x.user_readback.subscribe_value(pattern_generator.set_x)
|
|
17
|
-
self.y.user_readback.subscribe_value(pattern_generator.set_y)
|
|
18
16
|
# Set name of device and child devices
|
|
19
17
|
super().__init__(name=name)
|
|
18
|
+
|
|
19
|
+
def stage(self):
|
|
20
|
+
"""Stage the motors and report the position to the pattern generator."""
|
|
21
|
+
# Tell the pattern generator about the motor positions
|
|
22
|
+
self.x.user_readback.subscribe_value(self.pattern_generator.set_x)
|
|
23
|
+
self.y.user_readback.subscribe_value(self.pattern_generator.set_y)
|
|
24
|
+
return super().stage()
|
|
25
|
+
|
|
26
|
+
def unstage(self):
|
|
27
|
+
"""Unstage the motors and remove the position subscription."""
|
|
28
|
+
self.x.user_readback.clear_sub(self.pattern_generator.set_x)
|
|
29
|
+
self.y.user_readback.clear_sub(self.pattern_generator.set_y)
|
|
30
|
+
return super().unstage()
|
|
@@ -212,7 +212,7 @@ class AttributeProxy(TangoProxy):
|
|
|
212
212
|
|
|
213
213
|
task = asyncio.create_task(_write())
|
|
214
214
|
await asyncio.wait_for(task, timeout)
|
|
215
|
-
except
|
|
215
|
+
except TimeoutError as te:
|
|
216
216
|
raise TimeoutError(f"{self._name} attr put failed: Timeout") from te
|
|
217
217
|
except DevFailed as de:
|
|
218
218
|
raise RuntimeError(
|
|
@@ -451,7 +451,7 @@ class CommandProxy(TangoProxy):
|
|
|
451
451
|
timestamp=time.time(),
|
|
452
452
|
alarm_severity=0,
|
|
453
453
|
)
|
|
454
|
-
except
|
|
454
|
+
except TimeoutError as te:
|
|
455
455
|
raise TimeoutError(f"{self._name} command failed: Timeout") from te
|
|
456
456
|
except DevFailed as de:
|
|
457
457
|
raise RuntimeError(
|
ophyd_async/testing/_assert.py
CHANGED
|
@@ -21,11 +21,11 @@ from ophyd_async.core import (
|
|
|
21
21
|
from ._utils import T
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def partial_reading(val: Any) ->
|
|
25
|
-
"""Helper function for building expected reading or configuration
|
|
24
|
+
def partial_reading(val: Any) -> Mapping[str, Any]:
|
|
25
|
+
"""Helper function for building expected reading or configuration mapping.
|
|
26
26
|
|
|
27
|
-
:param val: Value to be wrapped in
|
|
28
|
-
:return: The
|
|
27
|
+
:param val: Value to be wrapped in mapping with "value" as the key.
|
|
28
|
+
:return: The mapping that has wrapped the val with key "value".
|
|
29
29
|
"""
|
|
30
30
|
return {"value": val}
|
|
31
31
|
|
|
@@ -100,7 +100,7 @@ def _assert_readings_approx_equal(
|
|
|
100
100
|
|
|
101
101
|
async def assert_configuration(
|
|
102
102
|
configurable: AsyncConfigurable,
|
|
103
|
-
expected_configuration:
|
|
103
|
+
expected_configuration: Mapping[str, Mapping[str, Any]],
|
|
104
104
|
full_match: bool = True,
|
|
105
105
|
) -> None:
|
|
106
106
|
"""Assert that a configurable Device has the given configuration.
|
|
@@ -108,7 +108,7 @@ async def assert_configuration(
|
|
|
108
108
|
:param configurable:
|
|
109
109
|
Device with an async ``read_configuration()`` method to get the
|
|
110
110
|
configuration from.
|
|
111
|
-
:param
|
|
111
|
+
:param expected_configuration: The expected configuration from the configurable.
|
|
112
112
|
:param full_match: if expected_reading keys set is same as actual keys set.
|
|
113
113
|
true: exact match
|
|
114
114
|
false: expected_reading keys is subset of actual reading keys
|
|
@@ -6,6 +6,7 @@ import numpy as np
|
|
|
6
6
|
from ophyd_async.core import (
|
|
7
7
|
Array1D,
|
|
8
8
|
Device,
|
|
9
|
+
DeviceVector,
|
|
9
10
|
DTypeScalar_co,
|
|
10
11
|
SignalRW,
|
|
11
12
|
StandardReadable,
|
|
@@ -15,7 +16,6 @@ from ophyd_async.core import (
|
|
|
15
16
|
soft_signal_rw,
|
|
16
17
|
)
|
|
17
18
|
from ophyd_async.core import StandardReadableFormat as Format
|
|
18
|
-
from ophyd_async.core._device import DeviceVector
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class ExampleEnum(StrictEnum):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ophyd-async
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.2
|
|
4
4
|
Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
|
|
5
5
|
Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -35,10 +35,10 @@ License: BSD 3-Clause License
|
|
|
35
35
|
Project-URL: GitHub, https://github.com/bluesky/ophyd-async
|
|
36
36
|
Classifier: Development Status :: 3 - Alpha
|
|
37
37
|
Classifier: License :: OSI Approved :: BSD License
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
-
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
42
|
Description-Content-Type: text/markdown
|
|
43
43
|
License-File: LICENSE
|
|
44
44
|
Requires-Dist: numpy
|
|
@@ -50,6 +50,7 @@ Requires-Dist: pydantic>=2.0
|
|
|
50
50
|
Requires-Dist: pydantic-numpy
|
|
51
51
|
Requires-Dist: stamina>=23.1.0
|
|
52
52
|
Requires-Dist: scanspec>=1.0a1
|
|
53
|
+
Requires-Dist: velocity-profile
|
|
53
54
|
Provides-Extra: sim
|
|
54
55
|
Requires-Dist: h5py; extra == "sim"
|
|
55
56
|
Provides-Extra: ca
|
|
@@ -57,7 +58,7 @@ Requires-Dist: aioca>=2.0a4; extra == "ca"
|
|
|
57
58
|
Provides-Extra: pva
|
|
58
59
|
Requires-Dist: p4p>=4.2.0; extra == "pva"
|
|
59
60
|
Provides-Extra: tango
|
|
60
|
-
Requires-Dist: pytango==10.0.
|
|
61
|
+
Requires-Dist: pytango==10.0.2; extra == "tango"
|
|
61
62
|
Provides-Extra: demo
|
|
62
63
|
Requires-Dist: ipython; extra == "demo"
|
|
63
64
|
Requires-Dist: matplotlib; extra == "demo"
|