diff --git a/pyproject.toml b/pyproject.toml
index de6db933..576799c3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,7 +18,7 @@ dependencies = [
"h5py",
"softioc>=4.4.0",
"pandablocks>=0.5.3",
- "pvi>=0.6",
+ "pvi~=0.7.0",
] # Add project dependencies here, e.g. ["click", "numpy"]
dynamic = ["version"]
license.file = "LICENSE"
diff --git a/src/pandablocks_ioc/_pvi.py b/src/pandablocks_ioc/_pvi.py
index 0d7686cc..ce74d379 100644
--- a/src/pandablocks_ioc/_pvi.py
+++ b/src/pandablocks_ioc/_pvi.py
@@ -27,7 +27,7 @@
from softioc import builder
from softioc.pythonSoftIoc import RecordWrapper
-from ._types import OUT_RECORD_FUNCTIONS, EpicsName
+from ._types import OUT_RECORD_FUNCTIONS, EpicsName, epics_to_pvi_name
class PviGroup(Enum):
@@ -64,18 +64,20 @@ def add_pvi_info(
writeable: bool = record_creation_func in OUT_RECORD_FUNCTIONS
useComboBox: bool = record_creation_func == builder.mbbOut
+ pvi_name = epics_to_pvi_name(record_name)
+
if record_creation_func == builder.Action:
if record_name == "PCAP:ARM":
component = SignalRW(
- record_name,
- record_name,
- widget=ButtonPanel(actions=dict(Arm=1, Disarm=0)),
+ name=pvi_name,
+ pv=record_name,
+ widget=ButtonPanel(actions=dict(Arm="1", Disarm="0")),
read_widget=LED(),
)
access = "rw"
else:
- component = SignalX(record_name, record_name, value="")
+ component = SignalX(name=pvi_name, pv=record_name, value="")
access = "x"
elif writeable:
if useComboBox:
@@ -86,10 +88,10 @@ def add_pvi_info(
else:
widget = TextWrite(format=None)
- component = SignalRW(record_name, record_name, widget)
+ component = SignalRW(name=pvi_name, pv=record_name, widget=widget)
access = "rw"
else:
- component = SignalR(record_name, record_name, TextRead())
+ component = SignalR(name=pvi_name, pv=record_name, widget=TextRead())
access = "r"
block, field = record_name.split(":", maxsplit=1)
block_name_suffixed = f"pvi.{field.lower().replace(':', '_')}.{access}"
@@ -108,29 +110,56 @@ def add_pvi_info(
Pvi.add_pvi_info(record_name=record_name, group=group, component=component)
-_positions_table_group = Group("POSITIONS_TABLE", Grid(labelled=True), children=[])
+_positions_table_group = Group(
+ name="PositionsTable", layout=Grid(labelled=True), children=[]
+)
_positions_table_headers = ["VALUE", "UNITS", "SCALE", "OFFSET", "CAPTURE"]
# TODO: Replicate this for the BITS table
def add_positions_table_row(
- record_name: str,
- value_record_name: str,
- units_record_name: str,
- scale_record_name: str,
- offset_record_name: str,
- capture_record_name: str,
+ record_name: EpicsName,
+ value_record_name: EpicsName,
+ units_record_name: EpicsName,
+ scale_record_name: EpicsName,
+ offset_record_name: EpicsName,
+ capture_record_name: EpicsName,
) -> None:
"""Add a Row to the Positions table"""
# TODO: Use the Components defined in _positions_columns_defs to
# create the children, which will make it more obvious which
# component is for which column
children = [
- SignalR(value_record_name, value_record_name, TextWrite()),
- SignalRW(units_record_name, units_record_name, TextWrite()),
- SignalRW(scale_record_name, scale_record_name, TextWrite()),
- SignalRW(offset_record_name, offset_record_name, TextWrite()),
- SignalRW(capture_record_name, capture_record_name, TextWrite()),
+ SignalR(
+ name=epics_to_pvi_name(value_record_name),
+ label=value_record_name,
+ pv=value_record_name,
+ widget=TextRead(),
+ ),
+ SignalRW(
+ name=epics_to_pvi_name(units_record_name),
+ label=units_record_name,
+ pv=units_record_name,
+ widget=TextWrite(),
+ ),
+ SignalRW(
+ name=epics_to_pvi_name(scale_record_name),
+ label=scale_record_name,
+ pv=scale_record_name,
+ widget=TextWrite(),
+ ),
+ SignalRW(
+ name=epics_to_pvi_name(offset_record_name),
+ label=offset_record_name,
+ pv=offset_record_name,
+ widget=TextWrite(),
+ ),
+ SignalRW(
+ name=epics_to_pvi_name(capture_record_name),
+ label=capture_record_name,
+ pv=capture_record_name,
+ widget=TextWrite(),
+ ),
]
row = Row()
@@ -138,9 +167,10 @@ def add_positions_table_row(
row.header = _positions_table_headers
row_group = Group(
- record_name,
- row,
- children,
+ name=epics_to_pvi_name(record_name),
+ label=record_name,
+ layout=row,
+ children=children,
)
_positions_table_group.children.append(row_group)
@@ -156,9 +186,9 @@ class Pvi:
# to the positions table
_general_device_refs = {
"CAPTURE": DeviceRef(
- "AllPostionCaptureParameters",
- "CAPTURE",
- "PandA_POSITIONS_TABLE",
+ name="AllPostionCaptureParameters",
+ pv="CAPTURE",
+ ui="PandA_PositionsTable",
)
}
@@ -205,9 +235,11 @@ def create_pvi_records(record_prefix: str):
if PviGroup.NONE in v:
children.extend(v.pop(PviGroup.NONE))
for group, components in v.items():
- children.append(Group(group.name, Grid(), components))
+ children.append(
+ Group(name=group.name, layout=Grid(), children=components)
+ )
- device = Device(block_name, children=children)
+ device = Device(label=block_name, children=children)
devices.append(device)
# Add PVI structure. Unfortunately we need something in the database
@@ -238,16 +270,25 @@ def create_pvi_records(record_prefix: str):
# TODO: Properly add this to list of screens, add a PV, maybe roll into
# the "PLACEHOLDER" Device?
# Add Tables to a new top level screen
- top_device = Device("PandA", children=[_positions_table_group])
+ top_device = Device(label="PandA", children=[_positions_table_group])
devices.append(top_device)
# Create top level Device, with references to all child Devices
- index_device_refs = [
- DeviceRef(x, x, x.replace(":PVI", "")) for x in pvi_records
- ]
+ index_device_refs = []
+ for pvi_record in pvi_records:
+ record_with_no_suffix = EpicsName(pvi_record.replace(":PVI", ""))
+ name = epics_to_pvi_name(record_with_no_suffix)
+ index_device_refs.append(
+ DeviceRef(
+ name=name,
+ label=record_with_no_suffix,
+ pv=pvi_record,
+ ui=record_with_no_suffix,
+ )
+ )
# # TODO: What should the label be?
- device = Device("index", children=index_device_refs)
+ device = Device(label="index", children=index_device_refs)
devices.append(device)
# TODO: label widths need some tweaking - some are pretty long right now
diff --git a/src/pandablocks_ioc/_tables.py b/src/pandablocks_ioc/_tables.py
index e8eeff6b..76f11adf 100644
--- a/src/pandablocks_ioc/_tables.py
+++ b/src/pandablocks_ioc/_tables.py
@@ -25,6 +25,7 @@
RecordInfo,
RecordValue,
epics_to_panda_name,
+ epics_to_pvi_name,
trim_description,
)
@@ -56,7 +57,7 @@ class TableFieldRecordContainer:
def make_bit_order(
- table_field_records: Dict[str, TableFieldRecordContainer]
+ table_field_records: Dict[str, TableFieldRecordContainer],
) -> Dict[str, TableFieldRecordContainer]:
return dict(
sorted(table_field_records.items(), key=lambda item: item[1].field.bit_low)
@@ -144,12 +145,14 @@ def __init__(
)
self.all_values_dict = all_values_dict
+ pvi_table_name = epics_to_pvi_name(table_name)
+
# The PVI group to put all records into
pvi_group = PviGroup.PARAMETERS
Pvi.add_pvi_info(
table_name,
pvi_group,
- SignalRW(table_name, table_name, TableWrite([])),
+ SignalRW(name=pvi_table_name, pv=table_name, widget=TableWrite(widgets=[])),
)
# Note that the table_updater's table_fields are guaranteed sorted in bit order,
@@ -216,10 +219,11 @@ def __init__(
initial_value=TableModeEnum.VIEW.value,
on_update=self.update_mode,
)
+ pvi_name = epics_to_pvi_name(mode_record_name)
Pvi.add_pvi_info(
mode_record_name,
pvi_group,
- SignalRW(mode_record_name, mode_record_name, ComboBox()),
+ SignalRW(name=pvi_name, pv=mode_record_name, widget=ComboBox()),
)
self.mode_record_info = RecordInfo(lambda x: x, labels, False)
diff --git a/src/pandablocks_ioc/_types.py b/src/pandablocks_ioc/_types.py
index 7ae02c36..05d6c8bc 100644
--- a/src/pandablocks_ioc/_types.py
+++ b/src/pandablocks_ioc/_types.py
@@ -1,5 +1,6 @@
# Various new or derived types/classes and helper functions for the IOC module
import logging
+import re
from dataclasses import dataclass, field
from typing import Any, Awaitable, Callable, List, NewType, Optional, Union
@@ -20,6 +21,8 @@ class InErrorException(Exception):
EpicsName = NewType("EpicsName", str)
# PandA format, i.e. "." dividers
PandAName = NewType("PandAName", str)
+# No dividers and PascalCase
+PviName = NewType("PviName", str)
def panda_to_epics_name(field_name: PandAName) -> EpicsName:
@@ -34,6 +37,20 @@ def epics_to_panda_name(field_name: EpicsName) -> PandAName:
return PandAName(field_name.replace(":", "."))
+def epics_to_pvi_name(field_name: EpicsName) -> PviName:
+ """Converts EPICS naming convention to PVI naming convention.
+ For example PANDA:PCAP:TRIG_EDGE -> TrigEdge."""
+ relevant_section = field_name.split(":")[-1]
+ words = relevant_section.replace("-", "_").split("_")
+ capitalised_word = "".join(word.capitalize() for word in words)
+
+ # We don't want to allow any non-alphanumeric characters.
+ formatted_word = re.search(r"[A-Za-z0-9]+", capitalised_word)
+ assert formatted_word
+
+ return PviName(formatted_word.group())
+
+
def device_and_record_to_panda_name(field_name: EpicsName) -> PandAName:
"""Convert an EPICS naming convention (including Device prefix) to PandA
convention."""
diff --git a/tests/test-bobfiles/HDF5.bob b/tests/test-bobfiles/HDF5.bob
index 59627584..51d60d04 100644
--- a/tests/test-bobfiles/HDF5.bob
+++ b/tests/test-bobfiles/HDF5.bob
@@ -34,7 +34,7 @@
true
Label
- HDF5: File Path
+ Filepath
0
0
250
@@ -52,7 +52,7 @@
Label
- HDF5: File Name
+ Filename
0
25
250
@@ -70,7 +70,7 @@
Label
- HDF5: Num Capture
+ Numcapture
0
50
250
@@ -87,7 +87,7 @@
Label
- HDF5: Flush Period
+ Flushperiod
0
75
250
@@ -104,7 +104,7 @@
Label
- HDF5: Capture
+ Capture
0
100
250
@@ -129,7 +129,7 @@
true
Label
- HDF5: Status
+ Status
0
0
250
@@ -150,7 +150,7 @@
Label
- HDF5: Capturing
+ Capturing
0
25
250
diff --git a/tests/test-bobfiles/PCAP.bob b/tests/test-bobfiles/PCAP.bob
index 3ac71343..08454ae4 100644
--- a/tests/test-bobfiles/PCAP.bob
+++ b/tests/test-bobfiles/PCAP.bob
@@ -34,7 +34,7 @@
true
Label
- PCAP: LABEL
+ Label
0
0
250
@@ -52,7 +52,7 @@
Label
- PCAP: ARM
+ Arm
0
25
250
@@ -94,7 +94,7 @@
LED
- TEST_PREFIX:
+ TEST_PREFIX:PCAP:ARM
350
25
20
@@ -102,7 +102,7 @@
Label
- PCAP: GATE
+ Gate
0
50
250
@@ -120,7 +120,7 @@
Label
- PCAP: GATE: DELAY
+ Delay
0
75
250
@@ -145,7 +145,7 @@
true
Label
- PCAP: TRIG_ EDGE
+ Trig Edge
0
0
250
diff --git a/tests/test-bobfiles/PULSE.bob b/tests/test-bobfiles/PULSE.bob
index fdcf3f19..f1045ecb 100644
--- a/tests/test-bobfiles/PULSE.bob
+++ b/tests/test-bobfiles/PULSE.bob
@@ -34,7 +34,7 @@
true
Label
- PULSE: DELAY
+ Delay
0
0
250
@@ -51,7 +51,7 @@
Label
- PULSE: DELAY: UNITS
+ Units
0
25
250
diff --git a/tests/test-bobfiles/PandA.bob b/tests/test-bobfiles/PandA.bob
index 4a8da246..7e9133dd 100644
--- a/tests/test-bobfiles/PandA.bob
+++ b/tests/test-bobfiles/PandA.bob
@@ -26,7 +26,7 @@
1
- POSITIONS_ TABLE
+ Positions Table
5
30
36
diff --git a/tests/test-bobfiles/SEQ.bob b/tests/test-bobfiles/SEQ.bob
index 823f3f84..db99613a 100644
--- a/tests/test-bobfiles/SEQ.bob
+++ b/tests/test-bobfiles/SEQ.bob
@@ -42,7 +42,7 @@
Label
- SEQ: TABLE: MODE
+ Mode
0
205
250
diff --git a/tests/test-bobfiles/index.bob b/tests/test-bobfiles/index.bob
index 7a6b2418..77167918 100644
--- a/tests/test-bobfiles/index.bob
+++ b/tests/test-bobfiles/index.bob
@@ -27,7 +27,7 @@
Label
- PCAP: PVI
+ PCAP
23
30
250
@@ -42,7 +42,7 @@
Open Display
- PCAP: PVI
+ PCAP
278
30
125
@@ -51,7 +51,7 @@
Label
- HDF5: PVI
+ HDF5
23
55
250
@@ -66,7 +66,7 @@
Open Display
- HDF5: PVI
+ HDF5
278
55
125
@@ -75,7 +75,7 @@
Label
- SEQ: PVI
+ SEQ
23
80
250
@@ -90,7 +90,7 @@
Open Display
- SEQ: PVI
+ SEQ
278
80
125
@@ -99,7 +99,7 @@
Label
- PULSE: PVI
+ PULSE
23
105
250
@@ -114,7 +114,7 @@
Open Display
- PULSE: PVI
+ PULSE
278
105
125
diff --git a/tests/test_ioc_system.py b/tests/test_ioc_system.py
index 0634a005..bab4d673 100644
--- a/tests/test_ioc_system.py
+++ b/tests/test_ioc_system.py
@@ -385,10 +385,11 @@ async def test_create_bobfiles_deletes_existing_files_with_clear_bobfiles(
non_bobfile.touch()
Pvi.configure_pvi(tmp_path, True)
+ pv = new_random_test_prefix + ":PCAP:TRIG_EDGE"
Pvi.add_pvi_info(
- new_random_test_prefix + ":PCAP:TRIG_EDGE",
+ pv,
PviGroup.PARAMETERS,
- SignalX("TRIG_EDGE", "Falling"),
+ SignalX(name="TrigEdge", pv=pv, value="Falling"),
)
Pvi.create_pvi_records(new_random_test_prefix)
diff --git a/tests/test_types.py b/tests/test_types.py
index c6cedb9f..232935fe 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1,7 +1,10 @@
+import pytest
+
from pandablocks_ioc._types import (
EpicsName,
PandAName,
epics_to_panda_name,
+ epics_to_pvi_name,
panda_to_epics_name,
trim_description,
trim_string_value,
@@ -23,6 +26,21 @@ def test_panda_to_epics_and_back_name_conversion() -> None:
) == PandAName("ABC.123.456")
+@pytest.mark.parametrize(
+ "arg_result",
+ [
+ ("WOW:WHAT:A_THINGY", "AThingy"),
+ ("WOW:WHAT:A-THINGY", "AThingy"),
+ ("WOW:WHAT:aTHINGY", "Athingy"),
+ ("WOW:WHAT:A_THINGY123", "AThingy123"),
+ ("WOW:WHAT:A-THINGY_123", "AThingy123"),
+ ],
+)
+def test_epics_to_pvi_name(arg_result):
+ arg, result = arg_result
+ assert epics_to_pvi_name(arg) == result
+
+
def test_string_value():
"""Test trim_string_values for a few cases"""
assert trim_string_value("ABC", "SomeRecordName") == "ABC"