#!/usr/local/bin/perl
#
# --------------------------------------------------------------------
#  xgps --- FreeBSD GPS-card utility for X11
#  Copyright (C) 1996 by Tatsumi Hosokawa <hosokawa@mt.cs.keio.ac.jp>
# --------------------------------------------------------------------
#
# This utility requires
#
#	o Perl 5.002 or later
#	o pTk-b10 or later
#
# You can install them from FreeBSD ports/packages collections.
#

# -----------------------------------------------------------------
require 5.002;
use Getopt::Long;
use Socket;
use Tk;
use Text::ParseWords;
# -----------------------------------------------------------------

sub fixlng($);
sub fixlat($);
sub drawrad();
sub drawsat($$);
sub statchanged();

my $pi = 3.141592653569;

my $gpsdev = '/dev/cuaa3';
my @dows = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');

my $radius = 60;
my $satrad = 8;

my %sname;
$sname{'A'} = "SV1";
$sname{'B'} = "SV2";
$sname{'C'} = "SV3";
$sname{'D'} = "SV4";
$sname{'E'} = "SV5";
$sname{'F'} = "SV6";
$sname{'G'} = "SV7";
$sname{'H'} = "SV8";
$sname{'I'} = "SV9";
$sname{'J'} = "SV10";
$sname{'K'} = "SV11";
$sname{'L'} = "SV12";
$sname{'M'} = "SV13";
$sname{'N'} = "SV14";
$sname{'O'} = "SV15";
$sname{'P'} = "SV16";
$sname{'Q'} = "SV17";
$sname{'R'} = "SV18";
$sname{'S'} = "SV19";
$sname{'T'} = "SV20";
$sname{'U'} = "SV21";
$sname{'V'} = "SV22";
$sname{'W'} = "SV23";
$sname{'X'} = "SV24";
$sname{'a'} = "SV25";
$sname{'b'} = "SV26";
$sname{'c'} = "SV27";
$sname{'d'} = "SV28";
$sname{'e'} = "SV29";
$sname{'f'} = "SV30";
$sname{'g'} = "SV31";
$sname{'h'} = "SV32";

my %elev;
$elev{'A'} = 0;
$elev{'B'} = 10;
$elev{'C'} = 20;
$elev{'D'} = 30;
$elev{'E'} = 40;
$elev{'F'} = 50;
$elev{'G'} = 60;
$elev{'H'} = 70;
$elev{'I'} = 80;
$elev{'J'} = 90;
$elev{'a'} = 0;
$elev{'b'} = -10;
$elev{'c'} = -20;
$elev{'d'} = -30;
$elev{'e'} = -40;
$elev{'f'} = -50;
$elev{'g'} = -60;
$elev{'h'} = -70;
$elev{'i'} = -80;
$elev{'j'} = -90;

my %drct;
$drct{'A'} = 0;
$drct{'B'} = 10;
$drct{'C'} = 20;
$drct{'D'} = 30;
$drct{'E'} = 40;
$drct{'F'} = 50;
$drct{'G'} = 60;
$drct{'H'} = 70;
$drct{'I'} = 80;
$drct{'J'} = 90;
$drct{'K'} = 100;
$drct{'L'} = 110;
$drct{'M'} = 120;
$drct{'N'} = 130;
$drct{'O'} = 140;
$drct{'P'} = 150;
$drct{'Q'} = 160;
$drct{'R'} = 170;
$drct{'a'} = -10;
$drct{'b'} = -20;
$drct{'c'} = -30;
$drct{'d'} = -40;
$drct{'e'} = -50;
$drct{'f'} = -60;
$drct{'g'} = -70;
$drct{'h'} = -80;
$drct{'i'} = -90;
$drct{'j'} = -100;
$drct{'k'} = -110;
$drct{'l'} = -120;
$drct{'m'} = -130;
$drct{'n'} = -140;
$drct{'o'} = -150;
$drct{'p'} = -160;
$drct{'q'} = -170;
$drct{'r'} = 180;

my %stat;
$stat{'A'} = "SCAN";
$stat{'B'} = "LOCK";
$stat{'C'} = "RDY ";
$stat{'D'} = "HOLD";
$stat{'E'} = "ILL ";
$stat{'F'} = "OK  ";

my %statc;
$statc{'A'} = "red";
$statc{'B'} = "yellow";
$statc{'C'} = "green";
$statc{'D'} = "red";
$statc{'E'} = "black";
$statc{'F'} = "blue";

my @sats;
my @satsx;
my @satsy;

my $ok = 0;

open(GPS, $gpsdev) || die 'Can\'t open $gpsdev';

my $main = new MainWindow;
my $ftim = $main -> Frame -> pack(-side => 'top');
my $vtim = $ftim
	-> Label(	-text	=>	'',
			-width	=>	22,
			-anchor	=>	'w',
			-relief	=>	'sunken')
	-> pack(	-side	=>	'right');
my $ltim = $ftim
	-> Label(	-text	=>	'Time (GMT)', 
			-anchor	=>	'w',
			-width	=>	11)
	-> pack(	-side	=>	'right');
my $flat = $main -> Frame -> pack(-side => 'top');
my $vlat = $flat
	-> Label(	-text	=>	'',
			-width	=>	22,
			-anchor	=>	'w',
			-relief	=>	'sunken')
	-> pack(	-side	=>	'right');
my $llat = $flat
	-> Label(	-text	=>	'Latitude', 
			-anchor	=>	'w',
			-width	=>	11)
	-> pack(	-side	=>	'right');
my $flng = $main -> Frame -> pack(-side => 'top');
my $vlng = $flng
	-> Label(	-text	=>	'',
			-width	=>	22,
			-anchor	=>	'w',
			-relief	=>	'sunken')
	-> pack(	-side	=>	'right');
my $llng = $flng
	-> Label(	-text	=>	'Longtitude', 
			-anchor	=>	'w',
			-width	=>	11)
	-> pack(	-side	=>	'right');
my $falt = $main -> Frame -> pack(-side => 'top');
my $valt = $falt
	-> Label(	-text	=>	'',
			-width	=>	22,
			-anchor	=>	'w',
			-relief	=>	'sunken')
	-> pack(	-side	=>	'right');
my $lalt = $falt
	-> Label(	-text	=>	'Altitude', 
			-anchor	=>	'w',
			-width	=>	11)
	-> pack(	-side	=>	'right');
my $fvel = $main -> Frame -> pack(-side => 'top');
my $vvel = $fvel
	-> Label(	-text	=>	'',
			-width	=>	22,
			-anchor	=>	'w',
			-relief	=>	'sunken')
	-> pack(	-side	=>	'right');
my $lvel = $fvel
	-> Label(	-text	=>	'Velocity', 
			-anchor	=>	'w',
			-width	=>	11)
	-> pack(	-side	=>	'right');
my $fdim = $main -> Frame -> pack(-side => 'top');
my $vdim = $fdim
	-> Label(	-text	=>	'',
			-width	=>	22,
			-anchor	=>	'w',
			-relief	=>	'sunken')
	-> pack(	-side	=>	'right');
my $ldim = $fdim
	-> Label(	-text	=>	'Dimension', 
			-anchor	=>	'w',
			-width	=>	11)
	-> pack(	-side	=>	'right');
my $fcam = $main -> Frame -> pack(-side => 'top');
my $ccam = $main
	-> Canvas(	-width	=>	$radius * 2 + 10,
			-height	=>	$radius * 2 + 10,
			-background =>	'dim gray')
	-> pack(	-side	=>	'top');
drawrad;
$main->fileevent(GPS, 'readable', \&statchanged);
MainLoop;

sub statchanged {
	$gpsdata = <GPS>;
	if ($gpsdata =~ /^SONY\d\d/) {
		$t = substr($gpsdata, 6, 13);
		$year = substr($t, 0, 2);
		$month = substr($t, 2, 2);
		$date = substr($t, 4, 2);
		$dow = $dows[substr($t, 6, 1)];
		$hour = substr($t, 7, 2);
		$min = substr($t, 9, 2);
		$sec = substr($t, 11, 2);
		$lat = fixlat(substr($gpsdata, 19, 8));
		$lng = fixlng(substr($gpsdata, 27, 9));
		$alt = substr($gpsdata, 36, 5);
		$vel = substr($gpsdata, 41, 3);
		$dir = substr($gpsdata, 44, 3);
		$dop = substr($gpsdata, 60, 1);
		$dim = substr($gpsdata, 61, 1);
		if ($ok) {
			$stt = 'OK';
		}
		else {
			$stt = 'NG';
		}
		for ($i = 0; $i < 8; $i++) {
			$sat[$i] = substr($gpsdata, 63 + $i * 5, 5);
		}
		$tim = sprintf("$year/$month/$date($dow) $hour:$min:$sec");
		$vtim -> configure( -text => $tim);
		$vlat -> configure( -text => $lat);
		$vlng -> configure( -text => $lng);
		$valt -> configure( -text => ($alt . 'm'));
		$vvel -> configure( -text => ($vel . 'km/h'));
		if ($dim == 3) {
			$vdim -> configure( -text => "two dimensional");
			$valt -> configure( -foreground => 'dim gray');
			$lalt -> configure( -foreground => 'dim gray');
		}
		elsif ($dim == 4) {
			$vdim -> configure( -text => "three dimensional");
			$valt -> configure( -foreground => 'black');
			$lalt -> configure( -foreground => 'black');
		}
		else {
			$vdim -> configure( -text => "");
		}
		if ($ok) {
			$vlat -> configure( -foreground => 'black');
			$vlng -> configure( -foreground => 'black');
			$vvel -> configure( -foreground => 'black');
			$llat -> configure( -foreground => 'black');
			$llng -> configure( -foreground => 'black');
			$lvel -> configure( -foreground => 'black');
		}
		else {
			$vlat -> configure( -foreground => 'dim gray');
			$vlng -> configure( -foreground => 'dim gray');
			$vvel -> configure( -foreground => 'dim gray');
			$llat -> configure( -foreground => 'dim gray');
			$llng -> configure( -foreground => 'dim gray');
			$lvel -> configure( -foreground => 'dim gray');
		}
		for ($i = 0; $i < 8; $i++) {
			drawsat($sat[$i], $i);
		}
	}
}

sub fixlng {
	my($s) = shift;
	my($dir, $deg, $min, $sec, $ssec) = (substr($s, 0, 1), substr($s, 1, 3), substr($s, 4, 2), substr($s, 6, 2), substr($s, 8, 1));
	sprintf("%s %03dd %02dm %02d.%1ds", $dir, $deg, $min, $sec, $ssec);
}

sub fixlat {
	my($s) = shift;
	my($dir, $deg, $min, $sec, $ssec) = (substr($s, 0, 1), substr($s, 1, 2), substr($s, 3, 2), substr($s, 5, 2), substr($s, 7, 1));
	$ok = ($dir eq 'N' || $dir eq 'S');
	$dir = 'N' if ($dir eq 'n');
	$dir = 'S' if ($dir eq 's');
	sprintf("%s %02dd %02dm %02d.%1ds", $dir, $deg, $min, $sec, $ssec);
}

sub drawrad {
	$ccam	-> create ('oval',
			(5, 5), (5 + $radius * 2, 5 + $radius * 2),
			-width => 2);
	$ccam	-> create ('oval',
			(5 + $radius / 2, 5 + $radius / 2), 
			(5 + $radius * 3 / 2, 5 + $radius * 3 / 2),
			-width => 2);
	$ccam	-> create ('line',
			(5 + $radius, 5), (5 + $radius, 5 + $radius * 2),
			-width => 2);
	$ccam	-> create ('line',
			(5, 5 + $radius), (5 + $radius * 2, 5 + $radius),
			-width => 2);
	my $i;

	foreach $i (0 .. 7) {
		my $tag = sprintf("ov%d", $i);
		$satsx[$i] = 5 + $radius - $satrad;
		$satsy[$i] = 5 + $radius - $satrad;
		$sats[$i] = $ccam -> create ('oval', 
			($satsx[$i], $satsy[$i]),
			($satsx[$i] + $satrad * 2, $satsy[$i] + $satrad * 2),
			-fill => 'white',
			-width => 1,
			-tag => $tag);
	}
}

sub drawsat {
	my($str) = shift;
	my($num) = shift;
	my($name, $elv, $dir);

	if (substr($str, 1, 1) cmp '_') {
		my $tag = sprintf("ov%d", $i);
		my $x = 0;
		my $y = 0;
		my $s;
		my $rad;
		my $ang;

		$name = $sname{substr($str, 0, 1)};
		$elv  = $elev{substr($str, 1, 1)};
		$dir  = $drct{substr($str, 2, 1)};
		$s    = substr($str, 3, 1);
		$st   = $stat{$s};
		$lvl  = ord(substr($str, 4, 1)) - ord('D');
		$rad = (90 - $elv);
		$rad = 95 if ($rad > 90);
		$rad = $radius * $rad / 90;
		$ang = ($dir - 90) * $pi / 180.0;
		$x = int($rad * cos($ang) - $satrad / 2 + $radius);
		$y = int($rad * sin($ang) - $satrad / 2 + $radius);
		$color = $statc{$s};
		$ccam -> move ($tag, $x - $satsx[$num], $y - $satsy[$num]);
		$ccam -> itemconfigure ($tag, -fill => $color);
		$satsx[$num] = $x;
		$satsy[$num] = $y;
	}
}
