ophyd-async 0.4.0__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +86 -73
- ophyd_async/core/{detector.py → _detector.py} +42 -36
- ophyd_async/core/{device.py → _device.py} +1 -1
- ophyd_async/core/{device_save_loader.py → _device_save_loader.py} +3 -3
- ophyd_async/core/{flyer.py → _flyer.py} +6 -8
- ophyd_async/{epics/areadetector/writers/general_hdffile.py → core/_hdf_dataset.py} +4 -8
- ophyd_async/{log.py → core/_log.py} +11 -3
- ophyd_async/core/{mock_signal_backend.py → _mock_signal_backend.py} +3 -3
- ophyd_async/core/{mock_signal_utils.py → _mock_signal_utils.py} +7 -7
- ophyd_async/{protocols.py → core/_protocol.py} +1 -1
- ophyd_async/core/_providers.py +24 -37
- ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
- ophyd_async/core/{signal.py → _signal.py} +79 -35
- ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
- ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +3 -12
- ophyd_async/core/{async_status.py → _status.py} +3 -11
- ophyd_async/epics/adaravis/__init__.py +9 -0
- ophyd_async/epics/{areadetector/aravis.py → adaravis/_aravis.py} +9 -11
- ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +7 -10
- ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
- ophyd_async/epics/adcore/__init__.py +47 -0
- ophyd_async/epics/adcore/_core_io.py +138 -0
- ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +16 -52
- ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +54 -29
- ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
- ophyd_async/epics/adcore/_utils.py +132 -0
- ophyd_async/epics/adkinetix/__init__.py +9 -0
- ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +9 -11
- ophyd_async/epics/{areadetector/controllers/kinetix_controller.py → adkinetix/_kinetix_controller.py} +6 -9
- ophyd_async/epics/{areadetector/drivers/kinetix_driver.py → adkinetix/_kinetix_io.py} +9 -5
- ophyd_async/epics/adpilatus/__init__.py +11 -0
- ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +10 -14
- ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +15 -17
- ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +6 -4
- ophyd_async/epics/adsimdetector/__init__.py +7 -0
- ophyd_async/epics/{demo/demo_ad_sim_detector.py → adsimdetector/_sim.py} +10 -11
- ophyd_async/epics/{areadetector/controllers/ad_sim_controller.py → adsimdetector/_sim_controller.py} +8 -14
- ophyd_async/epics/advimba/__init__.py +9 -0
- ophyd_async/epics/{areadetector/vimba.py → advimba/_vimba.py} +9 -9
- ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +9 -17
- ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +11 -8
- ophyd_async/epics/demo/__init__.py +9 -132
- ophyd_async/epics/demo/_mover.py +97 -0
- ophyd_async/epics/demo/_sensor.py +36 -0
- ophyd_async/epics/{motion/motor.py → motor.py} +28 -14
- ophyd_async/epics/pvi/__init__.py +2 -2
- ophyd_async/epics/pvi/{pvi.py → _pvi.py} +17 -14
- ophyd_async/epics/signal/__init__.py +7 -1
- ophyd_async/epics/{_backend → signal}/_aioca.py +15 -7
- ophyd_async/epics/{_backend/common.py → signal/_common.py} +2 -2
- ophyd_async/epics/signal/_epics_transport.py +3 -3
- ophyd_async/epics/{_backend → signal}/_p4p.py +18 -14
- ophyd_async/epics/signal/{signal.py → _signal.py} +10 -9
- ophyd_async/fastcs/odin/__init__.py +0 -0
- ophyd_async/{panda → fastcs/panda}/__init__.py +20 -15
- ophyd_async/{panda/_common_blocks.py → fastcs/panda/_block.py} +5 -3
- ophyd_async/{panda/_panda_controller.py → fastcs/panda/_control.py} +2 -1
- ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +5 -10
- ophyd_async/{panda → fastcs/panda}/_trigger.py +3 -7
- ophyd_async/{panda/writers/_hdf_writer.py → fastcs/panda/_writer.py} +36 -28
- ophyd_async/plan_stubs/__init__.py +5 -2
- ophyd_async/plan_stubs/{ensure_connected.py → _ensure_connected.py} +1 -2
- ophyd_async/plan_stubs/{fly.py → _fly.py} +13 -9
- ophyd_async/plan_stubs/_nd_attributes.py +63 -0
- ophyd_async/sim/__init__.py +0 -11
- ophyd_async/sim/demo/__init__.py +18 -2
- ophyd_async/sim/demo/_pattern_detector/__init__.py +13 -0
- ophyd_async/sim/{sim_pattern_generator.py → demo/_pattern_detector/_pattern_detector.py} +8 -8
- ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +9 -7
- ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +4 -4
- ophyd_async/sim/{pattern_generator.py → demo/_pattern_detector/_pattern_generator.py} +13 -11
- ophyd_async/sim/demo/{sim_motor.py → _sim_motor.py} +7 -5
- ophyd_async/sim/testing/__init__.py +0 -0
- ophyd_async/tango/__init__.py +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/METADATA +46 -44
- ophyd_async-0.5.1.dist-info/RECORD +90 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/WHEEL +1 -1
- ophyd_async/epics/areadetector/__init__.py +0 -23
- ophyd_async/epics/areadetector/controllers/__init__.py +0 -5
- ophyd_async/epics/areadetector/drivers/__init__.py +0 -23
- ophyd_async/epics/areadetector/utils.py +0 -104
- ophyd_async/epics/areadetector/writers/__init__.py +0 -5
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -43
- ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -68
- ophyd_async/epics/motion/__init__.py +0 -3
- ophyd_async/panda/writers/__init__.py +0 -3
- ophyd_async-0.4.0.dist-info/RECORD +0 -84
- /ophyd_async/core/{utils.py → _utils.py} +0 -0
- /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
- /ophyd_async/{panda → fastcs/panda}/_table.py +0 -0
- /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from bluesky.protocols import Movable, Stoppable
|
|
5
|
+
|
|
6
|
+
from ophyd_async.core import (
|
|
7
|
+
DEFAULT_TIMEOUT,
|
|
8
|
+
AsyncStatus,
|
|
9
|
+
CalculatableTimeout,
|
|
10
|
+
CalculateTimeout,
|
|
11
|
+
ConfigSignal,
|
|
12
|
+
Device,
|
|
13
|
+
HintedSignal,
|
|
14
|
+
StandardReadable,
|
|
15
|
+
WatchableAsyncStatus,
|
|
16
|
+
WatcherUpdate,
|
|
17
|
+
observe_value,
|
|
18
|
+
)
|
|
19
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Mover(StandardReadable, Movable, Stoppable):
|
|
23
|
+
"""A demo movable that moves based on velocity"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
26
|
+
# Define some signals
|
|
27
|
+
with self.add_children_as_readables(HintedSignal):
|
|
28
|
+
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
29
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
30
|
+
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
31
|
+
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
32
|
+
self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
|
|
33
|
+
self.precision = epics_signal_r(int, prefix + "Readback.PREC")
|
|
34
|
+
# Signals that collide with standard methods should have a trailing underscore
|
|
35
|
+
self.stop_ = epics_signal_x(prefix + "Stop.PROC")
|
|
36
|
+
# Whether set() should complete successfully or not
|
|
37
|
+
self._set_success = True
|
|
38
|
+
|
|
39
|
+
super().__init__(name=name)
|
|
40
|
+
|
|
41
|
+
def set_name(self, name: str):
|
|
42
|
+
super().set_name(name)
|
|
43
|
+
# Readback should be named the same as its parent in read()
|
|
44
|
+
self.readback.set_name(name)
|
|
45
|
+
|
|
46
|
+
@WatchableAsyncStatus.wrap
|
|
47
|
+
async def set(
|
|
48
|
+
self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
|
|
49
|
+
):
|
|
50
|
+
self._set_success = True
|
|
51
|
+
old_position, units, precision, velocity = await asyncio.gather(
|
|
52
|
+
self.setpoint.get_value(),
|
|
53
|
+
self.units.get_value(),
|
|
54
|
+
self.precision.get_value(),
|
|
55
|
+
self.velocity.get_value(),
|
|
56
|
+
)
|
|
57
|
+
if timeout is CalculateTimeout:
|
|
58
|
+
assert velocity > 0, "Mover has zero velocity"
|
|
59
|
+
timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
|
|
60
|
+
# Make an Event that will be set on completion, and a Status that will
|
|
61
|
+
# error if not done in time
|
|
62
|
+
done = asyncio.Event()
|
|
63
|
+
done_status = AsyncStatus(asyncio.wait_for(done.wait(), timeout))
|
|
64
|
+
# Wait for the value to set, but don't wait for put completion callback
|
|
65
|
+
await self.setpoint.set(new_position, wait=False)
|
|
66
|
+
async for current_position in observe_value(
|
|
67
|
+
self.readback, done_status=done_status
|
|
68
|
+
):
|
|
69
|
+
yield WatcherUpdate(
|
|
70
|
+
current=current_position,
|
|
71
|
+
initial=old_position,
|
|
72
|
+
target=new_position,
|
|
73
|
+
name=self.name,
|
|
74
|
+
unit=units,
|
|
75
|
+
precision=precision,
|
|
76
|
+
)
|
|
77
|
+
if np.isclose(current_position, new_position):
|
|
78
|
+
done.set()
|
|
79
|
+
break
|
|
80
|
+
if not self._set_success:
|
|
81
|
+
raise RuntimeError("Motor was stopped")
|
|
82
|
+
|
|
83
|
+
async def stop(self, success=True):
|
|
84
|
+
self._set_success = success
|
|
85
|
+
status = self.stop_.trigger()
|
|
86
|
+
await status
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SampleStage(Device):
|
|
90
|
+
"""A demo sample stage with X and Y movables"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
93
|
+
# Define some child Devices
|
|
94
|
+
self.x = Mover(prefix + "X:")
|
|
95
|
+
self.y = Mover(prefix + "Y:")
|
|
96
|
+
# Set name of device and child devices
|
|
97
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import ConfigSignal, DeviceVector, HintedSignal, StandardReadable
|
|
4
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnergyMode(str, Enum):
|
|
8
|
+
"""Energy mode for `Sensor`"""
|
|
9
|
+
|
|
10
|
+
#: Low energy mode
|
|
11
|
+
low = "Low Energy"
|
|
12
|
+
#: High energy mode
|
|
13
|
+
high = "High Energy"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Sensor(StandardReadable):
|
|
17
|
+
"""A demo sensor that produces a scalar value based on X and Y Movers"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
20
|
+
# Define some signals
|
|
21
|
+
with self.add_children_as_readables(HintedSignal):
|
|
22
|
+
self.value = epics_signal_r(float, prefix + "Value")
|
|
23
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
24
|
+
self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
|
|
25
|
+
|
|
26
|
+
super().__init__(name=name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SensorGroup(StandardReadable):
|
|
30
|
+
def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
|
|
31
|
+
with self.add_children_as_readables():
|
|
32
|
+
self.sensors = DeviceVector(
|
|
33
|
+
{i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
super().__init__(name)
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
from bluesky.protocols import
|
|
4
|
+
from bluesky.protocols import (
|
|
5
|
+
Flyable,
|
|
6
|
+
Locatable,
|
|
7
|
+
Location,
|
|
8
|
+
Preparable,
|
|
9
|
+
Stoppable,
|
|
10
|
+
)
|
|
5
11
|
from pydantic import BaseModel, Field
|
|
6
12
|
|
|
7
13
|
from ophyd_async.core import (
|
|
14
|
+
DEFAULT_TIMEOUT,
|
|
15
|
+
AsyncStatus,
|
|
16
|
+
CalculatableTimeout,
|
|
17
|
+
CalculateTimeout,
|
|
8
18
|
ConfigSignal,
|
|
9
19
|
HintedSignal,
|
|
10
20
|
StandardReadable,
|
|
11
21
|
WatchableAsyncStatus,
|
|
12
|
-
)
|
|
13
|
-
from ophyd_async.core.async_status import AsyncStatus
|
|
14
|
-
from ophyd_async.core.signal import observe_value
|
|
15
|
-
from ophyd_async.core.utils import (
|
|
16
|
-
DEFAULT_TIMEOUT,
|
|
17
|
-
CalculatableTimeout,
|
|
18
|
-
CalculateTimeout,
|
|
19
22
|
WatcherUpdate,
|
|
23
|
+
observe_value,
|
|
20
24
|
)
|
|
21
|
-
|
|
22
|
-
from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
25
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
class MotorLimitsException(Exception):
|
|
@@ -54,7 +57,7 @@ class FlyMotorInfo(BaseModel):
|
|
|
54
57
|
timeout: CalculatableTimeout = Field(frozen=True, default=CalculateTimeout)
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
class Motor(StandardReadable,
|
|
60
|
+
class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
58
61
|
"""Device that moves a motor record"""
|
|
59
62
|
|
|
60
63
|
def __init__(self, prefix: str, name="") -> None:
|
|
@@ -116,6 +119,7 @@ class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
|
|
|
116
119
|
)
|
|
117
120
|
|
|
118
121
|
await self.set(fly_prepared_position)
|
|
122
|
+
await self.velocity.set(fly_velocity)
|
|
119
123
|
|
|
120
124
|
@AsyncStatus.wrap
|
|
121
125
|
async def kickoff(self):
|
|
@@ -182,7 +186,7 @@ class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
|
|
|
182
186
|
async def _prepare_velocity(
|
|
183
187
|
self, start_position: float, end_position: float, time_for_move: float
|
|
184
188
|
) -> float:
|
|
185
|
-
fly_velocity = (
|
|
189
|
+
fly_velocity = (end_position - start_position) / time_for_move
|
|
186
190
|
max_speed, egu = await asyncio.gather(
|
|
187
191
|
self.max_velocity.get_value(), self.motor_egu.get_value()
|
|
188
192
|
)
|
|
@@ -191,15 +195,25 @@ class Motor(StandardReadable, Movable, Stoppable, Flyable, Preparable):
|
|
|
191
195
|
f"Motor speed of {abs(fly_velocity)} {egu}/s was requested for a motor "
|
|
192
196
|
f" with max speed of {max_speed} {egu}/s"
|
|
193
197
|
)
|
|
194
|
-
|
|
198
|
+
# move to prepare position at maximum velocity
|
|
199
|
+
await self.velocity.set(abs(max_speed))
|
|
195
200
|
return fly_velocity
|
|
196
201
|
|
|
202
|
+
async def locate(self) -> Location[float]:
|
|
203
|
+
location: Location = {
|
|
204
|
+
"setpoint": await self.user_setpoint.get_value(),
|
|
205
|
+
"readback": await self.user_readback.get_value(),
|
|
206
|
+
}
|
|
207
|
+
return location
|
|
208
|
+
|
|
197
209
|
async def _prepare_motor_path(
|
|
198
210
|
self, fly_velocity: float, start_position: float, end_position: float
|
|
199
211
|
) -> float:
|
|
200
212
|
# Distance required for motor to accelerate from stationary to fly_velocity, and
|
|
201
213
|
# distance required for motor to decelerate from fly_velocity to stationary
|
|
202
|
-
run_up_distance = (
|
|
214
|
+
run_up_distance = (
|
|
215
|
+
(await self.acceleration_time.get_value()) * fly_velocity * 0.5
|
|
216
|
+
)
|
|
203
217
|
|
|
204
218
|
self._fly_completed_position = end_position + run_up_distance
|
|
205
219
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from ._pvi import create_children_from_annotations, fill_pvi_entries
|
|
2
2
|
|
|
3
|
-
__all__ = ["
|
|
3
|
+
__all__ = ["fill_pvi_entries", "create_children_from_annotations"]
|
|
@@ -10,25 +10,28 @@ from typing import (
|
|
|
10
10
|
Optional,
|
|
11
11
|
Tuple,
|
|
12
12
|
Type,
|
|
13
|
-
TypeVar,
|
|
14
13
|
Union,
|
|
15
14
|
get_args,
|
|
16
15
|
get_origin,
|
|
17
16
|
get_type_hints,
|
|
18
17
|
)
|
|
19
18
|
|
|
20
|
-
from ophyd_async.core import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
from ophyd_async.core import (
|
|
20
|
+
DEFAULT_TIMEOUT,
|
|
21
|
+
Device,
|
|
22
|
+
DeviceVector,
|
|
23
|
+
Signal,
|
|
24
|
+
SoftSignalBackend,
|
|
25
|
+
T,
|
|
26
|
+
)
|
|
27
|
+
from ophyd_async.epics.signal import (
|
|
28
|
+
PvaSignalBackend,
|
|
25
29
|
epics_signal_r,
|
|
26
30
|
epics_signal_rw,
|
|
27
31
|
epics_signal_w,
|
|
28
32
|
epics_signal_x,
|
|
29
33
|
)
|
|
30
34
|
|
|
31
|
-
T = TypeVar("T")
|
|
32
35
|
Access = FrozenSet[
|
|
33
36
|
Union[Literal["r"], Literal["w"], Literal["rw"], Literal["x"], Literal["d"]]
|
|
34
37
|
]
|
|
@@ -74,19 +77,19 @@ def _strip_device_vector(field: Union[Type[Device]]) -> Tuple[bool, Type[Device]
|
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
@dataclass
|
|
77
|
-
class
|
|
80
|
+
class _PVIEntry:
|
|
78
81
|
"""
|
|
79
82
|
A dataclass to represent a single entry in the PVI table.
|
|
80
83
|
This could either be a signal or a sub-table.
|
|
81
84
|
"""
|
|
82
85
|
|
|
83
|
-
sub_entries: Dict[str, Union[Dict[int, "
|
|
86
|
+
sub_entries: Dict[str, Union[Dict[int, "_PVIEntry"], "_PVIEntry"]]
|
|
84
87
|
pvi_pv: Optional[str] = None
|
|
85
88
|
device: Optional[Device] = None
|
|
86
89
|
common_device_type: Optional[Type[Device]] = None
|
|
87
90
|
|
|
88
91
|
|
|
89
|
-
def _verify_common_blocks(entry:
|
|
92
|
+
def _verify_common_blocks(entry: _PVIEntry, common_device: Type[Device]):
|
|
90
93
|
if not entry.sub_entries:
|
|
91
94
|
return
|
|
92
95
|
common_sub_devices = get_type_hints(common_device)
|
|
@@ -205,7 +208,7 @@ def _mock_common_blocks(device: Device, stripped_type: Optional[Type] = None):
|
|
|
205
208
|
sub_device.parent = device
|
|
206
209
|
|
|
207
210
|
|
|
208
|
-
async def _get_pvi_entries(entry:
|
|
211
|
+
async def _get_pvi_entries(entry: _PVIEntry, timeout=DEFAULT_TIMEOUT):
|
|
209
212
|
if not entry.pvi_pv or not entry.pvi_pv.endswith(":PVI"):
|
|
210
213
|
raise RuntimeError("Top level entry must be a pvi table")
|
|
211
214
|
|
|
@@ -235,7 +238,7 @@ async def _get_pvi_entries(entry: PVIEntry, timeout=DEFAULT_TIMEOUT):
|
|
|
235
238
|
else:
|
|
236
239
|
device = getattr(entry.device, sub_name, device_type())
|
|
237
240
|
|
|
238
|
-
sub_entry =
|
|
241
|
+
sub_entry = _PVIEntry(
|
|
239
242
|
device=device, common_device_type=device_type, sub_entries={}
|
|
240
243
|
)
|
|
241
244
|
|
|
@@ -257,7 +260,7 @@ async def _get_pvi_entries(entry: PVIEntry, timeout=DEFAULT_TIMEOUT):
|
|
|
257
260
|
_verify_common_blocks(entry, entry.common_device_type)
|
|
258
261
|
|
|
259
262
|
|
|
260
|
-
def _set_device_attributes(entry:
|
|
263
|
+
def _set_device_attributes(entry: _PVIEntry):
|
|
261
264
|
for sub_name, sub_entry in entry.sub_entries.items():
|
|
262
265
|
if isinstance(sub_entry, dict):
|
|
263
266
|
sub_device = DeviceVector() # type: ignore
|
|
@@ -289,7 +292,7 @@ async def fill_pvi_entries(
|
|
|
289
292
|
_mock_common_blocks(device)
|
|
290
293
|
else:
|
|
291
294
|
# check the pvi table for devices and fill the device with them
|
|
292
|
-
root_entry =
|
|
295
|
+
root_entry = _PVIEntry(
|
|
293
296
|
pvi_pv=root_pv,
|
|
294
297
|
device=device,
|
|
295
298
|
common_device_type=type(device),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from ._common import LimitPair, Limits, get_supported_values
|
|
2
|
+
from ._p4p import PvaSignalBackend
|
|
3
|
+
from ._signal import (
|
|
2
4
|
epics_signal_r,
|
|
3
5
|
epics_signal_rw,
|
|
4
6
|
epics_signal_rw_rbv,
|
|
@@ -7,6 +9,10 @@ from .signal import (
|
|
|
7
9
|
)
|
|
8
10
|
|
|
9
11
|
__all__ = [
|
|
12
|
+
"get_supported_values",
|
|
13
|
+
"LimitPair",
|
|
14
|
+
"Limits",
|
|
15
|
+
"PvaSignalBackend",
|
|
10
16
|
"epics_signal_r",
|
|
11
17
|
"epics_signal_rw",
|
|
12
18
|
"epics_signal_rw_rbv",
|
|
@@ -21,6 +21,8 @@ from bluesky.protocols import DataKey, Dtype, Reading
|
|
|
21
21
|
from epicscorelibs.ca import dbr
|
|
22
22
|
|
|
23
23
|
from ophyd_async.core import (
|
|
24
|
+
DEFAULT_TIMEOUT,
|
|
25
|
+
NotConnected,
|
|
24
26
|
ReadingValueCallback,
|
|
25
27
|
SignalBackend,
|
|
26
28
|
T,
|
|
@@ -28,9 +30,8 @@ from ophyd_async.core import (
|
|
|
28
30
|
get_unique,
|
|
29
31
|
wait_for_connection,
|
|
30
32
|
)
|
|
31
|
-
from ophyd_async.core.utils import DEFAULT_TIMEOUT, NotConnected
|
|
32
33
|
|
|
33
|
-
from .
|
|
34
|
+
from ._common import LimitPair, Limits, common_meta, get_supported_values
|
|
34
35
|
|
|
35
36
|
dbr_to_dtype: Dict[Dbr, Dtype] = {
|
|
36
37
|
dbr.DBR_STRING: "string",
|
|
@@ -46,7 +47,7 @@ def _data_key_from_augmented_value(
|
|
|
46
47
|
value: AugmentedValue,
|
|
47
48
|
*,
|
|
48
49
|
choices: Optional[List[str]] = None,
|
|
49
|
-
dtype: Optional[
|
|
50
|
+
dtype: Optional[Dtype] = None,
|
|
50
51
|
) -> DataKey:
|
|
51
52
|
"""Use the return value of get with FORMAT_CTRL to construct a DataKey
|
|
52
53
|
describing the signal. See docstring of AugmentedValue for expected
|
|
@@ -174,7 +175,7 @@ class CaBoolConverter(CaConverter):
|
|
|
174
175
|
return bool(value)
|
|
175
176
|
|
|
176
177
|
def get_datakey(self, value: AugmentedValue) -> DataKey:
|
|
177
|
-
return _data_key_from_augmented_value(value, dtype="
|
|
178
|
+
return _data_key_from_augmented_value(value, dtype="boolean")
|
|
178
179
|
|
|
179
180
|
|
|
180
181
|
class DisconnectedCaConverter(CaConverter):
|
|
@@ -228,10 +229,17 @@ def make_converter(
|
|
|
228
229
|
value = list(values.values())[0]
|
|
229
230
|
# Done the dbr check, so enough to check one of the values
|
|
230
231
|
if datatype and not isinstance(value, datatype):
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
# Allow int signals to represent float records when prec is 0
|
|
233
|
+
is_prec_zero_float = (
|
|
234
|
+
isinstance(value, float)
|
|
235
|
+
and get_unique({k: v.precision for k, v in values.items()}, "precision")
|
|
236
|
+
== 0
|
|
234
237
|
)
|
|
238
|
+
if not (datatype is int and is_prec_zero_float):
|
|
239
|
+
raise TypeError(
|
|
240
|
+
f"{pv} has type {type(value).__name__.replace('ca_', '')} "
|
|
241
|
+
+ f"not {datatype.__name__}"
|
|
242
|
+
)
|
|
235
243
|
return CaConverter(pv_dbr, None)
|
|
236
244
|
|
|
237
245
|
|
|
@@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple, Type
|
|
|
4
4
|
|
|
5
5
|
from typing_extensions import TypedDict
|
|
6
6
|
|
|
7
|
-
from ophyd_async.core
|
|
7
|
+
from ophyd_async.core import RuntimeSubsetEnum
|
|
8
8
|
|
|
9
9
|
common_meta = {
|
|
10
10
|
"units",
|
|
@@ -55,7 +55,7 @@ def get_supported_values(
|
|
|
55
55
|
f"which do not match {datatype}, which has {choices}."
|
|
56
56
|
)
|
|
57
57
|
return {x: datatype(x) if x else "_" for x in pv_choices}
|
|
58
|
-
elif datatype is None:
|
|
58
|
+
elif datatype is None or datatype is str:
|
|
59
59
|
return {x: x or "_" for x in pv_choices}
|
|
60
60
|
|
|
61
61
|
raise TypeError(
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
|
|
7
7
|
try:
|
|
8
|
-
from
|
|
8
|
+
from ._aioca import CaSignalBackend
|
|
9
9
|
except ImportError as ca_error:
|
|
10
10
|
|
|
11
11
|
class CaSignalBackend: # type: ignore
|
|
@@ -14,7 +14,7 @@ except ImportError as ca_error:
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
try:
|
|
17
|
-
from
|
|
17
|
+
from ._p4p import PvaSignalBackend
|
|
18
18
|
except ImportError as pva_error:
|
|
19
19
|
|
|
20
20
|
class PvaSignalBackend: # type: ignore
|
|
@@ -22,7 +22,7 @@ except ImportError as pva_error:
|
|
|
22
22
|
raise NotImplementedError("PVA support not available") from pva_error
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
25
|
+
class _EpicsTransport(Enum):
|
|
26
26
|
"""The sorts of transport EPICS support"""
|
|
27
27
|
|
|
28
28
|
#: Use Channel Access (using aioca library)
|
|
@@ -13,17 +13,18 @@ from p4p import Value
|
|
|
13
13
|
from p4p.client.asyncio import Context, Subscription
|
|
14
14
|
|
|
15
15
|
from ophyd_async.core import (
|
|
16
|
+
DEFAULT_TIMEOUT,
|
|
17
|
+
NotConnected,
|
|
16
18
|
ReadingValueCallback,
|
|
19
|
+
RuntimeSubsetEnum,
|
|
17
20
|
SignalBackend,
|
|
18
21
|
T,
|
|
19
22
|
get_dtype,
|
|
20
23
|
get_unique,
|
|
21
24
|
wait_for_connection,
|
|
22
25
|
)
|
|
23
|
-
from ophyd_async.core.signal_backend import RuntimeSubsetEnum
|
|
24
|
-
from ophyd_async.core.utils import DEFAULT_TIMEOUT, NotConnected
|
|
25
26
|
|
|
26
|
-
from .
|
|
27
|
+
from ._common import LimitPair, Limits, common_meta, get_supported_values
|
|
27
28
|
|
|
28
29
|
# https://mdavidsaver.github.io/p4p/values.html
|
|
29
30
|
specifier_to_dtype: Dict[str, Dtype] = {
|
|
@@ -63,7 +64,7 @@ def _data_key_from_value(
|
|
|
63
64
|
*,
|
|
64
65
|
shape: Optional[list[int]] = None,
|
|
65
66
|
choices: Optional[list[str]] = None,
|
|
66
|
-
dtype: Optional[
|
|
67
|
+
dtype: Optional[Dtype] = None,
|
|
67
68
|
) -> DataKey:
|
|
68
69
|
"""
|
|
69
70
|
Args:
|
|
@@ -84,7 +85,7 @@ def _data_key_from_value(
|
|
|
84
85
|
if isinstance(type_code, tuple):
|
|
85
86
|
dtype_numpy = ""
|
|
86
87
|
if type_code[1] == "enum_t":
|
|
87
|
-
if dtype == "
|
|
88
|
+
if dtype == "boolean":
|
|
88
89
|
dtype_numpy = "<i2"
|
|
89
90
|
else:
|
|
90
91
|
for item in type_code[2]:
|
|
@@ -240,7 +241,7 @@ class PvaEmumBoolConverter(PvaConverter):
|
|
|
240
241
|
return bool(value["value"]["index"])
|
|
241
242
|
|
|
242
243
|
def get_datakey(self, source: str, value) -> DataKey:
|
|
243
|
-
return _data_key_from_value(source, value, dtype="
|
|
244
|
+
return _data_key_from_value(source, value, dtype="boolean")
|
|
244
245
|
|
|
245
246
|
|
|
246
247
|
class PvaTableConverter(PvaConverter):
|
|
@@ -334,14 +335,17 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
|
|
|
334
335
|
return PvaEnumConverter(
|
|
335
336
|
get_supported_values(pv, datatype, datatype.choices)
|
|
336
337
|
)
|
|
337
|
-
elif (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
338
|
+
elif datatype and not issubclass(typ, datatype):
|
|
339
|
+
# Allow int signals to represent float records when prec is 0
|
|
340
|
+
is_prec_zero_float = typ is float and (
|
|
341
|
+
get_unique(
|
|
342
|
+
{k: v["display"]["precision"] for k, v in values.items()},
|
|
343
|
+
"precision",
|
|
344
|
+
)
|
|
345
|
+
== 0
|
|
346
|
+
)
|
|
347
|
+
if not (datatype is int and is_prec_zero_float):
|
|
348
|
+
raise TypeError(f"{pv} has type {typ.__name__} not {datatype.__name__}")
|
|
345
349
|
return PvaConverter()
|
|
346
350
|
elif "NTTable" in typeid:
|
|
347
351
|
return PvaTableConverter()
|
|
@@ -14,26 +14,27 @@ from ophyd_async.core import (
|
|
|
14
14
|
get_unique,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
from ._epics_transport import
|
|
17
|
+
from ._epics_transport import _EpicsTransport
|
|
18
18
|
|
|
19
|
-
_default_epics_transport =
|
|
19
|
+
_default_epics_transport = _EpicsTransport.ca
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def _transport_pv(pv: str) -> Tuple[
|
|
22
|
+
def _transport_pv(pv: str) -> Tuple[_EpicsTransport, str]:
|
|
23
23
|
split = pv.split("://", 1)
|
|
24
24
|
if len(split) > 1:
|
|
25
25
|
# We got something like pva://mydevice, so use specified comms mode
|
|
26
26
|
transport_str, pv = split
|
|
27
|
-
transport =
|
|
27
|
+
transport = _EpicsTransport[transport_str]
|
|
28
28
|
else:
|
|
29
29
|
# No comms mode specified, use the default
|
|
30
30
|
transport = _default_epics_transport
|
|
31
31
|
return transport, pv
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def _epics_signal_backend(
|
|
35
35
|
datatype: Optional[Type[T]], read_pv: str, write_pv: str
|
|
36
36
|
) -> SignalBackend[T]:
|
|
37
|
+
"""Create an epics signal backend."""
|
|
37
38
|
r_transport, r_pv = _transport_pv(read_pv)
|
|
38
39
|
w_transport, w_pv = _transport_pv(write_pv)
|
|
39
40
|
transport = get_unique({read_pv: r_transport, write_pv: w_transport}, "transports")
|
|
@@ -54,7 +55,7 @@ def epics_signal_rw(
|
|
|
54
55
|
write_pv:
|
|
55
56
|
If given, use this PV to write to, otherwise use read_pv
|
|
56
57
|
"""
|
|
57
|
-
backend =
|
|
58
|
+
backend = _epics_signal_backend(datatype, read_pv, write_pv or read_pv)
|
|
58
59
|
return SignalRW(backend, name=name)
|
|
59
60
|
|
|
60
61
|
|
|
@@ -85,7 +86,7 @@ def epics_signal_r(datatype: Type[T], read_pv: str, name: str = "") -> SignalR[T
|
|
|
85
86
|
read_pv:
|
|
86
87
|
The PV to read and monitor
|
|
87
88
|
"""
|
|
88
|
-
backend =
|
|
89
|
+
backend = _epics_signal_backend(datatype, read_pv, read_pv)
|
|
89
90
|
return SignalR(backend, name=name)
|
|
90
91
|
|
|
91
92
|
|
|
@@ -99,7 +100,7 @@ def epics_signal_w(datatype: Type[T], write_pv: str, name: str = "") -> SignalW[
|
|
|
99
100
|
write_pv:
|
|
100
101
|
The PV to write to
|
|
101
102
|
"""
|
|
102
|
-
backend =
|
|
103
|
+
backend = _epics_signal_backend(datatype, write_pv, write_pv)
|
|
103
104
|
return SignalW(backend, name=name)
|
|
104
105
|
|
|
105
106
|
|
|
@@ -111,5 +112,5 @@ def epics_signal_x(write_pv: str, name: str = "") -> SignalX:
|
|
|
111
112
|
write_pv:
|
|
112
113
|
The PV to write its initial value to on trigger
|
|
113
114
|
"""
|
|
114
|
-
backend: SignalBackend =
|
|
115
|
+
backend: SignalBackend = _epics_signal_backend(None, write_pv, write_pv)
|
|
115
116
|
return SignalX(backend, name=name)
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from ._block import (
|
|
2
2
|
CommonPandaBlocks,
|
|
3
3
|
DataBlock,
|
|
4
4
|
EnableDisableOptions,
|
|
@@ -9,9 +9,11 @@ from ._common_blocks import (
|
|
|
9
9
|
SeqBlock,
|
|
10
10
|
TimeUnits,
|
|
11
11
|
)
|
|
12
|
+
from ._control import PandaPcapController
|
|
12
13
|
from ._hdf_panda import HDFPanda
|
|
13
|
-
from ._panda_controller import PandaPcapController
|
|
14
14
|
from ._table import (
|
|
15
|
+
DatasetTable,
|
|
16
|
+
PandaHdf5DatasetType,
|
|
15
17
|
SeqTable,
|
|
16
18
|
SeqTableRow,
|
|
17
19
|
SeqTrigger,
|
|
@@ -25,28 +27,31 @@ from ._trigger import (
|
|
|
25
27
|
StaticSeqTableTriggerLogic,
|
|
26
28
|
)
|
|
27
29
|
from ._utils import phase_sorter
|
|
30
|
+
from ._writer import PandaHDFWriter
|
|
28
31
|
|
|
29
32
|
__all__ = [
|
|
30
33
|
"CommonPandaBlocks",
|
|
31
|
-
"
|
|
32
|
-
"PcompBlock",
|
|
33
|
-
"PcompInfo",
|
|
34
|
-
"PcompDirectionOptions",
|
|
34
|
+
"DataBlock",
|
|
35
35
|
"EnableDisableOptions",
|
|
36
36
|
"PcapBlock",
|
|
37
|
+
"PcompBlock",
|
|
38
|
+
"PcompDirectionOptions",
|
|
37
39
|
"PulseBlock",
|
|
38
|
-
"seq_table_from_arrays",
|
|
39
|
-
"seq_table_from_rows",
|
|
40
40
|
"SeqBlock",
|
|
41
|
-
"
|
|
41
|
+
"TimeUnits",
|
|
42
|
+
"HDFPanda",
|
|
43
|
+
"PandaHDFWriter",
|
|
44
|
+
"PandaPcapController",
|
|
45
|
+
"DatasetTable",
|
|
46
|
+
"PandaHdf5DatasetType",
|
|
42
47
|
"SeqTable",
|
|
43
48
|
"SeqTableRow",
|
|
44
49
|
"SeqTrigger",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"CommonPandABlocks",
|
|
50
|
-
"StaticSeqTableTriggerLogic",
|
|
50
|
+
"seq_table_from_arrays",
|
|
51
|
+
"seq_table_from_rows",
|
|
52
|
+
"PcompInfo",
|
|
53
|
+
"SeqTableInfo",
|
|
51
54
|
"StaticPcompTriggerLogic",
|
|
55
|
+
"StaticSeqTableTriggerLogic",
|
|
56
|
+
"phase_sorter",
|
|
52
57
|
]
|
|
@@ -2,9 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
|
|
5
|
-
from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW
|
|
6
|
-
|
|
7
|
-
from
|
|
5
|
+
from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW, SubsetEnum
|
|
6
|
+
|
|
7
|
+
from ._table import DatasetTable, SeqTable
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DataBlock(Device):
|
|
@@ -13,6 +13,8 @@ class DataBlock(Device):
|
|
|
13
13
|
hdf_file_name: SignalRW[str]
|
|
14
14
|
num_capture: SignalRW[int]
|
|
15
15
|
num_captured: SignalR[int]
|
|
16
|
+
create_directory: SignalRW[int]
|
|
17
|
+
directory_exists: SignalR[bool]
|
|
16
18
|
capture: SignalRW[bool]
|
|
17
19
|
flush_period: SignalRW[float]
|
|
18
20
|
datasets: SignalR[DatasetTable]
|