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.
@@ -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()
@@ -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(description=self.description, add_help=True)
89
- for flags, keywords in self.get_arguments().items():
90
- parser.add_argument(*flags, **keywords)
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 CommAdapterParser(ParserBase):
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 = "Process arguments needed to specify a comm-adapter"
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 {**adapter_arguments, **com_arguments}
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 = "Process arguments needed to specify a logging"
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 = "Process arguments needed to specify a tool using the middleware"
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 = [CommAdapterParser, MiddleWareParser, LogDeployParser]
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 = "Process arguments needed to specify a tool using the GDS"
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 = "Process arguments needed for running F prime binary"
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
- "Process arguments relevant to searching/filtering Channels/Events/Commands"
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 = "Process arguments relevant to retrieving Channels/Events"
935
+ DESCRIPTION = "Data retrieval options"
782
936
 
783
937
  def __init__(self, command_name: str) -> None:
784
938
  self.command_name = command_name
@@ -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
- fprime_gds.common.communication.checksum = args.checksum_type
66
- if args.comm_adapter == "none":
67
- print("[ERROR] Comm adapter set to 'none'. Nothing to do but exit.", file=sys.stderr)
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.comm_adapter
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
- framer_class = FpFramerDeframer
85
+ framer_instance = args.framing_selection_instance
89
86
  LOGGER.info(
90
87
  "Starting uplinker/downlinker connecting to FSW using %s with %s",
91
- adapter,
92
- framer_class.__name__,
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, framer_class(), discarded=discarded_file_handle
108
+ adapter, ground, framer_instance, discarded=discarded_file_handle
112
109
  )
113
- uplinker = Uplinker(adapter, ground, framer_class(), downlinker)
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, and wait for
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()