fprime-gds 3.6.2a1__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.
- fprime_gds/common/communication/adapters/ip.py +14 -9
- fprime_gds/common/communication/adapters/uart.py +34 -25
- fprime_gds/common/communication/ccsds/__init__.py +0 -0
- fprime_gds/common/communication/ccsds/apid.py +19 -0
- fprime_gds/common/communication/ccsds/chain.py +106 -0
- fprime_gds/common/communication/ccsds/space_data_link.py +196 -0
- fprime_gds/common/communication/ccsds/space_packet.py +129 -0
- fprime_gds/common/communication/framing.py +27 -32
- fprime_gds/common/decoders/ch_decoder.py +1 -1
- fprime_gds/common/decoders/event_decoder.py +9 -2
- fprime_gds/common/decoders/pkt_decoder.py +1 -1
- fprime_gds/common/distributor/distributor.py +6 -3
- 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/README.md +56 -0
- fprime_gds/common/fpy/SPEC.md +69 -0
- fprime_gds/common/fpy/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/directives.py +490 -0
- fprime_gds/common/fpy/codegen.py +1687 -0
- fprime_gds/common/fpy/grammar.lark +88 -0
- fprime_gds/common/fpy/main.py +40 -0
- fprime_gds/common/fpy/parser.py +239 -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 +125 -0
- fprime_gds/common/loaders/prm_json_loader.py +85 -0
- fprime_gds/common/logger/__init__.py +2 -2
- fprime_gds/common/pipeline/dictionaries.py +28 -2
- 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/cmd_template.py +8 -0
- fprime_gds/common/templates/prm_template.py +81 -0
- fprime_gds/common/testing_fw/api.py +148 -1
- fprime_gds/common/testing_fw/pytest_integration.py +37 -3
- 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/common/utils/data_desc_type.py +6 -1
- fprime_gds/executables/apps.py +189 -11
- fprime_gds/executables/cli.py +468 -127
- fprime_gds/executables/comm.py +5 -2
- fprime_gds/executables/data_product_writer.py +164 -165
- fprime_gds/executables/fprime_cli.py +3 -3
- fprime_gds/executables/run_deployment.py +13 -5
- 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 +172 -58
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/METADATA +23 -21
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/RECORD +61 -41
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/entry_points.txt +2 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.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,97 @@ 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.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
|
-
"""
|
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
|
-
"""
|
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
|
-
"""
|
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(
|
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
|
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.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: fprime-gds
|
3
|
-
Version:
|
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.
|
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 [
|
251
|
+
**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
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.
|
276
|
+
the F´ deployment.
|
277
|
+

|
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 [
|
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
|
-
|
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
|
-
|