1
1
from collections .abc import Callable
2
2
from dataclasses import dataclass
3
3
from types import MethodType
4
- from typing import Any
4
+ from typing import Any , Literal
5
5
6
6
from softioc import builder , softioc
7
7
from softioc .asyncio_dispatcher import AsyncioDispatcher
8
8
from softioc .pythonSoftIoc import RecordWrapper
9
9
10
10
from fastcs .attributes import AttrR , AttrRW , AttrW
11
+ from fastcs .controller import BaseController
11
12
from fastcs .datatypes import Bool , DataType , Float , Int , String
12
13
from fastcs .exceptions import FastCSException
13
14
from fastcs .mapping import Mapping
@@ -20,10 +21,11 @@ class EpicsIOCOptions:
20
21
21
22
class EpicsIOC :
22
23
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 )
24
26
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 )
27
29
28
30
def run (
29
31
self ,
@@ -40,95 +42,208 @@ def run(
40
42
softioc .interactive_ioc (context )
41
43
42
44
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 :
44
110
for single_mapping in mapping .get_controller_mappings ():
45
111
path = single_mapping .controller .path
46
112
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 )
49
115
50
116
match attribute :
51
117
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 )
54
122
case AttrR ():
55
- _create_and_link_read_pv (pv_name , attribute )
123
+ _create_and_link_read_pv (_pv_prefix , pv_name , attr_name , attribute )
56
124
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
+
58
127
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 )
59
132
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" )
62
134
63
135
async def async_wrapper (v ):
64
136
record .set (v )
65
137
66
138
attribute .set_update_callback (async_wrapper )
67
139
68
140
69
- def _get_input_record (pv_name : str , datatype : DataType ) -> RecordWrapper :
141
+ def _get_input_record (pv : str , datatype : DataType ) -> RecordWrapper :
70
142
match datatype :
71
143
case Bool (znam , onam ):
72
- return builder .boolIn (pv_name , ZNAM = znam , ONAM = onam )
144
+ return builder .boolIn (pv , ZNAM = znam , ONAM = onam )
73
145
case Int ():
74
- return builder .longIn (pv_name )
146
+ return builder .longIn (pv )
75
147
case Float (prec ):
76
- return builder .aIn (pv_name , PREC = prec )
148
+ return builder .aIn (pv , PREC = prec )
77
149
case String ():
78
- return builder .longStringIn (pv_name )
150
+ return builder .longStringIn (pv )
79
151
case _:
80
152
raise FastCSException (f"Unsupported type { type (datatype )} : { datatype } " )
81
153
82
154
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 :
84
158
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 ,
86
162
)
87
163
164
+ _add_attr_pvi_info (record , pv_prefix , attr_name , "w" )
165
+
88
166
async def async_wrapper (v ):
89
167
record .set (v , process = False )
90
168
91
169
attribute .set_write_display_callback (async_wrapper )
92
170
93
171
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 :
95
173
match datatype :
96
174
case Bool (znam , onam ):
97
175
return builder .boolOut (
98
- pv_name ,
176
+ pv ,
99
177
ZNAM = znam ,
100
178
ONAM = onam ,
101
179
always_update = True ,
102
180
on_update = on_update ,
103
181
)
104
182
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 )
106
184
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 )
110
186
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 )
114
188
case _:
115
189
raise FastCSException (f"Unsupported type { type (datatype )} : { datatype } " )
116
190
117
191
118
- def _create_and_link_command_pvs (mapping : Mapping ) -> None :
192
+ def _create_and_link_command_pvs (pv_prefix : str , mapping : Mapping ) -> None :
119
193
for single_mapping in mapping .get_controller_mappings ():
120
194
path = single_mapping .controller .path
121
195
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 )
124
198
125
199
_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 ),
127
204
)
128
205
129
206
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 :
131
210
async def wrapped_method (_ : Any ):
132
211
await method ()
133
212
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