symetrie-hexapod 0.17.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,550 @@
1
+ import math
2
+
3
+ import numpy as np
4
+
5
+ from egse.bits import set_bit
6
+ from egse.coordinates import ReferenceFrame
7
+ from egse.hexapod.symetrie import logger
8
+ from egse.hexapod.symetrie.pmac import decode_Q36
9
+
10
+
11
+ class HexapodSimulator:
12
+ """
13
+ HexapodSimulator simulates the Symétrie Hexapod. The class is heavily based on the
14
+ ReferenceFrames in the `egse.coordinates` package.
15
+
16
+ The simulator implements the same methods as the HexapodController class which acts on the
17
+ real hardware controller in either simulation mode or with a real Hexapod PUNA connected.
18
+
19
+ Therefore, the HexapodSimulator can be used instead of the Hexapod class in test harnesses
20
+ and when the hardware is not available.
21
+
22
+ This class simulates all the movements and status of the Hexapod.
23
+ """
24
+
25
+ def __init__(self):
26
+ identity = np.identity(4)
27
+
28
+ # Rotation around static axis, and around x, y and z in that order
29
+ self.rot_config = "sxyz"
30
+
31
+ # Configure the Master Reference Frame
32
+ self.cs_master = ReferenceFrame.createMaster()
33
+
34
+ # Configure the Machine Coordinate System, i.e. cs_mec [ref:cs_master]
35
+ self.cs_machine = ReferenceFrame(
36
+ transformation=identity,
37
+ ref=self.cs_master,
38
+ name="Machine[Master]",
39
+ rot_config=self.rot_config,
40
+ )
41
+
42
+ # Configure the Platform Coordinate System, i.e. cs_platform [ref:cs_machine]
43
+ # default after homing: PLATFORM = MACHINE
44
+
45
+ self.cs_platform = ReferenceFrame(
46
+ transformation=identity,
47
+ ref=self.cs_machine,
48
+ name="Platform[Machine]",
49
+ rot_config=self.rot_config,
50
+ )
51
+
52
+ # Configure the User Coordinate System, i.e. cs_user [ref:cs_machine]
53
+ self.cs_user = ReferenceFrame(
54
+ transformation=identity,
55
+ ref=self.cs_machine,
56
+ name="User[Machine]",
57
+ rot_config=self.rot_config,
58
+ )
59
+
60
+ # Configure the Object Coordinate System, i.e. cs_object [ref:cs_platform]
61
+ self.cs_object = ReferenceFrame(
62
+ transformation=identity,
63
+ ref=self.cs_platform,
64
+ name="Object[Platform]",
65
+ rot_config=self.rot_config,
66
+ )
67
+
68
+ # We use a CS called cs_object_in_user, i.e. Object as defined in the User CS,
69
+ # and we define this
70
+ # from the transformation user -> object.
71
+
72
+ tf_user_to_object = self.cs_user.getActiveTransformationTo(self.cs_object)
73
+ self.cs_object_in_user = ReferenceFrame(
74
+ tf_user_to_object, rot_config=self.rot_config, ref=self.cs_user, name="Object[User]"
75
+ )
76
+
77
+ # Define the invariant links within the system, i.e. some systems are bound with an
78
+ # invariant transformation
79
+ # matrix and those links shall be preserved throughout the movement within the system.
80
+
81
+ # We link this cs_object_in_user to cs_object with the identity transformation,
82
+ # which connects them together
83
+
84
+ self.cs_object_in_user.addLink(self.cs_object, transformation=identity)
85
+
86
+ # The User Coordinate System is linked to the Machine Coordinate System
87
+
88
+ self.cs_machine.addLink(self.cs_user, transformation=self.cs_user.transformation)
89
+
90
+ # The Object Coordinate System is linked to the Platform Coordinate System
91
+
92
+ self.cs_platform.addLink(self.cs_object, transformation=self.cs_object.transformation)
93
+
94
+ # Keep a record if the homing() command has been executed.
95
+
96
+ self.homing_done = False
97
+ self.control_loop = False
98
+ self._virtual_homing = False
99
+ self._virtual_homing_position = None
100
+
101
+ # Just keep the speed settings, no used in movement currently
102
+
103
+ self._speed = [1.0, 1.0, 0.01, 0.001, 4.0, 2.0]
104
+
105
+ # Print out some debugging information
106
+
107
+ logger.debug(f"Linked to cs_object_in_user {[i.name for i in self.cs_object_in_user.linkedTo]}")
108
+ logger.debug(f"Linked to cs_object {[i.name for i in self.cs_object.linkedTo]}")
109
+ logger.debug(f"Linked to cs_platform {[i.name for i in self.cs_platform.linkedTo]}")
110
+ logger.debug(f"Linked to cs_machine {[i.name for i in self.cs_machine.linkedTo or {}]}")
111
+
112
+ def is_simulator(self):
113
+ return True
114
+
115
+ def connect(self):
116
+ pass
117
+
118
+ def reconnect(self):
119
+ pass
120
+
121
+ def disconnect(self):
122
+ # TODO:
123
+ # Should I keep state in this class to check if it has been disconnected?
124
+ #
125
+ # TODO:
126
+ # What happens when I re-connect to this Simulator? Shall it be in Homing position or
127
+ # do I have to keep state via a persistency mechanism?
128
+ pass
129
+
130
+ def is_connected(self):
131
+ return True
132
+
133
+ def reset(self, wait=True, verbose=False):
134
+ # TODO:
135
+ # Find out what exactly a reset() should be doing. Does it bring back the Hexapod
136
+ # in it's original state, loosing all definitions of coordinate systems? Or does it
137
+ # do a clearError() and a homing()?
138
+ pass
139
+
140
+ def homing(self):
141
+ self.goto_zero_position()
142
+ self.homing_done = True
143
+ self._virtual_homing = False
144
+ self._virtual_homing_position = None
145
+ return 0
146
+
147
+ def is_homing_done(self):
148
+ return self.homing_done
149
+
150
+ def set_virtual_homing(self, tx, ty, tz, rx, ry, rz):
151
+ self._virtual_homing_position = [tx, ty, tz, rx, ry, rz]
152
+ self._virtual_homing = True
153
+ return 0
154
+
155
+ def stop(self):
156
+ pass
157
+
158
+ def clear_error(self):
159
+ return 0
160
+
161
+ def activate_control_loop(self):
162
+ self.control_loop = True
163
+ return self.control_loop
164
+
165
+ def deactivate_control_loop(self):
166
+ self.control_loop = False
167
+ return self.control_loop
168
+
169
+ def configure_coordinates_systems(self, tx_u, ty_u, tz_u, rx_u, ry_u, rz_u, tx_o, ty_o, tz_o, rx_o, ry_o, rz_o):
170
+ identity = np.identity(4)
171
+
172
+ # Redefine the User Coordinate System
173
+
174
+ translation = np.array([tx_u, ty_u, tz_u])
175
+ rotation = np.array([rx_u, ry_u, rz_u])
176
+ degrees = True
177
+
178
+ # Remove the old links between user and machine CS, and between Object in User and Object CS
179
+
180
+ self.cs_machine.removeLink(self.cs_user)
181
+ self.cs_object_in_user.removeLink(self.cs_object)
182
+
183
+ # Redefine the User Coordinate System
184
+
185
+ self.cs_user = ReferenceFrame.fromTranslationRotation(
186
+ translation,
187
+ rotation,
188
+ rot_config=self.rot_config,
189
+ ref=self.cs_machine,
190
+ name="User[Machine]",
191
+ degrees=degrees,
192
+ )
193
+
194
+ # Redefine the Object in User Coordinate System
195
+
196
+ tf_user_to_object = self.cs_user.getActiveTransformationTo(self.cs_object)
197
+ self.cs_object_in_user = ReferenceFrame(
198
+ tf_user_to_object, rot_config=self.rot_config, ref=self.cs_user, name="Object[User]"
199
+ )
200
+
201
+ # Define the invariant links within the system, i.e. some systems are bound with an
202
+ # invariant transformation
203
+ # matrix and those links shall be preserved throughout the movement within the system.
204
+
205
+ # User and Machine CS are invariant, reset the transformation. User in Object is
206
+ # identical to Object
207
+
208
+ self.cs_machine.addLink(self.cs_user, transformation=self.cs_user.transformation)
209
+ self.cs_object_in_user.addLink(self.cs_object, transformation=identity)
210
+
211
+ # Redefine the Object Coordinates System
212
+
213
+ translation = np.array([tx_o, ty_o, tz_o])
214
+ rotation = np.array([rx_o, ry_o, rz_o])
215
+ degrees = True
216
+
217
+ # Remove the old links between user and machine CS, and between Object in User and Object CS
218
+
219
+ self.cs_platform.removeLink(self.cs_object)
220
+ self.cs_object_in_user.removeLink(self.cs_object)
221
+
222
+ self.cs_object = ReferenceFrame.fromTranslationRotation(
223
+ translation,
224
+ rotation,
225
+ rot_config=self.rot_config,
226
+ ref=self.cs_platform,
227
+ name="Object[Platform]",
228
+ degrees=degrees,
229
+ )
230
+
231
+ # Redefine the Object in User Coordinate System
232
+
233
+ tf_user_to_object = self.cs_user.getActiveTransformationTo(self.cs_object)
234
+ self.cs_object_in_user = ReferenceFrame(
235
+ tf_user_to_object, rot_config=self.rot_config, ref=self.cs_user, name="Object[User]"
236
+ )
237
+
238
+ # Object CS and Platform CS are invariant, reset the transformation. User in Object is
239
+ # identical to Object
240
+
241
+ self.cs_platform.addLink(self.cs_object, transformation=self.cs_object.transformation)
242
+ self.cs_object_in_user.addLink(self.cs_object, transformation=identity)
243
+
244
+ return 0
245
+
246
+ def get_coordinates_systems(self):
247
+ degrees = True
248
+
249
+ t_user, r_user = self.cs_user.getTranslationRotationVectors(degrees=degrees)
250
+ t_object, r_object = self.cs_object.getTranslationRotationVectors(degrees=degrees)
251
+
252
+ return list(np.concatenate((t_user, r_user, t_object, r_object)))
253
+
254
+ def move_absolute(self, tx, ty, tz, rx, ry, rz):
255
+ # FIXME:
256
+ # to really simulate the behavior of the Hexapod, this method should implement limit
257
+ # checking and other condition or error checking, e.g. argument matching, etc.
258
+
259
+ logger.debug(f"moveAbsolute with {tx}, {ty}, {tz}, {rx}, {ry}, {rz}")
260
+
261
+ translation = np.array([tx, ty, tz])
262
+ rotation = np.array([rx, ry, rz])
263
+
264
+ # We set a new transformation for cs_object_in_user which will update our model,
265
+ # because cs_object and cs_object_in_user are linked.
266
+
267
+ self.cs_object_in_user.setTranslationRotation(
268
+ translation,
269
+ rotation,
270
+ rot_config=self.rot_config,
271
+ active=True,
272
+ degrees=True,
273
+ preserveLinks=True,
274
+ )
275
+
276
+ return 0
277
+
278
+ def move_relative_object(self, tx, ty, tz, rx, ry, rz):
279
+ tr_rel = np.array([tx, ty, tz])
280
+ rot_rel = np.array([rx, ry, rz])
281
+
282
+ self.cs_object.applyTranslationRotation(
283
+ tr_rel,
284
+ rot_rel,
285
+ rot_config=self.rot_config,
286
+ active=True,
287
+ degrees=True,
288
+ preserveLinks=True,
289
+ )
290
+
291
+ def move_relative_user(self, tx, ty, tz, rx, ry, rz):
292
+ # The Symétrie Hexapod definition of moveRelativeUser
293
+ #
294
+ # - Translation and rotations are expressed in USER reference frame
295
+ #
296
+ # - Actually,
297
+ # - the translations happen parallel to the USER reference frame
298
+ # - the rotations are applied after the translations, on the OBJ ReferenceFrame
299
+ #
300
+ # To achieve this,
301
+ #
302
+ # * OBUSR is "de-rotated", to become parallel to USR
303
+ # * the requested translation is applied
304
+ # * OBUSR is "re-rotated" to its original orientation
305
+ # * the requested rotation is applied
306
+
307
+ # Derotation of cs_object --> cs_user
308
+
309
+ derotation = self.cs_object.getActiveTransformationTo(self.cs_user)
310
+ derotation[:3, 3] = [0, 0, 0]
311
+
312
+ # Reverse rotation
313
+
314
+ rerotation = derotation.T
315
+
316
+ # Requested translation matrix
317
+
318
+ translation = np.identity(4)
319
+ translation[:3, 3] = [tx, ty, tz]
320
+
321
+ # Requested rotation matrix
322
+
323
+ import egse.coordinates.transform3d_addon as t3add
324
+
325
+ rotation = t3add.translationRotationToTransformation([0, 0, 0], [rx, ry, rz], rot_config=self.rot_config)
326
+
327
+ transformation = derotation @ translation @ rerotation @ rotation
328
+
329
+ # Adapt our model
330
+
331
+ self.cs_object.applyTransformation(transformation)
332
+
333
+ def check_absolute_movement(self, tx, ty, tz, rx, ry, rz):
334
+ rc = 0
335
+ rc_dict = {}
336
+ if not -30.0 <= tx <= 30.0:
337
+ rc += 1
338
+ rc_dict.update({1: "Tx should be in range ±30.0 mm"})
339
+ if not -30.0 <= ty <= 30.0:
340
+ rc += 1
341
+ rc_dict.update({2: "Ty should be in range ±30.0 mm"})
342
+ if not -20.0 <= tz <= 20.0:
343
+ rc += 1
344
+ rc_dict.update({3: "Tz should be in range ±20.0 mm"})
345
+ if not -11.0 <= rx <= 11.0:
346
+ rc += 1
347
+ rc_dict.update({4: "Rx should be in range ±11.0 mm"})
348
+ if not -11.0 <= ry <= 11.0:
349
+ rc += 1
350
+ rc_dict.update({5: "Ry should be in range ±11.0 mm"})
351
+ if not -20.0 <= rz <= 20.0:
352
+ rc += 1
353
+ rc_dict.update({6: "Rz should be in range ±20.0 mm"})
354
+ return rc, rc_dict
355
+
356
+ def check_relative_object_movement(self, tx, ty, tz, rx, ry, rz):
357
+ return 0, {}
358
+
359
+ def check_relative_user_movement(self, tx, ty, tz, rx, ry, rz):
360
+ return 0, {}
361
+
362
+ def get_user_positions(self):
363
+ t, r = self.cs_user.getActiveTranslationRotationVectorsTo(self.cs_object_in_user)
364
+
365
+ pos = list(np.concatenate((t, r)))
366
+
367
+ return pos
368
+
369
+ def get_machine_positions(self):
370
+ t, r = self.cs_platform.getTranslationRotationVectors()
371
+ t, r = self.cs_machine.getActiveTranslationRotationVectorsTo(self.cs_platform)
372
+
373
+ pos = list(np.concatenate((t, r)))
374
+
375
+ return pos
376
+
377
+ def get_actuator_length(self):
378
+ alen = [math.nan for _ in range(6)]
379
+
380
+ return alen
381
+
382
+ def get_general_state(self):
383
+ state = 0
384
+ state = set_bit(state, 1) # System Initialized
385
+ state = set_bit(state, 2) # In Position
386
+ if self.homing_done:
387
+ state = set_bit(state, 4)
388
+ if self.control_loop:
389
+ state = set_bit(state, 3)
390
+ if self._virtual_homing:
391
+ state = set_bit(state, 18)
392
+ return state, decode_Q36(state)
393
+
394
+ def goto_specific_position(self, pos):
395
+ return 0
396
+
397
+ def goto_retracted_position(self):
398
+ translation = np.array([0, 0, -20])
399
+ rotation = np.array([0, 0, 0])
400
+
401
+ self.cs_platform.setTranslationRotation(
402
+ translation,
403
+ rotation,
404
+ rot_config=self.rot_config,
405
+ active=True,
406
+ degrees=True,
407
+ preserveLinks=True,
408
+ )
409
+
410
+ return 0
411
+
412
+ def goto_zero_position(self):
413
+ # We set a new transformation for cs_platform which will update our model.
414
+ # See issue #58: updating the cs_platform is currently not a good idea because that will
415
+ # not properly update the chain/path upwards, i.e. the chain/path is followed in the
416
+ # direction of the references that are defined, not in the other direction.
417
+ #
418
+ translation = np.array([0, 0, 0])
419
+ rotation = np.array([0, 0, 0])
420
+
421
+ self.cs_platform.setTranslationRotation(
422
+ translation,
423
+ rotation,
424
+ rot_config=self.rot_config,
425
+ active=True,
426
+ degrees=True,
427
+ preserveLinks=True,
428
+ )
429
+
430
+ # As a work around for the bug in issue #58, we determine the transformation from
431
+ # cs_object as it is
432
+ # invariantly linked with cs_platform. Updating cs_object_in_user with that
433
+ # transformation will
434
+ # properly update all the reference frames.
435
+
436
+ # tr_abs, rot_abs = self.cs_object.getTranslationRotationVectors()
437
+ #
438
+ # self.cs_object_in_user.setTranslationRotation(
439
+ # tr_abs,
440
+ # rot_abs,
441
+ # rot_config=self.rot_config,
442
+ # active=True,
443
+ # degrees=True,
444
+ # preserveLinks=True,
445
+ # )
446
+
447
+ return 0
448
+
449
+ def is_in_position(self):
450
+ return True
451
+
452
+ def jog(self, axis: int, inc: float) -> int:
453
+ pass
454
+
455
+ def get_debug_info(self):
456
+ pass
457
+
458
+ def set_speed(self, vt, vr):
459
+ self._speed[0] = vt
460
+ self._speed[1] = vr
461
+
462
+ def get_speed(self):
463
+ return tuple(self._speed)
464
+
465
+ def get_actuator_state(self):
466
+ return [
467
+ (
468
+ {
469
+ 0: "In position",
470
+ 1: "Control loop on servo motors active",
471
+ 2: "Homing done",
472
+ 4: 'Input "Positive limit switch"',
473
+ 5: 'Input "Negative limit switch"',
474
+ 6: "Brake control output",
475
+ },
476
+ [1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
477
+ ),
478
+ (
479
+ {
480
+ 0: "In position",
481
+ 1: "Control loop on servo motors active",
482
+ 2: "Homing done",
483
+ 4: 'Input "Positive limit switch"',
484
+ 5: 'Input "Negative limit switch"',
485
+ 6: "Brake control output",
486
+ },
487
+ [1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
488
+ ),
489
+ (
490
+ {
491
+ 0: "In position",
492
+ 1: "Control loop on servo motors active",
493
+ 2: "Homing done",
494
+ 4: 'Input "Positive limit switch"',
495
+ 5: 'Input "Negative limit switch"',
496
+ 6: "Brake control output",
497
+ },
498
+ [1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
499
+ ),
500
+ (
501
+ {
502
+ 0: "In position",
503
+ 1: "Control loop on servo motors active",
504
+ 2: "Homing done",
505
+ 4: 'Input "Positive limit switch"',
506
+ 5: 'Input "Negative limit switch"',
507
+ 6: "Brake control output",
508
+ },
509
+ [1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
510
+ ),
511
+ (
512
+ {
513
+ 0: "In position",
514
+ 1: "Control loop on servo motors active",
515
+ 2: "Homing done",
516
+ 4: 'Input "Positive limit switch"',
517
+ 5: 'Input "Negative limit switch"',
518
+ 6: "Brake control output",
519
+ },
520
+ [1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
521
+ ),
522
+ (
523
+ {
524
+ 0: "In position",
525
+ 1: "Control loop on servo motors active",
526
+ 2: "Homing done",
527
+ 4: 'Input "Positive limit switch"',
528
+ 5: 'Input "Negative limit switch"',
529
+ 6: "Brake control output",
530
+ },
531
+ [1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
532
+ ),
533
+ ]
534
+
535
+ def perform_maintenance(self, axis):
536
+ pass
537
+
538
+ def info(self):
539
+ msg = "Info about the PunaSimulator:\n"
540
+ msg += "\n"
541
+ msg += "This Hexapod PUNA Simulator works with several reference frames:\n"
542
+ msg += " * The machine reference frame\n"
543
+ msg += " * The platform reference frame\n"
544
+ msg += " * The object reference frame\n"
545
+ msg += " * The user reference frame\n\n"
546
+ msg += "Any movement commands result in a transformation of the appropriate coordinate systems."
547
+
548
+ logger.info(msg)
549
+
550
+ return msg