OB.DAAC Logo
NASA Logo
Ocean Color Science Software

ocssw V2022
hktgen_pace.py
Go to the documentation of this file.
1 #! /usr/bin/env python3
2 
3 import sys
4 import numpy as np
5 import datetime
6 import os.path
7 import argparse
8 import netCDF4
9 
10 from tlm.PacketUtils import *
11 from tlm.pace.APID108 import *
12 from tlm.pace.APID128 import *
13 from tlm.pace.APID198 import *
14 from tlm.timestamp import *
15 
16 ignored_apids = [
17  636, # OCI Ancillary packet
18  700, # OCI science packet with spin number but no time field
19  720, # OCI SWIR science packet
20  751, # HARP science packet
21  848, # SPEX science packet
22 ]
23 
24 max_SC_packet = 2048
25 max_OCI_packet = 1618
26 max_HARP2_packet = 40
27 max_SPEXone_packet = 298
28 
29 def main():
30 
31  # Read command line options
32  parser = argparse.ArgumentParser(
33  formatter_class=argparse.RawTextHelpFormatter,
34  description='Convert S-band housekeeping telemetry to NetCDF4'
35  )
36  parser.add_argument('ifile', nargs='?', type=str,
37  help='path to S-band telemetry (HSK file) OR list of input files, one per line, in chronological order')
38  parser.add_argument("-o", "--output", metavar="ofile", dest="ofile",
39  help="output NetCDF4 file; defaults to PACE.yyyymmddThhmmss.HKT.nc")
40  parser.add_argument('--verbose', '-v', action='store_true',
41  default=False, help="print status messages")
42 
43  args = parser.parse_args()
44 
45  if args.ifile is None:
46  parser.print_help()
47  sys.exit(1)
48 
49  # Is input file tlm or list of files?
50  filelist = []
51  infile = os.path.expandvars(args.ifile)
52 
53  with open(infile, mode='rt') as flist: # try to read as list of files
54  try:
55  input_list = True
56  for ifile in flist:
57  f = os.path.expandvars(ifile.rstrip())
58  if os.path.isfile(f):
59  filelist.append(f)
60  except UnicodeDecodeError:
61  input_list = False # contains invalid byte - infile is binary
62 
63  if not len(filelist): # if that didn't work, it's a single HSK file
64  filelist.append(infile)
65 
66  # Initialize record and packet lists
67  att_recordlist = []
68  orb_recordlist = []
69  tilt_recordlist = []
70  alltimes = []
71  SC_HKT_packets = []
72  OCI_HKT_packets = []
73  HARP2_HKT_packets = []
74  SPEXone_HKT_packets = []
75  oversize_packets = bytearray()
76 
77  # Step through all input files
78  for filename in filelist:
79  print('reading: ',filename)
80 
81  try:
82  ifile = open(filename, mode='rb')
83  except BaseException:
84  print("Cannot open file \"%s\": exiting." % filename)
85  return 100
86 
87  # Read any file header(s)
88  filehdr = readFileHeader(ifile)
89  if filehdr:
90  if args.verbose:
91  print(filehdr)
92  print()
93 
94  # Is it the right kind of file?
95  desired = (filehdr['subtype'] == 101 and
96  filehdr['length'] == 64 and
97  filehdr['SCID'] == b'PACE' and
98  filehdr['processorid'] in (1,2,30) )
99  if not desired:
100  return 110
101 
102  # Now read CCSDS packets
103  while True:
104 
105  # read header and data
106  header, data, rawhdr = readPacket(ifile)
107  if header is None:
108  break # EOF
109 
110  # check for invalid header
111  if header['length'] > 16378:
112  print('Invalid CCSDS packet header:', rawhdr)
113  continue
114 
115  # check for invalid timestamp
116  if (header['secondary'] == 1
117  and data[0] > 112 # after 2017-07-18T05:49:15Z
118  and data[0] < 192): # before 2060-01-28T16:50:35Z
119  ts = readTimestamp(data)
120  alltimes.append(ts)
121  header['timestamp'] = ts
122  # keep going to save packet
123 
124  if args.verbose:
125  print(header)
126 
127  if header['APID'] not in ignored_apids:
128  tmpDict = {}
129  packet = rawhdr+data
130 
131  if header['APID'] < 550: # Spacecraft packet
132 
133  if header['APID'] == 108: # Attitude
134  myDict = APID108(data)
135  keys = ['timestamp', 'Est_Time', 'q_EciToBrf_Est', 'w_EciToBrf_Brf_Est',]
136  for k in keys:
137  tmpDict[k] = myDict[k]
138  att_recordlist.append(tmpDict)
139 
140  elif header['APID'] == 128: # Ephemeris
141  myDict = APID128(data)
142  keys = ['timestamp', 'FSWTime', 'scPosJ2000', 'scVelJ2000','DCM_ecef2eci',]
143  for k in keys:
144  tmpDict[k] = myDict[k]
145  orb_recordlist.append(tmpDict)
146 
147  elif header['APID'] == 198: # Tilt
148  myDict = APID198(data)
149  keys = ['timestamp', 'TILT_ENCPOS',]
150  for k in keys:
151  tmpDict[k] = myDict[k]
152  tilt_recordlist.append(tmpDict)
153 
154  else: # not yet implemented or don't care
155  pass
156 
157  # append rawhdr, data to sc_packets
158  if len(packet) > max_SC_packet:
159  oversize_packets += packet
160  else:
161  packet = pad_packet(packet, max_SC_packet)
162  SC_HKT_packets.append(packet)
163 
164  # endif (Spacecraft packet)
165 
166  elif header['APID'] < 750: # OCI packet
167  if len(packet) > max_OCI_packet:
168  oversize_packets += packet
169  else:
170  packet = pad_packet(packet, max_OCI_packet)
171  OCI_HKT_packets.append(packet)
172 
173  elif header['APID'] < 800: # HARP2 telemetry packet
174  if len(packet) > max_HARP2_packet:
175  oversize_packets += packet
176  else:
177  packet = pad_packet(packet, max_HARP2_packet)
178  HARP2_HKT_packets.append(packet)
179 
180  elif header['APID'] < 850: # SPEXone telemetry packet
181  if len(packet) > max_SPEXone_packet:
182  oversize_packets += packet
183  else:
184  packet = pad_packet(packet, max_SPEXone_packet)
185  SPEXone_HKT_packets.append(packet)
186 
187  else: # APID >= 850
188  pass
189 
190  # endif (not in ignored_apids)
191 
192  # close input file
193  ifile.close()
194 
195  # endfor filename in filelist
196 
197 
198  # calculate orbit parameters
199  if len(orb_recordlist) > 0:
200  orbitparams = derive_orbitparams(orb_recordlist)
201 
202  # get start and end times
203  if len(alltimes) == 0:
204  print('No input packets with valid times')
205  return 110
206 
207  stime = tai58_as_datetime(alltimes[0])
208  etime = tai58_as_datetime(alltimes[-1])
209  daystart = stime.replace(hour=0, minute=0, second=0, microsecond=0)
210 
211  # construct product name
212  prod_name = stime.strftime('PACE.%Y%m%dT%H%M%S.HKT.nc')
213  if args.ofile is None:
214  args.ofile = prod_name
215 
216  # create new netcdf file
217  try:
218  ofile = netCDF4.Dataset(args.ofile, 'w')
219  except BaseException:
220  print("Cannot write file \"%s\": exiting." % args.ofile)
221  return 1
222 
223  # define dimensions
224  ofile.createDimension('vector_elements', 3)
225  ofile.createDimension('quaternion_elements', 4)
226 
227  # write raw housekeeping data to file
228  group = ofile.createGroup('housekeeping_data')
229 
230  if len(SC_HKT_packets) > 0:
231  ofile.createDimension('SC_hkt_pkts', None)
232  ofile.createDimension('max_SC_packet', max_SC_packet)
233  var = group.createVariable('SC_HKT_packets', 'u1', ('SC_hkt_pkts', 'max_SC_packet'))
234  var.long_name = "Spacecraft housekeeping telemetry packets"
235  var[:] = SC_HKT_packets
236 
237  if len(OCI_HKT_packets) > 0:
238  ofile.createDimension('OCI_hkt_pkts', None)
239  ofile.createDimension('max_OCI_packet', max_OCI_packet)
240  var = group.createVariable('OCI_HKT_packets', 'u1', ('OCI_hkt_pkts', 'max_OCI_packet'))
241  var.long_name = "OCI housekeeping telemetry packets"
242  var[:] = OCI_HKT_packets
243 
244  if len(HARP2_HKT_packets) > 0:
245  ofile.createDimension('HARP2_hkt_pkts', None)
246  ofile.createDimension('max_HARP2_packet', max_HARP2_packet)
247  var = group.createVariable('HARP2_HKT_packets', 'u1', ('HARP2_hkt_pkts', 'max_HARP2_packet'))
248  var.long_name = "HARP2 housekeeping telemetry packets"
249  var[:] = HARP2_HKT_packets
250 
251  if len(SPEXone_HKT_packets) > 0:
252  ofile.createDimension('SPEXone_hkt_pkts', None)
253  ofile.createDimension('max_SPEXone_packet', max_SPEXone_packet)
254  var = group.createVariable('SPEXone_HKT_packets', 'u1', ('SPEXone_hkt_pkts', 'max_SPEXone_packet'))
255  var.long_name = "SPEXone housekeeping telemetry packets"
256  var[:] = SPEXone_HKT_packets
257 
258  if len(oversize_packets) > 0:
259  ofile.createDimension('os_pkts', None)
260  var = group.createVariable('oversize_packets', 'u1', ('os_pkts'))
261  var.long_name = "Buffer for packets exceeding maximum size"
262  var[:] = oversize_packets
263 
264  # write navigation data to file
265  group = ofile.createGroup('navigation_data')
266 
267  if len(att_recordlist) > 0: # ATTITUDE
268  ofile.createDimension('att_records', None)
269 
270  var = group.createVariable( # att_time
271  'att_time', 'f8', ('att_records'), fill_value=-999.9)
272  var.long_name = "Attitude sample time (seconds of day)"
273  var.valid_min = 0
274  var.valid_max = 86400.999999
275  var.units = "seconds"
276  var[:] = [seconds_since(rec['timestamp'], basetime=daystart) for rec in att_recordlist]
277 
278  var = group.createVariable( # att_quat
279  'att_quat', 'f4', ('att_records', 'quaternion_elements'), fill_value=-999.9)
280  var.long_name = "Attitude quaternions (J2000 to spacecraft)"
281  var.valid_min = -1
282  var.valid_max = 1
283  var.units = "seconds"
284  var[:] = [rec['q_EciToBrf_Est'] for rec in att_recordlist]
285 
286  var = group.createVariable( # att_rate
287  'att_rate', 'f4', ('att_records', 'vector_elements'), fill_value=-999.9)
288  var.long_name = "Attitude angular rates in spacecraft frame"
289  var.valid_min = np.array((-0.004), 'f4')
290  var.valid_max = np.array(( 0.004), 'f4')
291  var.units = "radians/second"
292  var[:] = [rec['w_EciToBrf_Brf_Est'] for rec in att_recordlist]
293 
294  if len(orb_recordlist) > 0: # EPHEMERIS (orbit)
295  ofile.createDimension('orb_records', None)
296 
297  var = group.createVariable( # orb_time
298  'orb_time', 'f8', ('orb_records'), fill_value=-999.9)
299  var.long_name = "Orbit vector time (seconds of day)"
300  var.valid_min = 0
301  var.valid_max = 86400.999999
302  var.units = "seconds"
303  var[:] = [seconds_since(rec['timestamp'], basetime=daystart)
304  for rec in orb_recordlist]
305 
306  var = group.createVariable( # orb_pos
307  'orb_pos', 'f4', ('orb_records', 'vector_elements'), fill_value= -9999999)
308  var.long_name = "Orbit position vectors (ECR)"
309  var.valid_min = -7200000
310  var.valid_max = 7200000
311  var.units = "meters"
312  var[:] = orbitparams['posr']
313 
314  var = group.createVariable( # orb_vel
315  'orb_vel', 'f4', ('orb_records', 'vector_elements'), fill_value= -9999999)
316  var.long_name = "Orbit velocity vectors (ECR)"
317  var.valid_min = -7600
318  var.valid_max = 7600
319  var.units = "meters/second"
320  var[:] = orbitparams['velr']
321 
322  var = group.createVariable( # orb_lon
323  'orb_lon', 'f8', ('orb_records'), fill_value=-999.9)
324  var.long_name = "Orbit longitude (degrees East)"
325  var.valid_min = -180
326  var.valid_max = 180
327  var.units = "degrees"
328  var[:] = orbitparams['lon']
329 
330  var = group.createVariable( # orb_lat
331  'orb_lat', 'f8', ('orb_records'), fill_value=-999.9)
332  var.long_name = "Orbit latitude (degrees North)"
333  var.valid_min = -90
334  var.valid_max = 90
335  var.units = "degrees"
336  var[:] = orbitparams['lat']
337 
338  var = group.createVariable( # orb_alt
339  'orb_alt', 'f8', ('orb_records'), fill_value=-999.9)
340  var.long_name = "Orbit altitude"
341  var.valid_min = 670
342  var.valid_max = 710
343  var.units = "meters"
344  var[:] = orbitparams['alt']
345 
346  if len(tilt_recordlist) > 0: # TILT
347  ofile.createDimension('tilt_records', None)
348 
349  var = group.createVariable( # tilt_time
350  'tilt_time', 'f8', ('tilt_records'), fill_value=-999.9)
351  var.long_name = "Tilt sample time (seconds of day)"
352  var.valid_min = 0
353  var.valid_max = 86400.999999
354  var.units = "seconds"
355  var[:] = [seconds_since(rec['timestamp'], basetime=daystart)
356  for rec in tilt_recordlist]
357 
358  var = group.createVariable( # tilt
359  'tilt', 'f4', ('tilt_records'), fill_value=-999.9)
360  var.long_name = "Tilt angle"
361  var.valid_min = np.array((-20.1), 'f4')
362  var.valid_max = np.array(( 20.1), 'f4')
363  var.units = "degrees"
364  var[:] = [rec['TILT_ENCPOS'] for rec in tilt_recordlist]
365 
366  # write global metadata
367 
368  # static
369  ofile.title = "PACE HKT Data"
370  ofile.instrument = "Observatory"
371  ofile.processing_version = "V1.0"
372  ofile.Conventions = "CF-1.6"
373  ofile.institution = "NASA Goddard Space Flight Center, Ocean Biology Processing Group"
374  ofile.license = "http://science.nasa.gov/earth-science/earth-science-data/data-information-policy/"
375  ofile.naming_authority = "gov.nasa.gsfc.sci.oceancolor"
376  ofile.keywords_vocabulary = "NASA Global Change Master Directory (GCMD) Science Keywords"
377  ofile.stdname_vocabulary = "NetCDF Climate and Forecast (CF) Metadata Convention"
378  ofile.creator_name = "NASA/GSFC"
379  ofile.creator_email = "data@oceancolor.gsfc.nasa.gov"
380  ofile.creator_url = "http://oceancolor.gsfc.nasa.gov"
381  ofile.project = "PACE Project"
382  ofile.publisher_name = "NASA/GSFC"
383  ofile.publisher_email = "data@oceancolor.gsfc.nasa.gov"
384  ofile.publisher_url = "http://oceancolor.gsfc.nasa.gov"
385  ofile.processing_level = "1"
386  ofile.cdm_data_type = ""
387  ofile.CDL_version_date = "2022-04-08"
388  # dynamic
389  ofile.product_name = args.ofile
390  ofile.time_coverage_start = datetime_repr(stime)
391  ofile.time_coverage_end = datetime_repr(etime)
392  ofile.history = ' '.join([v for v in sys.argv])
393  if input_list:
394  ofile.source = ','.join([os.path.basename(f) for f in filelist])
395  ofile.date_created = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
396 
397  # close file
398  ofile.close()
399 
400  return 0
401 
402 if __name__ == '__main__':
403  sys.exit(main())
def main()
Definition: hktgen_pace.py:29
def seconds_since(tai58, basetime=None)
Definition: timestamp.py:28
def derive_orbitparams(orb_recordlist)
Definition: APID128.py:43
def pad_packet(data, length)
Definition: PacketUtils.py:29
def readTimestamp(data)
Definition: timestamp.py:18
def tai58_as_datetime(tai58)
Definition: timestamp.py:23
def datetime_repr(dt)
Definition: timestamp.py:35