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 all 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: 4 additions & 3 deletions .github/actions/spelling/excludes.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
pyproject.toml
setup.py
ignore$
/html/
(?:^|/)(?i)COPYRIGHT
(?:^|/)(?i)LICEN[CS]E
(?:^|/)vendor/
^\.github/
^Autocoders/Python/test/.*\.xml$
RefTopologyDictionary.json
dictionary.xml
/doc/xml/
/third-party/
/third/
Expand Down Expand Up @@ -43,6 +47,3 @@ ignore$
\.xlsx$
\.xsd$
\.ico$
pyproject.toml
setup.py
dictionary.xml
3 changes: 3 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ CCB
CCFF
cdn
Cdioux
cdxoefg
CFDG
CFDP
CFPD
Expand Down Expand Up @@ -229,6 +230,7 @@ focusring
FONTNAME
FONTPATH
FONTSIZE
fpp
fprime
fptable
frac
Expand Down Expand Up @@ -420,6 +422,7 @@ nitpicky
noapp
noqa
normpath
NOSONAR
nosort
Noto
novalidate
Expand Down
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
27 changes: 16 additions & 11 deletions src/fprime_gds/common/data_types/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@
Created on Jan 9, 2015
@author: reder
"""

# Exception classes for all controllers modules


class GseControllerException(Exception):
class FprimeGdsException(Exception):
"""
Base Exception for exceptions that need to be handled at the system-level
by the GDS
"""

pass


class GdsDictionaryParsingException(FprimeGdsException):
pass


# Note: Gse is the historical name for what is now called the GDS
class GseControllerException(FprimeGdsException):
def __init__(self, val):
self.except_msg = val
super().__init__(val)
Expand All @@ -27,13 +42,3 @@ def __init__(self, val):
class GseControllerParsingException(GseControllerException):
def __init__(self, val):
super().__init__(f"Parsing error: {str(val)}")


class GseControllerMnemonicMismatchException(GseControllerException):
def __init__(self, val1, val2):
super().__init__(f"ID mismatch ({str(val1)}, {str(val2)})!")


class GseControllerStatusUpdateException(GseControllerException):
def __init__(self, val):
super().__init__(f"Bad status update mode: {str(val)}!")
107 changes: 107 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,107 @@
"""
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
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException


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

CHANNELS_FIELD = "telemetryChannels"

ID = "id"
NAME = "name"
DESC = "annotation"
TYPE = "type"
FMT_STR = "format"
LIMIT_FIELD = "limit"
LIMIT_LOW = "low"
LIMIT_HIGH = "high"
LIMIT_RED = "red"
LIMIT_ORANGE = "orange"
LIMIT_YELLOW = "yellow"

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

Args:
_: Unused argument (inherited)
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 = {}

if self.CHANNELS_FIELD not in self.json_dict:
raise GdsDictionaryParsingException(
f"Ground Dictionary missing '{self.CHANNELS_FIELD}' field: {str(self.json_file)}"
)

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

id_dict[ch_temp.get_id()] = ch_temp
name_dict[ch_temp.get_full_name()] = ch_temp

return (
dict(sorted(id_dict.items())),
dict(sorted(name_dict.items())),
self.get_versions(),
)

def construct_template_from_dict(self, channel_dict: dict) -> ChTemplate:
try:
ch_id = channel_dict[self.ID]
# The below assignment also raises a ValueError if the name does not contain a '.'
component_name, channel_name = channel_dict[self.NAME].split(".")
if not component_name or not channel_name:
raise ValueError()

type_obj = self.parse_type(channel_dict[self.TYPE])
except ValueError as e:
raise GdsDictionaryParsingException(
f"Channel dictionary entry malformed, expected name of the form '<COMP_NAME>.<CH_NAME>' in : {str(channel_dict)}"
)
except KeyError as e:
raise GdsDictionaryParsingException(
f"{str(e)} key missing from Channel dictionary entry or its associated type in the dictionary: {str(channel_dict)}"
)

format_str = JsonLoader.preprocess_format_str(channel_dict.get(self.FMT_STR))

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(
ch_id,
channel_name,
component_name,
type_obj,
ch_fmt_str=format_str,
ch_desc=channel_dict.get(self.DESC),
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
85 changes: 85 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,85 @@
"""
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
from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException


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

COMMANDS_FIELD = "commands"

NAME = "name"
OPCODE = "opcode"
DESC = "annotation"
PARAMETERS = "formalParams"

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

Args:
_: Unused argument (inherited)
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 = {}

if self.COMMANDS_FIELD not in self.json_dict:
raise GdsDictionaryParsingException(
f"Ground Dictionary missing '{self.COMMANDS_FIELD}' field: {str(self.json_file)}"
)

for cmd_dict in self.json_dict[self.COMMANDS_FIELD]:
cmd_temp = self.construct_template_from_dict(cmd_dict)

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

return (
dict(sorted(id_dict.items())),
dict(sorted(name_dict.items())),
self.get_versions(),
)

def construct_template_from_dict(self, cmd_dict: dict) -> CmdTemplate:
try:
cmd_comp, cmd_mnemonic = cmd_dict[self.NAME].split(".")
cmd_opcode = cmd_dict[self.OPCODE]
cmd_desc = cmd_dict.get(self.DESC)
except ValueError as e:
raise GdsDictionaryParsingException(
f"Command dictionary entry malformed, expected name of the form '<COMP_NAME>.<CMD_NAME>' in : {str(cmd_dict)}"
)
except KeyError as e:
raise GdsDictionaryParsingException(
f"{str(e)} key missing from Command dictionary entry: {str(cmd_dict)}"
)
# Parse Arguments
cmd_args = []
for param in cmd_dict.get(self.PARAMETERS, []):
try:
param_name = param["name"]
param_type = self.parse_type(param["type"])
except KeyError as e:
raise GdsDictionaryParsingException(
f"{str(e)} key missing from Command parameter or its associated type in the dictionary: {str(param)}"
)
cmd_args.append(
(
param_name,
param.get("annotation"),
param_type,
)
)
return CmdTemplate(cmd_opcode, cmd_mnemonic, cmd_comp, cmd_args, cmd_desc)
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
Loading
Loading