ariel-tcu 0.17.2__py3-none-any.whl → 0.17.4__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,693 @@
1
+ import inspect
2
+ import multiprocessing
3
+ import threading
4
+ import types
5
+ from pathlib import Path
6
+ from typing import Union, Any, Callable
7
+
8
+ import sys
9
+ import typer
10
+ from PyQt5.QtCore import QLockFile
11
+ from PyQt5.QtWidgets import (
12
+ QApplication,
13
+ QMainWindow,
14
+ QTabWidget,
15
+ QWidget,
16
+ QVBoxLayout,
17
+ QComboBox,
18
+ QLineEdit,
19
+ QHBoxLayout,
20
+ QLabel,
21
+ QPushButton,
22
+ QMessageBox,
23
+ )
24
+
25
+ from egse.ariel.tcu import (
26
+ TcuMode,
27
+ NUM_M2MD_AXES,
28
+ NUM_M2MD_POSITIONS,
29
+ NUM_TSM_PROBES_PER_FRAME,
30
+ NUM_TSM_FRAMES,
31
+ AXIS_VELOCITY,
32
+ )
33
+ from egse.ariel.tcu.tcu import TcuInterface, TcuHex, TcuProxy
34
+ from egse.ariel.tcu.tcu_cs import is_tcu_cs_active
35
+ from egse.gui import QHLine
36
+ from egse.log import logging
37
+ from egse.observer import Observable, Observer
38
+ from egse.process import ProcessStatus
39
+ from egse.resource import get_resource
40
+ from egse.response import Failure
41
+ from egse.settings import Settings
42
+ from egse.system import do_every
43
+
44
+ MODULE_LOGGER = logging.getLogger(__name__)
45
+ TCU_COMPONENTS = ["GENERAL", "M2MD", "TSM", "HK"]
46
+ CTRL_SETTINGS = Settings.load("Ariel TCU Controller")
47
+
48
+ app = typer.Typer()
49
+
50
+
51
+ class TcuUIModel:
52
+ """Model in the MVC pattern that makes the TCU UI application."""
53
+
54
+ def __init__(self):
55
+ """Initialisation of the TCU UI Model."""
56
+
57
+ # This is used to generate the hex string that is sent to the Arduino, so it can be shown in the TCU UI
58
+ # (Might be discontinued in the future)
59
+
60
+ self.tcu_hex = TcuHex()
61
+
62
+ # This is used to establish a connection to the TCU and actually send commands to it
63
+
64
+ try:
65
+ self.tcu_proxy: TcuProxy[TcuHex, None] = TcuProxy()
66
+ except RuntimeError:
67
+ MODULE_LOGGER.error("Could not connect to Ariel TCU Control Server")
68
+ self.tcu_proxy: Union[TcuProxy, None] = None
69
+
70
+ @staticmethod
71
+ def build_dyn_cmds_list() -> dict:
72
+ """Keep track of which dynamic commands have been defined.
73
+
74
+ Per component of the TCU, we store a dictionary of the dynamic commands that have been defined for that
75
+ component. The keys in these dictionaries are the names of the dynamic commands, and the values are the
76
+ functions that implement them.
77
+
78
+ Returns:
79
+ Dictionary that stores - per component of the TCU - a dictionary of the dynamic commands that have been
80
+ defined for that component.
81
+ """
82
+
83
+ dyn_cmds = {}
84
+ for component in TCU_COMPONENTS:
85
+ dyn_cmds[component] = {}
86
+
87
+ for cmd_name, func in inspect.getmembers(TcuInterface, inspect.isfunction):
88
+ if hasattr(func, "target_comp"):
89
+ target_comp = getattr(func, "target_comp")
90
+ dyn_cmds[target_comp][cmd_name] = func
91
+
92
+ return dyn_cmds
93
+
94
+
95
+ class TcuUiView(QMainWindow, Observable):
96
+ """View in the MVC pattern that makes the TCU UI application."""
97
+
98
+ def __init__(self):
99
+ """Initialisation of the TCU UI View."""
100
+
101
+ super(TcuUiView, self).__init__()
102
+ Observable.__init__(self)
103
+
104
+ self.setGeometry(300, 300, 500, 300)
105
+ self.setWindowTitle("Telescope Control Unit")
106
+
107
+ # Central widget = tabs per component
108
+
109
+ self.tabs = QTabWidget()
110
+ self.setCentralWidget(self.tabs)
111
+
112
+ def build_tabs(self, dyn_cmds: dict, observer):
113
+ """Build the tabs of the TCU UI view.
114
+
115
+ For each component of the TCU commanding, a tab is created. In this tab, you can find:
116
+
117
+ - Drop-down menu with the list of available dynamic commands for the component,
118
+ - If applicable: entry for cargo1,
119
+ - If applicable: entry for cargo2,
120
+ - "Run" button,
121
+ - Command string (read-only),
122
+ - Response (read-only).
123
+
124
+ Args:
125
+ dyn_cmds (dict): Dictionary that stores - per component of the TCU - a dictionary of the dynamic commands
126
+ that have been defined for that component.
127
+ observer: Observer that will be notified when a command is run (i.e. when the "Run" button is clicked).
128
+ """
129
+
130
+ for tab_name, dyn_cmd_for_tab in dyn_cmds.items():
131
+ tab = TabWidget(dyn_cmd_for_tab)
132
+ tab.add_observer(observer)
133
+
134
+ self.tabs.addTab(tab, tab_name)
135
+
136
+ # When all tabs have been populated, select the first dynamic command in the drop-down menu in the first tab,
137
+ # to make sure that the correct input parameters are requested (if applicable)
138
+
139
+ self.tabs.currentChanged.connect(self.on_tab_changed)
140
+ self.on_tab_changed(0)
141
+
142
+ def on_tab_changed(self, _):
143
+ """Takes action when another tab is selected.
144
+
145
+ When a new tab has been selected, select the current dynamic command in the drop-down menu in the new tab,
146
+ to make sure that the correct input parameters are requested (if applicable).
147
+ """
148
+
149
+ self.tabs.currentWidget().dyn_cmd_drop_down_menu_changed(
150
+ self.tabs.currentWidget().dyn_cmd_drop_down_menu.currentText()
151
+ )
152
+
153
+
154
+ class TabWidget(QWidget, Observable):
155
+ """Tab in the TCU UI View."""
156
+
157
+ def __init__(self, dyn_cmds: dict):
158
+ """Initialisation of a tab in the TCU UI View.
159
+
160
+ In this tab, you can find:
161
+
162
+ - Drop-down menu with the list of available dynamic commands for the component,
163
+ - If applicable: entry for cargo1,
164
+ - If applicable: entry for cargo2,
165
+ - "Run" button,
166
+ - Command string (read-only),
167
+ - Response (read-only).
168
+
169
+ Args:
170
+ dyn_cmds (dict): Dictionary that stores for one TCU component a dictionary of the dynamic commands
171
+ that have been defined for that component.
172
+ """
173
+
174
+ super().__init__()
175
+ layout = QVBoxLayout(self)
176
+
177
+ # Dynamic commands that are available for this component
178
+ # (i.e. the dictionary of dynamic commands that have been defined for this component)
179
+
180
+ self.dyn_cmds = dyn_cmds
181
+
182
+ # Entries for cargo1 and/or cargo2 (if applicable)
183
+ # We have to define these before we create the drop-down menu with the dynamic commands
184
+
185
+ self.cargo1_layout = CargoLayout(1)
186
+ self.cargo2_layout = CargoLayout(2)
187
+
188
+ # Drop-down menu with the list of dynamic commands that are available for this component
189
+
190
+ self.dyn_cmd_drop_down_menu = QComboBox()
191
+
192
+ for cmd_name in self.dyn_cmds:
193
+ self.dyn_cmd_drop_down_menu.addItem(cmd_name)
194
+ self.dyn_cmd_drop_down_menu.currentTextChanged.connect(self.dyn_cmd_drop_down_menu_changed)
195
+
196
+ # Button to send the command to the Arduino
197
+
198
+ button = QPushButton("Run")
199
+ button.clicked.connect(self.button_clicked)
200
+
201
+ # Display the hex string tha is sent to the Arduino
202
+
203
+ cmd_string_layout = QHBoxLayout()
204
+ self.cmd_string = QLineEdit()
205
+ self.cmd_string.setReadOnly(True)
206
+ cmd_string_layout.addWidget(QLabel("Command string"))
207
+ cmd_string_layout.addWidget(self.cmd_string)
208
+
209
+ # Display the response that is received from the Arduino
210
+ # (after the command has been sent by clicking the "Run" button)
211
+
212
+ response_layout = QHBoxLayout()
213
+ self.response = QLineEdit()
214
+ self.response.setReadOnly(True)
215
+ response_layout.addWidget(QLabel("Response"))
216
+ response_layout.addWidget(self.response)
217
+
218
+ # Assembly of all components in the tab
219
+
220
+ layout.addWidget(self.dyn_cmd_drop_down_menu) # Drop-down menu with available dynamic commands
221
+ layout.addLayout(self.cargo1_layout) # Cargo1
222
+ layout.addLayout(self.cargo2_layout) # Cargo2
223
+ layout.addWidget(button) # "Run" button
224
+ layout.addWidget(QHLine()) # Horizontal separator
225
+ layout.addLayout(cmd_string_layout) # Hex string that is sent to the Arduino
226
+ layout.addWidget(QHLine()) # Horizontal separator
227
+ layout.addLayout(response_layout) # Response that is received from the Arduino
228
+
229
+ def dyn_cmd_drop_down_menu_changed(self, dyn_cmd_name):
230
+ """Takes action when another dynamic command has been selected in the drop-down menu.
231
+
232
+ Args:
233
+ dyn_cmd_name (str): Name of the dynamic command that has been selected in the drop-down menu.
234
+ """
235
+
236
+ # Extract the signature of the selected function (1st item is always `self`)
237
+
238
+ signature = inspect.signature(self.dyn_cmds[dyn_cmd_name])
239
+ parameters = signature.parameters
240
+
241
+ # `self` + cargo1 + cargo2 (the latter two may have a different name -> extract from signature)
242
+
243
+ if len(parameters) == 3:
244
+ self.cargo1_layout.set_visible(True)
245
+ self.cargo1_layout.update_entry(list(parameters.keys())[-2])
246
+ self.cargo2_layout.set_visible(True)
247
+ self.cargo2_layout.update_entry(list(parameters.keys())[-1])
248
+
249
+ # `self` + cargo2 (the latter may have a different name -> extract from signature)
250
+
251
+ elif len(parameters) == 2:
252
+ self.cargo1_layout.set_visible(False)
253
+ self.cargo2_layout.set_visible(True)
254
+ self.cargo2_layout.update_entry(list(parameters.keys())[-1])
255
+
256
+ # `self` (no cargo)
257
+
258
+ else:
259
+ self.cargo1_layout.set_visible(False)
260
+ self.cargo2_layout.set_visible(False)
261
+
262
+ self.repaint()
263
+
264
+ def button_clicked(self):
265
+ """Takes action when the "Run" button is clicked.
266
+
267
+ When the "Run" button is clicked, the observer (i.c. TCU UI Controller) is notified. It will receive a tuple
268
+ with the following information:
269
+
270
+ - The tab widget in which the "Run" button has been clicked (needed to be able to fill out the response),
271
+ - The dynamic command that has been selected in the drop-down menu (function),
272
+ - The cargo1 value (if applicable),
273
+ - The cargo2 value (if applicable).
274
+
275
+ The TCU UI Controller will make sure that the hex string that is sent to the Arduino and the response that is
276
+ received from the Arduino are displayed in the UI. It will receive this information from the TCU UI Model.
277
+ """
278
+
279
+ self.notify_observers(
280
+ (
281
+ self, # Tab widget in which the "Run" button has been clicked
282
+ self.dyn_cmds[self.dyn_cmd_drop_down_menu.currentText()], # Selected dynamic command (as a function)
283
+ self.cargo1_layout.entry.text(), # Cargo1 entry
284
+ self.cargo2_layout.entry.text(), # Cargo2 entry
285
+ )
286
+ )
287
+
288
+
289
+ class CargoLayout(QHBoxLayout):
290
+ """Layout for the input argument(s) of the dynamic commands."""
291
+
292
+ def __init__(self, cargo: int):
293
+ """Initialisation of a layout for the given cargo argument (1 or 2).
294
+
295
+ Args:
296
+ cargo (int): Number of the cargo argument (1 or 2).
297
+ """
298
+
299
+ super().__init__()
300
+
301
+ # Label for the argument ("cargo1" / "cargo2", depending on the input, as default)
302
+
303
+ self.label = QLabel(f"cargo{cargo}")
304
+
305
+ # By default, the entry is a QLineEdit
306
+
307
+ self.entry = QLineEdit()
308
+
309
+ self.addWidget(self.label)
310
+ self.addWidget(self.entry)
311
+
312
+ def update_entry(self, cargo_name: str):
313
+ """Update the entry for the input argument with the given name.
314
+
315
+ Updating the entry entails:
316
+
317
+ - Updating the label with the name of the input argument,
318
+ - Replacing the entry with the appropriate widget (this can be a line edit, a combo box, etc.).
319
+
320
+ Args:
321
+ cargo_name (str): Name of the input argument.
322
+ """
323
+
324
+ # Update the name of the input argument
325
+
326
+ self.label.setText(cargo_name)
327
+
328
+ # The `axis` argument is only used for M2MD commands, to denote the axis that should be commanded
329
+ # -> Use a drop-down menu instead of the default QLineEdit
330
+
331
+ if cargo_name == "axis":
332
+ self.replace_entry_with_axis()
333
+
334
+ # The `tcu_mode` argument is only used to change the TCU mode
335
+ # -> Use a drop-down menu instead of the default QLineEdit
336
+
337
+ elif cargo_name == "tcu_mode":
338
+ self.replace_with_tcu_mode()
339
+
340
+ # The `position` argument is only used for `sw_rs_xx_sw_rise` and `sw_rs_xx_sw_fall`, to denote the position
341
+ # that should be commanded
342
+ # -> Use a drop-down menu instead of the default QLineEdit (read the number of positions from the settings)
343
+
344
+ elif cargo_name == "position":
345
+ self.replace_with_int_combo_box(1, NUM_M2MD_POSITIONS)
346
+
347
+ # The `probe` argument is only used for `tsm_adc_value_xx_currentn`, `tsm_adc_value_xx_biasn`,
348
+ # `tsm_adc_value_xx_currentp`, and `tsm_adc_value_xx_biasp`
349
+ # -> Use a drop-down menu instead of the default QLineEdit (read the number of probes from the settings)
350
+
351
+ elif cargo_name == "probe":
352
+ self.replace_with_int_combo_box(1, NUM_TSM_FRAMES * NUM_TSM_PROBES_PER_FRAME)
353
+
354
+ elif cargo_name == "speed":
355
+ self.replace_with_axis_speed_combo_box()
356
+
357
+ # By default, the entry is a QLineEdit
358
+
359
+ else:
360
+ self.replace_with_line_edit()
361
+
362
+ def replace_with_line_edit(self):
363
+ """Replaces the entry with a QLineEdit (if it's not a QLineEdit already)."""
364
+
365
+ if not isinstance(self.entry, QLineEdit):
366
+ # Remove the current entry
367
+
368
+ self.removeWidget(self.entry)
369
+ self.entry.hide()
370
+ self.entry.deleteLater()
371
+
372
+ # Replace it with a QLineEdit
373
+
374
+ self.entry = QLineEdit()
375
+ self.insertWidget(1, self.entry)
376
+
377
+ def replace_entry_with_axis(self):
378
+ """Replaces the entry with an AxisComboBox (if it's not an AxisComboBox already)."""
379
+
380
+ if not isinstance(self.entry, AxisComboBox):
381
+ # Remove the current entry
382
+
383
+ self.removeWidget(self.entry)
384
+ self.entry.hide()
385
+ self.entry.deleteLater()
386
+
387
+ # Replace it with an AxisComboBox
388
+
389
+ self.entry = AxisComboBox()
390
+ self.insertWidget(1, self.entry)
391
+
392
+ def replace_with_tcu_mode(self):
393
+ """Replaces the entry with a TcuModeComboBox (if it's not a TcuModeComboBox already)."""
394
+
395
+ if not isinstance(self.entry, TcuModeComboBox):
396
+ # Remove the current entry
397
+
398
+ self.removeWidget(self.entry)
399
+ self.entry.hide()
400
+ self.entry.deleteLater()
401
+
402
+ # Replace it with a TcuModeComboBox
403
+
404
+ self.entry = TcuModeComboBox()
405
+ self.insertWidget(1, self.entry)
406
+
407
+ def replace_with_int_combo_box(self, min_value: int, max_value: int):
408
+ """Replaces the entry with an IntComboBox (if it's not an IntComboBox already).
409
+
410
+ Args:
411
+ min_value (int): Minimum value of the IntComboBox.
412
+ max_value (int): Maximum value of the IntComboBox.
413
+ """
414
+
415
+ # Remove the current entry
416
+
417
+ self.removeWidget(self.entry)
418
+ self.entry.hide()
419
+ self.entry.deleteLater()
420
+
421
+ # Replace it with an IntComboBox
422
+
423
+ self.entry = IntComboBox(min_value, max_value)
424
+ self.insertWidget(1, self.entry)
425
+
426
+ def replace_with_axis_speed_combo_box(self):
427
+ """Replaces the entry with an AxisSpeedComboBox (if it's not an AxisSpeedComboBox already)."""
428
+
429
+ if not isinstance(self.entry, AxisSpeedComboBox):
430
+ # Remove the current entry
431
+
432
+ self.removeWidget(self.entry)
433
+ self.entry.hide()
434
+ self.entry.deleteLater()
435
+
436
+ # Replace it with an AxisSpeedComboBox
437
+
438
+ self.entry = AxisSpeedComboBox()
439
+ self.insertWidget(1, self.entry)
440
+
441
+ def set_visible(self, visible: bool):
442
+ """Changes the visibility of the label and the entry.
443
+
444
+ Args:
445
+ visible (bool): Indicates whether the label and the entry should be visible or not.
446
+ """
447
+
448
+ self.label.setVisible(visible)
449
+ self.entry.setVisible(visible)
450
+
451
+
452
+ class AxisComboBox(QComboBox):
453
+ def __init__(self):
454
+ """Initialisation of a drop-down menu for the M2MD axes."""
455
+
456
+ super().__init__()
457
+
458
+ for axis in range(1, NUM_M2MD_AXES + 1):
459
+ self.addItem(f"M2MD axis {axis}")
460
+ self.setEditable(False)
461
+
462
+ def text(self):
463
+ """Converts the selected text to a valid input argument for the dynamic command."""
464
+
465
+ return self.currentText()[-1]
466
+
467
+
468
+ class TcuModeComboBox(QComboBox):
469
+ def __init__(self):
470
+ """Initialisation of a drop-down menu for the TCU mode."""
471
+
472
+ super().__init__()
473
+
474
+ for tcu_mode in TcuMode:
475
+ self.addItem(tcu_mode.name)
476
+ self.setEditable(False)
477
+
478
+ def text(self):
479
+ """Converts the selected text to a valid input argument for the dynamic command."""
480
+
481
+ return TcuMode[self.currentText()].value
482
+
483
+
484
+ class IntComboBox(QComboBox):
485
+ def __init__(self, min_value: int, max_value: int):
486
+ """Initialisation of a drop-down menu for a range of integers.
487
+
488
+ Args:
489
+ min_value (int): Minimum value in the drop-down menu.
490
+ max_value (int): Maximum value in the drop-down menu.
491
+ """
492
+
493
+ super().__init__()
494
+
495
+ self.addItems([str(x) for x in range(min_value, max_value + 1)])
496
+ self.setEditable(False)
497
+
498
+ def text(self):
499
+ """Converts the selected text to a valid input argument for the dynamic command."""
500
+
501
+ return int(self.currentText())
502
+
503
+
504
+ class AxisSpeedComboBox(QComboBox):
505
+ def __init__(self):
506
+ """Initialisation of a drop-down menu for the axis speed."""
507
+
508
+ super().__init__()
509
+
510
+ for speed in AXIS_VELOCITY:
511
+ self.addItem(f"{speed}Hz")
512
+
513
+ self.setEditable(False)
514
+
515
+ def text(self):
516
+ """Converts the selected text to a valid input argument for the dynamic command.
517
+
518
+ The selected text is of the form "XHz", where X is the axis speed in Hz. This has to be converted into a hex
519
+ string that represents this axis speed.
520
+
521
+ Returns:
522
+ Axis speed in hex string format.
523
+ """
524
+
525
+ return AXIS_VELOCITY[int(self.currentText()[:-2])]
526
+
527
+
528
+ class TcuUiController(Observer):
529
+ """Controller in the MVC pattern that makes the TCU UI application."""
530
+
531
+ def __init__(self, model: TcuUIModel, view: TcuUiView):
532
+ """Initialisation of the TCU UI Controller."""
533
+
534
+ super().__init__()
535
+
536
+ self.model = model
537
+ self.view = view
538
+
539
+ self.view.build_tabs(self.model.build_dyn_cmds_list(), self)
540
+
541
+ def do(self, actions):
542
+ # Abstract method from the Observer class, which we do not need here
543
+ pass
544
+
545
+ def update(self, changed_object):
546
+ """Updates the TCU UI when the "Run" button is clicked in one of the tabs.
547
+
548
+ In this context, updating means:
549
+
550
+ - Generating the command string that will be sent to the Arduino and display it in the TCU UI View,
551
+ - Sending the command to the Arduino and display the response in the TCU UI View.
552
+ """
553
+
554
+ origin_tab, func, cargo1, cargo2 = changed_object
555
+
556
+ signature = inspect.signature(func)
557
+ args = ()
558
+ kwargs = {}
559
+
560
+ parameters = signature.parameters
561
+
562
+ if len(parameters) == 3:
563
+ cargo1_name = list(parameters.keys())[-2]
564
+ kwargs[cargo1_name] = cargo1
565
+
566
+ if len(parameters) >= 2:
567
+ cargo2_name = list(parameters.keys())[-1]
568
+ kwargs[cargo2_name] = cargo2
569
+
570
+ # Generate the command string + display
571
+
572
+ cmd_string = call_unbound(func, self.model.tcu_hex, *args, **kwargs)
573
+ origin_tab.cmd_string.setText(cmd_string.decode())
574
+
575
+ # Execute the command + display the response
576
+
577
+ if self.model.tcu_proxy:
578
+ response = call_unbound(func, self.model.tcu_proxy, *args, **kwargs)
579
+
580
+ if isinstance(response, Failure):
581
+ origin_tab.response.setStyleSheet("background-color: red;")
582
+ origin_tab.response.setText(str(response.cause))
583
+
584
+ else:
585
+ if hasattr(response, "decode") and callable(getattr(response, "decode")):
586
+ response = response.decode()
587
+
588
+ origin_tab.response.setText(str(response))
589
+
590
+ elif is_tcu_cs_active():
591
+ self.model.tcu_proxy = TcuProxy()
592
+
593
+
594
+ def call_unbound(func: Callable, instance: object, *args: Any, **kwargs: Any) -> Any:
595
+ """Executes the given function on the given instance with the given arguments.
596
+
597
+ This helper handles three cases:
598
+
599
+ 1. `func` is already a bound method (has `__self__`) -> Call it directly.
600
+ 2. Prefer looking up the attribute on `instance` with `getattr(instance, func.__name__)`.
601
+ This lets Python perform normal Method Resolution Order (MRO) so overrides on the instance's class or proxy
602
+ wrappers are returned as bound methods.
603
+ 3. If look-up fails (no such attribute on the instance), fall back to binding the original function to `instance`
604
+ using `types.MethodType` (this may call the base/class implementation).
605
+
606
+ Args:
607
+ func (Callable): Function (dynamic command) to execute.
608
+ instance (object): Instance on which to execute the function.
609
+ *args: Optional positional arguments for the function.
610
+ **kwargs: Optional keyword arguments for the function.
611
+
612
+ Returns:
613
+ Any: Result of the function execution.
614
+ """
615
+
616
+ # Case 1: already bound -> call directly
617
+
618
+ if getattr(func, "__self__", None) is not None:
619
+ return func(*args, **kwargs)
620
+
621
+ # Case 2: preferred lookup on the instance so Python performs normal MRO (Method Resolution Order) and returns
622
+ # the appropriate bound method (honours overrides / proxy behaviour).
623
+
624
+ try:
625
+ bound = getattr(instance, func.__name__)
626
+ return bound(*args, **kwargs)
627
+ except AttributeError:
628
+ # Case 3: Fall-back to binding the original function to the instance. This will create a bound method that
629
+ # directly calls the function as if it were defined on the instance.
630
+
631
+ bound = types.MethodType(func, instance)
632
+ return bound(*args, **kwargs)
633
+
634
+
635
+ # def call_method_hex(instance: TcuHex, func: Callable, *args, **kwargs) -> Any:
636
+ # """Executes the given dynamic command for the given TCU interface.
637
+ #
638
+ # Args:
639
+ # instance (TcuHex): TCU interface for which to execute the given dynamic command.
640
+ # func (Callable): Dynamic command to execute.
641
+ # *args: Optional positional arguments for the dynamic command.
642
+ # **kwargs: Optional keyword arguments for the dynamic command.
643
+ #
644
+ # Returns:
645
+ # Response received from the TCU interface.
646
+ # """
647
+ #
648
+ # wrapper = instance.handle_dynamic_command(func)
649
+ # result = wrapper(*args, **kwargs)
650
+ #
651
+ # return result
652
+
653
+
654
+ @app.command()
655
+ def main():
656
+ multiprocessing.current_process().name = "tcu_ui"
657
+ lock_file = QLockFile(str(Path("~/tcu_ui.app.lock").expanduser()))
658
+
659
+ tcu_app = QApplication(["-stylesheet", str(get_resource(":/styles/default.qss"))])
660
+ # app_logo = get_resource(":/icons/logo-tcu.svg")
661
+ # app.setWindowIcon(QIcon(str(app_logo)))
662
+
663
+ if lock_file.tryLock(100):
664
+ process_status = ProcessStatus()
665
+
666
+ timer_thread = threading.Thread(target=do_every, args=(10, process_status.update))
667
+ timer_thread.daemon = True
668
+ timer_thread.start()
669
+
670
+ # Create the TCU UI, following the MVC-model
671
+
672
+ model = TcuUIModel()
673
+ view = TcuUiView()
674
+ controller = TcuUiController(model, view)
675
+ view.add_observer(controller)
676
+
677
+ view.show()
678
+
679
+ return tcu_app.exec_()
680
+ else:
681
+ error_message = QMessageBox()
682
+ error_message.setIcon(QMessageBox.Warning)
683
+ error_message.setWindowTitle("Error")
684
+ error_message.setText("The TCU GUI application is already running!")
685
+ error_message.setStandardButtons(QMessageBox.Ok)
686
+
687
+ return error_message.exec()
688
+
689
+
690
+ if __name__ == "__main__":
691
+ logging.basicConfig(level=logging.DEBUG, format=Settings.LOG_FORMAT_FULL)
692
+
693
+ sys.exit(app())
@@ -1,15 +0,0 @@
1
- ariel_tcu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ariel_tcu/cgse_explore.py,sha256=vyplUdKvizZeuaChU-Y-sje1Pwz72gFH_fUptp_-8Iw,283
3
- ariel_tcu/cgse_services.py,sha256=c04Xd75QyZ_rfRnAZgPq-HChjAg7HJJISEcHOWCeycI,1543
4
- ariel_tcu/settings.yaml,sha256=Czc58_bK3sEozVkTnAPSXGiE6v-9meS2ay9tBCMkAMI,1000
5
- egse/ariel/tcu/__init__.py,sha256=S8TZxQ39g0-PEQsUAyQ3fovHEGlsJpbf9Kd1N2Hr2FA,3127
6
- egse/ariel/tcu/tcu.py,sha256=etu-iTW4DBYpdzuDv75x_o4vp-ODvLGKKT1U5awMENE,35147
7
- egse/ariel/tcu/tcu.yaml,sha256=hzhsa6XkWB0fX8R-2eryxcfBtP3r2spLFv6cu_tY-V4,1287
8
- egse/ariel/tcu/tcu_cmd_utils.py,sha256=xp2znLbC20sYA4T1dP0mG2MNI9QC7r37U8Wo5HxnXZk,46061
9
- egse/ariel/tcu/tcu_cs.py,sha256=KwjeADMQavLF3zDr4TnGEcgfQjBPqaKmLkxiUvQurBo,7901
10
- egse/ariel/tcu/tcu_devif.py,sha256=5A60IKyh9ej6IwosjPFxWAE2lCDZjp5tqfIIutN9F6c,3088
11
- egse/ariel/tcu/tcu_protocol.py,sha256=ie_ivSkG5Tx0lP2AKvqlN1JyemOA5g-_tVpk7m-45Ho,5394
12
- ariel_tcu-0.17.2.dist-info/METADATA,sha256=pkPpfeRNz5X7N0iFOX6oTAxYowtMA36Zj26VkZS_7YU,467
13
- ariel_tcu-0.17.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
- ariel_tcu-0.17.2.dist-info/entry_points.txt,sha256=eijgIMSb0EWjBjIRxhPJd2ltwoOo3qTbyDySEccIjQ4,283
15
- ariel_tcu-0.17.2.dist-info/RECORD,,