#!/usr/bin/perl

#
#  Kustomoitu OH2MQK:n koti-igate:ksi..
#

@ARGV = ('OH2MQK-1','/dev/ttyUSB1', '19200', '--pk96', '--make-log');


$aprsserver = "rotate.aprs.net:14580";
#$aprsserver = "finland.aprs2.net:14580";

#$kissmode = 1;

#
# 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;


# Monitor output behaviour detail..
$tnc_is_pk96 = 0;
if (defined($ARGV[0]) &&
    ($ARGV[0] eq '--pk96')) {
    $tnc_is_pk96 = 1;
    shift @ARGV;
}
if (defined($ARGV[0]) &&
    ($ARGV[0] eq '--oh-tnc')) {
    $tnc_is_pk96 = 0;
    shift @ARGV;
}

$makelog = 0;
if (defined($ARGV[0]) &&
    ($ARGV[0] eq '--make-log')) {
    $makelog = 1;
    shift @ARGV;
}

chdir "/tmp";

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


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

if ($makelog) {
    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..

    & kissreader if ($kissmode);
    & 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($aprsserver, $aprsuser, ".");

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

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


$when_beacon = time + 5;
while ( &iomux(10) ) { };

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


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

exit 0;


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

    printf("./ilmari-aprs-ohtnc-monitor-to-aprsis.pl [--debug] GW-Callsign /dev/ttyUSB0 19200 [--pk96|--oh-tnc] [--make-log]\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
#}

local $asykissinbuf = '';

sub asygetbufchar {
    if (length($asykissinbuf) < 1) {
	$asyio->blocking(0);
	local $i = $asyio->sysread($asykissinbuf, 1000);
	if ($i < 1) {
	    $asyio->blocking(1);
	    local $i = $asyio->sysread($asykissinbuf, 1);
	    if ($i < 1) {
		$seen_error = 1;
		return undef;
	    }
	}
    }

    # Now we have at least one char.. (or EOF)

    local $c = substr($asykissinbuf, 0, 1);
    $asykissinbuf = substr($asykissinbuf, 1);
    return $c;
}


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

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

    } else {

	while (1) {
	    $i = asygetbufchar();
	    if (!defined($i)) {
		# ERROR! EOF or otherwise..
		$line = '';
		$seen_error = 1;
		$asyio->close();
		return undef;
	    }
	    if ($i ne "\n") {
		#printf "SYSGETLINE c= 0x%02x = '%s'\n",ord($c),$c;
		$line .= $i;
		next;
	    }
	    last;
	}
    }
    return $line;
}

sub asyreadn {
    local $num = shift;

    local $line = '';
    local $c;

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


sub asygetkiss {
    local $c, $cmdbyte;
    local $line = '';
    local $seen_fend = 0;

    # At first wait for FEND...
    while (1) {
	$c = asygetbufchar();
	if (!defined($c)) {
	    # ERROR! EOF or otherwise..
	    $line = '';
	    $seen_error = 1;
	    $asyio->close();
	    return undef;
	}
	# Eat up multiple consequent FENDs..
	if (ord($c) == 0xc0) { # FEND
	    # It is FEND !
	    $seen_fend = 1;
	    next;
	}
	# If not yet seen FEND, spin...
	next if (! $seen_fend);
	# Seen FEND, it should now be byte 0x00 !
	last;
    }

    # Whatever the value, save it..
    $cmdbyte = $c;
    #$line = $cmdbyte;

    while (1) {
	$c = asygetbufchar();
	if (!defined($c)) {
	    # ERROR! EOF or otherwise..
	    $line = '';
	    $seen_error = 1;
	    $asyio->close();
	    return undef;
	}
	# Frame END ?
	if (ord($c) == 0xc0) {
	    last;
	}

	# Are there escape sequences ?
	if (ord($c) == 0xdb) { # FESC
	    # ESC SEQUENCE !
	    $c = asygetbufchar();
	    if (!defined($c)) {
		# ERROR! EOF or otherwise..
		$line = '';
		$seen_error = 1;
		$asyio->close();
		return undef;
	    }
	    if (ord($c) == 0xdc) { # TFEND
		$c = chr(0xc0); # --> FEND
	    } elsif (ord($c) == 0xdd) { # TFESC
		$c = chr(0xdb); # --> FESC
	    } else {
		# FIXME ???? HUH ??  Should not happen!
		next; # Discard this byte..
	    }
	}

	# Append on the accumulating line..
	$line .= $c;

    }

    return $line;
}


sub asyreader {

    $seen_error = 0;
    OUTER: while (1) {

	if ($seen_error != 0) {
	    undef $ASY;
	    $seen_error = 0;
	    sleep(15);  ## Wait 15 sec .. 
	}


	$ASY = POSIX::open($asyname,O_RDWR,0);
	if (!defined($ASY) ||  !$ASY || $ASY < 0) {
		warn "ei saa avattua sarjaporttia: ".$asyname;
		$seen_error = 1;
		undef $ASY;
		next OUTER;
	}


	#$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+');
	if (!defined ($asyio) or !$asyio) {
		warn "IO::Handle:n avaus sarjaporttiin epäonnistui.";
		undef $asyio;
		$seen_error = 1;
		undef $ASY;
		next OUTER;
	}

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

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


	$SIG{'PIPE'} = 'DEFAULT'; # "death" - if caller dies

	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;

	    if (!defined($line)) {
		# D'uh..  some error!  close the line, and re-open..
		$seen_error = 1;
		undef $ASY;
		next OUTER;
	    }

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

	    if ($tnc_is_pk96 &&
		($line =~ m/[^>]*>.*:$/o)) {
		# On monitor mode the AX.25 header comes out on its own line, and data on second line..
		$more = &asygetline;
		if (!defined($more)) {
		    # D'uh..  some error!  close the line, and re-open..
		    $seen_error = 1;
		    undef $ASY;
		    next OUTER;
		}
		chomp $more; chomp $more;
		if (length($more) < 2) {
		    $more = &asygetline;
		    chomp $more; chomp $more;
		}
		$line .= $more;
	    }
	    
	    printf(LOG "%d\t%s\n", time, $line) if (!$inputsimulator && $makelog);


	    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) {
		$axpath    = $1;
		$firstchar = $2;
		$aprsdata  = $3;

		$axpath =~ s/>>/>/og;

		if ($tnc_is_pk96) {
		    # The AX25 path is different from what TNC2 does..
		    local @addrs = split(/>/, $axpath);

		    $axpath = shift @addrs;         #  First addres..
		    $axpath .= ">" . pop   @addrs;  #  Last address

		    foreach $i (@addrs) {           # All the rest in sequence
			$axpath .= "," . $i;
		    }
		}

		next if ($firstchar eq '}');
		$out = sprintf("%s,%s,I:%s%s", $axpath, $aprsuser, $firstchar, $aprsdata);

		next if ($axpath =~ m/RDONLY/io); # Has anywhere any of these..
		next if ($axpath =~ m/RFONLY/io);
		next if ($axpath =~ m/NOGATE/io);
		next if ($axpath =~ m/UIDIGI/io);
		next if ($axpath =~ m/^WIDE/io); # Begins with = is sourced by..
		next if ($axpath =~ m/^RELAY/io);
		next if ($axpath =~ m/^TRACE/io);

	    }
	    #printf STDERR "ASY2: <-  %s\n", $out;
	    printf STDERR "ASY ->:  %s\n", $out if ($debug);
	    
	    send(ASYWRT, $out, 0);
	}
    }
}

# ($tnc2text, $line) = parseax25addrs($line);
sub parseax25aprs {
    local ($line) = @_;
    local $tnc2text = '';
    local $i, $c, $abytes = '';
    local $ll = length($line);

    for ($i = 0; $i < $ll; ++$i) {
	$c = substr($line, 0, 1);      # First byte of the rest of the packet..
	$abytes .= chr(ord($c) >> 1);  # Downshift the address field bytes for further processing
	$line = substr($line, 1);      # rest of the line..
	last if (ord($c) % 2);         # Address end flag
    }


printf STDERR " abytes='%s'  line='%s'\n", $abytes, $line;

    # chop $line; # Drop the Checksum byte..

    chomp $line; # There possibly are newlines at the end of the line...


    # Address bytes are split separate from rest of the data..
    # Now make sure we have sensible amount of data in address..
    return (undef, undef) if (length($abytes) < 14 || length($abytes) > 70);

    # arrays begin with index 0...
    local @addrs = ();
    local $address = '';
    $ll = length($abytes);
    for ($i = 0; $i < $ll; ++$i) {
	$c = substr($abytes, $i, 1);
	if (($i % 7) != 6) {
	    # Not the SSID et.al. flags byte.
	    # Append it on address array if it is callsign character (not space or whatnot)
	    $address .= $c if ($c =~ m/[A-Z0-9]/o); 
	} else {
	    # This is the SSID et.al. flags byte..
	    # note: the bits are downshifted by one!
	    local $ssid = ord($c) % 16;
	    $address .= sprintf("-%d", $ssid)  	if ($ssid > 0);
	    # Add "has been repeated" '*' chars on digipeat paths addresses, if set
	    if ($i > 14) {
		$address .= "*"  if (ord($c) & 64); # Top-most bit in SSID byte, now shifted one down.
	    }
	    push @addrs, $address;
	    $address = '';
	}
    }

    # Now process the @addrs array..
    $tnc2text = $addrs[1] . ">" . $addrs[0];
    shift @addrs;
    shift @addrs;

    foreach $i (@addrs) {
	$tnc2text .= "," . $i;
    };
    $tnc2text .= ":";

printf STDERR " tnc2text='%s'  line='%s'\n",$tnc2text, $line;


    # Now the  $line   has  ControlByte leading it..  Then ProtocolId
    # They should have values: 0x03 and 0xF0 respectively.

    if  ($tnc_is_pk96) {
	if (ord(substr($line, 0, 1)) == 0xF0) {
	    # The ControlByte vanishes ???
	    $line = substr($line, 1);
	}
    } else {
	# AEA TNC96 has weird bug -- it eats away these two bytes even in KISS MODE !

	if ((ord(substr($line, 0, 1)) != 0x03) ||
	    (ord(substr($line, 1, 1)) != 0xF0)) {
	    # BAD BAD!
	    return (undef, undef);
	}
	$line = substr($line, 2);
    }

    return ($tnc2text, $line);
}



sub kissreader {

    $seen_error = 0;
    OUTER: while (1) {

	if ($seen_error != 0) {
	    undef $ASY;
	    $seen_error = 0;
	    sleep(15);  ## Wait 15 sec .. 
	}


	$ASY = POSIX::open($asyname,O_RDWR,0);
	if (!defined($ASY) ||  !$ASY || $ASY < 0) {
		warn "ei saa avattua sarjaporttia: ".$asyname;
		$seen_error = 1;
		undef $ASY;
		next OUTER;
	}


	#$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+');
	if (!defined ($asyio) or !$asyio) {
		warn "IO::Handle:n avaus sarjaporttiin epäonnistui.";
		undef $asyio;
		$seen_error = 1;
		undef $ASY;
		next OUTER;
	}

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

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


	$SIG{'PIPE'} = 'DEFAULT'; # "death" - if caller dies

	local $line;

	autoflush STDERR 1;
	autoflush ASYWRT 1;


	local $poscount = 0;
	
	for(;;) {
	    local $tnc2text;

	    $line = & asygetkiss;

	    if (!defined($line)) {
		# D'uh..  some error!  close the line, and re-open..
		$seen_error = 1;
		undef $ASY;
		next OUTER;
	    }
	    
	    next if (length($line) < 21);  # Too small for a KISS frame

	    ($tnc2text, $line) = & parseax25aprs($line);
	    
	    # Merge them...
	    $line = $tnc2text . $line;

	    printf(LOG "%d\t%s%s\n", time, $line) if (!$inputsimulator && $makelog);

	    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) {
		$axpath    = $1;
		$firstchar = $2;
		$aprsdata  = $3;

		next if ($firstchar eq '}');
		$out = sprintf("%s,%s,I:%s%s", $axpath, $aprsuser, $firstchar, $aprsdata);

		next if ($axpath =~ m/RDONLY/io); # Has anywhere any of these..
		next if ($axpath =~ m/NOGATE/io);
		next if ($axpath =~ m/UIDIGI/io);
		next if ($axpath =~ m/^WIDE/io); # Begins with = is sourced by..
		next if ($axpath =~ m/^RELAY/io);
		next if ($axpath =~ m/^TRACE/io);

	    }
	    #printf STDERR "ASY2: <-  %s\n", $out;
	    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;
    $aprsfileno = $APRSIS->fileno;
    vec($rin,$aprsfileno,1) = 1 if ($aprsfileno >= 0);

    return 0 if ($stop_now);

    if (time > $when_beacon) {
	$when_beacon += 34.8*60; ## slowly drift aroud the hour..

	my $aprsline = sprintf("%s>APRS,%s,I:!6016.35N/02506.36E-Matin Perl \"i-gate\"", $aprsuser, $aprsuser);
	$APRSIS->sendline($aprsline);
    }


    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 if ($debug);
	printf ALOG "%d\t%s\n", time, $line if (!$inputsimulator && $makelog);

	# 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;


	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->{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} );

    $self->{url} = $url;


    return sockconnect($self);
}

sub sockconnect {
    my ($self) = @_;

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

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


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

##
##    *  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;

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

    $self;
}

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

sub fileno {
    my $self = shift;

    return -1 if (!defined ($self->{sock}));
    return $self->{sock}->fileno;
}

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


sub getline {
    my $self = shift;

    my @ready;
    my $line;

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

	# We have only one socket...

	$line = $self->{sock}->getline;
	if (!defined($line)) {
	    # Got EOF from APRS-IS ?
	    # ... re-open ?
	    undef $self->{select};
	    $self->{sock}->close;
	    sleep(1);
	    sockconnect($self);
	    return undef;
	}
	return $line;

    }

    undef;
}

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

    my @ready;

    sockconnect($self) if (!defined($self->{sock}));

    return if (!defined($self->{sock}));

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

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

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