rtint 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,105 @@
1
+ # This file is part of the mechatronic project
2
+ #
3
+ # Copyright (c) Beamline Control Unit, ESRF
4
+ # Distributed under the GNU LGPLv3. See LICENSE for more info.
5
+
6
+ """
7
+ REAL-TIME TARGET PARAMETERS
8
+ """
9
+
10
+ import numpy as np
11
+ from collections.abc import MutableSequence
12
+ from treelib import Tree
13
+ from tabulate import tabulate
14
+
15
+
16
+ class RtintHdwParameterController:
17
+ def __init__(self, tg):
18
+ self._tg = tg
19
+ self._system = tg._system
20
+ self._program = tg._program
21
+ self._param_tree = self._program.tree.params
22
+
23
+ self._set_attr_root_params()
24
+ self._set_default_params()
25
+
26
+ def __info__(self, debug=False):
27
+ if self._root_params is None:
28
+ return "\n No parameters in the model"
29
+ lines = [["Name", "Value"]]
30
+ for parameter in self._root_params:
31
+ lines.append([parameter, repr(getattr(self, parameter))])
32
+ return "\n" + tabulate(lines, headers="firstrow", tablefmt="grid", stralign="left")
33
+
34
+ # Used to automatically add "root parameters" as attributes for easy access/modification
35
+ def _set_attr_root_params(self):
36
+ for param_name in self._root_params:
37
+ setattr(self.__class__, param_name, RtintParam(self, param_name))
38
+
39
+ @property
40
+ def _root_params(self):
41
+ return [
42
+ param
43
+ for param in self._param_tree
44
+ if (self._program.name not in param and param[-1] != "_")
45
+ ]
46
+
47
+ # Set default params as defined in the YML file
48
+ def _set_default_params(self):
49
+ params_yml = self._tg._config.get("default_parameters")
50
+ if params_yml is None:
51
+ return
52
+
53
+ for param in params_yml:
54
+ try:
55
+ if isinstance(param["value"], MutableSequence):
56
+ self.set(param["path"], np.array(param["value"]))
57
+ else:
58
+ self.set(param["path"], param["value"])
59
+ except Exception:
60
+ print(
61
+ f"Default parameter with path \"{param['path']}\" does not exist in the model"
62
+ )
63
+
64
+ @property
65
+ def _tree(self):
66
+ tree = Tree()
67
+ root = self._program.name
68
+ tree.create_node(root, root)
69
+
70
+ for path in self._param_tree:
71
+ parts = path.split("/")
72
+ parent = root
73
+ for i, p in enumerate(parts[1:], 1): # start at index 1
74
+ node_id = "/".join(
75
+ parts[: i + 1]
76
+ ) # build unique identifier up to this level
77
+ if not tree.contains(node_id):
78
+ tree.create_node(p, node_id, parent=parent)
79
+ parent = node_id
80
+
81
+ return tree
82
+
83
+ def set(self, param_name, value):
84
+ # Automatically add "model_name" if necessary
85
+ if "/" in param_name:
86
+ param_name = self._program.name + "/" + param_name
87
+ self._param_tree[param_name].value = value
88
+
89
+ def get(self, param_name):
90
+ # Automatically add "model_name" if necessary
91
+ if "/" in param_name:
92
+ param_name = self._program.name + "/" + param_name
93
+ return self._param_tree[param_name].value
94
+
95
+
96
+ class RtintParam:
97
+ def __init__(self, param_ctl, param_name):
98
+ self._param_ctl = param_ctl
99
+ self._name = param_name
100
+
101
+ def __get__(self, obj, objtype):
102
+ return self._param_ctl.get(self._name)
103
+
104
+ def __set__(self, obj, value):
105
+ self._param_ctl.set(self._name, value)
rtint/rtint_regul.py ADDED
@@ -0,0 +1,310 @@
1
+ # This file is part of the mechatronic project
2
+ #
3
+ # Copyright (c) Beamline Control Unit, ESRF
4
+ # Distributed under the GNU LGPLv3. See LICENSE for more info.
5
+
6
+ """
7
+ REAL-TIME TARGET REGULATOR
8
+ """
9
+
10
+ import numpy as np
11
+ import enum
12
+ from tabulate import tabulate
13
+
14
+ RED = "\033[31m"
15
+ GREEN = "\033[32m"
16
+ RESET = "\033[0m"
17
+
18
+
19
+ class RegulState(enum.IntEnum):
20
+ Off = 0
21
+ On = 1
22
+
23
+
24
+ class RegulError(enum.IntEnum):
25
+ NoError = 0
26
+ MinInput = 1
27
+ MaxInput = 2
28
+ MinOutput = 3
29
+ MaxOutput = 4
30
+ ExtError = 5
31
+
32
+
33
+ class RtintHdwRegulController:
34
+ def __init__(self, tg):
35
+ self._tg = tg
36
+ self._reguls: dict[str, RtintHdwRegul] | None = None
37
+ self._load()
38
+
39
+ def __info__(self, debug=False):
40
+ if self._reguls is None:
41
+ return "\n No Regulator in the model"
42
+
43
+ if debug:
44
+ lines = [["Name", "Path", "Wanted", "State", "Error", "Input", "Output"]]
45
+ else:
46
+ lines = [["Name", "Wanted", "State"]]
47
+
48
+ for _regul in self._reguls.values():
49
+ if debug:
50
+ lines.append(
51
+ [
52
+ _regul._name,
53
+ _regul._unique_name,
54
+ _regul.wanted,
55
+ _regul.ctrl_status.name,
56
+ _regul.ctrl_error.name,
57
+ _regul.error_signal,
58
+ _regul.command_signal,
59
+ ]
60
+ )
61
+ else:
62
+ lines.append([_regul._name, _regul.wanted, _regul.ctrl_status.name])
63
+ return "\n" + tabulate(lines, headers="firstrow", tablefmt="grid", stralign="left")
64
+
65
+ def _load(self):
66
+ reguls = self._tg._get_all_objects_from_key("bliss_regulator")
67
+ if len(reguls) > 0:
68
+ self._reguls = {}
69
+ for regul in reguls:
70
+ tg_regul = RtintHdwRegul(self._tg, regul)
71
+
72
+ if hasattr(self, tg_regul._name):
73
+ print(
74
+ f"{RED}WARNING: Regulator '{tg_regul._name}' already exists{RESET}"
75
+ )
76
+ return
77
+ else:
78
+ setattr(self, tg_regul._name, tg_regul)
79
+ self._reguls[tg_regul._name] = tg_regul
80
+
81
+
82
+ class RtintHdwRegul:
83
+ def __init__(self, tg, unique_name):
84
+ self._tg = tg
85
+ self._unique_name = unique_name
86
+
87
+ def __info__(self):
88
+ lines = []
89
+ lines.append(["Name", self._name])
90
+ wanted_str = f"{GREEN}True{RESET}" if self.wanted else f"{RED}False{RESET}"
91
+ lines.append(["Wanted", wanted_str])
92
+ if self.ctrl_status == 0:
93
+ lines.append(["State", f"{RED}{self.ctrl_status.name}{RESET}"])
94
+ lines.append(["Error", f"{RED}{self.ctrl_error.name}{RESET}"])
95
+ if self.ctrl_error != 0:
96
+ lines.append(
97
+ [
98
+ " Output at error",
99
+ "["
100
+ + ", ".join(f"{x:.2g}" for x in np.ravel(self.command_fault))
101
+ + "]",
102
+ ]
103
+ )
104
+ lines.append(
105
+ [
106
+ " Intput at error",
107
+ "["
108
+ + ", ".join(f"{x:.2g}" for x in np.ravel(self.error_fault))
109
+ + "]",
110
+ ]
111
+ )
112
+ else:
113
+ lines.append(["State", f"{GREEN}{self.ctrl_status.name}{RESET}"])
114
+ lines.append(["", ""])
115
+ lines.append(
116
+ [
117
+ "Current Error",
118
+ "[" + ", ".join(f"{x:.2g}" for x in np.ravel(self.error_signal)) + "]",
119
+ ]
120
+ )
121
+ lines.append(
122
+ [
123
+ "Current Command",
124
+ "["
125
+ + ", ".join(f"{x:.2g}" for x in np.ravel(self.command_signal))
126
+ + "]",
127
+ ]
128
+ )
129
+ lines.append(["", ""])
130
+ lines.append(
131
+ [
132
+ "Error Limits",
133
+ "["
134
+ + ", ".join(
135
+ f"{x:.2g}" for x in [self.error_lower_limit, self.error_upper_limit]
136
+ )
137
+ + "]",
138
+ ]
139
+ )
140
+ lines.append(
141
+ [
142
+ "Command Limits",
143
+ "["
144
+ + ", ".join(
145
+ f"{x:.2g}"
146
+ for x in [self.command_lower_limit, self.command_upper_limit]
147
+ )
148
+ + "]",
149
+ ]
150
+ )
151
+ return tabulate(lines, tablefmt="plain", stralign="right")
152
+
153
+ def _tree(self):
154
+ print("Parameters:")
155
+ self._tg.parameter._tree.subtree(
156
+ self._tg._program.name + "/" + self._unique_name
157
+ ).show()
158
+ print("Signals:")
159
+ self._tg.signal._tree.subtree(
160
+ self._tg._program.name + "/" + self._unique_name
161
+ ).show()
162
+
163
+ @property
164
+ def _name(self):
165
+ return self._tg.parameter.get(
166
+ f"{self._unique_name}/bliss_regulator/String"
167
+ )
168
+
169
+ @property
170
+ def error_lower_limit(self):
171
+ """Minimum allowed input (usually control error)"""
172
+ return self._tg.parameter.get(f"{self._unique_name}/error_lower_limit")
173
+
174
+ @error_lower_limit.setter
175
+ def error_lower_limit(self, value):
176
+ if value < self.error_upper_limit:
177
+ self._tg.parameter.set(
178
+ f"{self._unique_name}/error_lower_limit", value
179
+ )
180
+ else:
181
+ raise ValueError(
182
+ f"error_lower_limit {value} must be < {self.error_upper_limit}"
183
+ )
184
+
185
+ @property
186
+ def error_upper_limit(self):
187
+ """Maximum allowed input (usually control error)"""
188
+ return self._tg.parameter.get(f"{self._unique_name}/error_upper_limit")
189
+
190
+ @error_upper_limit.setter
191
+ def error_upper_limit(self, value):
192
+ if value > self.error_lower_limit:
193
+ self._tg.parameter.set(
194
+ f"{self._unique_name}/error_upper_limit", value
195
+ )
196
+ else:
197
+ raise ValueError(
198
+ f"error_upper_limit {value} must be > {self.error_lower_limit}"
199
+ )
200
+
201
+ @property
202
+ def command_lower_limit(self):
203
+ """Mimum allowed output (usually control command signal)"""
204
+ return self._tg.parameter.get(f"{self._unique_name}/command_lower_limit")
205
+
206
+ @command_lower_limit.setter
207
+ def command_lower_limit(self, value):
208
+ if value < self.command_upper_limit:
209
+ self._tg.parameter.set(
210
+ f"{self._unique_name}/command_lower_limit", value
211
+ )
212
+ else:
213
+ raise ValueError(
214
+ f"command_lower_limit {value} must be < {self.command_upper_limit}"
215
+ )
216
+
217
+ @property
218
+ def command_upper_limit(self):
219
+ """Maximum allowed output (usually control command signal)"""
220
+ return self._tg.parameter.get(f"{self._unique_name}/command_upper_limit")
221
+
222
+ @command_upper_limit.setter
223
+ def command_upper_limit(self, value):
224
+ if value > self.command_lower_limit:
225
+ self._tg.parameter.set(
226
+ f"{self._unique_name}/command_upper_limit", value
227
+ )
228
+ else:
229
+ raise ValueError(
230
+ f"command_upper_limit {value} must be > {self.command_lower_limit}"
231
+ )
232
+
233
+ @property
234
+ def error_fault(self):
235
+ """Stored input (usually error signal) when the regul was last turned OFF."""
236
+ return self._tg.signal.get(f"{self._unique_name}/error_fault")
237
+
238
+ @property
239
+ def command_fault(self):
240
+ """Stored command signal when the regul was last turned OFF."""
241
+ return self._tg.signal.get(f"{self._unique_name}/command_fault")
242
+
243
+ @property
244
+ def wanted(self):
245
+ """Is the regul is wanted?"""
246
+ return bool(int(self._tg.signal.get(f"{self._unique_name}/ctrl_wanted")))
247
+
248
+ def on(self):
249
+ """Turn the regul ON"""
250
+ self.reset_error()
251
+ self._tg.parameter.set(f"{self._unique_name}/wanted/Value", 1)
252
+
253
+ def off(self):
254
+ """Turn the regul OFF"""
255
+ self._tg.parameter.set(f"{self._unique_name}/wanted/Value", 0)
256
+
257
+ def reset_error(self):
258
+ """If the regul was OFF due to too large input or output, reset the error to be
259
+ able to turn it ON again."""
260
+ reset_error = int(
261
+ self._tg.parameter.get(f"{self._unique_name}/reset_error/Bias")
262
+ )
263
+ self._tg.parameter.set(
264
+ f"{self._unique_name}/reset_error/Bias", reset_error + 1
265
+ )
266
+
267
+ @property
268
+ def fault_time(self):
269
+ """IF the regul goes from ON to OFF, time it takes to put the command signal at zero."""
270
+ return self._tg.parameter.get(f"{self._unique_name}/fault_time")
271
+
272
+ @fault_time.setter
273
+ def fault_time(self, value):
274
+ if value <= 0:
275
+ raise ValueError("fault_time must be positive")
276
+ self._tg.parameter.set(f"{self._unique_name}/fault_time", value)
277
+
278
+ @property
279
+ def fault_output(self):
280
+ """IF the regul goes from ON to OFF, the output signal will be fault_output."""
281
+ return self._tg.parameter.get(f"{self._unique_name}/fault_output")
282
+
283
+ @fault_output.setter
284
+ def fault_output(self, value):
285
+ self._tg.parameter.set(f"{self._unique_name}/fault_output", value)
286
+
287
+ @property
288
+ def ctrl_error(self):
289
+ """It regul is OFF due to some error, display this error."""
290
+ # 0:no_error 1:min_input_reached 2:max_input_reached 3:min_output_reached 4:max_output_reached 5:ext_error
291
+ return RegulError(
292
+ int(self._tg.signal.get(f"{self._unique_name}/ctrl_error"))
293
+ )
294
+
295
+ @property
296
+ def ctrl_status(self):
297
+ # 0:off 1:on
298
+ return RegulState(
299
+ int(self._tg.signal.get(f"{self._unique_name}/ctrl_status"))
300
+ )
301
+
302
+ @property
303
+ def error_signal(self):
304
+ """Regul input, usually error signal."""
305
+ return self._tg.signal.get(f"{self._unique_name}/error_signal")
306
+
307
+ @property
308
+ def command_signal(self):
309
+ """Regul output, usually command signal."""
310
+ return self._tg.signal.get(f"{self._unique_name}/command_signal")
rtint/rtint_signal.py ADDED
@@ -0,0 +1,42 @@
1
+ # This file is part of the mechatronic project
2
+ #
3
+ # Copyright (c) Beamline Control Unit, ESRF
4
+ # Distributed under the GNU LGPLv3. See LICENSE for more info.
5
+
6
+ from treelib import Tree
7
+
8
+ """
9
+ SIGNALS
10
+ """
11
+
12
+
13
+ class RtintHdwSignalController:
14
+ def __init__(self, tg):
15
+ self._tg = tg
16
+ self._system = tg._system
17
+ self._program = tg._program
18
+ self._signal_tree = tg._program.tree.signals
19
+
20
+ def __info__(self):
21
+ self._tree.show()
22
+ return ""
23
+
24
+ @property
25
+ def _tree(self):
26
+ tree = Tree()
27
+ root = self._program.name
28
+ tree.create_node(root, root)
29
+
30
+ for path in self._signal_tree:
31
+ parts = path.split("/")
32
+ parent = root
33
+ for p in parts[1:]:
34
+ node_id = "/".join(parts[: parts.index(p) + 1]) # unique identifier
35
+ if not tree.contains(node_id):
36
+ tree.create_node(p, node_id, parent=parent)
37
+ parent = node_id
38
+
39
+ return tree
40
+
41
+ def get(self, signal_path):
42
+ return self._signal_tree[self._program.name + "/" + signal_path].value
rtint/rtint_trigger.py ADDED
@@ -0,0 +1,107 @@
1
+ # This file is part of the mechatronic project
2
+ #
3
+ # Copyright (c) Beamline Control Unit, ESRF
4
+ # Distributed under the GNU LGPLv3. See LICENSE for more info.
5
+
6
+ from tabulate import tabulate
7
+
8
+ RED = "\033[31m"
9
+ GREEN = "\033[32m"
10
+ RESET = "\033[0m"
11
+
12
+
13
+ """
14
+ REAL-TIME TARGET triggers
15
+ """
16
+
17
+
18
+ class RtintHdwTriggerController:
19
+ def __init__(self, tg):
20
+ self._tg = tg
21
+ self._triggers: dict[str, RtintHdwTrigger] | None = None
22
+ self._load()
23
+
24
+ def __info__(self, debug=False):
25
+ if self._triggers is None:
26
+ return "\n No trigger in the model"
27
+
28
+ if debug:
29
+ lines = [["Name", "Path", "Trigger Number"]]
30
+ else:
31
+ lines = [["Name"]]
32
+
33
+ for _trigger in self._triggers.values():
34
+ if debug:
35
+ lines.append(
36
+ [_trigger._name, _trigger._unique_name, _trigger.trig_number]
37
+ )
38
+ else:
39
+ lines.append([_trigger._name])
40
+ return "\n" + tabulate(lines, headers="firstrow", tablefmt="grid", stralign="left")
41
+
42
+ def _load(self):
43
+ triggers = self._tg._get_all_objects_from_key("bliss_trigger")
44
+ if len(triggers) > 0:
45
+ self._triggers = {}
46
+ for trigger in triggers:
47
+ tg_trigger = RtintHdwTrigger(self._tg, trigger)
48
+
49
+ if hasattr(self, tg_trigger._name):
50
+ print(
51
+ f"{RED}WARNING: Trigger '{tg_trigger._name}' already exists{RESET}"
52
+ )
53
+ return
54
+ else:
55
+ setattr(self, tg_trigger._name, tg_trigger)
56
+ self._triggers[tg_trigger._name] = tg_trigger
57
+
58
+
59
+ class RtintHdwTrigger:
60
+ def __init__(self, tg, unique_name):
61
+ self._tg = tg
62
+ self._unique_name = unique_name
63
+
64
+ def __info__(self):
65
+ lines = []
66
+ lines.append(["Name", self._name])
67
+ lines.append(["Unique Name", self._unique_name])
68
+ lines.append(["", ""])
69
+ lines.append(["Number of trigs", self.trig_number])
70
+ return tabulate(lines, tablefmt="plain", stralign="right")
71
+
72
+ def _tree(self):
73
+ print("Parameters:")
74
+ self._tg.parameter._tree.subtree(
75
+ self._tg._program.name + "/" + self._unique_name
76
+ ).show()
77
+ print("Signals:")
78
+ self._tg.signal._tree.subtree(
79
+ self._tg._program.name + "/" + self._unique_name
80
+ ).show()
81
+
82
+ @property
83
+ def _name(self):
84
+ return self._tg.parameter.get(
85
+ f"{self._unique_name}/bliss_trigger/String"
86
+ )
87
+
88
+ @property
89
+ def trig_number(self):
90
+ return int(self._tg.signal.get(f"{self._unique_name}/trig_number"))
91
+
92
+ def reset_trig_number(self):
93
+ self._tg.parameter.set(f"{self._unique_name}/start_trigger/Bias", 0)
94
+ reset_trigger = int(
95
+ self._tg.parameter.get(f"{self._unique_name}/reset_trig_number/Bias")
96
+ )
97
+ self._tg.parameter.set(
98
+ f"{self._unique_name}/reset_trig_number/Bias", reset_trigger + 1
99
+ )
100
+
101
+ def trig(self):
102
+ bias = int(
103
+ self._tg.parameter.get(f"{self._unique_name}/start_trigger/Bias")
104
+ )
105
+ self._tg.parameter.set(
106
+ f"{self._unique_name}/start_trigger/Bias", bias + 1
107
+ )