From 2ba60f133a5075e03c90638a93bf4133c0455aab Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Mon, 22 Jul 2024 10:10:19 -0600 Subject: [PATCH] Xtce combined (#654) * MNT: Update telemetry generator to iterate over all packet definitions - Add a polynomial calibrator for analog conversions - Add logic for variable length BYTES Some instruments don't provide the apId column, so we can calculate it ourselves. We do need the integer apid for the XTCE file. --- imap_processing/ccsds/excel_to_xtce.py | 433 ++++++++++++++++++ .../tests/ccsds/test_excel_to_xtce.py | 60 +++ poetry.lock | 11 + pyproject.toml | 3 +- tools/tests/test_xtce_generator_template.py | 86 ---- tools/xtce_generation/26850.02-TLMDEF-04.xlsx | Bin 50414 -> 0 bytes .../TLM_SWP_20231006-121021.xlsx | Bin 48631 -> 0 bytes .../ccsds_header_xtce_generator.py | 54 --- tools/xtce_generation/telemetry_generator.py | 393 ---------------- .../xtce_generation/xtce_generator_codice.py | 54 --- tools/xtce_generation/xtce_generator_glows.py | 59 --- tools/xtce_generation/xtce_generator_hi.py | 37 -- tools/xtce_generation/xtce_generator_hit.py | 45 -- tools/xtce_generation/xtce_generator_lo.py | 77 ---- tools/xtce_generation/xtce_generator_mag.py | 49 -- tools/xtce_generation/xtce_generator_swapi.py | 41 -- tools/xtce_generation/xtce_generator_swe.py | 43 -- .../xtce_generator_template.py | 112 ----- 18 files changed, 505 insertions(+), 1052 deletions(-) create mode 100644 imap_processing/ccsds/excel_to_xtce.py create mode 100644 imap_processing/tests/ccsds/test_excel_to_xtce.py delete mode 100644 tools/tests/test_xtce_generator_template.py delete mode 100644 tools/xtce_generation/26850.02-TLMDEF-04.xlsx delete mode 100644 tools/xtce_generation/TLM_SWP_20231006-121021.xlsx delete mode 100644 tools/xtce_generation/ccsds_header_xtce_generator.py delete mode 100644 tools/xtce_generation/telemetry_generator.py delete mode 100644 tools/xtce_generation/xtce_generator_codice.py delete mode 100644 tools/xtce_generation/xtce_generator_glows.py delete mode 100644 tools/xtce_generation/xtce_generator_hi.py delete mode 100644 tools/xtce_generation/xtce_generator_hit.py delete mode 100644 tools/xtce_generation/xtce_generator_lo.py delete mode 100644 tools/xtce_generation/xtce_generator_mag.py delete mode 100644 tools/xtce_generation/xtce_generator_swapi.py delete mode 100644 tools/xtce_generation/xtce_generator_swe.py delete mode 100644 tools/xtce_generation/xtce_generator_template.py diff --git a/imap_processing/ccsds/excel_to_xtce.py b/imap_processing/ccsds/excel_to_xtce.py new file mode 100644 index 000000000..69ca2c93b --- /dev/null +++ b/imap_processing/ccsds/excel_to_xtce.py @@ -0,0 +1,433 @@ +""" +Convert an Excel file of packet definitions into the XTCE format. + +This script reads in an Excel file containing packet definitions and converts +them into an XTCE file. + +.. code:: + imap_xtce /path/to/excel_file.xlsx --output /path/to/output.xml +""" + +import argparse +import xml.etree.ElementTree as Et +from importlib.util import find_spec +from pathlib import Path + +import pandas as pd + +_CCSDS_PARAMETERS = [ + { + "name": "VERSION", + "lengthInBits": 3, + "description": "CCSDS Packet Version Number (always 0)", + }, + { + "name": "TYPE", + "lengthInBits": 1, + "description": "CCSDS Packet Type Indicator (0=telemetry)", + }, + { + "name": "SEC_HDR_FLG", + "lengthInBits": 1, + "description": "CCSDS Packet Secondary Header Flag (always 1)", + }, + { + "name": "PKT_APID", + "lengthInBits": 11, + "description": "CCSDS Packet Application Process ID", + }, + { + "name": "SEQ_FLGS", + "lengthInBits": 2, + "description": "CCSDS Packet Grouping Flags (3=not part of group)", + }, + { + "name": "SRC_SEQ_CTR", + "lengthInBits": 14, + "description": "CCSDS Packet Sequence Count " + "(increments with each new packet)", + }, + { + "name": "PKT_LEN", + "lengthInBits": 16, + "description": "CCSDS Packet Length " + "(number of bytes after Packet length minus 1)", + }, +] + + +class XTCEGenerator: + """ + Automatically generate XTCE files from excel definition files. + + The excel file should have the following columns: mnemonic, sequence, lengthInBits, + startBit, dataType, convertAs, units, source, and either shortDescription or + longDescription. + + This class will correctly generate a CCSDS header and the provided data types. + + It is intended for use as a first pass of XTCE generation, and most cases, the + packet definitions will require manual updates. + + Use ``to_xml`` to write the output xml file. + + Parameters + ---------- + path_to_excel_file : Path + Path to the excel file. + """ + + def __init__(self, path_to_excel_file: Path): + # Read in all sheets from the excel file + self.sheets = pd.read_excel(path_to_excel_file, sheet_name=None) + # Set up the packet mapping from packetName to Apid + packet_sheet = self.sheets["Packets"] + if "apId" not in packet_sheet.columns: + # Create the apId column from the apIdHex (base=0 works with the 0x prefix) + packet_sheet["apId"] = packet_sheet["apIdHex"].apply(int, base=0) + self._packet_mapping = packet_sheet.set_index("packetName")["apId"].to_dict() + + # Create the XML containers that will be populated later + self._setup_xml_containers() + # Add the CCSDS Header information to the containers + self._setup_ccsds_header() + # Create the sequence containers (also adding parameters within) + self._create_container_sets() + + def _setup_xml_containers(self) -> None: + """Create an XML representation of telemetry data.""" + # Register the XML namespace + source_link = "http://www.omg.org/space/xtce" + Et.register_namespace("xtce", source_link) + + # Create the root element and add namespaces + root = Et.Element("xtce:SpaceSystem") + self._root = root + root.attrib["xmlns:xtce"] = source_link + # Subsystem sheet name is used as the base name for this XTCE definition + subsystem = self.sheets["Subsystem"] + root.attrib["name"] = str( + subsystem.loc[subsystem["infoField"] == "subsystem", "infoValue"].values[0] + ) + # Create the Header element with attributes 'date', 'version', and 'author' + # Versioning is used to keep track of changes to the XML file. + header = Et.SubElement(root, "xtce:Header") + header.attrib["date"] = str( + subsystem.loc[ + subsystem["infoField"] == "sheetReleaseDate", "infoValue" + ].values[0] + ) + header.attrib["version"] = str( + subsystem.loc[ + subsystem["infoField"] == "sheetReleaseRev", "infoValue" + ].values[0] + ) + header.attrib["author"] = "IMAP SDC" + + # Create the TelemetryMetaData element + self._telemetry_metadata = Et.SubElement(root, "xtce:TelemetryMetaData") + + # Create the ParameterTypeSet element + self._parameter_type_set = Et.SubElement( + self._telemetry_metadata, "xtce:ParameterTypeSet" + ) + + # Create the ParameterSet element + self._parameter_set = Et.SubElement( + self._telemetry_metadata, "xtce:ParameterSet" + ) + + # Create ContainerSet element + self._container_sets = Et.SubElement( + self._telemetry_metadata, "xtce:ContainerSet" + ) + + def _setup_ccsds_header(self) -> None: + """Fill in the default CCSDS header information.""" + # Create CCSDSPacket SequenceContainer + ccsds_container = Et.SubElement(self._container_sets, "xtce:SequenceContainer") + ccsds_container.attrib["name"] = "CCSDSPacket" + ccsds_container.attrib["abstract"] = "true" + ccsds_entry_list = Et.SubElement(ccsds_container, "xtce:EntryList") + + # Populate EntryList for CCSDSPacket SequenceContainer + for parameter_data in _CCSDS_PARAMETERS: + parameter_ref_entry = Et.SubElement( + ccsds_entry_list, "xtce:ParameterRefEntry" + ) + name = str(parameter_data["name"]) + + parameter_ref_entry.attrib["parameterRef"] = name + + # Add the parameter to the ParameterSet + parameter = Et.SubElement(self._parameter_set, "xtce:Parameter") + parameter.attrib["name"] = name + parameter.attrib["parameterTypeRef"] = name + + description = Et.SubElement(parameter, "xtce:LongDescription") + description.text = str(parameter_data["description"]) + + # Add the typeref to the parameter type set + parameter_type = Et.SubElement( + self._parameter_type_set, "xtce:IntegerParameterType" + ) + parameter_type.attrib["name"] = name + parameter_type.attrib["signed"] = "false" + + encoding = Et.SubElement(parameter_type, "xtce:IntegerDataEncoding") + encoding.attrib["sizeInBits"] = str(parameter_data["lengthInBits"]) + encoding.attrib["encoding"] = "unsigned" + + def _create_container_sets(self) -> None: + """Create a container set for each packet in the Excel file.""" + # Iterate over all packets and create Packet SequenceContainers + for packet_name, apid in self._packet_mapping.items(): + # Populate EntryList for packet SequenceContainers + # The sheets are sometimes prefixed with P_, so we need to try both options + try: + packet_df = self.sheets[packet_name] + except KeyError: + try: + packet_df = self.sheets[f"P_{packet_name}"] + except KeyError: + print( + f"Packet definition for {packet_name} " + "not found in the excel file." + ) + continue + + # Create Packet SequenceContainer that use the CCSDSPacket SequenceContainer + # as the base container + science_container = Et.SubElement( + self._container_sets, "xtce:SequenceContainer" + ) + science_container.attrib["name"] = packet_name + + # Every container should inherit from the base container, CCSDSPacket + base_container = Et.SubElement(science_container, "xtce:BaseContainer") + base_container.attrib["containerRef"] = "CCSDSPacket" + + # Add RestrictionCriteria element to use the given APID for comparison + restriction_criteria = Et.SubElement( + base_container, "xtce:RestrictionCriteria" + ) + comparison = Et.SubElement(restriction_criteria, "xtce:Comparison") + comparison.attrib["parameterRef"] = "PKT_APID" + comparison.attrib["value"] = str(apid) + comparison.attrib["useCalibratedValue"] = "false" + + packet_entry_list = Et.SubElement(science_container, "xtce:EntryList") + # Needed for dynamic binary packet length + total_packet_bits = int(packet_df["lengthInBits"].sum()) + for i, row in packet_df.iterrows(): + if i < 7: + # Skip first 7 rows as they are the CCSDS header elements + continue + if pd.isna(row.get("packetName")): + # This is a poorly formatted row, skip it + continue + name = f"{row['packetName']}_{row['mnemonic']}" + parameter_ref_entry = Et.SubElement( + packet_entry_list, "xtce:ParameterRefEntry" + ) + parameter_ref_entry.attrib["parameterRef"] = name + # Add this parameter to the ParameterSet too + self._add_parameter(row, total_packet_bits) + + def _add_parameter(self, row: pd.Series, total_packet_bits: int) -> None: + """ + Row from a packet definition to be added to the XTCE file. + + Parameters + ---------- + row : pandas.Row + Row to be added to the XTCE file, containing mnemonic, lengthInBits, ... + total_packet_bits : int + Total number of bits in the packet, as summed from the lengthInBits column. + """ + parameter = Et.SubElement(self._parameter_set, "xtce:Parameter") + # Combine the packet name and mnemonic to create a unique parameter name + name = f"{row['packetName']}_{row['mnemonic']}" + parameter.attrib["name"] = name + # UINT8, ... + parameter.attrib["parameterTypeRef"] = name + + # Add descriptions if they exist + if pd.notna(row.get("shortDescription")): + parameter.attrib["shortDescription"] = row.get("shortDescription") + if pd.notna(row.get("longDescription")): + description = Et.SubElement(parameter, "xtce:LongDescription") + description.text = row.get("longDescription") + + length_in_bits = int(row["lengthInBits"]) + + # Add the parameterTypeRef for this row + if "UINT" in row["dataType"]: + parameter_type = Et.SubElement( + self._parameter_type_set, "xtce:IntegerParameterType" + ) + parameter_type.attrib["name"] = name + parameter_type.attrib["signed"] = "false" + + encoding = Et.SubElement(parameter_type, "xtce:IntegerDataEncoding") + encoding.attrib["sizeInBits"] = str(length_in_bits) + encoding.attrib["encoding"] = "unsigned" + + elif any(x in row["dataType"] for x in ["SINT", "INT"]): + parameter_type = Et.SubElement( + self._parameter_type_set, "xtce:IntegerParameterType" + ) + parameter_type.attrib["name"] = name + parameter_type.attrib["signed"] = "true" + encoding = Et.SubElement(parameter_type, "xtce:IntegerDataEncoding") + encoding.attrib["sizeInBits"] = str(length_in_bits) + encoding.attrib["encoding"] = "signed" + + elif "BYTE" in row["dataType"]: + parameter_type = Et.SubElement( + self._parameter_type_set, "xtce:BinaryParameterType" + ) + parameter_type.attrib["name"] = name + + encoding = Et.SubElement(parameter_type, "xtce:BinaryDataEncoding") + encoding.attrib["bitOrder"] = "mostSignificantBitFirst" + + size_in_bits = Et.SubElement(encoding, "xtce:SizeInBits") + + # If it is a byte field consider it a dynamic value. + dynamic_value = Et.SubElement(size_in_bits, "xtce:DynamicValue") + param_ref = Et.SubElement(dynamic_value, "xtce:ParameterInstanceRef") + param_ref.attrib["parameterRef"] = "PKT_LEN" + linear_adjustment = Et.SubElement(dynamic_value, "xtce:LinearAdjustment") + linear_adjustment.attrib["slope"] = str(8) + # The length of all other variables (other than this specific one) + other_variable_bits = total_packet_bits - length_in_bits + # PKT_LEN == number of bytes in the packet data field - 1 + # So we need to subtract the header bytes plus 1 to get the offset + # The amount to subtract to get the intercept is then: + # number of other bits in the packet - (6 + 1) * 8 + linear_adjustment.attrib["intercept"] = str(-int(other_variable_bits - 56)) + + # TODO: Do we want to allow fixed length values? + # fixed_value = Et.SubElement(size_in_bits, "xtce:FixedValue") + # fixed_value.text = str(row["lengthInBits"]) + + if row["convertAs"] == "ANALOG": + # Go look up the conversion in the AnalogConversions tab + # and add it to the encoding + self._add_analog_conversion(row, encoding) + + def _add_analog_conversion(self, row: pd.Series, encoding: Et.Element) -> None: + """ + Add an analog conversion to the encoding element. + + Parameters + ---------- + row : pandas.Row + Row to be added to the XTCE file, containing mnemonic, packetName. + encoding : Element + The encoding element to add the conversion to. + """ + # Look up the conversion in the AnalogConversions tab + analog_conversion = self.sheets["AnalogConversions"] + # conversion is a row from the AnalogConversions sheet + conversion = analog_conversion.loc[ + (analog_conversion["mnemonic"] == row["mnemonic"]) + & (analog_conversion["packetName"] == row["packetName"]) + ].iloc[0] + + # Create the Conversion element + default_calibrator = Et.SubElement(encoding, "xtce:DefaultCalibrator") + polynomial_calibrator = Et.SubElement( + default_calibrator, "xtce:PolynomialCalibrator" + ) + # FIXME: Use lowValue / highValue from the conversion sheet + # FIXME: Handle segmented polynomials (only using first segment now) + for i in range(8): + col = f"c{i}" + if conversion[col] != 0: + term = Et.SubElement(polynomial_calibrator, "xtce:Term") + term.attrib["coefficient"] = str(conversion[col]) + term.attrib["exponent"] = str(i) + + def to_xml(self, output_xml_path: Path) -> None: + """ + Create and output an XTCE file from the Element Tree representation. + + Parameters + ---------- + output_xml_path : Path + Path to the output XML file. + """ + # Create the XML tree and save the document + tree = Et.ElementTree(self._root) + Et.indent(tree, space="\t", level=0) + + # Use the provided output_xml_path + tree.write(output_xml_path, encoding="utf-8", xml_declaration=True) + + +# Function to parse command line arguments +def _parse_args() -> argparse.Namespace: + """ + Parse the command line arguments. + + The expected input format is a required argument of "/path/to/excel_file.xlsx" + with an optional argument containing the output path for the XTCE file + "/path/to/output.xml". + + Returns + ------- + args : argparse.Namespace + An object containing the parsed arguments and their values. + """ + description = ( + "This command line program generates an instrument specific XTCE file. " + "Example usage: imap_xtce " + "path/to/excel_packet_file.xlsx --output path/to/output_packet_definition.xml" + ) + output_help = ( + "Where to save the output XTCE file. " + "If not provided, the input file name will be used with a " + ".xml extension." + ) + file_path_help = "Provide the full path to the input excel file." + + parser = argparse.ArgumentParser(prog="imap_xtce", description=description) + parser.add_argument("excel_file", type=Path, help=file_path_help) + parser.add_argument("--output", type=Path, required=False, help=output_help) + + if not find_spec("openpyxl"): + parser.error( + "The openpyxl package is required for this script. " + "Please install it using 'pip install openpyxl'." + ) + + args = parser.parse_args() + + if not args.excel_file.exists(): + parser.error(f"File not found: {args.excel_file}") + + if not args.output: + args.output = args.excel_file.with_suffix(".xml") + + return args + + +def main() -> None: + """ + Generate xtce file from CLI information given. + + The xtce file will be written in an instrument specific subfolder. + """ + # Parse arguments from the command line + args = _parse_args() + + xtce_generator = XTCEGenerator( + path_to_excel_file=args.excel_file, + ) + xtce_generator.to_xml(args.output) + + +if __name__ == "__main__": + main() diff --git a/imap_processing/tests/ccsds/test_excel_to_xtce.py b/imap_processing/tests/ccsds/test_excel_to_xtce.py new file mode 100644 index 000000000..efecee5ad --- /dev/null +++ b/imap_processing/tests/ccsds/test_excel_to_xtce.py @@ -0,0 +1,60 @@ +"""Testing for xtce generator template.""" + +import sys +from pathlib import Path +from unittest import mock + +import pytest + +from imap_processing.ccsds import excel_to_xtce + +pytest.importorskip("openpyxl") + + +@pytest.fixture() +def filepath(tmpdir): + p = Path(tmpdir / "test_file.xlsx").resolve() + p.touch() + return p + + +# General test +@mock.patch("imap_processing.ccsds.excel_to_xtce.XTCEGenerator") +def test_main_general(mock_input, filepath): + """Testing base main function.""" + test_args = [ + "test_script", + "--output", + "swe.xml", + f"{filepath}", + ] + with mock.patch.object(sys, "argv", test_args): + excel_to_xtce.main() + mock_input.assert_called_once() + + +# Testing without required arguments +def test_main_inval_arg(): + """Testing with invalid instrument.""" + test_args = [ + "test_script", + "--output", + "glows.xml", + ] + with mock.patch.object(sys, "argv", test_args): + with pytest.raises(SystemExit): + excel_to_xtce.main() + + +# File does not exist +def test_main_inval_file(): + """Testing with invalid file.""" + test_args = [ + "test_script", + "--instrument", + "glows", + "not-a-valid-file.txt", + ] + with mock.patch.object(sys, "argv", test_args): + with pytest.raises(SystemExit): + excel_to_xtce.main() diff --git a/poetry.lock b/poetry.lock index 88066b2e0..e24e2123e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1143,6 +1143,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1150,8 +1151,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1168,6 +1177,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1175,6 +1185,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, diff --git a/pyproject.toml b/pyproject.toml index e353ddaea..a84c2e0ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,14 +86,13 @@ lint.ignore = ["D104", "PLR2004", "RUF200", "S311"] # S603 unchecked input in subprocess call is fine in our tests # PT006 Wrong type passed to first argument of @pytest.mark.parametrize "*/tests/*" = ["D", "S101", "S603", "PT006"] -"tools/xtce*" = ["D"] [tool.ruff.lint.pydocstyle] convention = "numpy" [tool.poetry.scripts] imap_cli = 'imap_processing.cli:main' -imap_xtce = 'tools.xtce_generation.xtce_generator_template:main' +imap_xtce = 'imap_processing.ccsds.excel_to_xtce:main' [tool.codespell] ignore-words-list = "livetime" diff --git a/tools/tests/test_xtce_generator_template.py b/tools/tests/test_xtce_generator_template.py deleted file mode 100644 index 1b95d298c..000000000 --- a/tools/tests/test_xtce_generator_template.py +++ /dev/null @@ -1,86 +0,0 @@ -"""Testing for xtce generator template.""" - -import sys -from pathlib import Path -from unittest import mock - -import pytest - -from tools.xtce_generation.xtce_generator_template import main - - -# Create filepath for testing -def filepath(file_name: str): - file_path = Path(__file__).parent.parent / "xtce_generation" / file_name - return str(file_path) - - -# General test -@mock.patch("tools.xtce_generation.xtce_generator_template.main") -def test_main_general(mock_input): - """Testing base main function.""" - test_args = [ - "test_script", - "--instrument", - "swe", - "--file-path", - f"{filepath('TLM_SWP_20231006-121021.xlsx')}", - "--packets", - '{"P_SWP_HK": 1184, "P_SWP_SCI": 1188, "P_SWP_AUT": 1192}', - ] - with mock.patch.object(sys, "argv", test_args): - main() - - -# Testing invalid instrument -@mock.patch("tools.xtce_generation.xtce_generator_template.main") -def test_main_inval_instr(mock_input): - """Testing with invalid instrument.""" - test_args = [ - "test_script", - "--instrument", - "ana", - "--file-path", - f"{filepath('TLM_SWP_20231006-121021.xlsx')}", - "--packets", - '{"P_SWP_HK": 1184, "P_SWP_SCI": 1188, "P_SWP_AUT": 1192}', - ] - with mock.patch.object(sys, "argv", test_args): - with pytest.raises( - ValueError, match="ana is not in the supported instrument list:" - ): - main() - - -# Testing without required arguments -@mock.patch("tools.xtce_generation.xtce_generator_template.main") -def test_main_inval_arg(mock_input): - """Testing with invalid instrument.""" - test_args = [ - "test_script", - "--instrument", - "glows", - "--packets", - '{"P_SWP_HK": 1184, "P_SWP_SCI": 1188, "P_SWP_AUT": 1192}', - ] - with mock.patch.object(sys, "argv", test_args): - with pytest.raises(SystemExit): - main() - - -# File does not exist -@mock.patch("tools.xtce_generation.xtce_generator_template.main") -def test_main_inval_file(mock_input): - """Testing with invalid file.""" - test_args = [ - "test_script", - "--instrument", - "glows", - "--file-path", - "This file is silly", - "--packets", - '{"P_SWP_HK": 1184, "P_SWP_SCI": 1188, "P_SWP_AUT": 1192}', - ] - with mock.patch.object(sys, "argv", test_args): - with pytest.raises(FileNotFoundError): - main() diff --git a/tools/xtce_generation/26850.02-TLMDEF-04.xlsx b/tools/xtce_generation/26850.02-TLMDEF-04.xlsx deleted file mode 100644 index 44df5af1ac34641b4c3a82d0295d1a43f33e60c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50414 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 EAGf9QD*ylh diff --git a/tools/xtce_generation/TLM_SWP_20231006-121021.xlsx b/tools/xtce_generation/TLM_SWP_20231006-121021.xlsx deleted file mode 100644 index d7a9bb0f1984044bde5d7abc71c21f7e23ca99a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48631 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 None: - self.parameters = [ - { - "name": "VERSION", - "parameterTypeRef": "UINT3", - "description": "CCSDS Packet Version Number (always 0)", - }, - { - "name": "TYPE", - "parameterTypeRef": "UINT1", - "description": "CCSDS Packet Type Indicator (0=telemetry)", - }, - { - "name": "SEC_HDR_FLG", - "parameterTypeRef": "UINT1", - "description": "CCSDS Packet Secondary Header Flag (always 1)", - }, - { - "name": "PKT_APID", - "parameterTypeRef": "UINT11", - "description": "CCSDS Packet Application Process ID", - }, - { - "name": "SEQ_FLGS", - "parameterTypeRef": "UINT2", - "description": "CCSDS Packet Grouping Flags (3=not part of group)", - }, - { - "name": "SRC_SEQ_CTR", - "parameterTypeRef": "UINT14", - "description": "CCSDS Packet Sequence Count " - "(increments with each new packet)", - }, - { - "name": "PKT_LEN", - "parameterTypeRef": "UINT16", - "description": "CCSDS Packet Length " - "(number of bytes after Packet length minus 1)", - }, - ] - - -# Other utility functions related to CCSDS parameters can also be added here diff --git a/tools/xtce_generation/telemetry_generator.py b/tools/xtce_generation/telemetry_generator.py deleted file mode 100644 index 2d7560598..000000000 --- a/tools/xtce_generation/telemetry_generator.py +++ /dev/null @@ -1,393 +0,0 @@ -"""Class Creation for TelemetryGenerator.""" - -import xml.etree.ElementTree as Et -from datetime import datetime -from typing import Optional - -import pandas as pd - -from tools.xtce_generation.ccsds_header_xtce_generator import CCSDSParameters - - -class TelemetryGenerator: - """ - This class will automatically generate XTCE files from excel definition files. - - The excel file should have the following columns: mnemonic, sequence, lengthInBits, - startBit, dataType, convertAs, units, source, and either shortDescription or - longDescription. - - This class will correctly generate a CCSDS header and the provided data types. - It doesn't handle the following cases: - - Enum generation - - Variable packet lengths - - Multiple APIDs or complex APID comparison - - It is intended for use as a first pass of XTCE generation, and most cases, the - packet definitions will require manual updates. - - Use generate_telemetry_xml to create XML files. - - Parameters - ---------- - packet_name : str - Name of packet. - path_to_excel_file : dict todo check - Path to the excel file. - apid : int todo check - The application ID associated with the telemetry packet. - pkt : str todo check - Default set to None. - """ - - def __init__( - self, - packet_name: str, - path_to_excel_file: str, - apid: int, - pkt: Optional[str] = None, - ) -> None: - """Initialize TelemetryGenerator.""" - self.packet_name = packet_name - self.apid = apid - self.source_link = "http://www.omg.org/space/xtce" - - if pkt is None: - # Read excel sheet - xls = pd.ExcelFile(path_to_excel_file) - self.pkt = xls.parse(packet_name) - else: - self.pkt = pkt - - def create_telemetry_xml(self) -> tuple: - """ - Create an XML representation of telemetry data based on input parameters. - - Returns - ------- - root - The root element of the generated XML tree. - parameter_type_set - The ParameterTypeSet element. - parameter_set - The ParameterSet element. - telemetry_metadata - The TelemetryMetaData element. - """ - # Register the XML namespace - Et.register_namespace("xtce", self.source_link) - - # Get the current date and time - current_date = datetime.now().strftime("%Y-%m") - - # Create the root element and add namespaces - root = Et.Element("xtce:SpaceSystem") - root.attrib["xmlns:xtce"] = f"{self.source_link}" - root.attrib["name"] = self.packet_name - # xmlns:xtce="http://www.omg.org/space/xtce" - - # Create the Header element with attributes 'date', 'version', and 'author' - # Versioning is used to keep track of changes to the XML file. - header = Et.SubElement(root, "xtce:Header") - header.attrib["date"] = current_date - header.attrib["version"] = "1.0" - header.attrib["author"] = "IMAP SDC" - - # Create the TelemetryMetaData element - telemetry_metadata = Et.SubElement(root, "xtce:TelemetryMetaData") - - # Create the ParameterTypeSet element - parameter_type_set = Et.SubElement(telemetry_metadata, "xtce:ParameterTypeSet") - - # Create the ParameterSet element - parameter_set = Et.SubElement(telemetry_metadata, "xtce:ParameterSet") - - return root, parameter_type_set, parameter_set, telemetry_metadata - - def get_unique_bits_length(self) -> dict: - """ - Create dictionary. - - Get unique values from the 'lengthInBits' column and create dictionary - with key and value using the dataType and lengthInBits. - - Returns - ------- - unique_lengths : dict - Dictionary containing all unique bits lengths. - - Examples - -------- - { - 'UINT16': 16, - 'UINT32': 32, - 'BYTE13000': 13000, - } - """ - # Extract unique values from the 'lengthInBits' column - length_in_bits = self.pkt["lengthInBits"] - data_types = self.pkt["dataType"] - unique_lengths = {} - for index in range(len(length_in_bits)): - # Here, we are creating a dictionary like this: - # { - # 'UINT16': 16, - # 'UINT32': 32, - # 'BYTE13000': 13000, - # } - unique_lengths[f"{data_types[index]}{length_in_bits[index]}"] = ( - length_in_bits[index] - ) - # Sort the dictionary by the value (lengthInBits) - unique_lengths = dict(sorted(unique_lengths.items(), key=lambda item: item[1])) - return unique_lengths - - def create_parameter_types( - self, - parameter_type_set: Et.Element, - unique_lengths: dict, - ) -> Et.Element: - """ - Create parameter types based on 'dataType' for the unique 'lengthInBits' values. - - This will loop through the unique lengths and create a ParameterType element - for each length representing a data type. - - Parameters - ---------- - parameter_type_set : Et.Element - The ParameterTypeSet element where parameter types are. - unique_lengths : dict - Unique values from the 'lengthInBits' column. - - Returns - ------- - parameter_type_set : Et.Element - The updated ParameterTypeSet element. - """ - for parameter_type_ref_name, size in unique_lengths.items(): - if "UINT" in parameter_type_ref_name: - parameter_type = Et.SubElement( - parameter_type_set, - "xtce:IntegerParameterType", - ) - parameter_type.attrib["name"] = parameter_type_ref_name - parameter_type.attrib["signed"] = "false" - - encoding = Et.SubElement(parameter_type, "xtce:IntegerDataEncoding") - encoding.attrib["sizeInBits"] = str(size) - encoding.attrib["encoding"] = "unsigned" - - elif any(x in parameter_type_ref_name for x in ["SINT", "INT"]): - parameter_type = Et.SubElement( - parameter_type_set, - "xtce:IntegerParameterType", - ) - parameter_type.attrib["name"] = parameter_type_ref_name - parameter_type.attrib["signed"] = "true" - encoding = Et.SubElement(parameter_type, "xtce:IntegerDataEncoding") - encoding.attrib["sizeInBits"] = str(size) - encoding.attrib["encoding"] = "signed" - - elif "BYTE" in parameter_type_ref_name: - binary_parameter_type = Et.SubElement( - parameter_type_set, - "xtce:BinaryParameterType", - ) - binary_parameter_type.attrib["name"] = parameter_type_ref_name - - Et.SubElement(binary_parameter_type, "xtce:UnitSet") - - binary_data_encoding = Et.SubElement( - binary_parameter_type, "xtce:BinaryDataEncoding" - ) - binary_data_encoding.attrib["bitOrder"] = "mostSignificantBitFirst" - - size_in_bits = Et.SubElement(binary_data_encoding, "xtce:SizeInBits") - fixed_value = Et.SubElement(size_in_bits, "xtce:FixedValue") - fixed_value.text = str(size) - - return parameter_type_set - - def create_ccsds_packet_parameters( - self, parameter_set: Et.Element, ccsds_parameters: list - ) -> Et.Element: - """ - Create XML elements to define CCSDS packet parameters based on the given data. - - Parameters - ---------- - parameter_set : Et.Element - The ParameterSet element where parameters will be added. - ccsds_parameters : list - A list of dictionaries containing CCSDS parameter data. - - Returns - ------- - parameter_set : Et.Element - The updated ParameterSet element. - """ - for parameter_data in ccsds_parameters: - parameter = Et.SubElement(parameter_set, "xtce:Parameter") - parameter.attrib["name"] = parameter_data["name"] - parameter.attrib["parameterTypeRef"] = parameter_data["parameterTypeRef"] - - description = Et.SubElement(parameter, "xtce:LongDescription") - description.text = parameter_data["description"] - - return parameter_set - - def create_container_set( - self, - telemetry_metadata: Et.Element, - ccsds_parameters: list, - container_name: str, - ) -> Et.Element: - """ - Create XML elements. - - These elements are for ContainerSet, CCSDSPacket SequenceContainer, - and Packet SequenceContainer. - - Parameters - ---------- - telemetry_metadata : Et.Element - The TelemetryMetaData element where containers are. - ccsds_parameters : list - A list of dictionaries containing CCSDS parameter data. - container_name : str - The name of sequence container. - - Returns - ------- - telemetry_metadata : Et.Element - The updated TelemetryMetaData element. - """ - # Create ContainerSet element - container_set = Et.SubElement(telemetry_metadata, "xtce:ContainerSet") - # Create CCSDSPacket SequenceContainer - ccsds_packet_container = Et.SubElement(container_set, "xtce:SequenceContainer") - ccsds_packet_container.attrib["name"] = "CCSDSPacket" - ccsds_packet_entry_list = Et.SubElement( - ccsds_packet_container, "xtce:EntryList" - ) - - # Populate EntryList for CCSDSPacket SequenceContainer - for parameter_data in ccsds_parameters: - parameter_ref_entry = Et.SubElement( - ccsds_packet_entry_list, "xtce:ParameterRefEntry" - ) - parameter_ref_entry.attrib["parameterRef"] = parameter_data["name"] - - # Create Packet SequenceContainer that use CCSDSPacket SequenceContainer - # as base container - science_container = Et.SubElement(container_set, "xtce:SequenceContainer") - science_container.attrib["name"] = container_name - - base_container = Et.SubElement(science_container, "xtce:BaseContainer") - base_container.attrib["containerRef"] = "CCSDSPacket" - - # Add RestrictionCriteria element to use the given APID for comparison - restriction_criteria = Et.SubElement(base_container, "xtce:RestrictionCriteria") - comparison = Et.SubElement(restriction_criteria, "xtce:Comparison") - comparison.attrib["parameterRef"] = "PKT_APID" - comparison.attrib["value"] = f"{self.apid}" - comparison.attrib["useCalibratedValue"] = "false" - - # Populate EntryList for packet SequenceContainer - packet_entry_list = Et.SubElement(science_container, "xtce:EntryList") - parameter_refs = self.pkt.loc[7:, "mnemonic"].tolist() - - for parameter_ref in parameter_refs: - parameter_ref_entry = Et.SubElement( - packet_entry_list, "xtce:ParameterRefEntry" - ) - parameter_ref_entry.attrib["parameterRef"] = parameter_ref - - return telemetry_metadata - - def create_remaining_parameters(self, parameter_set: Et.Element) -> Et.Element: - """ - Create XML elements for parameters. - - These are based on DataFrame rows starting from SHCOARSE (also known as MET). - - Parameters - ---------- - parameter_set : Et.Element - The ParameterSet element where parameters will be added. - - Returns - ------- - parameter_set : Et.Element - The updated ParameterSet element. - """ - # Process rows from SHCOARSE until the last available row in the DataFrame - for index, row in self.pkt.iterrows(): - if index < 7: - # Skip rows until SHCOARSE(also known as MET) which are - # not part of CCSDS header - continue - - parameter = Et.SubElement(parameter_set, "xtce:Parameter") - parameter.attrib["name"] = row["mnemonic"] - parameter_type_ref = f"{row['dataType']}{row['lengthInBits']}" - - parameter.attrib["parameterTypeRef"] = parameter_type_ref - - # Add descriptions if they exist - if pd.notna(row.get("shortDescription")): - parameter.attrib["shortDescription"] = row.get("shortDescription") - if pd.notna(row.get("longDescription")): - description = Et.SubElement(parameter, "xtce:LongDescription") - description.text = row.get("longDescription") - - return parameter_set - - def generate_telemetry_xml(self, output_xml_path: str, container_name: str) -> None: - """ - Create and output an XTCE file based on the data within the class. - - Parameters - ---------- - output_xml_path : str - The path for the final xml file. - container_name : str - The name of the sequence container in the output xml. - """ - - unique_bits_lengths_data = self.get_unique_bits_length() - - # Here, we create the XML components so that we can add data in following steps - ( - telemetry_xml_root, - parameter_type_set, - parameter_set, - telemetry_metadata, - ) = self.create_telemetry_xml() - - parameter_type_set = self.create_parameter_types( - parameter_type_set, unique_bits_lengths_data - ) - - # Create CCSDSPacket parameters and add them to the ParameterSet element - parameter_set = self.create_ccsds_packet_parameters( - parameter_set, CCSDSParameters().parameters - ) - # Add remaining parameters to the ParameterSet element - parameter_set = self.create_remaining_parameters(parameter_set) - - # Create ContainerSet with CCSDSPacket SequenceContainer and packet - # SequenceContainer - telemetry_metadata = self.create_container_set( - telemetry_metadata, - CCSDSParameters().parameters, - container_name=container_name, - ) - - # Create the XML tree and save the document - tree = Et.ElementTree(telemetry_xml_root) - Et.indent(tree, space="\t", level=0) - - # Use the provided output_xml_path - tree.write(output_xml_path, encoding="utf-8", xml_declaration=True) diff --git a/tools/xtce_generation/xtce_generator_codice.py b/tools/xtce_generation/xtce_generator_codice.py deleted file mode 100644 index 76d8fc6ad..000000000 --- a/tools/xtce_generation/xtce_generator_codice.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for codice. - -This module provides functionality to generate XTCE files for telemetry packets -for codice. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -def main() -> None: - """Function used by instrument to generate XTCE.""" - instrument_name = "codice" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - path_to_excel_file = "TLM_COD.xlsx" - - # CoDICE packets - packets = { - "P_COD_HSKP": 1136, - "P_COD_LO_IAL": 1152, - "P_COD_LO_PHA": 1153, - "P_COD_LO_SW_PRIORITY": 1155, - "P_COD_LO_SW_SPECIES": 1156, - "P_COD_LO_NSW_SPECIES": 1157, - "P_COD_LO_SW_ANGULAR": 1158, - "P_COD_LO_NSW_ANGULAR": 1159, - "P_COD_LO_NSW_PRIORITY": 1160, - "P_COD_LO_INST_COUNTS_AGGREGATED": 1161, - "P_COD_LO_INST_COUNTS_SINGLES": 1162, - "P_COD_HI_IAL": 1168, - "P_COD_HI_PHA": 1169, - "P_COD_HI_INST_COUNTS_AGGREGATED": 1170, - "P_COD_HI_INST_COUNTS_SINGLES": 1171, - "P_COD_HI_OMNI_SPECIES_COUNTS": 1172, - "P_COD_HI_SECT_SPECIES_COUNTS": 1173, - } - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_glows.py b/tools/xtce_generation/xtce_generator_glows.py deleted file mode 100644 index 31a9a9669..000000000 --- a/tools/xtce_generation/xtce_generator_glows.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for glows. - -This module provides functionality to generate XTCE files for telemetry packets -for glows. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -def main() -> None: - """ - Function is used to generate the GLOWS XTCE files for packet processing. - - This will create two XML files, P_GLX_TMSCDE.xml and P_GLX_TMSCHIST.xml. For - processing, these need to be manually combined into one file: GLX_COMBINED.xml. - - To do this, first duplicate P_GLX_TMSCHIST into a new file GLX_COMBINED.xml. - - Copy the DE specific parameter entries out of P_GLX_TMSCDE.xml. ALl the non-CCSDS - entries need to be copied into the combined XML file. This should go under the - combined ParameterSet tag. Then, remove the "SEC" entry, as this is a duplicate. - - Finally, copy the SequenceContainer named "P_GLX_TMSCDE" out of P_GLX_TMSCDE.xml - into the ContainerSet in GLX_COMBINED. Then, delete the "SEC" entry. This is a - duplicate and will cause an error. - """ - - instrument_name = "glows" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - path_to_excel_file = f"{current_directory}/tlm_glx_2023_06_22-edited.xlsx" - - # Eg. - # packets = { - # "P_COD_HI_PHA": 1169, - # "P_COD_LO_PHA": 1153, - # "P_COD_LO_NSW_SPECIES_COUNTS": 1157, - # "P_COD_HI_OMNI_SPECIES_COUNTS": 1172, - # } - - packets = {"P_GLX_TMSCHIST": 1480, "P_GLX_TMSCDE": 1481} - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_hi.py b/tools/xtce_generation/xtce_generator_hi.py deleted file mode 100644 index 4a74000a4..000000000 --- a/tools/xtce_generation/xtce_generator_hi.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for hi. - -This module provides functionality to generate XTCE files for telemetry packets -for hi. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -def main() -> None: - """Function used by hi to generate XTCE.""" - - instrument_name = "hi" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - # NOTE: Copy packet definition to tools/xtce_generation/ folder - path_to_excel_file = f"{current_directory}/26850.02-TLMDEF-04.xlsx" - - packets = {"H45_APP_NHK": 754, "H45_SCI_CNT": 769, "H45_SCI_DE": 770} - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_hit.py b/tools/xtce_generation/xtce_generator_hit.py deleted file mode 100644 index a8428804f..000000000 --- a/tools/xtce_generation/xtce_generator_hit.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for hit. - -This module provides functionality to generate XTCE files for telemetry packets -for hit. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from telemetry_generator import TelemetryGenerator - -from imap_processing import imap_module_directory - - -def main() -> None: - """Function used by hit to generate XTCE.""" - instrument_name = "hit" - current_directory = Path(__file__).parent - packet_definition_path = ( - f"{imap_module_directory}/{instrument_name}/packet_definitions" - ) - path_to_excel_file = f"{current_directory}/TLM_HIT_modified.xlsx" - - # Lo packets - packets = { - "P_HIT_AUT": 1250, - "P_HIT_HSKP": 1251, - "P_HIT_SCIENCE": 1252, - "P_HIT_MSGLOG": 1254, - "P_HIT_MEMDUMP": 1255, - } - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_lo.py b/tools/xtce_generation/xtce_generator_lo.py deleted file mode 100644 index 536d14107..000000000 --- a/tools/xtce_generation/xtce_generator_lo.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for lo. - -This module provides functionality to generate XTCE files for telemetry packets -for lo. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from telemetry_generator import TelemetryGenerator - -# Following the creation of the XTCE files, manual updates need to be -# made to the following packets to make their binary field a variable -# length value in XTCE: -# P_ILO_BOOT_MEMDMP -# P_ILO_MEMDMP -# P_ILO_SCI_DE - - -def main() -> None: - """Function used by lo to generate XTCE.""" - instrument_name = "lo" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - # In the telemetry definition sheets, modifications need to be made to the - # P_ILO_RAW_DE and P_ILO_SCI_CNT. - # - # P_ILO_RAW_DE: the rows for bits 112 to 20591 were collapsed into a - # single binary row called RAW_DE. The reason for this is the number - # of fields in the packet caused the XTCE file to exceed the repo - # file size limit. Because of this modification, the binary field - # will need to be parsed in the python code. - # - # P_ILO_SCI_CNT: the rows for bits 80 to 26959 were collapsed into a - # single binary row called SCI_CNT. The reason for this is the number - # of fields in the packet caused the XTCE file to exceed the repo - # file size limit. Because of this modification, the binary field - # will need to be parsed in the python code. - path_to_excel_file = f"{current_directory}/telem_def_lo_modified.xls" - - # Lo packets - packets = { - "P_ILO_APP_NHK": 677, - "P_ILO_APP_SHK": 676, - "P_ILO_AUTO": 672, - "P_ILO_BOOT_HK": 673, - "P_ILO_BOOT_MEMDMP": 674, - "P_ILO_DIAG_BULK_HVPS": 724, - "P_ILO_DIAG_CDH": 721, - "P_ILO_DIAG_IFB": 722, - "P_ILO_DIAG_PCC": 725, - "P_ILO_DIAG_TOF_BD": 723, - "P_ILO_EVTMSG": 678, - "P_ILO_MEMDMP": 679, - "P_ILO_RAW_CNT": 689, - "P_ILO_RAW_DE": 690, - "P_ILO_RAW_STAR": 691, - "P_ILO_SCI_CNT": 705, - "P_ILO_SCI_DE": 706, - "P_ILO_SPIN": 708, - "P_ILO_STAR": 707, - } - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_mag.py b/tools/xtce_generation/xtce_generator_mag.py deleted file mode 100644 index b70b04176..000000000 --- a/tools/xtce_generation/xtce_generator_mag.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for mag. - -This module provides functionality to generate XTCE files for telemetry packets -for mag. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -def main() -> None: - """Function used by mag to generate XTCE.""" - - instrument_name = "mag" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - path_to_excel_file = f"{current_directory}/TLM_MAG_SCI.xls" - - # Eg. - # packets = { - # "P_COD_HI_PHA": 1169, - # "P_COD_LO_PHA": 1153, - # "P_COD_LO_NSW_SPECIES_COUNTS": 1157, - # "P_COD_HI_OMNI_SPECIES_COUNTS": 1172, - # } - - packets = { - "P_MAG_SCI_BURST": "1068", - "P_MAG_SCI_NORM": "1052", - } - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, - path_to_excel_file=path_to_excel_file, - apid=int(app_id), - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_swapi.py b/tools/xtce_generation/xtce_generator_swapi.py deleted file mode 100644 index 80fd26e62..000000000 --- a/tools/xtce_generation/xtce_generator_swapi.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for swapi. - -This module provides functionality to generate XTCE files for telemetry packets -for swapi. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -def main() -> None: - """Function used by swapi to generate XTCE.""" - - instrument_name = "swapi" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - # NOTE: Copy packet definition to tools/xtce_generation/ folder if hasn't already - path_to_excel_file = f"{current_directory}/TLM_SWP_20231006-121021.xlsx" - - packets = { - "P_SWP_HK": 1184, - "P_SWP_SCI": 1188, - "P_SWP_AUT": 1192, - } - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_swe.py b/tools/xtce_generation/xtce_generator_swe.py deleted file mode 100644 index dab0d89b0..000000000 --- a/tools/xtce_generation/xtce_generator_swe.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Module for generating XTCE files for telemetry packets for swe. - -This module provides functionality to generate XTCE files for telemetry packets -for swe. It includes a `TelemetryGenerator` class for creating XTCE files -based on packet definitions stored in an Excel file. -""" - -from pathlib import Path - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -def main() -> None: - """Function used by swe to generate XTCE.""" - - instrument_name = "swe" - current_directory = Path(__file__).parent - module_path = f"{current_directory}/../../imap_processing" - packet_definition_path = f"{module_path}/{instrument_name}/packet_definitions" - - path_to_excel_file = f"{current_directory}/TLM_SWE_20230904.xlsx" - - # SWE packets - packets = { - "P_SWE_APP_HK": 1330, - "P_SWE_EVTMSG": 1317, - "P_SWE_CEM_RAW": 1334, - "P_SWE_SCIENCE": 1344, - } - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, path_to_excel_file=path_to_excel_file, apid=app_id - ) - telemetry_generator.generate_telemetry_xml( - f"{packet_definition_path}/{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main() diff --git a/tools/xtce_generation/xtce_generator_template.py b/tools/xtce_generation/xtce_generator_template.py deleted file mode 100644 index 352075bfb..000000000 --- a/tools/xtce_generation/xtce_generator_template.py +++ /dev/null @@ -1,112 +0,0 @@ -"""XTCE generator script that uses CLI for generalized format.""" - -import argparse -import json -from pathlib import Path - -import imap_data_access - -from tools.xtce_generation.telemetry_generator import TelemetryGenerator - - -# Function to parse command line arguments -def _parse_args() -> argparse.Namespace: - """ - Parse the command line arguments. - - The expected input format is: - --instrument "instrument_name" - --file-path "full_file_path" - --packets '{"packet_name_1": app_id_1, "packet_name_2": app_id_2}' - - Returns - ------- - args : argparse.Namespace - An object containing the parsed arguments and their values. - """ - description = ( - "This command line program generates an instrument specific XTCE file." - "Example usage: " - '--instrument "swapi"' - "--file-path " - '"/Users/anma6676/Desktop/Repositories/imap_processing/tools/xtce_generation/TLM_SWP_20231006-121021.xlsx"' - "--packets '" - '{"P_SWP_HK": 1184, ' - '"P_SWP_SCI": 1188, ' - '"P_SWP_AUT": 1192, ' - "}'" - ) - instrument_help = ( - "The instrument to process. Acceptable values are: " - f"{imap_data_access.VALID_INSTRUMENTS}" - ) - file_path_help = "Provide full file path to write packets to." - packets_help = ( - "Provide packet dictionary using packet_name, and app_id." - '{"": }' - ) - - parser = argparse.ArgumentParser(prog="imap_xtce", description=description) - parser.add_argument("--instrument", type=str, required=True, help=instrument_help) - parser.add_argument("--file-path", type=str, required=True, help=file_path_help) - parser.add_argument("--packets", type=str, required=True, help=packets_help) - - args = parser.parse_args() - - return args - - -def _validate_args(args: argparse.Namespace) -> None: - """ - Ensure that the instrument is valid. - - Parameters - ---------- - args : argparse.Namespace - An object containing the parsed arguments and their values. - """ - if args.instrument not in imap_data_access.VALID_INSTRUMENTS: - raise ValueError( - f"{args.instrument} is not in the supported instrument list: " - f"{imap_data_access.VALID_INSTRUMENTS}" - ) - - directory = Path(args.file_path) - if not directory.exists(): - raise FileNotFoundError(f"{args.file_path} not found, and may not exist.") - - -def main() -> None: - """ - Generate xtce file from CLI information given. - - The xtce file will be written in an instrument specific subfolder. - """ - - # Parse arguments, and validate instrument - args = _parse_args() - _validate_args(args) - - instrument_name = args.instrument - current_directory = Path(__file__).parent.parent.parent - module_path = current_directory / "imap_processing" - packet_definition_path = module_path / instrument_name / "packet_definitions" - path_to_excel_file = args.file_path - - # Update packets dictionary with given CLI information - packets = json.loads(args.packets) - - for packet_name, app_id in packets.items(): - print(packet_name) - telemetry_generator = TelemetryGenerator( - packet_name=packet_name, - path_to_excel_file=path_to_excel_file, - apid=int(app_id), - ) - telemetry_generator.generate_telemetry_xml( - packet_definition_path / f"{packet_name}.xml", packet_name - ) - - -if __name__ == "__main__": - main()