#!/usr/bin/env python3 import logging import sys import time import traceback from pathlib import Path from eive_tmtc.cfdp.fault_handler import EiveCfdpFaultHandler from eive_tmtc.cfdp.tm import CfdpInCcsdsWrapper from eive_tmtc.cfdp.user import EiveCfdpUser from spacepackets.version import get_version as get_spacepackets_version from spacepackets.cfdp import ( ChecksumType, TransmissionMode, ) from eive_tmtc.pus_tc.tc_handler import TcHandler from tmtccmd.logging import add_colorlog_console_logger from tmtccmd.cfdp.handler import CfdpInCcsdsHandler from tmtccmd.cfdp.mib import ( LocalEntityCfg, IndicationCfg, RemoteEntityCfg, ) try: import spacepackets except ImportError as error: print(error) print("Python spacepackets module could not be imported") print( 'Install with "cd spacepackets && python3 -m pip intall -e ." for interative installation' ) sys.exit(1) try: import tmtccmd except ImportError: run_tmtc_commander = None initialize_tmtc_commander = None tb = traceback.format_exc() print(tb) print("Python tmtccmd submodule could not be imported") sys.exit(1) from spacepackets.ecss import PusVerificator from tmtccmd import BackendBase from tmtccmd.util import FileSeqCountProvider, PusFileSeqCountProvider from tmtccmd.fsfw.tmtc_printer import FsfwTmTcPrinter from tmtccmd.logging.pus import ( RawTmtcTimedLogWrapper, RegularTmtcLogWrapper, TimedLogWhen, ) from tmtccmd.pus import VerificationWrapper from tmtccmd.tm import SpecificApidHandlerBase, GenericApidHandlerBase, CcsdsTmHandler from tmtccmd.core import BackendRequest from tmtccmd.config import ( default_json_path, SetupWrapper, params_to_procedure_conversion, ) from tmtccmd.config.args import ( SetupParams, PreArgsParsingWrapper, ProcedureParamsWrapper, ) from eive_tmtc import APP_LOGGER from eive_tmtc import __version__ from eive_tmtc.config.definitions import ( PUS_APID, CFDP_APID, CFDP_LOCAL_ENTITY_ID, CFDP_REMOTE_ENTITY_ID, ) from eive_tmtc.config.hook import EiveHookObject from eive_tmtc.pus_tm.pus_demux import pus_factory_hook _LOGGER = APP_LOGGER _LOG_LEVEL = logging.INFO # Put rotating file logger parameters here for quick changes ROTATING_TIMED_LOGGER_INTERVAL_WHEN = TimedLogWhen.PER_MINUTE ROTATING_TIMED_LOGGER_INTERVAL = 30 class PusHandler(SpecificApidHandlerBase): def __init__( self, wrapper: VerificationWrapper, printer: FsfwTmTcPrinter, raw_logger: RawTmtcTimedLogWrapper, hk_level: int, ): super().__init__(PUS_APID, None) self.printer = printer self.verif_wrapper = wrapper self.raw_logger = raw_logger self.hk_level = hk_level def handle_tm(self, packet: bytes, _user_args: any): # with open("tc.bin", "wb") as of: # of.write(packet) pus_factory_hook( packet, self.verif_wrapper, self.printer, self.raw_logger, self.hk_level ) class UnknownApidHandler(GenericApidHandlerBase): def handle_tm(self, apid: int, packet: bytes, _user_args: any): _LOGGER.warning( f"Packet with unknown APID {apid} detected: {packet.hex(sep=',')}" ) class CustomCcsdsTmHandler(CcsdsTmHandler): def user_hook(self, apid: int, packet: bytes): _LOGGER.debug(f"Received packet {packet.hex(sep=',')} with APID {apid}") def setup_params() -> (SetupWrapper, int): hook_obj = EiveHookObject(default_json_path()) params = SetupParams() parser_wrapper = PreArgsParsingWrapper() parser_wrapper.create_default_parent_parser() parser_wrapper.create_default_parser() tmtc_parser, cfdp_parser = parser_wrapper.add_def_proc_and_cfdp_as_subparsers() tmtc_parser.add_argument( "--hk", help="HK output level", default=2, ) post_arg_parsing_wrapper = parser_wrapper.parse( setup_params=params, hook_obj=hook_obj ) tmtccmd.init_printout(post_arg_parsing_wrapper.use_gui) use_prompts = not post_arg_parsing_wrapper.use_gui proc_param_wrapper = ProcedureParamsWrapper() if use_prompts: post_arg_parsing_wrapper.set_params_with_prompts(proc_param_wrapper) else: post_arg_parsing_wrapper.set_params_without_prompts(proc_param_wrapper) if hasattr(post_arg_parsing_wrapper.args_raw, "hk"): hk_level = int(post_arg_parsing_wrapper.args_raw.hk) else: hk_level = 0 params.apid = PUS_APID if params.com_if is None: raise ValueError("could not determine a COM interface.") setup_wrapper = SetupWrapper( hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_param_wrapper ) return setup_wrapper, hk_level def setup_cfdp_handler() -> CfdpInCcsdsWrapper: fh_base = EiveCfdpFaultHandler() cfdp_cfg = LocalEntityCfg( local_entity_id=CFDP_LOCAL_ENTITY_ID, indication_cfg=IndicationCfg(), default_fault_handlers=fh_base, ) self_as_remote = RemoteEntityCfg( closure_requested=False, entity_id=CFDP_LOCAL_ENTITY_ID, max_file_segment_len=990, check_limit=None, crc_on_transmission=False, crc_type=ChecksumType.CRC_32, default_transmission_mode=TransmissionMode.UNACKNOWLEDGED ) remote_cfg = RemoteEntityCfg( closure_requested=False, entity_id=CFDP_REMOTE_ENTITY_ID, max_file_segment_len=990, check_limit=None, crc_on_transmission=False, crc_type=ChecksumType.CRC_32, default_transmission_mode=TransmissionMode.UNACKNOWLEDGED, ) cfdp_seq_count_provider = FileSeqCountProvider( max_bit_width=16, file_name=Path("seqcnt_cfdp_transaction.txt") ) cfdp_ccsds_seq_count_provider = PusFileSeqCountProvider( file_name=Path("seqcnt_cfdp_in_ccsds.txt") ) cfdp_user = EiveCfdpUser() cfdp_in_ccsds_handler = CfdpInCcsdsHandler( cfg=cfdp_cfg, remote_cfgs=[remote_cfg, self_as_remote], ccsds_apid=CFDP_APID, ccsds_seq_cnt_provider=cfdp_ccsds_seq_count_provider, cfdp_seq_cnt_provider=cfdp_seq_count_provider, user=cfdp_user, ) return CfdpInCcsdsWrapper(cfdp_in_ccsds_handler) def setup_tmtc_handlers( verificator: PusVerificator, printer: FsfwTmTcPrinter, raw_logger: RawTmtcTimedLogWrapper, gui: bool, hk_level: int, ) -> (CcsdsTmHandler, TcHandler): cfdp_in_ccsds_wrapper = setup_cfdp_handler() verification_wrapper = VerificationWrapper( verificator, _LOGGER, printer.file_logger ) pus_handler = PusHandler(verification_wrapper, printer, raw_logger, hk_level) ccsds_handler = CustomCcsdsTmHandler(generic_handler=UnknownApidHandler(None)) ccsds_handler.add_apid_handler(pus_handler) ccsds_handler.add_apid_handler(cfdp_in_ccsds_wrapper) seq_count_provider = PusFileSeqCountProvider() tc_handler = TcHandler( seq_count_provider=seq_count_provider, pus_verificator=verificator, high_level_file_logger=printer.file_logger, raw_pus_logger=raw_logger, gui=gui, cfdp_in_ccsds_handler=cfdp_in_ccsds_wrapper.handler, ) return ccsds_handler, tc_handler def setup_backend( setup_wrapper: SetupWrapper, tc_handler: TcHandler, ccsds_handler: CcsdsTmHandler, ) -> BackendBase: init_proc = params_to_procedure_conversion(setup_wrapper.proc_param_wrapper) tmtc_backend = tmtccmd.create_default_tmtc_backend( setup_wrapper=setup_wrapper, tm_handler=ccsds_handler, tc_handler=tc_handler, init_procedure=init_proc, ) tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=setup_wrapper.hook_obj) return tmtc_backend def main(): # noqa C901: Complexity okay here. print(f"-- eive tmtc v{__version__} --") print(f"-- spacepackets v{get_spacepackets_version()} --") add_colorlog_console_logger(_LOGGER) # TODO: -V CLI argument to enable this? _LOGGER.setLevel(_LOG_LEVEL) try: setup_wrapper, hk_level = setup_params() except KeyboardInterrupt as e: _LOGGER.info(f"{e}. Exiting") sys.exit(0) tmtc_logger = RegularTmtcLogWrapper() printer = FsfwTmTcPrinter(tmtc_logger.logger) raw_logger = RawTmtcTimedLogWrapper( when=ROTATING_TIMED_LOGGER_INTERVAL_WHEN, interval=ROTATING_TIMED_LOGGER_INTERVAL, ) pus_verificator = PusVerificator() ccsds_handler, tc_handler = setup_tmtc_handlers( pus_verificator, printer, raw_logger, setup_wrapper.params.use_gui, hk_level ) tmtccmd.setup(setup_wrapper) tmtc_backend = setup_backend( setup_wrapper=setup_wrapper, ccsds_handler=ccsds_handler, tc_handler=tc_handler ) try: while True: state = tmtc_backend.periodic_op(None) tc_handler.cfdp_in_ccsds_handler.fsm() if state.request == BackendRequest.TERMINATION_NO_ERROR: sys.exit(0) elif state.request == BackendRequest.DELAY_IDLE: _LOGGER.info("TMTC Client in IDLE mode") time.sleep(3.0) elif state.request == BackendRequest.DELAY_LISTENER: if tc_handler.cfdp_done(): _LOGGER.info("CFDP transaction done, closing client") sys.exit(0) time.sleep(0.5) elif state.request == BackendRequest.DELAY_CUSTOM: if state.next_delay.total_seconds() < 0.5: time.sleep(state.next_delay.total_seconds()) else: time.sleep(0.5) elif state.request == BackendRequest.CALL_NEXT: pass except KeyboardInterrupt: sys.exit(0) if __name__ == "__main__": main()