diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..f53a6cb5 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,23 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.8 + install: + - method: pip + path: . + extra_requirements: + - gui diff --git a/README.md b/README.md index 681536d5..1d23c863 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,14 @@ This commander application was first developed by KSat for the software but has evolved into a more generic tool for satellite developers to perform TMTC (Telemetry and Telecommand) handling and testing via different communication interfaces. Currently, only the PUS standard is implemented as a packet standard. This tool can be used either -as a command line tool or as a GUI tool, but the GUI capabilities are still in an alpha state. +as a command line tool or as a GUI tool. The GUI features require a PyQt5 installation. This client currently supports the following communication interfaces: -1. TCP/IP with UDP packets +1. TCP/IP with UDP and TCP 2. Serial Communication using fixed frames or a simple ASCII based transport layer 3. QEMU, using a virtual serial interface -A TCP implementation is planned. - The TMTC commander also includes a Space Packet and a ECSS PUS packet stack. Some of these components might be moved to an own library soon, so they were decoupled from the rest of the TMTC commander components. @@ -96,4 +94,4 @@ python3 -m pip install -e .[gui] ``` Omit the `-e` for a regular installation. Alternatively you can now install the package -from PyPI with `python3 -m pip install -e tmtccmd`. +from PyPI with `python3 -m pip install -e tmtccmd[gui]`. diff --git a/docs/api.rst b/docs/api.rst index a490105c..3477ee3b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,3 +1,49 @@ ==== API -==== \ No newline at end of file +==== + +Core Submodules +=============== + +tmtccmd.runner module +--------------------- + +.. automodule:: tmtccmd.runner + :members: + :undoc-members: + :show-inheritance: + +.. toctree:: + :maxdepth: 4 + + api/tmtccmd.core + api/tmtccmd.sendreceive + +Configuration Submodules +========================= + +.. toctree:: + :maxdepth: 4 + + api/tmtccmd.config + api/tmtccmd.com_if + +ECSS & PUS Submodules +========================= + +.. toctree:: + :maxdepth: 4 + + api/tmtccmd.ccsds + api/tmtccmd.ecss + api/tmtccmd.pus + api/tmtccmd.pus_tc + api/tmtccmd.pus_tm + +Other Submodules +========================= + +.. toctree:: + :maxdepth: 4 + + api/tmtccmd.utility \ No newline at end of file diff --git a/docs/api/tmtccmd.ccsds.rst b/docs/api/tmtccmd.ccsds.rst new file mode 100644 index 00000000..698f605b --- /dev/null +++ b/docs/api/tmtccmd.ccsds.rst @@ -0,0 +1,29 @@ +tmtccmd.ccsds package +===================== + +Submodules +---------- + +tmtccmd.ccsds.cfdp module +------------------------- + +.. automodule:: tmtccmd.ccsds.cfdp + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.ccsds.spacepacket module +-------------------------------- + +.. automodule:: tmtccmd.ccsds.spacepacket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.ccsds + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.com_if.rst b/docs/api/tmtccmd.com_if.rst new file mode 100644 index 00000000..9dca814c --- /dev/null +++ b/docs/api/tmtccmd.com_if.rst @@ -0,0 +1,85 @@ +tmtccmd.com\_if package +======================= + +Submodules +---------- + +tmtccmd.com\_if.com\_if\_utilities module +----------------------------------------- + +.. automodule:: tmtccmd.com_if.com_if_utilities + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.com\_interface\_base module +------------------------------------------- + +.. automodule:: tmtccmd.com_if.com_interface_base + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.dummy\_com\_if module +------------------------------------- + +.. automodule:: tmtccmd.com_if.dummy_com_if + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.qemu\_com\_if module +------------------------------------ + +.. automodule:: tmtccmd.com_if.qemu_com_if + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.serial\_com\_if module +-------------------------------------- + +.. automodule:: tmtccmd.com_if.serial_com_if + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.serial\_utilities module +---------------------------------------- + +.. automodule:: tmtccmd.com_if.serial_utilities + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.tcpip\_tcp\_com\_if module +------------------------------------------ + +.. automodule:: tmtccmd.com_if.tcpip_tcp_com_if + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.tcpip\_udp\_com\_if module +------------------------------------------ + +.. automodule:: tmtccmd.com_if.tcpip_udp_com_if + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.com\_if.tcpip\_utilities module +--------------------------------------- + +.. automodule:: tmtccmd.com_if.tcpip_utilities + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.com_if + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.config.rst b/docs/api/tmtccmd.config.rst new file mode 100644 index 00000000..1f61869e --- /dev/null +++ b/docs/api/tmtccmd.config.rst @@ -0,0 +1,61 @@ +tmtccmd.config package +====================== + +Submodules +---------- + +tmtccmd.config.hook module +-------------------------- + +.. automodule:: tmtccmd.config.hook + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.config.args module +-------------------------- + +.. automodule:: tmtccmd.config.args + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.config.com\_if module +----------------------------- + +.. automodule:: tmtccmd.config.com_if + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.config.definitions module +--------------------------------- + +.. automodule:: tmtccmd.config.definitions + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.config.globals module +----------------------------- + +.. automodule:: tmtccmd.config.globals + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.config.objects module +----------------------------- + +.. automodule:: tmtccmd.config.objects + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.core.rst b/docs/api/tmtccmd.core.rst new file mode 100644 index 00000000..3a7accbe --- /dev/null +++ b/docs/api/tmtccmd.core.rst @@ -0,0 +1,53 @@ +tmtccmd.core package +==================== + +Submodules +---------- + +tmtccmd.core.backend module +--------------------------- + +.. automodule:: tmtccmd.core.backend + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.core.frontend module +---------------------------- + +.. automodule:: tmtccmd.core.frontend + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.core.frontend\_base module +---------------------------------- + +.. automodule:: tmtccmd.core.frontend_base + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.core.globals\_manager module +------------------------------------ + +.. automodule:: tmtccmd.core.globals_manager + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.core.object\_id\_manager module +--------------------------------------- + +.. automodule:: tmtccmd.core.object_id_manager + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.ecss.rst b/docs/api/tmtccmd.ecss.rst new file mode 100644 index 00000000..13519a06 --- /dev/null +++ b/docs/api/tmtccmd.ecss.rst @@ -0,0 +1,45 @@ +tmtccmd.ecss package +==================== + +Submodules +---------- + +tmtccmd.ecss.conf module +------------------------ + +.. automodule:: tmtccmd.ecss.conf + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.ecss.tc module +---------------------- + +.. automodule:: tmtccmd.ecss.tc + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.ecss.tm module +---------------------- + +.. automodule:: tmtccmd.ecss.tm + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.ecss.tm\_creator module +------------------------------- + +.. automodule:: tmtccmd.ecss.tm_creator + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.ecss + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.pus.rst b/docs/api/tmtccmd.pus.rst new file mode 100644 index 00000000..deb55ecf --- /dev/null +++ b/docs/api/tmtccmd.pus.rst @@ -0,0 +1,45 @@ +tmtccmd.pus package +=================== + +Submodules +---------- + +tmtccmd.pus.service\_17\_test module +------------------------------------ + +.. automodule:: tmtccmd.pus.service_17_test + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus.service\_20\_parameter module +----------------------------------------- + +.. automodule:: tmtccmd.pus.service_20_parameter + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus.service\_5\_event module +------------------------------------ + +.. automodule:: tmtccmd.pus.service_5_event + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus.service\_list module +-------------------------------- + +.. automodule:: tmtccmd.pus.service_list + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.pus + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.pus_tc.rst b/docs/api/tmtccmd.pus_tc.rst new file mode 100644 index 00000000..893f01bb --- /dev/null +++ b/docs/api/tmtccmd.pus_tc.rst @@ -0,0 +1,77 @@ +tmtccmd.pus\_tc package +======================= + +Submodules +---------- + +tmtccmd.pus\_tc.definitions module +---------------------------------- + +.. automodule:: tmtccmd.pus_tc.definitions + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.packer module +----------------------------- + +.. automodule:: tmtccmd.pus_tc.packer + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.service\_17\_test module +---------------------------------------- + +.. automodule:: tmtccmd.pus_tc.service_17_test + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.service\_200\_mode module +----------------------------------------- + +.. automodule:: tmtccmd.pus_tc.service_200_mode + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.service\_20\_parameter module +--------------------------------------------- + +.. automodule:: tmtccmd.pus_tc.service_20_parameter + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.service\_3\_housekeeping module +----------------------------------------------- + +.. automodule:: tmtccmd.pus_tc.service_3_housekeeping + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.service\_5\_event module +---------------------------------------- + +.. automodule:: tmtccmd.pus_tc.service_5_event + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tc.service\_8\_functional\_cmd module +-------------------------------------------------- + +.. automodule:: tmtccmd.pus_tc.service_8_functional_cmd + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.pus_tc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.pus_tm.rst b/docs/api/tmtccmd.pus_tm.rst new file mode 100644 index 00000000..bb4f16a0 --- /dev/null +++ b/docs/api/tmtccmd.pus_tm.rst @@ -0,0 +1,101 @@ +tmtccmd.pus\_tm package +======================= + +Submodules +---------- + +tmtccmd.pus\_tm.definitions module +---------------------------------- + +.. automodule:: tmtccmd.pus_tm.definitions + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.factory module +------------------------------ + +.. automodule:: tmtccmd.pus_tm.factory + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_17\_test module +---------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_17_test + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_1\_verification module +----------------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_1_verification + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_200\_mode module +----------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_200_mode + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_20\_parameters module +---------------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_20_parameters + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_2\_raw\_cmd module +------------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_2_raw_cmd + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_3\_base module +--------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_3_base + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_3\_housekeeping module +----------------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_3_housekeeping + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_5\_event module +---------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_5_event + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.pus\_tm.service\_8\_functional\_cmd module +-------------------------------------------------- + +.. automodule:: tmtccmd.pus_tm.service_8_functional_cmd + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.pus_tm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.sendreceive.rst b/docs/api/tmtccmd.sendreceive.rst new file mode 100644 index 00000000..64214da8 --- /dev/null +++ b/docs/api/tmtccmd.sendreceive.rst @@ -0,0 +1,53 @@ +tmtccmd.sendreceive package +=========================== + +Submodules +---------- + +tmtccmd.sendreceive.cmd\_sender\_receiver module +------------------------------------------------ + +.. automodule:: tmtccmd.sendreceive.cmd_sender_receiver + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.sendreceive.multiple\_cmds\_sender\_receiver module +----------------------------------------------------------- + +.. automodule:: tmtccmd.sendreceive.multiple_cmds_sender_receiver + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.sendreceive.sequential\_sender\_receiver module +------------------------------------------------------- + +.. automodule:: tmtccmd.sendreceive.sequential_sender_receiver + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.sendreceive.single\_command\_sender\_receiver module +------------------------------------------------------------ + +.. automodule:: tmtccmd.sendreceive.single_command_sender_receiver + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.sendreceive.tm\_listener module +--------------------------------------- + +.. automodule:: tmtccmd.sendreceive.tm_listener + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.sendreceive + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tmtccmd.utility.rst b/docs/api/tmtccmd.utility.rst new file mode 100644 index 00000000..d6241579 --- /dev/null +++ b/docs/api/tmtccmd.utility.rst @@ -0,0 +1,69 @@ +tmtccmd.utility package +======================= + +Submodules +---------- + +tmtccmd.utility.conf\_util module +--------------------------------- + +.. automodule:: tmtccmd.utility.conf_util + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.utility.dle\_encoder module +----------------------------------- + +.. automodule:: tmtccmd.utility.dle_encoder + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.utility.exit\_handler module +------------------------------------ + +.. automodule:: tmtccmd.utility.exit_handler + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.utility.hammingcode module +---------------------------------- + +.. automodule:: tmtccmd.utility.hammingcode + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.utility.json\_handler module +------------------------------------ + +.. automodule:: tmtccmd.utility.json_handler + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.utility.logger module +----------------------------- + +.. automodule:: tmtccmd.utility.logger + :members: + :undoc-members: + :show-inheritance: + +tmtccmd.utility.tmtc\_printer module +------------------------------------ + +.. automodule:: tmtccmd.utility.tmtc_printer + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: tmtccmd.utility + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index d4953e8e..2879ce69 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,10 +10,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - +import os +import sys +sys.path.insert(0, os.path.abspath('../src')) # -- Project information ----------------------------------------------------- @@ -30,7 +29,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.intersphinx'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -47,6 +46,10 @@ # for source files. exclude_trees = ['_build'] +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None) +} + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -61,7 +64,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [] # -- Options for LaTeX output -------------------------------------------------- diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index 6252e57a..21cd6d0b 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -1,3 +1,27 @@ =============== Getting Started -=============== \ No newline at end of file +=============== + +The example provided in the ``example`` folder of the Python package is a good place to get started. +You can run the ``tmtc_cli.py`` file to test the CLI interface or the ``tmtc_gui.py`` file +to test the GUI interface. The only working communication interface for the example applications is +the ``dummy`` interface. + +CLI +=== + +If ``tmtc_cli.py`` is run without any command line arguments the commander core will prompt values +like the service or operation code. These values are passed on to the hook functions, which +allows a developers to package different telecommand stacks for different service and op code +combinations. + +GUI +=== + +Simply run the ``tmtc_gui.py`` application and connect to the Dummy communication interface. +After that, you can send a ping command and see the generated replies. + +Implementing the hook function +============================== + +Coming Soon diff --git a/docs/index.rst b/docs/index.rst index fbdb2035..adeadd3c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,12 +15,10 @@ to allow for easier adaption to other missions. This client currently supports the following communication interfaces: -1. TCP/IP with UDP packets +1. TCP/IP with UDP and TCP 2. Serial Communication using fixed frames or a simple ASCII based transport layer 3. QEMU, using a virtual serial interface -A TCP implementation is planned. - Other pages (online) - `project page on GitHub`_ diff --git a/docs/introduction.rst b/docs/introduction.rst index af258270..5ce288c2 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -22,12 +22,18 @@ via Ethernet to a microcontroller running a TCP/IP server are possible as well. .. _`SOURCE`: https://www.ksat-stuttgart.de/en/our-missions/source/ +The application is configured by passing an instance of a special hook object to the commander core +using the ``initialize_tmtc_commander`` function and then running the ``run_tmtc_commander`` +function which also allows to specify whether the CLI or the GUI functionality is used. It is +recommended to implement the class ``TmTcHookBase`` for the hook object instantiation +because this class contains all important functions as abstract functions. + Features ========= - `Packet Utilisation Standard (PUS)`_ TMTC stack to simplify the packaging of PUS telecommand packets and the analysis and deserialization of raw PUS telemetry -- Common communication interfaces like a serial interface or a TCP/IP interface +- Common communication interfaces like a serial interface or a TCP/IP interfaces to send and receive TMTC packets. - Listener mode to display incoming packets - Sequential mode which allows inserting telecommands into a queue diff --git a/example/config/hook_implementation.py b/example/config/hook_implementation.py index 1683aff9..2fab158d 100644 --- a/example/config/hook_implementation.py +++ b/example/config/hook_implementation.py @@ -26,9 +26,9 @@ def add_globals_pre_args_parsing(self, gui: bool = False): from tmtccmd.config.globals import set_default_globals_pre_args_parsing set_default_globals_pre_args_parsing(gui=gui, apid=0xef) - def add_globals_post_args_parsing(self, args: argparse.Namespace, json_cfg_path: str = ""): + def add_globals_post_args_parsing(self, args: argparse.Namespace): from tmtccmd.config.globals import set_default_globals_post_args_parsing - set_default_globals_post_args_parsing(args=args, json_cfg_path=json_cfg_path) + set_default_globals_post_args_parsing(args=args, json_cfg_path=self.get_json_config_file_path()) def assign_communication_interface(self, com_if_key: str, tmtc_printer: TmTcPrinter) -> \ Union[CommunicationInterface, None]: @@ -55,10 +55,12 @@ def tm_user_factory_hook(self, raw_tm_packet: bytearray) -> PusTelemetry: return default_factory_hook(raw_tm_packet=raw_tm_packet) def get_object_ids(self) -> Dict[bytes, list]: - pass + from tmtccmd.config.objects import get_core_object_ids + return get_core_object_ids() def get_service_op_code_dictionary(self) -> ServiceOpCodeDictT: - pass + from tmtccmd.config.globals import get_default_service_op_code_dict + return get_default_service_op_code_dict() @staticmethod def handle_service_8_telemetry( diff --git a/example/tmtc_cli.py b/example/tmtc_cli.py index dfbf2490..0ce8fa04 100755 --- a/example/tmtc_cli.py +++ b/example/tmtc_cli.py @@ -9,10 +9,8 @@ def main(): hook_obj = ExampleHookClass() initialize_tmtc_commander(hook_object=hook_obj) - run_tmtc_commander(False) - pass + run_tmtc_commander(use_gui=False ) if __name__ == '__main__': main() - diff --git a/example/tmtc_config.json b/example/tmtc_config.json new file mode 100644 index 00000000..de0d92fb --- /dev/null +++ b/example/tmtc_config.json @@ -0,0 +1,3 @@ +{ + "COM_IF_KEY": "dummy" +} \ No newline at end of file diff --git a/example/tmtc_gui.py b/example/tmtc_gui.py new file mode 100644 index 00000000..b032ff46 --- /dev/null +++ b/example/tmtc_gui.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +""" +Example application for the TMTC Commander +""" +from tmtccmd.runner import run_tmtc_commander, initialize_tmtc_commander +from config.hook_implementation import ExampleHookClass + + +def main(): + hook_obj = ExampleHookClass() + initialize_tmtc_commander(hook_object=hook_obj) + run_tmtc_commander(use_gui=True, app_name="TMTC Commander Example") + + +if __name__ == '__main__': + main() diff --git a/src/tests/test_pus.py b/src/tests/test_pus.py index 83476919..8b7627e1 100755 --- a/src/tests/test_pus.py +++ b/src/tests/test_pus.py @@ -9,7 +9,6 @@ from tmtccmd.ccsds.spacepacket import get_sp_packet_sequence_control from tmtccmd.ecss.conf import set_default_apid, get_default_apid, PusVersion, get_pus_tm_version from tmtccmd.pus_tm.service_17_test import Service17TM, Service17TmPacked -from tmtccmd.ecss.tm import PusTelemetry class TestTelemetry(TestCase): diff --git a/src/tmtccmd/__init__.py b/src/tmtccmd/__init__.py index 1e2e426b..61075055 100644 --- a/src/tmtccmd/__init__.py +++ b/src/tmtccmd/__init__.py @@ -1,8 +1,8 @@ VERSION_NAME = "tmtccmd" VERSION_MAJOR = 1 -VERSION_MINOR = 5 +VERSION_MINOR = 6 VERSION_REVISION = 0 # I think this needs to be in string representation to be parsed so we can't # use a formatted string here. -__version__ = "1.5.0" +__version__ = "1.6.0" diff --git a/src/tmtccmd/ccsds/spacepacket.py b/src/tmtccmd/ccsds/spacepacket.py index 5e9778c0..4240430d 100644 --- a/src/tmtccmd/ccsds/spacepacket.py +++ b/src/tmtccmd/ccsds/spacepacket.py @@ -11,6 +11,7 @@ class PacketTypes(enum.IntEnum): class SpacePacketCommonFields: + """Encapsulates common fields in a SpacePacket""" def __init__( self, packet_type: PacketTypes, apid: int, source_sequence_count: int, data_length: int, version: int = 0b000, secondary_header_flag: int = 0b1, sequence_flags: int = 0b11 @@ -33,13 +34,11 @@ def __init__( # pylint: disable=too-many-instance-attributes class SpacePacketHeaderDeserializer(SpacePacketCommonFields): - """ - This class unnpacks the common spacepacket header, also see PUS structure below or + """This class unnpacks the common spacepacket header, also see PUS structure below or PUS documentation. """ def __init__(self, pus_packet_raw: bytearray): - """ - Deserializes space packet fields from raw bytearray + """Deserializes space packet fields from raw bytearray :param pus_packet_raw: """ if len(pus_packet_raw) < SPACE_PACKET_HEADER_SIZE: @@ -75,14 +74,13 @@ def __init__( self, apid: int, packet_type: PacketTypes, data_length: int, source_sequence_count: int, secondary_header_flag: int = 0b1, version: int = 0b000, sequence_flags: int = 0b11 ): - """ - Serialize raw space packet header - :param packet_type: 0 for telemetry, 1 for telecommands - :param data_length: Length of packet data field + """Serialize raw space packet header. + :param packet_type: 0 for telemetry, 1 for telecommands + :param data_length: Length of packet data field :param source_sequence_count: :param secondary_header_flag: - :param version: Shall be b000 for CCSDS Version 1 packets - :param sequence_flags: 0b11 for stand-alone packets + :param version: Shall be b000 for CCSDS Version 1 packets + :param sequence_flags: 0b11 for stand-alone packets :param apid: """ self.packet_id_bytes = [0x00, 0x00] @@ -104,14 +102,15 @@ def __init__( ) def pack(self) -> bytearray: + """Return the bytearray representation of the space packet header""" return self.header def get_sp_packet_id_bytes( version: int, packet_type: PacketTypes, secondary_header_flag: int, apid: int ) -> Tuple[int, int]: - """ - This function also includes the first three bits reserved for the version + """This function also includes the first three bits reserved for the version. + :param version: :param packet_type: :param secondary_header_flag: diff --git a/src/tmtccmd/com_if/com_interface_base.py b/src/tmtccmd/com_if/com_interface_base.py index c18c2fb1..17545283 100644 --- a/src/tmtccmd/com_if/com_interface_base.py +++ b/src/tmtccmd/com_if/com_interface_base.py @@ -9,6 +9,7 @@ :author: R. Mueller """ from abc import abstractmethod +from typing import Callable from tmtccmd.ecss.tc import PusTelecommand from tmtccmd.pus_tm.factory import PusTmListT @@ -48,7 +49,7 @@ def open(self, args: any = None) -> None: @abstractmethod def close(self, args: any = None) -> None: """ - Closes the ComIF and releases any held resources (for example a Communication Port) + Closes the ComIF and releases any held resources (for example a Communication Port). :return: """ diff --git a/src/tmtccmd/com_if/tcpip_tcp_com_if.py b/src/tmtccmd/com_if/tcpip_tcp_com_if.py new file mode 100644 index 00000000..5d6d93a4 --- /dev/null +++ b/src/tmtccmd/com_if/tcpip_tcp_com_if.py @@ -0,0 +1,132 @@ +""" +:file: tcpip_tcp_com_if.py +:date: 13.05.2021 +:brief: TCP communication interface +:author: R. Mueller +""" +import socket +import time +import threading +from collections import deque +from typing import Union + +from tmtccmd.utility.logger import get_logger +from tmtccmd.config.definitions import CoreModeList +from tmtccmd.com_if.com_interface_base import CommunicationInterface, PusTmListT +from tmtccmd.pus_tm.factory import PusTelemetryFactory +from tmtccmd.utility.tmtc_printer import TmTcPrinter +from tmtccmd.ecss.tc import PusTelecommand +from tmtccmd.config.definitions import EthernetAddressT + +LOGGER = get_logger() + +TCP_RECV_WIRETAPPING_ENABLED = False +TCP_SEND_WIRETAPPING_ENABLED = False + + +# pylint: disable=abstract-method +# pylint: disable=arguments-differ +# pylint: disable=too-many-arguments +class TcpIpTcpComIF(CommunicationInterface): + """ + Communication interface for UDP communication. + """ + def __init__( + self, com_if_key: str, tm_polling_freqency: int, tm_timeout: float, tc_timeout_factor: float, + send_address: EthernetAddressT, max_recv_size: int, max_packets_stored: int = 50, + tmtc_printer: Union[None, TmTcPrinter] = None, init_mode: int = CoreModeList.LISTENER_MODE): + """ + Initialize a communication interface to send and receive UDP datagrams. + :param tm_timeout: + :param tc_timeout_factor: + :param send_address: + :param max_recv_size: + :param tmtc_printer: Printer instance, can be passed optionally to allow packet debugging + """ + super().__init__(com_if_key=com_if_key, tmtc_printer=tmtc_printer) + self.tm_timeout = tm_timeout + self.tc_timeout_factor = tc_timeout_factor + self.tm_polling_frequency = tm_polling_freqency + self.send_address = send_address + self.max_recv_size = max_recv_size + self.max_packets_stored = max_packets_stored + self.init_mode = init_mode + + self.__last_connection_time = 0 + self.__tm_thread_kill_signal = threading.Event() + # Separate thread to request TM packets periodically if no TCs are being sent + self.__tcp_conn_thread = threading.Thread(target=self.__tcp_tm_client, daemon=True) + self.__tm_queue = deque() + # Only allow one connection to OBSW at a time for now by using this lock + self.__socket_lock = threading.Lock() + + def __del__(self): + try: + self.close() + except IOError: + LOGGER.warning("Could not close UDP communication interface!") + + def initialize(self, args: any = None) -> any: + self.__tm_thread_kill_signal.clear() + + def open(self, args: any = None): + self.__tcp_conn_thread.start() + + def close(self, args: any = None) -> None: + self.__tm_thread_kill_signal.set() + self.__tcp_conn_thread.join(self.tm_polling_frequency) + + def send_data(self, data: bytearray): + try: + with self.__socket_lock: + tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcp_socket.connect(self.send_address) + tcp_socket.sendto(data, self.send_address) + tcp_socket.shutdown(socket.SHUT_WR) + self.__receive_tm_packets(tcp_socket) + self.__last_connection_time = time.time() + tcp_socket.close() + except ConnectionRefusedError: + LOGGER.warning("TCP connection attempt failed..") + + def send_telecommand(self, tc_packet: bytearray, tc_packet_obj: PusTelecommand) -> None: + self.send_data(data=tc_packet) + + def receive_telemetry(self, poll_timeout: float = 0) -> PusTmListT: + tm_packet_list = [] + while self.__tm_queue: + tm_packet_list.append(self.__tm_queue.pop()) + return tm_packet_list + + def __tcp_tm_client(self): + while True and not self.__tm_thread_kill_signal.is_set(): + if time.time() - self.__last_connection_time >= self.tm_polling_frequency: + try: + with self.__socket_lock: + tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcp_socket.connect(self.send_address) + tcp_socket.shutdown(socket.SHUT_WR) + self.__receive_tm_packets(tcp_socket=tcp_socket) + self.__last_connection_time = time.time() + except ConnectionRefusedError: + LOGGER.warning("TCP connection attempt failed..") + self.__last_connection_time = time.time() + time.sleep(self.tm_polling_frequency / 2.0) + + def __receive_tm_packets(self, tcp_socket: socket.socket): + while True: + bytes_recvd = tcp_socket.recv(self.max_recv_size) + if len(bytes_recvd) > 0: + if self.__tm_queue.__len__() >= self.max_packets_stored: + LOGGER.warning("Number of packets in TCP queue too large. Overwriting old packets..") + self.__tm_queue.pop() + tm_packet = PusTelemetryFactory.create(bytes_recvd) + self.__tm_queue.appendleft(tm_packet) + elif bytes_recvd is None or len(bytes_recvd) == 0: + break + + def data_available(self, timeout: float = 0) -> bool: + if self.__tm_queue: + return True + else: + return False diff --git a/src/tmtccmd/com_if/tcpip_udp_com_if.py b/src/tmtccmd/com_if/tcpip_udp_com_if.py index ff00b41b..365cf383 100644 --- a/src/tmtccmd/com_if/tcpip_udp_com_if.py +++ b/src/tmtccmd/com_if/tcpip_udp_com_if.py @@ -1,9 +1,8 @@ """ -@file tmtcc_ethernet_com_if.py -@date 01.11.2019 -@brief Ethernet Communication Interface - -@author R. Mueller +:file: tcpip_udp_com_if.py +:date: 13.05.2021 +:brief: UDP Communication Interface +:author: R. Mueller """ import select import socket diff --git a/src/tmtccmd/com_if/tcpip_utilities.py b/src/tmtccmd/com_if/tcpip_utilities.py index beebcb57..f1ee19df 100644 --- a/src/tmtccmd/com_if/tcpip_utilities.py +++ b/src/tmtccmd/com_if/tcpip_utilities.py @@ -2,7 +2,6 @@ import socket import struct import enum -from typing import Union from tmtccmd.config.definitions import EthernetAddressT from tmtccmd.utility.json_handler import check_json_file @@ -12,6 +11,12 @@ LOGGER = get_logger() +class TcpIpType(enum.Enum): + TCP = enum.auto() + UDP = enum.auto() + UDP_RECV = enum.auto() + + class TcpIpConfigIds(enum.Enum): from enum import auto SEND_ADDRESS = auto() @@ -20,80 +25,63 @@ class TcpIpConfigIds(enum.Enum): def determine_udp_send_address(json_cfg_path: str) -> EthernetAddressT: - address_tuple = () - reconfigure_ip_address = False - if not check_json_file(json_cfg_path=json_cfg_path): - reconfigure_ip_address = True + return determine_tcpip_address(tcpip_type=TcpIpType.UDP, json_cfg_path=json_cfg_path) - with open(json_cfg_path, "r") as write: - load_data = json.load(write) - if JsonKeyNames.TCPIP_UDP_DEST_IP_ADDRESS.value not in load_data or \ - JsonKeyNames.TCPIP_UDP_DEST_PORT.value not in load_data: - reconfigure_ip_address = True - else: - ip_address = load_data[JsonKeyNames.TCPIP_UDP_DEST_IP_ADDRESS.value] - port = int(load_data[JsonKeyNames.TCPIP_UDP_DEST_PORT.value]) - address_tuple = ip_address, port - if reconfigure_ip_address: - address_tuple = prompt_ip_address(type_str="UDP destination") - save_to_json = input("Do you want to store the destination send address configuration? [y/n]: ") - if save_to_json.lower() in ['y', "yes"]: - with open(json_cfg_path, "r+") as file: - json_dict = json.load(file) - json_dict[JsonKeyNames.TCPIP_UDP_DEST_IP_ADDRESS.value] = address_tuple[0] - json_dict[JsonKeyNames.TCPIP_UDP_DEST_PORT.value] = address_tuple[1] - file.seek(0) - json.dump(json_dict, file, indent=4) - LOGGER.info( - "Destination IP address was stored to the JSON file config/tmtcc_config.json." - ) - LOGGER.info("Delete this file or edit it manually to change the loaded IP addresses.") - return address_tuple +def determine_tcp_send_address(json_cfg_path: str) -> EthernetAddressT: + return determine_tcpip_address(tcpip_type=TcpIpType.TCP, json_cfg_path=json_cfg_path) + +def determine_udp_recv_address(json_cfg_path: str) -> EthernetAddressT: + return determine_tcpip_address(tcpip_type=TcpIpType.UDP_RECV, json_cfg_path=json_cfg_path) -def determine_udp_recv_address(json_cfg_path: str) -> Union[None, EthernetAddressT]: + +def determine_tcpip_address(tcpip_type: TcpIpType, json_cfg_path: str) -> EthernetAddressT: address_tuple = () reconfigure_ip_address = False if not check_json_file(json_cfg_path=json_cfg_path): reconfigure_ip_address = True + if tcpip_type == TcpIpType.TCP: + json_key_address = JsonKeyNames.TCPIP_TCP_DEST_IP_ADDRESS.value + json_key_port = JsonKeyNames.TCPIP_TCP_DEST_PORT.value + info_string = "TCP destination" + elif tcpip_type == TcpIpType.UDP: + json_key_address = JsonKeyNames.TCPIP_UDP_DEST_IP_ADDRESS.value + json_key_port = JsonKeyNames.TCPIP_UDP_DEST_PORT.value + info_string = "UDP destination" + elif tcpip_type == TcpIpType.UDP_RECV: + json_key_address = JsonKeyNames.TCPIP_UDP_RECV_IP_ADDRESS.value + json_key_port = JsonKeyNames.TCPIP_UDP_RECV_PORT.value + info_string = "UDP receive destination" + else: + json_key_address = JsonKeyNames.TCPIP_UDP_DEST_IP_ADDRESS.value + json_key_port = JsonKeyNames.TCPIP_UDP_DEST_PORT.value + info_string = "UDP destination" + with open(json_cfg_path, "r") as write: load_data = json.load(write) - if JsonKeyNames.TCPIP_UDP_RECV_IP_ADDRESS.value not in load_data or \ - JsonKeyNames.TCPIP_UDP_RECV_PORT.value not in load_data: + if json_key_address not in load_data or json_key_port not in load_data: reconfigure_ip_address = True else: - ip_address = load_data[JsonKeyNames.TCPIP_UDP_RECV_IP_ADDRESS.value] - if ip_address is None: - return None - port = int(load_data[JsonKeyNames.TCPIP_UDP_RECV_PORT.value]) + ip_address = load_data[json_key_address] + port = int(load_data[json_key_port]) address_tuple = ip_address, port if reconfigure_ip_address: - use_recv_addr = input("Use receive address to bind client to? " - "This is not necessary [y/n]: ") - if use_recv_addr not in ["y", "yes", "1"]: - with open(json_cfg_path, "r+") as file: - json_dict = json.load(file) - json_dict[JsonKeyNames.TCPIP_UDP_RECV_IP_ADDRESS.value] = None - json_dict[JsonKeyNames.TCPIP_UDP_RECV_PORT.value] = None - file.seek(0) - json.dump(json_dict, file, indent=4) - return None - address_tuple = prompt_ip_address(type_str="UDP receive") - save_to_json = input("Do you want to store the UDP receive address configuration? [y/n]: ") + address_tuple = prompt_ip_address(type_str=info_string) + save_to_json = input(f"Do you want to store the {info_string} configuration? [y/n]: ") if save_to_json.lower() in ['y', "yes"]: with open(json_cfg_path, "r+") as file: json_dict = json.load(file) - json_dict[JsonKeyNames.TCPIP_UDP_RECV_IP_ADDRESS.value] = address_tuple[0] - json_dict[JsonKeyNames.TCPIP_UDP_RECV_PORT.value] = address_tuple[1] + json_dict[json_key_address] = address_tuple[0] + json_dict[json_key_port] = address_tuple[1] file.seek(0) json.dump(json_dict, file, indent=4) LOGGER.info( - "Reception IP address was stored to the JSON file config/tmtcc_config.json." + f"{info_string} was stored to the JSON file config/tmtcc_config.json." ) - LOGGER.info("Delete this file or edit it manually to change the loaded IP addresses.") + LOGGER.info("Delete this file or edit it manually to change the used addresses.") return address_tuple @@ -134,35 +122,39 @@ def prompt_ip_address(type_str: str) -> EthernetAddressT: return address_tuple -def determine_recv_buffer_len(json_cfg_path: str, udp: bool): +def determine_recv_buffer_len(json_cfg_path: str, tcpip_type: TcpIpType): recv_max_size = 0 reconfigure_recv_buf_size = False + if tcpip_type == TcpIpType.UDP: + json_key = JsonKeyNames.TCPIP_UDP_RECV_MAX_SIZE.value + elif tcpip_type == TcpIpType.TCP: + json_key = JsonKeyNames.TCPIP_TCP_RECV_MAX_SIZE.value if not check_json_file(json_cfg_path=json_cfg_path): reconfigure_recv_buf_size = True with open(json_cfg_path, "r") as write: load_data = json.load(write) - if JsonKeyNames.TCPIP_UDP_RECV_MAX_SIZE.value not in load_data: + if json_key not in load_data: reconfigure_recv_buf_size = True else: - recv_max_size = load_data[JsonKeyNames.TCPIP_UDP_RECV_MAX_SIZE.value] + recv_max_size = load_data[json_key] if reconfigure_recv_buf_size: - recv_max_size = prompt_recv_buffer_len(udp=udp) + recv_max_size = prompt_recv_buffer_len(tcpip_type=tcpip_type) store_size = input("Do you store the maximum receive size configuration? [y/n]: ") if store_size.lower() in ["y", "yes", "1"]: with open(json_cfg_path, "r+") as file: json_dict = json.load(file) - json_dict[JsonKeyNames.TCPIP_UDP_RECV_MAX_SIZE.value] = recv_max_size + json_dict[json_key] = recv_max_size file.seek(0) json.dump(json_dict, file, indent=4) return recv_max_size -def prompt_recv_buffer_len(udp: bool) -> int: +def prompt_recv_buffer_len(tcpip_type: TcpIpType) -> int: + if tcpip_type == TcpIpType.UDP: + type_str = "UDP" + else: + type_str = "TCP" while True: - if udp: - type_str = "UDP" - else: - type_str = "TCP" recv_max_size = input(f"Please enter maximum receive size for {type_str} packets: ") if not recv_max_size.isdigit(): LOGGER.warning("Specified size is not a number.") diff --git a/src/tmtccmd/config/args.py b/src/tmtccmd/config/args.py index 5f7226aa..617b33d9 100644 --- a/src/tmtccmd/config/args.py +++ b/src/tmtccmd/config/args.py @@ -4,7 +4,7 @@ import argparse import sys -from tmtccmd.config.definitions import CoreModeList, CoreComInterfaces +from tmtccmd.config.definitions import CoreModeList, CoreComInterfaces, ServiceOpCodeDictT from tmtccmd.utility.logger import get_logger @@ -25,8 +25,7 @@ def parse_input_arguments( def parse_default_input_arguments(print_known_args: bool = False, print_unknown_args: bool = False): - """ - Parses all input arguments + """Parses all input arguments :return: Input arguments contained in a special namespace and accessable by args. """ from tmtccmd.utility.conf_util import AnsiColors @@ -52,17 +51,22 @@ def parse_default_input_arguments(print_known_args: bool = False, print_unknown_ arg_parser.add_argument( '--tc_timeout_factor', type=float, help='TC Timeout Factor. Multiplied with ' - 'TM Timeout, TC sent again after this time period. Default: 3.5', default=3.5) + 'TM Timeout, TC sent again after this time period. Default: 3.5', default=3.5 + ) arg_parser.add_argument( '-r', '--raw_data_print', help='Supply -r to print all raw TM data directly', - action='store_true') + action='store_true' + ) arg_parser.add_argument( - '-d', '--short_display_mode', help='Supply -d to print short output', action='store_true') + '-d', '--short_display_mode', help='Supply -d to print short output', action='store_true' + ) arg_parser.add_argument( - '--hk', dest='print_hk', help='Supply -k or --hk to print HK data', action='store_true') + '--hk', dest='print_hk', help='Supply -k or --hk to print HK data', action='store_true' + ) arg_parser.add_argument( '--rs', dest="resend_tc", help='Specify whether TCs are sent again after timeout', - action='store_true') + action='store_true' + ) if len(sys.argv) == 1: LOGGER.info("No input arguments specified. Run with -h to get list of arguments") @@ -83,10 +87,10 @@ def parse_default_input_arguments(print_known_args: bool = False, print_unknown_ def add_generic_arguments(arg_parser: argparse.ArgumentParser): - arg_parser.add_argument('-s', '--service', type=str, help='Service to test. Default: 17', default="17") + arg_parser.add_argument('-s', '--service', type=str, help='Service to test', default=None) arg_parser.add_argument( - '-o', '--op_code', - help='Operation code, which is passed to the TC packer functions', default=0 + '-o', '--op_code', help='Operation code, which is passed to the TC packer functions', + default=None ) arg_parser.add_argument( '-l', '--listener', @@ -158,10 +162,8 @@ def add_ethernet_arguments(arg_parser: argparse.ArgumentParser): def args_post_processing(args, unknown: list) -> None: - """ - Handles the parsed arguments. - :param args: Namespace objects - (see https://docs.python.org/dev/library/argparse.html#argparse.Namespace) + """Handles the parsed arguments. + :param args: Namespace objects (see https://docs.python.org/dev/library/argparse.html#argparse.Namespace) :param unknown: List of unknown parameters. :return: None """ @@ -174,27 +176,84 @@ def args_post_processing(args, unknown: list) -> None: def handle_unspecified_args(args) -> None: - """ - If some arguments are unspecified, they are set here with (variable) default values. + """If some arguments are unspecified, they are set here with (variable) default values. :param args: :return: None """ + from tmtccmd.config.hook import get_global_hook_obj if args.tm_timeout is None: args.tm_timeout = 5.0 if args.mode is None: args.mode = CoreModeList.SEQUENTIAL_CMD_MODE + service_op_code_dict = dict() + if args.service is None or args.op_code is None: + hook_obj = get_global_hook_obj() + service_op_code_dict = hook_obj.get_service_op_code_dictionary() + if args.service is None: + LOGGER.info("No service argument (-s) specified, prompting from user..") + # Try to get the service list from the hook base and prompt service from user + args.service = prompt_service(service_op_code_dict) + if args.op_code is None: + current_service = args.service + args.op_code = prompt_op_code(service_op_code_dict=service_op_code_dict, service=current_service) + elif args.op_code is None: + current_service = args.service + args.op_code = prompt_op_code(service_op_code_dict=service_op_code_dict, service=current_service) def handle_empty_args(args) -> None: - """ - If no args were supplied, request input from user directly. - TODO: This still needs to be extended. + """If no args were supplied, request input from user directly. :param args: :return: """ - LOGGER.info("No arguments specified. Setting dummy mode..") - args.com_if = CoreComInterfaces.DUMMY - LOGGER.info("Setting sequential command mode..") - args.mode = CoreModeList.SEQUENTIAL_CMD_MODE - LOGGER.info("Setting service 17 (ping command)..") - args.__service = 17 + LOGGER.info("No arguments specified..") + handle_unspecified_args(args=args) + + +def prompt_service(service_op_code_dict: ServiceOpCodeDictT) -> str: + service_adjustment = 10 + info_adjustment = 30 + horiz_line_num = service_adjustment + info_adjustment + 3 + horiz_line = horiz_line_num * "-" + service_string = "Service".ljust(service_adjustment) + info_string = "Information".ljust(info_adjustment) + while True: + LOGGER.info(f"{service_string} | {info_string}") + LOGGER.info(horiz_line) + for service_entry in service_op_code_dict.items(): + adjusted_service_entry = service_entry[0].ljust(service_adjustment) + adjusted_service_info = service_entry[1][0].ljust(info_adjustment) + LOGGER.info(f"{adjusted_service_entry} | {adjusted_service_info}") + service_string = input("Please select a service by specifying the key: ") + if service_string in service_op_code_dict: + LOGGER.info(f"Selected service: {service_string}") + return service_string + else: + LOGGER.warning("Invalid key, try again") + + +def prompt_op_code(service_op_code_dict: ServiceOpCodeDictT, service: str) -> str: + op_code_adjustment = 16 + info_adjustment = 34 + horz_line_num = op_code_adjustment + info_adjustment + 3 + horiz_line = horz_line_num * "-" + op_code_string = "Operation Code".ljust(op_code_adjustment) + info_string = "Information".ljust(info_adjustment) + while True: + LOGGER.info(f"{op_code_string} | {info_string}") + LOGGER.info(horiz_line) + if service in service_op_code_dict: + op_code_dict = service_op_code_dict[service][1] + for op_code_entry in op_code_dict.items(): + adjusted_op_code_entry = op_code_entry[0].ljust(op_code_adjustment) + adjusted_op_code_info = op_code_entry[1][0].ljust(info_adjustment) + LOGGER.info(f"{adjusted_op_code_entry} | {adjusted_op_code_info}") + op_code_string = input("Please select an operation code by specifying the key: ") + if op_code_string in op_code_dict: + LOGGER.info(f"Selected op code: {op_code_string}") + return op_code_string + else: + LOGGER.warning("Invalid key, try again") + else: + LOGGER.warning("Service not in dictionary. Setting default operation code 0") + return "0" diff --git a/src/tmtccmd/config/com_if.py b/src/tmtccmd/config/com_if.py index 86157e5b..17ac8a50 100644 --- a/src/tmtccmd/config/com_if.py +++ b/src/tmtccmd/config/com_if.py @@ -1,43 +1,37 @@ import sys -from typing import Union +from typing import Optional from tmtccmd.config.definitions import CoreGlobalIds, CoreComInterfaces from tmtccmd.core.globals_manager import get_global, update_global from tmtccmd.com_if.com_interface_base import CommunicationInterface from tmtccmd.com_if.serial_com_if import SerialConfigIds, SerialCommunicationType, SerialComIF from tmtccmd.com_if.serial_utilities import determine_com_port, determine_baud_rate -from tmtccmd.com_if.tcpip_utilities import TcpIpConfigIds +from tmtccmd.com_if.tcpip_utilities import TcpIpConfigIds, TcpIpType from tmtccmd.utility.logger import get_logger from tmtccmd.utility.tmtc_printer import TmTcPrinter - +from tmtccmd.com_if.tcpip_udp_com_if import TcpIpUdpComIF +from tmtccmd.com_if.tcpip_tcp_com_if import TcpIpTcpComIF LOGGER = get_logger() def create_communication_interface_default( com_if_key: str, tmtc_printer: TmTcPrinter, json_cfg_path: str -) -> Union[CommunicationInterface, None]: +) -> Optional[CommunicationInterface]: + """Return the desired communication interface object + + :param com_if_key: + :param tmtc_printer: + :param json_cfg_path: + :return: + """ from tmtccmd.com_if.dummy_com_if import DummyComIF - from tmtccmd.com_if.tcpip_udp_com_if import TcpIpUdpComIF from tmtccmd.com_if.qemu_com_if import QEMUComIF - """ - Return the desired communication interface object - :param tmtc_printer: TmTcPrinter object. - :return: CommunicationInterface object - """ + try: - if com_if_key == CoreComInterfaces.TCPIP_UDP.value: - default_tcpip_udp_cfg_setup(json_cfg_path=json_cfg_path) - ethernet_cfg_dict = get_global(CoreGlobalIds.ETHERNET_CONFIG) - send_addr = ethernet_cfg_dict[TcpIpConfigIds.SEND_ADDRESS] - recv_addr = ethernet_cfg_dict[TcpIpConfigIds.RECV_ADDRESS] - max_recv_size = ethernet_cfg_dict[TcpIpConfigIds.RECV_MAX_SIZE] - init_mode = get_global(CoreGlobalIds.MODE) - communication_interface = TcpIpUdpComIF( - com_if_key=com_if_key, tm_timeout=get_global(CoreGlobalIds.TM_TIMEOUT), - tc_timeout_factor=get_global(CoreGlobalIds.TC_SEND_TIMEOUT_FACTOR), - send_address=send_addr, recv_addr=recv_addr, max_recv_size=max_recv_size, - tmtc_printer=tmtc_printer, init_mode=init_mode + if com_if_key == CoreComInterfaces.TCPIP_UDP.value or com_if_key == CoreComInterfaces.TCPIP_TCP.value: + communication_interface = create_default_tcpip_interface( + com_if_key=com_if_key, json_cfg_path=json_cfg_path, tmtc_printer=tmtc_printer ) elif com_if_key == CoreComInterfaces.SERIAL_DLE.value or \ com_if_key == CoreComInterfaces.SERIAL_FIXED_FRAME.value: @@ -57,9 +51,11 @@ def create_communication_interface_default( ) else: communication_interface = DummyComIF(com_if_key=com_if_key, tmtc_printer=tmtc_printer) + if communication_interface is None: + return communication_interface if not communication_interface.valid: LOGGER.warning("Invalid communication interface!") - sys.exit() + return None communication_interface.initialize() return communication_interface except (IOError, OSError) as e: @@ -68,14 +64,26 @@ def create_communication_interface_default( sys.exit(1) -def default_tcpip_udp_cfg_setup(json_cfg_path: str): - from tmtccmd.com_if.tcpip_utilities import determine_udp_send_address, \ - determine_recv_buffer_len, determine_udp_recv_address +def default_tcpip_cfg_setup(tcpip_type: TcpIpType, json_cfg_path: str): + """Default setup for TCP/IP communication interfaces. This intantiates all required data in the globals + manager so a TCP/IP communication interface can be built with :func:`create_default_tcpip_interface` + :param tcpip_type: + :param json_cfg_path: + :return: + """ + from tmtccmd.com_if.tcpip_utilities import determine_udp_send_address, determine_tcp_send_address, \ + determine_recv_buffer_len update_global(CoreGlobalIds.USE_ETHERNET, True) - # This will either load the addresses from a JSON file or prompt them from the user. - send_tuple = determine_udp_send_address(json_cfg_path=json_cfg_path) - recv_tuple = determine_udp_recv_address(json_cfg_path=json_cfg_path) - max_recv_buf_size = determine_recv_buffer_len(json_cfg_path=json_cfg_path, udp=True) + recv_tuple = None + if tcpip_type == TcpIpType.UDP: + # This will either load the addresses from a JSON file or prompt them from the user. + send_tuple = determine_udp_send_address(json_cfg_path=json_cfg_path) + # Not used for now + # from tmtccmd.com_if.tcpip_utilities import determine_tcpip_address + # recv_tuple = determine_tcpip_address(tcpip_type=TcpIpType.UDP_RECV, json_cfg_path=json_cfg_path) + elif tcpip_type == TcpIpType.TCP: + send_tuple = determine_tcp_send_address(json_cfg_path=json_cfg_path) + max_recv_buf_size = determine_recv_buffer_len(json_cfg_path=json_cfg_path, tcpip_type=tcpip_type) ethernet_cfg_dict = get_global(CoreGlobalIds.ETHERNET_CONFIG) ethernet_cfg_dict.update({TcpIpConfigIds.SEND_ADDRESS: send_tuple}) ethernet_cfg_dict.update({TcpIpConfigIds.RECV_ADDRESS: recv_tuple}) @@ -84,6 +92,12 @@ def default_tcpip_udp_cfg_setup(json_cfg_path: str): def default_serial_cfg_setup(com_if_key: str, json_cfg_path: str): + """Default setup for serial interfaces + + :param com_if_key: + :param json_cfg_path: + :return: + """ baud_rate = determine_baud_rate(json_cfg_path=json_cfg_path) if com_if_key == CoreComInterfaces.SERIAL_DLE.value: serial_port = determine_com_port(json_cfg_path=json_cfg_path) @@ -92,9 +106,54 @@ def default_serial_cfg_setup(com_if_key: str, json_cfg_path: str): set_up_serial_cfg(json_cfg_path=json_cfg_path, com_if_key=com_if_key, baud_rate=baud_rate, com_port=serial_port) +def create_default_tcpip_interface( + com_if_key: str, tmtc_printer: TmTcPrinter, json_cfg_path: str +) -> Optional[CommunicationInterface]: + """Create a default serial interface. Requires a certain set of global variables set up. See + :func:`default_tcpip_cfg_setup` for more details. + + :param com_if_key: + :param tmtc_printer: + :param json_cfg_path: + :return: + """ + if com_if_key == CoreComInterfaces.TCPIP_UDP.value: + default_tcpip_cfg_setup(tcpip_type=TcpIpType.UDP, json_cfg_path=json_cfg_path) + elif com_if_key == CoreComInterfaces.TCPIP_TCP.value: + default_tcpip_cfg_setup(tcpip_type=TcpIpType.TCP, json_cfg_path=json_cfg_path) + ethernet_cfg_dict = get_global(CoreGlobalIds.ETHERNET_CONFIG) + send_addr = ethernet_cfg_dict[TcpIpConfigIds.SEND_ADDRESS] + recv_addr = ethernet_cfg_dict[TcpIpConfigIds.RECV_ADDRESS] + max_recv_size = ethernet_cfg_dict[TcpIpConfigIds.RECV_MAX_SIZE] + init_mode = get_global(CoreGlobalIds.MODE) + if com_if_key == CoreComInterfaces.TCPIP_UDP.value: + communication_interface = TcpIpUdpComIF( + com_if_key=com_if_key, tm_timeout=get_global(CoreGlobalIds.TM_TIMEOUT), + tc_timeout_factor=get_global(CoreGlobalIds.TC_SEND_TIMEOUT_FACTOR), + send_address=send_addr, recv_addr=recv_addr, max_recv_size=max_recv_size, + tmtc_printer=tmtc_printer, init_mode=init_mode + ) + elif com_if_key == CoreComInterfaces.TCPIP_TCP.value: + communication_interface = TcpIpTcpComIF( + com_if_key=com_if_key, tm_timeout=get_global(CoreGlobalIds.TM_TIMEOUT), + tc_timeout_factor=get_global(CoreGlobalIds.TC_SEND_TIMEOUT_FACTOR), + tm_polling_freqency=0.5, send_address=send_addr, max_recv_size=max_recv_size, tmtc_printer=tmtc_printer, + init_mode=init_mode + ) + return communication_interface + + def create_default_serial_interface( com_if_key: str, tmtc_printer: TmTcPrinter, json_cfg_path: str -) -> Union[CommunicationInterface, None]: +) -> Optional[CommunicationInterface]: + """Create a default serial interface. Requires a certain set of global variables set up. See + :func:`set_up_serial_cfg` for more details. + + :param com_if_key: + :param tmtc_printer: + :param json_cfg_path: + :return: + """ try: # For a serial communication interface, there are some configuration values like # baud rate and serial port which need to be set once but are expected to stay @@ -134,9 +193,9 @@ def set_up_serial_cfg( ser_com_type: SerialCommunicationType = SerialCommunicationType.DLE_ENCODING, ser_frame_size: int = 256, dle_queue_len: int = 25, dle_frame_size: int = 1024 ): - """ - Default configuration to set up serial communication. The serial port and the baud rate - will be determined from a JSON configuration file and prompted from the user + """Default configuration to set up serial communication. The serial port and the baud rate + will be determined from a JSON configuration file and prompted from the user. Sets up all global variables + so that a serial communication interface can be built with :func:`create_default_serial_interface` :param json_cfg_path: :param com_if_key: :param com_port: diff --git a/src/tmtccmd/config/definitions.py b/src/tmtccmd/config/definitions.py index 8d43da8b..33d88ffe 100644 --- a/src/tmtccmd/config/definitions.py +++ b/src/tmtccmd/config/definitions.py @@ -2,7 +2,7 @@ @brief Definitions for the TMTC commander core """ import enum -from typing import Tuple, Dict, Union +from typing import Tuple, Dict, Optional class CoreGlobalIds(enum.IntEnum): @@ -54,10 +54,10 @@ class OpCodeDictKeys(enum.IntEnum): OpCodeNameT = str OpCodeInfoT = str # Operation code options are optional. If none are supplied, default values are assumed -OpCodeOptionsT = Union[None, Dict[OpCodeDictKeys, any]] +OpCodeOptionsT = Optional[Dict[OpCodeDictKeys, any]] OpCodeEntryT = Dict[OpCodeNameT, Tuple[OpCodeInfoT, OpCodeOptionsT]] # It is possible to specify a service without any op codes -ServiceDictValueT = Union[None, Tuple[ServiceInfoT, OpCodeEntryT]] +ServiceDictValueT = Optional[Tuple[ServiceInfoT, OpCodeEntryT]] ServiceOpCodeDictT = Dict[ServiceNameT, ServiceDictValueT] # Com Interface Types @@ -71,6 +71,7 @@ class CoreComInterfaces(enum.Enum): DUMMY = "dummy" SERIAL_DLE = "ser_dle" TCPIP_UDP = "udp" + TCPIP_TCP = "tcp" SERIAL_FIXED_FRAME = "ser_fixed" SERIAL_QEMU = "ser_qemu" UNSPECIFIED = "unspec" @@ -80,6 +81,7 @@ class CoreComInterfaces(enum.Enum): CoreComInterfaces.DUMMY.value: ("Dummy Interface"), CoreComInterfaces.SERIAL_DLE.value: ("Serial Interace with DLE encoding"), CoreComInterfaces.TCPIP_UDP.value: ("TCP/IP with UDP datagrams"), + CoreComInterfaces.TCPIP_TCP.value: ("TCP/IP with TCP"), CoreComInterfaces.SERIAL_FIXED_FRAME.value: ("Serial Interface with fixed size frames"), CoreComInterfaces.SERIAL_QEMU.value: ("Serial Interface using QEMU"), CoreComInterfaces.UNSPECIFIED.value: ("Unspecified") diff --git a/src/tmtccmd/config/globals.py b/src/tmtccmd/config/globals.py index 38042454..0a604e7f 100644 --- a/src/tmtccmd/config/globals.py +++ b/src/tmtccmd/config/globals.py @@ -10,7 +10,7 @@ from tmtccmd.config.definitions import CoreGlobalIds, CoreModeList, CoreServiceList, \ CoreModeStrings, CoreComInterfacesDict, CoreComInterfaces from tmtccmd.com_if.com_if_utilities import determine_com_if -from tmtccmd.config.com_if import default_serial_cfg_setup, default_tcpip_udp_cfg_setup +from tmtccmd.config.com_if import default_serial_cfg_setup, default_tcpip_cfg_setup from tmtccmd.config.definitions import DEBUG_MODE, ServiceOpCodeDictT, OpCodeDictKeys, ComIFDictT LOGGER = get_logger() @@ -37,9 +37,11 @@ def get_glob_com_if_dict() -> ComIFDictT: def set_default_globals_pre_args_parsing( gui: bool, apid: int, pus_tc_version: PusVersion = PusVersion.PUS_C, pus_tm_version: PusVersion = PusVersion.PUS_C, - com_if_id: str = CoreComInterfaces.DUMMY.value, custom_com_if_dict: ComIFDictT = dict(), display_mode="long", + com_if_id: str = CoreComInterfaces.DUMMY.value, custom_com_if_dict=None, display_mode="long", tm_timeout: float = 4.0, print_to_file: bool = True, tc_send_timeout_factor: float = 2.0 ): + if custom_com_if_dict is None: + custom_com_if_dict = dict() update_global(CoreGlobalIds.APID, apid) set_default_apid(default_apid=apid) set_pus_tc_version(pus_tc_version) @@ -49,6 +51,7 @@ def set_default_globals_pre_args_parsing( update_global(CoreGlobalIds.TM_TIMEOUT, tm_timeout) update_global(CoreGlobalIds.DISPLAY_MODE, display_mode) update_global(CoreGlobalIds.PRINT_TO_FILE, print_to_file) + update_global(CoreGlobalIds.CURRENT_SERVICE, CoreServiceList.SERVICE_17.value) update_global(CoreGlobalIds.SERIAL_CONFIG, dict()) update_global(CoreGlobalIds.ETHERNET_CONFIG, dict()) set_glob_com_if_dict(custom_com_if_dict=custom_com_if_dict) @@ -71,17 +74,17 @@ def set_default_globals_post_args_parsing( custom_services_list: Union[None, List[Union[collections.abc.Iterable, dict]]] = None, custom_com_if_dict: Dict[str, any] = None ): - """ - This function takes the argument namespace as a parameter and determines + """This function takes the argument namespace as a parameter and determines a set of globals from the parsed arguments. If custom dictionaries are specified, the developer should take care of specifying integers as keys and the string representation of the command line argument as value. This will be used for internalization. - :param args: Namespace generated by parsing command line arguments. + + :param args: Namespace generated by parsing command line arguments. :param json_cfg_path: - :param custom_modes_list: List of collections or dictionaries containing custom modes - :param custom_services_list: List of collections or dictionaries containing custom services - :param custom_com_if_dict: List of collections or dictionaries containing customcommunication interfaces + :param custom_modes_list: List of collections or dictionaries containing custom modes + :param custom_services_list: List of collections or dictionaries containing custom services + :param custom_com_if_dict: List of collections or dictionaries containing customcommunication interfaces :return: """ @@ -140,11 +143,6 @@ def set_default_globals_post_args_parsing( except AttributeError: LOGGER.exception("Passed arguments are missing components.") - # Same as above, but for server address and server port - if com_if_key == CoreComInterfaces.TCPIP_UDP: - # TODO: Port and IP address can also be passed as CLI parameters. - # Use them here if applicable? - default_tcpip_udp_cfg_setup(json_cfg_path=json_cfg_path) if DEBUG_MODE: print_core_globals() @@ -171,11 +169,11 @@ def check_and_set_core_mode_arg( mode_arg: any, custom_modes_list: Union[None, List[Union[dict, collections.abc.Iterable]]] = None ) -> int: - """ - Checks whether the mode argument is contained inside the core mode list integer enumeration + """Checks whether the mode argument is contained inside the core mode list integer enumeration or a custom mode list integer which can be passed optionally. This function will set the single command mode as the global mode parameter if the passed mode is not found in either enumerations. + :param mode_arg: :param custom_modes_list: :return: diff --git a/src/tmtccmd/config/hook.py b/src/tmtccmd/config/hook.py index bacd80d1..4b0a7a28 100644 --- a/src/tmtccmd/config/hook.py +++ b/src/tmtccmd/config/hook.py @@ -1,61 +1,66 @@ import sys import argparse from abc import abstractmethod -from typing import Union, Dict, Tuple +from typing import Dict, Tuple, Optional, Union from tmtccmd.config.definitions import DEFAULT_APID, ServiceOpCodeDictT from tmtccmd.utility.logger import get_logger +from tmtccmd.core.backend import TmTcHandler +from tmtccmd.utility.tmtc_printer import TmTcPrinter +from tmtccmd.ecss.tm import PusTelemetry +from tmtccmd.pus_tc.definitions import TcQueueT +from tmtccmd.com_if.com_interface_base import CommunicationInterface +from tmtccmd.pus_tm.service_3_base import Service3Base LOGGER = get_logger() class TmTcHookBase: - from tmtccmd.core.backend import TmTcHandler - from tmtccmd.utility.tmtc_printer import TmTcPrinter - from tmtccmd.ecss.tm import PusTelemetry - from tmtccmd.pus_tc.definitions import PusTelecommand - from tmtccmd.pus_tc.definitions import TcQueueT - from tmtccmd.com_if.com_interface_base import CommunicationInterface - from tmtccmd.pus_tm.service_3_base import Service3Base + """This hook allows users to adapt the TMTC commander core to the unique mission requirements. + It is used by implementing all abstract functions and then passing the instance to the TMTC commander core. + """ def __init__(self): pass @abstractmethod def get_version(self) -> str: + """The user can specify a custom version by overriding this function. + :return: + """ from tmtccmd import VERSION_NAME, __version__ return f"{VERSION_NAME} {__version__}" @abstractmethod def get_object_ids(self) -> Dict[bytes, list]: - """ - The user can specify an object ID dictionary here mapping object ID bytearrays to a list (e.g. containing - the string representation) + """The user can specify an object ID dictionary here mapping object ID bytearrays to a list. This list could + contain containing the string representation or additional information about that object ID. """ pass @abstractmethod def add_globals_pre_args_parsing(self, gui: bool = False): - """ - Add all global variables prior to parsing the CLI arguments. - :param gui: Specify whether a GUI is used + """Add all global variables prior to parsing the CLI arguments. + + :param gui: Set to true if the GUI mode is used + :return: """ from tmtccmd.config.globals import set_default_globals_pre_args_parsing set_default_globals_pre_args_parsing(gui=gui, apid=DEFAULT_APID) @abstractmethod def get_json_config_file_path(self) -> str: - """ - The user can specify a path and filename for the JSON configuration file by overriding this function. + """The user can specify a path and filename for the JSON configuration file by overriding this function. + :return: """ return "tmtc_config.json" @abstractmethod def add_globals_post_args_parsing(self, args: argparse.Namespace): - """ - Add global variables prior after parsing the CLI arguments. - :param args: Specify whether a GUI is used + """Add global variables prior after parsing the CLI arguments. + + :param args: Specify whether a GUI is used """ from tmtccmd.config.globals import set_default_globals_post_args_parsing set_default_globals_post_args_parsing(args=args, json_cfg_path=self.get_json_config_file_path()) @@ -63,10 +68,10 @@ def add_globals_post_args_parsing(self, args: argparse.Namespace): @abstractmethod def assign_communication_interface( self, com_if_key: str, tmtc_printer: TmTcPrinter - ) -> Union[CommunicationInterface, None]: - """ - Assign the communication interface used by the TMTC commander to send and receive TMTC with. - :param com_if: Integer representation of the communication interface to be created. + ) -> Optional[CommunicationInterface]: + """Assign the communication interface used by the TMTC commander to send and receive TMTC with. + + :param com_if_key: String key of the communication interface to be created. :param tmtc_printer: Printer utility instance. """ from tmtccmd.config.com_if import create_communication_interface_default @@ -76,15 +81,32 @@ def assign_communication_interface( @abstractmethod def get_service_op_code_dictionary(self) -> ServiceOpCodeDictT: - from tmtccmd.config.definitions import get_default_service_op_code_dict + """This is a dicitonary mapping services represented by strings to an operation code dictionary. + + :return: + """ + from tmtccmd.config.globals import get_default_service_op_code_dict return get_default_service_op_code_dict() @abstractmethod def perform_mode_operation(self, tmtc_backend: TmTcHandler, mode: int): + """Perform custom mode operations + :param tmtc_backend: + :param mode: + :return: + """ pass @abstractmethod def pack_service_queue(self, service: Union[int, str], op_code: str, service_queue: TcQueueT): + """Overriding this function allows the user to package a telecommand queue for a given service and operation + code combination. + + :param service: + :param op_code: + :param service_queue: + :return: + """ pass @abstractmethod @@ -93,27 +115,24 @@ def tm_user_factory_hook(self, raw_tm_packet: bytearray) -> Union[None, PusTelem @staticmethod def custom_args_parsing() -> Union[None, argparse.Namespace]: - """ - The user can implement args parsing here to override the default argument parsing for - the CLI mode - :return + """The user can implement args parsing here to override the default argument parsing for the CLI mode + :return: """ return None @staticmethod def handle_service_8_telemetry( - object_id: bytearray, action_id: int, custom_data: bytearray + object_id: bytes, action_id: int, custom_data: bytearray ) -> Tuple[list, list]: - """ - This function is called by the TMTC core if a Service 8 data reply (subservice 130) + """This function is called by the TMTC core if a Service 8 data reply (subservice 130) is received. The user can return a tuple of two lists, where the first list is a list of header strings to print and the second list is a list of values to print. The TMTC core will take care of printing both lists and logging them. - :param object_id: Object ID bytearray + :param object_id: Byte representation of the object ID :param action_id: :param custom_data: - :return + :return: """ LOGGER.info( "TmTcHookBase: No service 8 handling implemented yet in handle_service_8_telemetry " @@ -123,22 +142,20 @@ def handle_service_8_telemetry( @staticmethod def handle_service_3_housekeeping( - object_id: bytearray, set_id: int, hk_data: bytearray, service3_packet: Service3Base + object_id: bytes, set_id: int, hk_data: bytearray, service3_packet: Service3Base ) -> Tuple[list, list, bytearray, int]: - """ - This function is called when a Service 3 Housekeeping packet is received. - :param object_id: Integer representation of the found object ID. See - the :func:`~tmtccmd.core.hook_base.set_object_ids function for more information - :param set_id: Unique set ID of the HK reply - :param hk_data: HK data. For custom HK handling, whole HK data will be passed here. - Otherwise, a 8 byte SID conisting of the 4 byte object ID and - 4 byte set ID will be assumed and the remaining packet after - the first 4 bytes will be passed here. + """This function is called when a Service 3 Housekeeping packet is received. + + :param object_id: Byte representation of the object ID + :param set_id: Unique set ID of the HK reply + :param hk_data: HK data. For custom HK handling, whole HK data will be passed here. Otherwise, a 8 byte SID + consisting of the 4 byte object ID and 4 byte set ID will be assumed and the remaining + packet after the first 4 bytes will be passed here. :param service3_packet: Service 3 packet object - :return Expects a tuple, consisting of two lists, a bytearray and an integer - The first list contains the header columns, the second list the list with - the corresponding values. The bytearray is the validity buffer, which is usually appended - at the end of the housekeeping packet. The last value is the number of parameters. + :return: Expects a tuple, consisting of two lists, a bytearray and an integer + The first list contains the header columns, the second list the list with + the corresponding values. The bytearray is the validity buffer, which is usually appended + at the end of the housekeeping packet. The last value is the number of parameters. """ LOGGER.info( "TmTcHookBase: No service 3 housekeeping data handling implemented yet in " @@ -148,22 +165,24 @@ def handle_service_3_housekeeping( @staticmethod def handle_service_5_event( - object_id: bytearray, event_id: int, param_1: int, param_2: int + object_id: bytes, event_id: int, param_1: int, param_2: int ) -> str: - """ - This function is called when a Service 5 Event Packet is received. The user can specify a custom + """This function is called when a Service 5 Event Packet is received. The user can specify a custom string here which will be printed to display additional information related to an event. - :param object_id: Integer representation of the found object ID. See - the :func:`~tmtccmd.core.hook_base.set_object_ids function for more information - :param event_id: Two-byte event ID - :param param_1: Four-byte Parameter 1 - :param param_2: Four-byte Parameter 2 - :return: Custom information string which will be printed with the event + + :param object_id: Byte representation of the object ID + :param event_id: Two-byte event ID + :param param_1: Four-byte Parameter 1 + :param param_2: Four-byte Parameter 2 + :return: Custom information string which will be printed with the event """ return "" -def get_global_hook_obj() -> TmTcHookBase: +def get_global_hook_obj() -> Optional[TmTcHookBase]: + """This function can be used to get the handle to the global hook object. + :return: + """ try: from tmtccmd.core.globals_manager import get_global from tmtccmd.config.definitions import CoreGlobalIds @@ -176,5 +195,7 @@ def get_global_hook_obj() -> TmTcHookBase: return cast(TmTcHookBase, hook_obj_raw) except ImportError: LOGGER.exception("Issues importing modules to get global hook handle!") + return None except AttributeError: LOGGER.exception("Attribute error when trying to get global hook handle!") + return None diff --git a/src/tmtccmd/core/backend.py b/src/tmtccmd/core/backend.py index a77e6f56..4900d70b 100644 --- a/src/tmtccmd/core/backend.py +++ b/src/tmtccmd/core/backend.py @@ -23,28 +23,22 @@ class BackendBase: @abstractmethod def initialize(self): - """ - Initialize the backend. Raise RuntimeError or ValueError on failure - """ + """Initialize the backend. Raise RuntimeError or ValueError on failure""" @abstractmethod def start_listener(self): - """ - Start the backend. Raise RuntimeError on failure - """ + """Start the backend. Raise RuntimeError on failure""" @abstractmethod def set_mode(self, mode: int): - """ - Set backend mode + """Set backend mode :param mode: :return: """ class TmTcHandler(BackendBase): - """ - This is the primary class which handles TMTC reception. This can be seen as the backend + """This is the primary class which handles TMTC reception. This can be seen as the backend in case a GUI or front-end is implemented. """ diff --git a/src/tmtccmd/core/frontend.py b/src/tmtccmd/core/frontend.py index 62bb3402..53fb943c 100644 --- a/src/tmtccmd/core/frontend.py +++ b/src/tmtccmd/core/frontend.py @@ -8,7 +8,6 @@ @author R. Mueller, P. Scheurenbrand, D. Nguyen """ import enum -import threading import os import sys import time @@ -17,17 +16,14 @@ from PyQt5.QtWidgets import QMainWindow, QGridLayout, QTableWidget, QWidget, QLabel, QCheckBox, \ QDoubleSpinBox, QFrame, QComboBox, QPushButton, QTableWidgetItem, QMenu, QAction, QMenuBar -from PyQt5 import QtCore from PyQt5.QtGui import QPixmap, QIcon -from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QObject, QThread, QRunnable +from PyQt5.QtCore import Qt, pyqtSignal, QObject, QThread, QRunnable from tmtccmd.core.frontend_base import FrontendBase from tmtccmd.core.backend import TmTcHandler from tmtccmd.config.hook import TmTcHookBase -from tmtccmd.config.definitions import CoreComInterfacesDict, CoreGlobalIds, CoreModeList, CoreComInterfaces -from tmtccmd.config.globals import get_global_apid +from tmtccmd.config.definitions import CoreGlobalIds, CoreModeList, CoreComInterfaces from tmtccmd.config.hook import get_global_hook_obj -from tmtccmd.pus_tc.definitions import PusTelecommand from tmtccmd.utility.logger import get_logger from tmtccmd.core.globals_manager import get_global, update_global from tmtccmd.com_if.tcpip_utilities import TcpIpConfigIds diff --git a/src/tmtccmd/core/globals_manager.py b/src/tmtccmd/core/globals_manager.py index 5c9b97b9..680ecd74 100644 --- a/src/tmtccmd/core/globals_manager.py +++ b/src/tmtccmd/core/globals_manager.py @@ -10,9 +10,7 @@ class GlobalsManager: @classmethod def get_manager(cls): - """ - Retrieve a handle to the global object ID manager. - """ + """Retrieve a handle to the global object ID manager.""" if cls.MANAGER_INSTANCE is None: cls.MANAGER_INSTANCE = GlobalsManager() return cls.MANAGER_INSTANCE @@ -55,19 +53,16 @@ def update_global(global_param_id: int, parameter: any): def lock_global_pool(timeout_seconds: float = -1) -> bool: - """ - Lock the global objects. This is important if the values are changed. Don't forget to unlock the pool + """Lock the global objects. This is important if the values are changed. Don't forget to unlock the pool after finishing work with the globals! - @param: timeout_seconds Attempt to lock for this many second. Default value -1 blocks permanently until lock is + :param timeout_seconds: Attempt to lock for this many second. Default value -1 blocks permanently until lock is released. - @return: Returns whether lock was locked or not. + :return: Returns whether lock was locked or not. """ return GlobalsManager.get_manager().lock_global_pool(timeout_seconds) def unlock_global_pool(): - """ - Releases the lock so other objects can use the global pool as well. - """ + """Releases the lock so other objects can use the global pool as well""" GlobalsManager.get_manager().unlock_global_pool() diff --git a/src/tmtccmd/core/object_id_manager.py b/src/tmtccmd/core/object_id_manager.py index ec140642..cee86202 100644 --- a/src/tmtccmd/core/object_id_manager.py +++ b/src/tmtccmd/core/object_id_manager.py @@ -7,17 +7,14 @@ class ObjectIdManager: - """ - Global object manager. This is a singleton class, only one global instance should be created. - The instance can be retrieved with the get_manager class method. + """Global object manager. This is a singleton class, only one global instance should be created. + The instance can be retrieved with the `get_manager` class method. """ MANAGER_INSTANCE = None @classmethod def get_manager(cls): - """ - Retrieve a handle to the global object ID manager. - """ + """Retrieve a handle to the global object ID manager""" if cls.MANAGER_INSTANCE is None: cls.MANAGER_INSTANCE = ObjectIdManager() return cls.MANAGER_INSTANCE diff --git a/src/tmtccmd/ecss/tc.py b/src/tmtccmd/ecss/tc.py index 75ea0173..52865108 100644 --- a/src/tmtccmd/ecss/tc.py +++ b/src/tmtccmd/ecss/tc.py @@ -46,8 +46,7 @@ def pack(self) -> bytearray: # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments class PusTelecommand: - """ - Class representation of a PUS telecommand. It can be used to pack a raw telecommand from + """Class representation of a PUS telecommand. It can be used to pack a raw telecommand from input parameters. The structure of a PUS telecommand is specified in ECSS-E-70-41A on p.42 and is also shown below (bottom) """ @@ -60,18 +59,17 @@ def __init__( app_data: bytearray = bytearray([]), source_id: int = 0, pus_tc_version: int = 0b1, ack_flags: int = 0b1111, apid: int = -1 ): - """ - Initiate a PUS telecommand from the given parameters. The raw byte representation + """Initiate a PUS telecommand from the given parameters. The raw byte representation can then be retrieved with the pack() function. - :param service: PUS service number - :param subservice: PUS subservice number - :param apid: Application Process ID as specified by CCSDS - :param ssc: Source Sequence Count. Application should take care of incrementing - this. Limited to 2 to the power of 14 by the number of bits in - the header - :param app_data: Application data in the Packet Data Field - :param source_id: Source ID will be supplied as well. Can be used to distinguish - different packet sources (e.g. different ground stations) + + :param service: PUS service number + :param subservice: PUS subservice number + :param apid: Application Process ID as specified by CCSDS + :param ssc: Source Sequence Count. Application should take care of incrementing this. + Limited to 2 to the power of 14 by the number of bits in the header + :param app_data: Application data in the Packet Data Field + :param source_id: Source ID will be supplied as well. Can be used to distinguish + different packet sources (e.g. different ground stations) :param pus_tc_version: PUS TC version. 1 for ECSS-E-70-41A """ @@ -102,31 +100,24 @@ def __init__( self.packed_data = bytearray() def __repr__(self): - """ - Returns the representation of a class instance. - """ + """Returns the representation of a class instance.""" return f"{self.__class__.__name__}(service={self._data_field_header.service_type!r}, " \ f"subservice={self._data_field_header.service_subtype!r}, " \ f"ssc={self._space_packet_header.ssc!r}, apid={self.apid})" def __str__(self): - """ - Returns string representation of a class instance. - """ + """Returns string representation of a class instance.""" return f"TC[{self._data_field_header.service_type}, " \ f"{self._data_field_header.service_subtype}] with SSC {self._space_packet_header.ssc}" def get_total_length(self): - """ - Length of full packet in bytes. + """Length of full packet in bytes. The header length is 6 bytes and the data length + 1 is the size of the data field. """ return self.get_data_length(len(self.app_data)) + SPACE_PACKET_HEADER_SIZE + 1 def pack(self) -> bytearray: - """ - Serializes the TC data fields into a bytearray. - """ + """Serializes the TC data fields into a bytearray.""" self.packed_data = bytearray() self.packed_data.extend(self._space_packet_header.pack()) self.packed_data.extend(self._data_field_header.pack()) @@ -140,8 +131,7 @@ def pack(self) -> bytearray: @staticmethod def get_data_length(app_data_len: int) -> int: - """ - Retrieve size of TC packet in bytes. + """Retrieve size of TC packet in bytes. Formula according to PUS Standard: C = (Number of octets in packet data field) - 1. The size of the TC packet is the size of the packet secondary header with source ID + the length of the application data + length of the CRC16 checksum - 1 @@ -154,8 +144,7 @@ def get_data_length(app_data_len: int) -> int: return 0 def pack_command_tuple(self) -> Tuple[bytearray, PusTelecommand]: - """ - Pack a tuple consisting of the raw packet as the first entry and the class representation + """Pack a tuple consisting of the raw packet as the first entry and the class representation as the second entry """ command_tuple = (self.pack(), self) @@ -180,8 +169,7 @@ def get_app_data(self): return self.app_data def print(self): - """ - Print the raw command in a clean format. + """Print the raw command in a clean format. """ packet = self.pack() print("Command in Hexadecimal: [", end="") @@ -194,8 +182,7 @@ def print(self): def generate_packet_crc(tc_packet: bytearray) -> bytearray: - """ - Removes current Packet Error Control, calculates new + """Removes current Packet Error Control, calculates new CRC16 checksum and adds it as correct Packet Error Control Code. Reference: ECSS-E70-41A p. 207-212 """ @@ -207,8 +194,7 @@ def generate_packet_crc(tc_packet: bytearray) -> bytearray: def generate_crc(data: bytearray) -> bytearray: - """ - Takes the application data, appends the CRC16 checksum and returns resulting bytearray + """Takes the application data, appends the CRC16 checksum and returns resulting bytearray """ data_with_crc = bytearray() data_with_crc += data diff --git a/src/tmtccmd/ecss/tm.py b/src/tmtccmd/ecss/tm.py index 1c642616..b8ad878a 100644 --- a/src/tmtccmd/ecss/tm.py +++ b/src/tmtccmd/ecss/tm.py @@ -9,8 +9,7 @@ class PusTelemetry: - """ - Generic PUS telemetry class representation. + """Generic PUS telemetry class representation. It is instantiated by passing the raw pus telemetry packet (bytearray) to the constructor. It automatically deserializes the packet, exposing various packet fields via getter functions. PUS Telemetry structure according to ECSS-E-70-41A p.46. Also see structure below (bottom). @@ -19,10 +18,9 @@ class PusTelemetry: PUS_TIMESTAMP_SIZE = CDS_SHORT_SIZE def __init__(self, raw_telemetry: bytearray = bytearray()): - """ - Attempts to construct a generic PusTelemetry class given a raw bytearray. + """Attempts to construct a generic PusTelemetry class given a raw bytearray. Raises a ValueError if the format of the raw bytearray is invalid. - @param raw_telemetry: + :param raw_telemetry: """ if raw_telemetry is None or raw_telemetry == bytearray(): if raw_telemetry is None: @@ -101,24 +99,21 @@ def __perform_crc_check(self, raw_telemetry: bytearray): print("PusTelemetry: Invalid CRC detected !") def specify_packet_info(self, print_info: str): - """ - Caches a print information string for later printing + """Caches a print information string for later printing :param print_info: :return: """ self.print_info = print_info def append_packet_info(self, print_info: str): - """ - Similar to the function above, but appends to the existing information string. + """Similar to the function above, but appends to the existing information string. :param print_info: :return: """ self.print_info = self.print_info + print_info def append_telemetry_content(self, content_list: list): - """ - Default implementation adds the PUS header content to the list which can then be + """Default implementation adds the PUS header content to the list which can then be printed with a simple print() command. To add additional content, override this method (don't forget to still call this function with super() if the header is required) :param content_list: Header content will be appended to this list @@ -132,8 +127,7 @@ def append_telemetry_content(self, content_list: list): content_list.append("No") def append_telemetry_column_headers(self, header_list: list): - """ - Default implementation adds the PUS header content header (confusing, I know) + """Default implementation adds the PUS header content header (confusing, I know) to the list which can then be printed with a simple print() command. To add additional headers, override this method (don't forget to still call this function with super() if the header is required) @@ -145,15 +139,13 @@ def append_telemetry_column_headers(self, header_list: list): header_list.append("Packet valid") def get_custom_printout(self) -> str: - """ - Can be used to supply any additional custom printout. + """Can be used to supply any additional custom printout. :return: String which will be printed by TmTcPrinter class as well as logged if specified """ return "" def get_raw_packet(self) -> bytearray: - """ - Get the whole TM packet as a bytearray (raw) + """Get the whole TM packet as a bytearray (raw) :return: TM wiretapping_packet """ return bytearray(self._packet_raw) @@ -166,8 +158,7 @@ def get_packet_size(self) -> int: return SPACE_PACKET_HEADER_SIZE + self._space_packet_header.data_length + 1 def get_ssc(self) -> int: - """ - Get the source sequence count + """Get the source sequence count :return: Source Sequence Count (see below, or PUS documentation) """ return self._space_packet_header.ssc @@ -176,29 +167,22 @@ def return_full_packet_string(self): return return_data_string(self._packet_raw, len(self._packet_raw)) def print_full_packet_string(self): - """ - Print the full TM packet in a clean format. - """ + """Print the full TM packet in a clean format.""" print(return_data_string(self._packet_raw, len(self._packet_raw))) def print_source_data(self): - """ - Prints the TM source data in a clean format + """Prints the TM source data in a clean format :return: """ print(return_data_string(self._tm_data, len(self._tm_data))) def return_source_data_string(self): - """ - Returns the source data string - """ + """Returns the source data string""" return return_data_string(self._tm_data, len(self._tm_data)) class PusPacketDataFieldHeader: - """ - Unpacks the PUS packet data field header. Currently only supports CDS short timestamps - """ + """Unpacks the PUS packet data field header. Currently only supports CDS short timestamps""" def __init__(self, bytes_array: bytearray, pus_version: PusVersion): self.pus_version = pus_version @@ -224,8 +208,7 @@ def __init__(self, bytes_array: bytearray, pus_version: PusVersion): self.time = PusCdsShortTimestamp(bytes_array[7: 7 + PusTelemetry.PUS_TIMESTAMP_SIZE]) def append_data_field_header(self, content_list: list): - """ - Append important data field header parameters to the passed content list. + """Append important data field header parameters to the passed content list. :param content_list: :return: """ @@ -235,8 +218,7 @@ def append_data_field_header(self, content_list: list): self.time.print_time(content_list) def append_data_field_header_column_header(self, header_list: list): - """ - Append important data field header column headers to the passed list. + """Append important data field header column headers to the passed list. :param header_list: :return: """ @@ -253,11 +235,10 @@ def get_header_size(self): class PusCdsShortTimestamp: - """ - Unpacks the time datafield of the TM packet. Right now, CDS Short timeformat is used, + """Unpacks the time datafield of the TM packet. Right now, CDS Short timeformat is used, and the size of the time stamp is expected to be seven bytes. - TODO: Implement more time formats """ + # TODO: Implement more time formats CDS_ID = 4 SECONDS_PER_DAY = 86400 EPOCH = datetime.datetime.utcfromtimestamp(0) @@ -279,9 +260,7 @@ def __init__(self, byte_array: bytearray = bytearray([])): @staticmethod def pack_current_time() -> bytearray: - """ - Returns a seven byte CDS short timestamp - """ + """Returns a seven byte CDS short timestamp""" timestamp = bytearray() p_field = (PusCdsShortTimestamp.CDS_ID << 4) + 0 days = \ @@ -316,8 +295,7 @@ def print_time_headers(header_list): def return_data_string(byte_array: bytearray, length: int) -> str: - """ - Returns the TM data in a clean printable string format + """Returns the TM data in a clean printable string format Prints payload data in default mode and prints the whole packet if full_packet = True is passed. :return: diff --git a/src/tmtccmd/pus_tc/service_17_test.py b/src/tmtccmd/pus_tc/service_17_test.py index 06ccd9c6..99d9f18f 100644 --- a/src/tmtccmd/pus_tc/service_17_test.py +++ b/src/tmtccmd/pus_tc/service_17_test.py @@ -5,6 +5,7 @@ def pack_service17_ping_command(ssc: int, apid: int = -1) -> PusTelecommand: + """Generate a simple ping PUS telecommand packet""" if apid == -1: apid = get_global_apid() return PusTelecommand(service=17, subservice=Srv17Subservices.PING_CMD, ssc=ssc, apid=apid) diff --git a/src/tmtccmd/pus_tm/factory.py b/src/tmtccmd/pus_tm/factory.py index e27684da..9b49a754 100644 --- a/src/tmtccmd/pus_tm/factory.py +++ b/src/tmtccmd/pus_tm/factory.py @@ -19,9 +19,7 @@ class PusTelemetryFactory(object): - """ - Deserialize TM bytearrays into PUS TM Classes - """ + """Deserialize TM bytearrays into PUS TM Classes""" @staticmethod def create(raw_tm_packet: bytearray) -> Union[PusTelemetry, None]: try: diff --git a/src/tmtccmd/pus_tm/service_3_housekeeping.py b/src/tmtccmd/pus_tm/service_3_housekeeping.py index 909bc485..28632804 100644 --- a/src/tmtccmd/pus_tm/service_3_housekeeping.py +++ b/src/tmtccmd/pus_tm/service_3_housekeeping.py @@ -40,7 +40,6 @@ def __init__(self, byte_array: bytearray, custom_hk_handling: bool = False, :param minimum_reply_size: :param minimum_structure_report_header_size: """ - from tmtccmd.core.object_id_manager import get_object_id_info super().__init__(byte_array) if len(self._tm_data) < 8: warning = "Service3TM: handle_filling_definition_arrays: Invalid Service 3 packet," \ diff --git a/src/tmtccmd/runner.py b/src/tmtccmd/runner.py index 7aa30a45..86776e6a 100644 --- a/src/tmtccmd/runner.py +++ b/src/tmtccmd/runner.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ :brief: Core method called by entry point files to initiate the TMTC commander. - The commander is started by first running initialize_tmtc_commander and then - running run_tmtc_commander + The commander is started by first running `initialize_tmtc_commander` and then + running `run_tmtc_commander` :details: :manual: :author: R. Mueller @@ -27,8 +27,7 @@ def initialize_tmtc_commander(hook_object: TmTcHookBase): - """ - This function needs to be called first before running the TMTC commander core. A hook + """This function needs to be called first before running the TMTC commander core. A hook object handle needs to be passed to this function. The user should implement an own hook class instance which in turn implemented TmTcHookBase. An instantiation of the hook object is then passed to the core. The hook object ecncapsulates the control of the user over the TMTC @@ -57,10 +56,9 @@ def run_tmtc_commander( tmtc_frontend: Union[FrontendBase, None] = None, app_name: str = "TMTC Commander" ): - """ - This is the primary function to run the TMTC commander. Users should call this function to + """This is the primary function to run the TMTC commander. Users should call this function to start the TMTC commander. Please note that assign_tmtc_commander_hooks needs to be called - before this function. Raises RuntimeError if initialize_tmtc_commander + before this function. Raises RuntimeError if `initialize_tmtc_commander` has not been called before calling this function. Example for a simple main function content to use the command line mode: @@ -74,7 +72,8 @@ def run_tmtc_commander( :param ansi_colors: Enable ANSI color output for terminal :param tmtc_backend: :param tmtc_frontend: - :raises: ValueError if initialize_tmtc_commander was not called before + :param app_name: Name of application. Will be displayed in GUI + :raises: ValueError if `initialize_tmtc_commander` was not called before :return: """ try: diff --git a/src/tmtccmd/utility/dle_encoder.py b/src/tmtccmd/utility/dle_encoder.py index 74ded5ce..62c4ea05 100644 --- a/src/tmtccmd/utility/dle_encoder.py +++ b/src/tmtccmd/utility/dle_encoder.py @@ -66,12 +66,12 @@ def decode_dle(source_packet: bytearray) -> Tuple[DleErrorCodes, bytearray, int] Decodes a given DLE encoded data stream. This call only returns the first packet found. It might be necessary to call this function multiple times, depending on the third returnvalue - - :return: Returns a tuple of three values. - 1. DleErrorCode: If decoding has failed, this will not be DleErrorCodes.OK - 2. Decoded bytearray: Decoded packet - 3. Read length: Read length in the encoded stream. If this is smaller than the length of - the passed bytearray, the decoding function should be called again. + :return: + Returns a tuple of three values. + 1. DleErrorCode: If decoding has failed, this will not be DleErrorCodes.OK + 2. Decoded bytearray: Decoded packet + 3. Read length: Read length in the encoded stream. If this is smaller than the length of + the passed bytearray, the decoding function should be called again. """ encoded_index = 0 source_len = len(source_packet) diff --git a/src/tmtccmd/utility/hammingcode.py b/src/tmtccmd/utility/hammingcode.py index 7596d460..aa530695 100644 --- a/src/tmtccmd/utility/hammingcode.py +++ b/src/tmtccmd/utility/hammingcode.py @@ -1,6 +1,6 @@ """ -@brief Hamming Code Implementation -@details +:brief: Hamming Code Implementation +:details: Hamming codes belong to the family of linear error correcting codes. Documentation: https://en.wikipedia.org/wiki/Hamming_code They can be used to identify up to two bit errors and correct one bit error per 256 byte block. @@ -59,9 +59,9 @@ def hamming_compute_256x(data: bytearray) -> bytearray: """ Computes 3-bytes hamming codes for a data block whose size is multiple of 256 bytes. Each 256 bytes block gets its own code. - @param data: Data to compute code for. Should be a multiple of 256 bytes, pad data with 0 + :param data: Data to compute code for. Should be a multiple of 256 bytes, pad data with 0 if necessary! - @return: bytearray of hamming codes with the size (3 / 256 * size). Empty bytearray if input + :return: bytearray of hamming codes with the size (3 / 256 * size). Empty bytearray if input is invalid. """ if len(data) % 256 != 0: @@ -119,8 +119,8 @@ def hamming_compute_256(data: bytearray) -> bytearray: """ Takes a bytearray with the size of 256 bytes and calculates the 22 parity bits for the hamming code which will be returned as a three byte bytearray. - @param data: - @return: + :param data: + :return: """ hamming_code = bytearray(3) if len(data) != 256: @@ -228,14 +228,14 @@ def hamming_verify_256(data: bytearray, original_hamming_code: bytearray) -> Ham """ Verifies and corrects a 256-bytes block of data using the given 22-bits hamming code. Returns 0 if there is no error, otherwise returns a HAMMING_ERROR code. - @param data: 256 code block to verify - @param original_hamming_code: Original 3 byte hamming code with 22 parity bits - @return: See HammingReturnCodes enums. - - -1 for invalid input - - 0 if there are no errors. - - 1 if there is a single bit error which has been corrected - - 2 if the hamming code has been corrupted - - 3 if there was a multi bit error which can not be corrected + :param data: 256 code block to verify + :param original_hamming_code: Original 3 byte hamming code with 22 parity bits + :return: See HammingReturnCodes enums. + - -1 for invalid input + - 0 if there are no errors. + - 1 if there is a single bit error which has been corrected + - 2 if the hamming code has been corrupted + - 3 if there was a multi bit error which can not be corrected """ if len(data) != 256: LOGGER.error("hamming_compute_256: Invalid input, data does not have " diff --git a/src/tmtccmd/utility/json_handler.py b/src/tmtccmd/utility/json_handler.py index 7e1a15ff..bb003796 100644 --- a/src/tmtccmd/utility/json_handler.py +++ b/src/tmtccmd/utility/json_handler.py @@ -14,6 +14,10 @@ class JsonKeyNames(enum.Enum): TCPIP_UDP_RECV_PORT = "TCPIP_UDP_RECV_PORT" TCPIP_UDP_RECV_MAX_SIZE = "TCPIP_UDP_RECV_MAX_SIZE" + TCPIP_TCP_DEST_IP_ADDRESS = "TCPIP_TCP_DEST_IP_ADDRESS" + TCPIP_TCP_DEST_PORT = "TCPIP_TCP_DEST_PORT" + TCPIP_TCP_RECV_MAX_SIZE = "TCPIP_UDP_RECV_MAX_SIZE" + SERIAL_BAUDRATE = "SERIAL_BAUDRATE" SERIAL_PORT = "SERIAL_PORT" diff --git a/src/tmtccmd/utility/logger.py b/src/tmtccmd/utility/logger.py index 2ba55ecd..6ac78995 100644 --- a/src/tmtccmd/utility/logger.py +++ b/src/tmtccmd/utility/logger.py @@ -73,12 +73,15 @@ def set_tmtc_logger() -> logging.Logger: def set_up_coloredlogs_logger(logger: logging.Logger): - import coloredlogs - coloredlogs.install( - level='INFO', logger=logger, milliseconds=True, - fmt='%(asctime)s.%(msecs)03d %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) + try: + import coloredlogs + coloredlogs.install( + level='INFO', logger=logger, milliseconds=True, + fmt='%(asctime)s.%(msecs)03d %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + except ImportError: + print("Please install coloredlogs package first") def set_up_colorlog_logger(logger: logging.Logger):