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,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
+ )