NTP plugin for a detachable nmea GPS source

NTP ( the timekeeping daemon ) does not like devices that are not present at startup or go away on occasion like a detachable GPS device.

This is unix / _linux_ centric.

Your ntp configuration should contain something like:

# basic nmea source, mode: listen to GPRMC sentence, 9600baud ( in my case a sure electronics gps eval board ) :
server 127.127.20.1 mode 17
fudge  127.127.20.1  flag1 0 flag2 0 flag3 0 flag4 0 time1 0.7800

with time1 being an adjustable value depending on what sentences the GPS outputs and your application specific delays.

All examples create a pty and link it to the expected gps device entry ( /dev/gps1 ) and handle disconnects / reconnects on that side gracefully.

The first script then just connects to a usb tty device and pushes selected sentences to the pty side, start this in boot up before rcntp runs:

#!/usr/bin/tclsh

package require Expect
package require udp


set ::Stat(pty,spid) {}
set ::Stat(gps,path) "/dev/gps1"

proc respawn_pty {} {
        set spid $::Stat(pty,spid)
        if {$spid != "" } {
                close -i $spid -slave
                wait
        }
        catch { puts stderr exist:[file exists $::Stat(gps,path)] }
        catch { puts stderr type_:[file type $::Stat(gps,path)] }
        # if {   [file exists $::Stat(gps,path)] }
        catch {
        if {[file type $::Stat(gps,path)] == "link" } {
                puts stderr "deleting existing link: $::Stat(gps,path)"
                file delete $::Stat(gps,path)
        }
        } cerr; puts stderr cerr:$cerr
        spawn -pty
        set ::Stat(pty,spid)            [ set spid $spawn_id ]
        set ::Stat(pty,slave,name)      $spawn_out(slave,name)
        set ::Stat(pty,slave,fd)        $spawn_out(slave,fd)
        file link -symbolic  $::Stat(gps,path)  $::Stat(pty,slave,name)

        expect_background -i $::Stat(pty,spid) \
                          -re "AT.*?\[\r\n\]{1,2}" {
                                exp_send -i $::Stat(pty,spid) "OK\r"
                                puts stderr "AT sentence, OK sent"
                        } \004 {
                                puts stderr "$::Stat(pty,slave,name) ^D, reopen"
                                # respawn_pty 
                        } eof {
                                puts stderr "$::Stat(pty,slave,name) EOF, reopen"
                                # respawn_pty 
        }

        puts stderr "$::Stat(pty,slave,name) linked to $$::Stat(gps,path), open"
        parray ::Stat

        return $::Stat(pty,slave,name)
}

respawn_pty

set fd [ open /dev/ttyUSB0 {RDWR NONBLOCK} ]
fconfigure $fd \
        -encoding binary -translation binary \
        -mode 9600,n,8,1 -handshake none

spawn -open $fd
set gps_spid $spawn_id

log_user 0
expect -i $gps_spid \
          -re {(\$GPGGA.*?)\[\r\]} {
                set sentence $expect_out(1,string)
                exp_send -i $::Stat(pty,spid) $sentence\r
                puts stderr 1:$sentence
                exp_continue
        } -re {(\$GPGGA.*?\*[[:xdigit:]]{2})[\r]} {
                set sentence $expect_out(1,string)
                exp_send -i $::Stat(pty,spid) $sentence\r
                puts stderr 2:$sentence
                exp_continue
        } timeout {
                puts stderr timeout
                exp_continue
        } eof {
                puts stderr "EOF on GPSside"
        }

puts end

# vwait forever

The second example sets up a udp gateway to ntp, receiving udp packets on a select port and piping that down into the pty/tty device opened by ntpd, start this in boot up before rcntp runs:

#!/usr/bin/tclsh
#  $Id: gpsplug.tcl,v 1.3 2011/05/16 07:05:45 uwe Exp uwe $
# cprt: C2011 Uwe Klein Habertwedt
# what: plugin for ntpd to allow a nmea source to 
# what: be remote or intermittently available.
# what: provides a tty(pty) for ntp to attach to 
# what: while listening on udp:61161 for incoming 
# what: nmea GPRMC messages ( intp:mode:0=1 ~= listen to RMC )
# what: ntp.conf: # basic nmea source, mode: listen to GPRMC sentence, 9600baud:
# what: ntp.conf: server 127.127.20.1 mode 17
# what: ntp.conf: fudge  127.127.20.1  flag1 0 flag2 0 flag3 0 flag4 0 time1 0.7800
# use_: start before ntpd at boot time.



package require Expect
package require udp


set ::Stat(pty,spid) {}
set ::Stat(gps,path) "/dev/gps1"
set ::Stat(udp,port) 61161

proc respawn_pty {} {
        set spid $::Stat(pty,spid)
        if {$spid != "" } {
                close -i $spid -slave
                wait
        }
        catch { puts stderr exist:[file exists $::Stat(gps,path)] }
        catch { puts stderr type_:[file type $::Stat(gps,path)] }
        catch {
                if {[file type $::Stat(gps,path)] == "link" } {
                        puts stderr "deleting existing link: $::Stat(gps,path)"
                        file delete $::Stat(gps,path)
                }
        } cerr; puts stderr cerr:$cerr
        spawn -pty
        set ::Stat(pty,spid)            [ set spid $spawn_id ]
        set ::Stat(pty,slave,name)      $spawn_out(slave,name)
        set ::Stat(pty,slave,fd)        $spawn_out(slave,fd)
        file link -symbolic  $::Stat(gps,path)  $::Stat(pty,slave,name)

        expect_background -i $::Stat(pty,spid) \
                          -re "AT.*?\[\r\n\]{1,2}" {
                                exp_send -i $::Stat(pty,spid) "OK\r"
                                puts stderr "AT sentence, OK sent"
                        } \004 {
                                puts stderr "$::Stat(pty,slave,name) ^D, reopen"
                                # respawn_pty 
                        } eof {
                                puts stderr "$::Stat(pty,slave,name) EOF, reopen"
                                # respawn_pty 
        }

        puts stderr "$::Stat(pty,slave,name) linked to $$::Stat(gps,path), open"
        parray ::Stat

        return $::Stat(pty,slave,name)
}

respawn_pty

proc udpEventHandler {sock} {
        set sentence [read $sock]
        set peer [fconfigure $sock -peer]
        puts stderr "$peer: [string length $sentence] {$sentence}"
        exp_send -i $::Stat(pty,spid) $sentence\r

        return
}

proc udp_listen {port} {
        set srv [udp_open $port]
        fconfigure $srv -buffering none -translation binary
        fileevent $srv readable [list ::udpEventHandler $srv]
        puts "Listening on udp port: [fconfigure $srv -myport]"
        return $srv
}

set gpssock [ udp_listen $::Stat(udp,port) ]


vwait forever
#end

The next script provides the nmea server:

#!/usr/bin/tclsh

package require Expect
package require udp


set ::Stat(pty,spid) {}
set ::Stat(gps,path) "/dev/gps1"
set ::Stat(udp,sock) [ udp_open 616162 ]
udp_conf $::Stat(udp,sock) localhost 616161


set fd [ open /dev/ttyUSB0 {RDWR NONBLOCK} ]
fconfigure $fd \
        -encoding binary -translation binary \
        -mode 9600,n,8,1 -handshake none

spawn -open $fd
set gps_spid $spawn_id

log_user 0
expect -i $gps_spid \
          -re {(\$GPRMC.*?\*[[:xdigit:]]{2})[\r]} {
                set sentence $expect_out(1,string)
                puts -nonewline $::Stat(udp,sock) $sentence
                flush $::Stat(udp,sock)

                puts stderr 1:$sentence
                exp_continue
        } -re {(\$GPGGA.*?\*[[:xdigit:]]{2})[\r]} {
                set sentence $expect_out(1,string)
                # puts -nonewline $::Stat(udp,sock) $sentence 
                # flush $::Stat(udp,sock)

                puts stderr 2:$sentence
                exp_continue
        } timeout {
                puts stderr timeout
                exp_continue
        } eof {
                puts stderr "EOF on GPSside"
        }

puts end

# vwait forever