#!/usr/bin/perl -w # # gps-geiger - simultaneous logging from a GPS and a Geiger counter # copyright (c) 2004 by Ian Kluft # released under the GNU General Public License Version 2 # http://www.kluft.com/~ikluft/opensource/GPLv2.txt # # Credits to people who posted code which saved me time making this: # # GPS NMEA code derived from scripts posted by Waider, Sept 2000 # http://www.waider.ie/hacks/workshop/perl/nmea.pl # http://www.waider.ie/hacks/workshop/perl/gps.pl # # geiger counter code used Rik Faith's geiger.c as a reference # http://www.aw-el.com/linux1.txt use strict; use warnings; use lib "Device-Geiger-Aware/blib/lib"; use lib "Device-Geiger-Aware/blib/arch"; use Getopt::Long; use Device::SerialPort; use Device::Geiger::Aware; my ( $gps_port, $geiger_port ); my $gps_baud = 9600; my ( $port, $LOCKFILE, $origdev, $geiger_model, $geiger_obj, $logpath, $verbose, $log_fh ); sub meters2feet { $_[0] * 3.2808399 } sub opengps { my $device = shift; my ( $dev_path, $dev_basename ); # cope with device name being a basename or /dev/... path if ( $device =~ m=^.*/([^/]*)$= ) { $dev_basename = $1; $dev_path = $device; } else { $dev_basename = $device; $dev_path = "/dev/".$device; } $LOCKFILE = "/var/lock/LCK..$dev_basename"; # stat the lockfile, open it, check the process ID, check if # the process is still running, nuke the lockfile if it's not. if ( -f $LOCKFILE ) { if ( open( LOCKFILE, "<$LOCKFILE" )) { my $pid = ; chomp $pid; if ( kill 0, $pid ) { # process still running die "$origdev is locked by process $pid"; } close( LOCKFILE ); unlink( $LOCKFILE ); } else { die "Can't open lockfile for $origdev: $!"; } } $port = Device::SerialPort->new( $dev_path, 1, $LOCKFILE ); if ( !$port ) { die "Failed to open port: $!\n"; } # now set up the port $port->baudrate( $gps_baud ); $port->parity( "none" ); $port->databits( 8 ); $port->stopbits( 1 ); $port->handshake( "none" ); $port->alias( "gps" ); $port; } sub closegps { if ( ref( $port )) { $port->close; } unlink $LOCKFILE; undef $port; } sub opengeiger { $geiger_obj = Device::Geiger::Aware->new( device => $geiger_port, model => $geiger_model ) } sub readgeiger { my $logobj_ref = shift; # collect geiger counter data after each GPS data pulse { my @data = $geiger_obj->get_data(); my $ur_hr = $data[0]; my $cpm = $data[1]; if (( defined $ur_hr ) and $ur_hr >= 0 ) { $verbose and print "geiger ur_hr: $ur_hr\n"; setlog( $logobj_ref, "ur-hr", $ur_hr ); $verbose and print "geiger cpm: $cpm\n"; setlog( $logobj_ref, "cpm", $cpm ); } elsif ( ! defined $ur_hr ) { $verbose and print "no data from geiger counter\n"; } else { $verbose and print "geiger counter failed: ".$geiger_obj->last_error()."\n"; } } } sub closegeiger { undef $geiger_obj; } sub csum{ my $data = shift; my $cs = 0; for my $i ( split( //, $data )) { $cs += ord( $i ); } ~($cs & 0xff) + 1; } sub setlog { my $logobj_ref = shift; my $name = shift; my $value = shift; if ( !defined $logobj_ref->{$name}) { $logobj_ref->{$name} = $value; } } sub logdump { my $logobj_ref = shift; if ( ! keys %$logobj_ref ) { print STDERR "debug: log object is empty\n"; return; } my $var; foreach $var ( "time", "position", "ur-hr", "cpm", "altitude", "epe", "sats" ) { if ( defined $logobj_ref->{$var}) { print $log_fh ucfirst(lc($var)).": " .$logobj_ref->{$var}."\n"; } } print $log_fh "\n"; # reset the log object for the next round %$logobj_ref = (); } sub sighandler { my($sig) = @_; print "Caught a SIG$sig--shutting down\n"; closegps; closegeiger; exit 0; } # check command line GetOptions ( "gps:s" => \$gps_port, "baud:i" => \$gps_baud, "geiger:s" => \$geiger_port, "model:s" => \$geiger_model, "log:s" => \$logpath, "verbose" => \$verbose, ); if (( !defined $gps_port ) or ( !defined $geiger_port ) or ( !defined $geiger_model ) or ( !defined $logpath )) { die "usage: $0 --gps gps_dev_path [--baud rate]\n" ." --geiger geiger_dev_path --model geiger_model_name\n" ." --log logfile_path\n"; } # open devices and logs opengps( $gps_port ); opengeiger( $geiger_port ); $log_fh = IO::File->new ( $logpath, "w" ); if ( ! $log_fh ) { die "$0: failed to open $logpath for writing: $!\n"; } $log_fh->autoflush(); print STDERR "logging to $logpath\n"; # catch signals $SIG{"INT"} = \&sighandler; $SIG{"QUIT"} = \&sighandler; $SIG{"HUP"} = \&sighandler; $SIG{"TERM"} = \&sighandler; # main data collection and processing loop my %logobj; while ( 1 ) { my $line = $port->streamline; my @lines = split ( /\r*\n/s, $line ); foreach ( @lines ) { #$verbose and print $_."\n"; next if !s/^\$//; # discard header s/\r{0,1}\n$//; # discard trailer # do the checksum thing if ( s/\*([0-9A-F][0-9A-F])$// ) { my ( $csum ) = eval( "0x$1" ); for my $c ( split( // )) { $csum ^= ord( $c ); } if ( $csum ) { print STDERR "invalid checksum\n"; next; # invalid checksum } } # Break it up into fields my @fields = split( /,/ ); # Parse type of data shift @fields; # discard source/command # Woop. Check for proprietary sentence: if ( m/^P(...)(.*?),/ ) { if ( $1 eq 'GRM' ) { # GARMIN PROPRIETARY GARMIN: { # E - estimated error if ( $2 eq 'E' ) { if ( $fields[ 0 ] =~ /[0-9.]/) { # verify that we have data $verbose and print "Estimated error: "; $verbose and printf( "HPE: %f %s VPE: %f %s Spherical: %f %s\n", @fields); setlog ( \%logobj, "epe", sprintf( "HPE: %f %s VPE: %f %s Spherical: %f %s", @fields)); } } # Z - altitude. Always in feet. if ( $2 eq 'Z' ) { if ( $fields[ 0 ] =~ /[0-9.]/) { my $lf; if ( $fields[ -1 ] =~ /^([0-9]+)/ ) { $lf = $1; } else { $lf = 0; } $verbose and printf( "Altitude (%s): %d %s\n", ( $lf == 2 ) ? "user" : "GPS", $fields[ 0 ], $fields[ 1 ] ); setlog ( \%logobj, "altitude", sprintf( "Altitude (%s): %d %s\n", ( $lf == 2 ) ? "user" : "GPS", $fields[ 0 ], $fields[ 1 ] )); } } last GARMIN; } } else { # don't know what to do! AIE! $verbose and print "command $2 from $1\n"; } } else { s/^(..)(.+?),//; my ( $source, $datatype ) = ( $1, $2 ); # $verbose and print "command $datatype from "; if ( $source eq 'GP' ) { # $verbose and print "GPS receiver\n"; } elsif ( $source eq 'LC' ) { # $verbose and print "Loran-C receiver\n"; } elsif ( $source eq 'OM' ) { # $verbose and print "Omega Navigation receiver\n"; } elsif ($source eq 'II' ) { # $verbose and print "Integrated Instrumentation\n"; } else { $verbose and print "$source ???\n"; } COMMAND: { # BOD Bearing, Origin to Destination # GGA GPS Fix Data if ( $datatype eq 'GGA' ) { # at this point we mark a new set of data $verbose and print "--\n"; logdump( \%logobj ); readgeiger( \%logobj ); my ( $time, $lat, $latd, $long, $longd, $qual, $nsat, $hdil, $alt, $altu, $geo, $geou, $lastdgps, $dgpsid, @leftovers ) = @fields; # Is this good data? if ( $qual && $#fields >= 11 ) { # field count drops sometimes. $verbose and printf( "%02d:%02d:%02d ", substr( $time, 0, 2), substr( $time, 2, 2 ), substr( $time, 4, 2 )); setlog ( \%logobj, "time", sprintf( "%02d:%02d:%02d ", substr( $time, 0, 2), substr( $time, 2, 2 ), substr( $time, 4, 2 ))); $verbose and printf( "%s%02d.%02d%02d %s%03d.%02d%02d ", $latd eq 'N' ? " " : "-", # N/S indicator substr( $lat, 0, 2 ), substr( $lat, 2, 2 ) * 100/60, substr( $lat, 4 ) * 100/60, $latd eq 'W' ? " " : "-", # E/W indicator substr( $long, 0, 3 ), substr( $long, 3, 2 ) * 100/60, substr( $long, 5 ) * 100/60 ); # setlog ( \%logobj, "position", # sprintf( "%s%02d.%02d%02d %s%03d.%02d%02d ", # $latd eq 'N' ? " " : "-", # N/S indicator # substr( $lat, 0, 2 ), # substr( $lat, 2, 2 ) * 100/60, # substr( $lat, 4 ) * 100/60, # $latd eq 'W' ? " " : "-", # E/W indicator # substr( $long, 0, 3 ), # substr( $long, 3, 2 ) * 100/60, # substr( $long, 5 ) * 100/60 )); $verbose and printf( "alt: %s%s (WGS84 + %s%s)) ", $alt, $altu, $geo, $geou ); setlog ( \%logobj, "altitude", sprintf( "%s%s (WGS84 + %s%s)) ", $alt, $altu, $geo, $geou )); $verbose and printf( "(%d sats) ", $nsat ); setlog ( \%logobj, "sats", $nsat ); $verbose and print "\n"; } else { $verbose and print "Something up: F $#fields\n" if $#fields < 11; } } elsif ( $datatype eq 'GLL' ) { $verbose and print "Geographic Position, Lat/Lon: "; # N/S val, N/S, E/W val, E/W, A => valid my $lat_deg = int($fields[0]/100); my $lat_min = $fields[0] - $lat_deg*100; my $lon_deg = int($fields[2]/100); my $lon_min = $fields[2] - $lon_deg*100; my $utc_hour = int( $fields[4]/10000 ); my $utc_min = int ( $fields[4]/100) % 100; my $utc_sec = int ( $fields[4]) % 100; $verbose and printf "%d %4.4f %s %d %4.4f %s %02d:%02d:%02d %s\n", $lat_deg, $lat_min, $fields[1], $lon_deg, $lon_min, $fields[3], $utc_hour, $utc_min, $utc_sec, $fields[5]; # redundant - sets position in log only if GGA was corrupted setlog ( \%logobj, "position", sprintf "%d %4.4f %s %d %4.4f %s %02d:%02d:%02d %s", $lat_deg, $lat_min, $fields[1], $lon_deg, $lon_min, $fields[3], $utc_hour, $utc_min, $utc_sec, $fields[5] ); } elsif ( $datatype eq 'GSA' ) { $verbose and print "GPS DOP and active satellites\n"; # A/M - auto/manual # 2/3 - 2D/3D fix # 12 spaces for satellite PRNs # PDOP (dilution of precision) # HDOP # VDOP } elsif ( $datatype eq 'GSV' ) { $verbose and print "Satellites in view\n"; # Number of sentences for full data # Sentence N of the above # Number of sats in view # Sat PRN # Elev # Azimuth # Signal strength # Repate for up to 4 sats per sentence, 3 GSV sentences per packet } elsif ( $datatype eq 'RMB' ) { $verbose and print "Recommended Minimum Navigation Information\n"; # A/V okay/warning # cross track error, nautical miles # directon to steer # origin waypoint ID # destination waypoint ID # dest lat DDMM.MM,N/S # dest long DDMM.MM E/W # Range to dest, nautical # true bearing to dest # velocity towards dest # A/V arrival alarm } elsif ( $datatype eq 'RMC' ) { $verbose and print "Recommended minimum specific GPS/Transit data\n"; # time HHMMSS UTC # A/V # LAT N/S # LONG E/W # Speed, Knots # Course Made Good, True # Date of fix DDMMYY # Magnetic Variation dist, dir } elsif ( $datatype eq 'RTE' ) { $verbose and print "Waypoints in active route\n"; # Sentences of data # sentence num # c omplete, w first listed start of current leg # route identifier # Waypoint IDs } elsif ( $datatype eq 'BOD' ) { $verbose and print "Origin to destination bearing\n"; # Bearing, T (true) from STart to Dest # Bearing, M (magnetic) # Dest # Start } else { $verbose and print "ERROR! Unhandled data $datatype\n"; } } } } } closegps(); $log_fh->close();