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,10 @@ class EpicsIOCOptions:
20
21
21
22
class EpicsIOC :
22
23
def __init__ (self , pv_prefix : str , mapping : Mapping ):
23
- builder .SetDeviceName (pv_prefix )
24
+ _create_and_link_attribute_pvs (pv_prefix , mapping )
25
+ _create_and_link_command_pvs (pv_prefix , mapping )
24
26
25
- _create_and_link_attribute_pvs (mapping )
26
- _create_and_link_command_pvs (mapping )
27
+ _add_pvi_refs (pv_prefix , mapping .controller )
27
28
28
29
def run (
29
30
self ,
@@ -40,95 +41,174 @@ def run(
40
41
softioc .interactive_ioc (context )
41
42
42
43
43
- def _create_and_link_attribute_pvs (mapping : Mapping ) -> None :
44
+ def _create_and_link_attribute_pvs (pv_prefix : str , mapping : Mapping ) -> None :
44
45
for single_mapping in mapping .get_controller_mappings ():
45
46
path = single_mapping .controller .path
46
47
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
48
+ pv_name = attr_name .title ().replace ("_" , "" )
49
+ _pv_prefix = ":" .join ([ pv_prefix ] + path )
49
50
50
51
match attribute :
51
52
case AttrRW ():
52
- _create_and_link_read_pv (pv_name + "_RBV" , attribute )
53
- _create_and_link_write_pv (pv_name , attribute )
53
+ _create_and_link_read_pv (
54
+ _pv_prefix , f"{ pv_name } _RBV" , attr_name , attribute
55
+ )
56
+ _create_and_link_write_pv (_pv_prefix , pv_name , attr_name , attribute )
54
57
case AttrR ():
55
- _create_and_link_read_pv (pv_name , attribute )
58
+ _create_and_link_read_pv (_pv_prefix , pv_name , attr_name , attribute )
56
59
case AttrW ():
57
- _create_and_link_write_pv (pv_name , attribute )
60
+ _create_and_link_write_pv (_pv_prefix , pv_name , attr_name , attribute )
58
61
59
62
60
- def _create_and_link_read_pv (pv_name : str , attribute : AttrR ) -> None :
61
- record = _get_input_record (pv_name , attribute .datatype )
63
+ def _create_and_link_read_pv (
64
+ pv_prefix : str , pv_name : str , attr_name : str , attribute : AttrR
65
+ ) -> None :
66
+ record = _get_input_record (f"{ pv_prefix } :{ pv_name } " , attribute .datatype )
67
+
68
+ _add_pvi_info (record , pv_prefix , attr_name , "r" )
62
69
63
70
async def async_wrapper (v ):
64
71
record .set (v )
65
72
66
73
attribute .set_update_callback (async_wrapper )
67
74
68
75
69
- def _get_input_record (pv_name : str , datatype : DataType ) -> RecordWrapper :
76
+ def _get_input_record (pv : str , datatype : DataType ) -> RecordWrapper :
70
77
match datatype :
71
78
case Bool (znam , onam ):
72
- return builder .boolIn (pv_name , ZNAM = znam , ONAM = onam )
79
+ return builder .boolIn (pv , ZNAM = znam , ONAM = onam )
73
80
case Int ():
74
- return builder .longIn (pv_name )
81
+ return builder .longIn (pv )
75
82
case Float (prec ):
76
- return builder .aIn (pv_name , PREC = prec )
83
+ return builder .aIn (pv , PREC = prec )
77
84
case String ():
78
- return builder .longStringIn (pv_name )
85
+ return builder .longStringIn (pv )
79
86
case _:
80
87
raise FastCSException (f"Unsupported type { type (datatype )} : { datatype } " )
81
88
82
89
83
- def _create_and_link_write_pv (pv_name : str , attribute : AttrW ) -> None :
90
+ def _create_and_link_write_pv (
91
+ pv_prefix : str , pv_name : str , attr_name : str , attribute : AttrW
92
+ ) -> None :
84
93
record = _get_output_record (
85
- pv_name , attribute .datatype , on_update = attribute .process_without_display_update
94
+ f"{ pv_prefix } :{ pv_name } " ,
95
+ attribute .datatype ,
96
+ on_update = attribute .process_without_display_update ,
86
97
)
87
98
99
+ _add_pvi_info (record , pv_prefix , attr_name , "w" )
100
+
88
101
async def async_wrapper (v ):
89
102
record .set (v , process = False )
90
103
91
104
attribute .set_write_display_callback (async_wrapper )
92
105
93
106
94
- def _get_output_record (pv_name : str , datatype : DataType , on_update : Callable ) -> Any :
107
+ def _get_output_record (pv : str , datatype : DataType , on_update : Callable ) -> Any :
95
108
match datatype :
96
109
case Bool (znam , onam ):
97
110
return builder .boolOut (
98
- pv_name ,
111
+ pv ,
99
112
ZNAM = znam ,
100
113
ONAM = onam ,
101
114
always_update = True ,
102
115
on_update = on_update ,
103
116
)
104
117
case Int ():
105
- return builder .longOut (pv_name , always_update = True , on_update = on_update )
118
+ return builder .longOut (pv , always_update = True , on_update = on_update )
106
119
case Float (prec ):
107
- return builder .aOut (
108
- pv_name , always_update = True , on_update = on_update , PREC = prec
109
- )
120
+ return builder .aOut (pv , always_update = True , on_update = on_update , PREC = prec )
110
121
case String ():
111
- return builder .longStringOut (
112
- pv_name , always_update = True , on_update = on_update
113
- )
122
+ return builder .longStringOut (pv , always_update = True , on_update = on_update )
114
123
case _:
115
124
raise FastCSException (f"Unsupported type { type (datatype )} : { datatype } " )
116
125
117
126
118
- def _create_and_link_command_pvs (mapping : Mapping ) -> None :
127
+ def _create_and_link_command_pvs (pv_prefix : str , mapping : Mapping ) -> None :
119
128
for single_mapping in mapping .get_controller_mappings ():
120
129
path = single_mapping .controller .path
121
130
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
131
+ pv_name = attr_name .title ().replace ("_" , "" )
132
+ _pv_prefix = ":" .join ([ pv_prefix ] + path )
124
133
125
134
_create_and_link_command_pv (
126
- pv_name , MethodType (method .fn , single_mapping .controller )
135
+ _pv_prefix ,
136
+ pv_name ,
137
+ attr_name ,
138
+ MethodType (method .fn , single_mapping .controller ),
127
139
)
128
140
129
141
130
- def _create_and_link_command_pv (pv_name : str , method : Callable ) -> None :
142
+ def _create_and_link_command_pv (
143
+ pv_prefix : str , pv_name : str , attr_name : str , method : Callable
144
+ ) -> None :
131
145
async def wrapped_method (_ : Any ):
132
146
await method ()
133
147
134
- builder .aOut (pv_name , initial_value = 0 , always_update = True , on_update = wrapped_method )
148
+ record = builder .aOut (
149
+ f"{ pv_prefix } :{ pv_name } " ,
150
+ initial_value = 0 ,
151
+ always_update = True ,
152
+ on_update = wrapped_method ,
153
+ )
154
+
155
+ _add_pvi_info (record , pv_prefix , attr_name , "x" )
156
+
157
+
158
+ def _add_pvi_info (
159
+ record : RecordWrapper ,
160
+ prefix : str ,
161
+ name : str ,
162
+ access_mode : Literal ["r" , "w" , "rw" , "x" ],
163
+ ):
164
+ """Add an info tag to a record to include it in the PVI for the controller.
165
+
166
+ Args:
167
+ record: Record to add info tag to
168
+ prefix: PV prefix of controller
169
+ name: Name of parameter to add to PVI
170
+ access_mode: Access mode of parameter
171
+ """
172
+ record .add_info (
173
+ "Q:group" ,
174
+ {
175
+ f"{ prefix } :PVI" : {
176
+ f"pvi.{ name } .{ access_mode } " : {
177
+ "+channel" : "NAME" ,
178
+ "+type" : "plain" ,
179
+ "+trigger" : f"pvi.{ name } .{ access_mode } " ,
180
+ }
181
+ }
182
+ },
183
+ )
184
+
185
+
186
+ def _add_pvi_refs (pv_prefix : str , parent : BaseController ):
187
+ """Add PVI references from controller to its sub controllers, recursively.
188
+
189
+ Args:
190
+ pv_prefix: PV Prefix of IOC
191
+ parent: Controller to add PVI refs for
192
+ """
193
+ parent_pvi = ":" .join ([pv_prefix ] + parent .path + ["PVI" ])
194
+
195
+ for child in parent .get_sub_controllers ().values ():
196
+ child_pvi = ":" .join ([pv_prefix ] + child .path + ["PVI" ])
197
+ child_name = f"pvi.{ child .path [- 1 ].lower ()} .d"
198
+
199
+ # Make a record with an info tag to add child to parent PVI structure
200
+ dummy_record = builder .longStringIn (f"{ child_pvi } _PV" , initial_value = child_pvi )
201
+ dummy_record .add_info (
202
+ "Q:group" ,
203
+ {
204
+ parent_pvi : {
205
+ child_name : {
206
+ "+channel" : "VAL" ,
207
+ "+type" : "plain" ,
208
+ "+trigger" : child_name ,
209
+ }
210
+ }
211
+ },
212
+ )
213
+
214
+ _add_pvi_refs (pv_prefix , child )
0 commit comments