fprime-gds 3.6.1__py3-none-any.whl → 4.0.0__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.
Files changed (70) hide show
  1. fprime_gds/common/communication/adapters/ip.py +14 -9
  2. fprime_gds/common/communication/adapters/uart.py +34 -25
  3. fprime_gds/common/communication/ccsds/__init__.py +0 -0
  4. fprime_gds/common/communication/ccsds/apid.py +19 -0
  5. fprime_gds/common/communication/ccsds/chain.py +106 -0
  6. fprime_gds/common/communication/ccsds/space_data_link.py +196 -0
  7. fprime_gds/common/communication/ccsds/space_packet.py +129 -0
  8. fprime_gds/common/communication/framing.py +27 -32
  9. fprime_gds/common/decoders/ch_decoder.py +1 -1
  10. fprime_gds/common/decoders/event_decoder.py +9 -2
  11. fprime_gds/common/decoders/pkt_decoder.py +1 -1
  12. fprime_gds/common/distributor/distributor.py +6 -3
  13. fprime_gds/common/encoders/ch_encoder.py +2 -2
  14. fprime_gds/common/encoders/cmd_encoder.py +2 -2
  15. fprime_gds/common/encoders/event_encoder.py +2 -2
  16. fprime_gds/common/encoders/pkt_encoder.py +2 -2
  17. fprime_gds/common/encoders/seq_writer.py +2 -2
  18. fprime_gds/common/fpy/README.md +56 -0
  19. fprime_gds/common/fpy/SPEC.md +69 -0
  20. fprime_gds/common/fpy/__init__.py +0 -0
  21. fprime_gds/common/fpy/bytecode/__init__.py +0 -0
  22. fprime_gds/common/fpy/bytecode/directives.py +490 -0
  23. fprime_gds/common/fpy/codegen.py +1687 -0
  24. fprime_gds/common/fpy/grammar.lark +88 -0
  25. fprime_gds/common/fpy/main.py +40 -0
  26. fprime_gds/common/fpy/parser.py +239 -0
  27. fprime_gds/common/gds_cli/base_commands.py +1 -1
  28. fprime_gds/common/handlers.py +39 -0
  29. fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
  30. fprime_gds/common/loaders/json_loader.py +15 -0
  31. fprime_gds/common/loaders/pkt_json_loader.py +125 -0
  32. fprime_gds/common/loaders/prm_json_loader.py +85 -0
  33. fprime_gds/common/logger/__init__.py +2 -2
  34. fprime_gds/common/pipeline/dictionaries.py +60 -41
  35. fprime_gds/common/pipeline/encoding.py +19 -0
  36. fprime_gds/common/pipeline/histories.py +4 -0
  37. fprime_gds/common/pipeline/standard.py +16 -2
  38. fprime_gds/common/templates/cmd_template.py +8 -0
  39. fprime_gds/common/templates/prm_template.py +81 -0
  40. fprime_gds/common/testing_fw/api.py +148 -1
  41. fprime_gds/common/testing_fw/pytest_integration.py +37 -3
  42. fprime_gds/common/tools/README.md +34 -0
  43. fprime_gds/common/tools/params.py +246 -0
  44. fprime_gds/common/utils/config_manager.py +6 -6
  45. fprime_gds/common/utils/data_desc_type.py +6 -1
  46. fprime_gds/executables/apps.py +189 -11
  47. fprime_gds/executables/cli.py +468 -127
  48. fprime_gds/executables/comm.py +5 -2
  49. fprime_gds/executables/data_product_writer.py +164 -165
  50. fprime_gds/executables/fprime_cli.py +3 -3
  51. fprime_gds/executables/run_deployment.py +13 -5
  52. fprime_gds/flask/app.py +3 -0
  53. fprime_gds/flask/resource.py +5 -2
  54. fprime_gds/flask/static/addons/chart-display/addon.js +8 -3
  55. fprime_gds/flask/static/js/datastore.js +1 -0
  56. fprime_gds/flask/static/js/vue-support/channel.js +1 -1
  57. fprime_gds/flask/static/js/vue-support/event.js +1 -1
  58. fprime_gds/plugin/definitions.py +86 -8
  59. fprime_gds/plugin/system.py +172 -58
  60. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/METADATA +23 -21
  61. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/RECORD +66 -50
  62. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/WHEEL +1 -1
  63. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/entry_points.txt +2 -0
  64. fprime_gds/common/loaders/ch_py_loader.py +0 -79
  65. fprime_gds/common/loaders/cmd_py_loader.py +0 -66
  66. fprime_gds/common/loaders/event_py_loader.py +0 -75
  67. fprime_gds/common/loaders/python_loader.py +0 -132
  68. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/LICENSE.txt +0 -0
  69. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/NOTICE.txt +0 -0
  70. {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/top_level.txt +0 -0
@@ -197,12 +197,17 @@ Vue.component("chart-display", {
197
197
  if (this.selected == null || this.chart == null) {
198
198
  return;
199
199
  }
200
- // Get channel name assuming the string is in component.channel format.
200
+ // Get channel name assuming the string is in either of these format:
201
+ // - component.channel format for XML dictionaries
202
+ // - deployment.component.channel for JSON dictionaries
203
+ // Can't just slice by last '.' because channel name can include complex types
204
+ // which are in the form of 'component.channel.fieldName'
205
+ let SLICE_INDEX = _dictionaries.metadata.dictionary_type == "xml" ? 2 : 3;
201
206
  let channel_full_name = this.selected
202
207
  .split(".")
203
- .slice(0, 2)
208
+ .slice(0, SLICE_INDEX)
204
209
  .join(".");
205
- let serial_path = this.selected.split(".").slice(2).join(".");
210
+ let serial_path = this.selected.split(".").slice(SLICE_INDEX).join(".");
206
211
 
207
212
  // Filter channels down to the graphed channel
208
213
  let new_channels = channels.filter((channel) => {
@@ -253,6 +253,7 @@ class DataStore {
253
253
  commands_by_id: Object.fromEntries(Object.values(_loader.endpoints["command-dict"].data.dictionary).map((value) => [value.id, value])),
254
254
  framework_version: _loader.endpoints["command-dict"].data.framework_version,
255
255
  project_version: _loader.endpoints["command-dict"].data.project_version,
256
+ metadata: _loader.endpoints["command-dict"].data.metadata,
256
257
  });
257
258
  // Setup channels object in preparation for updates. Channel object need to be well formed, even if blank,
258
259
  // because rendering of edit-views is still possible.
@@ -67,7 +67,7 @@ Vue.component("channel-table", {
67
67
  if (item.time == null || item.val == null) {
68
68
  return ["", "0x" + item.id.toString(16), template.full_name, ""];
69
69
  }
70
- return [timeToString(item.datetime || item.time), "0x" + item.id.toString(16), template.full_name,
70
+ return [timeToString(item.time || item.datetime), "0x" + item.id.toString(16), template.full_name,
71
71
  (typeof(item.display_text) !== "undefined")? item.display_text : item.val]
72
72
  },
73
73
  /**
@@ -80,7 +80,7 @@ Vue.component("event-list", {
80
80
  const msg = '<span title="' + groups[0] + '">' + command_mnemonic + '</span>'
81
81
  display_text = display_text.replace(OPREG, msg);
82
82
  }
83
- return [timeToString(item.datetime || item.time), "0x" + item.id.toString(16), template.full_name,
83
+ return [timeToString(item.time || item.datetime), "0x" + item.id.toString(16), template.full_name,
84
84
  template.severity.value.replace("EventSeverity.", ""), display_text];
85
85
  },
86
86
  /**
@@ -1,4 +1,4 @@
1
- """ fprime_gds.plugin.definitions: definitions of plugin specifications and decorators
1
+ """fprime_gds.plugin.definitions: definitions of plugin specifications and decorators
2
2
 
3
3
  In order to define a plugin, an implementation decorator is used. Users can import `gds_plugin_implementation` from this
4
4
  file to decorate functions that implement plugins.
@@ -7,6 +7,8 @@ This file also defines helper classes to support the plugin system.
7
7
 
8
8
  @author lestarch
9
9
  """
10
+
11
+ import inspect
10
12
  import pluggy
11
13
  from enum import Enum, auto
12
14
  from typing import Any, Dict, Tuple, Type
@@ -17,8 +19,67 @@ gds_plugin_specification = pluggy.HookspecMarker(PROJECT_NAME)
17
19
  gds_plugin_implementation = pluggy.HookimplMarker(PROJECT_NAME)
18
20
 
19
21
 
22
+ def gds_plugin(plugin_class):
23
+ """Decorator: make decorated class a plugin of the plugin_class
24
+
25
+ This allows users to quickly define plugin implementations by decorating their class with the definition of the
26
+ plugin they are implementing. This works by searching the plugin_class for a function with the attribute
27
+ { PROJECT_NAME }_spec. This will be the specification function. It will then add an implementation of the
28
+ implementation spec to the supplied class.
29
+
30
+ It will check the following assertions:
31
+ 1. plugin_class has a valid gds_plugin_specification
32
+ 2. plugin_class specification is well-formed (no arguments, class method)
33
+ 2. The decorated class is a valid subclass of plugin_class
34
+ 3. The decorated class is non-abstract
35
+ """
36
+ assert isinstance(
37
+ plugin_class, type
38
+ ), "Must supply @gds_plugin valid plugin classes"
39
+ plugin_name = plugin_class.__name__
40
+ spec_attr = f"{ PROJECT_NAME }_spec"
41
+ spec_methods = inspect.getmembers(
42
+ plugin_class, predicate=lambda item: hasattr(item, spec_attr)
43
+ )
44
+ assert len(spec_methods) == 1, f"'{plugin_name}' is not a valid F Prime GDS plugin."
45
+ spec_method = spec_methods[0]
46
+ spec_method_name, spec_method_function = spec_method
47
+ assert (
48
+ getattr(spec_method_function, "__self__", None) == plugin_class
49
+ ), f"'{plugin_name}' specification invalid."
50
+ assert inspect.isabstract(
51
+ plugin_class
52
+ ), f"{plugin_class} is not a plugin superclass."
53
+
54
+ def decorator(decorated_class):
55
+ """Implementation of the decorator: check valid subclass, and add plugin method"""
56
+ assert isinstance(decorated_class, type), "Must use @gds_plugin on classes"
57
+ class_name = decorated_class.__name__
58
+ assert issubclass(
59
+ decorated_class, plugin_class
60
+ ), f"{class_name} is not a subclass of {plugin_name}"
61
+ assert not inspect.isabstract(
62
+ decorated_class
63
+ ), f"{class_name} is abstract. Plugins may not be abstract."
64
+
65
+ def return_decorated_class(cls):
66
+ """Function to become the plugin implementation method"""
67
+ assert cls == decorated_class, "Plugin system failure"
68
+ return decorated_class
69
+
70
+ setattr(
71
+ decorated_class,
72
+ spec_method_name,
73
+ classmethod(gds_plugin_implementation(return_decorated_class)),
74
+ )
75
+ return decorated_class
76
+
77
+ return decorator
78
+
79
+
20
80
  class PluginType(Enum):
21
- """ Enumeration of plugin types"""
81
+ """Enumeration of plugin types"""
82
+
22
83
  ALL = auto()
23
84
  """ Plugin selection including all types of plugins """
24
85
 
@@ -30,10 +91,10 @@ class PluginType(Enum):
30
91
 
31
92
 
32
93
  class Plugin(object):
33
- """ Plugin wrapper object """
94
+ """Plugin wrapper object"""
34
95
 
35
96
  def __init__(self, category: str, plugin_type: PluginType, plugin_class: Type[Any]):
36
- """ Initialize the plugin
97
+ """Initialize the plugin
37
98
 
38
99
  Args:
39
100
  category: category of the plugin (i.e. register_<category>_function)
@@ -44,8 +105,12 @@ class Plugin(object):
44
105
  self.type = plugin_type
45
106
  self.plugin_class = plugin_class
46
107
 
108
+ def get_implementor(self):
109
+ """Get the implementor of this plugin"""
110
+ return self.plugin_class
111
+
47
112
  def get_name(self):
48
- """ Get the name of the plugin
113
+ """Get the name of the plugin
49
114
 
50
115
  Plugin names are derived from the `get_name` class method of the plugin's implementation class. When not defined
51
116
  that name is derived from the plugin's implementation class __name__ property instead.
@@ -54,12 +119,13 @@ class Plugin(object):
54
119
  name of plugin
55
120
  """
56
121
  return (
57
- self.plugin_class.get_name() if hasattr(self.plugin_class, "get_name")
122
+ self.plugin_class.get_name()
123
+ if hasattr(self.plugin_class, "get_name")
58
124
  else self.plugin_class.__name__
59
125
  )
60
126
 
61
127
  def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
62
- """ Get arguments needed by plugin
128
+ """Get arguments needed by plugin
63
129
 
64
130
  Plugin argument are derived from the `get_arguments` class method of the plugin's implementation class. When not
65
131
  defined an empty dictionary is returned.
@@ -67,5 +133,17 @@ class Plugin(object):
67
133
  Returns:
68
134
  argument specification for plugin
69
135
  """
70
- return self.plugin_class.get_arguments() if hasattr(self.plugin_class, "get_arguments") else {}
136
+ return (
137
+ self.plugin_class.get_arguments()
138
+ if hasattr(self.plugin_class, "get_arguments")
139
+ else {}
140
+ )
71
141
 
142
+ def check_arguments(self, **kwargs):
143
+ """Check a plugin's arguments
144
+
145
+ Check a plugin's arguments if it defines a check method. Arguments are passed as kwargs just like the arguments are
146
+ passed to the constructor.
147
+ """
148
+ if hasattr(self.plugin_class, "check_arguments"):
149
+ self.plugin_class.check_arguments(**kwargs)
@@ -1,4 +1,4 @@
1
- """ fprime_gds.plugin.system: implementation of plugins
1
+ """fprime_gds.plugin.system: implementation of plugins
2
2
 
3
3
  This file contains the implementation and registration of plugins for fprime_gds. Primarily, it defines the Plugins
4
4
  class that handles plugins. Users can acquire the Plugin singleton with `Plugin.system()`.
@@ -8,6 +8,7 @@ using entrypoints.
8
8
 
9
9
  @author lestarch
10
10
  """
11
+ import copy
11
12
  import os
12
13
  import importlib
13
14
  import inspect
@@ -18,46 +19,11 @@ import pluggy
18
19
 
19
20
  from fprime_gds.plugin.definitions import Plugin, PluginType, PROJECT_NAME
20
21
 
21
- # For automatic validation of plugins, each plugin class type must be imported here
22
- from fprime_gds.executables.apps import GdsFunction, GdsApp
23
- from fprime_gds.common.communication.framing import FramerDeframer, FpFramerDeframer
24
- from fprime_gds.common.communication.adapters.base import BaseAdapter, NoneAdapter
25
- from fprime_gds.common.communication.adapters.ip import IpAdapter
26
-
27
- try:
28
- from fprime_gds.common.communication.adapters.uart import SerialAdapter
29
- except ImportError:
30
- SerialAdapter = None
31
22
 
32
23
  # Handy constants
33
24
  LOGGER = logging.getLogger(__name__)
34
25
 
35
26
 
36
- # Metadata regarding each plugin:
37
- _PLUGIN_METADATA = {
38
- "framing": {
39
- "class": FramerDeframer,
40
- "type": PluginType.SELECTION,
41
- "built-in": [FpFramerDeframer]
42
- },
43
- "communication": {
44
- "class": BaseAdapter,
45
- "type": PluginType.SELECTION,
46
- "built-in": [adapter for adapter in [NoneAdapter, IpAdapter, SerialAdapter] if adapter is not None]
47
- },
48
- "gds_function": {
49
- "class": GdsFunction,
50
- "type": PluginType.FEATURE,
51
- "built-in": []
52
- },
53
- "gds_app": {
54
- "class": GdsApp,
55
- "type": PluginType.FEATURE,
56
- "built-in": []
57
- }
58
- }
59
-
60
-
61
27
  class PluginException(Exception):
62
28
  pass
63
29
 
@@ -66,17 +32,23 @@ class InvalidCategoryException(PluginException):
66
32
  pass
67
33
 
68
34
 
35
+ class PluginsNotLoadedException(PluginException):
36
+ pass
37
+
38
+
69
39
  class Plugins(object):
70
40
  """GDS plugin system providing a plugin Singleton for use across the GDS
71
41
 
72
42
  GDS plugins are broken into categories (e.g. framing) that represent the key features users can adjust. Each GDS
73
43
  application will support and load the plugins for a given category.
74
44
  """
45
+
75
46
  PLUGIN_ENVIRONMENT_VARIABLE = "FPRIME_GDS_EXTRA_PLUGINS"
47
+ PLUGIN_METADATA = None
76
48
  _singleton = None
77
49
 
78
50
  def __init__(self, categories: Union[None, List] = None):
79
- """ Initialize the plugin system with specific categories
51
+ """Initialize the plugin system with specific categories
80
52
 
81
53
  Initialize the plugin system with support for the supplied categories. Only plugins for the specified categories
82
54
  will be loaded for use. Other plugins will not be available for use.
@@ -84,30 +56,41 @@ class Plugins(object):
84
56
  Args:
85
57
  categories: None for all categories otherwise a list of categories
86
58
  """
59
+ self.metadata = copy.deepcopy(self.get_plugin_metadata())
87
60
  categories = self.get_all_categories() if categories is None else categories
88
61
  self.categories = categories
89
62
  self.manager = pluggy.PluginManager(PROJECT_NAME)
90
63
 
91
64
  # Load hook specifications from only the configured categories
92
65
  for category in categories:
93
- self.manager.add_hookspecs(_PLUGIN_METADATA[category]["class"])
66
+ self.manager.add_hookspecs(self.metadata[category]["class"])
94
67
 
95
68
  # Load plugins from setuptools entrypoints and the built-in plugins (limited to category)
96
- self.manager.load_setuptools_entrypoints(PROJECT_NAME)
97
-
69
+ try:
70
+ self.manager.load_setuptools_entrypoints(PROJECT_NAME)
71
+ except Exception as e:
72
+ LOGGER.warning("Failed to load entrypoint plugins: %s", e)
98
73
  # Load plugins from environment variable specified modules
99
- for token in [token for token in os.environ.get(self.PLUGIN_ENVIRONMENT_VARIABLE, "").split(";") if token]:
74
+ for token in [
75
+ token
76
+ for token in os.environ.get(self.PLUGIN_ENVIRONMENT_VARIABLE, "").split(";")
77
+ if token
78
+ ]:
100
79
  module, class_token = token.split(":")
101
80
  try:
102
81
  imported_module = importlib.import_module(module)
103
- module_class = module if class_token == "" else getattr(imported_module, class_token, imported_module)
82
+ module_class = (
83
+ module
84
+ if class_token == ""
85
+ else getattr(imported_module, class_token, imported_module)
86
+ )
104
87
  self.register_plugin(module_class)
105
88
  except ImportError as imp:
106
89
  LOGGER.debug("Failed to load %s.%s as plugin", module, class_token)
107
90
 
108
91
  # Load built-in plugins
109
92
  for category in categories:
110
- for built_in in _PLUGIN_METADATA[category]["built-in"]:
93
+ for built_in in self.metadata[category]["built-in"]:
111
94
  self.register_plugin(built_in)
112
95
 
113
96
  def get_plugins(self, category) -> Iterable:
@@ -133,6 +116,64 @@ class Plugins(object):
133
116
  if self.validate_selection(category, plugin_class)
134
117
  ]
135
118
 
119
+ def start_loading(self, category: str):
120
+ """Start a category loading
121
+
122
+ When loading plugins via the CLI, it is imperative to distinguish between the no loaded implementors case, and
123
+ the case where loading was never attempted. This sets the variable in metadata to [] to indicate the loading
124
+ was attempted.
125
+ """
126
+ metadata = self.metadata[category]
127
+ metadata["bound_classes"] = (
128
+ metadata["bound_classes"] if "bound_classes" in metadata else []
129
+ )
130
+
131
+ def add_bound_class(self, category: str, bound_class: List[object]):
132
+ """Add class for plugin category with constructor arguments bound
133
+
134
+ Called from the plugin cli parser, this will add a class ready for zero-argument construction to the categories'
135
+ list of classes. This is the plugin class with constructor arguments bound to the cli arguments that fill those
136
+ values. For SELECTION plugins, only a single instance is allowed. For FEATURE plugins, multiple bound classes
137
+ are allowed.
138
+
139
+ Args:
140
+ category: category to set
141
+ bound_class: constructor argument bound class
142
+ """
143
+ self.start_loading(category)
144
+ metadata = self.metadata[category]
145
+ metadata["bound_classes"].append(bound_class)
146
+ assert (
147
+ metadata["type"] == PluginType.FEATURE
148
+ or len(metadata["bound_classes"]) == 1
149
+ ), f"Multiple selections for: {category}"
150
+
151
+ def get_selected_class(self, category: str) -> object:
152
+ """Get the selected constructor-bound class for the category"""
153
+ metadata = self.metadata[category]
154
+ assert (
155
+ metadata["type"] == PluginType.SELECTION
156
+ ), "Features allow multiple plugins"
157
+ try:
158
+ return metadata["bound_classes"][0]
159
+ except (KeyError, IndexError):
160
+ raise PluginsNotLoadedException(
161
+ f"Plugins not loaded for category: {category}"
162
+ )
163
+
164
+ def get_feature_classes(self, category: str) -> object:
165
+ """Get the selected instance for the category"""
166
+ metadata = self.metadata[category]
167
+ assert (
168
+ metadata["type"] == PluginType.FEATURE
169
+ ), "Selections have single instances"
170
+ try:
171
+ return metadata["bound_classes"]
172
+ except KeyError:
173
+ raise PluginsNotLoadedException(
174
+ f"Plugins not loaded for category: {category}"
175
+ )
176
+
136
177
  def register_plugin(self, module_or_class):
137
178
  """Register a plugin directly
138
179
 
@@ -144,27 +185,97 @@ class Plugins(object):
144
185
  self.manager.register(module_or_class)
145
186
 
146
187
  def get_categories(self):
147
- """ Get plugin categories """
188
+ """Get plugin categories"""
148
189
  return self.categories
149
190
 
150
- @staticmethod
151
- def get_all_categories():
152
- """ Get all plugin categories """
153
- return _PLUGIN_METADATA.keys()
191
+ @classmethod
192
+ def get_all_categories(cls):
193
+ """Get all plugin categories"""
194
+ return cls.get_plugin_metadata().keys()
195
+
196
+ @classmethod
197
+ def get_plugin_metadata(cls, category: str = None):
198
+ """Get the metadata describing a given category
199
+
200
+ F Prime supports certain plugin types that break down into categories. Each category has a set of built-in
201
+ plugins class type, and plugin type. This function will load that metadata for the supplied category. If no
202
+ category is supplied then the complete metadata block is returned.
203
+
204
+ On first invocation, the method will load plugin types and construct the metadata object.
154
205
 
155
- @staticmethod
156
- def get_plugin_metadata(category):
157
- """ Get the plugin metadata for a given plugin category """
158
- return _PLUGIN_METADATA[category]
206
+ Warning:
207
+ Since the loaded plugins may use features of the plugin system, these plugins must be imported at call time
208
+ instead of imported at the top of the module to prevent circular imports.
209
+
210
+ Return:
211
+ plugin metadata definitions
212
+ """
213
+ # Load on the first time only
214
+ if cls.PLUGIN_METADATA is None:
215
+ from fprime_gds.executables.apps import GdsFunction, GdsApp
216
+ from fprime_gds.common.handlers import DataHandlerPlugin
217
+ from fprime_gds.common.communication.framing import (
218
+ FramerDeframer,
219
+ FpFramerDeframer,
220
+ )
221
+ from fprime_gds.common.communication.ccsds.chain import SpacePacketSpaceDataLinkFramerDeframer
222
+ from fprime_gds.common.communication.adapters.base import (
223
+ BaseAdapter,
224
+ NoneAdapter,
225
+ )
226
+ from fprime_gds.common.communication.adapters.ip import IpAdapter
227
+ from fprime_gds.executables.apps import CustomDataHandlers
228
+
229
+ try:
230
+ from fprime_gds.common.communication.adapters.uart import SerialAdapter
231
+ except ImportError:
232
+ SerialAdapter = None
233
+ cls.PLUGIN_METADATA = {
234
+ "framing": {
235
+ "class": FramerDeframer,
236
+ "type": PluginType.SELECTION,
237
+ "built-in": [FpFramerDeframer, SpacePacketSpaceDataLinkFramerDeframer],
238
+ },
239
+ "communication": {
240
+ "class": BaseAdapter,
241
+ "type": PluginType.SELECTION,
242
+ "built-in": [
243
+ adapter
244
+ for adapter in [NoneAdapter, IpAdapter, SerialAdapter]
245
+ if adapter is not None
246
+ ],
247
+ },
248
+ "gds_function": {
249
+ "class": GdsFunction,
250
+ "type": PluginType.FEATURE,
251
+ "built-in": [],
252
+ },
253
+ "gds_app": {
254
+ "class": GdsApp,
255
+ "type": PluginType.FEATURE,
256
+ "built-in": [CustomDataHandlers],
257
+ },
258
+ "data_handler": {
259
+ "class": DataHandlerPlugin,
260
+ "type": PluginType.FEATURE,
261
+ "built-in": [],
262
+ },
263
+ }
264
+ assert cls.PLUGIN_METADATA is not None, "Failed to set plugin metadata"
265
+ return (
266
+ cls.PLUGIN_METADATA[category]
267
+ if category is not None
268
+ else cls.PLUGIN_METADATA
269
+ )
159
270
 
160
271
  @classmethod
161
272
  def get_category_plugin_type(cls, category):
162
- """ Get the plugin type given the category """
273
+ """Get the plugin type given the category"""
163
274
  return cls.get_plugin_metadata(category)["type"]
164
275
 
165
276
  @classmethod
166
277
  def get_category_specification_class(cls, category):
167
- """ Get the plugin class given the category """
278
+ """Get the plugin class given the category"""
168
279
  return cls.get_plugin_metadata(category)["class"]
169
280
 
170
281
  @classmethod
@@ -204,7 +315,7 @@ class Plugins(object):
204
315
 
205
316
  @classmethod
206
317
  def system(cls, categories: Union[None, List] = None) -> "Plugins":
207
- """ Get plugin system singleton
318
+ """Get plugin system singleton
208
319
 
209
320
  Constructs the plugin system singleton (when it has yet to be constructed) then returns the singleton. The
210
321
  singleton will support specific categories and further requests for a singleton will cause an assertion error
@@ -218,8 +329,11 @@ class Plugins(object):
218
329
  """
219
330
  # Singleton undefined, construct it
220
331
  if cls._singleton is None:
221
- cls._singleton = cls(cls.get_all_categories() if categories is None else categories)
332
+ cls._singleton = cls(
333
+ cls.get_all_categories() if categories is None else categories
334
+ )
222
335
  # Ensure categories was unspecified or matches the singleton
223
- assert categories is None or cls._singleton.categories == categories, "Inconsistent plugin categories"
336
+ assert (
337
+ categories is None or cls._singleton.categories == categories
338
+ ), f"Inconsistent plugin categories: {categories} vs {cls._singleton.categories}"
224
339
  return cls._singleton
225
-
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: fprime-gds
3
- Version: 3.6.1
3
+ Version: 4.0.0
4
4
  Summary: F Prime Flight Software Ground Data System layer
5
5
  Author-email: Michael Starch <Michael.D.Starch@jpl.nasa.gov>, Thomas Boyer-Chammard <Thomas.Boyer.Chammard@jpl.nasa.gov>
6
6
  License:
@@ -216,15 +216,15 @@ Classifier: Operating System :: Unix
216
216
  Classifier: Operating System :: POSIX
217
217
  Classifier: Programming Language :: Python
218
218
  Classifier: Programming Language :: Python :: 3
219
- Classifier: Programming Language :: Python :: 3.8
220
219
  Classifier: Programming Language :: Python :: 3.9
221
220
  Classifier: Programming Language :: Python :: 3.10
222
221
  Classifier: Programming Language :: Python :: 3.11
223
222
  Classifier: Programming Language :: Python :: 3.12
223
+ Classifier: Programming Language :: Python :: 3.13
224
224
  Classifier: Programming Language :: Python :: Implementation :: CPython
225
225
  Classifier: Programming Language :: Python :: Implementation :: PyPy
226
226
  Classifier: License :: OSI Approved :: Apache Software License
227
- Requires-Python: >=3.8
227
+ Requires-Python: >=3.9
228
228
  Description-Content-Type: text/markdown
229
229
  License-File: LICENSE.txt
230
230
  License-File: NOTICE.txt
@@ -240,10 +240,15 @@ Requires-Dist: Jinja2>=2.11.3
240
240
  Requires-Dist: openpyxl>=3.0.10
241
241
  Requires-Dist: pyserial>=3.5
242
242
  Requires-Dist: pydantic>=2.6
243
+ Requires-Dist: lark>=1.2.2
244
+ Requires-Dist: PyYAML>=6.0.2
245
+ Requires-Dist: spacepackets>=0.30.0
246
+ Requires-Dist: crc>=7.0.0
247
+ Dynamic: license-file
243
248
 
244
249
  # F´ GDS
245
250
 
246
- **Note:** This README describes GDS internals. Refer to the [user's guide](https://nasa.github.io/fprime/UsersGuide/gds/gds-introduction.html) for instructions on how to use the GDS.
251
+ **Note:** This README describes GDS internals. Refer to the [ Documentation](https://fprime.jpl.nasa.gov/latest/docs/user-manual/overview/gds-introduction/) for instructions on how to use the GDS.
247
252
 
248
253
  Issues should be reported here: [File an issue](https://github.com/nasa/fprime/issues/new/choose)
249
254
 
@@ -268,7 +273,8 @@ output data type included. Command data objects are created in the command panel
268
273
  the command encoder registered to that panel. Encoders take a data object and turn it into binary
269
274
  data that can be sent to the F´ deployment. The binary data is then passed to the TCP client
270
275
  which is registered to the encoder. Finally, the TCP client send the data back to the TCP server and
271
- the F´ deployment. ![The layout of the GDS](https://github.com/nasa/fprime/blob/devel/docs/UsersGuide/media/gds_layout.jpg)
276
+ the F´ deployment.
277
+ ![The layout of the GDS](https://raw.githubusercontent.com/nasa/fprime/refs/heads/devel/docs/img/gds_layout.jpg)
272
278
 
273
279
  All of these objects are created and registered to other objects when the GDS
274
280
  is initialized. Thus, all of the structure of the GDS is created in one place,
@@ -285,15 +291,11 @@ commands and registering consumers to the GDS decoders. The Standard Pipeline ca
285
291
  [here](src/fprime_gds/common/pipeline/standard.py).
286
292
 
287
293
  ### GDS Integration Test API
288
- The Integration Test API is a tool that provides the ability to write integration-level tests for an
289
- F´ deployment using the GDS. The tool provides history searches/asserts, command sending, a
290
- detailed test log, sub-histories and convenient access to GDS data objects. The test API comes with
291
- separate [documentation](https://github.com/nasa/fprime/blob/master/docs/UsersGuide/dev/testAPI/markdown/contents.md) and its own [user
292
- guide](https://github.com/nasa/fprime/blob/master/docs/UsersGuide/dev/testAPI/user_guide.md) and is built on top of the Standard Pipeline.
294
+ The Integration Test API is a tool that provides the ability to write integration-level tests for an F´ deployment using the GDS. The tool provides history searches/asserts, command sending, a detailed test log, sub-histories and convenient access to GDS data objects. The test API comes with its own [user guide](https://fprime.jpl.nasa.gov/latest/docs/user-manual/gds/gds-test-api-guide/) and is built on top of the Standard Pipeline.
293
295
 
294
296
  ## GDS GUI Usage
295
297
 
296
- A guide for how to use the GDS is available in the [fprime documentation](https://nasa.github.io/fprime/UsersGuide/gds/gds-introduction.html)
298
+ A guide for how to use the GDS is available in the [F Prime documentation](https://fprime.jpl.nasa.gov/latest/docs/user-manual/overview/gds-introduction)
297
299
 
298
300
  ## Classes
299
301
  The GDS back end is composed of several different data processing units. For
@@ -316,8 +318,15 @@ that descriptor. Descriptor types include events, channels, packets, etc (a full
316
318
  enumeration can be found in (src/utils/data_desc_type.py). The binary data that
317
319
  the descriptor receives should be of the form:
318
320
 
319
- | Length (4 bytes) | Type Descriptor (4 bytes) | Message Data |
320
- | ---------------- | ------------------------- | ------------ |
321
+ ```mermaid
322
+ ---
323
+ title: "Binary format received by Distributors"
324
+ ---
325
+ packet-beta
326
+ 0-31: "Length [4 bytes]"
327
+ 32-63: "Type Descriptor [4 bytes]"
328
+ 64-95: "Message Data [variable length]"
329
+ ```
321
330
 
322
331
  The distributor should then pass only the message data along to the decoders.
323
332
 
@@ -433,10 +442,3 @@ pip install doxypypy
433
442
 
434
443
  Next, make `docs/py_filter` available in your system path however you see fit.
435
444
  Now you can run `doxygen Doxyfile` in the root directory to generate documentation in `docs/doxy/index.html`
436
-
437
- ## Notes
438
- - Currently, the models/common directory has command.py, event.py, and
439
- channel.py. These files must be present in order for the python dictionaries
440
- to be properly imported. However, they are empty and not used in the GDS.
441
- When we switch fully to XML dictionaries, these can go away.
442
-