#!/usr/bin/perl

=head1 NAME satctl

satctl - monitor and control mobilsat dish

=head1 SYNOPSIS

 satctl
 satctl search
 satctl stow
 satctl stop
 satctl panel
 satctl getpos > file
 satctl setpos < file
 satctl clearlastpos
 satctl autocompasscal n n
 satctl dishlight on|off
 satctl stats
 satctl move

=head1 DESCRIPTION

Monitor or control a mobilsat dish. If run without any parameters, displays
a constantly updating status display for the dish. Parameters can be used
to initiate a search, stow, and stop the dish, and to display a virtual
front panel.

The getpos and setpos commands can be used to store the current satellite
position and to return to a stored position. The clearlastpos command
clears the stored last satelite position. The autocompasscal command
performs automatic compass calibration using the given degree offset and
number of steps. The dishlight command turns the blue lights off and on.
The stats command displays transmit and receive statistics from the modem.

The move command allows you to manually move a mobilsat sattellite dish.
Use the left and right arrow keys to rotate the dish's azimuth, and the up
and down arrow keys to change its elevation. To change the skew of the dish,
use pageup and pagedown. To change the number of degrees moved by each
keypresses, use the number keys, period, and backspace to edit the stepping
number that appears on screen.

=cut

use SatUtils::D2;
use SatUtils::DW6000;
use strict;
use warnings;

my $s=SatUtils::D2->new;

my @leds=qw(lnb lan gps ready busy stow);
my ($target_elevation, $target_azimuth, $target_skew, $sysstatus, $txstatus, $rxstatus);
my $iter=0;
my $beststrength=0;
my $bestquality=0;

sub gettarget {
	# Avoid expensive lookups if possible. Assume target will not
	# change.
	if (defined $target_elevation && $target_elevation ne 'n/a' &&
	    defined $target_azimuth && $target_azimuth ne 'n/a' &&
	    defined $target_skew && $target_skew ne 'n/a') {
		return
	}
	
	$target_elevation=$s->getvalue("dishel");
	$target_azimuth=$s->getvalue("dishaz");
	$target_skew=$s->getvalue("dishsk");
}

sub waitstatus {
	my $syscondition=shift || ".";
	my $txcondition=shift || ".";
	my $rxcondition=shift || ".";
	showstatus();
	until ($sysstatus =~ /$syscondition/ && $txstatus =~ /$txcondition/ &&
	       $rxstatus =~ /$rxcondition/) {
		showstatus();
	}
}

sub showleds {
	foreach my $led (@leds) {
		my $color=$s->getvalue($led."color");
		if ($color eq '#4FAFFF') {
			print "[".uc($led)."]\t";
		}
		else {
			print "\t";
		}
		
	}
	print "\n";
}

sub normalise {
	my $val=shift;
	if (lc $val eq 'n/a' || $val=~/err:/) {
		$val=0;
	}
	return $val;
}

sub showstatus {
	my $quality=$s->getvalue("signalquality");
	my $strength=$s->getvalue("signalstrength");
	my $dish_elevation=$s->getvalue("elangle");
	my $dish_azimuth=$s->getvalue("azangle");
	my $dish_skew=$s->getvalue("skangle");
	my $bestqualitymarker="";
	my $beststrengthmarker="";
	$txstatus=$s->getvalue("txstatus");
	$rxstatus=$s->getvalue("rxstatus");
	$sysstatus=$s->getvalue("sysstatus");
	my $time=localtime;
	gettarget();
	
	$iter++;
	if (normalise($quality) > $bestquality) {
		$bestquality=normalise($quality);
		$bestqualitymarker="*"
	}
	if (normalise($strength) > $beststrength) {
		$beststrength=normalise($strength);
		$beststrengthmarker="*"
	}
	
	print "[$time] quality: $bestqualitymarker$quality strength: $beststrengthmarker$strength\n";
	print "elev: $dish_elevation [".sprintf("%+.3f", normalise($target_elevation) - normalise($dish_elevation))."] ";
	print "azim: $dish_azimuth [".sprintf("%+.3f", normalise($target_azimuth) - normalise($dish_azimuth))."] ";
	print "skew: $dish_skew [".sprintf("%+.3f", normalise($target_skew) - normalise($dish_skew))."]\n";
	print "TX: $txstatus\nRX: $rxstatus\nstatus: $sysstatus\n";
        
        print "\n";
}

sub summary {
	if ($iter > 1) {
		print "\n";
		print "Summary for $iter iterations:\n";
		print "Best quality: $bestquality\n";
		print "Best strength: $beststrength\n";
	}
}
$SIG{'INT'} = sub { summary(); exit };

if (@ARGV) {
	my $command=shift;
	if ($command eq 'search') {
		$s->sendcommand("findsat");
		waitstatus("operation completed", "Transmitter ready", "Receiver operational");
	}
	elsif ($command eq 'stow') {
		$s->sendcommand("stowdish");
		waitstatus("operation completed", undef, undef);
	}
	elsif ($command eq 'stop') {
		$s->sendcommand("stopdish");
		showstatus();
	}
	elsif ($command eq 'clearlastpos') {
		$s->sendcommand("clearlastpos");
	}
	elsif ($command eq 'autocompasscal') {
		$s->sendcommand("autocompasscal", @ARGV);
		waitstatus("operation completed", undef, undef);
	}
	elsif ($command eq 'panel') {
		showleds() while 1;
	}
	elsif ($command eq 'getpos') {
		print $s->getvalue("elangle")." ".
			$s->getvalue("azangle")." ".
			$s->getvalue("skangle")."\n";
	}
	elsif ($command eq 'setpos') {
		$s->sendcommand("stopdish");
		my $pos=<>;
		chomp $pos;
		my ($el, $az, $sk)=split(" ", $pos, 3);
		if (! length $sk || $sk !~ /^[0-9.]+$/ || $el !~ /^[0-9.]+$/ || $az !~ /^[0-9.]+$/) {
			die "bad position\n";
		}
		$s->sendcommand("moveelang ".($el - $s->getvalue("elangle")));
		waitstatus("operation completed", undef, undef);
		$s->sendcommand("moveskang ".($sk - $s->getvalue("skangle")));
		waitstatus("operation completed", undef, undef);
		$s->sendcommand("moveazang ".($az - $s->getvalue("azangle")));
		waitstatus("operation completed", undef, undef);
	}
	elsif ($command eq 'dishlight') {
		my $set=shift;
		if ($set eq 'on') {
			$s->sendcommand("setvalue dishlight 1");
		}
		elsif ($set eq 'off') {
			$s->sendcommand("setvalue dishlight 0");
		}
		else {
			die "bad light setting\n";
		}
	}
	elsif ($command eq 'move') {
		eval 'use Term::Slang qw(:all)';
		if ($@) {
			die "Cannot load Term::Slang; manual movement interface not available.\n";
		}
		SLtt_get_terminfo();
		SLang_init_tty(-1,0,1);
		SLsig_block_signals();
		SLsmg_init_smg();
		SLsig_unblock_signals();
		SLkp_init();

		my $step=1;
		my $lastup=time;
		$sysstatus=$s->getvalue("sysstatus");
		my $quality=$s->getvalue("signalquality");
		my $strength=$s->getvalue("signalstrength");

		my $display = sub {
			my $time=time;
			if ($time - $lastup > 1) {
				$sysstatus=$s->getvalue("sysstatus");
				$quality=$s->getvalue("signalquality");
				$strength=$s->getvalue("signalstrength");
				$lastup=$time;
			}
			SLsmg_normal_video();
			SLsmg_gotorc(0,0);
			SLsmg_erase_eol();
			SLsmg_write_string("quality: $quality strength: $strength");
			SLsmg_gotorc(1,0);
			SLsmg_erase_eol();
			SLsmg_write_string("status: $sysstatus");
			SLsmg_gotorc(3,0);
			SLsmg_erase_eol();
			SLsmg_write_string("Stepping: $step");
			SLsmg_refresh();
		};
		
		$display->();
		while (1) {
			if (SLang_input_pending(10)) {
				my $key = SLkp_getkey();
				if ($key == 258) { # up
					$s->sendcommand("moveelang -".$step);
				}
				elsif ($key == 257) { # down
					$s->sendcommand("moveelang ".$step);
				}
				elsif ($key == 259) { # left
					$s->sendcommand("moveazang ".$step);
				}
				elsif ($key == 260) { # right
					$s->sendcommand("moveazang -".$step);
				}
				elsif ($key == 261) { # pageup
					$s->sendcommand("moveskang ".$step);
				}
				elsif ($key == 262) { # pagedown
					$s->sendcommand("moveskang -".$step);
				}
				elsif ($key == 272) { # backspace
					$step=~s/.$//;
				}
				elsif ($key == 46) { # period
					$step=~s/\.//;
					$step.=".";
				}
				elsif ($key >=48 && $key <= 57) { # number
					$step.=($key - 48);
				}
			}
			$display->();
		}
	}
	elsif ($command eq 'stats') {
		my $m=SatUtils::DW6000->new;
		while (1) {
			my $time=localtime;
			my %vals=$m->getvalues;
			print "[$time]\n";
			print "Frames received: $vals{'Frames Received'}\n";
			print "Frames with errors: $vals{'Frame Errors: CRC/Bad Key'}\n";
			print "Successful/Failed transmissions: $vals{'Stream Msg-Ackd/Nakd'}\n";
			print "\n";
			sleep 1;
		}
	}
	else {
		die "usage: satctl [search|stow|stop|panel|setpos|getpos|clearlastpos|autocompasscal|dishlight|stats|move]\n";
	}
}
else {
	while (1) {
		showstatus();
	}
}

summary();

=head1 AUTHOR

Joey Hess <joey@kitenet.net>
=cut
