#!/usr/bin/perl # # gpslog.pl - track logger for Bluetooth GPS receivers for Linux. # # Copyright (c) 2009 Dmitry Marakasov # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. use warnings; use strict; use utf8; use Curses; my $RFCOMM_CMD = "rfcomm connect 0"; my $RFCOMM_DEV = "/dev/rfcomm0"; #my $RFCOMM_DEV = "temp1.nmea"; # for testing #my $RFCOMM_LOG = "/tmp/rfcomm.log"; #my $WIFI_TIME = 5; my $rfcomm_pid; my $nmea_file; my $last_wifi_time = 0; my $gpsdata; my $satdata; my $newsatdata; my $maxsats = 0; my $speed; my $length = 0; my $win = new Curses; noecho(); start_color(); curs_set(0); use_default_colors(); init_pair(1, COLOR_WHITE, -1); $win->nodelay(1); $SIG{CHLD} = \&reaper; $SIG{INT} = \&cleanup; $SIG{HUP} = \&cleanup; $SIG{TERM} = \&cleanup; $SIG{QUIT} = \&cleanup; open(LOG, '>', get_log_name()) || die "Cannot open output"; while (1) { update_display(); $rfcomm_pid = run_rfcomm() unless ($rfcomm_pid); eval { $nmea_file = open_nmea() unless ($nmea_file); read_next_line(); }; if ($@) { my $error = $@; chomp ($error); undef $rfcomm_pid if (defined $rfcomm_pid && waitpid($rfcomm_pid, 1) == $rfcomm_pid); close($nmea_file) if (defined $nmea_file); undef $nmea_file; sleep(1); } } close(LOG); sub bold { if ($_[0]) { $win->attron(COLOR_PAIR(1) | A_BOLD | 0*A_UNDERLINE); } else { $win->attroff(COLOR_PAIR(1) | A_BOLD | 0*A_UNDERLINE); } } sub prval { my ($y, $x, $align, $width, $descr, $val) = @_; $align = length($descr) if (length($descr) > $align || $align == 0); $width = length($val) if (length($val) > $width || $width == 0); $win->addstr($y, $x, sprintf("%*s: ", $align, $descr)); bold(1); $win->addstr($y, $x + $align + 2, sprintf("%-*s", $width, $val)); bold(0); } sub stnbar { my ($stn, $width) = @_; my $stnmax = 50; $stn = $stnmax if ($stn > $stnmax); return '-' if ($stn == 0); my $str = '['; my $bars = int($stn/$stnmax*($width-2)); $str .= '#' foreach (0..$bars-1); $str .= ' ' foreach ($bars..$width-1); $str .= ']'; return $str; } sub update_display { $win->box(0,0); $win->addstr(0, 3, "[ Marakas-SAT V0.1 ]"); $win->addstr(1, 2, "Status"); prval(2, 3, 0, 5, "RFCOMM PID", defined $rfcomm_pid ? "$rfcomm_pid" : "-"); prval(2, 22, 0, 4, "NMEA input", defined $nmea_file ? "open" : "-"); $win->addstr(4, 2, "GPS"); prval(5, 3, 5, 12, "Time", defined $gpsdata ? $gpsdata->{timestr} : "-"); prval(6, 3, 5, 12, "Lat", defined $gpsdata ? $gpsdata->{lat}.$gpsdata->{latl} : "-"); prval(7, 3, 5, 12, "Lon", defined $gpsdata ? $gpsdata->{lon}.$gpsdata->{lonl} : "-"); prval(8, 3, 5, 12, "Ele", defined $gpsdata ? $gpsdata->{ele} : "-"); prval(9, 3, 5, 12, "NSat", defined $gpsdata ? $gpsdata->{nsat} : "-"); prval(10, 3, 5, 12, "Qual", defined $gpsdata ? $gpsdata->{qualitystr} : "-"); prval(11, 3, 5, 12, "HDOP", defined $gpsdata ? $gpsdata->{hdop} : "-"); $win->addstr(4, 30, "Satellites"); foreach (my $n = 0; $n < $maxsats; $n++) { if (exists $satdata->[$n]) { prval(5+$n, 31, 5, 50, '#'.$satdata->[$n]->{num}, stnbar($satdata->[$n]->{stn}, 50)); } else { prval(5+$n, 31, 5, 50, '# ', '-'); } } $win->refresh(); my $char = $win->getch(); cleanup() if ($char eq chr(27) || $char eq 'q'); } sub read_next_line { my $line = <$nmea_file>; die "cannot read nmea file" unless $line; if ($line =~ /^\$GPGGA,([0-9\.]+),([0-9\.]+),([NS]),([0-9\.]+),([EW]),([0123]),(\d+),([0-9\.]+),([0-9\.]+),M/) { #my $lastlon = defined $gpsdata ? $gpsdata->{lon}; #my $lastlat = defined $gpsdata ? $gpsdata->{lat}; $gpsdata = { time => $1, timestr => $1, lat => $2, latl => $3, lon => $4, lonl => $5, quality => $6, qualitystr => '?', nsat => $7, hdop => $8, ele => $9, }; $gpsdata->{lat} =~ s/^(\d{2})(\d{2})\.(.*)$/$1.$2$3/; $gpsdata->{lon} =~ s/^0?(\d{2,3}?)(\d{2})\.(.*)$/$1.$2$3/; $gpsdata->{timestr} =~ s/^(\d{2})(\d{2})(\d{2}\.\d+)$/$1:$2:$3/; $gpsdata->{qualitystr} = "none" if ($gpsdata->{quality} == 0); $gpsdata->{qualitystr} = "normal" if ($gpsdata->{quality} == 1); $gpsdata->{qualitystr} = "diff" if ($gpsdata->{quality} == 2); $gpsdata->{qualitystr} = "precision" if ($gpsdata->{quality} == 3); } elsif ($line =~ /^\$GPGGA/) { undef $gpsdata; } elsif ($line =~ /^\$GPGSV,(\d+),(\d+),(\d+)((,\d+,\d+,\d+,\d*)+)/) { my $nmessages = $1; my $nmessage = $2; my $nsats = $3; $newsatdata = [] if ($nmessage == 1); my $sats = $4; while ($sats =~ /,(\d+),(\d+),(\d+),(\d*)/g) { push @$newsatdata, { num => $1, alt => $2, az => $3, stn => $4 eq '' ? 0 : $4, }; } if ($nmessage == $nmessages && $#$newsatdata+1 == $nsats) { @$newsatdata = sort { $a->{num} <=> $b->{num} } @$newsatdata; $satdata = $newsatdata; $newsatdata = []; $maxsats = $#$satdata+1 if ($#$satdata+1 > $maxsats); } } print LOG "$line"; } sub get_log_name { my @logtime = localtime(time()); return sprintf("%04d-%02d-%02d %02d:%02d.nmea", $logtime[5]+1900, $logtime[4], $logtime[3], $logtime[2], $logtime[1]); } sub reaper { } sub run_rfcomm { my $pid = fork(); die "cannot fork" if ($pid == -1); if ($pid == 0) { open(STDOUT, '>/dev/null'); open(STDERR, '>/dev/null'); exec(split(/\s+/, $RFCOMM_CMD)); # never reached exit(0); } return $pid; } sub open_nmea { open(NMEA, $RFCOMM_DEV) || die "cannot open $RFCOMM_DEV"; return \*NMEA; } sub cleanup { if (defined $rfcomm_pid) { kill(15, $rfcomm_pid); waitpid($rfcomm_pid, 0); } endwin(); exit(0); }