eive-tmtc/tmtcc.py

479 lines
17 KiB
Python
Raw Normal View History

2022-07-04 18:09:24 +02:00
#!/usr/bin/env python3
2022-07-04 15:22:53 +02:00
import logging
2022-05-17 11:13:32 +02:00
import sys
2022-07-04 18:09:24 +02:00
import time
2022-05-17 11:13:32 +02:00
import traceback
2022-09-16 17:28:19 +02:00
from pathlib import Path
2022-09-16 17:37:01 +02:00
from typing import cast
2022-07-04 15:22:53 +02:00
2022-09-16 17:28:19 +02:00
from spacepackets import SpacePacketHeader, SpacePacket
2022-10-21 11:28:33 +02:00
from spacepackets.ccsds import SPACE_PACKET_HEADER_SIZE
2022-09-16 17:37:01 +02:00
from spacepackets.cfdp import (
ConditionCode,
ChecksumType,
TransmissionMode,
PduHolder,
DirectiveType,
2022-10-21 11:28:33 +02:00
PduFactory,
PduType,
2022-09-16 17:37:01 +02:00
)
2022-09-16 17:28:19 +02:00
from tmtccmd.cfdp import CfdpUserBase, TransactionId
2022-09-16 17:37:01 +02:00
from tmtccmd.cfdp.defs import CfdpRequestType
2022-09-16 17:28:19 +02:00
from tmtccmd.cfdp.handler import CfdpInCcsdsHandler
2022-09-16 17:37:01 +02:00
from tmtccmd.cfdp.mib import (
DefaultFaultHandlerBase,
LocalEntityCfg,
IndicationCfg,
RemoteEntityCfg,
)
from tmtccmd.cfdp.user import (
TransactionFinishedParams,
MetadataRecvParams,
FileSegmentRecvdParams,
)
2022-07-14 15:45:57 +02:00
from tmtccmd.tc.handler import SendCbParams
2022-05-17 11:13:32 +02:00
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:
2022-07-04 18:14:51 +02:00
import tmtccmd
2022-05-17 11:13:32 +02:00
except ImportError as error:
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)
2022-07-04 18:14:51 +02:00
from spacepackets.ecss import PusVerificator
from tmtccmd import get_console_logger, TcHandlerBase, BackendBase
2022-07-13 10:42:48 +02:00
from tmtccmd.util import FileSeqCountProvider, PusFileSeqCountProvider
from tmtccmd.util.tmtc_printer import FsfwTmTcPrinter
2022-07-04 18:14:51 +02:00
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.logging import get_current_time_string
from tmtccmd.tc import (
2022-09-16 17:28:19 +02:00
ProcedureWrapper,
2022-07-04 18:14:51 +02:00
FeedWrapper,
TcProcedureType,
TcQueueEntryType,
DefaultPusQueueHelper,
QueueWrapper,
2022-07-04 18:14:51 +02:00
)
2022-09-16 17:37:01 +02:00
from tmtccmd.config import (
default_json_path,
SetupWrapper,
params_to_procedure_conversion,
)
from tmtccmd.config.args import (
SetupParams,
PreArgsParsingWrapper,
ProcedureParamsWrapper,
)
2022-11-29 16:53:29 +01:00
from eive_tmtc import __version__
from eive_tmtc.config.definitions import (
2022-09-16 17:37:01 +02:00
PUS_APID,
CFDP_APID,
CFDP_LOCAL_ENTITY_ID,
CFDP_REMOTE_ENTITY_ID,
)
2022-11-29 16:53:29 +01:00
from eive_tmtc.config.hook import EiveHookObject
from eive_tmtc.pus_tm.factory_hook import pus_factory_hook
from eive_tmtc.pus_tc.procedure_packer import handle_default_procedure
2022-07-04 15:22:53 +02:00
LOGGER = get_console_logger()
# Put rotating file logger parameters here for quick changes
ROTATING_TIMED_LOGGER_INTERVAL_WHEN = TimedLogWhen.PER_MINUTE
ROTATING_TIMED_LOGGER_INTERVAL = 30
2022-09-16 17:28:19 +02:00
class EiveCfdpFaultHandler(DefaultFaultHandlerBase):
def notice_of_suspension_cb(self, cond: ConditionCode):
pass
def notice_of_cancellation_cb(self, cond: ConditionCode):
pass
def abandoned_cb(self, cond: ConditionCode):
pass
def ignore_cb(self, cond: ConditionCode):
pass
class EiveCfdpUser(CfdpUserBase):
def transaction_indication(self, transaction_id: TransactionId):
LOGGER.info(f"CFDP User: Start of File {transaction_id}")
def eof_sent_indication(self, transaction_id: TransactionId):
LOGGER.info(f"CFDP User: EOF sent for {transaction_id}")
def transaction_finished_indication(self, params: TransactionFinishedParams):
LOGGER.info(f"CFDP User: {params.transaction_id} finished")
def metadata_recv_indication(self, params: MetadataRecvParams):
pass
def file_segment_recv_indication(self, params: FileSegmentRecvdParams):
pass
def report_indication(self, transaction_id: TransactionId, status_report: any):
pass
def suspended_indication(
self, transaction_id: TransactionId, cond_code: ConditionCode
):
pass
def resumed_indication(self, transaction_id: TransactionId, progress: int):
pass
def fault_indication(
self, transaction_id: TransactionId, cond_code: ConditionCode, progress: int
):
pass
def abandoned_indication(
self, transaction_id: TransactionId, cond_code: ConditionCode, progress: int
):
pass
def eof_recv_indication(self, transaction_id: TransactionId):
pass
2022-07-04 15:22:53 +02:00
class PusHandler(SpecificApidHandlerBase):
def __init__(
self,
wrapper: VerificationWrapper,
printer: FsfwTmTcPrinter,
raw_logger: RawTmtcTimedLogWrapper,
):
super().__init__(PUS_APID, None)
self.printer = printer
self.verif_wrapper = wrapper
self.raw_logger = raw_logger
def handle_tm(self, packet: bytes, _user_args: any):
2022-10-31 19:32:42 +01:00
# with open("tc.bin", "wb") as of:
# of.write(packet)
2022-07-04 15:22:53 +02:00
pus_factory_hook(packet, self.verif_wrapper, self.printer, self.raw_logger)
class UnknownApidHandler(GenericApidHandlerBase):
def handle_tm(self, apid: int, _packet: bytes, _user_args: any):
LOGGER.warning(f"Packet with unknwon APID {apid} detected")
2022-09-16 17:28:19 +02:00
class CfdpInCcsdsWrapper(SpecificApidHandlerBase):
def __init__(self, cfdp_in_ccsds_handler: CfdpInCcsdsHandler):
super().__init__(CFDP_APID, None)
self.handler = cfdp_in_ccsds_handler
def handle_tm(self, packet: bytes, _user_args: any):
2022-10-21 11:28:33 +02:00
# Ignore the space packet header. Its only purpose is to use the same protocol and
# have a seaprate APID for space packets. If this function is called, the APID is correct.
pdu = packet[SPACE_PACKET_HEADER_SIZE:]
pdu_base = PduFactory.from_raw(pdu)
if pdu_base.pdu_type == PduType.FILE_DATA:
LOGGER.info("Received File Data PDU TM")
else:
if pdu_base.directive_type == DirectiveType.FINISHED_PDU:
LOGGER.info(f"Received Finished PDU TM")
else:
LOGGER.info(
f"Received File Directive PDU with type {pdu_base.directive_type!r} TM"
)
self.handler.pass_pdu_packet(pdu_base)
2022-09-16 17:28:19 +02:00
2022-07-04 15:22:53 +02:00
class TcHandler(TcHandlerBase):
def __init__(
self,
seq_count_provider: FileSeqCountProvider,
2022-09-16 17:37:01 +02:00
cfdp_in_ccsds_wrapper: CfdpInCcsdsWrapper,
2022-07-04 15:22:53 +02:00
pus_verificator: PusVerificator,
2022-10-27 15:33:25 +02:00
high_level_file_logger: logging.Logger,
2022-10-27 15:32:50 +02:00
raw_pus_logger: RawTmtcTimedLogWrapper,
2022-08-08 16:32:18 +02:00
gui: bool,
2022-07-04 15:22:53 +02:00
):
super().__init__()
2022-09-16 17:37:01 +02:00
self.cfdp_handler_started = False
self.cfdp_dest_id = CFDP_REMOTE_ENTITY_ID
2022-07-04 15:22:53 +02:00
self.seq_count_provider = seq_count_provider
self.pus_verificator = pus_verificator
2022-10-27 15:33:25 +02:00
self.high_level_file_logger = high_level_file_logger
2022-10-27 15:32:50 +02:00
self.pus_raw_logger = raw_pus_logger
2022-07-08 16:25:46 +02:00
self.gui = gui
2022-08-08 16:32:18 +02:00
self.queue_helper = DefaultPusQueueHelper(
2023-01-31 12:56:13 +01:00
queue_wrapper=QueueWrapper.empty(),
default_pus_apid=PUS_APID,
2022-08-08 16:32:18 +02:00
seq_cnt_provider=seq_count_provider,
pus_verificator=pus_verificator,
2023-01-31 15:41:51 +01:00
tc_sched_timestamp_len=4,
2022-08-08 16:32:18 +02:00
)
2022-09-16 17:37:01 +02:00
self.cfdp_in_ccsds_wrapper = cfdp_in_ccsds_wrapper
2022-07-04 15:22:53 +02:00
2022-10-21 11:28:33 +02:00
def cfdp_done(self) -> bool:
2022-11-02 16:08:36 +01:00
if self.cfdp_handler_started:
if not self.cfdp_in_ccsds_wrapper.handler.put_request_pending():
self.cfdp_handler_started = False
return True
return False
2022-10-21 11:28:33 +02:00
2022-09-16 17:28:19 +02:00
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
2022-08-08 16:32:18 +02:00
self.queue_helper.queue_wrapper = wrapper.queue_wrapper
2022-07-04 15:22:53 +02:00
if info.proc_type == TcProcedureType.DEFAULT:
2022-08-18 14:08:05 +02:00
handle_default_procedure(self, info.to_def_procedure(), self.queue_helper)
2022-09-16 17:37:01 +02:00
elif info.proc_type == TcProcedureType.CFDP:
self.handle_cfdp_procedure(info)
2022-05-17 11:13:32 +02:00
2022-07-14 15:45:57 +02:00
def send_cb(self, send_params: SendCbParams):
entry_helper = send_params.entry
2022-07-04 17:59:09 +02:00
if entry_helper.is_tc:
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
2023-01-30 14:31:38 +01:00
# pus_tc_wrapper.pus_tc.apid = PUS_APID
2022-07-04 17:59:09 +02:00
raw_tc = pus_tc_wrapper.pus_tc.pack()
2022-10-27 15:32:50 +02:00
self.pus_raw_logger.log_tc(pus_tc_wrapper.pus_tc)
2022-07-04 17:59:09 +02:00
tc_info_string = f"Sent {pus_tc_wrapper.pus_tc}"
LOGGER.info(tc_info_string)
2022-10-27 15:33:25 +02:00
self.high_level_file_logger.info(
2022-07-04 17:59:09 +02:00
f"{get_current_time_string(True)}: {tc_info_string}"
)
2022-10-31 19:32:42 +01:00
# with open("tc.bin", "wb") as of:
# of.write(raw_tc)
2022-07-14 15:45:57 +02:00
send_params.com_if.send(raw_tc)
2022-09-16 17:37:01 +02:00
elif entry_helper.entry_type == TcQueueEntryType.CCSDS_TC:
cfdp_packet_in_ccsds = entry_helper.to_space_packet_entry()
send_params.com_if.send(cfdp_packet_in_ccsds.space_packet.pack())
2022-10-27 15:21:56 +02:00
# TODO: Log raw CFDP packets similarly to how PUS packets are logged.
# - Log full raw format including space packet wrapper
# - Log context information: Transaction ID, and PDU type and directive
2022-10-27 15:32:50 +02:00
# Could re-use file logger. Should probably do that
2022-10-27 15:21:56 +02:00
# print(f"sending packet: [{cfdp_packet_in_ccsds.space_packet.pack()}]")
# with open(f"cfdp_packet_{self.cfdp_counter}", "wb") as of:
# of.write(cfdp_packet_in_ccsds.space_packet.pack())
# self.cfdp_counter += 1
2022-07-04 17:59:09 +02:00
elif entry_helper.entry_type == TcQueueEntryType.LOG:
log_entry = entry_helper.to_log_entry()
LOGGER.info(log_entry.log_str)
2022-10-27 15:33:25 +02:00
self.high_level_file_logger.info(log_entry.log_str)
2022-07-04 17:59:09 +02:00
2022-09-16 17:37:01 +02:00
def handle_cfdp_procedure(self, info: ProcedureWrapper):
cfdp_procedure = info.to_cfdp_procedure()
if cfdp_procedure.cfdp_request_type == CfdpRequestType.PUT:
if (
not self.cfdp_in_ccsds_wrapper.handler.put_request_pending()
and not self.cfdp_handler_started
):
put_req = cfdp_procedure.request_wrapper.to_put_request()
put_req.cfg.destination_id = self.cfdp_dest_id
LOGGER.info(
f"CFDP: Starting file put request with parameters:\n{put_req}"
)
self.cfdp_in_ccsds_wrapper.handler.cfdp_handler.put_request(put_req)
self.cfdp_handler_started = True
for source_pair, dest_pair in self.cfdp_in_ccsds_wrapper.handler:
pdu, sp = source_pair
pdu = cast(PduHolder, pdu)
if pdu.is_file_directive:
if pdu.pdu_directive_type == DirectiveType.METADATA_PDU:
metadata = pdu.to_metadata_pdu()
self.queue_helper.add_log_cmd(
f"CFDP Source: Sending Metadata PDU for file with size "
f"{metadata.file_size}"
)
elif pdu.pdu_directive_type == DirectiveType.EOF_PDU:
self.queue_helper.add_log_cmd(
f"CFDP Source: Sending EOF PDU"
)
else:
fd_pdu = pdu.to_file_data_pdu()
self.queue_helper.add_log_cmd(
f"CFDP Source: Sending File Data PDU for segment at offset "
f"{fd_pdu.offset} with length {len(fd_pdu.file_data)}"
)
self.queue_helper.add_ccsds_tc(sp)
self.cfdp_in_ccsds_wrapper.handler.confirm_source_packet_sent()
self.cfdp_in_ccsds_wrapper.handler.source_handler.state_machine()
2022-09-16 17:28:19 +02:00
def queue_finished_cb(self, info: ProcedureWrapper):
2022-09-16 17:37:01 +02:00
if info is not None:
if info.proc_type == TcQueueEntryType.PUS_TC:
def_proc = info.to_def_procedure()
LOGGER.info(
f"Finished queue for service {def_proc.service} and op code {def_proc.op_code}"
)
elif info.proc_type == TcProcedureType.CFDP:
LOGGER.info(f"Finished CFDP queue")
2022-07-04 18:09:24 +02:00
2022-05-17 11:13:32 +02:00
2022-07-04 18:09:24 +02:00
def setup_params() -> SetupWrapper:
2022-05-17 11:13:32 +02:00
print(f"-- eive tmtc v{__version__} --")
print(f"-- spacepackets v{spacepackets.__version__} --")
2022-07-04 18:09:24 +02:00
hook_obj = EiveHookObject(default_json_path())
params = SetupParams()
2022-09-16 17:28:19 +02:00
parser_wrapper = PreArgsParsingWrapper()
2022-08-19 12:38:18 +02:00
parser_wrapper.create_default_parent_parser()
parser_wrapper.create_default_parser()
parser_wrapper.add_def_proc_and_cfdp_as_subparsers()
2023-01-31 15:41:51 +01:00
post_arg_parsing_wrapper = parser_wrapper.parse(
setup_params=params, hook_obj=hook_obj
)
2022-09-16 17:28:19 +02:00
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:
2023-01-31 12:56:13 +01:00
post_arg_parsing_wrapper.set_params_with_prompts(proc_param_wrapper)
2022-09-16 17:28:19 +02:00
else:
2023-01-31 12:56:13 +01:00
post_arg_parsing_wrapper.set_params_without_prompts(proc_param_wrapper)
2022-07-04 18:09:24 +02:00
params.apid = PUS_APID
2023-01-31 12:56:13 +01:00
if params.com_if is None:
raise ValueError("could not determine a COM interface.")
2022-09-16 17:28:19 +02:00
setup_wrapper = SetupWrapper(
2022-09-16 17:37:01 +02:00
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_param_wrapper
2022-09-16 17:28:19 +02:00
)
2022-07-04 18:09:24 +02:00
return setup_wrapper
2022-09-16 17:28:19 +02:00
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,
)
remote_cfg = RemoteEntityCfg(
closure_requested=False,
entity_id=CFDP_REMOTE_ENTITY_ID,
max_file_segment_len=1024,
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(
2022-12-15 11:10:33 +01:00
file_name=Path("seqcnt_cfdp_in_ccsds.txt")
2022-09-16 17:28:19 +02:00
)
cfdp_user = EiveCfdpUser()
cfdp_in_ccsds_handler = CfdpInCcsdsHandler(
cfg=cfdp_cfg,
remote_cfgs=[remote_cfg],
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(
2022-07-04 18:09:24 +02:00
verificator: PusVerificator,
printer: FsfwTmTcPrinter,
raw_logger: RawTmtcTimedLogWrapper,
2022-08-08 16:32:18 +02:00
gui: bool,
2022-07-04 18:09:24 +02:00
) -> (CcsdsTmHandler, TcHandler):
2022-09-16 17:28:19 +02:00
cfdp_in_ccsds_wrapper = setup_cfdp_handler()
2022-07-04 18:09:24 +02:00
verification_wrapper = VerificationWrapper(verificator, LOGGER, printer.file_logger)
pus_handler = PusHandler(verification_wrapper, printer, raw_logger)
ccsds_handler = CcsdsTmHandler(generic_handler=UnknownApidHandler(None))
ccsds_handler.add_apid_handler(pus_handler)
2022-09-16 17:28:19 +02:00
ccsds_handler.add_apid_handler(cfdp_in_ccsds_wrapper)
2022-07-08 16:25:46 +02:00
seq_count_provider = PusFileSeqCountProvider()
2022-07-04 18:09:24 +02:00
tc_handler = TcHandler(
seq_count_provider=seq_count_provider,
pus_verificator=verificator,
2022-10-27 15:33:25 +02:00
high_level_file_logger=printer.file_logger,
2022-10-27 15:32:50 +02:00
raw_pus_logger=raw_logger,
2022-08-08 16:32:18 +02:00
gui=gui,
2022-09-16 17:37:01 +02:00
cfdp_in_ccsds_wrapper=cfdp_in_ccsds_wrapper,
2022-07-04 18:09:24 +02:00
)
return ccsds_handler, tc_handler
2022-05-17 11:13:32 +02:00
2022-07-04 18:09:24 +02:00
def setup_backend(
setup_wrapper: SetupWrapper,
tc_handler: TcHandler,
ccsds_handler: CcsdsTmHandler,
) -> BackendBase:
2022-09-16 17:28:19 +02:00
init_proc = params_to_procedure_conversion(setup_wrapper.proc_param_wrapper)
2022-05-17 11:13:32 +02:00
tmtc_backend = tmtccmd.create_default_tmtc_backend(
2022-09-16 17:37:01 +02:00
setup_wrapper=setup_wrapper,
tm_handler=ccsds_handler,
tc_handler=tc_handler,
init_procedure=init_proc,
2022-07-04 18:09:24 +02:00
)
tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=setup_wrapper.hook_obj)
return tmtc_backend
def main():
try:
setup_wrapper = 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,
2022-08-17 11:19:23 +02:00
interval=ROTATING_TIMED_LOGGER_INTERVAL,
)
2022-07-04 18:09:24 +02:00
pus_verificator = PusVerificator()
2022-09-16 17:28:19 +02:00
ccsds_handler, tc_handler = setup_tmtc_handlers(
2022-07-08 16:25:46 +02:00
pus_verificator, printer, raw_logger, setup_wrapper.params.use_gui
)
2022-07-04 18:09:24 +02:00
tmtccmd.setup(setup_wrapper)
tmtc_backend = setup_backend(
setup_wrapper=setup_wrapper, ccsds_handler=ccsds_handler, tc_handler=tc_handler
2022-05-17 11:13:32 +02:00
)
2022-07-04 18:09:24 +02:00
try:
while True:
state = tmtc_backend.periodic_op(None)
2022-10-21 15:24:57 +02:00
tc_handler.cfdp_in_ccsds_wrapper.handler.fsm()
2022-07-04 18:09:24 +02:00
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:
2022-10-21 11:28:33 +02:00
if tc_handler.cfdp_done():
LOGGER.info("CFDP transaction done, closing client")
sys.exit(0)
2022-08-11 18:10:15 +02:00
time.sleep(0.5)
2022-07-04 18:09:24 +02:00
elif state.request == BackendRequest.DELAY_CUSTOM:
2022-08-11 18:10:15 +02:00
if state.next_delay.total_seconds() < 0.5:
time.sleep(state.next_delay.total_seconds())
else:
time.sleep(0.5)
2022-07-04 18:09:24 +02:00
elif state.request == BackendRequest.CALL_NEXT:
pass
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
main()