######################################################################
# parseDataBits.py
#
# Usage: pareDataBits.py Data.T04
#
# Converts the input to Dat removing records not processed by this
# script and outputs the binary nav/correction data received from 
# the various systems. If the input is .dat it will use the file
# directly. However, the file should only containt record 25 & 26
# dats. If any data records that have a 2-byte length are included
# the script will fail
#
# Copyright Trimble Inc 2017
#
######################################################################
import binascii
import struct
import sys
import subprocess

def GPSNavType(x):
  return {
    0:'LNAV   ',
    1:'L2CNAV ',
    2:'L5CNAV ',
    3:'L1CNAV '
  }[x]

def GALNavType(x):
  return {
    0:'E1B    ',
    1:'E5A    ',
    2:'E5B    ',
    3:'E5A-Alt',  # AltBOC
    4:'E5B-Alt'   # AltBOC
  }[x]

def SBASType(x):  
  return {
      0:'SBAS L1          ',
      1:'SBAS L5          ',
      2:'QZSS L1SAIF      '
  }[x]

def QZSSNavType(x):  
  return {
      0:'L1CA  ',
      1:'L2CNAV',
      2:'L5CNAV',
      3:'L1C   '
  }[x]

def IRNSSNavType(x):
  return {
      0:'L5CA ',
      1:'S1CA '
  }[x]

# T04 & T02 are compressed record formats. Extract just what we need and
# convert to the uncompressed easy to parse .dat format

if(len(sys.argv) == 2):
  filename = sys.argv[1]

  if(filename.lower().endswith('t02') or filename.lower().endswith('t04') ):
    # Extract just the records we want
    print("Extracting records 25 & 26")
    subprocess.check_call("t0x2t0x -i25,26 " + filename + " DataBits.T04",shell=True)
    
    # Now convert to the uncompressed .dat format. Note script assumes
    # 1-byte length records. Some .dat records are 2-bytee so make sure
    # only type 25 and 26 are included
    print("Convert to .dat format")
    subprocess.check_call("t012dat DataBits.T04 DataBits.dat",shell=True)
    filename = "DataBits.dat"
  elif(not filename.lower().endswith('dat')):
    print("unsupported file type")
    exit(2)
else:
  print("usage: python parseDataBits.py Filename.T04")
  exit(1)

with open(filename, 'rb') as file:
  while True:
    ######################################################################
    # Get the 4-byte header
    ######################################################################
    binData = file.read(4)
 
    if(binData):
      data = map(ord,binData)
      if(data[0] == 0x74):
        # This script will only work for single byte length records
        length  = data[1]
        subtype = data[2]
        record  = data[3]

        # length includes the 4 byte header which we've already read
        length = length -4

        if(record == 26):
          ######################################################################
          # Read the data common to all subtypes
          ######################################################################
          time = struct.unpack("<L", file.read(4) )[0] # Unsigned Long
          week = struct.unpack("<H", file.read(2) )[0] # Unsigned short
          PRN = ord(file.read(1)) # PRN / Alm # for GLN

          length = length - 7 # We've read a further 7 bytes

          ######################################################################
          # Read the subtye subtype specific data
          ######################################################################

          if( (subtype == 0) or (subtype == 1) ):
            print("Need obsolete rec 0 / 1 support")
            break
          elif(subtype == 7):
            # GLN decode is different to the rest
            GLNLineNum = ord(file.read(1))
            HWChan = ord(file.read(1))
            Flags1 = ord(file.read(1))
            length = length -3
            if(Flags1 >= 128):
              # If the top bit is set there's anther flag byte
              Flags2 = ord(file.read(1))
              length = length - 1
          else:
            HWChan = ord(file.read(1))
            Flags1 = ord(file.read(1))
            length = length - 2;
            if(Flags1 >= 128):
              # If the top bit is set there's anther flag byte
              Flags2 = ord(file.read(1))
              length = length - 1;

          # Get the data bits
          binData = file.read(length)

          ######################################################################
          # interpret the data and output
          ######################################################################

          # Output format
          #
          # Col 0 = Record (26)
          # Col 1 = Subtype
          # Col 2 = PRN / GLN ALM
          # Col 3 = HW Channel
          # Col 4 = Week seconds
          # Col 5 = Week Num
          # Col 6 = String provides data type
          # Col 7 = CRCPassed (not supported in all messages)
          # Col 8 = Hex data
          
          if(subtype == 6):
            # L1C, L2C & L5 CNAV are before the FEC has been removed so
            # are symbols. Not supported in standard code see subtype 15
            NavType = Flags1 & 0x3
            print("%d %2d %3d %2d %d %d (GPS  %s     )   %s" % (record, subtype, PRN, HWChan, time, week, GPSNavType(NavType), binascii.b2a_hex(binData)))
          elif(subtype == 15):
            # GPS CNAV bits (after the FEC has been removed)
            NavType = Flags1 & 0x0f
            CRCPassed = (Flags1 & 0x10) >> 4
            print("%d %2d %3d %2d %d %d (GPS  %s     ) %d %s" % (record, subtype, PRN, HWChan, time, week, GPSNavType(NavType), CRCPassed, binascii.b2a_hex(binData)))
          
          elif(subtype == 16):
            # QZSS CNAV bits (after the FEC has been removed)
            NavType = Flags1 & 0x0f
            CRCPassed = (Flags1 & 0x10) >> 4
            print("%d %2d %3d %2d %d %d (QZSS %s      ) %d %s" % (record, subtype, PRN, HWChan, time, week, QZSSNavType(NavType), CRCPassed, binascii.b2a_hex(binData)))

          elif(subtype == 9):
            # QZSS Data
            NavType = Flags1 & 0x03
            print("%d %2d %3d %2d %d %d (QZSS %s      )   %s" % (record, subtype, PRN, HWChan, time, week, QZSSNavType(NavType), binascii.b2a_hex(binData)))
            
          elif(subtype == 11):
            # BeiDou
            NavType = Flags1 & 0x03
            print("%d %2d %3d %2d %d %d (BDS  B%d          )   %s" % (record, subtype, PRN, HWChan, time, week, NavType + 1, binascii.b2a_hex(binData)))

          elif(subtype == 13):
            # IRNSS Data
            NavType = Flags1 & 0x03
            print("%d %2d %3d %2d %d %d (IRNSS %s      )   %s" % (record, subtype, PRN, HWChan, time, week, IRNSSNavType(NavType), binascii.b2a_hex(binData)))

          elif( (subtype == 2) or (subtype == 3) ):
            # Galileo
            NavType = Flags1 & 0x0f
            CRCPassed = (Flags1 & 0x10) >> 4
            if(subtype == 2):
              print("%d %2d %3d %2d %d %d (GAL F/NAV %s) %d %s" % (record, subtype, PRN, HWChan, time, week, GALNavType(NavType), CRCPassed, binascii.b2a_hex(binData)))
            else:
              print("%d %2d %3d %2d %d %d (GAL I/NAV %s) %d %s" % (record, subtype, PRN, HWChan, time, week, GALNavType(NavType), CRCPassed, binascii.b2a_hex(binData)))

          elif(subtype == 7):
            # GLN data after RTZ encoding has been removed so no post-amble. 85 bits of data content
            #print("%d %2d (GLN L1CA            ) %d %d %2d %s" % (record, subtype, time, week, PRN, binascii.b2a_hex(binData)))
            print("%d %2d %3d %2d %d %d (GLN L1CA         )   %s" % (record, subtype, PRN, HWChan, time, week, binascii.b2a_hex(binData)))

          else:
            print("%d %2d (UNKNOWN Subtype) %s %2d %2d %s" % (record, subtype, time, week, PRN, binascii.b2a_hex(binData)))
        elif(record == 25):
          # SBAS data - after FEC has been removed
          if(subtype == 2):
            # Note we do not store SBAS type 63 NULL messages
            time = struct.unpack("<L", file.read(4) )[0] # Unsigned Long
            week = struct.unpack("<H", file.read(2) )[0] # Unsigned short
            PRN = ord(file.read(1)) # PRN / Alm # for GLN
            Flags1 = ord(file.read(1))
            NavType = Flags1 & 0x07
            CRCPassed= (~((Flags1 & 64) >> 6)) & 1
            length = length - 8 # We've read a further 7 bytes

            binData = file.read(28)
            if(CRCPassed):
              # Preamble and CRC are not saved to the record, the data
              # has passed CRC
              print("%d %2d %3d    %d %d (%s) %d %s" % (record, subtype, PRN, time, week, SBASType(NavType), CRCPassed, binascii.b2a_hex(binData)))
            else:
              CRCData = file.read(4)
              # Includes the Preamble and CRC when we fail CRC
              print("%d %2d %3d    %d %d (%s) %d %s Preamble/CRC = %s" % (record, subtype, PRN, time, week, SBASType(NavType), CRCPassed, binascii.b2a_hex(binData),binascii.b2a_hex(CRCData)))
          else:
            binData = file.read(length)
      else:
        print("Bad file - missing header")
        break
    else:
      # EOF
      break

