fprime-gds 3.6.2a1__py3-none-any.whl → 4.0.0a1__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/decoders/ch_decoder.py +1 -1
- fprime_gds/common/decoders/event_decoder.py +2 -1
- fprime_gds/common/decoders/pkt_decoder.py +1 -1
- fprime_gds/common/distributor/distributor.py +2 -2
- fprime_gds/common/encoders/ch_encoder.py +2 -2
- fprime_gds/common/encoders/cmd_encoder.py +2 -2
- fprime_gds/common/encoders/event_encoder.py +2 -2
- fprime_gds/common/encoders/pkt_encoder.py +2 -2
- fprime_gds/common/encoders/seq_writer.py +2 -2
- fprime_gds/common/fpy/__init__.py +0 -0
- fprime_gds/common/fpy/serialize_bytecode.py +229 -0
- fprime_gds/common/fpy/types.py +203 -0
- fprime_gds/common/gds_cli/base_commands.py +1 -1
- fprime_gds/common/handlers.py +39 -0
- fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
- fprime_gds/common/loaders/pkt_json_loader.py +121 -0
- fprime_gds/common/loaders/prm_json_loader.py +85 -0
- fprime_gds/common/pipeline/dictionaries.py +21 -4
- fprime_gds/common/pipeline/encoding.py +19 -0
- fprime_gds/common/pipeline/histories.py +4 -0
- fprime_gds/common/pipeline/standard.py +16 -2
- fprime_gds/common/templates/prm_template.py +81 -0
- fprime_gds/common/testing_fw/api.py +42 -0
- fprime_gds/common/testing_fw/pytest_integration.py +25 -2
- fprime_gds/common/tools/README.md +34 -0
- fprime_gds/common/tools/params.py +246 -0
- fprime_gds/common/utils/config_manager.py +6 -6
- fprime_gds/executables/apps.py +184 -11
- fprime_gds/executables/cli.py +280 -122
- fprime_gds/executables/comm.py +5 -2
- fprime_gds/executables/fprime_cli.py +3 -3
- fprime_gds/executables/run_deployment.py +10 -3
- fprime_gds/flask/static/js/vue-support/channel.js +1 -1
- fprime_gds/flask/static/js/vue-support/event.js +1 -1
- fprime_gds/plugin/definitions.py +86 -8
- fprime_gds/plugin/system.py +171 -58
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/METADATA +17 -19
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/RECORD +43 -34
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/entry_points.txt +2 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/top_level.txt +0 -0
fprime_gds/plugin/definitions.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
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
|
-
"""
|
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
|
-
"""
|
94
|
+
"""Plugin wrapper object"""
|
34
95
|
|
35
96
|
def __init__(self, category: str, plugin_type: PluginType, plugin_class: Type[Any]):
|
36
|
-
"""
|
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
|
-
"""
|
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()
|
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
|
-
"""
|
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
|
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)
|
fprime_gds/plugin/system.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
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
|
-
"""
|
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(
|
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
|
-
|
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 [
|
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 =
|
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
|
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,96 @@ class Plugins(object):
|
|
144
185
|
self.manager.register(module_or_class)
|
145
186
|
|
146
187
|
def get_categories(self):
|
147
|
-
"""
|
188
|
+
"""Get plugin categories"""
|
148
189
|
return self.categories
|
149
190
|
|
150
|
-
@
|
151
|
-
def get_all_categories():
|
152
|
-
"""
|
153
|
-
return
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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.adapters.base import (
|
222
|
+
BaseAdapter,
|
223
|
+
NoneAdapter,
|
224
|
+
)
|
225
|
+
from fprime_gds.common.communication.adapters.ip import IpAdapter
|
226
|
+
from fprime_gds.executables.apps import CustomDataHandlers
|
227
|
+
|
228
|
+
try:
|
229
|
+
from fprime_gds.common.communication.adapters.uart import SerialAdapter
|
230
|
+
except ImportError:
|
231
|
+
SerialAdapter = None
|
232
|
+
cls.PLUGIN_METADATA = {
|
233
|
+
"framing": {
|
234
|
+
"class": FramerDeframer,
|
235
|
+
"type": PluginType.SELECTION,
|
236
|
+
"built-in": [FpFramerDeframer],
|
237
|
+
},
|
238
|
+
"communication": {
|
239
|
+
"class": BaseAdapter,
|
240
|
+
"type": PluginType.SELECTION,
|
241
|
+
"built-in": [
|
242
|
+
adapter
|
243
|
+
for adapter in [NoneAdapter, IpAdapter, SerialAdapter]
|
244
|
+
if adapter is not None
|
245
|
+
],
|
246
|
+
},
|
247
|
+
"gds_function": {
|
248
|
+
"class": GdsFunction,
|
249
|
+
"type": PluginType.FEATURE,
|
250
|
+
"built-in": [],
|
251
|
+
},
|
252
|
+
"gds_app": {
|
253
|
+
"class": GdsApp,
|
254
|
+
"type": PluginType.FEATURE,
|
255
|
+
"built-in": [CustomDataHandlers],
|
256
|
+
},
|
257
|
+
"data_handler": {
|
258
|
+
"class": DataHandlerPlugin,
|
259
|
+
"type": PluginType.FEATURE,
|
260
|
+
"built-in": [],
|
261
|
+
},
|
262
|
+
}
|
263
|
+
assert cls.PLUGIN_METADATA is not None, "Failed to set plugin metadata"
|
264
|
+
return (
|
265
|
+
cls.PLUGIN_METADATA[category]
|
266
|
+
if category is not None
|
267
|
+
else cls.PLUGIN_METADATA
|
268
|
+
)
|
159
269
|
|
160
270
|
@classmethod
|
161
271
|
def get_category_plugin_type(cls, category):
|
162
|
-
"""
|
272
|
+
"""Get the plugin type given the category"""
|
163
273
|
return cls.get_plugin_metadata(category)["type"]
|
164
274
|
|
165
275
|
@classmethod
|
166
276
|
def get_category_specification_class(cls, category):
|
167
|
-
"""
|
277
|
+
"""Get the plugin class given the category"""
|
168
278
|
return cls.get_plugin_metadata(category)["class"]
|
169
279
|
|
170
280
|
@classmethod
|
@@ -204,7 +314,7 @@ class Plugins(object):
|
|
204
314
|
|
205
315
|
@classmethod
|
206
316
|
def system(cls, categories: Union[None, List] = None) -> "Plugins":
|
207
|
-
"""
|
317
|
+
"""Get plugin system singleton
|
208
318
|
|
209
319
|
Constructs the plugin system singleton (when it has yet to be constructed) then returns the singleton. The
|
210
320
|
singleton will support specific categories and further requests for a singleton will cause an assertion error
|
@@ -218,8 +328,11 @@ class Plugins(object):
|
|
218
328
|
"""
|
219
329
|
# Singleton undefined, construct it
|
220
330
|
if cls._singleton is None:
|
221
|
-
cls._singleton = cls(
|
331
|
+
cls._singleton = cls(
|
332
|
+
cls.get_all_categories() if categories is None else categories
|
333
|
+
)
|
222
334
|
# Ensure categories was unspecified or matches the singleton
|
223
|
-
assert
|
335
|
+
assert (
|
336
|
+
categories is None or cls._singleton.categories == categories
|
337
|
+
), f"Inconsistent plugin categories: {categories} vs {cls._singleton.categories}"
|
224
338
|
return cls._singleton
|
225
|
-
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: fprime-gds
|
3
|
-
Version:
|
3
|
+
Version: 4.0.0a1
|
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:
|
@@ -240,10 +240,11 @@ 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
|
+
Dynamic: license-file
|
243
244
|
|
244
245
|
# F´ GDS
|
245
246
|
|
246
|
-
**Note:** This README describes GDS internals. Refer to the [
|
247
|
+
**Note:** This README describes GDS internals. Refer to the [F´ Documentation](https://fprime.jpl.nasa.gov/latest/docs/user-manual/overview/gds-introduction/) for instructions on how to use the GDS.
|
247
248
|
|
248
249
|
Issues should be reported here: [File an issue](https://github.com/nasa/fprime/issues/new/choose)
|
249
250
|
|
@@ -268,7 +269,8 @@ output data type included. Command data objects are created in the command panel
|
|
268
269
|
the command encoder registered to that panel. Encoders take a data object and turn it into binary
|
269
270
|
data that can be sent to the F´ deployment. The binary data is then passed to the TCP client
|
270
271
|
which is registered to the encoder. Finally, the TCP client send the data back to the TCP server and
|
271
|
-
the F´ deployment.
|
272
|
+
the F´ deployment.
|
273
|
+

|
272
274
|
|
273
275
|
All of these objects are created and registered to other objects when the GDS
|
274
276
|
is initialized. Thus, all of the structure of the GDS is created in one place,
|
@@ -285,15 +287,11 @@ commands and registering consumers to the GDS decoders. The Standard Pipeline ca
|
|
285
287
|
[here](src/fprime_gds/common/pipeline/standard.py).
|
286
288
|
|
287
289
|
### 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.
|
290
|
+
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
291
|
|
294
292
|
## GDS GUI Usage
|
295
293
|
|
296
|
-
A guide for how to use the GDS is available in the [
|
294
|
+
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
295
|
|
298
296
|
## Classes
|
299
297
|
The GDS back end is composed of several different data processing units. For
|
@@ -316,8 +314,15 @@ that descriptor. Descriptor types include events, channels, packets, etc (a full
|
|
316
314
|
enumeration can be found in (src/utils/data_desc_type.py). The binary data that
|
317
315
|
the descriptor receives should be of the form:
|
318
316
|
|
319
|
-
|
320
|
-
|
317
|
+
```mermaid
|
318
|
+
---
|
319
|
+
title: "Binary format received by Distributors"
|
320
|
+
---
|
321
|
+
packet-beta
|
322
|
+
0-31: "Length [4 bytes]"
|
323
|
+
32-63: "Type Descriptor [4 bytes]"
|
324
|
+
64-95: "Message Data [variable length]"
|
325
|
+
```
|
321
326
|
|
322
327
|
The distributor should then pass only the message data along to the decoders.
|
323
328
|
|
@@ -433,10 +438,3 @@ pip install doxypypy
|
|
433
438
|
|
434
439
|
Next, make `docs/py_filter` available in your system path however you see fit.
|
435
440
|
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
|
-
|