Ravn::

GPSClient class

Parse output from a running gpsd daemon to retrieve GPS data. See: gpsd.io/client-howto.html

Constants

GPSD_HOST

The host running gpsd to connect to.

GPSD_PORT

The default gpsd socket port.

SOCKET_TIMEOUT

Timeout for socket reads.

Attributes

host R

The host running gpsd.

port R

The port gpsd is listening on.

socket R

The raw socket to gpsd.

Public Class Methods

new( host: GPSD_HOST, port: GPSD_PORT )

Create a new gpsd parser. Defer connection.

# File lib/ravn/gps_client.rb, line 33
def initialize( host: GPSD_HOST, port: GPSD_PORT )
        @socket = nil
        @host   = host
        @port   = port
end

Public Instance Methods

connect()

Connect to a running gpsd daemon.

# File lib/ravn/gps_client.rb, line 82
def connect
        @socket = TCPSocket.new( self.host, self.port )
        self.socket.puts '?WATCH={"enable":true}'
        self.socket.flush
        self.get_daemon_info
end
fetch_state()

Retrieve current GPSD time and location data, after the initial connection is made.

# File lib/ravn/gps_client.rb, line 54
def fetch_state
        return unless self.socket
        self.socket.puts '?POLL;'
        self.socket.flush

        ready, _, _ = IO.select( [self.socket], nil, nil, SOCKET_TIMEOUT )
        data = nil

        if ready && ! ready.empty?
                data = self.parse_response( self.socket.gets )
        end

        return unless data

        gps = self.gps_position( data )
        payload = { time: self.gps_time( data ) }
        payload.merge!( gps: gps ) if gps

        return payload

rescue Errno::EPIPE, SocketError => err
        self.log.error "Error while polling gpsd: %p (attempting to reconnect)" % [ err.message ]
        self.connect
        return nil
end

Protected Instance Methods

get_daemon_info()

GPSD is a simple, lined based protocol. Pull initial connection info off the socket.

# File lib/ravn/gps_client.rb, line 133
def get_daemon_info
        ready, _, _ = IO.select( [self.socket], nil, nil, SOCKET_TIMEOUT )
        while ready && ! ready.empty?
                data = JSON.parse( self.socket.gets )
                if data[ 'class' ] == "VERSION"
                        self.log.warn "Connected to GPSD v%s" % [ data['release'] ]
                end
                ready, _, _ = IO.select( [self.socket], nil, nil, SOCKET_TIMEOUT )
        end
end
gps_position( data )

Construct and return a ‘sys.gps.position’ message, or nil if there’s no GPS fix. Munge the GPS message to be more CoT-like before delivery.

# File lib/ravn/gps_client.rb, line 103
def gps_position( data )
        level = data[ 'mode' ]

        # gpsd docs claim 1 == no fix, but there appears to be a 0 state as
        # well.
        #
        if level <= 1
                self.log.warn "No GPS fix yet."
                return nil
        end

        return {
                pos: data.values_at( 'lat', 'lon' ),
                hae: data[ 'alt' ] || 0,
                ce:  level == 3 ? 3.0 : 15.0
        }
end
gps_time( data )

Return the current time from the PPS signal.

# File lib/ravn/gps_client.rb, line 95
def gps_time( data )
        return Time.parse( data['time'] ).to_i
end
parse_response( raw )

Parse and return the relevant information from the gpsd response, or nil if it isn’t what we’re expecting.

# File lib/ravn/gps_client.rb, line 124
def parse_response( raw )
        data = JSON.parse( raw )
        return unless data[ 'class' ] == "POLL"
        return data.dig( 'tpv', 0 )
end