diff --git a/src/ophyd_async/core/_device.py b/src/ophyd_async/core/_device.py index 2f2bc0ee87..1e330c5f25 100644 --- a/src/ophyd_async/core/_device.py +++ b/src/ophyd_async/core/_device.py @@ -290,20 +290,13 @@ class DeviceVector(MutableMapping[int, DeviceT], Device): def __init__( self, - children: Mapping[int, DeviceT], + children: Mapping[int, DeviceT] | None = None, name: str = "", + connector: DeviceConnector | None = None, ) -> None: self._children: dict[int, DeviceT] = {} - self.update(children) - super().__init__(name=name) - - def __setattr__(self, name: str, child: Any) -> None: - if name != "parent" and isinstance(child, Device): - raise AttributeError( - "DeviceVector can only have integer named children, " - "set via device_vector[i] = child" - ) - super().__setattr__(name, child) + self.update(children) if children else self.update({}) + super().__init__(name=name, connector=connector) def __getitem__(self, key: int) -> DeviceT: return self._children[key] diff --git a/src/ophyd_async/core/_device_filler.py b/src/ophyd_async/core/_device_filler.py index 86278fd201..c77fb31d31 100644 --- a/src/ophyd_async/core/_device_filler.py +++ b/src/ophyd_async/core/_device_filler.py @@ -30,10 +30,19 @@ def _get_datatype(annotation: Any) -> type | None: - """Return int from SignalRW[int].""" + """Return int from SignalRW[int] or CustomList[list[int]].""" args = get_args(annotation) if len(args) == 1 and get_origin_class(args[0]): return args[0] + + # Handle sub-classes of generic classes + for base in getattr(annotation, "__orig_bases__", ()): + origin = get_origin_class(base) + if origin: + base_args = get_args(base) + if len(base_args) == 1 and get_origin_class(base_args[0]): + return base_args[0] + return None @@ -149,7 +158,7 @@ def _scan_for_annotations(self): if issubclass(origin, Signal): self._store_signal_datatype(name, annotation) self._uncreated_signals[name] = origin - elif origin == DeviceVector: + elif origin == DeviceVector or issubclass(origin, DeviceVector): child_type = _get_datatype(annotation) child_origin = get_origin_class(child_type) if child_origin is None or not issubclass(child_origin, Device): @@ -160,7 +169,7 @@ def _scan_for_annotations(self): if issubclass(child_origin, Signal): self._store_signal_datatype(name, child_type) self._vector_device_type[_logical(name)] = child_origin - setattr(self._device, name, DeviceVector({})) + self._uncreated_devices[name] = origin else: self._uncreated_devices[name] = origin @@ -243,6 +252,9 @@ def create_device_vector_entries_to_mock(self, num: int): if not cls: msg = "Malformed device vector" raise TypeError(msg) + # Fill DeviceVector + self.fill_child_device(name, cls) + # Then handle children for i in range(1, num + 1): if issubclass(cls, Signal): self.fill_child_signal(name, cls, i) @@ -271,10 +283,6 @@ def check_filled(self, source: str): ) def _ensure_device_vector(self, name: LogicalName) -> DeviceVector: - if not hasattr(self._device, name): - # We have no type hints, so use whatever we are told - self._vector_device_type[name] = None - setattr(self._device, name, DeviceVector({})) vector = getattr(self._device, name) if not isinstance(vector, DeviceVector): self._raise(name, f"Expected DeviceVector, got {vector}") @@ -327,7 +335,7 @@ def fill_child_signal( def fill_child_device( self, name: str, - device_type: type[Device] = Device, + device_type: type[Device | DeviceVector] = Device, vector_index: int | None = None, ) -> DeviceConnectorT: """Mark a Device as filled, and return its connector for filling. @@ -346,7 +354,7 @@ def fill_child_device( elif name in self._filled_backends: # We made it and filled it so return for validation connector = self._filled_connectors[name] - elif vector_index: + elif vector_index is not None: # We need to add a new entry to a DeviceVector vector = self._ensure_device_vector(name) vector_device_type = self._vector_device_type[name] or device_type @@ -358,8 +366,13 @@ def fill_child_device( elif child := getattr(self._device, name, None): # There is an existing child, so raise self._raise(name, f"Cannot make child as it would shadow {child}") + elif device_type is DeviceVector: + # We need to add a new child DeviceVector to the top level Device + self._vector_device_type[name] = None + connector = self._device_connector_factory() + setattr(self._device, name, device_type(connector=connector, children={})) else: - # We need to add a new child to the top level Device + # We need to add a new child Device to the top level Device connector = self._device_connector_factory() setattr(self._device, name, device_type(connector=connector)) return connector diff --git a/src/ophyd_async/epics/core/__init__.py b/src/ophyd_async/epics/core/__init__.py index a7dbdf65e1..9ced422244 100644 --- a/src/ophyd_async/epics/core/__init__.py +++ b/src/ophyd_async/epics/core/__init__.py @@ -1,6 +1,6 @@ from ._epics_connector import EpicsDeviceConnector, PvSuffix from ._epics_device import EpicsDevice -from ._pvi_connector import PviDeviceConnector +from ._pvi_connector import PviDeviceConnector, PviTree, SignalDetails from ._signal import ( CaSignalBackend, PvaSignalBackend, @@ -14,6 +14,8 @@ __all__ = [ "PviDeviceConnector", + "PviTree", + "SignalDetails", "EpicsDeviceConnector", "PvSuffix", "EpicsDevice", diff --git a/src/ophyd_async/epics/core/_pvi_connector.py b/src/ophyd_async/epics/core/_pvi_connector.py index 78c9cc5590..66e91e1e14 100644 --- a/src/ophyd_async/epics/core/_pvi_connector.py +++ b/src/ophyd_async/epics/core/_pvi_connector.py @@ -1,67 +1,32 @@ from __future__ import annotations -from typing import Literal, cast +import re +from collections.abc import Mapping + +from pydantic import ( + Field, + computed_field, + field_validator, +) from ophyd_async.core import ( + ConfinedModel, Device, DeviceConnector, DeviceFiller, + DeviceVector, LazyMock, Signal, SignalR, SignalRW, SignalW, SignalX, + gather_dict, ) from ._epics_connector import fill_backend_with_prefix from ._signal import PvaSignalBackend, pvget_with_timeout -Entry = dict[str, str] - -OldPVIVector = list[Entry | None] -# The older PVI structure has vectors of the form -# structure[] ttlout -# (none) -# structure -# string d PANDABLOCKS_IOC:TTLOUT1:PVI -# structure -# string d PANDABLOCKS_IOC:TTLOUT2:PVI -# structure -# string d PANDABLOCKS_IOC:TTLOUT3:PVI - - -FastCSPVIVector = dict[Literal["d"], Entry] -# The newer pva FastCS PVI structure has vectors of the form -# structure ttlout -# structure d -# string v1 FASTCS_PANDA:Ttlout1:PVI -# string v2 FASTCS_PANDA:Ttlout2:PVI -# string v3 FASTCS_PANDA:Ttlout3:PVI -# string v4 FASTCS_PANDA:Ttlout4:PVI - - -def _get_signal_details(entry: Entry) -> tuple[type[Signal], str, str]: - match entry: - case {"r": read_pv, "w": write_pv}: - return SignalRW, read_pv, write_pv - case {"r": read_pv}: - return SignalR, read_pv, read_pv - case {"w": write_pv}: - return SignalW, write_pv, write_pv - case {"rw": read_write_pv}: - return SignalRW, read_write_pv, read_write_pv - case {"x": execute_pv}: - return SignalX, execute_pv, execute_pv - case _: - raise TypeError(f"Can't process entry {entry}") - - -def _is_device_vector_entry(entry: Entry | OldPVIVector | FastCSPVIVector) -> bool: - return isinstance(entry, list) or ( - entry.keys() == {"d"} and isinstance(entry["d"], dict) - ) - class PviDeviceConnector(DeviceConnector): """Connect to PVI structure served over PVA. @@ -78,6 +43,7 @@ class PviDeviceConnector(DeviceConnector): """ mock_device_vector_len: int = 2 + pvi_tree: PviTree | None = None def __init__(self, prefix: str = "", error_hint: str = "") -> None: # TODO: what happens if we get a leading "pva://" here? @@ -101,52 +67,347 @@ def create_children_from_annotations(self, device: Device): fill_backend_with_prefix(self.prefix, backend, annotations) self.filler.check_created() - def _fill_child(self, name: str, entry: Entry, vector_index: int | None = None): - if set(entry) == {"d"}: - connector = self.filler.fill_child_device(name, vector_index=vector_index) - connector.pvi_pv = entry["d"] - else: - signal_type, read_pv, write_pv = _get_signal_details(entry) - backend = self.filler.fill_child_signal(name, signal_type, vector_index) - backend.read_pv = read_pv - backend.write_pv = write_pv - async def connect_mock(self, device: Device, mock: LazyMock): self.filler.create_device_vector_entries_to_mock(self.mock_device_vector_len) # Set the name of the device to name all children device.set_name(device.name) return await super().connect_mock(device, mock) - def _fill_vector_child(self, name: str, entry: OldPVIVector | FastCSPVIVector): - if isinstance(entry, list): - for i, e in enumerate(entry): - if e: - self._fill_child(name, e, i) - else: - for i_string, e in entry["d"].items(): - self._fill_child(name, {"d": e}, int(i_string.lstrip("v"))) - async def connect_real( self, device: Device, timeout: float, force_reconnect: bool ) -> None: - pvi_structure = await pvget_with_timeout(self.pvi_pv, timeout) - - entries: dict[str, Entry | OldPVIVector | FastCSPVIVector] = pvi_structure[ - "value" - ].todict() - # Fill based on what PVI gives us - for name, entry in entries.items(): - if _is_device_vector_entry(entry): - self._fill_vector_child( - name, cast(OldPVIVector | FastCSPVIVector, entry) + if not self.pvi_tree: + # Top-level device, so discover PVI tree + self.pvi_tree = await PviTree.build_device_tree( + pvi_pv=self.pvi_pv, timeout=timeout + ) + # Fill all sub devices + for device_name, device_sub_tree in self.pvi_tree.sub_devices.items(): + if device_sub_tree.vector_children: + # This is a DeviceVector + # Fill DeviceVector, then handle its vector children + connector = self.filler.fill_child_device( + device_name, device_type=DeviceVector ) + connector.pvi_tree = device_sub_tree + connector.pvi_pv = device_sub_tree.pvi_pv + for ( + vector_index, + vector_child, + ) in device_sub_tree.vector_children.items(): + if device_sub_tree.is_signal_vector: + # DeviceVector of signals + if isinstance(vector_child, SignalDetails): + backend = self.filler.fill_child_signal( + device_name, vector_child.signal_type, vector_index + ) + backend.read_pv = vector_child.read_pv + backend.write_pv = vector_child.write_pv + else: + raise TypeError( + "Failed to fill DeviceVector. " + f"Expected SignalDetails, got {type(vector_child)}" + ) + else: + # DeviceVector of devices + if isinstance(vector_child, PviTree): + connector = self.filler.fill_child_device( + device_name, vector_index=vector_index + ) + connector.pvi_tree = vector_child + connector.pvi_pv = vector_child.pvi_pv + else: + raise TypeError( + "Failed to fill DeviceVector. " + f"Expected PviTree, got {type(vector_child)}" + ) else: - # This is a child - self._fill_child(name, cast(Entry, entry)) + # This is a Device + connector = self.filler.fill_child_device(device_name) + connector.pvi_tree = device_sub_tree + connector.pvi_pv = device_sub_tree.pvi_pv + # Fill all signals + for signal_name, signal_details in self.pvi_tree.signals.items(): + backend = self.filler.fill_child_signal( + signal_name, signal_details.signal_type, None + ) + backend.read_pv = signal_details.read_pv + backend.write_pv = signal_details.write_pv # Check that all the requested children have been filled suffix = f"\n{self.error_hint}" if self.error_hint else "" - self.filler.check_filled(f"{self.pvi_pv}: {entries}{suffix}") + self.filler.check_filled(f"{self.pvi_pv}: {self.pvi_tree}{suffix}") # Set the name of the device to name all children device.set_name(device.name) return await super().connect_real(device, timeout, force_reconnect) + + +class SignalDetails(ConfinedModel): + """Representation of a Signal to be constructed.""" + + signal_type: type[Signal] + read_pv: str + write_pv: str + + @classmethod + def from_entry(cls, entry: dict[str, str]) -> SignalDetails: + match entry: + case {"r": read_pv, "w": write_pv}: + return cls(signal_type=SignalRW, read_pv=read_pv, write_pv=write_pv) + + case {"rw": pv}: + return cls(signal_type=SignalRW, read_pv=pv, write_pv=pv) + + case {"r": read_pv}: + return cls(signal_type=SignalR, read_pv=read_pv, write_pv=read_pv) + + case {"w": write_pv}: + return cls(signal_type=SignalW, read_pv=write_pv, write_pv=write_pv) + + case {"x": execute_pv}: + return cls(signal_type=SignalX, read_pv=execute_pv, write_pv=execute_pv) + + case _: + raise TypeError(f"Can't process entry {entry}") + + +class PviTree(ConfinedModel): + """Representation of a PVI structure of devices and signals in a PVI query. + + Example 1: A device with sub-devices and signals + -------------------------------------- + For a PVI structure such as: + + ```json + { + "bit": {"d": "TEST-PANDA:Bits:PVI"}, + "calc": {"d": "TEST-PANDA:Calc:PVI"}, + "a": {"rw": "TEST-PANDA:Bits:A"} + } + ``` + + From "TEST-PANDA:PVI", This would be represented as: + + ```python + PviTree( + pvi_pv="TEST-PANDA:PVI", + signals={ + "a": SignalDetails( + signal_type=SignalRW, + read_pv="TEST-PANDA:Bits:A", + write_pv="TEST-PANDA:Bits:A") + }, + sub_devices={ + "bit": PviTree(...), + "calc": PviTree(...) + }, + vector_children=[] + ) + ``` + + Example 2: A device with vector children + ----------------------------------------- + If an entry like `"calc"` is a **DeviceVector** + (e.g., mirroring a fastCS controller vector), the PVI entries will look like this: + + ```json + { + "__1": {"d": "TEST-PANDA:Calc:2:PVI"}, + "__2": {"d": "TEST-PANDA:Calc:1:PVI"} + } + ``` + + This would be represented as: + + ```python + PviTree( + pvi_pv="TEST-PANDA:Calc:PVI", + signals={}, + sub_devices={}, + vector_children=[ + PviTree(pvi_pv="TEST-PANDA:Calc:2:PVI", signals={}, ...), + PviTree(pvi_pv="TEST-PANDA:Calc:1:PVI", signals={}, ...) + ] + ) + ``` + + This is similar for vectors of signals, where `vector_children` would instead + be populated with `SignalDetails` + + Example 3: A device with legacy vector children + ----------------------------------------- + Legacy PVI vector structure is supported, for backwards compatability + with pandablocks-ioc, where vector children are represented as: + + ``` + { + "calc": [None, {"d": "TEST-PANDA:Calc1:PVI"}, {"d": "TEST-PANDA:Calc2:PVI"}], + } + ``` + generate the same PviTree as in Example 2, excluding a PVI PV. + + :param pvi_pv: + The PVI PV of the device. + + :param signals: + A mapping of signal names to `SignalDetails` objects. + + :param sub_devices: + A mapping of sub-device names to their corresponding `PviTree` objects. + + :param vector_children: + A mapping of int to `PviTree` objects representing child devices of a vector + device. + + :attr is_signal_vector: + A computed property returning True if any child device in `vector_children` + is an instance of `SignalDetails`, else False. + """ + + pvi_pv: str = Field(default="") + signals: Mapping[str, SignalDetails] = Field(default_factory=dict) + sub_devices: Mapping[str, PviTree] = Field(default_factory=dict) + vector_children: Mapping[int, PviTree | SignalDetails] = Field(default_factory=dict) + + @classmethod + async def build_device_tree(cls, pvi_pv: str, timeout: float) -> PviTree: + """Recursively build a PviTree from a top level device. + + Starting from the top-level device, this classmethod performs + post-order traversal over the served PVI structure, populating + a PviTree from the bottom up. + + :param name: Device name + :param pvi_pv: Device PVI PV + :param timeout: Timeout on pvget + """ + pvi_structure = await pvget_with_timeout(pvi_pv, timeout) + + # An example entry is: {"d": "Prefix:Device:PVI", "rw": "Prefix:A"} + # these entries are stored under the parent PVI structure name + # for example, {"device": {"d": "Prefix:Device:PVI", "rw": "Prefix:A"}} + entries: dict[str, dict[str, str]] = pvi_structure["value"].todict() + signal_details = { + entry_name: SignalDetails.from_entry(entries.pop(entry_name)) + for entry_name in list(entries) + if not isinstance(entries[entry_name], list) + and set(entries[entry_name]) != {"d"} + } + + sub_trees = await gather_dict( + { + entry_name: cls._handle_legacy_entry(entry, timeout) + if isinstance(entry, list) # Found a legacy entry, try to handle + else cls.build_device_tree(entry["d"], timeout) + for entry_name, entry in entries.items() + } + ) + + vector_children: dict[int, PviTree | SignalDetails] = {} + # Filter vector children out of stand-alone devices + + for processed_entries in (sub_trees, signal_details): + for child_name in list(processed_entries): + if m := re.match(r"^__(\d+)$", child_name): + sub_tree = processed_entries.pop(child_name) + vector_children[int(m.group(1))] = sub_tree + + return PviTree( + pvi_pv=pvi_pv, + signals=signal_details, + sub_devices=sub_trees, + vector_children=vector_children, + ) + + @classmethod + async def _handle_legacy_entry( + cls, legacy_entry: list[None | dict[str, str]], timeout: float + ) -> PviTree: + """Handle legacy vector entries. + + For example; + ``` + { + "calc": [None, {"d": "TEST-PANDA:Calc1:PVI"}, {"d": "TEST-PANDA:Calc2:PVI"}] + } + ``` + + a `PviTree` is built for each device entry in this list. + """ + sub_trees = await gather_dict( + { + vector_index: cls.build_device_tree(vector_entry["d"], timeout) + for vector_index, vector_entry in enumerate(legacy_entry) + if vector_entry is not None + } + ) + + # Legacy FastCS vector should not contain child signals, + # devices, or its own PVI PV. + return PviTree( + vector_children=sub_trees, + ) + + @computed_field + @property + def is_signal_vector(self) -> bool: + """Flags if a PviTree represents a DeviceVector of Signals or Devices.""" + return any(isinstance(v, SignalDetails) for v in self.vector_children.values()) + + def __str__(self) -> str: + """Print a readable top layer of the PviTree.""" + children = { + **{ + child_name: tree.pvi_pv for child_name, tree in self.sub_devices.items() + }, + **{ + child_name: vector_child.pvi_pv + for child_name, vector_child in self.vector_children.items() + if isinstance(vector_child, PviTree) + }, + } + + signals = { + **{ + signal_name: ( + detail.signal_type.__name__, + detail.read_pv, + detail.write_pv, + ) + for signal_name, detail in self.signals.items() + }, + **{ + signal_name: ( + vector_child.signal_type.__name__, + vector_child.read_pv, + vector_child.write_pv, + ) + for signal_name, vector_child in self.vector_children.items() + if isinstance(vector_child, SignalDetails) + }, + } + return f"sub_devices={children}\nsignals={signals}" + + @field_validator("vector_children") + @classmethod + def _check_consistency_of_vector_children_type( + cls, + vector_children: Mapping[int, PviTree | SignalDetails], + ): + """Validates that parsed vector children are all of the same type.""" + if not ( + all( + isinstance(vector_child, SignalDetails) + for vector_child in vector_children.values() + ) + or all( + isinstance(vector_child, PviTree) + for vector_child in vector_children.values() + ) + ): + raise ValueError( + "Failed to validate PviTree. " + "vector_children must all be of type `SignalDetails` or `PviTree`. " + f"Received mixed type: {vector_children=}" + ) + return vector_children diff --git a/tests/system_tests/fastcs/panda/test_panda_connect.py b/tests/system_tests/fastcs/panda/test_panda_connect.py index e9012e9402..8824d8d7ea 100644 --- a/tests/system_tests/fastcs/panda/test_panda_connect.py +++ b/tests/system_tests/fastcs/panda/test_panda_connect.py @@ -5,10 +5,7 @@ import pytest -from ophyd_async.core import ( - Device, - DeviceVector, -) +from ophyd_async.core import Device, DeviceVector, Signal from ophyd_async.fastcs.core import fastcs_connector from ophyd_async.fastcs.panda import ( PcapBlock, @@ -37,12 +34,15 @@ def __init__(self, uri: str, name: str = ""): @pytest.mark.timeout(15.0 if os.name == "nt" else 4.0) async def test_panda_with_missing_blocks(panda_pva, panda_t): panda = panda_t("PANDAQSRVI:", name="mypanda") + with pytest.raises( RuntimeError, match=re.escape( "mypanda: cannot provision ['pcap'] from PANDAQSRVI:PVI: " - "{'pulse': [None, {'d': 'PANDAQSRVI:PULSE1:PVI'}]," - " 'seq': [None, {'d': 'PANDAQSRVI:SEQ1:PVI'}]}\nIs it ok?" + "sub_devices={'pulse': 'PANDAQSRVI:PULSE:PVI', " + "'ttlout': 'PANDAQSRVI:TTLOUT:PVI', 'seq': 'PANDAQSRVI:SEQ:PVI'}" + "\nsignals={}" + "\nIs it ok?" ), ): await panda.connect() @@ -52,10 +52,15 @@ async def test_panda_with_missing_blocks(panda_pva, panda_t): async def test_panda_with_extra_blocks_and_signals(panda_pva, panda_t): panda = panda_t("PANDAQSRV:") await panda.connect() - assert panda.extra # type: ignore - assert panda.extra[1] # type: ignore - assert panda.extra[2] # type: ignore - assert panda.pcap.newsignal # type: ignore + assert panda.ttlout + assert panda.ttlout[1] + assert panda.ttlout[2] + assert isinstance(panda.ttlout[1], Signal) + assert panda.extra + assert panda.extra[1] + assert panda.extra[2] + assert isinstance(panda.extra[1], Device) + assert panda.pcap.newsignal @pytest.mark.timeout(15.0 if os.name == "nt" else 5.1) diff --git a/tests/unit_tests/epics/pvi/test_pvi.py b/tests/unit_tests/epics/pvi/test_pvi.py index 1513c70ec8..19f08fef95 100644 --- a/tests/unit_tests/epics/pvi/test_pvi.py +++ b/tests/unit_tests/epics/pvi/test_pvi.py @@ -1,6 +1,5 @@ from typing import Annotated as A from typing import TypeVar -from unittest.mock import MagicMock import pytest from bluesky.protocols import HasHints, Hints @@ -16,7 +15,7 @@ init_devices, ) from ophyd_async.core import StandardReadableFormat as Format -from ophyd_async.epics.core import PviDeviceConnector +from ophyd_async.epics.core import PviDeviceConnector, SignalDetails class Block1(Device, HasHints): @@ -207,14 +206,10 @@ async def test_no_type_annotation_blocks(cls): async def test_correctly_setting_signal_type_from_signal_details( mock_entry, expected_signal_type ): - connector = PviDeviceConnector("") - connector.filler = MagicMock() if not expected_signal_type: with pytest.raises(TypeError) as exc: - connector._fill_child("signal", mock_entry) + SignalDetails.from_entry(mock_entry) assert "Can't process entry" in str(exc.value) else: - connector._fill_child("signal", mock_entry) - connector.filler.fill_child_signal.assert_called_once_with( - "signal", expected_signal_type, None - ) + details = SignalDetails.from_entry(mock_entry) + assert details.signal_type == expected_signal_type diff --git a/tests/unit_tests/fastcs/panda/db/panda.db b/tests/unit_tests/fastcs/panda/db/panda.db index 995d844d72..73748964fe 100644 --- a/tests/unit_tests/fastcs/panda/db/panda.db +++ b/tests/unit_tests/fastcs/panda/db/panda.db @@ -1,9 +1,9 @@ -record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:DELAY") +record(ao, "$(IOC_NAME=PANDAQSRV):PULSE:1:DELAY") { field(EGU, "us") - # Add to PULSE1:PVI PVA structure + # Add to PULSE:1:PVI PVA structure info(Q:group, { - "$(IOC_NAME=PANDAQSRV):PULSE1:PVI": { + "$(IOC_NAME=PANDAQSRV):PULSE:1:PVI": { "value.delay.rw": { "+channel": "NAME", "+type": "plain" @@ -12,12 +12,12 @@ record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:DELAY") }) } -record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:STEP") +record(ao, "$(IOC_NAME=PANDAQSRV):PULSE:1:STEP") { field(EGU, "us") - # Add to PULSE1:PVI PVA structure + # Add to PULSE:1:PVI PVA structure info(Q:group, { - "$(IOC_NAME=PANDAQSRV):PULSE1:PVI": { + "$(IOC_NAME=PANDAQSRV):PULSE:1:PVI": { "value.step.rw": { "+channel": "NAME", "+type": "plain" @@ -26,11 +26,11 @@ record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:STEP") }) } -record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:PULSES") +record(ao, "$(IOC_NAME=PANDAQSRV):PULSE:1:PULSES") { - # Add to PULSE1:PVI PVA structure + # Add to PULSE:1:PVI PVA structure info(Q:group, { - "$(IOC_NAME=PANDAQSRV):PULSE1:PVI": { + "$(IOC_NAME=PANDAQSRV):PULSE:1:PVI": { "value.pulses.rw": { "+channel": "NAME", "+type": "plain" @@ -39,11 +39,11 @@ record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:PULSES") }) } -record(stringout, "$(IOC_NAME=PANDAQSRV):PULSE1:ENABLE") +record(stringout, "$(IOC_NAME=PANDAQSRV):PULSE:1:ENABLE") { field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):PULSE1:PVI": { + "$(IOC_NAME=PANDAQSRV):PULSE:1:PVI": { "value.enable.rw": { "+channel": "NAME", "+type": "plain" @@ -54,12 +54,12 @@ record(stringout, "$(IOC_NAME=PANDAQSRV):PULSE1:ENABLE") #if EXCLUDE_WIDTH is set to "#", this bit is commented out. -$(EXCLUDE_WIDTH=)record(ao, "$(IOC_NAME=PANDAQSRV):PULSE1:WIDTH") +$(EXCLUDE_WIDTH=)record(ao, "$(IOC_NAME=PANDAQSRV):PULSE:1:WIDTH") $(EXCLUDE_WIDTH=){ $(EXCLUDE_WIDTH=) field(EGU, "us") $(EXCLUDE_WIDTH=) info(Q:group, { -$(EXCLUDE_WIDTH=) # Add to PULSE1:PVI PVA structure -$(EXCLUDE_WIDTH=) "$(IOC_NAME=PANDAQSRV):PULSE1:PVI": { +$(EXCLUDE_WIDTH=) # Add to PULSE:1:PVI PVA structure +$(EXCLUDE_WIDTH=) "$(IOC_NAME=PANDAQSRV):PULSE:1:PVI": { $(EXCLUDE_WIDTH=) "value.width.rw": { $(EXCLUDE_WIDTH=) "+channel": "NAME", $(EXCLUDE_WIDTH=) "+type": "plain" @@ -68,13 +68,13 @@ $(EXCLUDE_WIDTH=) } $(EXCLUDE_WIDTH=) }) $(EXCLUDE_WIDTH=)} -record(bi, "$(IOC_NAME=PANDAQSRV):SEQ1:ACTIVE") +record(bi, "$(IOC_NAME=PANDAQSRV):SEQ:1:ACTIVE") { field(ZNAM, "0") field(ONAM, "1") field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.active.r": { "+channel": "NAME", "+type": "plain" @@ -83,11 +83,11 @@ record(bi, "$(IOC_NAME=PANDAQSRV):SEQ1:ACTIVE") }) } -record(longin, "$(IOC_NAME=PANDAQSRV):SEQ1:REPEATS") +record(longin, "$(IOC_NAME=PANDAQSRV):SEQ:1:REPEATS") { field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.repeats.rw": { "+channel": "NAME", "+type": "plain" @@ -96,11 +96,11 @@ record(longin, "$(IOC_NAME=PANDAQSRV):SEQ1:REPEATS") }) } -record(ai, "$(IOC_NAME=PANDAQSRV):SEQ1:PRESCALE") +record(ai, "$(IOC_NAME=PANDAQSRV):SEQ:1:PRESCALE") { field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.prescale.rw": { "+channel": "NAME", "+type": "plain" @@ -109,7 +109,7 @@ record(ai, "$(IOC_NAME=PANDAQSRV):SEQ1:PRESCALE") }) } -record(mbbi, "$(IOC_NAME=PANDAQSRV):SEQ1:PRESCALE:UNITS") +record(mbbi, "$(IOC_NAME=PANDAQSRV):SEQ:1:PRESCALE:UNITS") { field(ZRST, "min") field(ZRVL, "0") @@ -121,7 +121,7 @@ record(mbbi, "$(IOC_NAME=PANDAQSRV):SEQ1:PRESCALE:UNITS") field(THVL, "3") field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.prescale_units.rw": { "+channel": "NAME", "+type": "plain" @@ -130,11 +130,11 @@ record(mbbi, "$(IOC_NAME=PANDAQSRV):SEQ1:PRESCALE:UNITS") }) } -record(stringout, "$(IOC_NAME=PANDAQSRV):SEQ1:ENABLE") +record(stringout, "$(IOC_NAME=PANDAQSRV):SEQ:1:ENABLE") { field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.enable.rw": { "+channel": "NAME", "+type": "plain" @@ -143,11 +143,11 @@ record(stringout, "$(IOC_NAME=PANDAQSRV):SEQ1:ENABLE") }) } -record(stringout, "$(IOC_NAME=PANDAQSRV):SEQ1:POSA") +record(stringout, "$(IOC_NAME=PANDAQSRV):SEQ:1:POSA") { field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.posa.rw": { "+channel": "NAME", "+type": "plain" @@ -164,16 +164,16 @@ record(waveform, "BOOL:PLEASE") info(Q:form, "Binary") } -# We want to add $(IOC_NAME=PANDAQSRV):PULSE1 to $(IOC_NAME=PANDAQSRV):PVI +# We want to add $(IOC_NAME=PANDAQSRV):PULSE:1 to $(IOC_NAME=PANDAQSRV):PVI # structure, but we can't create a record called # $(IOC_NAME=PANDAQSRV):PVI as it will shadow the QSRV created structure. # We make a different PV and use its input link to hack around this -record(stringin, "$(IOC_NAME=PANDAQSRV):PULSE1:_PVI") +record(stringin, "$(IOC_NAME=PANDAQSRV):PULSE:_PVI") { - field(VAL, "$(IOC_NAME=PANDAQSRV):PULSE1:PVI") + field(VAL, "$(IOC_NAME=PANDAQSRV):PULSE:PVI") info(Q:group, { "$(IOC_NAME=PANDAQSRV):PVI": { - "value.pulse[1].d": { + "value.pulse.d": { "+channel": "VAL", "+type": "plain" } @@ -181,12 +181,25 @@ record(stringin, "$(IOC_NAME=PANDAQSRV):PULSE1:_PVI") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:LABELS") { +record(stringin, "$(IOC_NAME=PANDAQSRV):PULSE:1:_PVI") +{ + field(VAL, "$(IOC_NAME=PANDAQSRV):PULSE:1:PVI") + info(Q:group, { + "$(IOC_NAME=PANDAQSRV):PULSE:PVI": { + "value.__1.d": { + "+channel": "VAL", + "+type": "plain" + } + } + }) +} + +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:LABELS") { field(FTVL, "STRING") field(NELM, "64") field(INP , {const:["Repeats", "Trigger", "Position", "Time1", "OutA1", "OutB1", "OutC1", "OutD1", "OutE1", "OutF1", "Time2", "OutA2", "OutB2", "OutC2", "OutD2", "OutE2", "OutF2"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "+id": "epics:nt/NTTable:1.0", "labels": { "+type": "plain", @@ -196,13 +209,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:LABELS") { }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:REPEATS") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:REPEATS") { field(FTVL, "USHORT") field(NELM, "4096") field(INP, {const:[1, 1, 1, 32]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.repeats": { "+type": "plain", "+channel": "VAL", @@ -212,13 +225,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:REPEATS") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:TRIGGER") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:TRIGGER") { field(FTVL, "STRING") field(NELM, "4096") field(INP, {const:["POSA>=POSITION", "POSA<=POSITION", "Immediate", "Immediate"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.trigger": { "+type": "plain", "+channel": "VAL", @@ -228,13 +241,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:TRIGGER") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:POSITION") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:POSITION") { field(FTVL, "LONG") field(NELM, "4096") field(INP, {const:["3222", "-565", "0", "0"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.position": { "+type": "plain", "+channel": "VAL", @@ -244,13 +257,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:POSITION") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:TIME1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:TIME1") { field(FTVL, "ULONG") field(NELM, "4096") field(INP, {const:["5", "0", "10", "10"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.time1": { "+type": "plain", "+channel": "VAL", @@ -260,13 +273,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:TIME1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTA1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTA1") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, false, false, true]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outa1": { "+type": "plain", "+channel": "VAL", @@ -276,13 +289,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTA1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTB1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTB1") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[false, false, true, true]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outb1": { "+type": "plain", "+channel": "VAL", @@ -292,13 +305,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTB1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTC1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTC1") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[false, true, true, false]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outc1": { "+type": "plain", "+channel": "VAL", @@ -308,13 +321,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTC1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTD1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTD1") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, true, false, true]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outd1": { "+type": "plain", "+channel": "VAL", @@ -324,13 +337,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTD1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTE1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTE1") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, false, true, false]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.oute1": { "+type": "plain", "+channel": "VAL", @@ -340,13 +353,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTE1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTF1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTF1") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, false, false, false]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outf1": { "+type": "plain", "+channel": "VAL", @@ -356,13 +369,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTF1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:TIME2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:TIME2") { field(FTVL, "ULONG") field(NELM, "4096") field(INP, {const:["0", "10", "10", "11"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.time2": { "+type": "plain", "+channel": "VAL", @@ -372,13 +385,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:TIME2") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTA2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTA2") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, false, false, true]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outa2": { "+type": "plain", "+channel": "VAL", @@ -388,13 +401,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTA2") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTB2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTB2") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[false, false, true, true]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outb2": { "+type": "plain", "+channel": "VAL", @@ -404,13 +417,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTB2") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTC2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTC2") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[false, true, true, false]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outc2": { "+type": "plain", "+channel": "VAL", @@ -420,13 +433,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTC2") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTD2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTD2") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, true, false, true]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outd2": { "+type": "plain", "+channel": "VAL", @@ -436,13 +449,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTD2") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTE2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTE2") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, false, true, false]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.oute2": { "+type": "plain", "+channel": "VAL", @@ -453,13 +466,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTE2") } # Last column has metadata -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTF2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:OUTF2") { field(FTVL, "UCHAR") field(NELM, "4096") field(INP, {const:[true, false, false, false]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE": { "value.outf2": { "+type": "plain", "+channel": "VAL", @@ -470,13 +483,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:OUTF2") }) } -# Again, SEQ1:TABLE is QSRV only, so need to make a fake PV to put it in -# SEQ1:PVI -record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:_PVI") +# Again, SEQ:1:TABLE is QSRV only, so need to make a fake PV to put it in +# SEQ:1:PVI +record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE:_PVI") { - field(VAL, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE") + field(VAL, "$(IOC_NAME=PANDAQSRV):SEQ:1:TABLE") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:PVI": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI": { "value.table.rw": { "+channel": "VAL", "+type": "plain", @@ -487,12 +500,12 @@ record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ1:TABLE:_PVI") } -record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ1:_PVI") +record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ:1:_PVI") { - field(VAL, "$(IOC_NAME=PANDAQSRV):SEQ1:PVI") + field(VAL, "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI") info(Q:group, { "$(IOC_NAME=PANDAQSRV):PVI": { - "value.seq[1].d": { + "value.__1.d": { "+channel": "VAL", "+type": "plain", "+putorder":18 @@ -501,14 +514,41 @@ record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ1:_PVI") }) } +record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ:_PVI") +{ + field(VAL, "$(IOC_NAME=PANDAQSRV):SEQ:PVI") + info(Q:group, { + "$(IOC_NAME=PANDAQSRV):PVI": { + "value.seq.d": { + "+channel": "VAL", + "+type": "plain", + "+putorder":18 + } + } + }) +} + +record(stringin, "$(IOC_NAME=PANDAQSRV):SEQ:1:_PVI") +{ + field(VAL, "$(IOC_NAME=PANDAQSRV):SEQ:1:PVI") + info(Q:group, { + "$(IOC_NAME=PANDAQSRV):SEQ:PVI": { + "value.__1.d": { + "+channel": "VAL", + "+type": "plain", + "+putorder":18 + } + } + }) +} -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE:LABELS") { +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:STRTABLE:LABELS") { field(FTVL, "STRING") field(NELM, "64") field(INP , {const:["Col1", "Col2"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:STRTABLE": { "+id": "epics:nt/NTTable:1.0", "labels": { "+type": "plain", @@ -518,13 +558,13 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE:LABELS") { }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE:COL1") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:STRTABLE:COL1") { field(FTVL, "STRING") field(NELM, "64") field(INP, {const:["Foo", "Bar"]}) info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:STRTABLE": { "value.col1": { "+type": "plain", "+channel": "VAL", @@ -534,14 +574,14 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE:COL1") }) } -record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE:COL2") +record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ:1:STRTABLE:COL2") { field(FTVL, "STRING") field(NELM, "64") field(INP, {const:["Bat", "Baz"]}) field(PINI, "YES") info(Q:group, { - "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE": { + "$(IOC_NAME=PANDAQSRV):SEQ:1:STRTABLE": { "value.col2": { "+type": "plain", "+channel": "VAL", @@ -552,6 +592,44 @@ record(waveform, "$(IOC_NAME=PANDAQSRV):SEQ1:STRTABLE:COL2") }) } +record(stringin, "$(IOC_NAME=PANDAQSRV):TTLOUT:_PVI") +{ + field(VAL, "$(IOC_NAME=PANDAQSRV):TTLOUT:PVI") + info(Q:group, { + "$(IOC_NAME=PANDAQSRV):PVI": { + "value.ttlout.d": { + "+channel": "VAL", + "+type": "plain" + } + } + }) +} + +record(stringin, "$(IOC_NAME=PANDAQSRV):TTLOUT1:VAL") +{ + field(VAL, "$(IOC_NAME=PANDAQSRV):TTLOUT1:VAL") + info(Q:group, { + "$(IOC_NAME=PANDAQSRV):TTLOUT:PVI": { + "value.__1.r": { + "+channel": "VAL", + "+type": "plain" + } + } + }) +} + +record(stringin, "$(IOC_NAME=PANDAQSRV):TTLOUT2:VAL") +{ + field(VAL, "$(IOC_NAME=PANDAQSRV):TTLOUT2:VAL") + info(Q:group, { + "$(IOC_NAME=PANDAQSRV):TTLOUT:PVI": { + "value.__2.r": { + "+channel": "VAL", + "+type": "plain" + } + } + }) +} $(EXCLUDE_PCAP=)record(bo, "$(IOC_NAME=PANDAQSRV):PCAP:ARM") $(EXCLUDE_PCAP=){ @@ -610,12 +688,13 @@ $(EXCLUDE_PCAP=) } $(EXCLUDE_PCAP=) }) $(EXCLUDE_PCAP=)} - +# This EXTRA block uses the legacy PVI structure of defining vectors +# used by pandablocks-ioc, for backwards compatibility testing purposes. $(INCLUDE_EXTRA_BLOCK=#)record(ao, "$(IOC_NAME=PANDAQSRV):EXTRA1:SIG1") $(INCLUDE_EXTRA_BLOCK=#){ $(INCLUDE_EXTRA_BLOCK=#) info(Q:group, { $(INCLUDE_EXTRA_BLOCK=#) "$(IOC_NAME=PANDAQSRV):EXTRA1:PVI": { -$(INCLUDE_EXTRA_BLOCK=#) "value.sig[1].x": { +$(INCLUDE_EXTRA_BLOCK=#) "value.sig.x": { $(INCLUDE_EXTRA_BLOCK=#) "+channel": "NAME", $(INCLUDE_EXTRA_BLOCK=#) "+type": "plain" $(INCLUDE_EXTRA_BLOCK=#) } @@ -641,7 +720,7 @@ $(INCLUDE_EXTRA_BLOCK=#)record(ao, "$(IOC_NAME=PANDAQSRV):EXTRA2:SIG1") $(INCLUDE_EXTRA_BLOCK=#){ $(INCLUDE_EXTRA_BLOCK=#) info(Q:group, { $(INCLUDE_EXTRA_BLOCK=#) "$(IOC_NAME=PANDAQSRV):EXTRA2:PVI": { -$(INCLUDE_EXTRA_BLOCK=#) "value.sig[1].x": { +$(INCLUDE_EXTRA_BLOCK=#) "value.sig.x": { $(INCLUDE_EXTRA_BLOCK=#) "+channel": "NAME", $(INCLUDE_EXTRA_BLOCK=#) "+type": "plain" $(INCLUDE_EXTRA_BLOCK=#) }