#!/usr/bin/perl

#################################################################################
#                                                                               #
#  Easy PERL NTRIP client for Linux/Unix.                                       #
#  Copyright (C) 2004 by BKG, Denise Dettmering <euref-ip@bkg.bund.de>          #
#                                                                               #
#  This program is free software. It is distributed in the hope                 #
#  that it will be useful, but WITHOUT ANY WARRANTY                             #
#                                                                               #
#  Version history                                                              #
#  Please always keep revision history and the two related strings up to date!  #
#  0.1   2004-12-09 dettmering    initial version                               #
#  0.2   2004-12-20 dettmering    client authorization                          #
#  0.3   2005-01-05 dettmering    compatibility to GNCaster                     #
#  0.4   2005-02-09 dettmering    include sending NMEA GGA (from conf-file)     #
#  0.5   2005-06-14 dettmering    sending GGA with checksum			#
#  0.6   2005-08-30 dettmering    compatibility to Leica Spider			#
#                                                                               #
#################################################################################

use Socket;
use IO::Handle;

# declarations, start values: 
$useragent="NTRIP BKGPerlClient/0.6";
$confdatei="NtripPerlClient.conf";
$buffer=100; #standard buffer [byte] for reading data stream
$user="anonymous";
$out=0; #STDOUT
$dat="stream.out";
$tcpport=2101;
$comport=1;
$baudrate=19200;

# reconnect parameter (fixed values):
$reconnectstarttime=10.0;
$faktor=0.02;
$ramptime1=500.0;
$ramptime2=20000.0;
$reconnectendtime=400.0;
$maxreconnect=10000;

#################################################################################

# read configuration file:
%hash=(); 
open(DAT,$confdatei) || die "Error: can't open $confdatei!\n";
while ($ds=<DAT>) 
 {chomp($ds);  
  @felder=split(/=/,$ds); 
  $hash{$felder[0]}=$felder[1];
 }
close(DAT);
$caster=$hash{"caster"};
$ipport=$hash{"port"};
$mountpoint=$hash{"mountpoint"};
$user=$hash{"user"};
$out=$hash{"out"};
$tcpport=$hash{"tcpport"};
$comport=$hash{"comport"};
$baudrate=$hash{"baudrate"};
$dat=$hash{"file"};
$buffer=$hash{"buffer"};
$lat=$hash{"lat"};
$lon=$hash{"lon"};

# converting postion
$flagN="N";
$flagE="E";
if ($lon >180.) {$lon=($lon-360.)*(-1.); $flagE="W";}
if (($lon < 0.) && ($lon >= -180.))  {$lon=$lon*(-1.); $flagE="W";}
if ($lon < -180.)  {$lon=($lon+360.); $flagE="E";}
if ($lat < 0.)  {$lat=$lat*(-1.); $flagN="S";}

$lat_deg = int $lat;  $lat_min=($lat-$lat_deg)*60.;
$lon_deg = int $lon;  $lon_min=($lon-$lon_deg)*60.;

# time
$hh=`date -u +%H`;
$mm=`date -u +%M`;
$ss=`date -u +%S`;


# output of streaming data:
if (($out eq "TCP") || ($out eq "tcp")) {$out=1;}
if (($out eq "FILE") || ($out eq "file")) {$out=3;}
if (($out eq "COM") || ($out eq "com")) {$out=2;}
if (($out eq "NONE") || ($out eq "none")) {$out=4;}

# make stdout unbuffered
select(STDOUT); $| = 1;     

# base64-coding (user authorization):
$user_encoded=&encode64($user);

# define socket (caster connection):
$proto=getprotobyname('tcp'); #internet protocol=tcp
socket(SOCKET,PF_INET,SOCK_STREAM,$proto) || die "Error: Can't open socket!\n";

# connect to NtripCaster:
$port=$ipport;
if (($caster !~ /[a-z]/) && ($caster !~ /[A-Z]/)) # IP number
     {$ip=inet_aton($caster);}
else {$ip=gethostbyname($caster);} # IP name
$socketaddress_server=sockaddr_in($port,$ip);
$anzreconnect=0;
$sleeptime=0;
$reconnecttime=0;

do {$conn=connect(SOCKET,$socketaddress_server);
    
    if ($conn) 
       {print "Connecting to NtripCaster $caster:$port ... Done!\n";
        $anzreconnect=0;
        $sleeptime=0;
        $reconnecttime=0;
        
        # prepare output:
        if ($out==1) #TCP (Server, only on localhost):
           {socket(SERVER,PF_INET,SOCK_STREAM,$proto) || die "Error in socket()\n";
            setsockopt(SERVER,SOL_SOCKET,SO_REUSEADDR,1) || die "Error in setsockopt\n";
            bind(SERVER,sockaddr_in($tcpport,INADDR_ANY)) || die "Error in binding port\n";
            listen(SERVER,SOMAXCONN) || die "Error in listen()\n";
            print "Waiting for client connect ...\n";  #das ist noch ziemlich bld!!!!!
            accept(TCP,SERVER) || die "Error im accept()\n";
            print "Writing to localhost\:$tcpport ...\n";
            autoflush TCP;
           }
        if ($out==2) #serial port (only on LINUX systems!):
           {$devport=$comport-1; $dev="/dev/ttyS$devport";
            open(DEV,">$dev") || die "Error: Can't open $dev!\nMaybe you have no rights? Try as root!\n"; close DEV; 
            open(BAUD,"stty $baudrate < $dev |") || die "Error with baudrate!\n";
            print "Writing to COM$comport with $baudrate baud...\n";
           }
        if ($out==3) #file
           {open(DATEI,">$dat") || die "Error: Can't open $dat!\n";
            print "Writing to $dat ...\n";
            print DATEI "$caster:$ipport\/$mountpoint\n";
            close(DATEI);
           }
        if ($out==4) #no output
           {print "Reading data ... (no output)\n";
           } 
        
        # send request to NtripCaster:
        binmode(SOCKET);

        autoflush SOCKET;
        print SOCKET "GET /$mountpoint HTTP/1.0\r\nUser-Agent: $useragent\r\nAuthorization: Basic $user_encoded\r\n\r\n";

#        print SOCKET "GET \/$mountpoint HTTP\/1.0\r\n";
#        print SOCKET "User-Agent: $useragent\r\n";
#        print SOCKET "Authorization: Basic $user_encoded\r\n";
#        print SOCKET "Accept: */*\r\n";
#        print SOCKET "Connection: close\r\n";
#        print SOCKET "\r\n";
#        autoflush SOCKET;
        
        # read casters response:
        $zeile=<SOCKET>; chomp($zeile);         
        print "Rckgabe: $zeile\n";


        # Invalid Request (no valid mountpoint):
        if ($zeile =~ /SOURCETABLE/)
           {print "Desired NtripSource does not exist!\n";
            # read sourcetable and save into file:
            open(SOURCE,">sourcetable.dat") || die "Error: Can't write sourcetable.dat";
            do {$sourcezeile=<SOCKET>; chomp($sourcezeile);
                print SOURCE "$sourcezeile\n";
               } while ($sourcezeile !~ /ENDSOURCETABLE/);
            close(SOURCE);
            print "Writing sourctable.dat...\n";
            sleep($reconnectstarttime); 
            close(SOCKET);  # close socket and define a new one 
            exit(1);
           }

        # no valid user account:
        if ($zeile =~ /401 Unauthorized/)
#        if (($zeile =~ /401 Unauthorized/)||($zeile =~ /Bad Password/))
           {print "Unauthorized Request!\n";
            exit(1);
           }

        # Valid Request (Mountpoint okay, valid account):
        if ($zeile =~ /ICY 200 OK/) 
           {#print "valid request\n";

            # GGA:
            $gga= sprintf "GPGGA,%02d%02d%04.2f,%02d%011.8f,%1s,%03d%011.8f,%1s,1,05,0.19,+00400,M,47.950,M,,",$hh,$mm,$ss,$lat_deg,$lat_min,$flagN,$lon_deg,$lon_min,$flagE;

            # calculate Checksum: 
            $checksumm="\000";
            $lgga=length($gga);
            for ($i=0; $i<$lgga; $i++) 
                {$zeichen=substr($gga,$i,1);
                 $checksumm=$checksumm ^ $zeichen;
                }
            $check=sprintf("%02X",ord($checksumm));

            # send GGA-string to caster
            printf SOCKET "\$$gga*$check\r\n";
#            printf "\$$gga*$check\r\n";

            # read streaming data:
            binmode(SOCKET);
            $i=1;
            do {read(SOCKET,$daten,$buffer); 
                if ($out==0) {print "$daten";}
                if ($out==1) {print TCP "$daten";}
                if ($out==3) {open(DATEI,">>$dat"); autoflush DATEI; print DATEI "$daten"; close(DATEI);}
                if ($out==2) {open(DEV,">$dev"); autoflush DEV; print DEV "$daten"; close(DEV);}
               } while ($daten ne ""); 
            close(SOCKET);  # close socket and define a new one
            socket(SOCKET,PF_INET,SOCK_STREAM,$proto) || die "Error: Can't open Socket!\n";
           }

       } else
       {# reconnect parameter:
        $anzreconnect=$anzreconnect+1;
        $sleeptime=$sleeptime+$reconnecttime;
        
        if ($sleeptime <= $ramptime1) {$reconnecttime=$reconnectstarttime;}
        if ($sleeptime > $ramptime2) {$reconnecttime=$reconnectendtime;}
        if (($sleeptime > $ramptime1) && ($sleeptime <= $ramptime2)) {$reconnecttime=$faktor*$sleeptime};  

        $datum=`date`; chomp($datum); 
        print "$datum: No connection to NtripCaster. Reconnect in $reconnecttime sec ...\n";
        sleep($reconnecttime);
       }

   } while($anzreconnect < $maxreconnect);

# close socket:
close(SOCKET);
close(SERVER);
close(TCP);
close(DATEI);

#################################################################################

# encode into BASE64 encoded string.
sub encode64 {
  @code64 = ('A'..'Z', 'a'..'z', '0'..'9', '+', '/');
  grep($bin64{$code64[$_]} = unpack('B6', pack('C', $_ * 4)), 0 .. 63);
  %unbin64 = reverse %bin64;
  local($mod3) = length($_[$[]) % 3;
  local($coded) = unpack('B*', $_[$[]) . ('', '0000', '00')[$mod3];
  $coded =~ s/.{6}/$unbin64{$&}/g;
  $coded . ('', '==', '=')[$mod3];
}



