Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Dictionary Integration #173

Merged
merged 29 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
31d39ba
loading channels working - minus glutton of choice
thomas-bc Mar 19, 2024
2054755
Handle struct member arrays (inline)
thomas-bc Mar 19, 2024
ff45a01
Add JsonLoader base type
thomas-bc Mar 21, 2024
1c758fd
working MVP for all cmd/channels/events
thomas-bc Mar 21, 2024
63fa909
Remove commented out code
thomas-bc Mar 25, 2024
47719a9
Update for latest JSON format
thomas-bc Apr 16, 2024
b5a98a1
Formatting
thomas-bc Apr 23, 2024
e595fe3
Fix Python<3.10 compatibility (remove match statement)
thomas-bc Apr 24, 2024
07d6f62
Translate format strings at load-time
thomas-bc Apr 26, 2024
9ce646a
Remove tuple type hint that break Python3.8
thomas-bc Apr 26, 2024
bb7ab3d
Fix CodeQL warnings in test code
thomas-bc Apr 26, 2024
9f64f1e
spelling
thomas-bc Apr 26, 2024
486ff6e
Fix changed test case for more surface area
thomas-bc Apr 26, 2024
87e1e46
Formatting
thomas-bc Apr 26, 2024
db6aa69
Fix get_versions()
thomas-bc Apr 30, 2024
75b0a0a
reorganize code
thomas-bc Apr 30, 2024
f6abfda
Improve behavior of default command selection
thomas-bc Apr 30, 2024
f53342e
Prioritize selection of JSON dictionary over XML
thomas-bc Apr 30, 2024
c323648
Cache parsed_types in JsonLoader class
thomas-bc Apr 30, 2024
c6e79bd
Add test cases
thomas-bc Apr 30, 2024
cbd8715
ignore some spelling
thomas-bc Apr 30, 2024
d52fa53
Merge branch 'devel' into issue/2592-json-dictionary
thomas-bc May 1, 2024
d8296fc
Code cleanup and formatting
thomas-bc May 1, 2024
681b88b
Remove "tag" terminology
thomas-bc May 1, 2024
4471c81
Exclude dictionary from spelling
thomas-bc May 1, 2024
6685602
few review notes
thomas-bc May 2, 2024
4709651
case-insensitive sorting in commandList
thomas-bc May 2, 2024
5eacbf3
Improve error handling
thomas-bc May 7, 2024
95b1928
Sort dictionaries at load time
thomas-bc May 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/fprime_gds/common/data_types/event_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ def __init__(self, event_args, event_time, event_temp):
self.display_text = event_temp.description
elif event_temp.format_str == "":
args_template = self.template.get_args()
self.display_text = str([{args_template[index][0]:arg.val} for index, arg in enumerate(event_args)])
self.display_text = str(
[
{args_template[index][0]: arg.val}
for index, arg in enumerate(event_args)
]
)
else:
self.display_text = format_string_template(
event_temp.format_str, tuple([arg.val for arg in event_args])
Expand Down
95 changes: 95 additions & 0 deletions src/fprime_gds/common/loaders/ch_json_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
ch_json_loader.py:

Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of channels

@author thomas-bc
"""

from fprime_gds.common.templates.ch_template import ChTemplate
from fprime_gds.common.loaders.json_loader import JsonLoader


class ChJsonLoader(JsonLoader):
"""Class to load python based telemetry channel dictionaries"""

# Field names in the python module files (used to construct dictionaries)
ID_FIELD = "id"
NAME_FIELD = "name"
KIND_FIELD = "kind"
DESC_FIELD = "annotation"
TYPE_FIELD = "type"
FMT_STR_FIELD = "format"
LIMIT_FIELD = "limit"
LIMIT_LOW = "low"
LIMIT_HIGH = "high"
LIMIT_RED = "red"
LIMIT_ORANGE = "orange"
LIMIT_YELLOW = "yellow"

def __init__(self, dict_path: str):
super().__init__(dict_path)

def construct_dicts(self, dict_path: str):
"""
Constructs and returns python dictionaries keyed on id and name

Args:
path: Path to JSON dictionary file
Returns:
A tuple with two channel dictionaries (python type dict):
(id_dict, name_dict). The keys should be the channels' id and
name fields respectively and the values should be ChTemplate
objects.
"""
id_dict = {}
name_dict = {}

for ch_dict in self.json_dict["telemetryChannels"]:
# Create a channel template object
ch_temp = self.construct_template_from_dict(ch_dict)

id_dict[ch_dict[self.ID_FIELD]] = ch_temp
name_dict[ch_dict[self.NAME_FIELD]] = ch_temp

return (
dict(sorted(id_dict.items())),
dict(sorted(name_dict.items())),
("unknown", "unknown"),
)

def construct_template_from_dict(self, channel_dict: dict):
component_name = channel_dict[self.NAME_FIELD].split(".")[0]
channel_name = channel_dict[self.NAME_FIELD].split(".")[1]
channel_type = channel_dict.get("type")
type_obj = self.parse_type(channel_type)
format_str = JsonLoader.preprocess_format_str(
channel_dict.get(self.FMT_STR_FIELD)
)

limit_field = channel_dict.get(self.LIMIT_FIELD)
limit_low = limit_field.get(self.LIMIT_LOW) if limit_field else None
limit_high = limit_field.get(self.LIMIT_HIGH) if limit_field else None

limit_low_yellow = limit_low.get(self.LIMIT_YELLOW) if limit_low else None
limit_low_red = limit_low.get(self.LIMIT_RED) if limit_low else None
limit_low_orange = limit_low.get(self.LIMIT_ORANGE) if limit_low else None

limit_high_yellow = limit_high.get(self.LIMIT_YELLOW) if limit_high else None
limit_high_red = limit_high.get(self.LIMIT_RED) if limit_high else None
limit_high_orange = limit_high.get(self.LIMIT_ORANGE) if limit_high else None

return ChTemplate(
channel_dict[self.ID_FIELD],
channel_name,
component_name,
type_obj,
ch_fmt_str=format_str,
ch_desc=channel_dict.get(self.DESC_FIELD),
low_red=limit_low_red,
low_orange=limit_low_orange,
low_yellow=limit_low_yellow,
high_yellow=limit_high_yellow,
high_orange=limit_high_orange,
high_red=limit_high_red,
)
10 changes: 5 additions & 5 deletions src/fprime_gds/common/loaders/ch_xml_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ def construct_dicts(self, path):
respectively and the values are ChTemplate objects
"""
xml_tree = self.get_xml_tree(path)
versions = xml_tree.attrib.get("framework_version", "unknown"), xml_tree.attrib.get("project_version", "unknown")
versions = xml_tree.attrib.get(
"framework_version", "unknown"
), xml_tree.attrib.get("project_version", "unknown")

# Check if xml dict has channels section
ch_section = self.get_xml_section(self.CH_SECT, xml_tree)
if ch_section is None:
msg = f"Xml dict did not have a {self.CH_SECT} section"
raise exceptions.GseControllerParsingException(
msg
)
raise exceptions.GseControllerParsingException(msg)

id_dict = {}
name_dict = {}
Expand All @@ -84,7 +84,7 @@ def construct_dicts(self, path):
ch_desc = ch_dict[self.DESC_TAG]

if self.FMT_STR_TAG in ch_dict:
ch_fmt_str = ch_dict[self.FMT_STR_TAG]
ch_fmt_str = XmlLoader.preprocess_format_str(ch_dict[self.FMT_STR_TAG])

# TODO we need to convert these into numbers, is this the best
# way to do it?
Expand Down
67 changes: 67 additions & 0 deletions src/fprime_gds/common/loaders/cmd_json_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
cmd_json_loader.py:

Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of commands

@author thomas-bc
"""

from fprime_gds.common.templates.cmd_template import CmdTemplate
from fprime_gds.common.loaders.json_loader import JsonLoader


class CmdJsonLoader(JsonLoader):
"""Class to load xml based command dictionaries"""

MNEMONIC_TAG = "name"
OPCODE_TAG = "opcode"
DESC_TAG = "annotation"
PARAMETERS_TAG = "formalParams"

def __init__(self, dict_path: str):
super().__init__(dict_path)

def construct_dicts(self, path):
"""
Constructs and returns python dictionaries keyed on id and name

Args:
path: Path to JSON dictionary file
Returns:
A tuple with two command dictionaries (python type dict):
(id_dict, name_dict). The keys are the events' id and name fields
respectively and the values are CmdTemplate objects
"""

id_dict = {}
name_dict = {}

for cmd_dict in self.json_dict["commands"]:
cmd_name = cmd_dict.get("name")

cmd_comp = cmd_name.split(".")[0]
cmd_mnemonic = cmd_name.split(".")[1]

cmd_opcode = cmd_dict.get("opcode")

cmd_desc = cmd_dict.get("annotation")

# Parse Arguments
cmd_args = []
for arg in cmd_dict.get("formalParams", []):
cmd_args.append(
(
arg.get("name"),
arg.get("annotation"),
self.parse_type(arg.get("type")),
)
)

cmd_temp = CmdTemplate(
cmd_opcode, cmd_mnemonic, cmd_comp, cmd_args, cmd_desc
)

id_dict[cmd_opcode] = cmd_temp
name_dict[cmd_temp.get_full_name()] = cmd_temp

return id_dict, name_dict, ("unknown", "unknown")
2 changes: 1 addition & 1 deletion src/fprime_gds/common/loaders/dict_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def get_name_dict(self, path):
return name_dict

def get_versions(self):
""" Get version tuple """
"""Get version tuple"""
return self.versions

def construct_dicts(self, path):
Expand Down
92 changes: 92 additions & 0 deletions src/fprime_gds/common/loaders/event_json_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
event_json_loader.py:

Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of events

@author thomas-bc
"""

from fprime_gds.common.data_types import exceptions
Fixed Show fixed Hide fixed
from fprime_gds.common.templates.event_template import EventTemplate
from fprime_gds.common.utils.event_severity import EventSeverity

# Custom Python Modules
from fprime_gds.common.loaders.json_loader import JsonLoader


class EventJsonLoader(JsonLoader):
"""Class to load xml based event dictionaries"""

EVENT_SECT = "events"

COMP_TAG = "component"
NAME_TAG = "name"
ID_TAG = "id"
SEVERITY_TAG = "severity"
FMT_STR_TAG = "format"
DESC_TAG = "annotation"

def construct_dicts(self, path):
"""
Constructs and returns python dictionaries keyed on id and name

This function should not be called directly, instead, use
get_id_dict(path) and get_name_dict(path)

Args:
path: Path to the xml dictionary file containing event information

Returns:
A tuple with two event dictionaries (python type dict):
(id_idct, name_dict). The keys are the events' id and name fields
respectively and the values are ChTemplate objects
"""
versions = self.get_versions()

# Check if xml dict has events section
if self.json_dict.get("events") is None:
msg = "JSON dict did not have a events section"
raise exceptions.GseControllerParsingException(msg)

id_dict = {}
name_dict = {}

for event_dict in self.json_dict.get("events"):
event_mnemonic = event_dict.get("name")
event_comp = event_mnemonic.split(".")[0]
event_name = event_mnemonic.split(".")[1]

event_id = event_dict[self.ID_TAG]
event_severity = EventSeverity[event_dict[self.SEVERITY_TAG]]

event_fmt_str = JsonLoader.preprocess_format_str(
event_dict.get(self.FMT_STR_TAG, "")
)

event_desc = event_dict.get(self.DESC_TAG)

# Parse arguments
event_args = []
for arg in event_dict.get("formalParams", []):
event_args.append(
(
arg.get("name"),
arg.get("annotation"),
self.parse_type(arg.get("type")),
)
)

event_temp = EventTemplate(
event_id,
event_name,
event_comp,
event_args,
event_severity,
event_fmt_str,
event_desc,
)

id_dict[event_id] = event_temp
name_dict[event_temp.get_full_name()] = event_temp

return id_dict, name_dict, versions
16 changes: 10 additions & 6 deletions src/fprime_gds/common/loaders/event_xml_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ def construct_dicts(self, path):
respectively and the values are ChTemplate objects
"""
xml_tree = self.get_xml_tree(path)
versions = xml_tree.attrib.get("framework_version", "unknown"), xml_tree.attrib.get("project_version", "unknown")
versions = xml_tree.attrib.get(
"framework_version", "unknown"
), xml_tree.attrib.get("project_version", "unknown")

# Check if xml dict has events section
event_section = self.get_xml_section(self.EVENT_SECT, xml_tree)
if event_section is None:
msg = f"Xml dict did not have a {self.EVENT_SECT} section"
raise exceptions.GseControllerParsingException(
msg
)
raise exceptions.GseControllerParsingException(msg)

id_dict = {}
name_dict = {}
Expand All @@ -63,14 +63,18 @@ def construct_dicts(self, path):
event_name = event_dict[self.NAME_TAG]
event_id = int(event_dict[self.ID_TAG], base=16)
event_severity = EventSeverity[event_dict[self.SEVERITY_TAG]]
event_fmt_str = event_dict[self.FMT_STR_TAG]
event_fmt_str = XmlLoader.preprocess_format_str(
event_dict[self.FMT_STR_TAG]
)

event_desc = None
if self.DESC_TAG in event_dict:
event_desc = event_dict[self.DESC_TAG]

# Parse arguments
args = self.get_args_list(event, xml_tree, f"{ event_comp }::{ event_name }")
args = self.get_args_list(
event, xml_tree, f"{ event_comp }::{ event_name }"
)

event_temp = EventTemplate(
event_id,
Expand Down
Loading
Loading