fprime-gds 3.6.1__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/json_loader.py +15 -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 +53 -43
- 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/app.py +3 -0
- fprime_gds/flask/resource.py +5 -2
- fprime_gds/flask/static/addons/chart-display/addon.js +8 -3
- fprime_gds/flask/static/js/datastore.js +1 -0
- 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.1.dist-info → fprime_gds-4.0.0a1.dist-info}/METADATA +17 -19
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/RECORD +48 -43
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/entry_points.txt +2 -0
- fprime_gds/common/loaders/ch_py_loader.py +0 -79
- fprime_gds/common/loaders/cmd_py_loader.py +0 -66
- fprime_gds/common/loaders/event_py_loader.py +0 -75
- fprime_gds/common/loaders/python_loader.py +0 -132
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0a1.dist-info}/top_level.txt +0 -0
@@ -19,6 +19,7 @@ from fprime.common.models.serialize.type_exceptions import TypeException
|
|
19
19
|
|
20
20
|
from fprime_gds.common.data_types import event_data
|
21
21
|
from fprime_gds.common.decoders import decoder
|
22
|
+
from fprime_gds.common.decoders.decoder import DecodingException
|
22
23
|
from fprime_gds.common.utils import config_manager
|
23
24
|
|
24
25
|
|
@@ -43,7 +44,7 @@ class EventDecoder(decoder.Decoder):
|
|
43
44
|
config = config_manager.ConfigManager().get_instance()
|
44
45
|
|
45
46
|
self.__dict = event_dict
|
46
|
-
self.id_obj = config.get_type("
|
47
|
+
self.id_obj = config.get_type("FwEventIdType")
|
47
48
|
|
48
49
|
def decode_api(self, data):
|
49
50
|
"""
|
@@ -57,7 +57,7 @@ class Distributor(DataHandler):
|
|
57
57
|
self.key_frame = int(config.get("framing", "key_val"), 16)
|
58
58
|
self.key_obj = config.get_type("key_val")
|
59
59
|
self.len_obj = config.get_type("msg_len")
|
60
|
-
self.desc_obj = config.get_type("
|
60
|
+
self.desc_obj = config.get_type("FwPacketDescriptorType")
|
61
61
|
|
62
62
|
# NOTE we could use either the type of the object or an enum as the type argument.
|
63
63
|
# It should indicate what the decoder decodes.
|
@@ -204,4 +204,4 @@ class Distributor(DataHandler):
|
|
204
204
|
try:
|
205
205
|
d.data_callback(msg)
|
206
206
|
except DecodingException as dexc:
|
207
|
-
LOGGER.warning("Decoding error occurred: %s. Skipping.", dexc)
|
207
|
+
LOGGER.warning("Decoding error occurred: %s. Skipping.", dexc)
|
@@ -57,8 +57,8 @@ class ChEncoder(Encoder):
|
|
57
57
|
super().__init__(config)
|
58
58
|
|
59
59
|
self.len_obj = self.config.get_type("msg_len")
|
60
|
-
self.desc_obj = self.config.get_type("
|
61
|
-
self.id_obj = self.config.get_type("
|
60
|
+
self.desc_obj = self.config.get_type("FwPacketDescriptorType")
|
61
|
+
self.id_obj = self.config.get_type("FwChanIdType")
|
62
62
|
|
63
63
|
def encode_api(self, data):
|
64
64
|
"""
|
@@ -72,8 +72,8 @@ class CmdEncoder(encoder.Encoder):
|
|
72
72
|
super().__init__(config)
|
73
73
|
|
74
74
|
self.len_obj = self.config.get_type("msg_len")
|
75
|
-
self.desc_obj = self.config.get_type("
|
76
|
-
self.opcode_obj = self.config.get_type("
|
75
|
+
self.desc_obj = self.config.get_type("FwPacketDescriptorType")
|
76
|
+
self.opcode_obj = self.config.get_type("FwOpcodeType")
|
77
77
|
|
78
78
|
def encode_api(self, data):
|
79
79
|
"""
|
@@ -61,8 +61,8 @@ class EventEncoder(Encoder):
|
|
61
61
|
super().__init__(config)
|
62
62
|
|
63
63
|
self.len_obj = self.config.get_type("msg_len")
|
64
|
-
self.desc_obj = self.config.get_type("
|
65
|
-
self.id_obj = self.config.get_type("
|
64
|
+
self.desc_obj = self.config.get_type("FwPacketDescriptorType")
|
65
|
+
self.id_obj = self.config.get_type("FwEventIdType")
|
66
66
|
|
67
67
|
def encode_api(self, data):
|
68
68
|
"""
|
@@ -61,8 +61,8 @@ class PktEncoder(Encoder):
|
|
61
61
|
super().__init__(config)
|
62
62
|
|
63
63
|
self.len_obj = self.config.get_type("msg_len")
|
64
|
-
self.desc_obj = self.config.get_type("
|
65
|
-
self.id_obj = self.config.get_type("
|
64
|
+
self.desc_obj = self.config.get_type("FwPacketDescriptorType")
|
65
|
+
self.id_obj = self.config.get_type("FwTlmPacketizeIdType")
|
66
66
|
|
67
67
|
def encode_api(self, data):
|
68
68
|
"""
|
@@ -28,8 +28,8 @@ class SeqBinaryWriter:
|
|
28
28
|
|
29
29
|
self.__fd = None
|
30
30
|
self.__timebase = timebase
|
31
|
-
self.desc_obj = config.get_type("
|
32
|
-
self.opcode_obj = config.get_type("
|
31
|
+
self.desc_obj = config.get_type("FwPacketDescriptorType")
|
32
|
+
self.opcode_obj = config.get_type("FwOpcodeType")
|
33
33
|
self.len_obj = config.get_type("msg_len")
|
34
34
|
|
35
35
|
def open(self, filename):
|
File without changes
|
@@ -0,0 +1,229 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from dataclasses import astuple
|
3
|
+
import inspect
|
4
|
+
import json
|
5
|
+
from pathlib import Path
|
6
|
+
from argparse import ArgumentParser
|
7
|
+
import struct
|
8
|
+
import zlib
|
9
|
+
from fprime_gds.common.fpy.types import (
|
10
|
+
StatementTemplate,
|
11
|
+
StatementData,
|
12
|
+
Header,
|
13
|
+
Footer,
|
14
|
+
HEADER_FORMAT,
|
15
|
+
FOOTER_FORMAT,
|
16
|
+
StatementType,
|
17
|
+
FPY_DIRECTIVES,
|
18
|
+
BytecodeParseContext,
|
19
|
+
get_type_obj_for,
|
20
|
+
)
|
21
|
+
from fprime_gds.common.loaders.ch_json_loader import ChJsonLoader
|
22
|
+
from fprime_gds.common.loaders.cmd_json_loader import CmdJsonLoader
|
23
|
+
from fprime.common.models.serialize.numerical_types import (
|
24
|
+
U8Type,
|
25
|
+
)
|
26
|
+
|
27
|
+
from fprime_gds.common.loaders.prm_json_loader import PrmJsonLoader
|
28
|
+
|
29
|
+
|
30
|
+
def serialize_statement(stmt: StatementData) -> bytes:
|
31
|
+
"""converts a StatementData object into bytes that the FpySequencer can read"""
|
32
|
+
# see https://github.com/nasa/fprime/issues/3023#issuecomment-2693051677
|
33
|
+
# TODO replace this with actual documentation
|
34
|
+
|
35
|
+
# type: U8 (0 if directive, 1 if cmd)
|
36
|
+
# opcode: FwOpcodeType (default U32)
|
37
|
+
# argBufSize: FwSizeStoreType (default U16)
|
38
|
+
# argBuf: X bytes
|
39
|
+
|
40
|
+
output = bytes()
|
41
|
+
output += U8Type(stmt.template.statement_type.value).serialize()
|
42
|
+
output += get_type_obj_for("FwOpcodeType")(stmt.template.opcode).serialize()
|
43
|
+
|
44
|
+
arg_bytes = bytes()
|
45
|
+
for arg in stmt.arg_values:
|
46
|
+
arg_bytes += arg.serialize()
|
47
|
+
|
48
|
+
output += get_type_obj_for("FwSizeStoreType")(len(arg_bytes)).serialize()
|
49
|
+
output += arg_bytes
|
50
|
+
|
51
|
+
return output
|
52
|
+
|
53
|
+
|
54
|
+
def parse_str_as_statement(
|
55
|
+
stmt: str, templates: list[StatementTemplate], context: BytecodeParseContext
|
56
|
+
) -> StatementData:
|
57
|
+
"""Converts a human-readable line of bytecode into a StatementData instance, given a list of
|
58
|
+
possible statement templates"""
|
59
|
+
name = stmt.split()[0]
|
60
|
+
args = stmt[len(name) :]
|
61
|
+
|
62
|
+
args = json.loads("[" + args + "]")
|
63
|
+
|
64
|
+
matching_template = [t for t in templates if t.name == name]
|
65
|
+
if len(matching_template) != 1:
|
66
|
+
# no unique match
|
67
|
+
if len(matching_template) == 0:
|
68
|
+
raise RuntimeError("Could not find command or directive " + str(name))
|
69
|
+
raise RuntimeError(
|
70
|
+
"Found multiple commands or directives with name " + str(name)
|
71
|
+
)
|
72
|
+
matching_template = matching_template[0]
|
73
|
+
|
74
|
+
arg_values = []
|
75
|
+
if len(args) < len(matching_template.args):
|
76
|
+
raise RuntimeError(
|
77
|
+
"Missing arguments for statement "
|
78
|
+
+ str(matching_template.name)
|
79
|
+
+ ": "
|
80
|
+
+ str(matching_template.args[len(args) :])
|
81
|
+
)
|
82
|
+
if len(args) > len(matching_template.args):
|
83
|
+
raise RuntimeError(
|
84
|
+
"Extra arguments for"
|
85
|
+
+ str(matching_template.name)
|
86
|
+
+ ": "
|
87
|
+
+ str(args[len(matching_template.args) :])
|
88
|
+
)
|
89
|
+
for index, arg_json in enumerate(args):
|
90
|
+
arg_type = matching_template.args[index]
|
91
|
+
if inspect.isclass(arg_type):
|
92
|
+
# it's a type. instantiate it with the json
|
93
|
+
arg_value = arg_type(arg_json)
|
94
|
+
else:
|
95
|
+
# it's a function. give it the json and the ctx
|
96
|
+
arg_value = arg_type(arg_json, context)
|
97
|
+
arg_values.append(arg_value)
|
98
|
+
|
99
|
+
return StatementData(matching_template, arg_values)
|
100
|
+
|
101
|
+
|
102
|
+
def main():
|
103
|
+
arg_parser = ArgumentParser()
|
104
|
+
arg_parser.add_argument(
|
105
|
+
"input", type=Path, help="The path to the input .fpybc file"
|
106
|
+
)
|
107
|
+
|
108
|
+
arg_parser.add_argument(
|
109
|
+
"-d",
|
110
|
+
"--dictionary",
|
111
|
+
type=Path,
|
112
|
+
help="The JSON topology dictionary to compile against",
|
113
|
+
required=True,
|
114
|
+
)
|
115
|
+
|
116
|
+
arg_parser.add_argument(
|
117
|
+
"-o",
|
118
|
+
"--output",
|
119
|
+
type=Path,
|
120
|
+
help="The output .bin file path. Defaults to the input file path with a .bin extension",
|
121
|
+
default=None,
|
122
|
+
)
|
123
|
+
|
124
|
+
args = arg_parser.parse_args()
|
125
|
+
|
126
|
+
if not args.input.exists():
|
127
|
+
print("Input file", args.input, "does not exist")
|
128
|
+
exit(1)
|
129
|
+
|
130
|
+
if not args.dictionary.exists():
|
131
|
+
print("Dictionary file", args.dictionary, "does not exist")
|
132
|
+
exit(1)
|
133
|
+
|
134
|
+
serialize_bytecode(args.input, args.dictionary, args.output)
|
135
|
+
|
136
|
+
|
137
|
+
def serialize_bytecode(input: Path, dictionary: Path, output: Path = None):
|
138
|
+
"""Given an input .fpybc file, and a dictionary .json file, converts the
|
139
|
+
bytecode file into binary and writes it to the output file. If the output file
|
140
|
+
is None, writes it to the input file with a .bin extension"""
|
141
|
+
cmd_json_dict_loader = CmdJsonLoader(str(dictionary))
|
142
|
+
(_, cmd_name_dict, _) = cmd_json_dict_loader.construct_dicts(
|
143
|
+
str(dictionary)
|
144
|
+
)
|
145
|
+
|
146
|
+
stmt_templates = []
|
147
|
+
stmt_templates.extend(FPY_DIRECTIVES)
|
148
|
+
for cmd_template in cmd_name_dict.values():
|
149
|
+
stmt_template = StatementTemplate(
|
150
|
+
StatementType.CMD,
|
151
|
+
cmd_template.opcode,
|
152
|
+
cmd_template.get_full_name(),
|
153
|
+
[arg[2] for arg in cmd_template.arguments],
|
154
|
+
)
|
155
|
+
stmt_templates.append(stmt_template)
|
156
|
+
|
157
|
+
tlm_json_loader = ChJsonLoader(str(dictionary))
|
158
|
+
(_, tlm_name_dict, _) = tlm_json_loader.construct_dicts(
|
159
|
+
str(dictionary)
|
160
|
+
)
|
161
|
+
|
162
|
+
prm_json_loader = PrmJsonLoader(str(dictionary))
|
163
|
+
(_, prm_name_dict, _) = prm_json_loader.construct_dicts(
|
164
|
+
str(dictionary)
|
165
|
+
)
|
166
|
+
|
167
|
+
context = BytecodeParseContext()
|
168
|
+
context.types = cmd_json_dict_loader.parsed_types
|
169
|
+
context.channels = tlm_name_dict
|
170
|
+
context.params = prm_name_dict
|
171
|
+
|
172
|
+
input_lines = input.read_text().splitlines()
|
173
|
+
input_lines = [line.strip() for line in input_lines]
|
174
|
+
# remove comments and empty lines
|
175
|
+
input_lines = [
|
176
|
+
line for line in input_lines if not line.startswith(";") and len(line) > 0
|
177
|
+
]
|
178
|
+
|
179
|
+
goto_tags = {}
|
180
|
+
stmt_idx = 0
|
181
|
+
statement_strs: list[str] = []
|
182
|
+
for stmt in input_lines:
|
183
|
+
if stmt.endswith(":"):
|
184
|
+
# it's a goto tag
|
185
|
+
goto_tags[stmt[:-1]] = stmt_idx
|
186
|
+
else:
|
187
|
+
statement_strs.append(stmt)
|
188
|
+
stmt_idx += 1
|
189
|
+
|
190
|
+
context.goto_tags = goto_tags
|
191
|
+
|
192
|
+
statements: list[StatementData] = []
|
193
|
+
for stmt_idx, stmt in enumerate(statement_strs):
|
194
|
+
try:
|
195
|
+
stmt_data = parse_str_as_statement(stmt, stmt_templates, context)
|
196
|
+
statements.append(stmt_data)
|
197
|
+
except BaseException as e:
|
198
|
+
raise RuntimeError(
|
199
|
+
"Exception while parsing statement index " + str(stmt_idx) + ": " + stmt
|
200
|
+
) from e
|
201
|
+
|
202
|
+
# perform some checks for things we know will fail
|
203
|
+
for stmt in statements:
|
204
|
+
if stmt.template.name == "GOTO":
|
205
|
+
if stmt.arg_values[0].val > len(statements):
|
206
|
+
raise RuntimeError(
|
207
|
+
f"GOTO index is outside the valid range for this sequence (was {stmt.arg_values[0].val}, should be <{len(statements)})"
|
208
|
+
)
|
209
|
+
|
210
|
+
output_bytes = bytes()
|
211
|
+
|
212
|
+
for stmt in statements:
|
213
|
+
output_bytes += serialize_statement(stmt)
|
214
|
+
|
215
|
+
header = Header(0, 0, 0, 1, 0, len(statements), len(output_bytes))
|
216
|
+
output_bytes = struct.pack(HEADER_FORMAT, *astuple(header)) + output_bytes
|
217
|
+
|
218
|
+
crc = zlib.crc32(output_bytes) % (1 << 32)
|
219
|
+
footer = Footer(crc)
|
220
|
+
output_bytes += struct.pack(FOOTER_FORMAT, *astuple(footer))
|
221
|
+
|
222
|
+
if output is None:
|
223
|
+
output = input.with_suffix(".bin")
|
224
|
+
|
225
|
+
output.write_bytes(output_bytes)
|
226
|
+
|
227
|
+
|
228
|
+
if __name__ == "__main__":
|
229
|
+
main()
|
@@ -0,0 +1,203 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from enum import Enum
|
4
|
+
import struct
|
5
|
+
from typing import Any, Callable
|
6
|
+
from fprime.common.models.serialize.type_base import BaseType, ValueType
|
7
|
+
from fprime.common.models.serialize.time_type import TimeType
|
8
|
+
from fprime.common.models.serialize.numerical_types import U32Type, U16Type, U8Type
|
9
|
+
from fprime.common.models.serialize.string_type import StringType
|
10
|
+
|
11
|
+
from fprime_gds.common.loaders.json_loader import PRIMITIVE_TYPE_MAP
|
12
|
+
from fprime_gds.common.templates.ch_template import ChTemplate
|
13
|
+
from fprime_gds.common.templates.prm_template import PrmTemplate
|
14
|
+
|
15
|
+
|
16
|
+
def get_type_obj_for(type: str) -> type[ValueType]:
|
17
|
+
if type == "FwOpcodeType":
|
18
|
+
return U32Type
|
19
|
+
elif type == "FwSizeStoreType":
|
20
|
+
return U16Type
|
21
|
+
elif type == "FwChanIdType":
|
22
|
+
return U32Type
|
23
|
+
elif type == "FwPrmIdType":
|
24
|
+
return U32Type
|
25
|
+
|
26
|
+
raise RuntimeError("Unknown FPrime type alias " + str(type))
|
27
|
+
|
28
|
+
|
29
|
+
class StatementType(Enum):
|
30
|
+
DIRECTIVE = 0
|
31
|
+
CMD = 1
|
32
|
+
|
33
|
+
|
34
|
+
@dataclass
|
35
|
+
class StatementTemplate:
|
36
|
+
"""a statement with unspecified argument values"""
|
37
|
+
|
38
|
+
statement_type: StatementType
|
39
|
+
opcode: int
|
40
|
+
name: str
|
41
|
+
"""fully qualified statement name"""
|
42
|
+
args: list[type[BaseType] | Callable[[Any, BytecodeParseContext], BaseType]]
|
43
|
+
"""list of argument types of this statement, or functions that return an arg type"""
|
44
|
+
|
45
|
+
|
46
|
+
@dataclass
|
47
|
+
class StatementData:
|
48
|
+
template: StatementTemplate
|
49
|
+
arg_values: list[BaseType]
|
50
|
+
|
51
|
+
|
52
|
+
HEADER_FORMAT = "!BBBBBHI"
|
53
|
+
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
|
54
|
+
|
55
|
+
|
56
|
+
@dataclass
|
57
|
+
class Header:
|
58
|
+
majorVersion: int
|
59
|
+
minorVersion: int
|
60
|
+
patchVersion: int
|
61
|
+
schemaVersion: int
|
62
|
+
argumentCount: int
|
63
|
+
statementCount: int
|
64
|
+
bodySize: int
|
65
|
+
|
66
|
+
|
67
|
+
FOOTER_FORMAT = "!I"
|
68
|
+
FOOTER_SIZE = struct.calcsize(FOOTER_FORMAT)
|
69
|
+
|
70
|
+
|
71
|
+
@dataclass
|
72
|
+
class Footer:
|
73
|
+
crc: int
|
74
|
+
|
75
|
+
|
76
|
+
class DirectiveOpcode(Enum):
|
77
|
+
INVALID = 0x00000000
|
78
|
+
WAIT_REL = 0x00000001
|
79
|
+
WAIT_ABS = 0x00000002
|
80
|
+
SET_LVAR = 0x00000003
|
81
|
+
GOTO = 0x00000004
|
82
|
+
IF = 0x00000005
|
83
|
+
NO_OP = 0x00000006
|
84
|
+
GET_TLM = 0x00000007
|
85
|
+
GET_PRM = 0x00000008
|
86
|
+
|
87
|
+
|
88
|
+
@dataclass
|
89
|
+
class BytecodeParseContext:
|
90
|
+
goto_tags: map[str, int] = field(default_factory=dict)
|
91
|
+
"""a map of tag name with tag statement index"""
|
92
|
+
types: map[str, type[BaseType]] = field(default_factory=dict)
|
93
|
+
"""a map of name to all parsed types available in the dictionary"""
|
94
|
+
channels: map[str, ChTemplate] = field(default_factory=dict)
|
95
|
+
"""a map of name to ChTemplate object for all tlm channels"""
|
96
|
+
params: map[str, PrmTemplate] = field(default_factory=dict)
|
97
|
+
"""a map of name to PrmTemplate object for all prms"""
|
98
|
+
|
99
|
+
|
100
|
+
def time_type_from_json(js, ctx: BytecodeParseContext):
|
101
|
+
return TimeType(js["time_base"], js["time_context"], js["seconds"], js["useconds"])
|
102
|
+
|
103
|
+
|
104
|
+
def arbitrary_type_from_json(js, ctx: BytecodeParseContext):
|
105
|
+
type_name = js["type"]
|
106
|
+
|
107
|
+
if type_name == "string":
|
108
|
+
# by default no max size restrictions in the bytecode
|
109
|
+
return StringType.construct_type(f"String", None)(js["value"])
|
110
|
+
|
111
|
+
# try first checking parsed_types, then check primitive types
|
112
|
+
type_class = ctx.types.get(type_name, PRIMITIVE_TYPE_MAP.get(type_name, None))
|
113
|
+
if type_class is None:
|
114
|
+
raise RuntimeError("Unknown type " + str(type_name))
|
115
|
+
|
116
|
+
return type_class(js["value"])
|
117
|
+
|
118
|
+
|
119
|
+
def goto_tag_or_idx_from_json(js, ctx: BytecodeParseContext):
|
120
|
+
if isinstance(js, str):
|
121
|
+
# it's a tag
|
122
|
+
if js not in ctx.goto_tags:
|
123
|
+
raise RuntimeError("Unknown goto tag " + str(js))
|
124
|
+
return U32Type(ctx.goto_tags[js])
|
125
|
+
|
126
|
+
# otherwise it is a statement index
|
127
|
+
return U32Type(js)
|
128
|
+
|
129
|
+
|
130
|
+
def tlm_chan_id_from_json(js, ctx: BytecodeParseContext):
|
131
|
+
if isinstance(js, str):
|
132
|
+
if js not in ctx.channels:
|
133
|
+
raise RuntimeError("Unknown telemetry channel " + str(js))
|
134
|
+
return get_type_obj_for("FwChanIdType")(ctx.channels[js].id)
|
135
|
+
elif isinstance(js, int):
|
136
|
+
matching = [tmp for tmp in ctx.channels.keys() if tmp.id == js]
|
137
|
+
if len(matching) != 1:
|
138
|
+
if len(matching) == 0:
|
139
|
+
raise RuntimeError("Unknown telemetry channel id " + str(js))
|
140
|
+
raise RuntimeError("Multiple matches for telemetry channel id " + str(js))
|
141
|
+
matching = matching[0]
|
142
|
+
return get_type_obj_for("FwChanIdType")(matching.id)
|
143
|
+
|
144
|
+
|
145
|
+
def prm_id_from_json(js, ctx: BytecodeParseContext):
|
146
|
+
if isinstance(js, str):
|
147
|
+
if js not in ctx.params:
|
148
|
+
raise RuntimeError("Unknown parameter " + str(js))
|
149
|
+
return get_type_obj_for("FwPrmIdType")(ctx.params[js].prm_id)
|
150
|
+
elif isinstance(js, int):
|
151
|
+
matching = [tmp for tmp in ctx.params.keys() if tmp.prm_id == js]
|
152
|
+
if len(matching) != 1:
|
153
|
+
if len(matching) == 0:
|
154
|
+
raise RuntimeError("Unknown param id " + str(js))
|
155
|
+
raise RuntimeError("Multiple matches for param id " + str(js))
|
156
|
+
matching = matching[0]
|
157
|
+
return get_type_obj_for("FwPrmIdType")(matching.prm_id)
|
158
|
+
|
159
|
+
|
160
|
+
FPY_DIRECTIVES: list[StatementTemplate] = [
|
161
|
+
StatementTemplate(
|
162
|
+
StatementType.DIRECTIVE,
|
163
|
+
DirectiveOpcode.WAIT_REL.value,
|
164
|
+
"WAIT_REL",
|
165
|
+
[U32Type, U32Type],
|
166
|
+
),
|
167
|
+
StatementTemplate(
|
168
|
+
StatementType.DIRECTIVE,
|
169
|
+
DirectiveOpcode.WAIT_ABS.value,
|
170
|
+
"WAIT_ABS",
|
171
|
+
[time_type_from_json],
|
172
|
+
),
|
173
|
+
StatementTemplate(
|
174
|
+
StatementType.DIRECTIVE,
|
175
|
+
DirectiveOpcode.SET_LVAR.value,
|
176
|
+
"SET_LVAR",
|
177
|
+
[U8Type, arbitrary_type_from_json],
|
178
|
+
),
|
179
|
+
StatementTemplate(
|
180
|
+
StatementType.DIRECTIVE,
|
181
|
+
DirectiveOpcode.GOTO.value,
|
182
|
+
"GOTO",
|
183
|
+
[goto_tag_or_idx_from_json],
|
184
|
+
),
|
185
|
+
StatementTemplate(
|
186
|
+
StatementType.DIRECTIVE,
|
187
|
+
DirectiveOpcode.IF.value,
|
188
|
+
"IF",
|
189
|
+
[U8Type, goto_tag_or_idx_from_json],
|
190
|
+
),
|
191
|
+
StatementTemplate(
|
192
|
+
StatementType.DIRECTIVE,
|
193
|
+
DirectiveOpcode.GET_TLM.value,
|
194
|
+
"GET_TLM",
|
195
|
+
[U8Type, U8Type, tlm_chan_id_from_json],
|
196
|
+
),
|
197
|
+
StatementTemplate(
|
198
|
+
StatementType.DIRECTIVE,
|
199
|
+
DirectiveOpcode.GET_PRM.value,
|
200
|
+
"GET_PRM",
|
201
|
+
[U8Type, prm_id_from_json],
|
202
|
+
),
|
203
|
+
]
|
@@ -130,7 +130,7 @@ class BaseCommand(abc.ABC):
|
|
130
130
|
passes the given filter
|
131
131
|
"""
|
132
132
|
project_dictionary = Dictionaries()
|
133
|
-
project_dictionary.load_dictionaries(dictionary_path, packet_spec=None)
|
133
|
+
project_dictionary.load_dictionaries(dictionary_path, packet_spec=None, packet_set_name=None)
|
134
134
|
items = cls._get_item_list(project_dictionary, search_filter)
|
135
135
|
return cls._get_item_list_string(items, json)
|
136
136
|
|
fprime_gds/common/handlers.py
CHANGED
@@ -6,7 +6,10 @@ defines the "DataHandler" base class for handling data.
|
|
6
6
|
|
7
7
|
@author mstarch
|
8
8
|
"""
|
9
|
+
|
9
10
|
import abc
|
11
|
+
from typing import List, Type
|
12
|
+
from fprime_gds.plugin.definitions import gds_plugin_specification
|
10
13
|
|
11
14
|
|
12
15
|
class DataHandler(abc.ABC):
|
@@ -27,6 +30,42 @@ class DataHandler(abc.ABC):
|
|
27
30
|
"""
|
28
31
|
|
29
32
|
|
33
|
+
class DataHandlerPlugin(DataHandler, abc.ABC):
|
34
|
+
"""PLugin class allowing for custom data handlers
|
35
|
+
|
36
|
+
This class acts as a DataHandler class with the addition that it can be used as a plugin and thus self reports the
|
37
|
+
data types it handles (whereas DataHandler leaves that up to the registration call). Users shall concretely subclass
|
38
|
+
this class with their own data handling functionality.
|
39
|
+
"""
|
40
|
+
|
41
|
+
@abc.abstractmethod
|
42
|
+
def get_handled_descriptors() -> List[str]:
|
43
|
+
"""Return a list of data descriptor names this plugin handles"""
|
44
|
+
raise NotImplementedError()
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
@gds_plugin_specification
|
48
|
+
def register_data_handler_plugin(cls) -> Type["DataHandlerPlugin"]:
|
49
|
+
"""Register a plugin to provide post-decoding data handling capabilities
|
50
|
+
|
51
|
+
Plugin hook for registering a plugin that supplies a DataHandler implementation. Implementors of this hook must
|
52
|
+
return a non-abstract subclass of DataHandlerPlugin. This class will be provided as a data handling
|
53
|
+
that is automatically enabled. Users may disable this via the command line. This data handler will be supplied
|
54
|
+
all data types returned by the `get_data_types()` method.
|
55
|
+
|
56
|
+
This DataHandler will run within the standard GDS (UI) process. Users wanting a separate process shall use a
|
57
|
+
GdsApp plugin instead.
|
58
|
+
|
59
|
+
Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
|
60
|
+
determined from class methods, solicited via the command line, and provided at construction time to the chosen
|
61
|
+
instantiation.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
DataHandlerPlugin subclass (not instance)
|
65
|
+
"""
|
66
|
+
raise NotImplementedError()
|
67
|
+
|
68
|
+
|
30
69
|
class HandlerRegistrar(abc.ABC):
|
31
70
|
"""
|
32
71
|
Defines a class that will take in registrants and remember them for calling back later. These objects should be of
|
@@ -0,0 +1,54 @@
|
|
1
|
+
"""
|
2
|
+
fw_type_json_loader.py:
|
3
|
+
|
4
|
+
Loads flight dictionary (JSON) and returns name based Python dictionaries of Fw types
|
5
|
+
|
6
|
+
@author jawest
|
7
|
+
"""
|
8
|
+
|
9
|
+
from fprime_gds.common.loaders.json_loader import JsonLoader
|
10
|
+
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
|
11
|
+
|
12
|
+
class FwTypeJsonLoader(JsonLoader):
|
13
|
+
"""Class to load python based Fw type dictionaries"""
|
14
|
+
|
15
|
+
TYPE_DEFINITIONS_FIELD = "typeDefinitions"
|
16
|
+
|
17
|
+
def construct_dicts(self, _):
|
18
|
+
"""
|
19
|
+
Constructs and returns python dictionaries keyed on id and name
|
20
|
+
|
21
|
+
Args:
|
22
|
+
_: Unused argument (inherited)
|
23
|
+
Returns:
|
24
|
+
A tuple with two Fw type dictionaries (python type dict):
|
25
|
+
(id_dict, name_dict). The keys should be the type id and
|
26
|
+
name fields respectively and the values should be type name
|
27
|
+
strings. Note: An empty id dictionary is returned since there
|
28
|
+
are no id fields in the Fw type alias JSON dictionary entries.
|
29
|
+
"""
|
30
|
+
id_dict = {}
|
31
|
+
name_dict = {}
|
32
|
+
|
33
|
+
if self.TYPE_DEFINITIONS_FIELD not in self.json_dict:
|
34
|
+
raise GdsDictionaryParsingException(
|
35
|
+
f"Ground Dictionary missing '{self.TYPE_DEFINITIONS_FIELD}' field: {str(self.json_file)}"
|
36
|
+
)
|
37
|
+
|
38
|
+
for type_def in self.json_dict[self.TYPE_DEFINITIONS_FIELD]:
|
39
|
+
try:
|
40
|
+
if type_def["kind"] == "alias":
|
41
|
+
name = str(type_def["qualifiedName"])
|
42
|
+
# Only consider names with the pattern Fw*Type
|
43
|
+
if name.startswith("Fw") and name.endswith("Type"):
|
44
|
+
name_dict[type_def["qualifiedName"]] = type_def["underlyingType"]["name"]
|
45
|
+
except KeyError as e:
|
46
|
+
raise GdsDictionaryParsingException(
|
47
|
+
f"{str(e)} key missing from Type Definition dictionary entry: {str(type_def)}"
|
48
|
+
)
|
49
|
+
|
50
|
+
return (
|
51
|
+
dict(sorted(id_dict.items())),
|
52
|
+
dict(sorted(name_dict.items())),
|
53
|
+
self.get_versions(),
|
54
|
+
)
|