diff --git a/edosl0util/headers.py b/edosl0util/headers.py index f21c68c893c5c2c1e5efa4033d3026db78d8cb8c..934c1e78d79003022b4f6cdcc870951b50e13627 100644 --- a/edosl0util/headers.py +++ b/edosl0util/headers.py @@ -1,29 +1,30 @@ # -*- coding: utf-8 -*- """ -CCSDS BaseStruct implementations for AQUA. +Structures for CCSDS Primary and Secondary headers See: - ICD_Space_Ground_Aqua.pdf - GSFC 422-11-19-03 - -Last known location: - http://directreadout.sci.gsfc.nasa.gov/links/rsd_eosdb/PDF/ICD_Space_Ground_Aqua.pdf + * ICD_Space_Ground_Aqua.pdf - GSFC 422-11-19-03 + - http://directreadout.sci.gsfc.nasa.gov/links/rsd_eosdb/PDF/ICD_Space_Ground_Aqua.pdf + * CCSDS Space Packet Protocol + - http://public.ccsds.org/publications/archive/133x0b1c2.pdf + * CCSDS Time Code Formats + - http://public.ccsds.org/publications/archive/301x0b4e1.pdf """ import ctypes as c from datetime import datetime, timedelta -from edos.ccsds import ( - GROUP_FIRST, - GROUP_LAST, - GROUP_CONTINUING, - GROUP_STANDALONE -) from edosl0util.timecode import cds_stamp, cds_to_timestamp +GROUP_FIRST = 0b01 +GROUP_LAST = 0b10 +GROUP_CONTINUING = 0b00 +GROUP_STANDALONE = 0b11 + + class BaseStruct(c.BigEndianStructure): _pack_ = 1 @@ -35,8 +36,25 @@ class BaseStruct(c.BigEndianStructure): return '<%s (%s)>' % (self.__class__.__name__, fields) -class Timecode(BaseStruct): +class PrimaryHeader(BaseStruct): + """ + CCSDS Primary Header. + """ + _fields_ = [ + ('version_number', c.c_uint8, 3), # == 0 + ('type_indicator', c.c_uint8, 1), + ('secondary_header_flag', c.c_uint8, 1), + ('apid', c.c_uint16, 11), + ('sequence_grouping', c.c_uint16, 2), + ('source_sequence_count', c.c_uint16, 14), + ('data_length_minus1', c.c_uint16) # octet count minus one + ] + +class Timecode(BaseStruct): + """ + Secondary header timecode baseclass. + """ def astimestamp(self): raise NotImplementedError() @@ -48,13 +66,18 @@ class AquaCucTimecode(Timecode): """ EOS AQUA implementation of a CCSDS Unsegmented Timecode. An CUT is an agency defined timecode which is in this case (I think) specific to Aqua. + + NOTE: I'm betting this will work for Terra as well but I do not have any + documentation for Terra. + + FIXME: Not tested or validated!! """ _fields_ = [ - ('extension_flag', c.c_uint8, 1), # 1 - ('timecode_epoch', c.c_uint8, 3), # 010 == Jan 1, 1958 - ('coarse_time_count', c.c_uint8, 2), # 11 == 4 octets - ('fine_time_count', c.c_uint8, 2), # 10 == 2 octets - ('no_continuation_flag', c.c_uint8, 1), # 0 + ('extension_flag', c.c_uint8, 1), + ('timecode_epoch', c.c_uint8, 3), + ('coarse_time_count', c.c_uint8, 2), + ('fine_time_count', c.c_uint8, 2), + ('no_continuation_flag', c.c_uint8, 1), ('leap_seconds', c.c_uint8, 7), ('seconds', c.c_uint32), ('sub_seconds', c.c_uint16) @@ -81,6 +104,9 @@ class AquaCucTimecode(Timecode): class DaySegmentedTimecode(Timecode): + """ + CCSDS Day Segmented Timecode + """ _pack_ = 1 _fields_ = [ ('days', c.c_uint16), @@ -98,16 +124,14 @@ class DaySegmentedTimecode(Timecode): return cds_stamp(self.days, self.milliseconds, self.microseconds) -class GirdSecondaryHeader(BaseStruct): - _pack_ = 1 +class AquaGirdSecondaryHeader(BaseStruct): _fields_ = [ ('flags', c.c_uint8), ('timecode', AquaCucTimecode), ] -class GiisSecondaryHeader(BaseStruct): - _pack_ = 1 +class AquaGiisSecondaryHeader(BaseStruct): _fields_ = [ ('timecode', DaySegmentedTimecode), ('quicklook_flag', c.c_uint8, 1), @@ -115,12 +139,42 @@ class GiisSecondaryHeader(BaseStruct): ] -class SpacecraftBusSecondaryHeader(BaseStruct): +class AquaSpacecraftBusSecondaryHeader(BaseStruct): _fields_ = [ ('timecode', AquaCucTimecode) ] +class JpssSecondaryHeader(BaseStruct): + """ + Secondary header using Day Segmented timecodes. + """ + _pack_ = 1 + _fields_ = [ + ('timecode', DaySegmentedTimecode) + ] + + +class JpssFirstSecondaryHeader(BaseStruct): + """ + Secondary header using Day Segmented timecodes and with a packet count. + """ + _pack_ = 1 + _fields_ = [ + ('timecode', DaySegmentedTimecode), + ('packet_count', c.c_uint8), + ('_spare', c.c_uint8) + ] + + +def jpss_header_lookup(primary_header): + grouping = primary_header.sequence_grouping + if grouping == GROUP_FIRST: + return JpssFirstSecondaryHeader + elif grouping == GROUP_STANDALONE: + return JpssSecondaryHeader + + def amsu_headers(): apids = [ # AMSU-A1 @@ -129,7 +183,7 @@ def amsu_headers(): 288, 289, 290 ] flags = [GROUP_FIRST, GROUP_CONTINUING, GROUP_LAST, GROUP_STANDALONE] - return {(apid, flag): GirdSecondaryHeader + return {(apid, flag): AquaGirdSecondaryHeader for apid in apids for flag in flags} @@ -137,32 +191,32 @@ def amsu_headers(): def airs_headers(): return { # AIRS - (404, GROUP_STANDALONE): GirdSecondaryHeader, - (405, GROUP_STANDALONE): GirdSecondaryHeader, - (406, GROUP_STANDALONE): GirdSecondaryHeader, - (407, GROUP_STANDALONE): GirdSecondaryHeader, - (414, GROUP_STANDALONE): GirdSecondaryHeader, - (415, GROUP_STANDALONE): GirdSecondaryHeader, - (416, GROUP_STANDALONE): GirdSecondaryHeader, - (417, GROUP_STANDALONE): GirdSecondaryHeader, + (404, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (405, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (406, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (407, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (414, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (415, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (416, GROUP_STANDALONE): AquaGirdSecondaryHeader, + (417, GROUP_STANDALONE): AquaGirdSecondaryHeader, } def hsb_headers(): return { # HSB - (342, GROUP_STANDALONE): GirdSecondaryHeader, + (342, GROUP_STANDALONE): AquaGirdSecondaryHeader, } def modis_headers(): return { # MODIS - (64, GROUP_STANDALONE): GiisSecondaryHeader, - (64, GROUP_FIRST): GiisSecondaryHeader, - (64, GROUP_LAST): GiisSecondaryHeader, - (64, GROUP_CONTINUING): GiisSecondaryHeader, - (127, GROUP_STANDALONE): GiisSecondaryHeader, + (64, GROUP_STANDALONE): AquaGiisSecondaryHeader, + (64, GROUP_FIRST): AquaGiisSecondaryHeader, + (64, GROUP_LAST): AquaGiisSecondaryHeader, + (64, GROUP_CONTINUING): AquaGiisSecondaryHeader, + (127, GROUP_STANDALONE): AquaGiisSecondaryHeader, } @@ -174,13 +228,13 @@ def ceres_headers(): 157, 158, 159, 160 ) groupings = (GROUP_FIRST, GROUP_CONTINUING, GROUP_LAST, GROUP_STANDALONE) - return {(a, g): GiisSecondaryHeader for a in apids for g in groupings} + return {(a, g): AquaGiisSecondaryHeader for a in apids for g in groupings} def gbad_headers(): return { # GBAD - (957, GROUP_STANDALONE): SpacecraftBusSecondaryHeader + (957, GROUP_STANDALONE): AquaSpacecraftBusSecondaryHeader } @@ -198,38 +252,7 @@ def aqua_headers(): return headers -class JpssSecondaryHeader(BaseStruct): - """Secondary Header for a JSPP CCSDS packet that is not part of a packet - sequence. - """ - _pack_ = 1 - _fields_ = [ - ('timecode', DaySegmentedTimecode) - ] - - -class JpssFirstSecondaryHeader(BaseStruct): - """Secondary Header for a JSPP CCSDS packet that is the first packet in a - packet sequence. Following packets that are part of this sequence will not - have a secondary header. - """ - _pack_ = 1 - _fields_ = [ - ('timecode', DaySegmentedTimecode), - ('packet_count', c.c_uint8), - ('_spare', c.c_uint8) - ] - - -def jpss_header_lookup(primary_header): - grouping = primary_header.sequence_grouping - if grouping == GROUP_FIRST: - return JpssFirstSecondaryHeader - elif grouping == GROUP_STANDALONE: - return JpssSecondaryHeader - - -def aqua_header_lookup(primary_header): # noqa +def aqua_header_lookup(primary_header): apid = primary_header.apid grouping = primary_header.sequence_grouping return _aqua_headers.get((apid, grouping)) diff --git a/edosl0util/stream.py b/edosl0util/stream.py index a3eab8ecb171b9a495027e41c80fcfbebd1cd7cb..993298caaadc82ad87f19d5639c14b21ba3ce15a 100644 --- a/edosl0util/stream.py +++ b/edosl0util/stream.py @@ -1,10 +1,14 @@ import io import logging +import ctypes as c from collections import deque, defaultdict -from edos.ccsds import ( - c, +from edosl0util.headers import ( PrimaryHeader, + GROUP_FIRST, + GROUP_CONTINUING, + GROUP_LAST, + GROUP_STANDALONE ) from edosl0util import headers @@ -19,17 +23,15 @@ def simple_stream(fobj): """ Generator that yields PrimaryHeaders and data. Files are read using mmap. """ - if fobj is not None: - data = fobj psize = c.sizeof(PrimaryHeader) while True: - buf = data.read(psize) + buf = fobj.read(psize) if len(buf) < psize: return h1 = PrimaryHeader.from_buffer_copy(buf) # read user data size = h1.data_length_minus1 + 1 - buf = data.read(size) + buf = fobj.read(size) if len(buf) < size: return yield h1, buf @@ -102,16 +104,16 @@ class Packet(object): return False def is_first(self): - return self.primary_header.sequence_grouping == 0b01 + return self.primary_header.sequence_grouping == GROUP_FIRST def is_continuine(self): - return self.primary_header.sequence_grouping == 0b00 + return self.primary_header.sequence_grouping == GROUP_CONTINUING def is_last(self): - return self.primary_header.sequence_grouping == 0b10 + return self.primary_header.sequence_grouping == GROUP_LAST def is_standalone(self): - return self.primary_header.sequence_grouping == 0b11 + return self.primary_header.sequence_grouping == GROUP_STANDALONE class PacketStream(object):