diff --git a/obsw_tmtc_client.py b/obsw_tmtc_client.py index b2b06c2ac01addd2015ae52f2148e27825392f01..167165ad7178b8dff5ec5e29471eec50f32771db 100644 --- a/obsw_tmtc_client.py +++ b/obsw_tmtc_client.py @@ -1,7 +1,7 @@ #!/usr/bin/python3.8 # -*- coding: utf-8 -*- - import atexit +import time import unittest import logging import sys @@ -19,14 +19,15 @@ from sendreceive.obsw_tm_listener import TmListener from utility.obsw_args_parser import parse_input_arguments from utility.obsw_tmtc_printer import TmTcPrinter from utility.obsw_exit_handler import keyboard_interrupt_handler +from utility.obsw_logger import set_tmtc_logger, get_logger from comIF.obsw_com_config import set_communication_interface from gui.obsw_tmtc_gui import TmTcGUI from gui.obsw_backend_test import TmTcBackend -from utility.obsw_logger import set_tmtc_logger -from utility.obsw_logger import get_logger + +logger = get_logger() def main(): @@ -35,7 +36,7 @@ def main(): It can be used to to send and receive TMTC packets and TMTC sequences. @manual - Manual installation of crcmod and pyserial might be needed + Manual installation of crcmod and pyserial for serial communication might be needed 1. Install pip if it is not installed yet 2. Install crcmod and all other required packages: Command: python3.8 -m pip install crcmod @@ -76,77 +77,20 @@ def main(): If the packets appear, there might be a problematic firewall setting. Please ensure that python.exe UDP packets are not blocked in advanced firewall settings and create a rule to allow packets from port 2008. - TODO: The whole code below could ba packed into a class too. - If a backend/frontend architecture is to be implemented, this will make things easier - We propably instantiate all sender/receiver objects (or maybe instantiate them once - we need them) on object construction. The main could spawn a process for the frontend - (e.g. GUI or web interface) and a process for the backend (the sender/receiver classes, - passing values to a SQL database, loading the MIB database...) """ set_tmtc_logger() - logger = get_logger() logger.info("Starting TMTC Client") + logger.info("Parsing input arguments") args = parse_input_arguments() + logger.info("Setting global variables") set_globals(args) - tmtc_printer = TmTcPrinter(g.G_DISPLAY_MODE, g.G_PRINT_TO_FILE, True) - if g.G_MODE_ID == g.ModeList.GUIMode: - backend = TmTcBackend() - backend.start() - gui = TmTcGUI() - gui.start() - backend.join() - gui.join() - logger.info("Both processes have closed") - sys.exit() - else: - communication_interface = set_communication_interface(tmtc_printer) - atexit.register(keyboard_interrupt_handler, comInterface=communication_interface) - tm_listener = TmListener( - com_interface=communication_interface, tm_timeout=g.G_TM_TIMEOUT, - tc_timeout_factor=g.G_TC_SEND_TIMEOUT_FACTOR) - tm_listener.start() - if g.G_MODE_ID == g.ModeList.ListenerMode: - logger.info("Listening for packages...") - elif g.G_MODE_ID == g.ModeList.SingleCommandMode: - pus_packet_tuple = command_preparation() - sender_and_receiver = SingleCommandSenderReceiver( - com_interface=communication_interface, tmtc_printer=tmtc_printer, tm_listener=tm_listener, - pus_packet_tuple=pus_packet_tuple) - logger.info("Performing single command operation") - sender_and_receiver.send_single_tc_and_receive_tm() - - elif g.G_MODE_ID == g.ModeList.ServiceTestMode: - service_queue = deque() - logger.info("Performing service command operation") - sender_and_receiver = SequentialCommandSenderReceiver( - com_interface=communication_interface, tmtc_printer=tmtc_printer, tm_listener=tm_listener, - tc_queue=service_test_select(g.G_SERVICE, service_queue)) - sender_and_receiver.send_queue_tc_and_receive_tm_sequentially() - - elif g.G_MODE_ID == g.ModeList.SoftwareTestMode: - all_tc_queue = create_total_tc_queue() - logger.info("Performing multjple service commands operation") - sender_and_receiver = SequentialCommandSenderReceiver( - com_interface=communication_interface, tmtc_printer=tmtc_printer, - tc_queue=all_tc_queue, tm_listener=tm_listener) - sender_and_receiver.send_queue_tc_and_receive_tm_sequentially() - - elif g.G_MODE_ID == g.ModeList.UnitTest: - # Set up test suite and run it with runner. Verbosity specifies detail level - g.G_TM_LISTENER = tm_listener - g.G_COM_INTERFACE = communication_interface - g.G_TMTC_PRINTER = tmtc_printer - logger.info("Performing module tests") - # noinspection PyTypeChecker - suite = unittest.TestLoader().loadTestsFromModule(obsw_pus_service_test) - unittest.TextTestRunner(verbosity=2).run(suite) - - else: - logging.error("Unknown Mode, Configuration error !") - sys.exit() + logger.info("Starting TMTC Handler") + tmtc_handler = TmTcHandler() + tmtc_handler.perform_operation() + # At some later point, the program will run permanently and be able to take commands. # For now we put a permanent loop here so the program # doesn't exit automatically (TM Listener is daemonic) @@ -165,5 +109,80 @@ def command_preparation(): return command.pack_command_tuple() +class TmTcHandler: + def __init__(self): + self.mode = g.G_MODE_ID + self.comIF = g.G_COM_IF + # This flag could be used later to command the TMTC Client with a front-end + self.command_received = True + + def perform_operation(self): + while True: + if self.command_received: + self.handle_action() + self.command_received = False + logger.info("TMTC Client in idle mode") + time.sleep(5) + + + def handle_action(self): + tmtc_printer = TmTcPrinter(g.G_DISPLAY_MODE, g.G_PRINT_TO_FILE, True) + if self.mode == g.ModeList.GUIMode: + backend = TmTcBackend() + backend.start() + gui = TmTcGUI() + gui.start() + backend.join() + gui.join() + logger.info("Both processes have closed") + sys.exit() + else: + communication_interface = set_communication_interface(tmtc_printer) + atexit.register(keyboard_interrupt_handler, comInterface=communication_interface) + tm_listener = TmListener( + com_interface=communication_interface, tm_timeout=g.G_TM_TIMEOUT, + tc_timeout_factor=g.G_TC_SEND_TIMEOUT_FACTOR) + tm_listener.start() + if self.mode == g.ModeList.ListenerMode: + logger.info("Listening for packages...") + elif self.mode == g.ModeList.SingleCommandMode: + pus_packet_tuple = command_preparation() + sender_and_receiver = SingleCommandSenderReceiver( + com_interface=communication_interface, tmtc_printer=tmtc_printer, + tm_listener=tm_listener) + logger.info("Performing single command operation") + sender_and_receiver.send_single_tc_and_receive_tm(pus_packet_tuple=pus_packet_tuple) + + elif self.mode== g.ModeList.ServiceTestMode: + service_queue = deque() + logger.info("Performing service command operation") + sender_and_receiver = SequentialCommandSenderReceiver( + com_interface=communication_interface, tmtc_printer=tmtc_printer, + tm_listener=tm_listener, + tc_queue=service_test_select(g.G_SERVICE, service_queue)) + sender_and_receiver.send_queue_tc_and_receive_tm_sequentially() + + elif self.mode == g.ModeList.SoftwareTestMode: + all_tc_queue = create_total_tc_queue() + logger.info("Performing multjple service commands operation") + sender_and_receiver = SequentialCommandSenderReceiver( + com_interface=communication_interface, tmtc_printer=tmtc_printer, + tc_queue=all_tc_queue, tm_listener=tm_listener) + sender_and_receiver.send_queue_tc_and_receive_tm_sequentially() + + elif self.mode == g.ModeList.UnitTest: + # Set up test suite and run it with runner. Verbosity specifies detail level + g.G_TM_LISTENER = tm_listener + g.G_COM_INTERFACE = communication_interface + g.G_TMTC_PRINTER = tmtc_printer + logger.info("Performing module tests") + # noinspection PyTypeChecker + suite = unittest.TestLoader().loadTestsFromModule(obsw_pus_service_test) + unittest.TextTestRunner(verbosity=2).run(suite) + + else: + logging.error("Unknown Mode, Configuration error !") + sys.exit() + if __name__ == "__main__": main() diff --git a/sendreceive/obsw_command_sender_receiver.py b/sendreceive/obsw_command_sender_receiver.py index c09a01fe96bfffe752396425ecf595661f5ba3f2..07cdb1ed2738e40266099dbeaae19737377cfa35 100644 --- a/sendreceive/obsw_command_sender_receiver.py +++ b/sendreceive/obsw_command_sender_receiver.py @@ -43,27 +43,34 @@ class CommandSenderReceiver: if isinstance(com_interface, CommunicationInterface): self._com_interface = com_interface else: - raise TypeError("Invalid communication interface service_type!") + logger.error("CommandSenderReceiver: Invalid communication interface!") + raise TypeError("CommandSenderReceiver: Invalid communication interface!") if isinstance(tmtc_printer, TmTcPrinter): self._tmtc_printer = tmtc_printer else: - raise TypeError("Invalid TMTC Printer service_type!") + logger.error("CommandSenderReceiver: Invalid TMTC printer!") + raise TypeError("CommandSenderReceiver: Invalid TMTC printer!") if isinstance(tm_listener, TmListener): self._tm_listener = tm_listener else: - raise TypeError("Invalid TM Listener service_type!") - self._reply_received = False + logger.error("CommandSenderReceiver: Invalid TM listener!") + raise TypeError("Invalid TM Listener!") self._start_time = 0 self._elapsed_time = 0 self._timeout_counter = 0 - # needed to store last actual tc wiretapping_packet from queue + # needed to store last actual TC packet from queue self._last_tc = bytearray() self._last_tc_info = dict() + # this flag can be used to notify when the operation is finished + self._operation_pending = False + # This flag can be used to notify when a reply was received. + self._reply_received = False + def set_tm_timeout(self, tm_timeout: float = g.G_TM_TIMEOUT): """ Set the TM timeout. Usually, the global value set by the args parser is set, @@ -89,6 +96,7 @@ class CommandSenderReceiver: """ if self._tm_listener.replyEvent.is_set(): self._reply_received = True + self._operation_pending = False self._tm_listener.replyEvent.clear() else: self._check_for_timeout() @@ -129,8 +137,8 @@ class CommandSenderReceiver: if self._start_time == 0: self._start_time = time.time() if self._timeout_counter == 5: - logger.info("Command Sender Receiver: No response from command !") - sys.exit() + logger.info("CommandSenderReceiver: No response from command !") + self._operation_pending = False if self._start_time != 0: self._elapsed_time = time.time() - self._start_time if self._elapsed_time >= self._tm_timeout * self._tc_send_timeout_factor: diff --git a/sendreceive/obsw_sequential_sender_receiver.py b/sendreceive/obsw_sequential_sender_receiver.py index bf8cf18c58fe03faab95bdb3ad2acfa2eb5ba32a..952b54aab6a4e00ac67c38bd21b292e5e66f9592 100644 --- a/sendreceive/obsw_sequential_sender_receiver.py +++ b/sendreceive/obsw_sequential_sender_receiver.py @@ -9,7 +9,7 @@ import time import config.obsw_config as g from sendreceive.obsw_command_sender_receiver import CommandSenderReceiver -from sendreceive.obsw_tm_listener import TmListenerT +from sendreceive.obsw_tm_listener import TmListener from comIF.obsw_com_interface import CommunicationInterface from utility.obsw_tmtc_printer import TmTcPrinter from tc.obsw_pus_tc_base import TcQueueT @@ -20,7 +20,7 @@ class SequentialCommandSenderReceiver(CommandSenderReceiver): Specific implementation of CommandSenderReceiver to send multiple telecommands in sequence """ def __init__(self, com_interface: CommunicationInterface, tmtc_printer: TmTcPrinter, - tm_listener: TmListenerT, tc_queue: TcQueueT): + tm_listener: TmListener, tc_queue: TcQueueT): """ :param com_interface: CommunicationInterface object, passed on to CommandSenderReceiver :param tm_listener: TmListener object which runs in the background and receives diff --git a/sendreceive/obsw_single_command_sender_receiver.py b/sendreceive/obsw_single_command_sender_receiver.py index 677e3893b17323d3420b11eadf12ee947b79ee0b..1cdb00449d36b6f7b73b3535db5a9f9c2ad2e2cd 100644 --- a/sendreceive/obsw_single_command_sender_receiver.py +++ b/sendreceive/obsw_single_command_sender_receiver.py @@ -7,22 +7,29 @@ @brief Used to send single tcs and listen for replies after that """ -import logging from sendreceive.obsw_command_sender_receiver import CommandSenderReceiver -from sendreceive.obsw_tm_listener import TmListenerT +from sendreceive.obsw_tm_listener import TmListener + from comIF.obsw_com_interface import CommunicationInterface + from utility.obsw_tmtc_printer import TmTcPrinter +from utility.obsw_logger import get_logger + +from tc.obsw_pus_tc_base import PusTcTupleT import config.obsw_config as g + +logger = get_logger() + class SingleCommandSenderReceiver(CommandSenderReceiver): """ Specific implementation of CommandSenderReceiver to send a single telecommand This object can be used by instantiating it and calling sendSingleTcAndReceiveTm() """ def __init__(self, com_interface: CommunicationInterface, tmtc_printer: TmTcPrinter, - tm_listener: TmListenerT, pus_packet_tuple: tuple): + tm_listener: TmListener): """ :param com_interface: CommunicationInterface object, passed on to CommandSenderReceiver :param tm_listener: TmListener object which runs in the background and receives all TM @@ -30,31 +37,30 @@ class SingleCommandSenderReceiver(CommandSenderReceiver): """ super().__init__(com_interface=com_interface, tm_listener=tm_listener, tmtc_printer=tmtc_printer) - self.faulty_input = False - try: - self.pus_packet, self.pus_packet_info = pus_packet_tuple - except TypeError: - print("Invalid queue entry detected") - logging.exception("Error") - self.faulty_input = True - def send_single_tc_and_receive_tm(self): + + def send_single_tc_and_receive_tm(self, pus_packet_tuple: PusTcTupleT): """ Send a single telecommand passed to the class and wait for replies :return: """ - if not self.faulty_input: - self._tm_listener.mode_id = g.ModeList.SingleCommandMode - self._tm_listener.mode_change_event.set() - self._tmtc_printer.print_telecommand(self.pus_packet, self.pus_packet_info) - self._com_interface.send_telecommand(self.pus_packet, self.pus_packet_info) - self._last_tc = self.pus_packet - self._last_tc_info = self.pus_packet_info - while not self._reply_received: - # wait until reply is received - super()._check_for_first_reply() - if self._reply_received: - self._tm_listener.mode_op_finished.set() - self.print_tm_queue(self._tm_listener.retrieve_tm_packet_queue()) - print("Single Command SenderReceiver: Reply received") - print("Listening for packages ...") + try: + pus_packet, pus_packet_info = pus_packet_tuple + except TypeError: + logger.error("SingleCommandSenderReceiver: Invalid command input") + return + self._operation_pending = True + self._tm_listener.mode_id = g.ModeList.SingleCommandMode + self._tm_listener.mode_change_event.set() + self._tmtc_printer.print_telecommand(pus_packet, pus_packet_info) + self._com_interface.send_telecommand(pus_packet, pus_packet_info) + self._last_tc = pus_packet + self._last_tc_info = pus_packet_info + while self._operation_pending: + # wait until reply is received + super()._check_for_first_reply() + if self._reply_received: + self._tm_listener.mode_op_finished.set() + self.print_tm_queue(self._tm_listener.retrieve_tm_packet_queue()) + logger.info("SingleCommandSenderReceiver: Reply received") + logger.info("Listening for packages ...") diff --git a/tm/obsw_pus_tm_base.py b/tm/obsw_pus_tm_base.py index b18784c7e8bc85ee062d79e2cf6e5b0b35526c83..a35eb6877082f73968ba0aa491f0e8379fb0c6df 100644 --- a/tm/obsw_pus_tm_base.py +++ b/tm/obsw_pus_tm_base.py @@ -186,6 +186,9 @@ class PusTelemetryPacked(PusTelemetry): self.pack_subcounter = 0 self.time = 0 + def pack(self) -> bytearray: + + # pylint: disable=too-many-instance-attributes diff --git a/tm/obsw_pus_tm_factory.py b/tm/obsw_pus_tm_factory.py index b77ff9682bb65e64dea062fd0019de085de245f5..547b2a42aef206d5df522f6567a9e576c7408013 100644 --- a/tm/obsw_pus_tm_factory.py +++ b/tm/obsw_pus_tm_factory.py @@ -1,10 +1,4 @@ # -*- coding: utf-8 -*- -""" -Program: obsw_pus_tm_factory.py -Date: 01.11.2019 -Description: Deserialize TM byte header_list into PUS TM Class -Author: R.Mueller, S. Gaisser -""" from typing import Deque, List, Dict, Tuple from tm.obsw_pus_tm_base import PusTelemetry, PusTmInfoT from tm.obsw_tm_service_1 import Service1TM @@ -21,8 +15,11 @@ PusTmTupleQueueT = Deque[PusTmTupleT] class PusTelemetryFactory(object): + """ + Deserialize TM bytearrays into PUS TM Classes + """ @staticmethod - def create(raw_tm_packet: bytes) -> PusTelemetry: + def create(raw_tm_packet: bytearray) -> PusTelemetry: service_type = raw_tm_packet[7] if service_type == 1: return Service1TM(raw_tm_packet) @@ -43,7 +40,7 @@ class PusTelemetryFactory(object): class Service2TM(PusTelemetry): - def __init__(self, byte_array: bytes): + def __init__(self, byte_array: bytearray): super().__init__(byte_array) self.specify_packet_info("Raw Commanding Reply") diff --git a/utility/obsw_args_parser.py b/utility/obsw_args_parser.py index 33803a47ea1201805c3f1d4d7d5b160ecd5785f7..f0d3161761a85fe2686d3de20d532cfca52bb987 100644 --- a/utility/obsw_args_parser.py +++ b/utility/obsw_args_parser.py @@ -1,16 +1,6 @@ -#!/usr/bin/python3.7 -""" -@file - obsw_args_parser.py -@date - 11.01.2020 -@brief - Reads all input arguments or requests them from user. -""" - +#!/usr/bin/python3.8 import argparse import sys -import logging from utility.obsw_logger import get_logger diff --git a/utility/obsw_logger.py b/utility/obsw_logger.py index 96e9511452e4fd44dd7201c60d34ae0e529ce229..ab343805763b9d830eabdf316c915b12cef242b4 100644 --- a/utility/obsw_logger.py +++ b/utility/obsw_logger.py @@ -5,12 +5,18 @@ import config.obsw_config as g class InfoFilter(logging.Filter): + """ + Filter object, which is used so that only INFO and DEBUG messages are printed to stdout. + """ def filter(self, rec): if rec.levelno == logging.INFO or rec.levelno == logging.DEBUG: return rec.levelno def set_tmtc_logger() -> logging.Logger: + """ + Sets the logger object which will be used globally. + """ logger = logging.getLogger(g.G_TMTC_LOGGER_NAME) logger.setLevel(level=logging.DEBUG) generic_format = logging.Formatter(