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.
- rtint/_version.py +24 -0
- rtint/rtint_acquisition.py +327 -0
- rtint/rtint_counter.py +202 -0
- rtint/rtint_ethercat.py +127 -0
- rtint/rtint_filter.py +872 -0
- rtint/rtint_generator.py +577 -0
- rtint/rtint_hardware.py +336 -0
- rtint/rtint_lut.py +143 -0
- rtint/rtint_motor.py +191 -0
- rtint/rtint_parameter.py +105 -0
- rtint/rtint_regul.py +310 -0
- rtint/rtint_signal.py +42 -0
- rtint/rtint_trigger.py +107 -0
- rtint/rtint_utils.py +648 -0
- rtint-0.0.3.dist-info/METADATA +74 -0
- rtint-0.0.3.dist-info/RECORD +19 -0
- rtint-0.0.3.dist-info/WHEEL +5 -0
- rtint-0.0.3.dist-info/licenses/LICENSE +238 -0
- rtint-0.0.3.dist-info/top_level.txt +1 -0
rtint/rtint_hardware.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
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
|
+
Rtint Real Time Controller
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from rtint.rtint_parameter import (
|
|
11
|
+
RtintHdwParameterController,
|
|
12
|
+
)
|
|
13
|
+
from rtint.rtint_signal import RtintHdwSignalController
|
|
14
|
+
from rtint.rtint_motor import RtintHdwMotorController
|
|
15
|
+
from rtint.rtint_regul import RtintHdwRegulController
|
|
16
|
+
from rtint.rtint_generator import (
|
|
17
|
+
RtintHdwGeneratorController,
|
|
18
|
+
)
|
|
19
|
+
from rtint.rtint_filter import RtintHdwFilterController
|
|
20
|
+
from rtint.rtint_trigger import RtintHdwTriggerController
|
|
21
|
+
from rtint.rtint_ethercat import (
|
|
22
|
+
RtintHdwEthercatController,
|
|
23
|
+
)
|
|
24
|
+
from rtint.rtint_lut import RtintHdwLutController
|
|
25
|
+
from rtint.rtint_counter import RtintHdwCounterController
|
|
26
|
+
from rtint.rtint_acquisition import RtintHdwAcquisition
|
|
27
|
+
from rtint.rtint_utils import RtintUtils
|
|
28
|
+
|
|
29
|
+
from rtint import _version
|
|
30
|
+
|
|
31
|
+
from resyst.client.system import System
|
|
32
|
+
from tabulate import tabulate
|
|
33
|
+
|
|
34
|
+
RED = "\033[31m"
|
|
35
|
+
GREEN = "\033[32m"
|
|
36
|
+
RESET = "\033[0m"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RtintController:
|
|
40
|
+
def __init__(self, name, config):
|
|
41
|
+
# Set the real-time target name
|
|
42
|
+
self._name = name
|
|
43
|
+
self._config = config
|
|
44
|
+
|
|
45
|
+
# Connect to dance server
|
|
46
|
+
self._server = config.get("server", None)
|
|
47
|
+
self._port = config.get("port", None)
|
|
48
|
+
|
|
49
|
+
self._system = System.create(f"{self._server}:{self._port}")
|
|
50
|
+
self._system.timeout = 30
|
|
51
|
+
self._program = self._system.running_program
|
|
52
|
+
|
|
53
|
+
self._check_resyst_version(min_major=0, min_minor=18)
|
|
54
|
+
|
|
55
|
+
if self._program is not None:
|
|
56
|
+
self._load_objects()
|
|
57
|
+
|
|
58
|
+
def __info__(self):
|
|
59
|
+
if not self._is_app_running:
|
|
60
|
+
return "No app is running"
|
|
61
|
+
|
|
62
|
+
lines = []
|
|
63
|
+
lines.append(["Real-time target name", self._name])
|
|
64
|
+
lines.append(["Program Name", self._app_name])
|
|
65
|
+
lines.append(["", ""])
|
|
66
|
+
lines.append(["Cycle Time", f"{1e6 * self._Ts:.0f} us"])
|
|
67
|
+
lines.append(
|
|
68
|
+
[
|
|
69
|
+
"Max TET",
|
|
70
|
+
f"{1e6 * self._tet_max:.1f} us ({100 * self._tet_max / self._Ts:.1f}%)",
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
lines.append(
|
|
74
|
+
["Overloads", f"{self._overloads} (Max allowed: {self._max_overloads})"]
|
|
75
|
+
)
|
|
76
|
+
lines.append(["Execution Time", self._exec_time_string()])
|
|
77
|
+
lines.append(["", ""])
|
|
78
|
+
lines.append(["Resyst Client", self._system.version])
|
|
79
|
+
lines.append(["Resyst Server", self._system.server_version])
|
|
80
|
+
lines.append(["", ""])
|
|
81
|
+
lines.append(["RT Interface Version", self._version])
|
|
82
|
+
lines.append(["Library Git Version", self._lib_git_version])
|
|
83
|
+
lines.append(["Model Git Version", self._model_git_version])
|
|
84
|
+
return tabulate(lines, tablefmt="plain", stralign="left")
|
|
85
|
+
|
|
86
|
+
def _check_resyst_version(self, min_major=None, min_minor=None):
|
|
87
|
+
clt_major, clt_minor = self._parse_resyst_version(self._system.version)
|
|
88
|
+
srv_major, srv_minor = self._parse_resyst_version(self._system.server_version)
|
|
89
|
+
|
|
90
|
+
# Check that major and minor are the same
|
|
91
|
+
if (clt_major, clt_minor) != (srv_major, srv_minor):
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Resyst version mismatch: client ({self._system.version}) vs server ({self._system.server_version})"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Check minimum version if specified
|
|
97
|
+
if min_major is not None and min_minor is not None:
|
|
98
|
+
if (clt_major, clt_minor) < (min_major, min_minor):
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"Resyst version ({self._system.version}) is lower than minimum required ({min_major}.{min_minor}.x)"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _parse_resyst_version(self, version_str):
|
|
104
|
+
"""Return (major, minor) as integers from a version string 'major.minor.revision'."""
|
|
105
|
+
major, minor, *_ = version_str.split(".")
|
|
106
|
+
return int(major), int(minor)
|
|
107
|
+
|
|
108
|
+
def _display_informations(self, debug=False):
|
|
109
|
+
sections = []
|
|
110
|
+
|
|
111
|
+
sections.append("Parameters")
|
|
112
|
+
sections.append(self.parameter.__info__(debug=debug))
|
|
113
|
+
|
|
114
|
+
sections.append("\n\nCounters")
|
|
115
|
+
sections.append(self.counter.__info__(debug=debug))
|
|
116
|
+
|
|
117
|
+
sections.append("\n\nFilters")
|
|
118
|
+
sections.append(self.filter.__info__(debug=debug))
|
|
119
|
+
|
|
120
|
+
sections.append("\n\nGenerators")
|
|
121
|
+
sections.append(self.generator.__info__(debug=debug))
|
|
122
|
+
|
|
123
|
+
sections.append("\n\nReguls")
|
|
124
|
+
sections.append(self.regul.__info__(debug=debug))
|
|
125
|
+
|
|
126
|
+
sections.append("\n\nMotors")
|
|
127
|
+
sections.append(self.motor.__info__(debug=debug))
|
|
128
|
+
|
|
129
|
+
sections.append("\n\nTriggers")
|
|
130
|
+
sections.append(self.trigger.__info__(debug=debug))
|
|
131
|
+
|
|
132
|
+
sections.append("\n\nEtherCAT")
|
|
133
|
+
sections.append(self.ethercat.__info__(debug=debug))
|
|
134
|
+
|
|
135
|
+
sections.append("\n\nLUT")
|
|
136
|
+
sections.append(self.lut.__info__(debug=debug))
|
|
137
|
+
|
|
138
|
+
sections.append("\n\nAcquisitions")
|
|
139
|
+
sections.append(self.acq.__info__(debug=debug))
|
|
140
|
+
|
|
141
|
+
print("\n".join(sections))
|
|
142
|
+
|
|
143
|
+
# Populate the tg_ctl object
|
|
144
|
+
def _load_objects(self):
|
|
145
|
+
self.parameter = RtintHdwParameterController(self)
|
|
146
|
+
self.signal = RtintHdwSignalController(self)
|
|
147
|
+
|
|
148
|
+
# Find the block were some informations are stored
|
|
149
|
+
tg_info_path = self._get_all_objects_from_key("bliss_speedgoat_info")
|
|
150
|
+
if len(tg_info_path) == 0:
|
|
151
|
+
print(
|
|
152
|
+
f"{RED}WARNING: The Simulink model does not have the 'Speedgoat Info' block{RESET}"
|
|
153
|
+
)
|
|
154
|
+
return
|
|
155
|
+
self._tg_info = tg_info_path[0]
|
|
156
|
+
self._Ts = self._cycle_time
|
|
157
|
+
self._Fs = 1 / self._Ts
|
|
158
|
+
|
|
159
|
+
self.counter = RtintHdwCounterController(self)
|
|
160
|
+
self.filter = RtintHdwFilterController(self)
|
|
161
|
+
self.generator = RtintHdwGeneratorController(self)
|
|
162
|
+
self.regul = RtintHdwRegulController(self)
|
|
163
|
+
self.motor = RtintHdwMotorController(self)
|
|
164
|
+
self.trigger = RtintHdwTriggerController(self)
|
|
165
|
+
self.ethercat = RtintHdwEthercatController(self)
|
|
166
|
+
self.lut = RtintHdwLutController(self)
|
|
167
|
+
self.acq = RtintHdwAcquisition(self)
|
|
168
|
+
self.utils = RtintUtils(self)
|
|
169
|
+
|
|
170
|
+
# Load model on real-time target and run
|
|
171
|
+
def _run_model(self, prg_name, force_load=False, force_run=False):
|
|
172
|
+
# First check if the program is available in the real-time target and or the PC Server
|
|
173
|
+
if prg_name not in self._program_stored_list:
|
|
174
|
+
if prg_name not in self._program_loaded_list:
|
|
175
|
+
raise RuntimeError(
|
|
176
|
+
f"Program file {prg_name} not found on PC server nor on the real-time target"
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
print(
|
|
180
|
+
"Program is only present in the real-time target and not in the PC server."
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if prg_name not in self._program_loaded_list and force_load is False:
|
|
184
|
+
raise RuntimeError(
|
|
185
|
+
f"Program file {prg_name} not loaded in the real-time target, use force_load=True"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Cases to load the program
|
|
189
|
+
if prg_name not in self._program_loaded_list or force_load is True:
|
|
190
|
+
self._system.program_load(prg_name, overwrite=True)
|
|
191
|
+
|
|
192
|
+
# Now run the program
|
|
193
|
+
running_program = self._system.running_program
|
|
194
|
+
# If program is already running and force is False: do nothing
|
|
195
|
+
if (
|
|
196
|
+
running_program is None
|
|
197
|
+
or running_program.name != prg_name
|
|
198
|
+
or force_run is True
|
|
199
|
+
):
|
|
200
|
+
default_timeout = self._system.timeout
|
|
201
|
+
self._system.timeout = 60
|
|
202
|
+
if running_program is not None:
|
|
203
|
+
self._system.program_stop()
|
|
204
|
+
self._system.program_run(prg_name)
|
|
205
|
+
self._program = self._system.running_program
|
|
206
|
+
self._system.timeout = default_timeout
|
|
207
|
+
self._load_objects()
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def _version(self):
|
|
211
|
+
return _version.version
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def _program_stored_list(self):
|
|
215
|
+
return [program.name for program in self._system.stored_programs]
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def _program_loaded_list(self):
|
|
219
|
+
return [program.name for program in self._system.loaded_programs]
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def _app_name(self):
|
|
223
|
+
return self._program.name
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def _is_app_running(self):
|
|
227
|
+
return self._system.running_program is not None
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def _tet(self):
|
|
231
|
+
return self.signal.get(f"{self._tg_info}/tet")
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def _tet_max(self):
|
|
235
|
+
return self.signal.get(f"{self._tg_info}/max_tet")
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def _cycle_time(self):
|
|
239
|
+
return self.signal.get(f"{self._tg_info}/Ts")
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def _exec_time(self):
|
|
243
|
+
return self._cycle_time * self.signal.get(
|
|
244
|
+
f"{self._tg_info}/exc_time"
|
|
245
|
+
) # in [s]
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def _disk_space(self):
|
|
249
|
+
return 1e-9 * self.signal.get(
|
|
250
|
+
f"{self._tg_info}/Get disk space"
|
|
251
|
+
) # in [Gb]
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def _memory_space(self):
|
|
255
|
+
return 1e-9 * self.signal.get(
|
|
256
|
+
f"{self._tg_info}/Get memory info"
|
|
257
|
+
) # in [Gb]
|
|
258
|
+
|
|
259
|
+
def _exec_time_string(self):
|
|
260
|
+
seconds = int(self._exec_time) # Ensure it's an integer
|
|
261
|
+
if seconds < 60:
|
|
262
|
+
return f"{seconds} second{'s' if seconds != 1 else ''}"
|
|
263
|
+
elif seconds < 3600:
|
|
264
|
+
minutes = seconds // 60
|
|
265
|
+
secs = seconds % 60
|
|
266
|
+
return f"{minutes} minute{'s' if minutes != 1 else ''}" + (
|
|
267
|
+
f" {secs} second{'s' if secs != 1 else ''}" if secs else ""
|
|
268
|
+
)
|
|
269
|
+
elif seconds < 86400:
|
|
270
|
+
hours = seconds // 3600
|
|
271
|
+
minutes = (seconds % 3600) // 60
|
|
272
|
+
return f"{hours} hour{'s' if hours != 1 else ''}" + (
|
|
273
|
+
f" {minutes} minute{'s' if minutes != 1 else ''}" if minutes else ""
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
days = seconds // 86400
|
|
277
|
+
hours = (seconds % 86400) // 3600
|
|
278
|
+
return f"{days} day{'s' if days != 1 else ''}" + (
|
|
279
|
+
f" {hours} hour{'s' if hours != 1 else ''}" if hours else ""
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def _autostart_enable(self):
|
|
283
|
+
self._system.autostart_program = self._program.name
|
|
284
|
+
|
|
285
|
+
def _autostart_disable(self):
|
|
286
|
+
self._system.autostart_program = None
|
|
287
|
+
|
|
288
|
+
def _autostart_status(self):
|
|
289
|
+
if self._system.autostart_program is None:
|
|
290
|
+
print("No autostart")
|
|
291
|
+
else:
|
|
292
|
+
print(f"Autostart program is {self._system.autostart_program.name}")
|
|
293
|
+
|
|
294
|
+
def _reset_max_tet(self):
|
|
295
|
+
self.parameter.set(
|
|
296
|
+
f"{self._tg_info}/reset_max_tet/Bias",
|
|
297
|
+
self.parameter.get(f"{self._tg_info}/reset_max_tet/Bias") + 1,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def _is_overloaded(self):
|
|
302
|
+
return self._overloads >= self._max_overloads
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def _overloads(self):
|
|
306
|
+
return self.signal.get(
|
|
307
|
+
f"{self._tg_info}/Overload Options/Overload Options Core/o1"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def _max_overloads(self):
|
|
312
|
+
return self.parameter.get(f"{self._tg_info}/max_overload/Value")
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def _model_git_version(self):
|
|
316
|
+
try:
|
|
317
|
+
version = self.parameter.get(f"{self._tg_info}/model_git_version")
|
|
318
|
+
except Exception:
|
|
319
|
+
version = "" # By default if not specified in the model
|
|
320
|
+
return version
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def _lib_git_version(self):
|
|
324
|
+
try:
|
|
325
|
+
version = self.parameter.get(f"{self._tg_info}/lib_git_version")
|
|
326
|
+
except Exception:
|
|
327
|
+
version = "" # By default if not specified in the model
|
|
328
|
+
return version
|
|
329
|
+
|
|
330
|
+
def _get_all_objects_from_key(self, name):
|
|
331
|
+
"""Return identifiers of all parents whose child tag == name."""
|
|
332
|
+
return [
|
|
333
|
+
"/".join(param.split("/")[1:-2])
|
|
334
|
+
for param in self.parameter._param_tree
|
|
335
|
+
if name in param
|
|
336
|
+
]
|
rtint/rtint_lut.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
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 Lookup Tables
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from tabulate import tabulate
|
|
12
|
+
|
|
13
|
+
RED = "\033[31m"
|
|
14
|
+
GREEN = "\033[32m"
|
|
15
|
+
RESET = "\033[0m"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RtintHdwLutController:
|
|
19
|
+
def __init__(self, tg):
|
|
20
|
+
self._tg = tg
|
|
21
|
+
self._luts: dict[str, RtintHdwLut] | None = None
|
|
22
|
+
self._load()
|
|
23
|
+
|
|
24
|
+
def __info__(self, debug=False):
|
|
25
|
+
if self._luts is None:
|
|
26
|
+
return "\n No LUT in the model"
|
|
27
|
+
|
|
28
|
+
if debug:
|
|
29
|
+
lines = [["Name", "Path", "Enabled"]]
|
|
30
|
+
else:
|
|
31
|
+
lines = [["Name", "Enabled"]]
|
|
32
|
+
for _lut in self._luts.values():
|
|
33
|
+
if debug:
|
|
34
|
+
lines.append(
|
|
35
|
+
[
|
|
36
|
+
_lut._name,
|
|
37
|
+
_lut._unique_name,
|
|
38
|
+
f"{GREEN}True{RESET}" if _lut.enabled else f"{RED}False{RESET}",
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
lines.append(
|
|
43
|
+
[_lut._name, f"{GREEN}True{RESET}" if _lut.enabled else f"{RED}False{RESET}"]
|
|
44
|
+
)
|
|
45
|
+
return "\n" + tabulate(lines, headers="firstrow", tablefmt="grid", stralign="left")
|
|
46
|
+
|
|
47
|
+
def _load(self, force=False):
|
|
48
|
+
luts = self._tg._get_all_objects_from_key("bliss_lut")
|
|
49
|
+
if len(luts) > 0:
|
|
50
|
+
self._luts = {}
|
|
51
|
+
for _lut in luts:
|
|
52
|
+
tg_lut = RtintHdwLut(self._tg, _lut)
|
|
53
|
+
|
|
54
|
+
if hasattr(self, tg_lut._name):
|
|
55
|
+
print(f"{RED}WARNING: LUT '{tg_lut._name}' already exists{RESET}")
|
|
56
|
+
return
|
|
57
|
+
else:
|
|
58
|
+
setattr(self, tg_lut._name, tg_lut)
|
|
59
|
+
self._luts[tg_lut._name] = tg_lut
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RtintHdwLut:
|
|
63
|
+
def __init__(self, tg, unique_name):
|
|
64
|
+
self._tg = tg
|
|
65
|
+
self._unique_name = unique_name
|
|
66
|
+
|
|
67
|
+
def __info__(self):
|
|
68
|
+
lines = []
|
|
69
|
+
lines.append(["", ""])
|
|
70
|
+
lines.append(["Name", self._name])
|
|
71
|
+
lines.append(["Unique Name", self._unique_name])
|
|
72
|
+
enabled_str = f"{GREEN}True{RESET}" if self.enabled else f"{RED}False{RESET}"
|
|
73
|
+
lines.append(["Enabled", enabled_str])
|
|
74
|
+
y_raw = self.y_raw
|
|
75
|
+
lines.append(
|
|
76
|
+
["X raw", f"[{y_raw[0]}, {y_raw[1]}, ..., {y_raw[-2]}, {y_raw[-1]}]"]
|
|
77
|
+
)
|
|
78
|
+
x_raw = self.x_raw
|
|
79
|
+
lines.append(
|
|
80
|
+
["Y raw", f"[{x_raw[0]}, {x_raw[1]}, ..., {x_raw[-2]}, {x_raw[-1]}]"]
|
|
81
|
+
)
|
|
82
|
+
lines.append(["", ""])
|
|
83
|
+
return tabulate(lines, tablefmt="plain", stralign="right")
|
|
84
|
+
|
|
85
|
+
def _tree(self):
|
|
86
|
+
print("Parameters:")
|
|
87
|
+
self._tg.parameter._tree.subtree(
|
|
88
|
+
self._tg._program.name + "/" + self._unique_name
|
|
89
|
+
).show()
|
|
90
|
+
print("Signals:")
|
|
91
|
+
self._tg.signal._tree.subtree(
|
|
92
|
+
self._tg._program.name + "/" + self._unique_name
|
|
93
|
+
).show()
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def _name(self):
|
|
97
|
+
return self._tg.parameter.get(f"{self._unique_name}/bliss_lut/String")
|
|
98
|
+
|
|
99
|
+
def enable(self):
|
|
100
|
+
self._tg.parameter.set(f"{self._unique_name}/enable/Value", 1)
|
|
101
|
+
|
|
102
|
+
def disable(self):
|
|
103
|
+
self._tg.parameter.set(f"{self._unique_name}/enable/Value", 0)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def length(self):
|
|
107
|
+
return len(self.x_raw)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def enabled(self):
|
|
111
|
+
return bool(self._tg.parameter.get(f"{self._unique_name}/enable/Value"))
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def y_raw(self):
|
|
115
|
+
return np.squeeze(self._tg.parameter.get(f"{self._unique_name}/y_raw"))
|
|
116
|
+
|
|
117
|
+
@y_raw.setter
|
|
118
|
+
def y_raw(self, value):
|
|
119
|
+
self._tg.parameter.set(f"{self._unique_name}/y_raw", value)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def x_raw(self):
|
|
123
|
+
return np.squeeze(self._tg.parameter.get(f"{self._unique_name}/x_raw"))
|
|
124
|
+
|
|
125
|
+
@x_raw.setter
|
|
126
|
+
def x_raw(self, value):
|
|
127
|
+
self._tg.parameter.set(f"{self._unique_name}/x_raw", value)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def y_input(self):
|
|
131
|
+
return self._tg.signal.get(f"{self._unique_name}/y_input")
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def y_output(self):
|
|
135
|
+
return self._tg.signal.get(f"{self._unique_name}/y_output")
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def x_data(self):
|
|
139
|
+
return self._tg.signal.get(f"{self._unique_name}/x_data")
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def y_data(self):
|
|
143
|
+
return self._tg.signal.get(f"{self._unique_name}/y_data")
|
rtint/rtint_motor.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
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 MOTORS
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import enum
|
|
11
|
+
from tabulate import tabulate
|
|
12
|
+
|
|
13
|
+
RED = "\033[31m"
|
|
14
|
+
GREEN = "\033[32m"
|
|
15
|
+
RESET = "\033[0m"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MotorState(enum.IntEnum):
|
|
19
|
+
Ready = 0
|
|
20
|
+
Moving = 1
|
|
21
|
+
LimitNeg = 2
|
|
22
|
+
LimitPos = 3
|
|
23
|
+
Stopped = 4
|
|
24
|
+
Error = 5
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RtintHdwMotorController:
|
|
28
|
+
def __init__(self, tg):
|
|
29
|
+
self._tg = tg
|
|
30
|
+
self._motors: dict[str, RtintHdwMotor] | None = None
|
|
31
|
+
self._load()
|
|
32
|
+
|
|
33
|
+
def __info__(self, debug=False):
|
|
34
|
+
if self._motors is None:
|
|
35
|
+
return "\n No Motor in the model"
|
|
36
|
+
|
|
37
|
+
if debug:
|
|
38
|
+
lines = [
|
|
39
|
+
["Name", "Path", "State", "Position", "Limits", "Velocity", "Acc. Time"]
|
|
40
|
+
]
|
|
41
|
+
else:
|
|
42
|
+
lines = [["Name", "State", "Position"]]
|
|
43
|
+
for motor in self._motors.values():
|
|
44
|
+
if debug:
|
|
45
|
+
lines.append(
|
|
46
|
+
[
|
|
47
|
+
motor._name,
|
|
48
|
+
motor._unique_name,
|
|
49
|
+
motor.state.name,
|
|
50
|
+
motor.position,
|
|
51
|
+
(motor.limit_pos, motor.limit_neg),
|
|
52
|
+
motor.velocity,
|
|
53
|
+
motor.acc_time,
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
lines.append([motor._name, motor.state.name, motor.position])
|
|
58
|
+
return "\n" + tabulate(lines, headers="firstrow", tablefmt="grid", stralign="left")
|
|
59
|
+
|
|
60
|
+
def _load(self):
|
|
61
|
+
motors = self._tg._get_all_objects_from_key("bliss_motor")
|
|
62
|
+
if len(motors) > 0:
|
|
63
|
+
self._motors = {}
|
|
64
|
+
for motor in motors:
|
|
65
|
+
tg_motor = RtintHdwMotor(self._tg, motor)
|
|
66
|
+
|
|
67
|
+
if hasattr(self, tg_motor._name):
|
|
68
|
+
print(f"{RED}WARNING: Motor '{tg_motor._name}' already exists{RESET}")
|
|
69
|
+
return
|
|
70
|
+
else:
|
|
71
|
+
setattr(self, tg_motor._name, tg_motor)
|
|
72
|
+
self._motors[tg_motor._name] = tg_motor
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class RtintHdwMotor:
|
|
76
|
+
def __init__(self, tg, unique_name):
|
|
77
|
+
self._tg = tg
|
|
78
|
+
self._unique_name = unique_name
|
|
79
|
+
|
|
80
|
+
def __info__(self):
|
|
81
|
+
lines = []
|
|
82
|
+
lines.append(["Name", self._name])
|
|
83
|
+
lines.append(["Unique Name", self._unique_name])
|
|
84
|
+
lines.append(["", ""])
|
|
85
|
+
lines.append(["State", self.state.name])
|
|
86
|
+
lines.append(["Position", self.position])
|
|
87
|
+
lines.append(["", ""])
|
|
88
|
+
lines.append(["Velocity", self.velocity])
|
|
89
|
+
lines.append(["Acc. Time", self.acc_time])
|
|
90
|
+
lines.append(["Limit Neg.", self.limit_neg])
|
|
91
|
+
lines.append(["Limit Pos.", self.limit_pos])
|
|
92
|
+
return tabulate(lines, tablefmt="plain", stralign="right")
|
|
93
|
+
|
|
94
|
+
def _tree(self):
|
|
95
|
+
print("Parameters:")
|
|
96
|
+
self._tg.parameter._tree.subtree(
|
|
97
|
+
self._tg._program.name + "/" + self._unique_name
|
|
98
|
+
).show()
|
|
99
|
+
print("Signals:")
|
|
100
|
+
self._tg.signal._tree.subtree(
|
|
101
|
+
self._tg._program.name + "/" + self._unique_name
|
|
102
|
+
).show()
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def _name(self):
|
|
106
|
+
return self._tg.parameter.get(f"{self._unique_name}/bliss_motor/String")
|
|
107
|
+
|
|
108
|
+
def start(self):
|
|
109
|
+
val = self._tg.parameter.get(f"{self._unique_name}/start_trigger/Bias")
|
|
110
|
+
self._tg.parameter.set(
|
|
111
|
+
f"{self._unique_name}/start_trigger/Bias", val + 1
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def stop(self):
|
|
115
|
+
val = self._tg.parameter.get(f"{self._unique_name}/stop_trigger/Bias")
|
|
116
|
+
self._tg.parameter.set(f"{self._unique_name}/stop_trigger/Bias", val + 1)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def acc_time(self):
|
|
120
|
+
return float(self._tg.parameter.get(f"{self._unique_name}/acc_time"))
|
|
121
|
+
|
|
122
|
+
@acc_time.setter
|
|
123
|
+
def acc_time(self, acc_time):
|
|
124
|
+
self._tg.parameter.set(f"{self._unique_name}/acc_time", acc_time)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def limit_neg(self):
|
|
128
|
+
return float(self._tg.parameter.get(f"{self._unique_name}/limit_neg"))
|
|
129
|
+
|
|
130
|
+
@limit_neg.setter
|
|
131
|
+
def limit_neg(self, limit_neg):
|
|
132
|
+
self._tg.parameter.set(f"{self._unique_name}/limit_neg", limit_neg)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def limit_pos(self):
|
|
136
|
+
return float(self._tg.parameter.get(f"{self._unique_name}/limit_pos"))
|
|
137
|
+
|
|
138
|
+
@limit_pos.setter
|
|
139
|
+
def limit_pos(self, limit_pos):
|
|
140
|
+
self._tg.parameter.set(f"{self._unique_name}/limit_pos", limit_pos)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def setpoint(self):
|
|
144
|
+
return float(self._tg.signal.get(f"{self._unique_name}/motor_setpoint"))
|
|
145
|
+
|
|
146
|
+
@setpoint.setter
|
|
147
|
+
def setpoint(self, setpoint):
|
|
148
|
+
if (
|
|
149
|
+
self._tg.parameter.get(
|
|
150
|
+
f"{self._unique_name}/select_setpoint/tracking_mode/Value"
|
|
151
|
+
)
|
|
152
|
+
!= 0
|
|
153
|
+
):
|
|
154
|
+
raise RuntimeError("Motor is in tracking mode")
|
|
155
|
+
|
|
156
|
+
self._tg.parameter.set(f"{self._unique_name}/setpoint", setpoint)
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def velocity(self):
|
|
160
|
+
return float(self._tg.parameter.get(f"{self._unique_name}/velocity"))
|
|
161
|
+
|
|
162
|
+
@velocity.setter
|
|
163
|
+
def velocity(self, velocity):
|
|
164
|
+
self._tg.parameter.set(f"{self._unique_name}/velocity", velocity)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def position(self):
|
|
168
|
+
return float(self._tg.signal.get(f"{self._unique_name}/motor_position"))
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def state(self):
|
|
172
|
+
return MotorState(
|
|
173
|
+
int(self._tg.signal.get(f"{self._unique_name}/motor_state"))
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def position_encoder(self):
|
|
178
|
+
if self._tg.signal._tree.contains(f"{self._unique_name}/encoder_value"):
|
|
179
|
+
return float(
|
|
180
|
+
self._tg.signal.get(f"{self._unique_name}/encoder_value")
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def position_error(self):
|
|
185
|
+
if self._tg.signal._tree.contains(f"{self._unique_name}/error_value"):
|
|
186
|
+
return float(self._tg.signal.get(f"{self._unique_name}/error_value"))
|
|
187
|
+
|
|
188
|
+
def _activate_tracking(self, tracking_enable):
|
|
189
|
+
self._tg.parameter.set(
|
|
190
|
+
f"{self._unique_name}/select_setpoint/tracking_mode/Value", tracking_enable
|
|
191
|
+
)
|