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,337 @@
1
+ BaseClass:
2
+ egse.hexapod.symetrie.alpha.AlphaPlusControllerInterface
3
+
4
+ ProxyClass:
5
+ egse.hexapod.symetrie.zonda.ZondaProxy
6
+
7
+ ControlServerClass:
8
+ egse.hexapod.symetrie.zonda_cs.ZondaControlServer
9
+
10
+ ControlServer:
11
+ egse.hexapod.symetrie.zonda_cs
12
+
13
+ UserInterface:
14
+ egse.hexapod.symetrie.zonda_ui
15
+
16
+ Commands:
17
+
18
+ # Each of these groups is parsed and used on both the server and the client side.
19
+ #
20
+ # The group name (e.g. is_simulator) will be monkey patched in the Proxy class for the device
21
+ # or service.
22
+ #
23
+ # The other field are:
24
+ # description: Used by the doc_string method to generate a help string
25
+ # cmd: Command string that will eventually be sent to the hardware controller for
26
+ # the device. This cmd string is also used at the client side to parse and
27
+ # validate the arguments.
28
+ # device_method: The name of the method to be called on the device class.
29
+ # These should all be defined by the interface class for the device, i.e.
30
+ # ZondaInterface in this case.
31
+ # When the device_method is the same as the group name, it can be omitted.
32
+ # response: The name of the method to be called from the device protocol.
33
+ # This method should exist in the subclass of the CommandProtocol base class,
34
+ # i.e. in this case it will be the ZondaProtocol class.
35
+ # The default (when no response is given) is 'handle_device_method'.
36
+
37
+ # Definition of the DeviceInterface
38
+
39
+ is_simulator:
40
+ description: Ask if the connected class is a simulator instead of the real device Controller class.
41
+ returns: bool | True if the far end is a simulator instead of the real hardware
42
+
43
+ is_connected:
44
+ description: Check if the Hexapod hardware controller is connected.
45
+
46
+ connect:
47
+ description: Connect the Hexapod hardware controller
48
+
49
+ reconnect:
50
+ description: Reconnect the Hexapod hardware controller.
51
+
52
+ This command will force a disconnect and then try to re-connect to the controller.
53
+
54
+ disconnect:
55
+ description: Disconnect from the hexapod controller.
56
+
57
+ This command will be send to the Hexapod Control Server which will then
58
+ disconnect from the hardware controller.
59
+
60
+ This command does not affect the ZeroMQ connection of the Proxy to the
61
+ control server. Use the service command `disconnect_cs()` to disconnect
62
+ from the control server.
63
+
64
+ info:
65
+ description: Retrieve basic information about the Hexapod and the Controller.
66
+
67
+ get_general_state:
68
+ description: Retrieve general state information of the hexapod.
69
+
70
+ reset:
71
+ description: Reboot the controller. This command reboot the controller and the Ethernet communication is
72
+ closed. The controller takes about 2 minutes to initialize. The configuration changes (commands
73
+ of “CFG” family) will be lost if no CFG_SAVE command were sent since last configuration
74
+ modifications The system reboot command is interpreted directly by the operating system of the
75
+ controller. STOP and CONTROLOFF the hexapod before sending the command.
76
+
77
+ homing:
78
+ description: Start the homing cycle for the Hexapod ZONDA. Homing is required before performing a control
79
+ movement. Without absolute encoders, the homing is performed with a hexapod movement until
80
+ detecting the reference sensor on each of the actuators. The Hexapod will go to a position were
81
+ the sensors are reached that signal a known calibrated position and then returns to the zero
82
+ position.
83
+ Whenever a homing is performed, the method will return before the actual movement is finished.
84
+ The homing cycle takes about two minutes to complete, but the homing() method returns almost
85
+ immediately. Therefore, to check if the homing is finished, use the is_homing_done() method.
86
+
87
+ stop:
88
+ description: Stop the current motion.
89
+
90
+ clear_error:
91
+ description: Clear all errors in the controller software.
92
+
93
+ activate_control_loop:
94
+ description: Activates the control loop on motors. It activates the power on the motors and release the
95
+ brakes if present. The hexapod status Control on switches to true when the command is successful.
96
+
97
+ deactivate_control_loop:
98
+ description: Disables the control loop on the servo motors.
99
+
100
+ jog:
101
+ description: Starts a JOG-type movement on the actuator defined by the arguments. A JOG type movement is a
102
+ movement done without using the kinematic of the hexapod. It's a relative movement along a
103
+ defined actuator. The actuator number is defined by the "axis" variable. The increment "inc"
104
+ can be positive to extend the actuator or negative to retract the actuator.
105
+
106
+ Arguments
107
+
108
+ axis(int) number of the actuator (1 to 6)
109
+ inc(float) increment to achieve in mm
110
+
111
+ * Note this is a maintenance feature.
112
+ * Note for this command the home do not necessary has to be completed. It is important to
113
+ realize that moving the actuator XX mm doesn't correspond to move XX mm in the hexapod workspace
114
+
115
+ set_speed:
116
+ description: Set the speed of the hexapod movements according to vt and vr arguments.
117
+
118
+ vt is the translation speed of the hexapod in mm per second [mm/s]
119
+ vr is the angular speed of the hexapod in deg per second [deg/s]
120
+
121
+ The parameters vt and vr are automatically limited by the controller between the factory
122
+ configured minimum and maximum speed
123
+ cmd: "{vt} {vr}"
124
+
125
+ sequence:
126
+ description: Execute a sequence of several points.
127
+ First, it checks if limits are enabled (get_limit_state) and if not it enables them (user_limit_enable?? or machine_limit_enable??).
128
+ Secondly, it checks if all points of the sequence are reachable (not outside the limits/workspace).
129
+ Eventually, it moves hexapod following the sequence step by step (and reads the step n+1 after that the step n is complete).
130
+ After a step it waits for "time_sleep" seconds before reading the next step.
131
+ cmd: "{file_path} {time_sleep}"
132
+
133
+ get_speed:
134
+ description: Retrieve the configuration of the movement speed.
135
+
136
+ vt is the translation speed of the hexapod in mm per second [mm/s]
137
+ vr is the angular speed of the hexapod in deg per second [deg/s]
138
+ vt_min, vt_max are the limits for the translation speed [mm/s]
139
+ vr_min, vr_max are the limits for the angular speed [mm/s]
140
+
141
+ get_temperature:
142
+ description: Retrieves the value of the 6 PT100 temperature sensors in C.
143
+ The security temperature limit has been factory set to 60 C.
144
+ The controller will be in error state and thus the hexapod movement will stop if the
145
+ temperature limit is exceeded. In that case, clear the error before using the Hexapod again.
146
+ returns: list | list of floats with the temperature values
147
+
148
+ perform_maintenance:
149
+ description: Starts a maintenance cycle. The mode selects the type of movement.
150
+
151
+ Arguments
152
+ mode = 1 Jog to zero, no other parameter is required for this mode. The Machine zero position
153
+ is reached by moving each actuator in JOG mode without inter-axis interpolation (kinematic
154
+ disabled). Backlash compensation is not performed with this mode. This is a maintenance
155
+ command that should be used only in case of kinematic error or failure of one/several
156
+ actuators.
157
+ mode = 2 Move along a single operational axis, the parameter axis is needed to define the
158
+ operational axis. The movements are executed in the machine coordinate system. The sequence
159
+ is (1) movement to machine zero (2) movement to axis operational negative limit (3) movement
160
+ to axis operational positive limit (4) movement to machine zero). The operational limits are
161
+ factory defined.
162
+ mode = 3 Move along all the operational axis one after the other, No other parameter is
163
+ required for this mode. Movements are executed in the machine coordinate system. The sequence
164
+ execute the sequence alon a single operational axis, one axis after another. The axis order is
165
+ Tx, Ty, Tz, Rx, Ry, Rz.
166
+
167
+ axis(int) defines the movement around the selected axis
168
+ cmd: "{axis}"
169
+
170
+ goto_specific_position:
171
+ description: Ask to go to a specific position.
172
+ * pos=1 User zero
173
+ * pos=2 Retracted position
174
+ * pos=3 Machine zero
175
+ cmd: "{pos}"
176
+
177
+ goto_retracted_position:
178
+ description: Ask the hexapod to go to the retracted position.
179
+
180
+ goto_zero_position:
181
+ description: Ask the hexapod to go to the user zero position.
182
+
183
+ is_homing_done:
184
+ description: Check if homing is done.
185
+
186
+ is_in_position:
187
+ description: Check if the actuators are in position.
188
+
189
+ move_absolute:
190
+ description: Starts the movement defined by the arguments.
191
+
192
+ tx(float) position on the X-axis [mm]
193
+ ty(float) position on the Y-axis [mm]
194
+ tz(float) position on the Z-axis [mm]
195
+ rx(float) rotation around the X-axis [deg]
196
+ ry(float) rotation around the Y-axis [deg]
197
+ rz(float) rotation around the Z-axis [deg]
198
+
199
+ Move/define the Object Coordinate System position and orientation expressed in the invariant
200
+ user coordinate system.
201
+
202
+ The rotation centre coincides with the Object Coordinates System origin and
203
+ the movements are controlled with translation components at first (Tx, Ty, tZ)
204
+ and then the rotation components (Rx, Ry, Rz).
205
+
206
+ Will raise an error code if the following conditions are not met
207
+ * there is no motion task running (motion task running)
208
+ * home is completed (home complete),
209
+ * the control loop on servo motors is activated (control on),
210
+ * the hexapod is not stopping
211
+ cmd: "{tx} {ty} {tz} {rx} {ry} {rz}"
212
+
213
+ move_relative_object:
214
+ description: Starts the movement defined by the arguments.
215
+
216
+ tx(float) position on the X-axis [mm]
217
+ ty(float) position on the Y-axis [mm]
218
+ tz(float) position on the Z-axis [mm]
219
+ rx(float) rotation around the X-axis [deg]
220
+ ry(float) rotation around the Y-axis [deg]
221
+ rz(float) rotation around the Z-axis [deg]
222
+
223
+ Move the object relative to its current object position and orientation. The relative movement
224
+ is expressed in the object coordinate system.
225
+
226
+ Will raise an error code if the following conditions are not met
227
+ * there is no motion task running (motion task running)
228
+ * home is completed (home complete),
229
+ * the control loop on servo motors is activated (control on),
230
+ * the hexapod is not stopping
231
+ cmd: "{tx} {ty} {tz} {rx} {ry} {rz}"
232
+
233
+ move_relative_user:
234
+ description: Starts the movement defined by the arguments.
235
+
236
+ tx(float) position on the X-axis [mm]
237
+ ty(float) position on the Y-axis [mm]
238
+ tz(float) position on the Z-axis [mm]
239
+ rx(float) rotation around the X-axis [deg]
240
+ ry(float) rotation around the Y-axis [deg]
241
+ rz(float) rotation around the Z-axis [deg]
242
+
243
+ The object is moved relative to its current object position and orientation. The relative
244
+ movement is expressed in the (invariant) user coordinate system.
245
+
246
+ Will raise an error code if the following conditions are not met
247
+ * there is no motion task running (motion task running)
248
+ * home is completed (home complete),
249
+ * the control loop on servo motors is activated (control on),
250
+ * the hexapod is not stopping
251
+ cmd: "{tx} {ty} {tz} {rx} {ry} {rz}"
252
+
253
+ check_absolute_movement:
254
+ description: Verifies if the movement defined by the arguments is feasible in absolute coordinate system.
255
+ After command execution, when the command execution has been successful, the result of the vali-
256
+ dation returns
257
+ 0 when target position is valid
258
+ Positive value when the validation shows limitations
259
+ Negative value when the validation fails
260
+ cmd: "{tx} {ty} {tz} {rx} {ry} {rz}"
261
+
262
+ check_relative_object_movement:
263
+ description: Verifies if the movement defined by the arguments is feasible in user coordinate system.
264
+ After command execution, when the command execution has been successful, the result of the
265
+ validation returns
266
+ 0 when target position is valid
267
+ Positive value when the validation shows limitations
268
+ Negative value when the validation fails
269
+ cmd: "{tx} {ty} {tz} {rx} {ry} {rz}"
270
+
271
+ check_relative_user_movement:
272
+ description: Verifies if the movement defined by the arguments is feasible in user coordinate system.
273
+ After command execution, when the command execution has been successful, the result of the
274
+ validation returns
275
+ 0 when target position is valid
276
+ Positive value when the validation shows limitations
277
+ Negative value when the validation fails
278
+ cmd: "{tx} {ty} {tz} {rx} {ry} {rz}"
279
+
280
+ get_coordinates_systems:
281
+ description: Retrieve the definition of the User Coordinate System and the Object Coordinate System.
282
+ Returns tx_u, ty_u, tz_u, rx_u, ry_u, rz_u, tx_o, ty_o, tz_o, rx_o, ry_o, rz_o where the
283
+ translation parameters are in [mm] and the rotation parameters are in [deg].
284
+
285
+ get_actuator_length:
286
+ description: Retrieve the current length of the hexapod actuators in mm.
287
+
288
+
289
+ get_user_positions:
290
+ description: Retrieve the position of the Object Coordinate System in the User Coordinate System.
291
+
292
+ get_machine_positions:
293
+ description: Retrieve the position of the Platform Coordinate System in the Machine Coordinate System.
294
+
295
+ get_actuator_state:
296
+ description: Retrieve general state information of the actuators. For each of the six actuators, an integer
297
+ value is returned that should be interpreted as a bit field containing status bits for that
298
+ actuator.
299
+
300
+ get_limits_value:
301
+ description: Retrieve the current limits that have been set to the hexapod. The argument "lim" determines
302
+ the workspace in which the limits wants to be retrieved.
303
+ args:
304
+ lim: int | 0 = Factory, 1 = machine cs limits, 2 = user cs limits
305
+ cmd: "{lim}"
306
+ returns: list | list of floats with the limits set to the selected workspace (factory, machine, user)
307
+
308
+ get_limits_state:
309
+ description: Return workspace limits enable state
310
+ returns: dict | Limit states of the different work spaces (factory, machine and user)
311
+
312
+ machine_limit_enable:
313
+ description: Enables (1) or disables (0) the machine workspace limits of the hexapod.
314
+
315
+ machine_limit_set:
316
+ description: Sets the machine workspace limits of the hexapod.
317
+
318
+ user_limit_enable:
319
+ description: Enables (1) or disables (0) the user workspace limits of the hexapod.
320
+
321
+ user_limit_set:
322
+ description: Sets the machine workspace limits of the hexapod.
323
+
324
+ set_default:
325
+ description: Restores the default configuration parameters. The command can be used to restore factory
326
+ default parameters. The restored configuration is not automatically saved. refer to the command
327
+ CFG_SAVE to save the parameters in order to keep them after a controller power off. The
328
+ calculation of the hexapod position is suspended during the command execution.
329
+
330
+ configure_coordinates_systems:
331
+ description: Change the definition of the User Coordinate System and the Object Coordinate System.
332
+ The parameters tx_u, ty_u, tz_u, rx_u, ry_u, rz_u are used to define the user coordinate system
333
+ relative to the Machine Coordinate System and the parameters tx_o, ty_o, tz_o, rx_o, ry_o, rz_o
334
+ are used to define the Object Coordinate System relative to the Platform Coordinate System.
335
+
336
+ No motion task shall be running when executing this parameter.
337
+ cmd: "{tx_u} {ty_u} {tz_u} {rx_u} {ry_u} {rz_u} {tx_o} {ty_o} {tz_o} {rx_o} {ry_o} {rz_o}"
@@ -0,0 +1,239 @@
1
+ """
2
+ The Control Server that connects to the Hexapod ZONDA Hardware Controller.
3
+
4
+ Start the control server from the terminal as follows:
5
+
6
+ $ zonda_cs start-bg
7
+
8
+ or when you don't have the device available, start the control server in simulator mode. That
9
+ will make the control server connect to a device software simulator:
10
+
11
+ $ zonda_cs start --sim
12
+
13
+ Please note that software simulators are intended for simple test purposes and will not simulate
14
+ all device behavior correctly, e.g. timing, error conditions, etc.
15
+
16
+ """
17
+
18
+ import multiprocessing
19
+ import sys
20
+ from typing import Annotated
21
+
22
+ import rich
23
+ import typer
24
+ import zmq
25
+ from prometheus_client import start_http_server
26
+
27
+ from egse.control import ControlServer
28
+ from egse.control import is_control_server_active
29
+ from egse.hexapod.symetrie import ProxyFactory
30
+ from egse.hexapod.symetrie import get_hexapod_controller_pars
31
+ from egse.hexapod.symetrie import logger
32
+ from egse.hexapod.symetrie.zonda_protocol import ZondaProtocol
33
+ from egse.registry.client import RegistryClient
34
+ from egse.services import ServiceProxy
35
+ from egse.settings import Settings
36
+ from egse.storage import store_housekeeping_information
37
+ from egse.zmq_ser import connect_address
38
+
39
+ CTRL_SETTINGS = Settings.load("Hexapod Control Server")
40
+
41
+
42
+ class ZondaControlServer(ControlServer):
43
+ """ZondaControlServer - Command and monitor the Hexapod ZONDA hardware.
44
+
45
+ This class works as a command and monitoring server to control the Symétrie Hexapod PUNA.
46
+ This control server shall be used as the single point access for controlling the hardware
47
+ device. Monitoring access should be done preferably through this control server also,
48
+ but can be done with a direct connection through the PunaController if needed.
49
+
50
+ The sever binds to the following ZeroMQ sockets:
51
+
52
+ * a REQ-REP socket that can be used as a command server. Any client can connect and
53
+ send a command to the Hexapod.
54
+
55
+ * a PUB-SUP socket that serves as a monitoring server. It will send out Hexapod status
56
+ information to all the connected clients every five seconds.
57
+
58
+ """
59
+
60
+ def __init__(self, device_id: str, simulator: bool = False):
61
+ super().__init__()
62
+
63
+ multiprocessing.current_process().name = "zonda_cs"
64
+
65
+ self.logger = logger
66
+
67
+ self.device_id = device_id
68
+ self.device_protocol = ZondaProtocol(self, device_id=device_id, simulator=simulator)
69
+
70
+ self.device_protocol.bind(self.dev_ctrl_cmd_sock)
71
+
72
+ self.poller.register(self.dev_ctrl_cmd_sock, zmq.POLLIN)
73
+
74
+ self.register_service(service_type=f"{device_id}")
75
+
76
+ def get_communication_protocol(self):
77
+ return CTRL_SETTINGS.PROTOCOL
78
+
79
+ def get_commanding_port(self):
80
+ return CTRL_SETTINGS.COMMANDING_PORT
81
+
82
+ def get_service_port(self):
83
+ return CTRL_SETTINGS.SERVICE_PORT
84
+
85
+ def get_monitoring_port(self):
86
+ return CTRL_SETTINGS.MONITORING_PORT
87
+
88
+ def get_storage_mnemonic(self):
89
+ try:
90
+ return CTRL_SETTINGS.STORAGE_MNEMONIC
91
+ except AttributeError:
92
+ return "ZONDA"
93
+
94
+ def is_storage_manager_active(self):
95
+ from egse.storage import is_storage_manager_active
96
+
97
+ return is_storage_manager_active()
98
+
99
+ def store_housekeeping_information(self, data):
100
+ """Send housekeeping information to the Storage manager."""
101
+
102
+ origin = self.get_storage_mnemonic()
103
+ store_housekeeping_information(origin, data)
104
+
105
+ def register_to_storage_manager(self):
106
+ from egse.storage import register_to_storage_manager
107
+ from egse.storage.persistence import TYPES
108
+
109
+ register_to_storage_manager(
110
+ origin=self.get_storage_mnemonic(),
111
+ persistence_class=TYPES["CSV"],
112
+ prep={
113
+ "column_names": list(self.device_protocol.get_housekeeping().keys()),
114
+ "mode": "a",
115
+ },
116
+ )
117
+
118
+ def unregister_from_storage_manager(self):
119
+ from egse.storage import unregister_from_storage_manager
120
+
121
+ unregister_from_storage_manager(origin=self.get_storage_mnemonic())
122
+
123
+ def before_serve(self):
124
+ start_http_server(CTRL_SETTINGS.METRICS_PORT)
125
+
126
+ def after_serve(self) -> None:
127
+ self.deregister_service()
128
+
129
+
130
+ app = typer.Typer()
131
+
132
+
133
+ @app.command()
134
+ def start(
135
+ device_id: Annotated[str, typer.Argument(help="the device identifier, identifies the hardware controller")],
136
+ simulator: Annotated[
137
+ bool, typer.Option("--simulator", "--sim", help="start the hexapod PUNA Control Server in simulator mode")
138
+ ] = False,
139
+ ):
140
+ """Start the Hexapod Zonda Control Server."""
141
+
142
+ try:
143
+ controller = ZondaControlServer(device_id, simulator)
144
+ controller.serve()
145
+
146
+ except KeyboardInterrupt:
147
+ print("Shutdown requested...exiting")
148
+
149
+ except SystemExit as exc:
150
+ exit_code = exc.code if hasattr(exc, "code") else 0
151
+ print(f"System Exit with code {exc.code}")
152
+ sys.exit(exit_code)
153
+
154
+ except Exception:
155
+ logger.exception("Cannot start the Hexapod Zonda Control Server")
156
+
157
+ # The above line does exactly the same as the traceback, but on the logger
158
+ # import traceback
159
+ # traceback.print_exc(file=sys.stdout)
160
+
161
+ return 0
162
+
163
+
164
+ @app.command()
165
+ def stop(device_id: str):
166
+ """Send a 'quit_server' command to the Hexapod Zonda Control Server."""
167
+
168
+ with RegistryClient() as reg:
169
+ service = reg.discover_service(device_id)
170
+ rich.print("service = ", service)
171
+
172
+ if service:
173
+ proxy = ServiceProxy(protocol="tcp", hostname=service["host"], port=service["metadata"]["service_port"])
174
+ proxy.quit_server()
175
+ else:
176
+ *_, device_type, controller_type = get_hexapod_controller_pars(device_id)
177
+
178
+ factory = ProxyFactory()
179
+ try:
180
+ with factory.create(device_type, device_id=device_id) as proxy:
181
+ sp = proxy.get_service_proxy()
182
+ sp.quit_server()
183
+ except ConnectionError:
184
+ rich.print("[red]Couldn't connect to 'zonda_cs', process probably not running. ")
185
+
186
+
187
+ @app.command()
188
+ def status(device_id: str):
189
+ """Request status information from the Control Server."""
190
+
191
+ *_, device_type, controller_type = get_hexapod_controller_pars(device_id)
192
+
193
+ with RegistryClient() as reg:
194
+ service = reg.discover_service(device_id)
195
+ # rich.print("service = ", service)
196
+
197
+ if service:
198
+ protocol = service.get("protocol", "tcp")
199
+ hostname = service["host"]
200
+ port = service["port"]
201
+ service_port = service["metadata"]["service_port"]
202
+ monitoring_port = service["metadata"]["monitoring_port"]
203
+ endpoint = connect_address(protocol, hostname, port)
204
+ # rich.print(f"{endpoint = }")
205
+ else:
206
+ rich.print(
207
+ f"[red]The ZONDA CS '{device_id}' isn't registered as a service. I cannot contact the control "
208
+ f"server without the required info from the service registry.[/]"
209
+ )
210
+ rich.print("ZONDA Hexapod: [red]inactive")
211
+ return
212
+
213
+ factory = ProxyFactory()
214
+
215
+ if is_control_server_active(endpoint):
216
+ rich.print("ZONDA Hexapod: [green]active")
217
+ with factory.create(device_type, device_id=device_id, protocol=protocol, hostname=hostname, port=port) as zonda:
218
+ sim = zonda.is_simulator()
219
+ connected = zonda.is_connected()
220
+ ip = zonda.get_ip_address()
221
+ rich.print(f"type: {controller_type}")
222
+ rich.print(f"mode: {'simulator' if sim else 'device'}{'' if connected else ' not'} connected")
223
+ rich.print(f"hostname: {ip}")
224
+ rich.print(f"commanding port: {port}")
225
+ rich.print(f"service port: {service_port}")
226
+ rich.print(f"monitoring port: {monitoring_port}")
227
+
228
+ else:
229
+ rich.print("ZONDA Hexapod: [red]inactive")
230
+
231
+
232
+ if __name__ == "__main__":
233
+ import logging
234
+
235
+ from egse.logger import set_all_logger_levels
236
+
237
+ set_all_logger_levels(logging.DEBUG)
238
+
239
+ sys.exit(app())