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.
Files changed (64) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +4 -26
  3. ophyd_async/core/_detector.py +9 -9
  4. ophyd_async/core/_device.py +27 -8
  5. ophyd_async/core/_protocol.py +0 -28
  6. ophyd_async/core/_signal.py +111 -136
  7. ophyd_async/core/_table.py +9 -4
  8. ophyd_async/core/_utils.py +11 -2
  9. ophyd_async/epics/adaravis/_aravis_controller.py +8 -8
  10. ophyd_async/epics/adaravis/_aravis_io.py +4 -4
  11. ophyd_async/epics/adcore/_core_io.py +21 -21
  12. ophyd_async/epics/adcore/_core_logic.py +6 -3
  13. ophyd_async/epics/adcore/_hdf_writer.py +6 -3
  14. ophyd_async/epics/adcore/_single_trigger.py +1 -1
  15. ophyd_async/epics/adcore/_utils.py +35 -35
  16. ophyd_async/epics/adkinetix/_kinetix_controller.py +7 -7
  17. ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
  18. ophyd_async/epics/adpilatus/_pilatus.py +3 -3
  19. ophyd_async/epics/adpilatus/_pilatus_controller.py +4 -4
  20. ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
  21. ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
  22. ophyd_async/epics/advimba/_vimba_controller.py +14 -14
  23. ophyd_async/epics/advimba/_vimba_io.py +23 -23
  24. ophyd_async/epics/core/_p4p.py +19 -0
  25. ophyd_async/epics/core/_pvi_connector.py +4 -2
  26. ophyd_async/epics/core/_signal.py +9 -2
  27. ophyd_async/epics/core/_util.py +9 -0
  28. ophyd_async/epics/demo/_mover.py +2 -2
  29. ophyd_async/epics/demo/_sensor.py +2 -2
  30. ophyd_async/epics/eiger/_eiger_controller.py +10 -5
  31. ophyd_async/epics/eiger/_eiger_io.py +3 -3
  32. ophyd_async/epics/motor.py +8 -5
  33. ophyd_async/epics/testing/__init__.py +24 -0
  34. ophyd_async/epics/testing/_example_ioc.py +107 -0
  35. ophyd_async/epics/testing/_utils.py +78 -0
  36. ophyd_async/epics/testing/test_records.db +158 -0
  37. ophyd_async/epics/testing/test_records_pva.db +177 -0
  38. ophyd_async/fastcs/core.py +2 -2
  39. ophyd_async/fastcs/panda/_block.py +9 -9
  40. ophyd_async/fastcs/panda/_control.py +2 -2
  41. ophyd_async/fastcs/panda/_hdf_panda.py +4 -1
  42. ophyd_async/fastcs/panda/_trigger.py +7 -7
  43. ophyd_async/plan_stubs/_fly.py +1 -1
  44. ophyd_async/sim/demo/_sim_motor.py +34 -32
  45. ophyd_async/tango/__init__.py +0 -43
  46. ophyd_async/tango/{signal → core}/__init__.py +7 -2
  47. ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
  48. ophyd_async/tango/{signal → core}/_signal.py +13 -3
  49. ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
  50. ophyd_async/tango/{signal → core}/_tango_transport.py +1 -1
  51. ophyd_async/tango/demo/_counter.py +6 -7
  52. ophyd_async/tango/demo/_mover.py +8 -7
  53. ophyd_async/testing/__init__.py +33 -0
  54. ophyd_async/testing/_assert.py +128 -0
  55. ophyd_async/{core → testing}/_mock_signal_utils.py +12 -8
  56. ophyd_async/testing/_wait_for_pending.py +22 -0
  57. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/METADATA +49 -47
  58. ophyd_async-0.9.0a1.dist-info/RECORD +119 -0
  59. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/WHEEL +1 -1
  60. ophyd_async/tango/base_devices/__init__.py +0 -4
  61. ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
  62. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/LICENSE +0 -0
  63. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/entry_points.txt +0 -0
  64. {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
- self.filler.check_filled(f"{self.pvi_pv}: {entries}")
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
- return epics_signal_rw(datatype, f"{write_pv}{read_suffix}", write_pv, name)
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(
@@ -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,
@@ -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
- low = "Low Energy"
18
+ LOW = "Low Energy"
19
19
  #: High energy mode
20
- high = "High Energy"
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.internal: EigerTriggerMode.internal,
15
- DetectorTrigger.constant_gate: EigerTriggerMode.gate,
16
- DetectorTrigger.variable_gate: EigerTriggerMode.gate,
17
- DetectorTrigger.edge_trigger: EigerTriggerMode.edge,
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, 1, self._drv.state, "ready", timeout=DEFAULT_TIMEOUT
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
- internal = "ints"
7
- edge = "exts"
8
- gate = "exte"
6
+ INTERNAL = "ints"
7
+ EDGE = "exts"
8
+ GATE = "exte"
9
9
 
10
10
 
11
11
  class EigerDriverIO(Device):
@@ -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, epics_signal_x
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
- self.motor_stop = epics_signal_x(prefix + ".STOP")
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.trigger(wait=False)
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
+ }