#!perl -w use strict; use Socket; use Math::Trig qw(great_circle_distance deg2rad); use Net::SMTP; use Authen::SASL qw(Perl); # auto-flush on socket $| = 1; my $VERSION="0.1"; print "NoForeignLand-AutomaticPositionUpdater $VERSION, by Frans Veldman s/v ZwerfCat (https://www.thefloatinglab.world)\n"; my $tcp=0; my $gpsd=0; my $daemon=0; my $test=0; my $interval=2; my $extended=0; my $user=''; my $pass=''; my $server=''; my $from=''; my $destination='tracking@noforeignland.com'; my $port=587; my @sockets; # Get command line options foreach my $a(@ARGV) { $daemon=1 if($a eq "-d" || $a eq "--daemon"); $test=1 if($a eq "-T" || $a eq "--test"); $tcp=1 if($a eq "-t" || $a eq "--tcp"); $tcp=1, $gpsd=1 if($a eq "-g" || $a eq "--gpsd"); $extended=1 if($a eq "-x" || $a eq "--extended"); $interval=$2 if($a=~/-(-interval|i)=(\d+)/); $port=$2 if($a=~/-(-port|p)=(\d+)/); $destination=$2 if($a=~/-(-override|o)=(\S+)/); $server=$2 if($a=~/-(-server|s)=(\S+)/); $user=$2 if($a=~/-(-user|u)=(\S+)/); $pass=$2 if($a=~/-(-password|w)=(\S+)/); $from=$2 if($a=~/-(-email|e)=(\S+)/); if($a eq "-?" || $a eq "-h" || $a eq "--help") { print "\nUsage:\n"; print "\tperl nfl-apu.pl [OPTIONS] \n"; print "Control options:\n"; print "\t-h --help Display help\n"; print "\t-T --test Debug output\n"; print "\t-d --daemon Run as daemon\n"; print "NMEA connection options:\n"; print "\t-t --tcp Use TCP source instead of UDP\n"; print "\t-g --gpsd Use GPSD source\n"; print "Update options:\n"; print "\t-x --extended Extended updates (with SOG, COG, DPT)\n"; print "\t-i --interval= Nautical miles between position updates (default $interval). A value 0 means \"arrival updates only\"\n"; print "Email options:\n"; print "\t-u --user= Email account inlog user\n"; print "\t-w --password= The password associated with the user account\n"; print "\t-s --server=
SMTP server\n"; print "\t-p --port= SMTP port (default $port)\n"; print "\t-e --email=
Source email address\n"; print "\t-o --override=
Override destination email address (default '$destination')\n"; print "Example:\n"; print "\tperl nfl-apu.pl -t -g -i=2 -x -u=Frans -s=mail.fransveldman.nl -e=noreply\@thefloatinglab.world 127.0.0.1:2947\n"; exit; } next if($a=~/^-/); if($a=~/^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])):([0-9]+)$/) { push @sockets,pack_sockaddr_in($5, inet_aton($1)); } else { die "Error: $a is not a valid IP:PORT address!\n"; } } use Fcntl qw(LOCK_EX LOCK_NB); open our $file, '<', $0 or die $!; die "Another instance is already running!\n" unless (flock $file, LOCK_EX|LOCK_NB); # Check that at least a source address has been specified die "Error: No NMEA source specified!\n" unless(@sockets); die "Error: No username specified!\n" if($user eq ''); die "Error: No smtp server specified!\n" if($server eq ''); die "Error: No email source specified!\n" if($from eq ''); while($pass eq '') { print "Enter password: "; $pass=; chomp $pass; print "\n"; } $daemon=0 if($test); daemonize() if($daemon); my $sock; sourceconnect(); my $datadetected=0; my $positiondetected=0; my $depthdetected=0; my $starttime=time; my $timer=0; my $prevlat=0; my $prevlon=0; my $depth=0; # just loop forever listening for packets while (1) { my $data=<$sock>; if(length($data)==0) { # We lost the connection, so re-establish it close($sock) if($tcp); sourceconnect(); next; } if(!$datadetected && $data=~/\$[A-Z]+,.*\*../) { $datadetected++; print "NMEA stream detected.\n"; } if($data=~/\$\w\wDPT,(\d+\.?\d*),([+-]?\d*\.?\d*).*\*../) { $depth=$1; $depth+=$2 if(defined $2); if(!$depthdetected) { $depthdetected++; print "Depth: $depth meters\n"; } } next if(time-$starttime<8 && !$depthdetected); # Just wait a little for NMEA data acquisition # Get the position update # $RMC,,,,,,,,,,,,,* if($data=~/\$G[A-Z]RMC,\d*\.?\d*,A,(\d\d)(\d\d\.\d+),([NS]),(\d\d\d)(\d\d\.\d+),([EW]),([0-9]*\.?[0-9]*),(\d*)\.?\d*,.*\*../) { my $latdeg=$1; my $latmin=$2; my $latdir=$3; my $londeg=$4; my $lonmin=$5; my $londir=$6; my $sog=$7; my $cog=$8; my $lat=$latdeg+($latmin/60); $lat=-$lat if($latdir eq 'S'); my $lon=$londeg+($lonmin/60); $lon=-$lon if($londir eq 'W'); if(!$positiondetected) { $positiondetected++; print "Lat: $latdeg degrees $latmin minutes $latdir\nLon: $londeg degrees $lonmin minutes $londir\nSOG: $sog COG: $cog\n"; } my $distance=gps_distance($lat,$lon,$prevlat,$prevlon); # We want to be one unit behind, so that when arriving at a destination we are guaranteed to have a substantial distance from our previous waypoint. # So, when the criteria for an update are met, we update a previous position, unless after we have arrived. # If the speed is almost zero, and we have a distance between the previous reported position, it looks like we arrived somewhere. my $arrived=0; if($sog<0.4 && $distance>0.1) { $timer=time if(!$timer); $arrived=1 if(time-$timer>600); } else { $timer=0; } my $update=0; $update=1 if($interval && $distance>$interval); # At the start of the program, we broadcast the current position, so we update prevlat and prevlon right away. We also do this if we have arrived somewhere. if((!$prevlat && !$prevlon) || $arrived) { $prevlat=$lat; $prevlon=$lon; $update=1; # Force a position update $arrived=1 if($sog<0.4); # Assume that we have arrived somewhere } if($update) { # Submit position report $latdir='N'; if($prevlat<0) { $prevlat=-$prevlat; $latdir='S'; } $prevlat=~/(\d+)(\.\d+)/; $latdeg=$1; $latmin='0'.$2; $latmin=sprintf("%.4f", $latmin*60); $londir='E'; if($prevlon<0) { $prevlon=-$prevlon; $londir='W'; } $prevlon=~/(\d+)(\.\d+)/; $londeg=$1; $lonmin='0'.$2; $lonmin=sprintf("%.4f", $lonmin*60); my $msgbody=<new($server, Port => $port, Timeout => 60, Debug => $test); if($smtp) { $smtp->starttls(); $smtp->auth($user,$pass) or die "Error: could not authenticate: $smtp->status, $smtp->message\n"; $smtp->mail($from); $smtp->to($destination); $smtp->data(); $smtp->datasend($msgbody); $smtp->dataend; $smtp->quit; print "Done\n" if(!$daemon); } else { print "Failed\n" if(!$daemon); } $prevlat=$lat; $prevlon=$lon; } } } sub gps_distance { my ($lat0,$lon0,$lat1,$lon1) = @_; return great_circle_distance(deg2rad($lon0), deg2rad(90-$lat0), deg2rad($lon1), deg2rad(90-$lat1), 3443.931); # nautical miles } sub sourceconnect { if($tcp) { # Connect for TCP source print "Connecting... " if(!$daemon); socket($sock, PF_INET, SOCK_STREAM, getprotobyname('tcp')) || die "socket: $!"; setsockopt($sock, SOL_SOCKET, SO_KEEPALIVE, 1); connect($sock,$sockets[0]) || die "Could not connect to TCP port!\n"; print "Connected!\n" if(!$daemon); if($gpsd) { # Configure GPSD output, and skip config messages send($sock,'?WATCH={"enable":true,"json":false,"nmea":true,"raw":0,"scaled":false,"timing":false,"split24":false,"pps":false}',0); while(my $line= <$sock>) { last unless($line=~/\{/); print $line if(!$daemon); } } } else { # Connect to UDP source socket($sock, PF_INET, SOCK_DGRAM, getprotobyname('udp')) || die "socket: $!"; setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!"; setsockopt($sock,SOL_SOCKET,SO_RCVBUF,100000); bind($sock, $sockets[0]) || die "bind: $!"; } } sub daemonize { use POSIX; POSIX::setsid or die "setsid: $!"; my $pid = fork() // die $!; #// if($pid) { print "Started daemon (PID $pid)\n"; exit(0); } chdir "/"; umask 0; open (STDIN, "/dev/null"); open (STDERR, ">&STDOUT"); }