diff --git a/.gitignore b/.gitignore index 169ad13..5292567 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,12 @@ OD.* # Beacon def formats *xtce.xml + +# MacOS +.DS_Store + +# Vim +*.swp + +# setuptools-scm +*/_version.py diff --git a/oresat_configs/__init__.py b/oresat_configs/__init__.py index 76fda6f..9e34a76 100644 --- a/oresat_configs/__init__.py +++ b/oresat_configs/__init__.py @@ -21,20 +21,28 @@ class OreSatConfig: """All the configs for an OreSat mission.""" - def __init__(self, oresat: Union[OreSatId, Consts, str]): - if isinstance(oresat, str): - oresat = Consts.from_string(oresat) - elif isinstance(oresat, OreSatId): - oresat = Consts.from_id(oresat) - elif not isinstance(oresat, Consts): - raise TypeError(f"Unsupported oresat type: '{type(oresat)}'") - - self.oresat = oresat - beacon_config = BeaconConfig.from_yaml(oresat.beacon_path) - self.cards = cards_from_csv(oresat) - self.configs = _load_configs(oresat.cards_path) - self.od_db = _gen_od_db(oresat, self.cards, beacon_config, self.configs) + def __init__(self, mission: Union[OreSatId, Consts, str]): + """The parameter mission may be: + - a string, either short or long mission name ('0', 'OreSat0.5', ...) + - an OreSatId (ORESAT0, ...) + - a Consts (ORESAT0, ...) + + It will be used to derive the appropriate Consts, the collection of + constants associated with a specific oresat mission. + """ + if isinstance(mission, str): + mission = Consts.from_string(mission) + elif isinstance(mission, OreSatId): + mission = Consts.from_id(mission) + elif not isinstance(mission, Consts): + raise TypeError(f"Unsupported mission type: '{type(mission)}'") + + self.mission = mission + beacon_config = BeaconConfig.from_yaml(mission.beacon_path) + self.cards = cards_from_csv(mission) + self.configs = _load_configs(mission.cards_path) + self.od_db = _gen_od_db(mission, self.cards, beacon_config, self.configs) c3_od = self.od_db["c3"] self.beacon_def = _gen_c3_beacon_defs(c3_od, beacon_config) self.fram_def = _gen_c3_fram_defs(c3_od, self.configs["c3"]) - self.fw_base_od = _gen_fw_base_od(oresat, FW_COMMON_CONFIG_PATH) + self.fw_base_od = _gen_fw_base_od(mission, FW_COMMON_CONFIG_PATH) diff --git a/oresat_configs/__main__.py b/oresat_configs/__main__.py index 86df854..7242b91 100644 --- a/oresat_configs/__main__.py +++ b/oresat_configs/__main__.py @@ -1,12 +1,19 @@ -"""oresat_configs main""" - -# Process for adding a new script: -# - Add module to scripts/ directory -# - It must have register_subparser() which takes a subparsers list -# - import the module here and add it to the SCRIPTS list -# - If it can also be a standalone script then update the pyproject.toml [project.scripts] section -# -# test it out - both through oresat_configs and directly +"""Entry point for for oresat_configs scripts. Invoke with either: +- python -m oresat_configs +- oresat-configs +Some scripts may be installed and run as a standalone program. Consult +pyproject.toml for names to invoke them with. + +Process for adding a new script: +- Add as a module to the adjacent scripts/ directory. The module must have the + function register_subparser() which takes the output of + ArgumentParser.add_subparsers(). +- Import the module here and add it to the _SCRIPTS list. +- If the script can also be standalone then update the pyproject.toml + [project.scripts] section. +- Test the new script out. Remember that the script may be invoked both through + oresat-configs and directly as a standalone. +""" import argparse @@ -21,7 +28,7 @@ # would have to be done through add_argument_group() but those can't # make subparser groups. -SCRIPTS = [ +_SCRIPTS = [ list_cards, print_od, sdo_transfer, @@ -32,19 +39,14 @@ ] -def oresat_configs() -> None: - """oresat_configs main.""" +if __name__ == "__main__": parser = argparse.ArgumentParser(prog="oresat_configs") parser.add_argument("--version", action="version", version="%(prog)s v" + __version__) parser.set_defaults(func=lambda x: parser.print_help()) subparsers = parser.add_subparsers(title="subcommands") - for subcommand in SCRIPTS: + for subcommand in _SCRIPTS: subcommand.register_subparser(subparsers) args = parser.parse_args() args.func(args) - - -if __name__ == "__main__": - oresat_configs() diff --git a/oresat_configs/_yaml_to_od.py b/oresat_configs/_yaml_to_od.py index 50680ad..198c216 100644 --- a/oresat_configs/_yaml_to_od.py +++ b/oresat_configs/_yaml_to_od.py @@ -579,7 +579,10 @@ def _load_configs(config_paths: ConfigPaths) -> dict[str, CardConfig]: def _gen_od_db( - oresat: Consts, cards: dict[str, Card], beacon_def: BeaconConfig, configs: dict[str, CardConfig] + mission: Consts, + cards: dict[str, Card], + beacon_def: BeaconConfig, + configs: dict[str, CardConfig], ) -> dict[str, ObjectDictionary]: od_db = {} node_ids = {name: cards[name].node_id for name in configs} @@ -622,7 +625,7 @@ def _gen_od_db( # set specific obj defaults od["versions"]["configs_version"].default = __version__ - od["satellite_id"].default = oresat.id + od["satellite_id"].default = mission.id for sat in Consts: od["satellite_id"].value_descriptions[sat.id] = sat.name.lower() if name == "c3": @@ -688,7 +691,7 @@ def _gen_c3_beacon_defs(c3_od: ObjectDictionary, beacon_def: BeaconConfig) -> li return beacon_objs -def _gen_fw_base_od(oresat: Consts, config_path: str) -> canopen.ObjectDictionary: +def _gen_fw_base_od(mission: Consts, config_path: str) -> canopen.ObjectDictionary: """Generate all ODs for a OreSat mission.""" od = canopen.ObjectDictionary() @@ -725,6 +728,6 @@ def _gen_fw_base_od(oresat: Consts, config_path: str) -> canopen.ObjectDictionar # set specific obj defaults od["versions"]["configs_version"].default = __version__ - od["satellite_id"].default = oresat.id + od["satellite_id"].default = mission.id return od diff --git a/oresat_configs/base/dxwifi.yaml b/oresat_configs/base/dxwifi.yaml index b9ef5f8..bd0e2e3 100644 --- a/oresat_configs/base/dxwifi.yaml +++ b/oresat_configs/base/dxwifi.yaml @@ -106,6 +106,11 @@ objects: description: number of images transmitted access_type: ro + - subindex: 0x5 + name: enable_pa + data_type: bool + description: enables the power amplifier + default: False tpdos: diff --git a/oresat_configs/card_info.py b/oresat_configs/card_info.py index b9f8e1d..02060d6 100644 --- a/oresat_configs/card_info.py +++ b/oresat_configs/card_info.py @@ -2,7 +2,7 @@ import csv import os -from dataclasses import dataclass +from dataclasses import dataclass, fields from dataclasses_json import dataclass_json @@ -28,11 +28,20 @@ class Card: """Optional child node name. Useful for CFC cards.""" -def cards_from_csv(oresat: Consts) -> dict[str, Card]: +def cards_from_csv(mission: Consts) -> dict[str, Card]: """Turns cards.csv into a dict of names->Cards, filtered by the current mission""" file_path = f"{os.path.dirname(os.path.abspath(__file__))}/cards.csv" with open(file_path, "r") as f: + reader = csv.DictReader(f) + cols = set(reader.fieldnames) if reader.fieldnames else set() + expect = {f.name for f in fields(Card)} + expect.add("name") # the 'name' column is the keys of the returned dict; not in Card + if cols - expect: + raise TypeError(f"cards.csv has excess columns: {cols-expect}. Update class Card?") + if expect - cols: + raise TypeError(f"class Card expects more columns than cards.csv has: {expect-cols}") + return { row["name"]: Card( row["nice_name"], @@ -42,6 +51,6 @@ def cards_from_csv(oresat: Consts) -> dict[str, Card]: row["opd_always_on"].lower() == "true", row["child"], ) - for row in csv.DictReader(f) - if row["name"] in oresat.cards_path + for row in reader + if row["name"] in mission.cards_path } diff --git a/oresat_configs/constants.py b/oresat_configs/constants.py index c8511c1..cb5dbc0 100644 --- a/oresat_configs/constants.py +++ b/oresat_configs/constants.py @@ -12,7 +12,18 @@ from . import oresat0, oresat0_5, oresat1 from .base import ConfigPaths -__version__ = "0.3.1" +__all__ = [ + "__version__", + "OreSatId", + "NodeId", + "Mission", + "Consts", +] + +try: + from ._version import version as __version__ # type: ignore +except ImportError: + __version__ = "0.0.0" # package is not installed @dataclass diff --git a/oresat_configs/scripts/gen_xtce.py b/oresat_configs/scripts/gen_xtce.py index 277c8e3..9364694 100644 --- a/oresat_configs/scripts/gen_xtce.py +++ b/oresat_configs/scripts/gen_xtce.py @@ -117,7 +117,7 @@ def write_xtce(config: OreSatConfig, dir_path: str = ".") -> None: root = ET.Element( "SpaceSystem", attrib={ - "name": str(config.oresat), + "name": str(config.mission), "xmlns:xtce": "http://www.omg.org/space/xtce", "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation": ( @@ -340,7 +340,7 @@ def write_xtce(config: OreSatConfig, dir_path: str = ".") -> None: # write tree = ET.ElementTree(root) ET.indent(tree, space=" ", level=0) - file_name = f"{config.oresat.name.lower()}.xtce" + file_name = f"{config.mission.name.lower()}.xtce" tree.write(f"{dir_path}/{file_name}", encoding="utf-8", xml_declaration=True) diff --git a/pyproject.toml b/pyproject.toml index 45ba72f..186e0ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools", "setuptools_scm"] build-backend = "setuptools.build_meta" [project] @@ -33,15 +33,15 @@ oresat-print-od = "oresat_configs.scripts.print_od:print_od" oresat-sdo-transfer = "oresat_configs.scripts.sdo_transfer:sdo_transfer" oresat-gen-xtce = "oresat_configs.scripts.gen_xtce:gen_xtce" -[tool.setuptools.dynamic] -version = {attr = "oresat_configs.constants.__version__"} - [tool.setuptools.packages.find] -exclude = ["docs*", "tests*"] +exclude = ["docs*", "tests*"] [tool.setuptools.package-data] "*" = ["*.yaml", "*.csv"] +[tool.setuptools_scm] +write_to = "oresat_configs/_version.py" + [tool.black] line_length = 100 diff --git a/requirements.txt b/requirements.txt index 393d948..2f3712b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ pylama[toml] pyyaml types-pyyaml setuptools +setuptools-scm sphinx sphinx-rtd-theme tabulate