1#!/usr/bin/env python2
2
3"""Implementation of CCSDS class."""
4
5from abc import ABC
6from abc import abstractmethod
7import logging
8import sys
9from datetime import datetime
10from datetime import timedelta
11from binascii import hexlify
12
13from chart.common.exceptions import ConfigError
14from chart.products.pus.packet_criteria import PacketCriteria
15from chart.products.pus.packetdef import PacketDef
16from chart.products.pus.packetdef import PacketHeaderType
17from chart.common.binary import pack_uint16_be
18from chart.common.binary import unpack_uint16_be
19from chart.common.binary import unpack_uint22_be
20from chart.common.binary import unpack_uint24_be
21from chart.common.binary import pack_uint32_be
22from chart.common.binary import unpack_uint32_be
23from chart.project import settings
24from chart.project import SID
25from chart.products.utils import Segment
26from chart.products.pus.exceptions import CCSDSBadHeader
27
28logger = logging.getLogger()
29
30# for int.from_bytes calls
31BYTEORDER = 'big'
32
33# Map from OBT time to UTC
34# This is the GPS reference time less number of leap seconds so far
35CUC_REFTIME = datetime(1980, 1, 6) - timedelta(seconds=17)
36
37# APID for Idle Packets, which can be discarded
38IDLE_PACKET_APID = 2047
39
40# First Header Pointer value indicating no CCSDS packet begins in this frame
41FHP_UNUSED = 2047
42
43# The true length of a CCSDS packet is 7 bytes more than the dflength field
44DFLENGTH_OFFSET = 7
45
46# Only allow reset_sensing_time() to be called for packets which are long enough
47CCSDSSTDXPUSS_LEN = 10
48
49# We need this due to special format of HPTM packet, S3 specific
50# Note: there is no APID= 29 for MTG or JCS defined, so do not require specific check
51SERVICE_HPTM = 0
52SUBSERVICE_HPTM = 0
53APID_HPTM = 29
54
55class CCSDS(Segment):
56 """Base class for specific CCSDS types."""
57
58 @staticmethod
59 def make_packet(header_type: PacketHeaderType, buff: bytes):
60 """Construct a CCSDS packet object, based on `header_type`, using binary `buff`."""
61 if header_type in [PacketHeaderType.CCSDS_NOM_TC, PacketHeaderType.CCSDS_STDXPUSS]:
62 return CCSDSSTDXPUSS(buff)
63 # return CCSDSTM(buff)
64
65 elif header_type is PacketHeaderType.CCSDS_STDXCCSD:
66 return CCSDSXCCSD(buff)
67
68 elif header_type is PacketHeaderType.CCSDS_NO_HDR:
69 return CCSDS_NO_HDR(buff)
70
71 else:
72 raise NotImplementedError()
73
74 def has_pec(self):
75 return False
76
77 def datetime_params_use_reference_time(self):
78 """Specify source of referent time values for relative datetime parameters in this packet.
79
80 For parameters with a coarse time defined, we use a standard fixed reference time.
81
82 But for parameters with no coarse time defined, some packets may be configured to use the
83 OBT of the packet itself as a referent time for datatime parameters."""
84 return False
85
86
87 def reset_sensing_time(self, sensing_time):
88 """Only applicable to TM packets, see below."""
89 return sensing_time
90
91
92class CCSDSXCCSD(CCSDS):
93 """CCSDS packet with 6-byte XCCSD header. `buffer` can be much bigger than the packet.
94
95 We don't access anything beyond packet end. Call `length()` to find how big the packet actually
96 is.
97
98 If `buff` is None then service and subservice must be set and a synthetic packet is created.
99 `header` is used to pull out the HPTM timestamps if needed.
100 """
101 HEADER_SKIP = 6 # 10
102
103 def payload(self):
104 return self.buff[self.HEADER_SKIP:]
105
106 @property
107 def version_number(self):
108 """Get Version Number (=0)."""
109 return self.buff[0] >> 5
110
111 @property
112 def type_flag(self):
113 """Get Type Flag."""
114 return (self.buff[0] >> 4) & 1
115 # return (ord(self.buff[0]) >> 4) & 1
116
117 @property
118 def dfh_flag(self):
119 """Get DFH Flag."""
120 return (self.buff[0] >> 3) & 1
121 # return (ord(self.buff[0]) >> 3) & 1
122
123 def get_apid(self):
124 """Get Application ID."""
125 return unpack_uint16_be(self.buff, 0) & 0x7ff
126 # return UINT16_BE.unpack(self.buff[:2])[0] & 0x7ff
127
128 def set_apid(self, apid):
129 """Set Application ID."""
130 assert apid >= 0 and apid <= 16383
131 exist = unpack_uint16_be(self.buff, 0)
132 exist = exist & 0xf800
133 exist = exist | apid
134 pack_uint16_be(self.buff, 0, exist)
135
136 apid = property(get_apid, set_apid)
137
138 @property
139 def pid(self):
140 """Process ID, stored in top 7 bits of APID."""
141 return self.apid >> 4
142
143 @property
144 def cat(self):
145 """CATegory, stored in bottom 4 bits of APID."""
146 return self.apid & 0xf
147
148 @property
149 def grouping_flags(self):
150 """Get Sequence Grouping Flags."""
151 return self.buff[2] >> 6
152 # return ord(self.buff[2]) >> 6
153
154 @property
155 def sequence_count(self):
156 """Get Sequence Count."""
157 return unpack_uint16_be(self.buff, 2) & 0x3fff
158
159 @property
160 def dflength(self):
161 """Retrieve the Data Field Length."""
162 try:
163 return unpack_uint16_be(self.buff, 4)
164 except IndexError:
165 raise CCSDSBadHeader('dflength', len(self.buff), 8)
166
167 # return int.from_bytes(self.buff, byteorder=BYTEORDER)
168
169 def get_length(self):
170 """Get packet length in bytes computed from the Packet DF Length field + 7."""
171 return self.dflength + DFLENGTH_OFFSET
172 # return unpack_uint16_be(self.buff, 4) + 7
173
174 def set_length(self, length):
175 """Set Packet Data Field Length to `length` - 7. This does not adjust the actual
176 buffer length."""
177 assert length >= 7
178 pack_uint16_be(self.buff, 4, length - DFLENGTH_OFFSET)
179
180 length = property(get_length, set_length)
181
182 @property
183 def obt_single(self):
184 return None
185
186 def write(self, handle):
187 """Store the (possibly modified) CCSDS packet to `handle` as a raw packet,no header."""
188
189 length = self.length
190 handle.write(self.buff[:length])
191 return length
192
193 def show_header(self, target=sys.stdout, indent=''):
194 """Display basic packet header information to stdout."""
195 target.write("""{i}Version: {version}
196{i}Type: {type}
197{i}DFH flag: {dfh}
198{i}APID: {apid} ({pid}/{cat})
199{i}Group flags: {group}
200{i}Sequence count: {count}
201{i}Length: {dflength} ({length})
202""".format(
203 i=indent,
204 version=self.version_number,
205 type=self.type_flag,
206 dfh=self.dfh_flag,
207 apid=self.apid,
208 pid=self.pid,
209 cat=self.cat,
210 group=self.grouping_flags,
211 count=self.sequence_count,
212 length=self.length,
213 dflength=self.dflength,
214))
215
216
217class CCSDSSTDXPUSS(CCSDSXCCSD):
218 """6-byte CCSDS header + 4-byte PUS header."""
219
220 HEADER_SKIP = 10
221 def payload(self):
222 return self.buff[self.HEADER_SKIP:]
223 # CCSDS + 4-byte PUS header
224
225 def datetime_params_use_reference_time(self):
226 """Specify source of referent time values for relative datetime parameters in this packet.
227
228 For parameters with a coarse time defined, we use a standard fixed reference time.
229
230 But for parameters with no coarse time defined, some packets may be configured to use the
231 OBT of the packet itself as a referent time for datatime parameters."""
232
233 return False
234
235 def get_service(self):
236 """Get Service Type."""
237 # S3 APID = 29 is HPTM packet with special format
238 # return self.buff[7]
239 try:
240 return self.buff[7] if self.apid != APID_HPTM else SERVICE_HPTM
241 except IndexError:
242 raise CCSDSBadHeader('service', len(self.buff), 7)
243
244 def set_service(self, service):
245 """Set Service value."""
246 assert 0 <= service and service < 256
247 self.buff[7] = service
248 # self.buff[7] = chr(service)
249
250 service = property(get_service, set_service)
251
252 def get_subservice(self):
253 """Get Subservice Type."""
254 # S3 APID = 29 is HPTM packet with special format
255 if self.apid == APID_HPTM:
256 return SUBSERVICE_HPTM
257
258 try:
259 return self.buff[8]
260 except IndexError as e:
261 raise CCSDSBadHeader('no subservice', len(self.buff), 9)
262
263 def set_subservice(self, subservice):
264 """Set Subservice value."""
265 assert 0 <= subservice and subservice < 256
266 self.buff[8] = subservice
267 # self.buff[8] = chr(subservice)
268
269 subservice = property(get_subservice, set_subservice)
270
271 def checksum(self):
272 """Compute correct checksum for this packet. Not Applicable for TC Packet"""
273 return None
274
275 @property
276 def destination_id(self):
277 """Get Destination ID."""
278 if len(self.buff) > 9:
279 # some CADU packets do not have destination_id set
280 return int(str(self.buff[9]))
281
282 return None
283 #return ord(self.buff[9])
284
285 def show_header(self, target=sys.stdout, indent=''):
286 """Display basic packet header information to stdout."""
287 super(CCSDSSTDXPUSS, self).show_header(target, indent)
288 target.write("""{i}Service: {stype}
289{i}Subservice: {sstype}
290""".format(i=indent,
291 stype=self.service,
292 sstype=self.subservice))
293
294class CCSDSTM(CCSDSSTDXPUSS):
295 # Standard 20+ byte CCSDS packet with CCSDS header, PUS header, OBT, time status and PEC
296 # 0: Primary Header (6)
297 # 6: PUS header (4)
298 # 10: OBT (7)
299 # 17: Time status (1)
300 # 18+: PEC (2)
301
302 # See comments in products/pus/ccsds.py param1() function. These two items need to be
303 # removed and made non-static. This is not the way to pass values around
304 packet_identifier = None
305 sid = None
306
307 def datetime_params_use_reference_time(self):
308 """Specify source of referent time values for relative datetime parameters in this packet.
309
310 For parameters with a coarse time defined, we use a standard fixed reference time.
311
312 But for parameters with no coarse time defined, some packets may be configured to use the
313 OBT of the packet itself as a referent time for datatime parameters."""
314 return self.dfh_flag == 1
315
316 @property
317 def obt(self):
318 """Retrieve OBT as a tuple of (seconds, 1/2^16 seconds, 1/2^24 seconds)."""
319 # APID = 29 is HPTM packet with special format - return OBT = 0
320 # Check with Mike, this modified code is correct for CORTEX data,
321 # may also be applicable to EDDS data
322 # Update: surely use DFH flag instead?
323 if self.apid == APID_HPTM:
324 s = unpack_uint32_be(self.buff, 16-2) # See S3-ID-TAF-SC-00438, Table 7-5 HPTM packet Format
325 return s, 0, 0
326 ## return (0, 0, 0)
327
328 if len(self.buff) < 17:
329 # time status packet
330 return None
331
332 s = unpack_uint32_be(self.buff, 10) # s
333 t = unpack_uint16_be(self.buff, 14) # 1/2^16s
334 u = self.buff[16] # 1/2^24s
335 return s, t, u
336
337 @property
338 def obt_single(self):
339 """Retrieve OBT as a single 56 bit counter."""
340 s, t, u = self.obt
341 return (s * 1 << 24) + (t * (1 << 8)) + u
342
343 @property
344 def obt_fractional(self):
345 """Retrieve OBT time as a floating point value with seconds and parts of seconds."""
346 if self.obt is None:
347 return None
348
349 s, t, u = self.obt
350 return s + (t/(2**16)) + (u/(2**24))
351
352 def has_pec(self):
353 if self.dfh_flag == 1:
354 return self.dfh_flag == 1 if self.apid != APID_HPTM else False
355 else:
356 return False
357
358 def checksum(self):
359 """Compute correct checksum for this packet."""
360 return self.crc_bitwise()
361
362 def crc_bitwise(self):
363 """Compute the cyclic redundancy checksum.
364
365 Adapted from ECSS-E-ST-70-41C Section B.1 page 617.
366
367 This is the slow version. See metopsgizer source for faster implementations.
368 """
369 # this is the super-slow implementation
370 result = 0xffff
371 for b in self.buff[0:self.length - 2]:
372 for _ in range(8):
373 if ((b & 0x80) ^ ((result & 0x8000) >> 8)) != 0:
374 result = ((result << 1) ^ 0x1021) & 0xffff
375
376 else:
377 result = (result << 1) & 0xffff
378
379 b <<= 1
380
381 return result
382
383 @property
384 def time_status(self):
385 """Get Time Status value."""
386 if len(self.buff) < 17:
387 # raise ValueError('Attempt to read time_status from CCSDS packet of length {l}'.format(
388 # l=len(self.buff)))
389 return None
390
391 # S3, APID = 29 is HPTM packet with special format - return time status of 1
392 return self.buff[17] if self.apid != APID_HPTM else 1
393
394 @property
395 def pec(self):
396 """Get Packet Error Code."""
397 if not self.has_pec():
398 return None
399
400 pos = self.length - 2
401 # logger.debug('Reading PEC from {pos}'.format(pos=pos))
402 return unpack_uint16_be(self.buff, pos)
403
404 @property
405 def param1(self):
406 """Get Param1 value, or None if not defined for this packet."""
407 if CCSDSTM.packet_identifier is None:
408 # This is bad design and we shouldn't be altering a static member of CCSDSTM
409 # here. It should have been removed with the change to multi-satellite
410 # PUS support
411 # !!!!! todo, see previous comment. Still needs fixing. Problem is even worse now
412 # as we're passing a SID around. This is not good enough
413 CCSDSTM.packet_identifier = PacketCriteria(
414 CCSDSTM.sid.db_dir(settings.PARAM1_PARAM2_INFO_FILENAME))
415
416 param_source = CCSDSTM.packet_identifier.get_criteria(
417 self.service, self.subservice, self.apid)
418
419 if param_source is None:
420 # None would be more accurate, but SRDB uses 0 to denote no param1
421 return 0
422
423 offset = param_source.param1_offset
424 width = param_source.param1_width
425
426 if offset is None:
427 return 0
428
429 if offset+(width//8) > self.length:
430 raise CCSDSBadHeader(
431 'Manual param1 of apid {apid} service {service} subservice {subservice}'.format(
432 apid=self.apid, service=self.service, subservice=self.subservice),
433 self.length,
434 offset+(width//8))
435
436 if width == 8:
437 return self.buff[offset]
438
439 elif width == 16:
440 return unpack_uint16_be(self.buff, offset)
441
442 elif width == 22:
443 # we assume that we take the 22 MSB
444 return unpack_uint22_be(self.buff, offset)
445
446 elif width == 24:
447 return unpack_uint24_be(self.buff, offset)
448
449 elif width == 32:
450 return unpack_uint32_be(self.buff, offset)
451
452 else:
453 raise ConfigError('Cannot read param1 with width {w}'.format(w=width))
454
455 @property
456 def param2(self):
457 """Get Param2 value, or None if not defined for this packet."""
458 param_source = CCSDSTM.packet_identifier.get_criteria(
459 self.service, self.subservice, self.apid)
460
461 if param_source is None:
462 return 0
463
464 offset = param_source.param2_offset
465 width = param_source.param2_width
466
467 if offset is None:
468 return 0
469
470 if offset + width//8 > self.length:
471 raise ValueError('Requested param2 offset of {o} is greater than packet length {l} in apid {a}|{s}|{ss}'.format(
472 o=offset, l=self.length, a=self.apid, s=self.service, ss=self.subservice))
473
474 if width == 8:
475 return self.buff[offset]
476
477 elif width == 16:
478 return unpack_uint16_be(self.buff, offset)
479
480 elif width == 22:
481 # we assume that we take the 22 MSB
482 return unpack_uint22_be(self.buff, offset)
483
484 elif width == 24:
485 return unpack_uint24_be(self.buff, offset)
486
487 elif width == 32:
488 return unpack_uint32_be(self.buff, offset)
489
490 else:
491 raise ConfigError('Cannot read param2 with width {w}'.format(w=width))
492
493 def criteria(self):
494 return self.service, self.subservice, self.apid, self.param1, self.param2
495
496 def show_header(self, target=sys.stdout, indent=''):
497 """Display basic packet header information to stdout."""
498 super(CCSDSTM, self).show_header(target, indent)
499 target.write("""{i}OBT: {obt}
500{i}Timestatus: {timestatus}
501{i}Param1: {param1}
502{i}Param2: {param2}
503""".format(i=indent,
504 timestatus=self.time_status,
505 obt=('/'.join(str(o) for o in self.obt) if self.obt is not None else 'none'),
506 param1=self.param1,
507 param2=self.param2))
508
509 def reset_sensing_time(self, sensing_time):
510 """Reset Sensing Time.
511 # From R Letor email 05-05-20
512 # it is to be noted that only for packets of the following type/sub-types: TM(191,26), TM(191,27), and TM(191,28),
513 # parameter COLLECTION_DATE should be used instead of the TM packet OBT as reference time for the timing of the
514 # super-commutated samples.
515 # For super-commutated parameters from any other TM packets, the packet OBT shall be used as reference time.
516 # A special rule shall therefore be put in place for the timing of the super-commutated parameters of
517 # PUS type 191, sub-types 26, 27 and 28 (DIAG, HRDM, and DHRD TM respectively).
518 # In that case, COLLECTION_DATE is a CUC time encoded on 4+3Bytes, starting from Byte 22 (counting up from 0 at
519 # start of the CCSDS TM packet). It is easier to address it which a fixed position rather than by its parameter
520 # name or position as name can vary and position (e.g calling it '2nd parameter') can also change if
521 # some intermediate overlapping parameters are defined in the MIB.
522 """
523 if self.dfh_flag == 0:
524 # not applicable
525 return sensing_time
526
527 if SID.special_HR_packets is not None and\
528 self.service in SID.special_HR_packets.hr_service and\
529 self.subservice in SID.special_HR_packets.hr_subservice:
530
531 # reset sensing time based on description above
532 byte_pos = SID.special_HR_packets.hr_time_location
533
534 # time component lengths in bytes
535 coarse_time_len = 4
536 fine_time_len = 3
537 length = coarse_time_len + fine_time_len
538
539 # convert datetime buffer to HEX Strings
540 raw_coarse = self.buff[byte_pos : byte_pos+coarse_time_len].hex()
541 raw_fine = self.buff[byte_pos+coarse_time_len : byte_pos+length].hex()
542
543 # convert to seconds:
544 # first seconds
545 coarse = int(raw_coarse, 16)
546
547 # fraction of seconds
548 fine_prop=int(raw_fine, 16) / float(2**(fine_time_len*8))
549
550 seconds = coarse + fine_prop
551
552 # get original packet sensing time
553 utc_sensing_time = sensing_time
554
555 # get reference OBT
556 ref_obt = self.obt
557 ref_obt_s = ref_obt[0]
558 ref_obt_us = ((ref_obt[1] << 8) + ref_obt[2]) * (1e6 / (1 << 24))
559
560 # MTG - Use packet sensing time and packet header OBT as reference.
561 # So, param value is Ref time + param_OBT - header_OBT
562 reset_sensing_time = utc_sensing_time + \
563 timedelta(seconds=seconds) - \
564 timedelta(seconds=ref_obt_s, microseconds=ref_obt_us)
565
566 else:
567 # return unmodified sensing time
568 reset_sensing_time = sensing_time
569
570 return reset_sensing_time
571
572
573class CCSDS_NO_HDR(CCSDS):
574 """CCSDS packet with no defined header. `buffer` can be much bigger than the packet.
575
576 if packet has a NIS header then, for some reason TBC, ccsds header not reliable
577 and sequence_count is ccsds.buff[3]
578 """
579 HEADER_SKIP = 0
580
581 def payload(self):
582 return self.buff[self.HEADER_SKIP:]
583
584 def write(self, handle):
585 """We don't write these packets to file from ccsds_tool --output option."""
586 return 0
587
588 @property
589 def version_number(self):
590 """Get Version Number (=0)."""
591 return 0
592
593 @property
594 def type_flag(self):
595 """Get Type Flag."""
596 return 0
597
598 @property
599 def dfh_flag(self):
600 """Get DFH Flag."""
601 return 0
602
603 @property
604 def apid(self):
605 """Get Application ID - not set reliably."""
606 return
607
608 @property
609 def grouping_flags(self):
610 """Get Sequence Grouping Flags."""
611 return 0
612
613 @property
614 def sequence_count(self):
615 """Get Sequence Count - only applicable to good Time Frame packets."""
616 if len(self.buff) > 3:
617 return self.buff[3]
618 else:
619 return 0
620
621 @property
622 def dflength(self):
623 """Retrieve the Data Field Length."""
624 try:
625 return unpack_uint16_be(self.buff, 4)
626 except IndexError:
627 raise CCSDSBadHeader('dflength', len(self.buff), 8)
628
629 @property
630 def destination_id(self):
631 """Get Destination ID - not set reliably."""
632 return 0
633
634 @property
635 def obt_single(self):
636 """Get OBT time - not set reliably."""
637 return 0
638
639 @property
640 def time_status(self):
641 """Get Time Status value."""
642 return None
643
644 def show_header(self, target=sys.stdout, indent=''):
645 """Display basic packet header information to stdout."""
646 target.write("""{i}Version: {version}
647{i}Type: {type}
648{i}DFH flag: {dfh}
649{i}Group flags: {group}
650{i}Sequence count: {count}
651{i}Length: {dflength}
652""".format(
653 i=indent,
654 version=self.version_number,
655 type=self.type_flag,
656 dfh=self.dfh_flag,
657 group=self.grouping_flags,
658 count=self.sequence_count,
659 dflength=len(self.buff)
660))
661
662
663class CCSDS_NIS_NO_HDR(CCSDS_NO_HDR):
664 """TM ccsds record with NIS header, so with no CCSDS Header.
665 if packet has a NIS header then, for some reason TBC, ccsds header not reliable
666 and sequence_count is ccsds.buff[3]
667 """
668 pass
669
670
671class CCSDSEV(CCSDS):
672 # No CCSDS header Not completed or correct TBD
673
674 packet_identifier = None
675
676 @property
677 def dfh_flag(self):
678 """Get DFH Flag."""
679 return 0
680
681 @property
682 def length(self):
683 """Compute correct checksum for this packet. Not Applicable for EV Packet"""
684 # Rapid_header_len + TDEV_header_Length = 120
685 return len(self.buff) - 120
686
687 @property
688 def apid(self):
689 """Apid for this packet. Not Applicable for EV Packet"""
690 return 0
691
692
693 def checksum(self):
694 """Compute correct checksum for this packet. Not Applicable for EV Packet"""
695 return None
696
697 def criteria(self):
698 # !!! Investigate, document and fix this !!!
699 return 'NA'
700
701 def write(self, handle):
702 """Store the (possibly modified) CCSDS packet to `handle` as a raw packet,no header."""
703
704 length = self.length
705 handle.write(self.buff[:length])
706 return length
707
708
709 def show_header(self, target=sys.stdout, indent=''):
710 """Display basic packet header information to stdout."""
711 target.write("""{i}Event Message:
712""".format(i=indent))
713
714
715# S3-ID-TAF-SC-01754_-_IF1_Packet_Structure_ICD-Iss6
716# LAYOUT = """
717# This diagram shows the MetOp CCSDS layout, the record structure contained in the EDDS .xml file.
718# It does not fully represent S3 CCSDS packets.
719
720# +-----------------------------------------------------+------------------------------------------+
721# | PACKET PRIMARY HEADER | PACKET DATA FIELD |
722# | (6 Bytes) | (Max:65536 Bytes) |
723# +----------------------------+--------------+---------+---------+--------------------------------+
724# | Packet Identification |Packet Seq. |Packet |Secondary| Source Data |
725# | |Control |Length |Header +---------------------------+----+
726# | | | | | User Data |PEC |
727# | (2 bytes) |(2 bytes) |(2 bytes)|(8 bytes)| | |
728# +----------------------------+------+-------+-------------------+---------------------------+----+
729# |Version|Type |Sec. |APID |Seq. |Seq. | |UTC-Time | Ancillary |Application| |
730# |Number |Ind. |Header| |Flags |Counter| |Stamp | Data | Data | |
731# | | |Flag | | | | | +----+----------| | |
732# | | | | | | | | |OBT |Ancillary | | |
733# | | | | | | | | | |Info Field| | |
734# |3 bits |1 bit|1 bit |11 bits|2 bits|14 bits| | | | | | |
735# +-------+-----+------+-------+------+-------+---------+---------+----+----------+-----------+----+
736# | 16 bits | 16 bits | 16 bits | 64 bits | 48 | Variable | Variable | 16 |
737# | | | | |bits| | |bits|
738# +----------------------------+--------------+---------+---------+----+----------+-----------+----+
739# """
740
741# """
742# +--------------------------------------------------------------------------------------------------+
743# | TM Packet (min 20 bytes bytes) |
744# +--------------------------------------------------------------------------------------------------+
745# | TM Packet Header | Packet Data Field |
746# | +----------------------------------------------------------------+
747# | | Data Field Header |Source|Packet |
748# | | |Data |Error |
749# | | | |Control |
750# +---------------------------------+----------------------------------------+------+----------------+
751# |Ver|Typ|DHF|APID |Packet |Pkt|Spare|PUS|Spare|Srv|Srv|Dest|Time|Time | |
752# |Num|Flg|Flg| |Sequence |DF | |Ver| |Typ|Sub|ID | |status| |
753# | | | | |Control |Len| |Num| | |Typ
754
755# | | | |PID|CAT|Group|Src| | |
756# | | | | |Flags|Seq| | |
757# | |Cnt|
758
759
760# """
761
762# We need this due to special format of HPTM packet
763# SERVICE_HPTM = 0
764# SUBSERVICE_HPTM = 0
765# APID_HPTM = 29
766
767
768class CCSDSContainer(ABC):
769 """Represent an object that contains a CCSDS packet, such as a raw byte array or RAPID block."""
770
771 def __init__(self):
772 self.tdev_tc_header = None
773
774 @abstractmethod
775 def packet_def(self):
776 """Return the PacketDef for reading the contained packet."""
777 pass
778
779 # Need some weird support code to make this work. It's not vital though. Just remember
780 # to write sensing_time property on any derived classes
781 # @abstractmethod
782 # @property
783 # def sensing_time(self):
784 # """Read or compute the CCSDS packet sensing time as a `datetime`."""
785 # pass
786
787 # @abstractmethod
788 # @property
789 # def ccsds(self):
790 # """Our container CCSDS packet, or None if we don't."""
791 # pass