From 013d1162a261001a759fbd873a82cf778a493e45 Mon Sep 17 00:00:00 2001 From: Marko Mecina <marko.mecina@univie.ac.at> Date: Thu, 27 Oct 2022 14:01:20 +0200 Subject: [PATCH] make S13 data header parameters configurable --- Ccs/ccs_function_lib.py | 135 +++++++++++++++++++++++++++++------- Ccs/decompression.py | 9 ++- Ccs/packet_config_CHEOPS.py | 14 ++++ Ccs/packet_config_SMILE.py | 17 ++++- 4 files changed, 144 insertions(+), 31 deletions(-) diff --git a/Ccs/ccs_function_lib.py b/Ccs/ccs_function_lib.py index 2a91934..e6abdeb 100644 --- a/Ccs/ccs_function_lib.py +++ b/Ccs/ccs_function_lib.py @@ -57,21 +57,25 @@ except SQLOperationalError as err: project = cfg.get('ccs-database', 'project') pc = importlib.import_module(PCPREFIX + str(project).upper()) -PUS_VERSION, TMHeader, TCHeader, PHeader, TM_HEADER_LEN, TC_HEADER_LEN, P_HEADER_LEN, PEC_LEN, MAX_PKT_LEN, timepack, \ - timecal, calc_timestamp, CUC_OFFSET, CUC_EPOCH, crc, PLM_PKT_PREFIX_TC_SEND, PLM_PKT_SUFFIX, FMT_TYPE_PARAM = \ - [pc.PUS_VERSION, pc.TMHeader, pc.TCHeader, pc.PHeader, - pc.TM_HEADER_LEN, pc.TC_HEADER_LEN, pc.P_HEADER_LEN, pc.PEC_LEN, - pc.MAX_PKT_LEN, pc.timepack, pc.timecal, pc.calc_timestamp, - pc.CUC_OFFSET, pc.CUC_EPOCH, pc.puscrc, pc.PLM_PKT_PREFIX_TC_SEND, pc.PLM_PKT_SUFFIX, pc.FMT_TYPE_PARAM] +# project specific parameters, must be present in all packet_config_* files +try: + PUS_VERSION, TMHeader, TCHeader, PHeader, TM_HEADER_LEN, TC_HEADER_LEN, P_HEADER_LEN, PEC_LEN, MAX_PKT_LEN, timepack, \ + timecal, calc_timestamp, CUC_OFFSET, CUC_EPOCH, crc, PLM_PKT_PREFIX_TC_SEND, PLM_PKT_SUFFIX, FMT_TYPE_PARAM = \ + [pc.PUS_VERSION, pc.TMHeader, pc.TCHeader, pc.PHeader, + pc.TM_HEADER_LEN, pc.TC_HEADER_LEN, pc.P_HEADER_LEN, pc.PEC_LEN, + pc.MAX_PKT_LEN, pc.timepack, pc.timecal, pc.calc_timestamp, + pc.CUC_OFFSET, pc.CUC_EPOCH, pc.puscrc, pc.PLM_PKT_PREFIX_TC_SEND, pc.PLM_PKT_SUFFIX, pc.FMT_TYPE_PARAM] + + s13_unpack_data_header = pc.s13_unpack_data_header +except AttributeError as err: + logger.critical(err) + raise err SREC_MAX_BYTES_PER_LINE = 250 SEG_HEADER_LEN = 12 SEG_SPARE_LEN = 2 SEG_CRC_LEN = 2 -SDU_PAR_LENGTH = 1 -S13_HEADER_LEN_TOTAL = 21 # length of PUS + source header in S13 packets (i.e. data to be removed when collecting S13) - pid_offset = int(cfg.get('ccs-misc', 'pid_offset')) fmtlist = {'INT8': 'b', 'UINT8': 'B', 'INT16': 'h', 'UINT16': 'H', 'INT32': 'i', 'UINT32': 'I', 'INT64': 'q', @@ -803,13 +807,13 @@ def read_stream(stream, fmt, pos=None, offbi=0): return x -## -# csize -# -# Returns the Amount of Bytes for the input format -# @param fmt Input String that defines the format -# @param offbi def csize(fmt, offbi=0): + """ + Returns the amount of bytes required for the input format + @param fmt: Input String that defines the format + @param offbi: + @return: + """ if fmt in ('i24', 'I24'): return 3 elif fmt.startswith('uint'): @@ -821,7 +825,10 @@ def csize(fmt, offbi=0): elif fmt.startswith('ascii'): return int(fmt[5:]) else: - return struct.calcsize(fmt) + try: + return struct.calcsize(fmt) + except struct.error: + raise NotImplementedError(fmt) ## @@ -855,7 +862,7 @@ def none_to_empty(s): def Tm_header_formatted(tm, detailed=False): - '''unpack APID, SEQCNT, PKTLEN, TYPE, STYPE, SOURCEID''' + """unpack APID, SEQCNT, PKTLEN, TYPE, STYPE, SOURCEID""" # if len(tm) < TC_HEADER_LEN: # return 'Cannot decode header - packet has only {} bytes!'.format(len(tm)) @@ -1462,9 +1469,15 @@ def get_cuctime(tml): return cuc_timestamp -def get_pool_rows(pool_name, dbcon=None): +def get_pool_rows(pool_name, check_existence=False): dbcon = scoped_session_storage + if check_existence: + check = dbcon.query(DbTelemetryPool).filter(DbTelemetryPool.pool_name == pool_name) + if not check.count(): + dbcon.close() + raise ValueError('Pool "{}" does not exist.'.format(pool_name)) + rows = dbcon.query( DbTelemetry ).join( @@ -3037,9 +3050,9 @@ def get_tc_calibration_and_parameters(ccf_descr=None): return calibrations_dict -def get_tm_parameter_list(st, sst, apid, pi1val): - que = 'SELECT pid_spid, pid_tpsd FROM pid WHERE pid_type={} AND pid_stype={} AND pid_apid={} AND pid_pi1_val={}'.format(st, sst, apid, pi1val) - spid, tpsd = scoped_session_idb.execute(que).fetchall()[0] +def get_tm_parameter_list(st, sst, apid=None, pi1val=0): + + spid, tpsd = _get_spid(st, sst, apid=apid, pi1val=pi1val) if tpsd == -1: que = 'SELECT plf_name, pcf_descr, plf_offby, pcf_ptc, pcf_pfc FROM plf LEFT JOIN pcf ON plf_name=pcf_name WHERE plf_spid={} ORDER BY plf_offby, plf_offbi'.format(spid) @@ -3052,7 +3065,8 @@ def get_tm_parameter_list(st, sst, apid, pi1val): def get_tm_parameter_info(pname): - que = 'SELECT ocp_lvalu, ocp_hvalu, ocp_type, txp_from, txp_altxt FROM pcf LEFT JOIN ocp ON pcf_name=ocp_name LEFT JOIN txp ON pcf_curtx=txp_numbr WHERE pcf_name="{}" ORDER BY txp_from, ocp_pos'.format(pname) + que = 'SELECT ocp_lvalu, ocp_hvalu, ocp_type, txp_from, txp_altxt FROM pcf LEFT JOIN ocp ON pcf_name=ocp_name ' \ + 'LEFT JOIN txp ON pcf_curtx=txp_numbr WHERE pcf_name="{}" ORDER BY txp_from, ocp_pos'.format(pname) res = scoped_session_idb.execute(que).fetchall() return res @@ -3103,6 +3117,55 @@ def get_tm_id(pcf_descr=None): return tms_dict +def get_tm_parameter_sizes(st, sst, apid=None, pi1val=0): + """ + Returns a list of parameters and their sizes. For variable length TMs only the first fixed part is considered. + @param st: + @param sst: + @param apid: + @param pi1val: + @return: + """ + + spid, tpsd = _get_spid(st, sst, apid=apid, pi1val=pi1val) + + if tpsd == -1: + que = 'SELECT plf_name, pcf_descr, pcf_ptc, pcf_pfc, NULL FROM plf LEFT JOIN pcf ON plf_name=pcf_name WHERE plf_spid={} ORDER BY plf_offby, plf_offbi'.format(spid) + else: + que = 'SELECT vpd_name, pcf_descr, pcf_ptc, pcf_pfc, vpd_grpsize FROM vpd LEFT JOIN pcf ON vpd_name=pcf_name WHERE vpd_tpsd={} ORDER BY vpd_pos'.format(tpsd) + + res = scoped_session_idb.execute(que).fetchall() + + pinfo = [] + for p in res: + pinfo.append((p[1], csize(ptt(*p[2:4])))) + # break after first "counter" parameter + if p[-1] != 0: + break + + return pinfo + + +def _get_spid(st, sst, apid=None, pi1val=0): + """ + + @param st: + @param sst: + @param apid: + @param pi1val: + @return: + """ + if apid is None: + apid = '' + else: + apid = ' AND pid_apid={}'.format(apid) + + que = 'SELECT pid_spid, pid_tpsd FROM pid WHERE pid_type={} AND pid_stype={}{} AND pid_pi1_val={}'.format(st, sst, apid, pi1val) + spid, tpsd = scoped_session_idb.execute(que).fetchall()[0] + + return spid, tpsd + + def get_data_pool_items(pcf_descr=None, src_file=None): if not isinstance(src_file, (str, type(None))): raise TypeError('src_file must be str, is {}.'.format(type(src_file))) @@ -4147,13 +4210,15 @@ def collect_13(pool_name, starttime=None, endtime=None, startidx=None, endidx=No def dump_large_data(pool_name, starttime=0, endtime=None, outdir="", dump_all=False, sdu=None, startidx=None, endidx=None): """ - Dump 13,2 data to disk - @param endidx: + Dump 13,2 data to disk. For pools loaded from a file, pool_name must be the absolute path of that file. @param pool_name: @param starttime: @param endtime: @param outdir: @param dump_all: + @param sdu: + @param startidx: + @param endidx: """ filedict = {} ldt_dict = collect_13(pool_name, starttime=starttime, endtime=endtime, join=True, collect_all=dump_all, @@ -4161,13 +4226,18 @@ def dump_large_data(pool_name, starttime=0, endtime=None, outdir="", dump_all=Fa for buf in ldt_dict: if ldt_dict[buf] is None: continue - obsid, time, ftime, ctr = struct.unpack('>IIHH', ldt_dict[buf][:12]) # TODO this has to be configurable + + try: + obsid, time, ftime, ctr = s13_unpack_data_header(ldt_dict[buf]) + except ValueError as err: + logger.error('Incompatible definition of S13 data header.') + raise err + fname = os.path.join(outdir, "LDT_{:03d}_{:010d}_{:06d}.ce".format(obsid, time, ctr)) with open(fname, "wb") as fdesc: fdesc.write(ldt_dict[buf]) filedict[buf] = fdesc.name - # return list(ldt_dict.keys()) return filedict @@ -5046,6 +5116,8 @@ class ProjectDialog(Gtk.Dialog): sys.exit() +# some default parameter definitions that require functions defined above + # create local look-up tables for data pool items from MIB try: DP_ITEMS_SRC_FILE = cfg.get('database', 'datapool-items') @@ -5061,3 +5133,14 @@ except (FileNotFoundError, ValueError, confignator.config.configparser.NoOptionE finally: DP_IDS_TO_ITEMS = {int(k[0]): k[1] for k in _dp_items} DP_ITEMS_TO_IDS = {k[1]: int(k[0]) for k in _dp_items} + +# S13 header/offset info +try: + _s13_info = get_tm_parameter_sizes(13, 1) + SDU_PAR_LENGTH = _s13_info[0][-1] + # length of PUS + source header in S13 packets (i.e. data to be removed when collecting S13) + S13_HEADER_LEN_TOTAL = TM_HEADER_LEN + sum([p[-1] for p in _s13_info]) +except (SQLOperationalError, NotImplementedError, IndexError): + logger.warning('Could not get S13 info from MIB, using default values') + SDU_PAR_LENGTH = 1 + S13_HEADER_LEN_TOTAL = 21 diff --git a/Ccs/decompression.py b/Ccs/decompression.py index de58d6d..62af3b8 100644 --- a/Ccs/decompression.py +++ b/Ccs/decompression.py @@ -13,10 +13,13 @@ cfg = confignator.get_config(check_interpolation=False) logger = logging.getLogger(__name__) logger.setLevel(getattr(logging, cfg.get('ccs-logging', 'level').upper())) +CE_COLLECT_TIMEOUT = 1 +LDT_MINIMUM_CE_GAP = 0.001 + ce_decompressors = {} -def create_fits(data=None, header=None, filename=None): +def create_fits(header=None, filename=None): hdulist = pyfits.HDUList() hdu = pyfits.PrimaryHDU() hdu.header = header @@ -117,8 +120,8 @@ class CeDecompress: self.ce_decompression_on = False self.ce_thread = None self.last_ce_time = 0 - self.ce_collect_timeout = 1 - self.ldt_minimum_ce_gap = 0.001 + self.ce_collect_timeout = CE_COLLECT_TIMEOUT + self.ldt_minimum_ce_gap = LDT_MINIMUM_CE_GAP def _ce_decompress(self): checkdir = os.path.dirname(self.outdir) diff --git a/Ccs/packet_config_CHEOPS.py b/Ccs/packet_config_CHEOPS.py index 3aca67c..5bb405c 100644 --- a/Ccs/packet_config_CHEOPS.py +++ b/Ccs/packet_config_CHEOPS.py @@ -8,6 +8,8 @@ Author: Marko Mecina (MM) import ctypes import datetime +import struct + from s2k_partypes import ptt import crcmod @@ -376,3 +378,15 @@ class EventDetectionData(ctypes.Union): def __init__(self): raise NotImplementedError('Not available in project CHEOPS') + + +# S13 data header format, using python struct conventions +S13_FMT_OBSID = 'I' +S13_FMT_TIME = 'I' +S13_FMT_FTIME = 'H' +S13_FMT_COUNTER = 'H' +_S13_HEADER_FMT = S13_FMT_OBSID + S13_FMT_TIME + S13_FMT_FTIME + S13_FMT_COUNTER + + +def s13_unpack_data_header(buf): + return struct.unpack('>' + _S13_HEADER_FMT, buf[:struct.calcsize(_S13_HEADER_FMT)]) diff --git a/Ccs/packet_config_SMILE.py b/Ccs/packet_config_SMILE.py index 9034adf..2fba577 100644 --- a/Ccs/packet_config_SMILE.py +++ b/Ccs/packet_config_SMILE.py @@ -8,6 +8,7 @@ Author: Marko Mecina (MM) import ctypes import datetime +import struct import numpy as np import crcmod @@ -737,10 +738,10 @@ class FeeDataTransfer(FeeDataTransferHeader): if self.bits.PKT_TYPE == FEE_PKT_TYPE_EV_DET: evtdata = EventDetectionData() evtdata.bin[:] = self.data + # structure according to MSSL-SMILE-SXI-IRD-0001 self.evt_data = {"COLUMN": evtdata.bits.column, "ROW": evtdata.bits.row, - "IMAGE": np.array(evtdata.bits.array)[ - ::-1]} # structure according to MSSL-SMILE-SXI-IRD-0001 + "IMAGE": np.array(evtdata.bits.array)[::-1]} else: self.evt_data = None @@ -760,3 +761,15 @@ class EventDetectionData(ctypes.Union): ("bits", EventDetectionFields), ("bin", ctypes.c_ubyte * ctypes.sizeof(EventDetectionFields)) ] + + +# S13 data header format, using python struct conventions +S13_FMT_OBSID = 'I' +S13_FMT_TIME = 'I' +S13_FMT_FTIME = 'H' +S13_FMT_COUNTER = 'H' +_S13_HEADER_FMT = S13_FMT_OBSID + S13_FMT_TIME + S13_FMT_FTIME + S13_FMT_COUNTER + + +def s13_unpack_data_header(buf): + return struct.unpack('>' + _S13_HEADER_FMT, buf[:struct.calcsize(_S13_HEADER_FMT)]) -- GitLab