gpsdate: Get the Date from a GPS Receiver

This updates and replaces the gpsdate script. I started it in 2004 and last touched it in 2006. There has been a lot of water under the dam since then.

  • Updated to use the Perl Net::GPSD3 module, which hides all the socket and JSON cruft. So all of that cruft is gone.
  • Added "use strict".
  • Added an option to print the time in local time.

This script show a simple proof of concept that you can expand to extract any data you want from your gpsd data. It also show a simple example of several facilities in the Perl date::manip module.

Note that this isn't all that accurate. For sub-second accuracy, look into using gpsd with ntpd or chrony. (man gpsd, then search on 'NTP' or 'CHRONY'.)

#! /usr/bin/perl

# OK, I'm lazy; it's one of the reasons I work in Perl. I figure if we
# have these 24 atomic clocks of reasonably acceptable accuracy
# orbiting the planet, I ought to be able to set my system clock from
# them occasionally. So this is a simple script to display the system
# time from GPS. You can also use it to set the system time.

# This version uses Net::GPSD3, which handles all the heavy lifting,
# but requires lbgps version 2.90 or higher.

# Options:

# -h is reserved in case I decide to add a "help" function.

# -l specifies printing the time in local time.

# -p sets the port on the remote host to try. You can give the service
# name (e.g. gpsd) or the port number (e.g. 2947). Or you can give
# both by using the syntax 'name(number)', e.g. 'gpsd(2947)'. If Perl
# can't find the name wherever it looks these things up (probably
# /etc/services), it goes for the number. So use both for maximum
# portability. The default value is 'gpsd(2947)'. To avoid bashisms,
# use '-p gpsd\(2947\)'.

# -s indicates that we should spit out the time and date ready for
# date to set the system clock. If the local time option is not set,
# we provide the -u switch for date. E.g:

# date -s $(gpsdate -s)
# date -s $(gpsdate -sl gpsbox)

# If -s is not set, we give a more human readable time format. We
# ignore portions of a second; the lag time and overhead make the time
# delivered by the program close enough.

# Requires: gpsd (http://gpsd.berlios.de/) version 2.90 or higher;
# date, the gnu time setting and displaying utility. Date twiddling is
# courtesy of Date::Manip; Debian and Ubuntu users will want to
# install libdate-manip-perl. Oh, and a GPS receiver somewhere would
# probably help.

# gpsdate will block until the GPS receiver has warmed up,
# i.e. acquired enough satellites (or whatever it needs) to have a
# valid time signal.

# Copyright 2004 through the last date of modification Charles Curley,
# http://www.charlescurley.com/.

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

# You can also contact the Free Software Foundation at
# http://www.fsf.org/

use Getopt::Std;
use Net::GPSD3;
use Date::Manip::Date;
use Date::Manip::TZ;

use strict;

my %option;
getopts ("lp:sv", \%option);

# $host is the name or IP address of the host. It may be local or
# remote. e.g: localhost. User configurable at the command line.

my $host = 'localhost';

if (@ARGV > 1) {
  die ("Maximum of one host name, please.\n");
}

$host = shift (@ARGV);

# If set, show the time as local time, not GMT.
$option{l} = 0 unless defined $option{l};
my $localTime = $option{l};

# $port is the port on the remote host to try. User configurable at
# the command line with -p.

$option{p} = 'gpsd(2947)' unless defined $option{p};
my $port = $option{p};

# $settime, if set, indicates that we should spit out the time and
# date ready for settime to set the system clock. We even provide the
# -u switch for date if appropriate.

my $settime=$option{s};

$option{v} = 0 unless defined $option{v};
my $verbose = $option{v};


# Originally from the Net::GPSD3 man page.
sub myHandler {
  my $object = shift;

  # use Data::Dumper qw{Dumper};
  # print Dumper($object);

  # print ("Class is $object->{'class'}\n");

  if ($object->{'class'} eq 'VERSION' && $verbose) {
    # Show the version of the host.
    # print ($object->{'string'} . "\n");
    print ("gpsd release: $object->{'release'}, rev: $object->{'rev'}");
    print (", protocol $object->{'proto_major'}.$object->{'proto_minor'}\n");

  } elsif ($object->{'class'} eq 'TPV') {
    # We have a Time, Position, Velocity report
    # print ($object->{'string'} . "\n");
    if ($object->{'mode'} == 3) {
      # Mode is 3, i.e. a 3D fix.

      my $date = new Date::Manip::Date;
      my $err = $date->parse ($object->{'time'});
      if ($err) {
        die ("Unacceptable date string: $date.\n");
      }

      # my $val = $date->value();
      # print ("Value is $val\n");

      if ($localTime) {
        my $tz = new Date::Manip::TZ;
        my $zone = $tz->curr_zone();
        $err = $date->convert($zone);
        if ($err) {
          die ("Time zone conversion failed.\n");
        }
        print ("The local time zone is $zone.\n") if ($verbose);
      }

      if ($settime && $localTime) {
        print $date->printf ("%m%d%H%M%Y.%S\n");
        # print  ("MMDDhhmmCCYY.ss\n");
      } elsif ($settime) {
        print $date->printf (" -u %m%d%H%M%Y.%S\n");
        # print (" -u MMDDhhmmCCYY.ss\n");
      } else {
        print $date->printf ("%Y-%m-%dT%H:%M:%S\n");
      }
      exit (0);
    }
  }
}

my $gpsd=Net::GPSD3->new(host=>$host, port=>$port);
$gpsd->addHandler(\&myHandler);
$gpsd->watch;  

exit (0);