fprime-gds 3.4.3__py3-none-any.whl → 3.4.4a1__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.
- fprime_gds/common/communication/adapters/base.py +30 -58
- fprime_gds/common/communication/adapters/ip.py +23 -5
- fprime_gds/common/communication/adapters/uart.py +20 -7
- fprime_gds/common/communication/checksum.py +1 -3
- fprime_gds/common/communication/framing.py +53 -4
- fprime_gds/common/zmq_transport.py +33 -18
- fprime_gds/executables/apps.py +150 -0
- fprime_gds/executables/cli.py +219 -65
- fprime_gds/executables/comm.py +14 -17
- fprime_gds/executables/run_deployment.py +55 -14
- fprime_gds/executables/utils.py +1 -1
- fprime_gds/plugin/__init__.py +0 -0
- fprime_gds/plugin/definitions.py +71 -0
- fprime_gds/plugin/system.py +225 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/METADATA +1 -1
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/RECORD +21 -17
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/WHEEL +1 -1
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/LICENSE.txt +0 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/NOTICE.txt +0 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/entry_points.txt +0 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a1.dist-info}/top_level.txt +0 -0
fprime_gds/executables/cli.py
CHANGED
@@ -7,6 +7,7 @@ code that they are importing.
|
|
7
7
|
|
8
8
|
@author mstarch
|
9
9
|
"""
|
10
|
+
|
10
11
|
import argparse
|
11
12
|
import datetime
|
12
13
|
import errno
|
@@ -23,12 +24,13 @@ from typing import Any, Dict, List, Tuple
|
|
23
24
|
# Required to set the checksum as a module variable
|
24
25
|
import fprime_gds.common.communication.checksum
|
25
26
|
import fprime_gds.common.logger
|
26
|
-
from fprime_gds.common.communication.adapters.base import BaseAdapter
|
27
27
|
from fprime_gds.common.communication.adapters.ip import check_port
|
28
28
|
from fprime_gds.common.pipeline.standard import StandardPipeline
|
29
29
|
from fprime_gds.common.transport import ThreadedTCPSocketClient
|
30
30
|
from fprime_gds.common.utils.config_manager import ConfigManager
|
31
31
|
from fprime_gds.executables.utils import find_app, find_dict, get_artifacts_root
|
32
|
+
from fprime_gds.plugin.definitions import PluginType
|
33
|
+
from fprime_gds.plugin.system import Plugins
|
32
34
|
|
33
35
|
# Optional import: ZeroMQ. Requires package: pyzmq
|
34
36
|
try:
|
@@ -45,7 +47,6 @@ try:
|
|
45
47
|
except ImportError:
|
46
48
|
SerialAdapter = None
|
47
49
|
|
48
|
-
|
49
50
|
GUIS = ["none", "html"]
|
50
51
|
|
51
52
|
|
@@ -85,11 +86,31 @@ class ParserBase(ABC):
|
|
85
86
|
Return:
|
86
87
|
argparse parser for supplied arguments
|
87
88
|
"""
|
88
|
-
parser = argparse.ArgumentParser(
|
89
|
-
|
90
|
-
|
89
|
+
parser = argparse.ArgumentParser(
|
90
|
+
description=self.description,
|
91
|
+
add_help=True,
|
92
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
93
|
+
)
|
94
|
+
self.fill_parser(parser)
|
91
95
|
return parser
|
92
96
|
|
97
|
+
def fill_parser(self, parser):
|
98
|
+
""" Fill supplied parser with arguments
|
99
|
+
|
100
|
+
Fills the supplied parser with the arguments returned via the `get_arguments` method invocation. This
|
101
|
+
implementation add the arguments directly to the parser.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
parser: parser to fill with arguments
|
105
|
+
|
106
|
+
"""
|
107
|
+
for flags, keywords in self.get_arguments().items():
|
108
|
+
try:
|
109
|
+
parser.add_argument(*flags, **keywords)
|
110
|
+
except argparse.ArgumentError:
|
111
|
+
# flag has already been added, pass
|
112
|
+
pass
|
113
|
+
|
93
114
|
def reproduce_cli_args(self, args_ns):
|
94
115
|
"""Reproduce the list of arguments needed on the command line"""
|
95
116
|
|
@@ -176,6 +197,7 @@ class ParserBase(ABC):
|
|
176
197
|
sys.exit(-1)
|
177
198
|
except Exception as exc:
|
178
199
|
print(f"[ERROR] {exc}", file=sys.stderr)
|
200
|
+
raise
|
179
201
|
sys.exit(-1)
|
180
202
|
return args_ns, parser
|
181
203
|
|
@@ -245,6 +267,162 @@ class DetectionParser(ParserBase):
|
|
245
267
|
return args
|
246
268
|
|
247
269
|
|
270
|
+
class PluginArgumentParser(ParserBase):
|
271
|
+
"""Parser for arguments coming from plugins"""
|
272
|
+
|
273
|
+
DESCRIPTION = "Plugin options"
|
274
|
+
FPRIME_CHOICES = {
|
275
|
+
"framing": "fprime",
|
276
|
+
"communication": "ip",
|
277
|
+
}
|
278
|
+
|
279
|
+
def __init__(self):
|
280
|
+
"""Initialize the plugin information for this parser"""
|
281
|
+
self._plugin_map = {
|
282
|
+
category: Plugins.system().get_plugins(category)
|
283
|
+
for category in Plugins.system().get_categories()
|
284
|
+
}
|
285
|
+
|
286
|
+
@staticmethod
|
287
|
+
def safe_add_argument(parser, *flags, **keywords):
|
288
|
+
""" Add an argument allowing duplicates
|
289
|
+
|
290
|
+
Add arguments to the parser (passes through *flags and **keywords) to the supplied parser. This method traps
|
291
|
+
errors to prevent duplicates.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
parser: parser or argument group to add arguments to
|
295
|
+
*flags: positional arguments passed to `add_argument`
|
296
|
+
**keywords: key word arguments passed to `add_argument`
|
297
|
+
"""
|
298
|
+
try:
|
299
|
+
parser.add_argument(*flags, **keywords)
|
300
|
+
except argparse.ArgumentError:
|
301
|
+
# flag has already been added, pass
|
302
|
+
pass
|
303
|
+
|
304
|
+
def fill_parser(self, parser):
|
305
|
+
""" File supplied parser with grouped arguments
|
306
|
+
|
307
|
+
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
308
|
+
arguments based on the constituent that sources the argument.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
parser: parser to fill
|
312
|
+
"""
|
313
|
+
for category, plugins in self._plugin_map.items():
|
314
|
+
argument_group = parser.add_argument_group(title=f"{category.title()} Plugin Options")
|
315
|
+
for flags, keywords in self.get_category_arguments(category).items():
|
316
|
+
self.safe_add_argument(argument_group, *flags, **keywords)
|
317
|
+
|
318
|
+
for plugin in plugins:
|
319
|
+
argument_group = parser.add_argument_group(title=f"{category.title()} Plugin '{plugin.get_name()}' Options")
|
320
|
+
if plugin.type == PluginType.FEATURE:
|
321
|
+
self.safe_add_argument(argument_group,
|
322
|
+
f"--disable-{plugin.get_name()}",
|
323
|
+
action="store_true",
|
324
|
+
default=False,
|
325
|
+
help=f"Disable the {category} plugin '{plugin.get_name()}'")
|
326
|
+
for flags, keywords in plugin.get_arguments().items():
|
327
|
+
self.safe_add_argument(argument_group, *flags, **keywords)
|
328
|
+
|
329
|
+
def get_category_arguments(self, category):
|
330
|
+
""" Get arguments for a plugin category """
|
331
|
+
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
332
|
+
plugins = self._plugin_map[category]
|
333
|
+
# Add category options: SELECTION plugins add a selection flag
|
334
|
+
plugin_type = Plugins.get_category_plugin_type(category)
|
335
|
+
if plugin_type == PluginType.SELECTION:
|
336
|
+
arguments.update(
|
337
|
+
{
|
338
|
+
(f"--{category}-selection",): {
|
339
|
+
"choices": [choice.get_name() for choice in plugins],
|
340
|
+
"help": f"Select {category} implementer.",
|
341
|
+
"default": self.FPRIME_CHOICES.get(
|
342
|
+
category, list(plugins)[0].get_name()
|
343
|
+
),
|
344
|
+
}
|
345
|
+
}
|
346
|
+
)
|
347
|
+
return arguments
|
348
|
+
|
349
|
+
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
350
|
+
"""Return arguments to used in plugins"""
|
351
|
+
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
352
|
+
for category, plugins in self._plugin_map.items():
|
353
|
+
arguments.update(self.get_category_arguments(category))
|
354
|
+
for plugin in plugins:
|
355
|
+
# Add disable flags for feature type plugins
|
356
|
+
if plugin.type == PluginType.FEATURE:
|
357
|
+
arguments.update({
|
358
|
+
(f"--disable-{plugin.get_name()}", ): {
|
359
|
+
"action": "store_true",
|
360
|
+
"default": False,
|
361
|
+
"help": f"Disable the {category} plugin '{plugin.get_name()}'"
|
362
|
+
}
|
363
|
+
})
|
364
|
+
arguments.update(plugin.get_arguments())
|
365
|
+
return arguments
|
366
|
+
|
367
|
+
def handle_arguments(self, args, **kwargs):
|
368
|
+
"""Handles the arguments"""
|
369
|
+
for category, plugins in self._plugin_map.items():
|
370
|
+
plugin_type = Plugins.get_category_plugin_type(category)
|
371
|
+
|
372
|
+
# Selection plugins choose one plugin and instantiate it
|
373
|
+
if plugin_type == PluginType.SELECTION:
|
374
|
+
selection_string = getattr(args, f"{category}_selection")
|
375
|
+
matching_plugins = [plugin for plugin in plugins if plugin.get_name() == selection_string]
|
376
|
+
assert len(matching_plugins) == 1, "Plugin selection system failed"
|
377
|
+
selection_class = matching_plugins[0].plugin_class
|
378
|
+
filled_arguments = self.extract_plugin_arguments(args, selection_class)
|
379
|
+
selection_instance = selection_class(**filled_arguments)
|
380
|
+
setattr(args, f"{category}_selection_instance", selection_instance)
|
381
|
+
# Feature plugins instantiate all enabled plugins
|
382
|
+
elif plugin_type == PluginType.FEATURE:
|
383
|
+
enabled_plugins = [
|
384
|
+
plugin for plugin in plugins
|
385
|
+
if not getattr(args, f"disable_{plugin.get_name().replace('-', '_')}", False)
|
386
|
+
]
|
387
|
+
plugin_instantiations = [
|
388
|
+
plugin.plugin_class(**self.extract_plugin_arguments(args, plugin))
|
389
|
+
for plugin in enabled_plugins
|
390
|
+
]
|
391
|
+
setattr(args, f"{category}_enabled_instances", plugin_instantiations)
|
392
|
+
return args
|
393
|
+
|
394
|
+
@staticmethod
|
395
|
+
def extract_plugin_arguments(args, plugin) -> Dict[str, Any]:
|
396
|
+
"""Extract plugin argument values from the args namespace into a map
|
397
|
+
|
398
|
+
Plugin arguments will be supplied to the `__init__` function of the plugin via a keyword argument dictionary.
|
399
|
+
This function maps from the argument namespace from parsing back into that dictionary.
|
400
|
+
|
401
|
+
Args:
|
402
|
+
args: argument namespace from argparse
|
403
|
+
plugin: plugin to extract arguments for
|
404
|
+
Return:
|
405
|
+
filled arguments dictionary
|
406
|
+
"""
|
407
|
+
expected_args = plugin.get_arguments()
|
408
|
+
argument_destinations = [
|
409
|
+
(
|
410
|
+
value["dest"]
|
411
|
+
if "dest" in value
|
412
|
+
else key[0].replace("--", "").replace("-", "_")
|
413
|
+
)
|
414
|
+
for key, value in expected_args.items()
|
415
|
+
]
|
416
|
+
filled_arguments = {
|
417
|
+
destination: getattr(args, destination)
|
418
|
+
for destination in argument_destinations
|
419
|
+
}
|
420
|
+
# Check arguments or yield a Value error
|
421
|
+
if hasattr(plugin, "check_arguments"):
|
422
|
+
plugin.check_arguments(**filled_arguments)
|
423
|
+
return filled_arguments
|
424
|
+
|
425
|
+
|
248
426
|
class CompositeParser(ParserBase):
|
249
427
|
"""Composite parser handles parsing as a composition of multiple other parsers"""
|
250
428
|
|
@@ -258,6 +436,22 @@ class CompositeParser(ParserBase):
|
|
258
436
|
]
|
259
437
|
self.constituent_parsers = {*itertools.chain.from_iterable(flattened)}
|
260
438
|
|
439
|
+
def fill_parser(self, parser):
|
440
|
+
""" File supplied parser with grouped arguments
|
441
|
+
|
442
|
+
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
443
|
+
arguments based on the constituent that sources the argument.
|
444
|
+
|
445
|
+
Args:
|
446
|
+
parser: parser to fill
|
447
|
+
"""
|
448
|
+
for constituent in sorted(self.constituents, key=lambda x: x.description):
|
449
|
+
if isinstance(constituent, (PluginArgumentParser, CompositeParser)):
|
450
|
+
constituent.fill_parser(parser)
|
451
|
+
else:
|
452
|
+
argument_group = parser.add_argument_group(title=constituent.description)
|
453
|
+
constituent.fill_parser(argument_group)
|
454
|
+
|
261
455
|
@property
|
262
456
|
def constituents(self):
|
263
457
|
"""Get constituent"""
|
@@ -286,49 +480,14 @@ class CompositeParser(ParserBase):
|
|
286
480
|
return args
|
287
481
|
|
288
482
|
|
289
|
-
class
|
290
|
-
"""
|
291
|
-
Handles parsing of all of the comm-layer arguments. This means selecting a comm adapter, and passing the arguments
|
292
|
-
required to setup that comm adapter. In addition, this parser uses the import parser to import modules such that a
|
293
|
-
user may import other adapter implementation files.
|
294
|
-
"""
|
483
|
+
class CommExtraParser(ParserBase):
|
484
|
+
"""Parses extra communication arguments"""
|
295
485
|
|
296
|
-
DESCRIPTION = "
|
486
|
+
DESCRIPTION = "Communications options"
|
297
487
|
|
298
488
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
299
489
|
"""Get arguments for the comm-layer parser"""
|
300
|
-
adapter_definition_dictionaries = BaseAdapter.get_adapters()
|
301
|
-
adapter_arguments = {}
|
302
|
-
for name, adapter in adapter_definition_dictionaries.items():
|
303
|
-
adapter_arguments_callable = getattr(adapter, "get_arguments", None)
|
304
|
-
if not callable(adapter_arguments_callable):
|
305
|
-
print(
|
306
|
-
f"[WARNING] '{name}' does not have 'get_arguments' method, skipping.",
|
307
|
-
file=sys.stderr,
|
308
|
-
)
|
309
|
-
continue
|
310
|
-
adapter_arguments.update(adapter.get_arguments())
|
311
490
|
com_arguments = {
|
312
|
-
("--comm-adapter",): {
|
313
|
-
"dest": "adapter",
|
314
|
-
"action": "store",
|
315
|
-
"type": str,
|
316
|
-
"help": "Adapter for communicating to flight deployment. [default: %(default)s]",
|
317
|
-
"choices": ["none"] + list(adapter_definition_dictionaries),
|
318
|
-
"default": "ip",
|
319
|
-
},
|
320
|
-
("--comm-checksum-type",): {
|
321
|
-
"dest": "checksum_type",
|
322
|
-
"action": "store",
|
323
|
-
"type": str,
|
324
|
-
"help": "Setup the checksum algorithm. [default: %(default)s]",
|
325
|
-
"choices": [
|
326
|
-
item
|
327
|
-
for item in fprime_gds.common.communication.checksum.CHECKSUM_MAPPING.keys()
|
328
|
-
if item != "default"
|
329
|
-
],
|
330
|
-
"default": fprime_gds.common.communication.checksum.CHECKSUM_SELECTION,
|
331
|
-
},
|
332
491
|
("--output-unframed-data",): {
|
333
492
|
"dest": "output_unframed_data",
|
334
493
|
"action": "store",
|
@@ -339,17 +498,9 @@ class CommAdapterParser(ParserBase):
|
|
339
498
|
"required": False,
|
340
499
|
},
|
341
500
|
}
|
342
|
-
return
|
501
|
+
return com_arguments
|
343
502
|
|
344
503
|
def handle_arguments(self, args, **kwargs):
|
345
|
-
"""
|
346
|
-
Handle the input arguments for the parser. This will help setup the adapter with its expected arguments.
|
347
|
-
|
348
|
-
:param args: parsed arguments in namespace format
|
349
|
-
:return: namespace with "comm_adapter" value added
|
350
|
-
"""
|
351
|
-
args.comm_adapter = BaseAdapter.construct_adapter(args.adapter, args)
|
352
|
-
fprime_gds.common.communication.checksum.CHECKSUM_SELECTION = args.checksum_type
|
353
504
|
return args
|
354
505
|
|
355
506
|
|
@@ -360,7 +511,7 @@ class LogDeployParser(ParserBase):
|
|
360
511
|
to end up in the proper place.
|
361
512
|
"""
|
362
513
|
|
363
|
-
DESCRIPTION = "
|
514
|
+
DESCRIPTION = "Logging options"
|
364
515
|
|
365
516
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
366
517
|
"""Return arguments to parse logging options"""
|
@@ -423,7 +574,7 @@ class MiddleWareParser(ParserBase):
|
|
423
574
|
however; it should be close enough.
|
424
575
|
"""
|
425
576
|
|
426
|
-
DESCRIPTION = "
|
577
|
+
DESCRIPTION = "Middleware options"
|
427
578
|
|
428
579
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
429
580
|
"""Return arguments necessary to run a and connect to the GDS middleware"""
|
@@ -437,12 +588,6 @@ class MiddleWareParser(ParserBase):
|
|
437
588
|
"help": "Switch to using the ZMQ transportation layer",
|
438
589
|
"default": False,
|
439
590
|
},
|
440
|
-
("--zmq-server",): {
|
441
|
-
"dest": "zmq_server",
|
442
|
-
"action": "store_true",
|
443
|
-
"help": "Sets the ZMQ connection to be a server. Default: false (client)",
|
444
|
-
"default": False,
|
445
|
-
},
|
446
591
|
("--zmq-transport",): {
|
447
592
|
"dest": "zmq_transport",
|
448
593
|
"nargs": 2,
|
@@ -501,6 +646,8 @@ class MiddleWareParser(ParserBase):
|
|
501
646
|
class DictionaryParser(DetectionParser):
|
502
647
|
"""Parser for deployments"""
|
503
648
|
|
649
|
+
DESCRIPTION = "Dictionary options"
|
650
|
+
|
504
651
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
505
652
|
"""Arguments to handle deployments"""
|
506
653
|
return {
|
@@ -540,6 +687,8 @@ class DictionaryParser(DetectionParser):
|
|
540
687
|
class FileHandlingParser(ParserBase):
|
541
688
|
"""Parser for deployments"""
|
542
689
|
|
690
|
+
DESCRIPTION = "File handling options"
|
691
|
+
|
543
692
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
544
693
|
"""Arguments to handle deployments"""
|
545
694
|
|
@@ -618,7 +767,12 @@ class StandardPipelineParser(CompositeParser):
|
|
618
767
|
class CommParser(CompositeParser):
|
619
768
|
"""Comm Executable Parser"""
|
620
769
|
|
621
|
-
CONSTITUENTS = [
|
770
|
+
CONSTITUENTS = [
|
771
|
+
CommExtraParser,
|
772
|
+
MiddleWareParser,
|
773
|
+
LogDeployParser,
|
774
|
+
PluginArgumentParser,
|
775
|
+
]
|
622
776
|
|
623
777
|
def __init__(self):
|
624
778
|
"""Initialization"""
|
@@ -639,7 +793,7 @@ class GdsParser(ParserBase):
|
|
639
793
|
Note: deployment can help in setting both dictionary and logs, but isn't strictly required.
|
640
794
|
"""
|
641
795
|
|
642
|
-
DESCRIPTION = "
|
796
|
+
DESCRIPTION = "GUI options"
|
643
797
|
|
644
798
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
645
799
|
"""Return arguments necessary to run a binary deployment via the GDS"""
|
@@ -686,7 +840,7 @@ class BinaryDeployment(DetectionParser):
|
|
686
840
|
and represents the flight-side of the equation.
|
687
841
|
"""
|
688
842
|
|
689
|
-
DESCRIPTION = "
|
843
|
+
DESCRIPTION = "FPrime binary options"
|
690
844
|
|
691
845
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
692
846
|
"""Return arguments necessary to run a binary deployment via the GDS"""
|
@@ -732,7 +886,7 @@ class SearchArgumentsParser(ParserBase):
|
|
732
886
|
"""Parser for search arguments"""
|
733
887
|
|
734
888
|
DESCRIPTION = (
|
735
|
-
"
|
889
|
+
"Searching and filtering options"
|
736
890
|
)
|
737
891
|
|
738
892
|
def __init__(self, command_name: str) -> None:
|
@@ -778,7 +932,7 @@ class SearchArgumentsParser(ParserBase):
|
|
778
932
|
class RetrievalArgumentsParser(ParserBase):
|
779
933
|
"""Parser for retrieval arguments"""
|
780
934
|
|
781
|
-
DESCRIPTION = "
|
935
|
+
DESCRIPTION = "Data retrieval options"
|
782
936
|
|
783
937
|
def __init__(self, command_name: str) -> None:
|
784
938
|
self.command_name = command_name
|
fprime_gds/executables/comm.py
CHANGED
@@ -16,7 +16,6 @@ Note: assuming the module containing the ground adapter has been imported, then
|
|
16
16
|
@author lestarch
|
17
17
|
"""
|
18
18
|
|
19
|
-
|
20
19
|
import logging
|
21
20
|
import signal
|
22
21
|
import sys
|
@@ -30,11 +29,9 @@ except ImportError:
|
|
30
29
|
ZmqGround = None
|
31
30
|
import fprime_gds.common.communication.adapters.base
|
32
31
|
import fprime_gds.common.communication.adapters.ip
|
33
|
-
import fprime_gds.common.communication.checksum
|
34
32
|
import fprime_gds.common.communication.ground
|
35
33
|
import fprime_gds.common.logger
|
36
34
|
import fprime_gds.executables.cli
|
37
|
-
from fprime_gds.common.communication.framing import FpFramerDeframer
|
38
35
|
from fprime_gds.common.communication.updown import Downlinker, Uplinker
|
39
36
|
|
40
37
|
# Uses non-standard PIP package pyserial, so test the waters before getting a hard-import crash
|
@@ -58,13 +55,16 @@ def main():
|
|
58
55
|
fprime_gds.executables.cli.LogDeployParser,
|
59
56
|
fprime_gds.executables.cli.MiddleWareParser,
|
60
57
|
fprime_gds.executables.cli.CommParser,
|
58
|
+
fprime_gds.executables.cli.PluginArgumentParser,
|
61
59
|
],
|
62
60
|
description="F prime communications layer.",
|
63
61
|
client=True,
|
64
62
|
)
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
if args.communication_selection == "none":
|
64
|
+
print(
|
65
|
+
"[ERROR] Comm adapter set to 'none'. Nothing to do but exit.",
|
66
|
+
file=sys.stderr,
|
67
|
+
)
|
68
68
|
sys.exit(-1)
|
69
69
|
|
70
70
|
# Create the handling components for either side of this script, adapter for hardware, and ground for the GDS side
|
@@ -73,23 +73,20 @@ def main():
|
|
73
73
|
sys.exit(-1)
|
74
74
|
elif args.zmq:
|
75
75
|
ground = fprime_gds.common.zmq_transport.ZmqGround(args.zmq_transport)
|
76
|
-
# Check for need to make this a server
|
77
|
-
if args.zmq_server:
|
78
|
-
ground.make_server()
|
79
76
|
else:
|
80
77
|
ground = fprime_gds.common.communication.ground.TCPGround(
|
81
78
|
args.tts_addr, args.tts_port
|
82
79
|
)
|
83
80
|
|
84
|
-
adapter = args.
|
81
|
+
adapter = args.communication_selection_instance
|
85
82
|
|
86
83
|
# Set the framing class used and pass it to the uplink and downlink component constructions giving each a separate
|
87
84
|
# instantiation
|
88
|
-
|
85
|
+
framer_instance = args.framing_selection_instance
|
89
86
|
LOGGER.info(
|
90
87
|
"Starting uplinker/downlinker connecting to FSW using %s with %s",
|
91
|
-
|
92
|
-
|
88
|
+
args.communication_selection,
|
89
|
+
args.framing_selection,
|
93
90
|
)
|
94
91
|
discarded_file_handle = None
|
95
92
|
try:
|
@@ -108,9 +105,9 @@ def main():
|
|
108
105
|
discarded_file_handle_path,
|
109
106
|
)
|
110
107
|
downlinker = Downlinker(
|
111
|
-
adapter, ground,
|
108
|
+
adapter, ground, framer_instance, discarded=discarded_file_handle
|
112
109
|
)
|
113
|
-
uplinker = Uplinker(adapter, ground,
|
110
|
+
uplinker = Uplinker(adapter, ground, framer_instance, downlinker)
|
114
111
|
|
115
112
|
# Open resources for the handlers on either side, this prepares the resources needed for reading/writing data
|
116
113
|
ground.open()
|
@@ -121,8 +118,8 @@ def main():
|
|
121
118
|
uplinker.start()
|
122
119
|
LOGGER.debug("Uplinker and downlinker running")
|
123
120
|
|
124
|
-
# Wait for shutdown event in the form of a KeyboardInterrupt then stop the processing, close resources,
|
125
|
-
# everything to terminate as expected.
|
121
|
+
# Wait for shutdown event in the form of a KeyboardInterrupt then stop the processing, close resources,
|
122
|
+
# and wait for everything to terminate as expected.
|
126
123
|
def shutdown(*_):
|
127
124
|
"""Shutdown function for signals"""
|
128
125
|
uplinker.stop()
|
@@ -13,6 +13,7 @@ from fprime_gds.executables.cli import (
|
|
13
13
|
GdsParser,
|
14
14
|
ParserBase,
|
15
15
|
StandardPipelineParser,
|
16
|
+
PluginArgumentParser,
|
16
17
|
)
|
17
18
|
from fprime_gds.executables.utils import AppWrapperException, run_wrapped_application
|
18
19
|
|
@@ -20,14 +21,20 @@ BASE_MODULE_ARGUMENTS = [sys.executable, "-u", "-m"]
|
|
20
21
|
|
21
22
|
|
22
23
|
def parse_args():
|
23
|
-
"""
|
24
|
+
"""Parse command line arguments
|
24
25
|
Gets an argument parsers to read the command line and process the arguments. Return
|
25
26
|
the arguments in their namespace.
|
26
27
|
|
27
28
|
:return: parsed argument namespace
|
28
29
|
"""
|
29
30
|
# Get custom handlers for all executables we are running
|
30
|
-
arg_handlers = [
|
31
|
+
arg_handlers = [
|
32
|
+
StandardPipelineParser,
|
33
|
+
GdsParser,
|
34
|
+
BinaryDeployment,
|
35
|
+
CommParser,
|
36
|
+
PluginArgumentParser,
|
37
|
+
]
|
31
38
|
# Parse the arguments, and refine through all handlers
|
32
39
|
args, parser = ParserBase.parse_args(arg_handlers, "Run F prime deployment and GDS")
|
33
40
|
return args
|
@@ -63,7 +70,7 @@ def launch_process(cmd, logfile=None, name=None, env=None, launch_time=5):
|
|
63
70
|
|
64
71
|
|
65
72
|
def launch_tts(parsed_args):
|
66
|
-
"""
|
73
|
+
"""Launch the ThreadedTcpServer middleware application
|
67
74
|
|
68
75
|
|
69
76
|
Args:
|
@@ -85,7 +92,7 @@ def launch_tts(parsed_args):
|
|
85
92
|
|
86
93
|
|
87
94
|
def launch_html(parsed_args):
|
88
|
-
"""
|
95
|
+
"""Launch the Flask application
|
89
96
|
|
90
97
|
Args:
|
91
98
|
parsed_args: parsed argument namespace
|
@@ -112,14 +119,18 @@ def launch_html(parsed_args):
|
|
112
119
|
str(parsed_args.gui_port),
|
113
120
|
]
|
114
121
|
ret = launch_process(gse_args, name="HTML GUI", env=flask_env, launch_time=2)
|
122
|
+
ui_url = f"http://{str(parsed_args.gui_addr)}:{str(parsed_args.gui_port)}/"
|
123
|
+
print(f"[INFO] Launched UI at: {ui_url}")
|
115
124
|
webbrowser.open(
|
116
|
-
|
125
|
+
ui_url,
|
126
|
+
new=0,
|
127
|
+
autoraise=True,
|
117
128
|
)
|
118
129
|
return ret
|
119
130
|
|
120
131
|
|
121
132
|
def launch_app(parsed_args):
|
122
|
-
"""
|
133
|
+
"""Launch the raw application
|
123
134
|
|
124
135
|
Args:
|
125
136
|
parsed_args: parsed argument namespace
|
@@ -128,14 +139,20 @@ def launch_app(parsed_args):
|
|
128
139
|
"""
|
129
140
|
app_path = parsed_args.app
|
130
141
|
logfile = os.path.join(parsed_args.logs, f"{app_path.name}.log")
|
131
|
-
app_cmd = [
|
142
|
+
app_cmd = [
|
143
|
+
app_path.absolute(),
|
144
|
+
"-p",
|
145
|
+
str(parsed_args.port),
|
146
|
+
"-a",
|
147
|
+
parsed_args.address,
|
148
|
+
]
|
132
149
|
return launch_process(
|
133
150
|
app_cmd, name=f"{app_path.name} Application", logfile=logfile, launch_time=1
|
134
151
|
)
|
135
152
|
|
136
153
|
|
137
154
|
def launch_comm(parsed_args):
|
138
|
-
"""
|
155
|
+
"""Launch the communication adapter process
|
139
156
|
|
140
157
|
Args:
|
141
158
|
parsed_args: parsed argument namespace
|
@@ -143,9 +160,27 @@ def launch_comm(parsed_args):
|
|
143
160
|
launched process
|
144
161
|
"""
|
145
162
|
arguments = CommParser().reproduce_cli_args(parsed_args)
|
146
|
-
arguments =
|
163
|
+
arguments = (
|
164
|
+
arguments + ["--log-directly"]
|
165
|
+
if "--log-directly" not in arguments
|
166
|
+
else arguments
|
167
|
+
)
|
147
168
|
app_cmd = BASE_MODULE_ARGUMENTS + ["fprime_gds.executables.comm"] + arguments
|
148
|
-
return launch_process(
|
169
|
+
return launch_process(
|
170
|
+
app_cmd,
|
171
|
+
name=f"comm[{parsed_args.communication_selection}] Application",
|
172
|
+
launch_time=1,
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
def launch_plugin(plugin_class_instance):
|
177
|
+
""" Launch a plugin instance """
|
178
|
+
plugin_name = getattr(plugin_class_instance, "get_name", lambda: cls.__name__)()
|
179
|
+
return launch_process(
|
180
|
+
plugin_class_instance.get_process_invocation(),
|
181
|
+
name=f"{ plugin_name } Plugin App",
|
182
|
+
launch_time=1,
|
183
|
+
)
|
149
184
|
|
150
185
|
|
151
186
|
def main():
|
@@ -155,20 +190,23 @@ def main():
|
|
155
190
|
parsed_args = parse_args()
|
156
191
|
launchers = []
|
157
192
|
|
158
|
-
# Launch
|
193
|
+
# Launch middleware layer if not using ZMQ
|
159
194
|
if not parsed_args.zmq:
|
160
195
|
launchers.append(launch_tts)
|
161
196
|
|
162
197
|
# Check if we are running with communications
|
163
|
-
if parsed_args.
|
198
|
+
if parsed_args.communication_selection != "none":
|
164
199
|
launchers.append(launch_comm)
|
165
200
|
|
166
201
|
# Add app, if possible
|
167
202
|
if parsed_args.app:
|
168
|
-
if parsed_args.
|
203
|
+
if parsed_args.communication_selection == "ip":
|
169
204
|
launchers.append(launch_app)
|
170
205
|
else:
|
171
|
-
print(
|
206
|
+
print(
|
207
|
+
"[WARNING] App cannot be auto-launched without IP adapter",
|
208
|
+
file=sys.stderr,
|
209
|
+
)
|
172
210
|
|
173
211
|
# Launch the desired GUI package
|
174
212
|
if parsed_args.gui == "html":
|
@@ -177,6 +215,9 @@ def main():
|
|
177
215
|
# Launch launchers and wait for the last app to finish
|
178
216
|
try:
|
179
217
|
procs = [launcher(parsed_args) for launcher in launchers]
|
218
|
+
_ = [launch_plugin(cls) for cls in parsed_args.gds_app_enabled_instances]
|
219
|
+
_ = [instance.run() for instance in parsed_args.gds_function_enabled_instances]
|
220
|
+
|
180
221
|
print("[INFO] F prime is now running. CTRL-C to shutdown all components.")
|
181
222
|
procs[-1].wait()
|
182
223
|
except KeyboardInterrupt:
|
fprime_gds/executables/utils.py
CHANGED
@@ -118,7 +118,7 @@ def run_wrapped_application(arguments, logfile=None, env=None, launch_time=None)
|
|
118
118
|
if launch_time is not None:
|
119
119
|
time.sleep(launch_time)
|
120
120
|
child.poll()
|
121
|
-
if child.returncode is not None:
|
121
|
+
if child.returncode is not None and child.returncode != 0:
|
122
122
|
raise ProcessNotStableException(
|
123
123
|
arguments[0], child.returncode, launch_time
|
124
124
|
)
|
File without changes
|