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.
@@ -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()
@@ -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
- """ Parse command line arguments
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 = [StandardPipelineParser, GdsParser, BinaryDeployment, CommParser]
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
- """ Launch the ThreadedTcpServer middleware application
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
- """ Launch the Flask application
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
- f"http://{str(parsed_args.gui_addr)}:{str(parsed_args.gui_port)}/", new=0, autoraise=True
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
- """ Launch the raw application
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 = [app_path.absolute(), "-p", str(parsed_args.port), "-a", parsed_args.address]
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
- """ Launch the communication adapter process
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 = arguments + ["--log-directly"] if "--log-directly" not in arguments else 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(app_cmd, name=f'comm[{parsed_args.adapter}] Application', launch_time=1)
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 a gui, if specified
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.adapter != "none":
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.adapter == "ip":
203
+ if parsed_args.communication_selection == "ip":
169
204
  launchers.append(launch_app)
170
205
  else:
171
- print("[WARNING] App cannot be auto-launched without IP adapter", file=sys.stderr)
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:
@@ -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