fprime-gds 3.4.2__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/loaders/xml_loader.py +5 -1
- 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.2.dist-info → fprime_gds-3.4.4a1.dist-info}/METADATA +2 -2
- {fprime_gds-3.4.2.dist-info → fprime_gds-3.4.4a1.dist-info}/RECORD +22 -18
- {fprime_gds-3.4.2.dist-info → fprime_gds-3.4.4a1.dist-info}/WHEEL +1 -1
- {fprime_gds-3.4.2.dist-info → fprime_gds-3.4.4a1.dist-info}/LICENSE.txt +0 -0
- {fprime_gds-3.4.2.dist-info → fprime_gds-3.4.4a1.dist-info}/NOTICE.txt +0 -0
- {fprime_gds-3.4.2.dist-info → fprime_gds-3.4.4a1.dist-info}/entry_points.txt +0 -0
- {fprime_gds-3.4.2.dist-info → fprime_gds-3.4.4a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
""" fprime_gds.executables.apps: an implementation of start-up apps in fprime
|
2
|
+
|
3
|
+
There are twp ways to approach start=up applications in fprime. First, is to implement a run method via a subclass of
|
4
|
+
`GdsFunction`. This gives the implementor the ability to run anything within the run function that python offers,
|
5
|
+
however; this comes with complexity of setting up a new thread/process/isolation to ensure that the plugin does not
|
6
|
+
threaten the fprime-gds core functionality and processes.
|
7
|
+
|
8
|
+
The second method is to inherit from `GdsApp` implementing the `get_process_invocation` function to return the necessary
|
9
|
+
command line that will be spun into its own process.
|
10
|
+
|
11
|
+
@author lestarch
|
12
|
+
"""
|
13
|
+
import subprocess
|
14
|
+
from abc import ABC, abstractmethod
|
15
|
+
from typing import List, Type
|
16
|
+
|
17
|
+
from fprime_gds.plugin.definitions import gds_plugin_specification
|
18
|
+
|
19
|
+
|
20
|
+
class GdsBaseFunction(ABC):
|
21
|
+
""" Base functionality for pluggable GDS start-up functions
|
22
|
+
|
23
|
+
GDS start-up functionality is pluggable. This class acts as a base for pluggable functionality supplies helpers to
|
24
|
+
the various start-up plugins.
|
25
|
+
|
26
|
+
Developers who intend to run in an isolated subprocess are strongly encouraged to use `GdsApp` (see below).
|
27
|
+
Developers who need flexibility may use GdsFunction.
|
28
|
+
"""
|
29
|
+
|
30
|
+
@abstractmethod
|
31
|
+
def run(self):
|
32
|
+
""" Run the start-up function
|
33
|
+
|
34
|
+
Run the start-up function unconstrained by the limitations of running in a dedicated subprocess.
|
35
|
+
|
36
|
+
"""
|
37
|
+
raise NotImplementedError()
|
38
|
+
|
39
|
+
|
40
|
+
class GdsFunction(GdsBaseFunction, ABC):
|
41
|
+
""" Functionality for pluggable GDS start-up functions
|
42
|
+
|
43
|
+
GDS start-up functionality is pluggable. This class acts as a wide-open implementation of functionality via a single
|
44
|
+
`run` callback. Developers have complete control of the start-up functionality. However, this comes at the cost of
|
45
|
+
instability in that case of poorly designed functions.
|
46
|
+
|
47
|
+
Developers who intend to run in an isolated subprocess are strongly encouraged to use `GdsApp` (see below).
|
48
|
+
|
49
|
+
Plugin developers are required to implement a single function `run`, which must take care of setting up and running
|
50
|
+
the start-up function. Developers **must** handle the isolation of this functionality including spinning off a new
|
51
|
+
thread, subprocess, etc. Additionally, the developer must define the `register_gds_function_plugin` class method
|
52
|
+
annotated with the @gds_plugin_implementation annotation.
|
53
|
+
|
54
|
+
Standard plug-in functions (get_name, get_arguments) are available should the implementer desire these features.
|
55
|
+
Arguments will be supplied to the class's `__init__` function.
|
56
|
+
"""
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
@gds_plugin_specification
|
60
|
+
def register_gds_function_plugin(cls) -> Type["GdsFunction"]:
|
61
|
+
"""Register gds start-up functionality
|
62
|
+
|
63
|
+
Plugin hook for registering a plugin that supplies start-up functionality. This functionality will run on start-up
|
64
|
+
of the GDS network.
|
65
|
+
|
66
|
+
Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
|
67
|
+
determined from class methods, solicited via the command line, and provided at construction time to the chosen
|
68
|
+
instantiation.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
GDSFunction subclass
|
72
|
+
"""
|
73
|
+
raise NotImplementedError()
|
74
|
+
|
75
|
+
|
76
|
+
class GdsApp(GdsBaseFunction):
|
77
|
+
""" GDS start-up process functionality
|
78
|
+
|
79
|
+
A pluggable base class used to start a new process as part of the GDS command line invocation. This allows
|
80
|
+
developers to add process-isolated functionality to the GDS network.
|
81
|
+
|
82
|
+
Plugin developers are required to implement the `get_process_invocation` function that returns a list of arguments
|
83
|
+
needed to invoke the process via python's `subprocess`. Additionally, the developer must define the
|
84
|
+
`register_gds_function_plugin` class method annotated with the @gds_plugin_implementation annotation.
|
85
|
+
|
86
|
+
Standard plug-in functions (get_name, get_arguments) are available should the implementer desire these features.
|
87
|
+
Arguments will be supplied to the class's `__init__` function.
|
88
|
+
"""
|
89
|
+
def __init__(self, **arguments):
|
90
|
+
""" Construct the communication applications around the arguments
|
91
|
+
|
92
|
+
Command line arguments are passed in to match those returned from the `get_arguments` functions.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
arguments: arguments from the command line
|
96
|
+
"""
|
97
|
+
self.process = None
|
98
|
+
self.arguments = arguments
|
99
|
+
|
100
|
+
def run(self):
|
101
|
+
""" Run the application as an isolated process
|
102
|
+
|
103
|
+
GdsFunction objects require an implementation of the `run` command. This implementation will take the arguments
|
104
|
+
provided from `get_process_invocation` function and supplies them as an invocation of the isolated subprocess.
|
105
|
+
"""
|
106
|
+
invocation_arguments = self.get_process_invocation()
|
107
|
+
self.process = subprocess.Popen(invocation_arguments)
|
108
|
+
|
109
|
+
def wait(self, timeout=None):
|
110
|
+
""" Wait for the app to complete then return the return code
|
111
|
+
|
112
|
+
Waits (blocking) for the process to complete. Then returns the return code of the underlying process. If timeout
|
113
|
+
is non-None then the process will be killed after waiting for the timeout and another wait of timeout will be
|
114
|
+
allowed for the killed process to exit.
|
115
|
+
|
116
|
+
Return:
|
117
|
+
return code of the underlying process
|
118
|
+
"""
|
119
|
+
try:
|
120
|
+
_, _ = self.process.wait(timeout=timeout)
|
121
|
+
except subprocess.TimeoutExpired:
|
122
|
+
self.process.kill()
|
123
|
+
_, _ = self.process.wait(timeout=timeout)
|
124
|
+
return self.process.returncode
|
125
|
+
|
126
|
+
@abstractmethod
|
127
|
+
def get_process_invocation(self) -> List[str]:
|
128
|
+
""" Run the start-up function
|
129
|
+
|
130
|
+
Run the start-up function unconstrained by the limitations of running in a dedicated subprocess.
|
131
|
+
|
132
|
+
"""
|
133
|
+
raise NotImplementedError()
|
134
|
+
|
135
|
+
@classmethod
|
136
|
+
@gds_plugin_specification
|
137
|
+
def register_gds_app_plugin(cls) -> Type["GdsApp"]:
|
138
|
+
"""Register a gds start-up application
|
139
|
+
|
140
|
+
Plugin hook for registering a plugin that supplies start-up functionality. This functionality will run on start-up
|
141
|
+
of the GDS network isolated into a dedicated process.
|
142
|
+
|
143
|
+
Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
|
144
|
+
determined from class methods, solicited via the command line, and provided at construction time to the chosen
|
145
|
+
instantiation.
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
GdsApp subclass
|
149
|
+
"""
|
150
|
+
raise NotImplementedError()
|
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()
|