ophyd-async 0.8.0a5__py3-none-any.whl → 0.9.0a1__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 +4 -26
- ophyd_async/core/_detector.py +9 -9
- ophyd_async/core/_device.py +27 -8
- ophyd_async/core/_protocol.py +0 -28
- ophyd_async/core/_signal.py +111 -136
- ophyd_async/core/_table.py +9 -4
- ophyd_async/core/_utils.py +11 -2
- ophyd_async/epics/adaravis/_aravis_controller.py +8 -8
- ophyd_async/epics/adaravis/_aravis_io.py +4 -4
- ophyd_async/epics/adcore/_core_io.py +21 -21
- ophyd_async/epics/adcore/_core_logic.py +6 -3
- ophyd_async/epics/adcore/_hdf_writer.py +6 -3
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +35 -35
- ophyd_async/epics/adkinetix/_kinetix_controller.py +7 -7
- ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
- ophyd_async/epics/adpilatus/_pilatus.py +3 -3
- ophyd_async/epics/adpilatus/_pilatus_controller.py +4 -4
- ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
- ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
- ophyd_async/epics/advimba/_vimba_controller.py +14 -14
- ophyd_async/epics/advimba/_vimba_io.py +23 -23
- ophyd_async/epics/core/_p4p.py +19 -0
- ophyd_async/epics/core/_pvi_connector.py +4 -2
- ophyd_async/epics/core/_signal.py +9 -2
- ophyd_async/epics/core/_util.py +9 -0
- ophyd_async/epics/demo/_mover.py +2 -2
- ophyd_async/epics/demo/_sensor.py +2 -2
- ophyd_async/epics/eiger/_eiger_controller.py +10 -5
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +8 -5
- ophyd_async/epics/testing/__init__.py +24 -0
- ophyd_async/epics/testing/_example_ioc.py +107 -0
- ophyd_async/epics/testing/_utils.py +78 -0
- ophyd_async/epics/testing/test_records.db +158 -0
- ophyd_async/epics/testing/test_records_pva.db +177 -0
- ophyd_async/fastcs/core.py +2 -2
- ophyd_async/fastcs/panda/_block.py +9 -9
- ophyd_async/fastcs/panda/_control.py +2 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +4 -1
- ophyd_async/fastcs/panda/_trigger.py +7 -7
- ophyd_async/plan_stubs/_fly.py +1 -1
- ophyd_async/sim/demo/_sim_motor.py +34 -32
- ophyd_async/tango/__init__.py +0 -43
- ophyd_async/tango/{signal → core}/__init__.py +7 -2
- ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
- ophyd_async/tango/{signal → core}/_signal.py +13 -3
- ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
- ophyd_async/tango/{signal → core}/_tango_transport.py +1 -1
- ophyd_async/tango/demo/_counter.py +6 -7
- ophyd_async/tango/demo/_mover.py +8 -7
- ophyd_async/testing/__init__.py +33 -0
- ophyd_async/testing/_assert.py +128 -0
- ophyd_async/{core → testing}/_mock_signal_utils.py +12 -8
- ophyd_async/testing/_wait_for_pending.py +22 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/METADATA +49 -47
- ophyd_async-0.9.0a1.dist-info/RECORD +119 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/tango/base_devices/__init__.py +0 -4
- ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/top_level.txt +0 -0
|
@@ -32,10 +32,11 @@ def _get_signal_details(entry: Entry) -> tuple[type[Signal], str, str]:
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class PviDeviceConnector(DeviceConnector):
|
|
35
|
-
def __init__(self, prefix: str = "") -> None:
|
|
35
|
+
def __init__(self, prefix: str = "", error_hint: str = "") -> None:
|
|
36
36
|
# TODO: what happens if we get a leading "pva://" here?
|
|
37
37
|
self.prefix = prefix
|
|
38
38
|
self.pvi_pv = prefix + "PVI"
|
|
39
|
+
self.error_hint = error_hint
|
|
39
40
|
|
|
40
41
|
def create_children_from_annotations(self, device: Device):
|
|
41
42
|
if not hasattr(self, "filler"):
|
|
@@ -85,7 +86,8 @@ class PviDeviceConnector(DeviceConnector):
|
|
|
85
86
|
if e:
|
|
86
87
|
self._fill_child(name, e, i)
|
|
87
88
|
# Check that all the requested children have been filled
|
|
88
|
-
|
|
89
|
+
suffix = f"\n{self.error_hint}" if self.error_hint else ""
|
|
90
|
+
self.filler.check_filled(f"{self.pvi_pv}: {entries}{suffix}")
|
|
89
91
|
# Set the name of the device to name all children
|
|
90
92
|
device.set_name(device.name)
|
|
91
93
|
return await super().connect_real(device, timeout, force_reconnect)
|
|
@@ -14,7 +14,7 @@ from ophyd_async.core import (
|
|
|
14
14
|
get_unique,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
from ._util import EpicsSignalBackend
|
|
17
|
+
from ._util import EpicsSignalBackend, get_pv_basename_and_field
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class EpicsProtocol(Enum):
|
|
@@ -124,7 +124,14 @@ def epics_signal_rw_rbv(
|
|
|
124
124
|
read_suffix:
|
|
125
125
|
Append this suffix to the write pv to create the readback pv
|
|
126
126
|
"""
|
|
127
|
-
|
|
127
|
+
|
|
128
|
+
base_pv, field = get_pv_basename_and_field(write_pv)
|
|
129
|
+
if field is not None:
|
|
130
|
+
read_pv = f"{base_pv}{read_suffix}.{field}"
|
|
131
|
+
else:
|
|
132
|
+
read_pv = f"{write_pv}{read_suffix}"
|
|
133
|
+
|
|
134
|
+
return epics_signal_rw(datatype, read_pv, write_pv, name)
|
|
128
135
|
|
|
129
136
|
|
|
130
137
|
def epics_signal_r(
|
ophyd_async/epics/core/_util.py
CHANGED
|
@@ -12,6 +12,15 @@ from ophyd_async.core import (
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def get_pv_basename_and_field(pv: str) -> tuple[str, str | None]:
|
|
16
|
+
"""Simple utility function for extracting base pv name without field"""
|
|
17
|
+
|
|
18
|
+
if "." in pv:
|
|
19
|
+
return (pv.split(".", -1)[0], pv.split(".", -1)[1])
|
|
20
|
+
else:
|
|
21
|
+
return (pv, None)
|
|
22
|
+
|
|
23
|
+
|
|
15
24
|
def get_supported_values(
|
|
16
25
|
pv: str,
|
|
17
26
|
datatype: type,
|
ophyd_async/epics/demo/_mover.py
CHANGED
|
@@ -37,8 +37,8 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
37
37
|
|
|
38
38
|
super().__init__(name=name)
|
|
39
39
|
|
|
40
|
-
def set_name(self, name: str):
|
|
41
|
-
super().set_name(name)
|
|
40
|
+
def set_name(self, name: str, *, child_name_separator: str | None = None) -> None:
|
|
41
|
+
super().set_name(name, child_name_separator=child_name_separator)
|
|
42
42
|
# Readback should be named the same as its parent in read()
|
|
43
43
|
self.readback.set_name(name)
|
|
44
44
|
|
|
@@ -15,9 +15,9 @@ class EnergyMode(StrictEnum):
|
|
|
15
15
|
"""Energy mode for `Sensor`"""
|
|
16
16
|
|
|
17
17
|
#: Low energy mode
|
|
18
|
-
|
|
18
|
+
LOW = "Low Energy"
|
|
19
19
|
#: High energy mode
|
|
20
|
-
|
|
20
|
+
HIGH = "High Energy"
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class Sensor(StandardReadable, EpicsDevice):
|
|
@@ -11,10 +11,10 @@ from ophyd_async.core import (
|
|
|
11
11
|
from ._eiger_io import EigerDriverIO, EigerTriggerMode
|
|
12
12
|
|
|
13
13
|
EIGER_TRIGGER_MODE_MAP = {
|
|
14
|
-
DetectorTrigger.
|
|
15
|
-
DetectorTrigger.
|
|
16
|
-
DetectorTrigger.
|
|
17
|
-
DetectorTrigger.
|
|
14
|
+
DetectorTrigger.INTERNAL: EigerTriggerMode.INTERNAL,
|
|
15
|
+
DetectorTrigger.CONSTANT_GATE: EigerTriggerMode.GATE,
|
|
16
|
+
DetectorTrigger.VARIABLE_GATE: EigerTriggerMode.GATE,
|
|
17
|
+
DetectorTrigger.EDGE_TRIGGER: EigerTriggerMode.EDGE,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
|
|
@@ -55,7 +55,12 @@ class EigerController(DetectorController):
|
|
|
55
55
|
async def arm(self):
|
|
56
56
|
# TODO: Detector state should be an enum see https://github.com/DiamondLightSource/eiger-fastcs/issues/43
|
|
57
57
|
self._arm_status = set_and_wait_for_other_value(
|
|
58
|
-
self._drv.arm,
|
|
58
|
+
self._drv.arm,
|
|
59
|
+
1,
|
|
60
|
+
self._drv.state,
|
|
61
|
+
"ready",
|
|
62
|
+
timeout=DEFAULT_TIMEOUT,
|
|
63
|
+
wait_for_set_completion=False,
|
|
59
64
|
)
|
|
60
65
|
|
|
61
66
|
async def wait_for_idle(self):
|
|
@@ -3,9 +3,9 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv, epics_si
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class EigerTriggerMode(StrictEnum):
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
INTERNAL = "ints"
|
|
7
|
+
EDGE = "exts"
|
|
8
|
+
GATE = "exte"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class EigerDriverIO(Device):
|
ophyd_async/epics/motor.py
CHANGED
|
@@ -20,7 +20,7 @@ from ophyd_async.core import (
|
|
|
20
20
|
observe_value,
|
|
21
21
|
)
|
|
22
22
|
from ophyd_async.core import StandardReadableFormat as Format
|
|
23
|
-
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw,
|
|
23
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_w
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class MotorLimitsException(Exception):
|
|
@@ -76,7 +76,10 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
76
76
|
self.low_limit_travel = epics_signal_rw(float, prefix + ".LLM")
|
|
77
77
|
self.high_limit_travel = epics_signal_rw(float, prefix + ".HLM")
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
# Note:cannot use epics_signal_x here, as the motor record specifies that
|
|
80
|
+
# we must write 1 to stop the motor. Simply processing the record is not
|
|
81
|
+
# sufficient.
|
|
82
|
+
self.motor_stop = epics_signal_w(int, prefix + ".STOP")
|
|
80
83
|
# Whether set() should complete successfully or not
|
|
81
84
|
self._set_success = True
|
|
82
85
|
|
|
@@ -91,8 +94,8 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
91
94
|
|
|
92
95
|
super().__init__(name=name)
|
|
93
96
|
|
|
94
|
-
def set_name(self, name: str):
|
|
95
|
-
super().set_name(name)
|
|
97
|
+
def set_name(self, name: str, *, child_name_separator: str | None = None) -> None:
|
|
98
|
+
super().set_name(name, child_name_separator=child_name_separator)
|
|
96
99
|
# Readback should be named the same as its parent in read()
|
|
97
100
|
self.user_readback.set_name(name)
|
|
98
101
|
|
|
@@ -178,7 +181,7 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
178
181
|
self._set_success = success
|
|
179
182
|
# Put with completion will never complete as we are waiting for completion on
|
|
180
183
|
# the move above, so need to pass wait=False
|
|
181
|
-
await self.motor_stop.
|
|
184
|
+
await self.motor_stop.set(1, wait=False)
|
|
182
185
|
|
|
183
186
|
async def _prepare_velocity(
|
|
184
187
|
self, start_position: float, end_position: float, time_for_move: float
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ._example_ioc import (
|
|
2
|
+
CA_PVA_RECORDS,
|
|
3
|
+
PVA_RECORDS,
|
|
4
|
+
ExampleCaDevice,
|
|
5
|
+
ExampleEnum,
|
|
6
|
+
ExamplePvaDevice,
|
|
7
|
+
ExampleTable,
|
|
8
|
+
connect_example_device,
|
|
9
|
+
get_example_ioc,
|
|
10
|
+
)
|
|
11
|
+
from ._utils import TestingIOC, generate_random_PV_prefix
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CA_PVA_RECORDS",
|
|
15
|
+
"PVA_RECORDS",
|
|
16
|
+
"ExampleCaDevice",
|
|
17
|
+
"ExampleEnum",
|
|
18
|
+
"ExamplePvaDevice",
|
|
19
|
+
"ExampleTable",
|
|
20
|
+
"connect_example_device",
|
|
21
|
+
"get_example_ioc",
|
|
22
|
+
"TestingIOC",
|
|
23
|
+
"generate_random_PV_prefix",
|
|
24
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated as A
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ophyd_async.core import (
|
|
9
|
+
Array1D,
|
|
10
|
+
SignalRW,
|
|
11
|
+
StrictEnum,
|
|
12
|
+
Table,
|
|
13
|
+
)
|
|
14
|
+
from ophyd_async.epics.core import (
|
|
15
|
+
EpicsDevice,
|
|
16
|
+
PvSuffix,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from ._utils import TestingIOC
|
|
20
|
+
|
|
21
|
+
CA_PVA_RECORDS = str(Path(__file__).parent / "test_records.db")
|
|
22
|
+
PVA_RECORDS = str(Path(__file__).parent / "test_records_pva.db")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExampleEnum(StrictEnum):
|
|
26
|
+
A = "Aaa"
|
|
27
|
+
B = "Bbb"
|
|
28
|
+
C = "Ccc"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ExampleTable(Table):
|
|
32
|
+
bool: Array1D[np.bool_]
|
|
33
|
+
int: Array1D[np.int32]
|
|
34
|
+
float: Array1D[np.float64]
|
|
35
|
+
str: Sequence[str]
|
|
36
|
+
enum: Sequence[ExampleEnum]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ExampleCaDevice(EpicsDevice):
|
|
40
|
+
my_int: A[SignalRW[int], PvSuffix("int")]
|
|
41
|
+
my_float: A[SignalRW[float], PvSuffix("float")]
|
|
42
|
+
my_str: A[SignalRW[str], PvSuffix("str")]
|
|
43
|
+
longstr: A[SignalRW[str], PvSuffix("longstr")]
|
|
44
|
+
longstr2: A[SignalRW[str], PvSuffix("longstr2")]
|
|
45
|
+
my_bool: A[SignalRW[bool], PvSuffix("bool")]
|
|
46
|
+
enum: A[SignalRW[ExampleEnum], PvSuffix("enum")]
|
|
47
|
+
enum2: A[SignalRW[ExampleEnum], PvSuffix("enum2")]
|
|
48
|
+
bool_unnamed: A[SignalRW[bool], PvSuffix("bool_unnamed")]
|
|
49
|
+
partialint: A[SignalRW[int], PvSuffix("partialint")]
|
|
50
|
+
lessint: A[SignalRW[int], PvSuffix("lessint")]
|
|
51
|
+
uint8a: A[SignalRW[Array1D[np.uint8]], PvSuffix("uint8a")]
|
|
52
|
+
int16a: A[SignalRW[Array1D[np.int16]], PvSuffix("int16a")]
|
|
53
|
+
int32a: A[SignalRW[Array1D[np.int32]], PvSuffix("int32a")]
|
|
54
|
+
float32a: A[SignalRW[Array1D[np.float32]], PvSuffix("float32a")]
|
|
55
|
+
float64a: A[SignalRW[Array1D[np.float64]], PvSuffix("float64a")]
|
|
56
|
+
stra: A[SignalRW[Sequence[str]], PvSuffix("stra")]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ExamplePvaDevice(ExampleCaDevice): # pva can support all signal types that ca can
|
|
60
|
+
int8a: A[SignalRW[Array1D[np.int8]], PvSuffix("int8a")]
|
|
61
|
+
uint16a: A[SignalRW[Array1D[np.uint16]], PvSuffix("uint16a")]
|
|
62
|
+
uint32a: A[SignalRW[Array1D[np.uint32]], PvSuffix("uint32a")]
|
|
63
|
+
int64a: A[SignalRW[Array1D[np.int64]], PvSuffix("int64a")]
|
|
64
|
+
uint64a: A[SignalRW[Array1D[np.uint64]], PvSuffix("uint64a")]
|
|
65
|
+
table: A[SignalRW[ExampleTable], PvSuffix("table")]
|
|
66
|
+
ntndarray_data: A[SignalRW[Array1D[np.int64]], PvSuffix("ntndarray:data")]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def connect_example_device(
|
|
70
|
+
ioc: TestingIOC, protocol: Literal["ca", "pva"]
|
|
71
|
+
) -> ExamplePvaDevice | ExampleCaDevice:
|
|
72
|
+
"""Helper function to return a connected example device.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
|
|
77
|
+
ioc: TestingIOC
|
|
78
|
+
TestingIOC configured to provide the records needed for the device
|
|
79
|
+
|
|
80
|
+
protocol: Literal["ca", "pva"]
|
|
81
|
+
The transport protocol of the device
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
ExamplePvaDevice | ExampleCaDevice
|
|
86
|
+
a connected EpicsDevice with signals of many EPICS record types
|
|
87
|
+
"""
|
|
88
|
+
device_cls = ExamplePvaDevice if protocol == "pva" else ExampleCaDevice
|
|
89
|
+
device = device_cls(f"{protocol}://{ioc.prefix_for(device_cls)}")
|
|
90
|
+
await device.connect()
|
|
91
|
+
return device
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_example_ioc() -> TestingIOC:
|
|
95
|
+
"""Get TestingIOC instance with the example databases loaded.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
TestingIOC
|
|
100
|
+
instance with test_records.db loaded for ExampleCaDevice and
|
|
101
|
+
test_records.db and test_records_pva.db loaded for ExamplePvaDevice.
|
|
102
|
+
"""
|
|
103
|
+
ioc = TestingIOC()
|
|
104
|
+
ioc.database_for(PVA_RECORDS, ExamplePvaDevice)
|
|
105
|
+
ioc.database_for(CA_PVA_RECORDS, ExamplePvaDevice)
|
|
106
|
+
ioc.database_for(CA_PVA_RECORDS, ExampleCaDevice)
|
|
107
|
+
return ioc
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from aioca import purge_channel_caches
|
|
9
|
+
|
|
10
|
+
from ophyd_async.core import Device
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_random_PV_prefix() -> str:
|
|
14
|
+
return "".join(random.choice(string.ascii_lowercase) for _ in range(12)) + ":"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestingIOC:
|
|
18
|
+
_dbs: dict[type[Device], list[Path]] = {}
|
|
19
|
+
_prefixes: dict[type[Device], str] = {}
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def with_database(cls, db: Path | str): # use as a decorator
|
|
23
|
+
def inner(device_cls: type[Device]):
|
|
24
|
+
cls.database_for(db, device_cls)
|
|
25
|
+
return device_cls
|
|
26
|
+
|
|
27
|
+
return inner
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def database_for(cls, db, device_cls):
|
|
31
|
+
path = Path(db)
|
|
32
|
+
if not path.is_file():
|
|
33
|
+
raise OSError(f"{path} is not a file.")
|
|
34
|
+
if device_cls not in cls._dbs:
|
|
35
|
+
cls._dbs[device_cls] = []
|
|
36
|
+
cls._dbs[device_cls].append(path)
|
|
37
|
+
|
|
38
|
+
def prefix_for(self, device_cls):
|
|
39
|
+
# generate random prefix, return existing if already generated
|
|
40
|
+
return self._prefixes.setdefault(device_cls, generate_random_PV_prefix())
|
|
41
|
+
|
|
42
|
+
def start_ioc(self):
|
|
43
|
+
ioc_args = [
|
|
44
|
+
sys.executable,
|
|
45
|
+
"-m",
|
|
46
|
+
"epicscorelibs.ioc",
|
|
47
|
+
]
|
|
48
|
+
for device_cls, dbs in self._dbs.items():
|
|
49
|
+
prefix = self.prefix_for(device_cls)
|
|
50
|
+
for db in dbs:
|
|
51
|
+
ioc_args += ["-m", f"device={prefix}", "-d", str(db)]
|
|
52
|
+
self._process = subprocess.Popen(
|
|
53
|
+
ioc_args,
|
|
54
|
+
stdin=subprocess.PIPE,
|
|
55
|
+
stdout=subprocess.PIPE,
|
|
56
|
+
stderr=subprocess.STDOUT,
|
|
57
|
+
universal_newlines=True,
|
|
58
|
+
)
|
|
59
|
+
start_time = time.monotonic()
|
|
60
|
+
while "iocRun: All initialization complete" not in (
|
|
61
|
+
self._process.stdout.readline().strip() # type: ignore
|
|
62
|
+
):
|
|
63
|
+
if time.monotonic() - start_time > 10:
|
|
64
|
+
try:
|
|
65
|
+
print(self._process.communicate("exit()")[0])
|
|
66
|
+
except ValueError:
|
|
67
|
+
# Someone else already called communicate
|
|
68
|
+
pass
|
|
69
|
+
raise TimeoutError("IOC did not start in time")
|
|
70
|
+
|
|
71
|
+
def stop_ioc(self):
|
|
72
|
+
# close backend caches before the event loop
|
|
73
|
+
purge_channel_caches()
|
|
74
|
+
try:
|
|
75
|
+
print(self._process.communicate("exit()")[0])
|
|
76
|
+
except ValueError:
|
|
77
|
+
# Someone else already called communicate
|
|
78
|
+
pass
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
record(bo, "$(device)bool") {
|
|
2
|
+
field(ZNAM, "No")
|
|
3
|
+
field(ONAM, "Yes")
|
|
4
|
+
field(VAL, "1")
|
|
5
|
+
field(PINI, "YES")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
record(bo, "$(device)bool_unnamed") {
|
|
9
|
+
field(VAL, "1")
|
|
10
|
+
field(PINI, "YES")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
record(longout, "$(device)int") {
|
|
14
|
+
field(LLSV, "MAJOR") # LOLO is alarm
|
|
15
|
+
field(LSV, "MINOR") # LOW is warning
|
|
16
|
+
field(HSV, "MINOR") # HIGH is warning
|
|
17
|
+
field(HHSV, "MAJOR") # HIHI is alarm
|
|
18
|
+
field(HOPR, "100")
|
|
19
|
+
field(HIHI, "98")
|
|
20
|
+
field(HIGH, "96")
|
|
21
|
+
field(DRVH, "90")
|
|
22
|
+
field(DRVL, "10")
|
|
23
|
+
field(LOW, "5")
|
|
24
|
+
field(LOLO, "2")
|
|
25
|
+
field(LOPR, "0")
|
|
26
|
+
field(VAL, "42")
|
|
27
|
+
field(PINI, "YES")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
record(longout, "$(device)partialint") {
|
|
31
|
+
field(LLSV, "MAJOR") # LOLO is alarm
|
|
32
|
+
field(HHSV, "MAJOR") # HIHI is alarm
|
|
33
|
+
field(HOPR, "100")
|
|
34
|
+
field(HIHI, "98")
|
|
35
|
+
field(DRVH, "90")
|
|
36
|
+
field(DRVL, "10")
|
|
37
|
+
field(LOLO, "2")
|
|
38
|
+
field(LOPR, "0")
|
|
39
|
+
field(VAL, "42")
|
|
40
|
+
field(PINI, "YES")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
record(longout, "$(device)lessint") {
|
|
44
|
+
field(HSV, "MINOR") # LOW is warning
|
|
45
|
+
field(LSV, "MINOR") # HIGH is warning
|
|
46
|
+
field(HOPR, "100")
|
|
47
|
+
field(HIGH, "98")
|
|
48
|
+
field(LOW, "2")
|
|
49
|
+
field(LOPR, "0")
|
|
50
|
+
field(VAL, "42")
|
|
51
|
+
field(PINI, "YES")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
record(ao, "$(device)float") {
|
|
55
|
+
field(PREC, "1")
|
|
56
|
+
field(EGU, "mm")
|
|
57
|
+
field(VAL, "3.141")
|
|
58
|
+
field(PINI, "YES")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
record(ao, "$(device)float_prec_0") {
|
|
62
|
+
field(PREC, "0")
|
|
63
|
+
field(EGU, "mm")
|
|
64
|
+
field(VAL, "3")
|
|
65
|
+
field(PINI, "YES")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
record(ao, "$(device)float_prec_1") {
|
|
69
|
+
field(PREC, "1")
|
|
70
|
+
field(EGU, "mm")
|
|
71
|
+
field(VAL, "3")
|
|
72
|
+
field(PINI, "YES")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
record(stringout, "$(device)str") {
|
|
76
|
+
field(VAL, "hello")
|
|
77
|
+
field(PINI, "YES")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
record(mbbo, "$(device)enum") {
|
|
81
|
+
field(ZRST, "Aaa")
|
|
82
|
+
field(ZRVL, "5")
|
|
83
|
+
field(ONST, "Bbb")
|
|
84
|
+
field(ONVL, "6")
|
|
85
|
+
field(TWST, "Ccc")
|
|
86
|
+
field(TWVL, "7")
|
|
87
|
+
field(VAL, "1")
|
|
88
|
+
field(PINI, "YES")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
record(mbbo, "$(device)enum2") {
|
|
92
|
+
field(ZRST, "Aaa")
|
|
93
|
+
field(ONST, "Bbb")
|
|
94
|
+
field(TWST, "Ccc")
|
|
95
|
+
field(VAL, "1")
|
|
96
|
+
field(PINI, "YES")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
record(waveform, "$(device)uint8a") {
|
|
100
|
+
field(NELM, "3")
|
|
101
|
+
field(FTVL, "UCHAR")
|
|
102
|
+
field(INP, {const:[0, 255]})
|
|
103
|
+
field(PINI, "YES")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
record(waveform, "$(device)int16a") {
|
|
107
|
+
field(NELM, "3")
|
|
108
|
+
field(FTVL, "SHORT")
|
|
109
|
+
field(INP, {const:[-32768, 32767]})
|
|
110
|
+
field(PINI, "YES")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
record(waveform, "$(device)int32a") {
|
|
114
|
+
field(NELM, "3")
|
|
115
|
+
field(FTVL, "LONG")
|
|
116
|
+
field(INP, {const:[-2147483648, 2147483647]})
|
|
117
|
+
field(PINI, "YES")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
record(waveform, "$(device)float32a") {
|
|
121
|
+
field(NELM, "3")
|
|
122
|
+
field(FTVL, "FLOAT")
|
|
123
|
+
field(INP, {const:[0.000002, -123.123]})
|
|
124
|
+
field(PINI, "YES")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
record(waveform, "$(device)float64a") {
|
|
128
|
+
field(NELM, "3")
|
|
129
|
+
field(FTVL, "DOUBLE")
|
|
130
|
+
field(INP, {const:[0.1, -12345678.123]})
|
|
131
|
+
field(PINI, "YES")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
record(waveform, "$(device)stra") {
|
|
135
|
+
field(NELM, "3")
|
|
136
|
+
field(FTVL, "STRING")
|
|
137
|
+
field(INP, {const:["five", "six", "seven"]})
|
|
138
|
+
field(PINI, "YES")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
record(waveform, "$(device)longstr") {
|
|
142
|
+
field(NELM, "80")
|
|
143
|
+
field(FTVL, "CHAR")
|
|
144
|
+
field(INP, {const:"a string that is just longer than forty characters"})
|
|
145
|
+
field(PINI, "YES")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
record(lsi, "$(device)longstr2") {
|
|
149
|
+
field(SIZV, "80")
|
|
150
|
+
field(INP, {const:"a string that is just longer than forty characters"})
|
|
151
|
+
field(PINI, "YES")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
record(calc, "$(device)ticking") {
|
|
155
|
+
field(INPA, "$(device)ticking")
|
|
156
|
+
field(CALC, "A+1")
|
|
157
|
+
field(SCAN, ".1 second")
|
|
158
|
+
}
|