From 380b36f1de58d9b7894c0e7209e9914e8ffd8811 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 24 Apr 2024 20:15:32 +0200 Subject: [PATCH 1/3] clean up python code a bit --- pytmtc/camera_params.py | 20 ++---------- pytmtc/common.py | 11 ++----- pytmtc/pus_tc.py | 68 ++++++++++++++++++++++------------------- pytmtc/pyclient.py | 4 +-- pytmtc/requirements.txt | 1 + pytmtc/test_serde.py | 20 ++++++++++++ 6 files changed, 65 insertions(+), 59 deletions(-) create mode 100644 pytmtc/test_serde.py diff --git a/pytmtc/camera_params.py b/pytmtc/camera_params.py index 2562372..aab22b3 100644 --- a/pytmtc/camera_params.py +++ b/pytmtc/camera_params.py @@ -1,7 +1,8 @@ import struct from serde import Model, fields -from common import EXPERIMENT_APID, UniqueId, make_addressable_id +from common import EXPERIMENT_APID, UniqueId, make_unique_id + class CameraParameters(Model): R: fields.Int() @@ -13,19 +14,4 @@ class CameraParameters(Model): W: fields.Int() def serialize_for_uplink(self) -> bytearray: - return self.to_json().encode('utf-8') - -# Example serialization -data = bytearray(make_addressable_id(EXPERIMENT_APID, UniqueId.CameraHandler)) -params = CameraParameters(8, 8, 8, 1, True, 200, 1000) -serialized = params.to_json().encode('utf-8') -byte_string = bytearray(struct.pack('!{}s'.format(len(serialized)), serialized)) -print(byte_string) -print(params.serialize_for_uplink()) -data.extend(params.serialize_for_uplink()) -print(data) - -# Example deserialization -data = '{"R": 100, "G": 150, "B": 200, "N": 3, "P": true, "E": 10, "W": 20}' -deserialized_params = CameraParameters.from_json(data) -print(deserialized_params) + return self.to_json().encode("utf-8") diff --git a/pytmtc/common.py b/pytmtc/common.py index 52bcbf1..f9bfa9d 100644 --- a/pytmtc/common.py +++ b/pytmtc/common.py @@ -3,14 +3,12 @@ from __future__ import annotations import dataclasses import enum import struct -from serde import Model, fields EXPERIMENT_ID = 278 EXPERIMENT_APID = 1024 + EXPERIMENT_ID class UniqueId(enum.IntEnum): - Controller = 0 PusEventManagement = 1 PusRouting = 2 @@ -59,14 +57,11 @@ class AcsHkIds(enum.IntEnum): MGM_SET = 1 -def make_addressable_id(target_id: int, unique_id: int) -> bytes: - byte_string = bytearray(struct.pack("!I", unique_id)) - # byte_string = bytearray(struct.pack("!I", target_id)) - # byte_string.extend(struct.pack("!I", unique_id)) - return byte_string +def make_unique_id(unique_id: int) -> bytes: + return struct.pack("!I", unique_id) -def make_addressable_id_with_action_id(unique_id: int, action_id: int) -> bytes: +def make_action_cmd_header(unique_id: int, action_id: int) -> bytes: byte_string = bytearray(struct.pack("!I", unique_id)) byte_string.extend(struct.pack("!I", action_id)) return byte_string diff --git a/pytmtc/pus_tc.py b/pytmtc/pus_tc.py index 136d311..104ca74 100644 --- a/pytmtc/pus_tc.py +++ b/pytmtc/pus_tc.py @@ -9,10 +9,13 @@ from tmtccmd.pus.tc.s200_fsfw_mode import Mode from tmtccmd.tmtc import DefaultPusQueueHelper from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice -from serde import Model, fields from camera_params import CameraParameters -from common import EXPERIMENT_APID, UniqueId, make_addressable_id, make_addressable_id_with_action_id +from common import ( + EXPERIMENT_APID, + UniqueId, + make_action_cmd_header, +) _LOGGER = logging.getLogger(__name__) @@ -70,33 +73,27 @@ def create_cmd_definition_tree() -> CmdTreeNode: root_node.add_child(scheduler_node) action_node = CmdTreeNode("action", "Action Node") - cam_node = CmdTreeNode( - "take_image", "Take Image with IMS Imager" - ) + cam_node = CmdTreeNode("take_image", "Take Image with IMS Imager") + cam_node.add_child( + CmdTreeNode("default_single", "Default Single Image Camera Parameters") + ) + cam_node.add_child( + CmdTreeNode("balanced_single", "Balanced Single Image Camera Parameters") + ) cam_node.add_child( CmdTreeNode( - "default_single", "Default Single Image Camera Parameters" + "default_single_flatsat", + "Default Single Image Camera Parameters for use on FlatSat", ) ) cam_node.add_child( CmdTreeNode( - "balanced_single", "Balanced Single Image Camera Parameters" + "balanced_single_flatsat", + "Balanced Single Image Camera Parameters for use on FlatSat", ) ) cam_node.add_child( - CmdTreeNode( - "default_single_flatsat", "Default Single Image Camera Parameters for use on FlatSat" - ) - ) - cam_node.add_child( - CmdTreeNode( - "balanced_single_flatsat", "Balanced Single Image Camera Parameters for use on FlatSat" - ) - ) - cam_node.add_child( - CmdTreeNode( - "custom_params", "Custom Camera Parameters as specified from file" - ) + CmdTreeNode("custom_params", "Custom Camera Parameters as specified from file") ) action_node.add_child(cam_node) root_node.add_child(action_node) @@ -134,25 +131,32 @@ def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str): if cmd_path_list[0] == "acs": assert len(cmd_path_list) >= 2 if cmd_path_list[0] == "action": - assert len(cmd_path_list)>= 2 + assert len(cmd_path_list) >= 2 if cmd_path_list[1] == "take_image": - assert len(cmd_path_list)>= 3 - q.add_log_cmd("Sending PUS take image action request with " + cmd_path_list[2] + " params.") + assert len(cmd_path_list) >= 3 + q.add_log_cmd( + "Sending PUS take image action request with " + + cmd_path_list[2] + + " params." + ) + data = bytearray() if cmd_path_list[2] == "default_single": - data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 1) + data.extend(make_action_cmd_header(UniqueId.CameraHandler, 1)) if cmd_path_list[2] == "balanced_single": - data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 2) + data.extend(make_action_cmd_header(UniqueId.CameraHandler, 2)) if cmd_path_list[2] == "default_single_flatsat": - data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 3) + data.extend(make_action_cmd_header(UniqueId.CameraHandler, 3)) if cmd_path_list[2] == "balanced_single_flatsat": - data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 4) + data.extend(make_action_cmd_header(UniqueId.CameraHandler, 4)) if cmd_path_list[2] == "custom": - data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 5) + data.extend(make_action_cmd_header(UniqueId.CameraHandler, 5)) params = CameraParameters(8, 8, 8, 1, True, 200, 1000) - bytes = params.serialize_for_uplink() - data.extend(bytes) - print(data.hex(sep=",")) - return q.add_pus_tc(PusTelecommand(service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data)) + data.extend(params.serialize_for_uplink()) + return q.add_pus_tc( + PusTelecommand( + service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data + ) + ) def handle_set_mode_cmd( diff --git a/pytmtc/pyclient.py b/pytmtc/pyclient.py index aec7024..6185390 100755 --- a/pytmtc/pyclient.py +++ b/pytmtc/pyclient.py @@ -149,10 +149,10 @@ class PusHandler(GenericApidHandlerBase): _LOGGER.info("Received test event") elif service == 8: if pus_tm.subservice == 130: - _LOGGER.info(f"Received Action Data Reply TM[8,130]") + _LOGGER.info("Received Action Data Reply TM[8,130]") reply = pus_tm.source_data reply = reply[6:] - _LOGGER.info(f"Data Reply Content: " + reply.decode('utf-8')) + _LOGGER.info(f"Data Reply Content: {reply.decode()}") elif service == 17: tm_packet = Service17Tm.unpack( packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE diff --git a/pytmtc/requirements.txt b/pytmtc/requirements.txt index 325615c..1e072a2 100644 --- a/pytmtc/requirements.txt +++ b/pytmtc/requirements.txt @@ -1,2 +1,3 @@ tmtccmd == 8.0.0rc2 +serde == 0.9.0 # -e git+https://github.com/robamu-org/tmtccmd@97e5e51101a08b21472b3ddecc2063359f7e307a#egg=tmtccmd diff --git a/pytmtc/test_serde.py b/pytmtc/test_serde.py new file mode 100644 index 0000000..35b7c07 --- /dev/null +++ b/pytmtc/test_serde.py @@ -0,0 +1,20 @@ +import struct +from camera_params import CameraParameters +from common import make_unique_id, EXPERIMENT_APID + + +def test_serde_serialization(): + # Example serializatn + data = bytearray(make_unique_id(EXPERIMENT_APID)) + params = CameraParameters(8, 8, 8, 1, True, 200, 1000) + serialized = params.to_json().encode("utf-8") + byte_string = bytearray(struct.pack("!{}s".format(len(serialized)), serialized)) + print(byte_string) + print(params.serialize_for_uplink()) + data.extend(params.serialize_for_uplink()) + print(data) + + # Example deserialization + data = '{"R": 100, "G": 150, "B": 200, "N": 3, "P": true, "E": 10, "W": 20}' + deserialized_params = CameraParameters.from_json(data) + print(deserialized_params) From 879a50f79e18b0aa275e57754f7e7cc1a9730221 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 24 Apr 2024 20:35:59 +0200 Subject: [PATCH 2/3] pytmtc is a package now --- README.md | 15 +- pytmtc/.gitignore | 133 ++++++++++++++++++ pytmtc/{pus_tm.py => opssat_tmtc/__init__.py} | 0 pytmtc/{ => opssat_tmtc}/camera_params.py | 2 +- pytmtc/{ => opssat_tmtc}/common.py | 0 pytmtc/{ => opssat_tmtc}/pus_tc.py | 4 +- pytmtc/opssat_tmtc/pus_tm.py | 0 pytmtc/{ => opssat_tmtc}/tc_definitions.py | 3 +- pytmtc/pyclient.py | 9 +- pytmtc/pyproject.toml | 26 ++++ pytmtc/requirements.txt | 3 +- pytmtc/{ => tests}/test_serde.py | 4 +- 12 files changed, 184 insertions(+), 15 deletions(-) rename pytmtc/{pus_tm.py => opssat_tmtc/__init__.py} (100%) rename pytmtc/{ => opssat_tmtc}/camera_params.py (81%) rename pytmtc/{ => opssat_tmtc}/common.py (100%) rename pytmtc/{ => opssat_tmtc}/pus_tc.py (98%) create mode 100644 pytmtc/opssat_tmtc/pus_tm.py rename pytmtc/{ => opssat_tmtc}/tc_definitions.py (97%) create mode 100644 pytmtc/pyproject.toml rename pytmtc/{ => tests}/test_serde.py (84%) diff --git a/README.md b/README.md index 492f072..695a444 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,20 @@ Commanding of the `ops-sat-rs` application is possible by different means. ### Using the `pyclient` and `pyserver` applications You can find both commanding application inside the `pytmtc` folder. -You can also find a `requirements.txt` file there to install all required Python dependencies. +It is recommended to set up a virtual environment first, for example by running the following +code inside the `pytmtc` folder: + +```sh +python3 -m venv venv +source venv/bin/activate +``` + +After that, you can install all requirements for both the client and server application +interactively using + +```sh +pip install -e . +``` If you want to command the satellite using the OPS-SAT infrastrucute, start the `pyserver.py` as a background application first, for example by simply running `pyserver.py` inside a diff --git a/pytmtc/.gitignore b/pytmtc/.gitignore index 53b730f..008bdd0 100644 --- a/pytmtc/.gitignore +++ b/pytmtc/.gitignore @@ -8,3 +8,136 @@ __pycache__ /seqcnt.txt /.tmtc-history.txt + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# PyCharm +.idea diff --git a/pytmtc/pus_tm.py b/pytmtc/opssat_tmtc/__init__.py similarity index 100% rename from pytmtc/pus_tm.py rename to pytmtc/opssat_tmtc/__init__.py diff --git a/pytmtc/camera_params.py b/pytmtc/opssat_tmtc/camera_params.py similarity index 81% rename from pytmtc/camera_params.py rename to pytmtc/opssat_tmtc/camera_params.py index aab22b3..34ee2c4 100644 --- a/pytmtc/camera_params.py +++ b/pytmtc/opssat_tmtc/camera_params.py @@ -1,7 +1,7 @@ import struct from serde import Model, fields -from common import EXPERIMENT_APID, UniqueId, make_unique_id +from opssat_tmtc.common import EXPERIMENT_APID, UniqueId, make_unique_id class CameraParameters(Model): diff --git a/pytmtc/common.py b/pytmtc/opssat_tmtc/common.py similarity index 100% rename from pytmtc/common.py rename to pytmtc/opssat_tmtc/common.py diff --git a/pytmtc/pus_tc.py b/pytmtc/opssat_tmtc/pus_tc.py similarity index 98% rename from pytmtc/pus_tc.py rename to pytmtc/opssat_tmtc/pus_tc.py index 104ca74..49bbc71 100644 --- a/pytmtc/pus_tc.py +++ b/pytmtc/opssat_tmtc/pus_tc.py @@ -10,8 +10,8 @@ from tmtccmd.tmtc import DefaultPusQueueHelper from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice -from camera_params import CameraParameters -from common import ( +from opssat_tmtc.camera_params import CameraParameters +from opssat_tmtc.common import ( EXPERIMENT_APID, UniqueId, make_action_cmd_header, diff --git a/pytmtc/opssat_tmtc/pus_tm.py b/pytmtc/opssat_tmtc/pus_tm.py new file mode 100644 index 0000000..e69de29 diff --git a/pytmtc/tc_definitions.py b/pytmtc/opssat_tmtc/tc_definitions.py similarity index 97% rename from pytmtc/tc_definitions.py rename to pytmtc/opssat_tmtc/tc_definitions.py index acd2741..435e3e6 100644 --- a/pytmtc/tc_definitions.py +++ b/pytmtc/opssat_tmtc/tc_definitions.py @@ -1,7 +1,7 @@ from tmtccmd.config import OpCodeEntry, TmtcDefinitionWrapper, CoreServiceList from tmtccmd.config.globals import get_default_tmtc_defs -from common import HkOpCodes +from opssat_tmtc.common import HkOpCodes def tc_definitions() -> TmtcDefinitionWrapper: @@ -41,6 +41,5 @@ def tc_definitions() -> TmtcDefinitionWrapper: name=CoreServiceList.SERVICE_8, info="PUS Service 8 Action", op_code_entry=srv_8, - ) return defs diff --git a/pytmtc/pyclient.py b/pytmtc/pyclient.py index 6185390..e21a655 100755 --- a/pytmtc/pyclient.py +++ b/pytmtc/pyclient.py @@ -46,9 +46,8 @@ from tmtccmd.tmtc import ( from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider from tmtccmd.util.obj_id import ObjectIdDictT - -import pus_tc -from common import EXPERIMENT_APID, EventU32 +from opssat_tmtc.pus_tc import create_cmd_definition_tree, pack_pus_telecommands +from opssat_tmtc.common import EXPERIMENT_APID, EventU32 _LOGGER = logging.getLogger() @@ -76,7 +75,7 @@ class SatRsConfigHook(HookBase): def get_command_definitions(self) -> CmdTreeNode: """This function should return the root node of the command definition tree.""" - return pus_tc.create_cmd_definition_tree() + return create_cmd_definition_tree() def get_cmd_history(self) -> Optional[History]: """Optionlly return a history class for the past command paths which will be used @@ -217,7 +216,7 @@ class TcHandler(TcHandlerBase): if info.proc_type == TcProcedureType.TREE_COMMANDING: def_proc = info.to_tree_commanding_procedure() assert def_proc.cmd_path is not None - pus_tc.pack_pus_telecommands(q, def_proc.cmd_path) + pack_pus_telecommands(q, def_proc.cmd_path) def main(): diff --git a/pytmtc/pyproject.toml b/pytmtc/pyproject.toml new file mode 100644 index 0000000..81460b4 --- /dev/null +++ b/pytmtc/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "opssat-tmtc" +description = "Python TMTC client for OPS-SAT" +readme = "README.md" +version = "0.1.0" +requires-python = ">=3.8" +authors = [ + {name = "Robin Mueller", email = "robin.mueller.m@gmail.com"}, + {name = "Linus Köster", email = "st167799@stud.uni-stuttgart.de"} +] +dependencies = [ + "tmtccmd==8.0.0rc.2", + "serde==0.9.0" +] + +[tool.setuptools.packages] +find = {} + +[tool.ruff.lint] +ignore = ["E501"] +[tool.ruff.lint.extend-per-file-ignores] +"__init__.py" = ["F401"] diff --git a/pytmtc/requirements.txt b/pytmtc/requirements.txt index 1e072a2..3629228 100644 --- a/pytmtc/requirements.txt +++ b/pytmtc/requirements.txt @@ -1,3 +1,2 @@ -tmtccmd == 8.0.0rc2 -serde == 0.9.0 +. # -e git+https://github.com/robamu-org/tmtccmd@97e5e51101a08b21472b3ddecc2063359f7e307a#egg=tmtccmd diff --git a/pytmtc/test_serde.py b/pytmtc/tests/test_serde.py similarity index 84% rename from pytmtc/test_serde.py rename to pytmtc/tests/test_serde.py index 35b7c07..010f979 100644 --- a/pytmtc/test_serde.py +++ b/pytmtc/tests/test_serde.py @@ -1,6 +1,6 @@ import struct -from camera_params import CameraParameters -from common import make_unique_id, EXPERIMENT_APID +from opssat_tmtc.camera_params import CameraParameters +from opssat_tmtc.common import make_unique_id, EXPERIMENT_APID def test_serde_serialization(): From 597f4ca9770c27f8a8669bad5dda79b96613ea1b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 24 Apr 2024 20:40:51 +0200 Subject: [PATCH 3/3] some more cleaning up --- pytmtc/pyserver.py | 2 +- src/config.rs | 4 ++-- src/logger.rs | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pytmtc/pyserver.py b/pytmtc/pyserver.py index 19caf93..88239d1 100755 --- a/pytmtc/pyserver.py +++ b/pytmtc/pyserver.py @@ -17,7 +17,7 @@ EXP_PACKET_ID_TM = PacketId(PacketType.TM, True, EXP_APID) EXP_PACKET_ID_TC = PacketId(PacketType.TC, True, EXP_APID) OPSSAT_SERVER_PORT = 4096 TMTC_SERVER_PORT = 4097 -LOG_LEVEL = logging.DEBUG +LOG_LEVEL = logging.INFO TC_QUEUE = Queue() diff --git a/src/config.rs b/src/config.rs index e3d3554..9c799a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,8 +22,8 @@ pub const VALID_PACKET_ID_LIST: &[PacketId] = &[PacketId::new_for_tc(true, EXPER // TODO: Would be nice if this can be commanded as well.. /// Can be enabled to print all SPP packets received from the SPP server on port 4096. -pub const SPP_CLIENT_WIRETAPPING_RX: bool = true; -pub const SPP_CLIENT_WIRETAPPING_TX: bool = true; +pub const SPP_CLIENT_WIRETAPPING_RX: bool = false; +pub const SPP_CLIENT_WIRETAPPING_TX: bool = false; #[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] diff --git a/src/logger.rs b/src/logger.rs index 8779c13..c186deb 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -8,7 +8,6 @@ pub fn setup_logger() -> Result<(), fern::InitError> { } let mut path_buf = PathBuf::from(LOG_FOLDER); path_buf.push("output.log"); - println!("{:?}", path_buf); fern::Dispatch::new() .format(move |out, message, record| { out.finish(format_args!(