#!/usr/bin/perl -w
#
# Library of functions to parse NAL Iridium messages
#
# Mike McCann 16 December 2005
#             11 December 2008  Modified from grabDorado.pl to handle the iceberg tracking modems
#             16 December 2008  Generally working
#             11 February 2009  Tests successful with kml and kmz creation, configured to bounce to base station sbd
# MBARI
#
# $Id: grabBergTrackers.pl 4380 2009-04-02 15:27:36Z mccann $

use POSIX;
use Time::Local;
use MIME::Base64;
use File::Basename;
use File::Copy;
use Getopt::Long;
use POSIX qw(strftime);



my $esecs = time();
my $strDate = POSIX::strftime("%a %b %e %T %Y %Z", localtime($esecs));

my $encodedLine = 0;
my $fileName = '';
my $msg = '';
my $momsnLine = '';
my $timeOfSessionLine = '';
my $unitLocationLine = '';
my $repeatTheBody = '';

#
# Usage note
#
sub usage {
    print <<EOF;

    Usage: parseNAL.pl --decode <attach> [ --verbose --help ]

    Where: 
           <attach>:      File name of attachment contents to
                          be decoded.
           -verbose:      Turn on all debugging output.
           -help:         Print this message.

    Examples:

           # Decode an attachment
           $0 -decode 300034012241360_000118.sbd
lon, lat, alt, date: -121.788033, 36.802950, 12.189, 02/06/2009 22:03:04.0

EOF
} # End usage()


#
# process command line arguments
#
if ( ! GetOptions ( 
                'decode=s' => \$attachFile,
                'verbose' => \$verboseFlag,
                'help' => \$helpFlag ) ) {
        print "Could not parse command line\n";
        usage;
        exit -1;
}

if ( $helpFlag ) {
	usage;
	exit;
}


my $debug = ($verboseFlag) ? 1 : 0;

if ( $attachFile ) {
	print "Opening $attachFile to decode.\n" if $debug;
	open (ATTACH, "<$attachFile") || die "Cannot open $attachFile: $!\n";
	binmode ATTACH;
	read(ATTACH, $b64Output, 37);		# Read in just 37 bytes
	close ATTACH;
	##$b64Output = `cat $attachFile`;	# This works, but just on Unix
	print "Decoding with parseNAL3(" . $b64Output . ")...\n" if $debug;
	##($esecs, $lat, $lon, $alt, $strDate, $course, $speed) = parseNAL4($b64Output);
	($esecs, $lat, $lon, $strDate) = parseNAL3($b64Output);
     	##print "lon, lat, alt, date, course, speed: $lon, $lat, $alt, $strDate, $course, $speed\n";
	print "Epoch Secs   Latitude   Longitude  Date & Time (GMT)\n";
	print "---------- ---------- -----------  ---------------------\n";
	printf "%9d  %10.6f %11.6f %21s\n", $esecs,$lat,$lon,$strDate;
	exit;
}
else {
	print "\n    *** An option of -decode must be specified. ***\n\n";
	usage();
	exit -1;
}




# ----------------------------------------------------------------
# Parse NAL Version 3 GPS binary message based on Scott Jensen's sleuthing

sub parseNAL3 {

	my $SBbuffer = $_[0];
	my @SBdata = unpack("C*", $SBbuffer);

	my $debug = ($verboseFlag) ? 1 : 0;

	#
	# Offsets into the $dataBuffer structure (1 less than Scott's VB code)
	#
	$hourdecade = 2;
	$minutes = 3;
	$seconds = 4;
	$hundreths = 5;
	$monthyear = 6;
	$gpsday = 7;
	$latdeg = 8;
	$latmin = 9;
	$longdeg = 10;
	$longmin = 11;
	$latfracmin1 = 12;
	$latfracmin2 = 13;
	$latlongfrac = 14;
	$longfracmin1 = 15;
	$longfracmin2 = 16;
	$indicators = 29;

	# These indicators are bitbasks 
	$nsindicator = 32;
	$ewindicator = 64;

	#
	# Each charactes (byte) in $dataBuffer is an unsigned int that is 
	# combined/scaled to the parameters as specifiec by the above indices
	#
	$i = 0;
	print "parseNAL3(): items (" . scalar(@SBdata) . " number of items):\n" if $debug;
	# <Header>
	print "<Header>\n" if $debug;
	for ($i = 0; $i <= 1; $i++) {
		# Print out elements to correspond to printed documentation
		print $i + 1 . " $SBdata[$i]\n" if $debug;
	}
	# <GPS_Information>
	$j = 1;
	print "<GPS_Information>\n" if $debug;
	for ($i = 2; $i <= $#SBdata; $i++) {
		# Print out elements to correspond to printed documentation
		print $j . " $SBdata[$i]\n" if $debug;
		$j++;
	}
	foreach my $sb ( @SBdata ) {
		print "$i. $sb\n" if $debug;
		$i++;
	}

	#
	# From Scott Jensen's VB program
	#
	$day = $SBdata[$gpsday];
	$month = int($SBdata[$monthyear]/10);
	$year = $SBdata[$monthyear] - ($month * 10);
	$hour = int($SBdata[$hourdecade]/10);
	$decade = $SBdata[$hourdecade] - ($hour * 10);
	$year = 2000 + $decade + $year;
	$minute = $SBdata[$minutes];
	print "SBdata(seconds) = $SBdata[$seconds]\n" if $debug;	
	print "SBdata(seconds) % 64 = " . $SBdata[$seconds] % 64 . "\n" if $debug;	
	$second = ($SBdata[$seconds] % 64) + $SBdata[$hundreths] / 100;

	$gpsdate = sprintf("%2d/%2d/%4d", $month, $day, $year);
	$gpstime = sprintf("%02d:%02d:%04.2f", $hour, $minute, $second);

	print "gpsdate (mm/dd/yyyy) = $gpsdate\n" if $debug;
	print "gpstime (hr:mn:secs)= $gpstime\n" if $debug;

	$latitudedeg = $SBdata[$latdeg];
	$latitudemin = $SBdata[$latmin];
	$latitudemin = $latitudemin + $SBdata[$latfracmin1]/100;
	$latitudemin = $latitudemin + $SBdata[$latfracmin2]/10000;
	$latitudemin = $latitudemin + int($SBdata[$latlongfrac]/100)/100000;

	$longitudedeg = $SBdata[$longdeg];
	$longitudemin = $SBdata[$longmin];
	$longitudemin = $longitudemin + $SBdata[$longfracmin1]/1000;
	$longitudemin = $longitudemin + $SBdata[$longfracmin2]/100000;
	$longitudemin = $longitudemin + ($SBdata[$latlongfrac] - int($SBdata[$latlongfrac]/10)*10)/10;

	$latNS = (($SBdata[$indicators] & $nsindicator) == 0) ? 'N' : 'S';
	$longEW = (($SBdata[$indicators] & $ewindicator) == 0) ? 'E' : 'W';

	print "Lat (deg mm.mm): $latitudedeg $latitudemin $latNS\n" if $debug;
	print "Long (deg mm.mm): $longitudedeg $longitudemin $longEW\n" if $debug;

	$lat = ($latNS eq 'N') ? $latitudedeg + $latitudemin / 60 : -($latitudedeg + $latitudemin / 60);
	$lat = sprintf("%9.6f", $lat);
	$long = ($longEW eq 'E') ? $longitudedeg + $longitudemin / 60 : -($longitudedeg + $longitudemin / 60);
	$long = sprintf("%10.6f", $long);

	#
	# Use POSIX routines to get unix epoch seconds and a string of the local time
	#
	$esecs = timegm($second, $minute, $hour, $day, $month - 1, $year);
	POSIX::strftime("%a %b %e %T %Y %Z", localtime($esecs));

	$strDate = "$gpsdate $gpstime";

	return $esecs, $lat, $long, $strDate;

} # End parseNAL3()


# ----------------------------------------------------------------
# Parse NAL Version 4 GPS binary message 

sub parseNAL4 {

	my $SBbuffer = $_[0];

	my $debug = ($verboseFlag) ? 1 : 0;

	#
	# To decode use this proprietary information from NAL
	#
	# header (1 byte)
	# This is the number 4.
	# number of points (1 byte)
	# In the 9601-DGS version 2.0.0 this is always the number 1 however this may change in future versions.
	# lng_lat_hdop_value (8 bytes)
	# dLLLMMFFFllmmfffHHHH
	# where:
	# d = latitude direction (0 North, 1 South)
	# l = latitude degrees
	# m = latitude minutes
	# f = latitude fraction minutes
	# L = longitude degrees with direction (<200 East, >200 West)
	# M = longitude minutes
	# F = longitude fraction minutes
	# H = horizontal dilution of precision which is set to always have 4 digits with the last 2 being decimal values.
	# pvv_date_time (8 bytes)
	# sMMDDYYYYHHmmSSTVVVV
	# where:
	# s = vertical velocity sign (1 positive, 0 negative)
	# M = month of year
	# D = day of month
	# Y = year
	# H = hour
	# m = minute of hour
	# S = second
	# T = tenths of seconds (hundredths of second is always a 0 because of time alignment)
	# VVVV = Last 4 digits in vertical velocity
	# alt_gv_course (8 bytes)
	# sCCCCCaAAAAAAAgGGGGG
	# where:
	# s = altitude sign (1 positive, 0 negative)
	# C = course in degrees with always 2 digits behind the decimal
	# a = altitude decimal place
	# A = altitude value
	# g = ground velocity decimal place
	# G = ground velocity value

	#
	# Unpack the bytes and words for Version 4 data
	#
	my @SBdata = unpack("CCQQQSCC*", $SBbuffer);

	#
	# Each charactes (byte) in $dataBuffer is an unsigned int that is 
	# combined/scaled to the parameters as specifiec by the above indices
	#
	$i = 0;
	print "parseNAL4(): items:\n" if $debug;
	foreach my $sb (  @SBdata ) {
		print "$i. $sb " if $debug;
		print "\t" . chr($sb) if ( $sb < 256 && $debug );
		print "\n" if $debug;
		$i++;
	}

	#
	# 1st 8-byte word (unsigned Quad)
	# lng_lat_hdop_value (8 bytes)
	#  d LLL MM FFF ll mm fff HHHH
	#    321 47 289 36 48 179 0139
	print "\nlng_lat_hdop_value (8 bytes):\n" if $debug;
	print "dLLLMMFFFllmmfffHHHH\n" if $debug;
	print sprintf("%020s\n", $SBdata[2]) if $debug;
	sprintf("%020s", $SBdata[2]) =~ /(\d)(\d\d\d)(\d\d)(\d\d\d)(\d\d)(\d\d)(\d\d\d)(\d\d\d\d)/;
	$long_deg = ( $2 < 200 ) ? $2 : $2 - 200;	# Subtract 200 for West longitude 
	our ($d, $LLL, $MM, $FFF, $ll, $mm, $fff, $HHHH) = ($1, $long_deg, $3, $4, $5, $6, $7, $8);

	$long = ($2 >= 200) ? -1 * ($LLL + ($MM + $FFF / 1000) / 60) : $LLL + ($MM + $FFF / 1000) / 60;
	$long = sprintf("%11.6f", $long);

	$lat = ($d) ? -1 * ($ll + ($mm + $fff / 1000) / 60) : $ll + ($mm + $fff / 1000) / 60;
	$lat = sprintf("%9.6f", $lat);

	print "long = $long\n" if $debug;
	print "lat = $lat\n" if $debug;

	#
	# 2nd 8-byte word (unsigned Quad)
	#  pvv_date_time (8 bytes)
	# s MM DD YYYY HH mm SS T VVVV
	#    1 09 2009 19 20 22 0 3000
	print "\npvv_date_time (8 bytes):\n" if $debug;
	print "sMMDDYYYYHHmmSSTVVVV\n" if $debug;
	print sprintf("%020s\n", $SBdata[3]) if $debug;
	sprintf("%020s", $SBdata[3]) =~ /(\d)(\d\d)(\d\d)(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d)(\d\d\d\d)/;
	our ($s, $MMt, $DD, $YYYY, $HH, $mmt, $SS, $T, $VVVV) = ($1, $2, $3, $4, $5, $6, $7, $8);

	$gpsdate = sprintf("%02d/%02d/%4d", $MMt, $DD, $YYYY);
	$gpstime = sprintf("%02d:%02d:%04.1f", $HH, $mmt, $SS + $T /10);

	print "gpsdate (mm/dd/yyyy) = $gpsdate\n" if $debug;
	print "gpstime (hr:mn:secs)= $gpstime\n" if $debug;

	#
	# Use POSIX routines to get unix epoch seconds and a string of the local time
	#
	$esecs = timegm($SS, $mmt, $HH, $DD, $MMt - 1, $YYYY);

	#
	# There are other variables that may be of interest in the 3rd word...
	# alt_gv_course (8 bytes)
        # sCCCCCaAAAAAAAgGGGGG
	#   783021346600100310
	#
	print "\nalt_gv_course (8 bytes):\n" if $debug;
	print "sCCCCCaAAAAAAAgGGGGG\n" if $debug;
	print sprintf("%020s\n", $SBdata[4]) if $debug;
	sprintf("%020s", $SBdata[4]) =~ /(\d)(\d\d\d\d\d)(\d)(\d\d\d\d\d\d\d)(\d)(\d\d\d\d\d)/;
	our ($sa, $CCCCC, $a, $AAAAAAA, $g, $GGGGG) = ($1, $2, $3, $4, $5, $6);

	$altitude = $AAAAAAA / (10 ** (7 - $a));
	$altitude = ($sa) ? $altitude : -$altitude;

	$course = sprintf("%04d", $CCCCC) / 100;
	$speed = $GGGGG / (10 ** (5 - $g));

	#
	# This is of interest:  After all the NAL fields we have our text identifiers
	#


	$strDate = "$gpsdate $gpstime";

	return $esecs, $lat, $long, $altitude, $strDate, $course, $speed;

} # End parseNAL4()

1;

