#!/usr/bin/perl

#
# Alkuperäisesti muokattu OH3TR:n Veritornin tarpeisiin
#
#    ---------------
#
# Ohjelma lukemaan Mikon, OH2AUE OH-TNC2:sta Ilmari-2007:n
# (OH2SIX-11) antamia paketteja ja muuntamaan ne APRS-IS:n
# muotoon ja jakeluun..
# 
# Ohjelma ei valttamatta ole hyodyllinen muualla kuin
# Cape Vesijako lennonjohdossa...
#
# Maililukemanne voi poiketa dokumenteissa olevista esimerkeista,
# yms.  Enimmat saadettavat detaljit ovat aivan taman skriptin
# alussa.
#

$debug = 0;
if ($ARGV[0] eq '--debug') {
    shift @ARGV;
    $debug = 1;
}

$aprsuser = $ARGV[0];
if ((!defined($aprsuser))
    or (!($aprsuser =~ m/[A-Z]+[0-9][A-Z]+/))
    ) {
    usage("Sorry sir, must have your HAM-callsign as run parameter!");
}
shift @ARGV;

$asyname = $ARGV[0];
if (!defined($asyname)) {
    usage("Sorry sir, /dev/ttyXX parameter not defined");
}
if (! -c $asyname) {
    usage("Sorry sir, ${asyname} device is not char-special device");
}
if (! -r $asyname) {
    usage("Sorry sir, ${asyname} device is not readable my me");
}
if (! -w $asyname) {
    usage("Sorry sir, ${asyname} device is not writable my me");
}
shift @ARGV;

$asyspeed = 2400;
$asyspeed = $ARGV[0];
if ((!defined($asyspeed))) {
    usage("Sorry sir, tty port speed parameter missing");
}
shift @ARGV;


chdir "/tmp";

#$aprsuser .= sprintf("%05d", rand(100000));
#$aprsuser = substr($aprsuser, 0, 9);


$inputsimulator = 0;
if ($ARGV[0] eq '--simulator') {
    $inputsimulator = 1;
}

#open LOG, ">>", "ilmari.log";
#autoflush LOG 1;

#open ALOG, ">>", "ilmari.aprs";
#autoflush ALOG 1;


@lines = ('','','','','','','');

use POSIX;      # POSIX facilities (for serial port)
use IO;         # Char by char IO
use Term::Cap;  # Terminal capabilities strings
use Math::Trig; # grad2rad()/rad2grad()
use Socket;

autoflush STDOUT 1;

$terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
$terminal->Trequire(qw/cl cm/);
#$terminal->Tgoto('cm', $col, $row, $FH);
#$terminal->Tputs('dl', $count, $FH);
#$terminal->Tpad($string, $count, $FH);

#$CursorVisible = $terminal->Tputs('vs',1); # StandOut cursor
#$CursorInvisible = $terminal->Tputs('vi',1); # Invisible cursor
#$B1 = $terminal->Tputs('md',1); # Start Bold
#$B0 = $terminal->Tputs('me',1); # End Bold (actually any)
#$TermHome = "\e[?5h".$terminal->Tputs('cl',1);

#$termios = new POSIX::Termios;
#$conio   = IO::Handle->new_from_fd(fileno(STDIN),'r')
#    or die "IO::Handle:n avaus STDIN:iin epäonnistui.";


#system "stty","sane";
#system "stty","-icanon","min","1","time","0", "-echo";

socketpair( ASYRDR, ASYWRT, AF_UNIX, SOCK_DGRAM, PF_UNSPEC );

$pid = fork();
if ($pid == 0) { # Child!
    shutdown(ASYWRT, 0); # No more reading for writer
    close ASYRDR;        # and nothing on reader socket..

    $ASY = POSIX::open($asyname,O_RDWR,0)
	or die "ei saa avattua sarjaporttia: ".$asyname;

    die "ei saa avattua sarjaporttia: ".$asyname if ($ASY < 0);


    #$sttycmd='stty sane -echo raw istrip speed 2400 -opost evenp inpck';
    #$sttycmd='stty speed 2400 evenp inpck istrip cread icanon -echo';
    #$sttycmd='stty speed 2400 clocal cread pass8 raw min 1';
    #$sttycmd='stty speed '.$asyspeed.' clocal cread pass8 cooked min 1';
    $sttycmd='stty speed '.$asyspeed.' sane clocal pass8 min 1 time 5 -hupcl ignbrk -echo -ixon -ixoff -icanon';

    system($sttycmd.' > /dev/null < '.$asyname);
    system($sttycmd.' > /dev/null < '.$asyname);
    #system('stty -a < '.$asyname);
    #sleep 1;

    #printf "Sarjaportti auki, kaapelitesti!\n";


    $asyio   = IO::Handle->new_from_fd($ASY,'w+')
	or die "IO::Handle:n avaus sarjaporttiin epäonnistui.";

    $asyio->autoflush(true);
    
    # &asycanon ();

    POSIX::tcflush($ASY, &POSIX::TCIOFLUSH);
    POSIX::tcflow ($ASY, &POSIX::TCION);


    & asyreader ;   # Does not return
}

close ASYWRT;        # Nothing on writer socket
shutdown(ASYRDR, 1); # No writing on reader socket..

$stop_now = 0;

local $APRSIS = APRS::IS->new("rotate.aprs.net:14580", $aprsuser, ".");

if (!defined($APRSIS)) {
    printf "aprsazel: Failed to open APRS-IS socket!\n";
    exit 4;
}

$SIG{'QUIT'} = \&sighandler;
$SIG{'TERM'} = \&sighandler;


while ( &iomux(999) ) { };

kill(1, $pid) if ($pid > 1);


#system "stty","sane";
#printf "%s\n",$CursorVisible;

exit 0;


sub usage {
    local ($msg) = @_;

    printf("ilmari-ohtnc-to-aprsis.pl [--debug] GW-Callsign /dev/ttyS7 2400\n");
    printf("  %s\n", $msg);

    exit 64;
}


sub sighandler {
    $stop_now = 1;
}


#sub asycanon {
#    $termios->getattr($ASY) or die "ei saanut getattr() dataa";
#    $termios->setispeed(&POSIX::B19200);
#    $termios->setospeed(&POSIX::B19200);
#    $termios->setcflag( &POSIX::CS8   | &POSIX::CREAD | # 8N1, Rx on
#			&POSIX::CLOCAL               ); # ignore modem
#    $termios->setiflag( &POSIX::IGNPAR );    # no input parity checking
#    $termios->setoflag( 0 );	# No output-flags
#    $termios->setlflag( &POSIX::ICANON ); # Pick lines..
#
#    $termios->setattr($ASY, &POSIX::TCSANOW); # Set the attribute set
#}


sub asygetline {
    local $c;
    local $line = '';

    if ($inputsimulator) {
	$line = <SIMU>;
	select(undef, undef, undef, 0.01);

    } else {

	while ($asyio->sysread($c,1) && $c ne "\n") {
	    #printf "SYSGETLINE c= 0x%02x = '%s'\n",ord($c),$c;
	    $line .= $c;
	}
    }
    return $line;
}

sub asyreadn {
    local $num = shift;

    local $line = '';
    local $c;

    for (;$num > 0; --$num) {
	$asyio->sysread($c,1);
	$line .= $c;
    }
    $line;
}

sub asyreader {
    $SIG{'PIPE'} = 'DEFAULT'; # "death"

    local $line;

    autoflush STDERR 1;
    autoflush ASYWRT 1;

    # OH2SIX-11>APRS:!6120.99N/02502.44EO233338h/A=088.2,Mm

    if ($inputsimulator) {
	open SIMU, "<", "ilmari.sim";
    }


    local $poscount = 0;

    for(;;) {
	$line = & asygetline;
	chomp $line;chomp $line;


	#printf(LOG "%s\n", $line) if (!$inputsimulator);

	next if (length($line) < 2);

	printf STDERR "ASY1 <-:  %s\n", $line if ($debug);
	
	# fix the APRS position fix message
	# <-- OH2SIX-11>APRS,WIDE3-3:!6121.00N/02502.44EO135623h/A=00109.4m
	# <-- OH2SIX-11>APRS:!6120.99N/02502.43EO035055h/A=091.7,Mm
	# --> OH2SIX-12>APRS,OH2AUE,I:!6120.99N/02502.43EO/A=000289 035055h

	local $out = $line;

	if ($line =~ m%^([^:]*?):(.*)%o) {
	    $out = sprintf("%s,%s,I:%s", $1, $aprsuser, $2);
	}
	#printf STDERR "ASY2: <-  %s\n", $line;
	printf STDERR "ASY ->:  %s\n", $out if ($debug);

	send(ASYWRT, $out, 0);
    }
}



sub iomux {
    local ($timeout) = @_;
    local $rin,$rout;

#printf "IOMUX(%.2f,%d) ",$timeout,$keyinput;

    $rin = '';
    #vec($rin,fileno(STDIN),1) = 1;
    vec($rin,fileno(ASYRDR),1) = 1;
    vec($rin,$APRSIS->fileno,1) = 1;


    return 0 if ($stop_now);


    local($nfound,$timeleft) = select($rout=$rin, undef, undef, $timeout);

    if ($keyinput) {
	printf "%s",$CursorInvisible;
    }

    if ($nfound == 0) {
#	printf "IOMUX-TIMEOUT!";
	return 1;
    }


    # What operator console tells ...
    while (0 && vec($rout,fileno(STDIN),1)) {

	local $line;

	$conio->blocking(0);
	$conio->sysread($line, 1);
	$conio->blocking(1);

	printf("CONread len=%d line='%s'\n", length($line), $line);

	if ($line eq 'X' or $line eq 'x') {
	    return 0;
	}

#	&komentotulkki($line);
	system "stty","-icanon","min","1","time","0";

	last;
    }

    # What OH-TNC tells...

    # OH2SIX-11>APRS:!6120.99N/02502.44EO233338h/A=088.2,Mm

    while (vec($rout,fileno(ASYRDR),1)) {
	local $line = undef;

	# We have a thing to read..
	recv(ASYRDR, $line, 9999, MSG_DONTWAIT);
	last if (!defined $line); # No data.. Boo..
	chomp $line;chomp $line;
	last if (length($line) < 1);
	#last if (!($line =~ m/OH2SIX/o));

	printf "TNC:  %s\n",$line;
	#printf ALOG "%d\t%s\n", time, $line if (!$inputsimulator);

	# Laheta positiopaketit APRS-IS verkkoon
	if (! $inputsimulator ) {
	    $APRSIS->sendline($line);
	}
	last;
    }

    # APRS has something for us..
    while (vec($rin,$APRSIS->fileno,1)) {
	local $line = $APRSIS->getline;

	#if ($line =~ m/OH2SIX/o) {
	#    printf "APRS: %s\n",$line;
	#}

	last;
    }



    return 1;
}


# -------------------------------------------------------------------------


package APRS::IS;

use 5.006;
use strict;
use warnings;

use IO::Socket::INET;
use IO::Select;

sub aprspass {
    my ($a, $h) = (0, 0);
    map($h ^= ord(uc) << ($a^=8),
	pop =~ m/./g);
    return ($h ^ 29666);
}


sub new {
    my $that = shift;
    my $class = ref($that) || $that;
    # my %atts = @_;
    my ($url, $mycall, $target_filter_re) = @_; # Just one arg: APRS-IS URL (host:port)

    # Register the callers package.
    my $self = { caller_pkg => (caller)[0] };

    bless ($self, $class);

    # parse attrs
    $self->{sock} = IO::Socket::INET->new($url);

    if (!defined($self->{sock})) {
        die(__PACKAGE__.": APRS::IS->new(".$url.")  failure: ".$!."\n");
    }


    $self->{select} = IO::Select->new( $self->{sock} );

    $self->{aprsmycall} = uc( $mycall );
    my $mycallbase = $mycall;
    if ($mycallbase =~ m/^([^-]+)-.*/) {
	$mycallbase = $1;
    }
    $self->{aprspass}   = aprspass( $mycallbase );

    # $self->{filterre} = $target_filter_re;

#   printf ( "APRS::IS->new()  mycall='%s'  aprspass=%d   filterre='%s'\n",
#            $self->{aprsmycall}, $self->{aprspass}, $self->{filterre} );


##
##    *  Need to send on initial connect the following logon line:
##      user callsign pass passcode vers appname versionnum rest_of_line
##
##      callsign = login callsign-SSID
##      passcode = login passcode per APRS-IS algorithm, -1 = read-only
##      appname = application name (1 word)
##      versionnum = application version number (no spaces)
##      rest_of_line = server command if connecting to a port that supports commands (see Server Commands)
##
##      (appname and versionnum should not exceed 15 characters)
##
##       
##    * Need to recognize both TCPIP and TCPXX as TCP/IP stations
##    * Need to provide a means to perform the user validation. This can either be a user entered password,
##      or a client program can automatically figure out the password given the callsign.
##      If the later is used, it is the client programmer's responsibility to be certain that non-amateurs
##      are not given registrations that can validate themselves in APRS-IS.
##    * Probably a good idea to perform some feedback about the limitations of TCPIP without a registration number.
##

    $self->{sock}->blocking(1);
    $self->{sock}->printf( "user %s pass %s vers %s\n",
			   $self->{aprsmycall},
			   $self->{aprspass}, # -- but we are read-only !
			   $main::VERSION
    );
    $self->{sock}->flush;

    $self->{sock}->blocking(1);

    # my $discard = $self->getline();

    $self;
}

# -------------------------------------------------------------------------
# Get socket fileno for select..

sub fileno {
    my $self = shift;

    return $self->{sock}->fileno;
}

# -------------------------------------------------------------------------
# Get a line, or wait 1 sec


sub getline {
    my $self = shift;

    my @ready;

    if (@ready = $self->{select}->can_read(1)) { # Wait at most 1.0 seconds

	# We have only one socket...

	return $self->{sock}->getline;

    }

    undef;
}

sub sendline {
    my $self = shift;
    my $line = shift;

    my @ready;

    $self->{sock}->blocking(1);
    $self->{sock}->printf( "%s\n", $line);
    $self->{sock}->flush;

    $self->{sock}->blocking(1);

    undef;
}

# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
