Skip to content

Commit f460d54

Browse files
authored
Merge pull request #54 from DiamondLightSource/pvi-structure
Add info tags to define PVI structure from controllers
2 parents 4370011 + 1ea93a7 commit f460d54

File tree

8 files changed

+378
-53
lines changed

8 files changed

+378
-53
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ dev = [
4141
"sphinx-design",
4242
"tox-direct",
4343
"types-mock",
44+
"aioca",
45+
"p4p",
4446
]
4547

4648
[project.scripts]

src/fastcs/backend.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
from collections import defaultdict
33
from collections.abc import Callable
4+
from concurrent.futures import Future
45
from types import MethodType
56

67
from softioc.asyncio_dispatcher import AsyncioDispatcher
@@ -20,6 +21,7 @@ def __init__(
2021
self._controller = controller
2122

2223
self._initial_tasks = [controller.connect]
24+
self._scan_tasks: list[Future] = []
2325

2426
asyncio.run_coroutine_threadsafe(
2527
self._controller.initialise(), self._loop

src/fastcs/backends/epics/ioc.py

Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from collections.abc import Callable
22
from dataclasses import dataclass
33
from types import MethodType
4-
from typing import Any
4+
from typing import Any, Literal
55

66
from softioc import builder, softioc
77
from softioc.asyncio_dispatcher import AsyncioDispatcher
88
from softioc.pythonSoftIoc import RecordWrapper
99

1010
from fastcs.attributes import AttrR, AttrRW, AttrW
11+
from fastcs.controller import BaseController
1112
from fastcs.datatypes import Bool, DataType, Float, Int, String
1213
from fastcs.exceptions import FastCSException
1314
from fastcs.mapping import Mapping
@@ -20,10 +21,11 @@ class EpicsIOCOptions:
2021

2122
class EpicsIOC:
2223
def __init__(self, pv_prefix: str, mapping: Mapping):
23-
builder.SetDeviceName(pv_prefix)
24+
_add_pvi_info(f"{pv_prefix}:PVI")
25+
_add_sub_controller_pvi_info(pv_prefix, mapping.controller)
2426

25-
_create_and_link_attribute_pvs(mapping)
26-
_create_and_link_command_pvs(mapping)
27+
_create_and_link_attribute_pvs(pv_prefix, mapping)
28+
_create_and_link_command_pvs(pv_prefix, mapping)
2729

2830
def run(
2931
self,
@@ -40,95 +42,208 @@ def run(
4042
softioc.interactive_ioc(context)
4143

4244

43-
def _create_and_link_attribute_pvs(mapping: Mapping) -> None:
45+
def _add_pvi_info(
46+
pvi: str,
47+
parent_pvi: str = "",
48+
name: str = "",
49+
):
50+
"""Add PVI metadata for a controller.
51+
52+
Args:
53+
pvi: PVI PV of controller
54+
parent_pvi: PVI PV of parent controller
55+
name: Name to register controller with parent as
56+
57+
"""
58+
# Create a record to attach the info tags to
59+
record = builder.longStringIn(
60+
f"{pvi}_PV",
61+
initial_value=pvi,
62+
DESC="The records in this controller",
63+
)
64+
65+
# Create PVI PV in preparation for adding attribute info tags to it
66+
q_group = {
67+
pvi: {
68+
"+id": "epics:nt/NTPVI:1.0",
69+
"display.description": {"+type": "plain", "+channel": "DESC"},
70+
"": {"+type": "meta", "+channel": "VAL"},
71+
}
72+
}
73+
# If this controller has a parent, add a link in the parent to this controller
74+
if parent_pvi and name:
75+
q_group.update(
76+
{
77+
parent_pvi: {
78+
f"value.{name}.d": {
79+
"+channel": "VAL",
80+
"+type": "plain",
81+
"+trigger": f"value.{name}.d",
82+
}
83+
}
84+
}
85+
)
86+
87+
record.add_info("Q:group", q_group)
88+
89+
90+
def _add_sub_controller_pvi_info(pv_prefix: str, parent: BaseController):
91+
"""Add PVI references from controller to its sub controllers, recursively.
92+
93+
Args:
94+
pv_prefix: PV Prefix of IOC
95+
parent: Controller to add PVI refs for
96+
97+
"""
98+
parent_pvi = ":".join([pv_prefix] + parent.path + ["PVI"])
99+
100+
for child in parent.get_sub_controllers().values():
101+
child_pvi = ":".join([pv_prefix] + child.path + ["PVI"])
102+
child_name = child.path[-1].lower()
103+
104+
_add_pvi_info(child_pvi, parent_pvi, child_name)
105+
106+
_add_sub_controller_pvi_info(pv_prefix, child)
107+
108+
109+
def _create_and_link_attribute_pvs(pv_prefix: str, mapping: Mapping) -> None:
44110
for single_mapping in mapping.get_controller_mappings():
45111
path = single_mapping.controller.path
46112
for attr_name, attribute in single_mapping.attributes.items():
47-
attr_name = attr_name.title().replace("_", "")
48-
pv_name = f"{':'.join(path)}:{attr_name}" if path else attr_name
113+
pv_name = attr_name.title().replace("_", "")
114+
_pv_prefix = ":".join([pv_prefix] + path)
49115

50116
match attribute:
51117
case AttrRW():
52-
_create_and_link_read_pv(pv_name + "_RBV", attribute)
53-
_create_and_link_write_pv(pv_name, attribute)
118+
_create_and_link_read_pv(
119+
_pv_prefix, f"{pv_name}_RBV", attr_name, attribute
120+
)
121+
_create_and_link_write_pv(_pv_prefix, pv_name, attr_name, attribute)
54122
case AttrR():
55-
_create_and_link_read_pv(pv_name, attribute)
123+
_create_and_link_read_pv(_pv_prefix, pv_name, attr_name, attribute)
56124
case AttrW():
57-
_create_and_link_write_pv(pv_name, attribute)
125+
_create_and_link_write_pv(_pv_prefix, pv_name, attr_name, attribute)
126+
58127

128+
def _create_and_link_read_pv(
129+
pv_prefix: str, pv_name: str, attr_name: str, attribute: AttrR
130+
) -> None:
131+
record = _get_input_record(f"{pv_prefix}:{pv_name}", attribute.datatype)
59132

60-
def _create_and_link_read_pv(pv_name: str, attribute: AttrR) -> None:
61-
record = _get_input_record(pv_name, attribute.datatype)
133+
_add_attr_pvi_info(record, pv_prefix, attr_name, "r")
62134

63135
async def async_wrapper(v):
64136
record.set(v)
65137

66138
attribute.set_update_callback(async_wrapper)
67139

68140

69-
def _get_input_record(pv_name: str, datatype: DataType) -> RecordWrapper:
141+
def _get_input_record(pv: str, datatype: DataType) -> RecordWrapper:
70142
match datatype:
71143
case Bool(znam, onam):
72-
return builder.boolIn(pv_name, ZNAM=znam, ONAM=onam)
144+
return builder.boolIn(pv, ZNAM=znam, ONAM=onam)
73145
case Int():
74-
return builder.longIn(pv_name)
146+
return builder.longIn(pv)
75147
case Float(prec):
76-
return builder.aIn(pv_name, PREC=prec)
148+
return builder.aIn(pv, PREC=prec)
77149
case String():
78-
return builder.longStringIn(pv_name)
150+
return builder.longStringIn(pv)
79151
case _:
80152
raise FastCSException(f"Unsupported type {type(datatype)}: {datatype}")
81153

82154

83-
def _create_and_link_write_pv(pv_name: str, attribute: AttrW) -> None:
155+
def _create_and_link_write_pv(
156+
pv_prefix: str, pv_name: str, attr_name: str, attribute: AttrW
157+
) -> None:
84158
record = _get_output_record(
85-
pv_name, attribute.datatype, on_update=attribute.process_without_display_update
159+
f"{pv_prefix}:{pv_name}",
160+
attribute.datatype,
161+
on_update=attribute.process_without_display_update,
86162
)
87163

164+
_add_attr_pvi_info(record, pv_prefix, attr_name, "w")
165+
88166
async def async_wrapper(v):
89167
record.set(v, process=False)
90168

91169
attribute.set_write_display_callback(async_wrapper)
92170

93171

94-
def _get_output_record(pv_name: str, datatype: DataType, on_update: Callable) -> Any:
172+
def _get_output_record(pv: str, datatype: DataType, on_update: Callable) -> Any:
95173
match datatype:
96174
case Bool(znam, onam):
97175
return builder.boolOut(
98-
pv_name,
176+
pv,
99177
ZNAM=znam,
100178
ONAM=onam,
101179
always_update=True,
102180
on_update=on_update,
103181
)
104182
case Int():
105-
return builder.longOut(pv_name, always_update=True, on_update=on_update)
183+
return builder.longOut(pv, always_update=True, on_update=on_update)
106184
case Float(prec):
107-
return builder.aOut(
108-
pv_name, always_update=True, on_update=on_update, PREC=prec
109-
)
185+
return builder.aOut(pv, always_update=True, on_update=on_update, PREC=prec)
110186
case String():
111-
return builder.longStringOut(
112-
pv_name, always_update=True, on_update=on_update
113-
)
187+
return builder.longStringOut(pv, always_update=True, on_update=on_update)
114188
case _:
115189
raise FastCSException(f"Unsupported type {type(datatype)}: {datatype}")
116190

117191

118-
def _create_and_link_command_pvs(mapping: Mapping) -> None:
192+
def _create_and_link_command_pvs(pv_prefix: str, mapping: Mapping) -> None:
119193
for single_mapping in mapping.get_controller_mappings():
120194
path = single_mapping.controller.path
121195
for attr_name, method in single_mapping.command_methods.items():
122-
attr_name = attr_name.title().replace("_", "")
123-
pv_name = f"{':'.join(path)}:{attr_name}" if path else attr_name
196+
pv_name = attr_name.title().replace("_", "")
197+
_pv_prefix = ":".join([pv_prefix] + path)
124198

125199
_create_and_link_command_pv(
126-
pv_name, MethodType(method.fn, single_mapping.controller)
200+
_pv_prefix,
201+
pv_name,
202+
attr_name,
203+
MethodType(method.fn, single_mapping.controller),
127204
)
128205

129206

130-
def _create_and_link_command_pv(pv_name: str, method: Callable) -> None:
207+
def _create_and_link_command_pv(
208+
pv_prefix: str, pv_name: str, attr_name: str, method: Callable
209+
) -> None:
131210
async def wrapped_method(_: Any):
132211
await method()
133212

134-
builder.aOut(pv_name, initial_value=0, always_update=True, on_update=wrapped_method)
213+
record = builder.aOut(
214+
f"{pv_prefix}:{pv_name}",
215+
initial_value=0,
216+
always_update=True,
217+
on_update=wrapped_method,
218+
)
219+
220+
_add_attr_pvi_info(record, pv_prefix, attr_name, "x")
221+
222+
223+
def _add_attr_pvi_info(
224+
record: RecordWrapper,
225+
prefix: str,
226+
name: str,
227+
access_mode: Literal["r", "w", "rw", "x"],
228+
):
229+
"""Add an info tag to a record to include it in the PVI for the controller.
230+
231+
Args:
232+
record: Record to add info tag to
233+
prefix: PV prefix of controller
234+
name: Name of parameter to add to PVI
235+
access_mode: Access mode of parameter
236+
237+
"""
238+
record.add_info(
239+
"Q:group",
240+
{
241+
f"{prefix}:PVI": {
242+
f"value.{name}.{access_mode}": {
243+
"+channel": "NAME",
244+
"+type": "plain",
245+
"+trigger": f"value.{name}.{access_mode}",
246+
}
247+
}
248+
},
249+
)

0 commit comments

Comments
 (0)