fprime-gds 3.4.4a1__py3-none-any.whl → 3.4.4a3__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/data_types/event_data.py +6 -1
- fprime_gds/common/data_types/exceptions.py +16 -11
- fprime_gds/common/loaders/ch_json_loader.py +107 -0
- fprime_gds/common/loaders/ch_xml_loader.py +5 -5
- fprime_gds/common/loaders/cmd_json_loader.py +85 -0
- fprime_gds/common/loaders/dict_loader.py +1 -1
- fprime_gds/common/loaders/event_json_loader.py +108 -0
- fprime_gds/common/loaders/event_xml_loader.py +10 -6
- fprime_gds/common/loaders/json_loader.py +222 -0
- fprime_gds/common/loaders/xml_loader.py +31 -9
- fprime_gds/common/pipeline/dictionaries.py +38 -3
- fprime_gds/common/tools/seqgen.py +4 -4
- fprime_gds/common/utils/string_util.py +57 -65
- fprime_gds/common/zmq_transport.py +4 -2
- fprime_gds/executables/cli.py +21 -39
- fprime_gds/executables/comm.py +3 -10
- fprime_gds/executables/data_product_writer.py +935 -0
- fprime_gds/executables/utils.py +23 -11
- fprime_gds/flask/sequence.py +1 -1
- fprime_gds/flask/static/addons/commanding/command-input.js +3 -2
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/METADATA +14 -13
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/RECORD +27 -22
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/WHEEL +1 -1
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/entry_points.txt +2 -3
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/LICENSE.txt +0 -0
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/NOTICE.txt +0 -0
- {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a3.dist-info}/top_level.txt +0 -0
@@ -44,7 +44,12 @@ class EventData(sys_data.SysData):
|
|
44
44
|
self.display_text = event_temp.description
|
45
45
|
elif event_temp.format_str == "":
|
46
46
|
args_template = self.template.get_args()
|
47
|
-
self.display_text = str(
|
47
|
+
self.display_text = str(
|
48
|
+
[
|
49
|
+
{args_template[index][0]: arg.val}
|
50
|
+
for index, arg in enumerate(event_args)
|
51
|
+
]
|
52
|
+
)
|
48
53
|
else:
|
49
54
|
self.display_text = format_string_template(
|
50
55
|
event_temp.format_str, tuple([arg.val for arg in event_args])
|
@@ -2,10 +2,25 @@
|
|
2
2
|
Created on Jan 9, 2015
|
3
3
|
@author: reder
|
4
4
|
"""
|
5
|
+
|
5
6
|
# Exception classes for all controllers modules
|
6
7
|
|
7
8
|
|
8
|
-
class
|
9
|
+
class FprimeGdsException(Exception):
|
10
|
+
"""
|
11
|
+
Base Exception for exceptions that need to be handled at the system-level
|
12
|
+
by the GDS
|
13
|
+
"""
|
14
|
+
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
class GdsDictionaryParsingException(FprimeGdsException):
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
# Note: Gse is the historical name for what is now called the GDS
|
23
|
+
class GseControllerException(FprimeGdsException):
|
9
24
|
def __init__(self, val):
|
10
25
|
self.except_msg = val
|
11
26
|
super().__init__(val)
|
@@ -27,13 +42,3 @@ class GseControllerUndefinedFileException(GseControllerException):
|
|
27
42
|
class GseControllerParsingException(GseControllerException):
|
28
43
|
def __init__(self, val):
|
29
44
|
super().__init__(f"Parsing error: {str(val)}")
|
30
|
-
|
31
|
-
|
32
|
-
class GseControllerMnemonicMismatchException(GseControllerException):
|
33
|
-
def __init__(self, val1, val2):
|
34
|
-
super().__init__(f"ID mismatch ({str(val1)}, {str(val2)})!")
|
35
|
-
|
36
|
-
|
37
|
-
class GseControllerStatusUpdateException(GseControllerException):
|
38
|
-
def __init__(self, val):
|
39
|
-
super().__init__(f"Bad status update mode: {str(val)}!")
|
@@ -0,0 +1,107 @@
|
|
1
|
+
"""
|
2
|
+
ch_json_loader.py:
|
3
|
+
|
4
|
+
Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of channels
|
5
|
+
|
6
|
+
@author thomas-bc
|
7
|
+
"""
|
8
|
+
|
9
|
+
from fprime_gds.common.templates.ch_template import ChTemplate
|
10
|
+
from fprime_gds.common.loaders.json_loader import JsonLoader
|
11
|
+
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
|
12
|
+
|
13
|
+
|
14
|
+
class ChJsonLoader(JsonLoader):
|
15
|
+
"""Class to load python based telemetry channel dictionaries"""
|
16
|
+
|
17
|
+
CHANNELS_FIELD = "telemetryChannels"
|
18
|
+
|
19
|
+
ID = "id"
|
20
|
+
NAME = "name"
|
21
|
+
DESC = "annotation"
|
22
|
+
TYPE = "type"
|
23
|
+
FMT_STR = "format"
|
24
|
+
LIMIT_FIELD = "limit"
|
25
|
+
LIMIT_LOW = "low"
|
26
|
+
LIMIT_HIGH = "high"
|
27
|
+
LIMIT_RED = "red"
|
28
|
+
LIMIT_ORANGE = "orange"
|
29
|
+
LIMIT_YELLOW = "yellow"
|
30
|
+
|
31
|
+
def construct_dicts(self, _):
|
32
|
+
"""
|
33
|
+
Constructs and returns python dictionaries keyed on id and name
|
34
|
+
|
35
|
+
Args:
|
36
|
+
_: Unused argument (inherited)
|
37
|
+
Returns:
|
38
|
+
A tuple with two channel dictionaries (python type dict):
|
39
|
+
(id_dict, name_dict). The keys should be the channels' id and
|
40
|
+
name fields respectively and the values should be ChTemplate
|
41
|
+
objects.
|
42
|
+
"""
|
43
|
+
id_dict = {}
|
44
|
+
name_dict = {}
|
45
|
+
|
46
|
+
if self.CHANNELS_FIELD not in self.json_dict:
|
47
|
+
raise GdsDictionaryParsingException(
|
48
|
+
f"Ground Dictionary missing '{self.CHANNELS_FIELD}' field: {str(self.json_file)}"
|
49
|
+
)
|
50
|
+
|
51
|
+
for ch_dict in self.json_dict[self.CHANNELS_FIELD]:
|
52
|
+
# Create a channel template object
|
53
|
+
ch_temp = self.construct_template_from_dict(ch_dict)
|
54
|
+
|
55
|
+
id_dict[ch_temp.get_id()] = ch_temp
|
56
|
+
name_dict[ch_temp.get_full_name()] = ch_temp
|
57
|
+
|
58
|
+
return (
|
59
|
+
dict(sorted(id_dict.items())),
|
60
|
+
dict(sorted(name_dict.items())),
|
61
|
+
self.get_versions(),
|
62
|
+
)
|
63
|
+
|
64
|
+
def construct_template_from_dict(self, channel_dict: dict) -> ChTemplate:
|
65
|
+
try:
|
66
|
+
ch_id = channel_dict[self.ID]
|
67
|
+
# The below assignment also raises a ValueError if the name does not contain a '.'
|
68
|
+
qualified_component_name, channel_name = channel_dict[self.NAME].rsplit('.', 1)
|
69
|
+
if not qualified_component_name or not channel_name:
|
70
|
+
raise ValueError()
|
71
|
+
|
72
|
+
type_obj = self.parse_type(channel_dict[self.TYPE])
|
73
|
+
except ValueError as e:
|
74
|
+
raise GdsDictionaryParsingException(
|
75
|
+
f"Channel dictionary entry malformed, expected name of the form '<QUAL_COMP_NAME>.<CH_NAME>' in : {str(channel_dict)}"
|
76
|
+
)
|
77
|
+
except KeyError as e:
|
78
|
+
raise GdsDictionaryParsingException(
|
79
|
+
f"{str(e)} key missing from Channel dictionary entry or its associated type in the dictionary: {str(channel_dict)}"
|
80
|
+
)
|
81
|
+
|
82
|
+
format_str = JsonLoader.preprocess_format_str(channel_dict.get(self.FMT_STR))
|
83
|
+
|
84
|
+
limit_field = channel_dict.get(self.LIMIT_FIELD)
|
85
|
+
limit_low = limit_field.get(self.LIMIT_LOW) if limit_field else None
|
86
|
+
limit_high = limit_field.get(self.LIMIT_HIGH) if limit_field else None
|
87
|
+
limit_low_yellow = limit_low.get(self.LIMIT_YELLOW) if limit_low else None
|
88
|
+
limit_low_red = limit_low.get(self.LIMIT_RED) if limit_low else None
|
89
|
+
limit_low_orange = limit_low.get(self.LIMIT_ORANGE) if limit_low else None
|
90
|
+
limit_high_yellow = limit_high.get(self.LIMIT_YELLOW) if limit_high else None
|
91
|
+
limit_high_red = limit_high.get(self.LIMIT_RED) if limit_high else None
|
92
|
+
limit_high_orange = limit_high.get(self.LIMIT_ORANGE) if limit_high else None
|
93
|
+
|
94
|
+
return ChTemplate(
|
95
|
+
ch_id,
|
96
|
+
channel_name,
|
97
|
+
qualified_component_name,
|
98
|
+
type_obj,
|
99
|
+
ch_fmt_str=format_str,
|
100
|
+
ch_desc=channel_dict.get(self.DESC),
|
101
|
+
low_red=limit_low_red,
|
102
|
+
low_orange=limit_low_orange,
|
103
|
+
low_yellow=limit_low_yellow,
|
104
|
+
high_yellow=limit_high_yellow,
|
105
|
+
high_orange=limit_high_orange,
|
106
|
+
high_red=limit_high_red,
|
107
|
+
)
|
@@ -49,15 +49,15 @@ class ChXmlLoader(XmlLoader):
|
|
49
49
|
respectively and the values are ChTemplate objects
|
50
50
|
"""
|
51
51
|
xml_tree = self.get_xml_tree(path)
|
52
|
-
versions = xml_tree.attrib.get(
|
52
|
+
versions = xml_tree.attrib.get(
|
53
|
+
"framework_version", "unknown"
|
54
|
+
), xml_tree.attrib.get("project_version", "unknown")
|
53
55
|
|
54
56
|
# Check if xml dict has channels section
|
55
57
|
ch_section = self.get_xml_section(self.CH_SECT, xml_tree)
|
56
58
|
if ch_section is None:
|
57
59
|
msg = f"Xml dict did not have a {self.CH_SECT} section"
|
58
|
-
raise exceptions.GseControllerParsingException(
|
59
|
-
msg
|
60
|
-
)
|
60
|
+
raise exceptions.GseControllerParsingException(msg)
|
61
61
|
|
62
62
|
id_dict = {}
|
63
63
|
name_dict = {}
|
@@ -84,7 +84,7 @@ class ChXmlLoader(XmlLoader):
|
|
84
84
|
ch_desc = ch_dict[self.DESC_TAG]
|
85
85
|
|
86
86
|
if self.FMT_STR_TAG in ch_dict:
|
87
|
-
ch_fmt_str = ch_dict[self.FMT_STR_TAG]
|
87
|
+
ch_fmt_str = XmlLoader.preprocess_format_str(ch_dict[self.FMT_STR_TAG])
|
88
88
|
|
89
89
|
# TODO we need to convert these into numbers, is this the best
|
90
90
|
# way to do it?
|
@@ -0,0 +1,85 @@
|
|
1
|
+
"""
|
2
|
+
cmd_json_loader.py:
|
3
|
+
|
4
|
+
Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of commands
|
5
|
+
|
6
|
+
@author thomas-bc
|
7
|
+
"""
|
8
|
+
|
9
|
+
from fprime_gds.common.templates.cmd_template import CmdTemplate
|
10
|
+
from fprime_gds.common.loaders.json_loader import JsonLoader
|
11
|
+
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
|
12
|
+
|
13
|
+
|
14
|
+
class CmdJsonLoader(JsonLoader):
|
15
|
+
"""Class to load json based command dictionaries"""
|
16
|
+
|
17
|
+
COMMANDS_FIELD = "commands"
|
18
|
+
|
19
|
+
NAME = "name"
|
20
|
+
OPCODE = "opcode"
|
21
|
+
DESC = "annotation"
|
22
|
+
PARAMETERS = "formalParams"
|
23
|
+
|
24
|
+
def construct_dicts(self, _):
|
25
|
+
"""
|
26
|
+
Constructs and returns python dictionaries keyed on id and name
|
27
|
+
|
28
|
+
Args:
|
29
|
+
_: Unused argument (inherited)
|
30
|
+
Returns:
|
31
|
+
A tuple with two command dictionaries (python type dict):
|
32
|
+
(id_dict, name_dict). The keys are the events' id and name fields
|
33
|
+
respectively and the values are CmdTemplate objects
|
34
|
+
"""
|
35
|
+
id_dict = {}
|
36
|
+
name_dict = {}
|
37
|
+
|
38
|
+
if self.COMMANDS_FIELD not in self.json_dict:
|
39
|
+
raise GdsDictionaryParsingException(
|
40
|
+
f"Ground Dictionary missing '{self.COMMANDS_FIELD}' field: {str(self.json_file)}"
|
41
|
+
)
|
42
|
+
|
43
|
+
for cmd_dict in self.json_dict[self.COMMANDS_FIELD]:
|
44
|
+
cmd_temp = self.construct_template_from_dict(cmd_dict)
|
45
|
+
|
46
|
+
id_dict[cmd_temp.get_id()] = cmd_temp
|
47
|
+
name_dict[cmd_temp.get_full_name()] = cmd_temp
|
48
|
+
|
49
|
+
return (
|
50
|
+
dict(sorted(id_dict.items())),
|
51
|
+
dict(sorted(name_dict.items())),
|
52
|
+
self.get_versions(),
|
53
|
+
)
|
54
|
+
|
55
|
+
def construct_template_from_dict(self, cmd_dict: dict) -> CmdTemplate:
|
56
|
+
try:
|
57
|
+
qualified_component_name, cmd_mnemonic = cmd_dict[self.NAME].rsplit('.', 1)
|
58
|
+
cmd_opcode = cmd_dict[self.OPCODE]
|
59
|
+
cmd_desc = cmd_dict.get(self.DESC)
|
60
|
+
except ValueError as e:
|
61
|
+
raise GdsDictionaryParsingException(
|
62
|
+
f"Command dictionary entry malformed, expected name of the form '<QUAL_COMP_NAME>.<CMD_NAME>' in : {str(cmd_dict)}"
|
63
|
+
)
|
64
|
+
except KeyError as e:
|
65
|
+
raise GdsDictionaryParsingException(
|
66
|
+
f"{str(e)} key missing from Command dictionary entry: {str(cmd_dict)}"
|
67
|
+
)
|
68
|
+
# Parse Arguments
|
69
|
+
cmd_args = []
|
70
|
+
for param in cmd_dict.get(self.PARAMETERS, []):
|
71
|
+
try:
|
72
|
+
param_name = param["name"]
|
73
|
+
param_type = self.parse_type(param["type"])
|
74
|
+
except KeyError as e:
|
75
|
+
raise GdsDictionaryParsingException(
|
76
|
+
f"{str(e)} key missing from Command parameter or its associated type in the dictionary: {str(param)}"
|
77
|
+
)
|
78
|
+
cmd_args.append(
|
79
|
+
(
|
80
|
+
param_name,
|
81
|
+
param.get("annotation"),
|
82
|
+
param_type,
|
83
|
+
)
|
84
|
+
)
|
85
|
+
return CmdTemplate(cmd_opcode, cmd_mnemonic, qualified_component_name, cmd_args, cmd_desc)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
"""
|
2
|
+
event_json_loader.py:
|
3
|
+
|
4
|
+
Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of events
|
5
|
+
|
6
|
+
@author thomas-bc
|
7
|
+
"""
|
8
|
+
|
9
|
+
from fprime_gds.common.templates.event_template import EventTemplate
|
10
|
+
from fprime_gds.common.utils.event_severity import EventSeverity
|
11
|
+
from fprime_gds.common.loaders.json_loader import JsonLoader
|
12
|
+
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
|
13
|
+
|
14
|
+
|
15
|
+
class EventJsonLoader(JsonLoader):
|
16
|
+
"""Class to load json based event dictionaries"""
|
17
|
+
|
18
|
+
EVENTS_FIELD = "events"
|
19
|
+
|
20
|
+
NAME = "name"
|
21
|
+
ID = "id"
|
22
|
+
SEVERITY = "severity"
|
23
|
+
FMT_STR = "format"
|
24
|
+
DESC = "annotation"
|
25
|
+
PARAMETERS = "formalParams"
|
26
|
+
|
27
|
+
def construct_dicts(self, _):
|
28
|
+
"""
|
29
|
+
Constructs and returns python dictionaries keyed on id and name
|
30
|
+
|
31
|
+
This function should not be called directly, instead, use
|
32
|
+
get_id_dict(path) and get_name_dict(path)
|
33
|
+
|
34
|
+
Args:
|
35
|
+
_: Unused argument (inherited)
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
A tuple with two event dictionaries (python type dict):
|
39
|
+
(id_dict, name_dict). The keys are the events' id and name fields
|
40
|
+
respectively and the values are ChTemplate objects
|
41
|
+
"""
|
42
|
+
id_dict = {}
|
43
|
+
name_dict = {}
|
44
|
+
|
45
|
+
if self.EVENTS_FIELD not in self.json_dict:
|
46
|
+
raise GdsDictionaryParsingException(
|
47
|
+
f"Ground Dictionary missing '{self.EVENTS_FIELD}' field: {str(self.json_file)}"
|
48
|
+
)
|
49
|
+
|
50
|
+
for event_dict in self.json_dict[self.EVENTS_FIELD]:
|
51
|
+
event_temp = self.construct_template_from_dict(event_dict)
|
52
|
+
|
53
|
+
id_dict[event_temp.get_id()] = event_temp
|
54
|
+
name_dict[event_temp.get_full_name()] = event_temp
|
55
|
+
|
56
|
+
return (
|
57
|
+
dict(sorted(id_dict.items())),
|
58
|
+
dict(sorted(name_dict.items())),
|
59
|
+
self.get_versions(),
|
60
|
+
)
|
61
|
+
|
62
|
+
def construct_template_from_dict(self, event_dict: dict):
|
63
|
+
try:
|
64
|
+
qualified_component_name, event_name = event_dict[self.NAME].rsplit('.', 1)
|
65
|
+
event_id = event_dict[self.ID]
|
66
|
+
event_severity = EventSeverity[event_dict[self.SEVERITY]]
|
67
|
+
except ValueError as e:
|
68
|
+
raise GdsDictionaryParsingException(
|
69
|
+
f"Event dictionary entry malformed, expected name of the form '<QUAL_COMP_NAME>.<EVENT_NAME>' in : {str(event_dict)}"
|
70
|
+
)
|
71
|
+
except KeyError as e:
|
72
|
+
raise GdsDictionaryParsingException(
|
73
|
+
f"{str(e)} key missing from Event dictionary entry: {str(event_dict)}"
|
74
|
+
)
|
75
|
+
|
76
|
+
event_fmt_str = JsonLoader.preprocess_format_str(
|
77
|
+
event_dict.get(self.FMT_STR, "")
|
78
|
+
)
|
79
|
+
|
80
|
+
event_desc = event_dict.get(self.DESC)
|
81
|
+
|
82
|
+
# Parse arguments
|
83
|
+
event_args = []
|
84
|
+
for arg in event_dict.get(self.PARAMETERS, []):
|
85
|
+
try:
|
86
|
+
arg_name = arg["name"]
|
87
|
+
arg_type = self.parse_type(arg["type"])
|
88
|
+
except KeyError as e:
|
89
|
+
raise GdsDictionaryParsingException(
|
90
|
+
f"{str(e)} key missing from Event parameter or its associated type in the dictionary: {str(arg)}"
|
91
|
+
)
|
92
|
+
event_args.append(
|
93
|
+
(
|
94
|
+
arg_name,
|
95
|
+
arg.get("annotation"),
|
96
|
+
arg_type,
|
97
|
+
)
|
98
|
+
)
|
99
|
+
|
100
|
+
return EventTemplate(
|
101
|
+
event_id,
|
102
|
+
event_name,
|
103
|
+
qualified_component_name,
|
104
|
+
event_args,
|
105
|
+
event_severity,
|
106
|
+
event_fmt_str,
|
107
|
+
event_desc,
|
108
|
+
)
|
@@ -43,15 +43,15 @@ class EventXmlLoader(XmlLoader):
|
|
43
43
|
respectively and the values are ChTemplate objects
|
44
44
|
"""
|
45
45
|
xml_tree = self.get_xml_tree(path)
|
46
|
-
versions = xml_tree.attrib.get(
|
46
|
+
versions = xml_tree.attrib.get(
|
47
|
+
"framework_version", "unknown"
|
48
|
+
), xml_tree.attrib.get("project_version", "unknown")
|
47
49
|
|
48
50
|
# Check if xml dict has events section
|
49
51
|
event_section = self.get_xml_section(self.EVENT_SECT, xml_tree)
|
50
52
|
if event_section is None:
|
51
53
|
msg = f"Xml dict did not have a {self.EVENT_SECT} section"
|
52
|
-
raise exceptions.GseControllerParsingException(
|
53
|
-
msg
|
54
|
-
)
|
54
|
+
raise exceptions.GseControllerParsingException(msg)
|
55
55
|
|
56
56
|
id_dict = {}
|
57
57
|
name_dict = {}
|
@@ -63,14 +63,18 @@ class EventXmlLoader(XmlLoader):
|
|
63
63
|
event_name = event_dict[self.NAME_TAG]
|
64
64
|
event_id = int(event_dict[self.ID_TAG], base=16)
|
65
65
|
event_severity = EventSeverity[event_dict[self.SEVERITY_TAG]]
|
66
|
-
event_fmt_str =
|
66
|
+
event_fmt_str = XmlLoader.preprocess_format_str(
|
67
|
+
event_dict[self.FMT_STR_TAG]
|
68
|
+
)
|
67
69
|
|
68
70
|
event_desc = None
|
69
71
|
if self.DESC_TAG in event_dict:
|
70
72
|
event_desc = event_dict[self.DESC_TAG]
|
71
73
|
|
72
74
|
# Parse arguments
|
73
|
-
args = self.get_args_list(
|
75
|
+
args = self.get_args_list(
|
76
|
+
event, xml_tree, f"{ event_comp }::{ event_name }"
|
77
|
+
)
|
74
78
|
|
75
79
|
event_temp = EventTemplate(
|
76
80
|
event_id,
|
@@ -0,0 +1,222 @@
|
|
1
|
+
"""
|
2
|
+
json_loader.py:
|
3
|
+
|
4
|
+
Base class for all loaders that load dictionaries from json dictionaries
|
5
|
+
|
6
|
+
@author thomas-bc
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
from typing import Optional
|
11
|
+
|
12
|
+
from fprime.common.models.serialize.array_type import ArrayType
|
13
|
+
from fprime.common.models.serialize.bool_type import BoolType
|
14
|
+
from fprime.common.models.serialize.enum_type import EnumType
|
15
|
+
import fprime.common.models.serialize.numerical_types as numerical_types
|
16
|
+
from fprime.common.models.serialize.serializable_type import SerializableType
|
17
|
+
from fprime.common.models.serialize.string_type import StringType
|
18
|
+
from fprime.common.models.serialize.type_base import BaseType
|
19
|
+
|
20
|
+
from fprime_gds.common.utils.string_util import preprocess_fpp_format_str
|
21
|
+
from fprime_gds.common.loaders import dict_loader
|
22
|
+
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
|
23
|
+
|
24
|
+
|
25
|
+
PRIMITIVE_TYPE_MAP = {
|
26
|
+
"I8": numerical_types.I8Type,
|
27
|
+
"I16": numerical_types.I16Type,
|
28
|
+
"I32": numerical_types.I32Type,
|
29
|
+
"I64": numerical_types.I64Type,
|
30
|
+
"U8": numerical_types.U8Type,
|
31
|
+
"U16": numerical_types.U16Type,
|
32
|
+
"U32": numerical_types.U32Type,
|
33
|
+
"U64": numerical_types.U64Type,
|
34
|
+
"F32": numerical_types.F32Type,
|
35
|
+
"F64": numerical_types.F64Type,
|
36
|
+
"bool": BoolType,
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
class JsonLoader(dict_loader.DictLoader):
|
41
|
+
"""Class to help load JSON dictionaries"""
|
42
|
+
|
43
|
+
# Cache parsed type objects at the class level so they can be reused across subclasses
|
44
|
+
parsed_types: dict = {}
|
45
|
+
|
46
|
+
def __init__(self, json_file: str):
|
47
|
+
"""
|
48
|
+
Constructor
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
An initialized loader object
|
52
|
+
"""
|
53
|
+
super().__init__()
|
54
|
+
self.json_file = json_file
|
55
|
+
with open(json_file, "r") as f:
|
56
|
+
self.json_dict = json.load(f)
|
57
|
+
|
58
|
+
def get_versions(self):
|
59
|
+
"""
|
60
|
+
Get the framework and project versions of the dictionary
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
A tuple of the framework and project versions
|
64
|
+
"""
|
65
|
+
if "metadata" not in self.json_dict:
|
66
|
+
raise GdsDictionaryParsingException(
|
67
|
+
f"Dictionary has no metadata field: {self.json_file}"
|
68
|
+
)
|
69
|
+
return (
|
70
|
+
self.json_dict["metadata"].get("frameworkVersion", "unknown"),
|
71
|
+
self.json_dict["metadata"].get("projectVersion", "unknown"),
|
72
|
+
)
|
73
|
+
|
74
|
+
def parse_type(self, type_dict: dict) -> BaseType:
|
75
|
+
type_name: str = type_dict.get("name", None)
|
76
|
+
|
77
|
+
if type_name is None:
|
78
|
+
raise GdsDictionaryParsingException(
|
79
|
+
f"Dictionary entry has no `name` field: {str(type_dict)}"
|
80
|
+
)
|
81
|
+
|
82
|
+
if type_name in PRIMITIVE_TYPE_MAP:
|
83
|
+
return PRIMITIVE_TYPE_MAP[type_name]
|
84
|
+
|
85
|
+
if type_name == "string":
|
86
|
+
return StringType.construct_type(
|
87
|
+
f'String_{type_dict["size"]}', type_dict["size"]
|
88
|
+
)
|
89
|
+
|
90
|
+
# Check if type has already been parsed
|
91
|
+
if type_name in self.parsed_types:
|
92
|
+
return self.parsed_types[type_name]
|
93
|
+
|
94
|
+
# Parse new enum/array/serializable types
|
95
|
+
qualified_type = None
|
96
|
+
for type_def in self.json_dict.get("typeDefinitions", []):
|
97
|
+
if type_name == type_def.get("qualifiedName"):
|
98
|
+
qualified_type = type_def
|
99
|
+
break
|
100
|
+
else:
|
101
|
+
raise GdsDictionaryParsingException(
|
102
|
+
f"Dictionary type name has no corresponding type definition: {type_name}"
|
103
|
+
)
|
104
|
+
|
105
|
+
if qualified_type.get("kind") == "array":
|
106
|
+
return self.construct_array_type(type_name, qualified_type)
|
107
|
+
|
108
|
+
if qualified_type.get("kind") == "enum":
|
109
|
+
return self.construct_enum_type(type_name, qualified_type)
|
110
|
+
|
111
|
+
if qualified_type.get("kind") == "struct":
|
112
|
+
return self.construct_serializable_type(type_name, qualified_type)
|
113
|
+
|
114
|
+
raise GdsDictionaryParsingException(
|
115
|
+
f"Dictionary entry has unknown type {str(type_dict)}"
|
116
|
+
)
|
117
|
+
|
118
|
+
def construct_enum_type(self, type_name: str, qualified_type: dict) -> EnumType:
|
119
|
+
"""
|
120
|
+
Constructs an EnumType object of the given type name and qualified type dictionary.
|
121
|
+
Caches the constructed EnumType object in the parsed_types dictionary.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
type_name (str): The name of the enum type.
|
125
|
+
qualified_type (dict): A dictionary containing the qualified type information.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
EnumType: The constructed EnumType object.
|
129
|
+
|
130
|
+
"""
|
131
|
+
enum_dict = {}
|
132
|
+
for member in qualified_type.get("enumeratedConstants"):
|
133
|
+
enum_dict[member["name"]] = member.get("value")
|
134
|
+
enum_type = EnumType.construct_type(
|
135
|
+
type_name,
|
136
|
+
enum_dict,
|
137
|
+
qualified_type["representationType"].get("name"),
|
138
|
+
)
|
139
|
+
self.parsed_types[type_name] = enum_type
|
140
|
+
return enum_type
|
141
|
+
|
142
|
+
def construct_array_type(self, type_name: str, qualified_type: dict) -> ArrayType:
|
143
|
+
"""
|
144
|
+
Constructs an ArrayType object based on the given type name and qualified type dictionary.
|
145
|
+
Caches the constructed ArrayType object in the parsed_types dictionary.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
type_name (str): The name of the array type.
|
149
|
+
qualified_type (dict): The qualified type dictionary containing information about the array type.
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
ArrayType: The constructed ArrayType object.
|
153
|
+
|
154
|
+
"""
|
155
|
+
array_type = ArrayType.construct_type(
|
156
|
+
type_name,
|
157
|
+
self.parse_type(qualified_type.get("elementType")),
|
158
|
+
qualified_type.get("size"),
|
159
|
+
JsonLoader.preprocess_format_str(
|
160
|
+
qualified_type["elementType"].get("format", "{}")
|
161
|
+
),
|
162
|
+
)
|
163
|
+
self.parsed_types[type_name] = array_type
|
164
|
+
return array_type
|
165
|
+
|
166
|
+
def construct_serializable_type(
|
167
|
+
self, type_name: str, qualified_type: dict
|
168
|
+
) -> SerializableType:
|
169
|
+
"""
|
170
|
+
Constructs a SerializableType based on the given type name and qualified type dictionary.
|
171
|
+
Caches the constructed SerializableType object in the parsed_types dictionary.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
type_name (str): The name of the serializable type.
|
175
|
+
qualified_type (dict): The qualified type dictionary containing information about the type.
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
SerializableType: The constructed serializable type.
|
179
|
+
|
180
|
+
"""
|
181
|
+
struct_members = []
|
182
|
+
for name, member_dict in qualified_type.get("members").items():
|
183
|
+
member_type_dict = member_dict["type"]
|
184
|
+
member_type_obj = self.parse_type(member_type_dict)
|
185
|
+
|
186
|
+
# For member arrays (declared inline, so we create a type on the fly)
|
187
|
+
if member_dict.get("size") is not None:
|
188
|
+
member_type_obj = ArrayType.construct_type(
|
189
|
+
f"Array_{member_type_obj.__name__}_{member_dict['size']}",
|
190
|
+
member_type_obj,
|
191
|
+
member_dict["size"],
|
192
|
+
JsonLoader.preprocess_format_str(
|
193
|
+
member_dict["type"].get("format", "{}")
|
194
|
+
),
|
195
|
+
)
|
196
|
+
fmt_str = JsonLoader.preprocess_format_str(
|
197
|
+
member_type_obj.FORMAT if hasattr(member_type_obj, "FORMAT") else "{}"
|
198
|
+
)
|
199
|
+
description = member_type_dict.get("annotation", "")
|
200
|
+
struct_members.append((name, member_type_obj, fmt_str, description))
|
201
|
+
|
202
|
+
ser_type = SerializableType.construct_type(
|
203
|
+
type_name,
|
204
|
+
struct_members,
|
205
|
+
)
|
206
|
+
self.parsed_types[type_name] = ser_type
|
207
|
+
return ser_type
|
208
|
+
|
209
|
+
@staticmethod
|
210
|
+
def preprocess_format_str(format_str: Optional[str]) -> Optional[str]:
|
211
|
+
"""Preprocess format strings before using them in Python format function
|
212
|
+
Internally, this converts FPP-style format strings to Python-style format strings
|
213
|
+
|
214
|
+
Args:
|
215
|
+
format_str (str): FPP-style format string
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
str: Python-style format string
|
219
|
+
"""
|
220
|
+
if format_str is None:
|
221
|
+
return None
|
222
|
+
return preprocess_fpp_format_str(format_str)
|