#!/usr/bin/env python
#
"Display GPS output.  Hexify it if necessary."

# This file is Copyright (c) 2010 by the GPSD project
# SPDX-License-Identifier: BSD-2-clause

# This code runs compatibly under Python 2 and 3.x for x >= 2.
# Preserve this property!
from __future__ import absolute_import, print_function, division

import getopt
import os
import select
import socket
import string
import sys
import termios

# pylint wants local modules last
try:
    import gps
    import gps.packet as sniffer
    from gps.misc import BINARY_ENCODING
except ImportError as e:
    sys.stderr.write(
        "gpscat: can't load Python gps libraries -- check PYTHONPATH.\n")
    sys.stderr.write("%s\n" % e)
    sys.exit(1)

gps_version = '3.20'
if gps.__version__ != gps_version:
    sys.stderr.write("gpscat: ERROR: need gps module version %s, got %s\n" %
                     (gps_version, gps.__version__))
    sys.exit(1)


# The spec says 82, but some receivers (TN-200, GSW 2.3.2) output 86 characters
# the Skyrtaq S2525F8 emits 100 chars
NMEA_MAX = 102

# Lowest debug level at which packet getter begins to emit messages, minus one
BASELEVEL = sniffer.LOG_IO

highhalf_latch = True

PRINTABLE = set(bytearray(string.printable, encoding=BINARY_ENCODING))


def hexdump(st):
    "Convert string to hex string"
    dmp = ""
    for ch in bytearray(st):  # bytearray gets array of ints in Python 2 and 3
        if ch in PRINTABLE:
            dmp += chr(ch)
        else:
            dmp += "\\x%02x" % ch
    return dmp


debuglevel = 0


def reporter(errlevel, mesg):
    "Report errors, depending on log level"
    if errlevel <= debuglevel:
        sys.stdout.write(mesg)


def printusage():
    "Print usage"
    sys.stderr.write("usage: gpscat [-D debuglevel] [-h] [-p] [-s speed] [-t] "
                     "[-V] serial-port\n")


if __name__ == '__main__':
    try:
        try:
            (options, arguments) = getopt.getopt(sys.argv[1:], "hps:tD:V")
        except getopt.GetoptError as msg:
            print("gpscat: " + str(msg))
            raise SystemExit(1)

        speed = None
        parity = None
        stopbits = None
        rawmode = True
        typeflag = False
        for (switch, val) in options:
            if switch == '-D':
                debuglevel = BASELEVEL + int(val)
            elif switch == '-h':
                printusage()
                raise SystemExit(0)
            elif switch == '-p':
                rawmode = False
            elif switch == '-s':
                if val[-2] in ('N', 'E', 'O'):
                    parity = val[-2]
                    stopbits = int(val[-1])
                    val = val[:-2]
                speed = int(val)
            elif switch == '-t':
                typeflag = True
                rawmode = False
            elif switch == '-V':
                sys.stderr.write("gpscat: Version %s\n" % gps_version)
                sys.exit(0)

        if len(arguments) != 1:
            printusage()
            raise SystemExit(1)

        if "rfcomm" in arguments[0]:     # Bluetooth special case
            s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM,
                              socket.BTPROTO_RFCOMM)
            s.connect((arguments[0], 1))
            tty = s.fileno()
        else:                            # Ordinary device
            tty = os.open(arguments[0], os.O_RDWR)

        if speed is not None:
            (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = \
                termios.tcgetattr(tty)
            try:
                ispeed = ospeed = eval("termios.B%d" % speed)
            except AttributeError:
                sys.stderr.write("gpscat: unknown baud rate %d\n" % speed)
                raise SystemExit(1)
            if stopbits:
                cflag &= ~termios.CSIZE
                cflag |= (termios.CS8, termios.CS7)[stopbits - 1]
            if parity:
                if parity == 'N':
                    iflag &= ~termios.PARENB
                    iflag &= ~termios.INPCK
                elif parity == 'O':
                    iflag |= termios.INPCK
                    cflag |= termios.PARENB
                    cflag |= termios.PARODD
                elif parity == 'E':
                    iflag |= termios.INPCK
                    cflag |= termios.PARENB
                    cflag &= ~termios.PARODD
            termios.tcsetattr(tty, termios.TCSANOW,
                              [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])

        if not rawmode:
            getter = sniffer.new()
            sniffer.register_report(reporter)
        seqno = 0
        while True:
            rlist, wlist, xlist = select.select([tty], [], [])
            if rlist == [tty]:
                if rawmode:
                    buf = os.read(tty, NMEA_MAX)
                    if not buf:
                        break
                    sys.stdout.write(hexdump(buf))
                else:
                    (length, ptype, packet, counter) = getter.get(tty)
                    seqno += 1
                    if length == 0:
                        break
                    if typeflag:
                        sys.stdout.write(repr(ptype) + " (" + repr(length) +
                                         "@" + repr(counter - length) + "): " +
                                         hexdump(packet))
                        sys.stdout.write("\n")
                    else:
                        sys.stdout.write(hexdump(packet) + "\n")
    except KeyboardInterrupt:
        if rawmode:
            sys.stdout.write("\n")
        raise SystemExit(0)

# Local variables:
# mode: python
# end:
