Skip to content

Commit c443171

Browse files
committed
refactor: convert PviDevice and PviTree operations into flattened free functions
1 parent 62f3761 commit c443171

File tree

3 files changed

+125
-247
lines changed

3 files changed

+125
-247
lines changed

src/fastcs/transport/epics/pva/ioc.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from p4p.server import Server, StaticProvider
44

5-
from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW
5+
from fastcs.attributes import AttrR, AttrRW, AttrW
66
from fastcs.controller_api import ControllerAPI
77
from fastcs.transport.epics.util import controller_pv_prefix
88
from fastcs.util import snake_to_pascal
@@ -12,32 +12,20 @@
1212
make_shared_read_pv,
1313
make_shared_write_pv,
1414
)
15-
from .pvi_tree import AccessModeType, PviTree
16-
17-
18-
def _attribute_to_access(attribute: Attribute) -> AccessModeType:
19-
match attribute:
20-
case AttrRW():
21-
return "rw"
22-
case AttrR():
23-
return "r"
24-
case AttrW():
25-
return "w"
26-
case _:
27-
raise ValueError(f"Unknown attribute type {type(attribute)}")
15+
from .pvi import add_pvi_info
2816

2917

3018
async def parse_attributes(
3119
root_pv_prefix: str, root_controller_api: ControllerAPI
32-
) -> list[StaticProvider]:
20+
) -> StaticProvider:
3321
"""Parses `Attribute` s into p4p signals in handlers."""
34-
pvi_tree = PviTree(root_pv_prefix)
3522
provider = StaticProvider(root_pv_prefix)
3623

3724
for controller_api in root_controller_api.walk_api():
3825
pv_prefix = controller_pv_prefix(root_pv_prefix, controller_api)
39-
40-
pvi_tree.add_sub_device(pv_prefix, controller_api.description, controller_api)
26+
provider = add_pvi_info(
27+
provider=provider, pv_prefix=pv_prefix, controller_api=controller_api
28+
)
4129

4230
for attr_name, attribute in controller_api.attributes.items():
4331
full_pv_name = f"{pv_prefix}:{snake_to_pascal(attr_name)}"
@@ -47,23 +35,19 @@ async def parse_attributes(
4735
attribute_pv_rbv = make_shared_read_pv(attribute)
4836
provider.add(f"{full_pv_name}", attribute_pv)
4937
provider.add(f"{full_pv_name}_RBV", attribute_pv_rbv)
50-
pvi_tree.add_signal(f"{full_pv_name}", "rw")
5138
case AttrR():
5239
attribute_pv = make_shared_read_pv(attribute)
5340
provider.add(f"{full_pv_name}", attribute_pv)
54-
pvi_tree.add_signal(f"{full_pv_name}", "r")
5541
case AttrW():
5642
attribute_pv = make_shared_write_pv(attribute)
5743
provider.add(f"{full_pv_name}", attribute_pv)
58-
pvi_tree.add_signal(f"{full_pv_name}", "w")
5944

6045
for attr_name, method in controller_api.command_methods.items():
6146
full_pv_name = f"{pv_prefix}:{snake_to_pascal(attr_name)}"
6247
command_pv = make_command_pv(method.fn)
6348
provider.add(f"{full_pv_name}", command_pv)
64-
pvi_tree.add_signal(f"{full_pv_name}", "x")
6549

66-
return [provider, pvi_tree.make_provider()]
50+
return provider
6751

6852

6953
class P4PIOC:
@@ -74,8 +58,8 @@ def __init__(self, pv_prefix: str, controller_api: ControllerAPI):
7458
self.controller_api = controller_api
7559

7660
async def run(self):
77-
providers = await parse_attributes(self.pv_prefix, self.controller_api)
61+
provider = await parse_attributes(self.pv_prefix, self.controller_api)
7862

7963
endless_event = asyncio.Event()
80-
with Server(providers):
64+
with Server([provider]):
8165
await endless_event.wait()
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from collections import defaultdict
2+
from typing import Literal
3+
4+
from p4p import Type, Value
5+
from p4p.nt.common import alarm, timeStamp
6+
from p4p.server import StaticProvider
7+
from p4p.server.asyncio import SharedPV
8+
9+
from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW
10+
from fastcs.controller_api import ControllerAPI
11+
from fastcs.util import snake_to_pascal
12+
13+
from .types import p4p_alarm_states, p4p_timestamp_now
14+
15+
AccessModeType = Literal["r", "w", "rw", "d", "x"]
16+
17+
18+
def _attribute_to_access(attribute: Attribute) -> AccessModeType:
19+
match attribute:
20+
case AttrRW():
21+
return "rw"
22+
case AttrR():
23+
return "r"
24+
case AttrW():
25+
return "w"
26+
case _:
27+
raise ValueError(f"Unknown attribute type {type(attribute)}")
28+
29+
30+
def add_pvi_info(
31+
provider: StaticProvider,
32+
pv_prefix: str,
33+
controller_api: ControllerAPI,
34+
description: str | None = None,
35+
) -> StaticProvider:
36+
provider.add(
37+
f"{pv_prefix}:PVI",
38+
SharedPV(initial=_make_p4p_value(pv_prefix, controller_api, description)),
39+
)
40+
return provider
41+
42+
43+
def _make_p4p_value(
44+
pv_prefix: str, controller_api: ControllerAPI, description: str | None
45+
) -> Value:
46+
display = (
47+
{"display": {"description": description}} if description is not None else {}
48+
) # Defined here so the value can be (none)
49+
50+
raw_value = _make_p4p_raw_value(pv_prefix, controller_api)
51+
p4p_type = _make_type_for_raw_value(raw_value)
52+
53+
try:
54+
return Value(
55+
p4p_type,
56+
{
57+
**p4p_alarm_states(),
58+
**p4p_timestamp_now(),
59+
**display,
60+
"value": raw_value,
61+
},
62+
)
63+
except KeyError as e:
64+
raise ValueError(f"Failed to create p4p Value from {raw_value}") from e
65+
66+
67+
def _make_p4p_raw_value(pv_prefix: str, controller_api: ControllerAPI) -> dict:
68+
p4p_raw_value = defaultdict(dict)
69+
# Sub-controller api returned if current item is a Controller
70+
for pv_leaf, sub_controller_api in controller_api.sub_apis.items():
71+
# Add Controller entry
72+
# Sub-device of a ControllerVector
73+
controller_pvi_name = f"{snake_to_pascal(pv_leaf)}"
74+
pv = f"{pv_prefix}:{controller_pvi_name}:PVI"
75+
if sub_controller_api.path[-1].isdigit():
76+
p4p_raw_value[f"__{int(pv_leaf)}"]["d"] = pv
77+
else:
78+
p4p_raw_value[controller_pvi_name]["d"] = pv
79+
for pv_leaf, attribute in controller_api.attributes.items():
80+
# Add attribute entry
81+
attr_pvi_name = f"{snake_to_pascal(pv_leaf)}"
82+
pv = f"{pv_prefix}:{attr_pvi_name}"
83+
p4p_raw_value[attr_pvi_name][_attribute_to_access(attribute)] = pv
84+
85+
return p4p_raw_value
86+
87+
88+
def _make_type_for_raw_value(raw_value: dict) -> Type:
89+
p4p_raw_type = []
90+
for pvi_group_name, access_to_field in raw_value.items():
91+
pvi_group_structure = []
92+
for access, field in access_to_field.items():
93+
if isinstance(field, str):
94+
pvi_group_structure.append((access, "s"))
95+
elif isinstance(field, dict):
96+
pvi_group_structure.append(
97+
(
98+
access,
99+
(
100+
"S",
101+
None,
102+
[(v, "s") for v, _ in field.items()],
103+
),
104+
)
105+
)
106+
107+
p4p_raw_type.append((pvi_group_name, ("S", "structure", pvi_group_structure)))
108+
109+
return Type(
110+
[
111+
("alarm", alarm),
112+
("timeStamp", timeStamp),
113+
("display", ("S", None, [("description", "s")])),
114+
("value", ("S", "structure", p4p_raw_type)),
115+
]
116+
)

0 commit comments

Comments
 (0)