#!/usr/pkg/bin/python3.13
#
# This code is generated by scons.  Do not hand-hack it!
# This file is Copyright 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!
# Codacy D203 and D211 conflict, I choose D203
# Codacy D212 and D213 conflict, I choose D212
"""Display GPS output.  Hexify it if necessary."""

from __future__ import absolute_import, print_function, division

import argparse
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.27.5'
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


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


if __name__ == '__main__':
    usage = '%(prog)s [OPTIONS]'
    epilog = ('BSD terms apply: see the file COPYING in the distribution root'
              ' for details.')

    parser = argparse.ArgumentParser(usage=usage, epilog=epilog)
    parser.add_argument(
        '-?',
        action="help",
        help='show this help message and exit'
    )
    parser.add_argument(
        '-D',
        '--debug',
        dest='debug',
        default=0,
        type=int,
        help='Set level of debug. Must be integer. [Default %(default)s]',
    )
    parser.add_argument(
        '-p', '--packetizer',
        action='store_true',
        dest='packetizer',
        default=False,
        help="Set packetizer mode",
    )
    parser.add_argument(
        '-s', '--speed',
        dest='speed_str',
        default=None,
        help=('Set port speed. '
              ' Optionally parity (N, O, E) and stop bits (1, 2)'),
    )
    parser.add_argument(
        '-t', '--typeflag',
        action='store_true',
        dest='typeflag',
        default=False,
        help="Select packetizer mode with type and length",
    )
    parser.add_argument(
        '-V', '--version',
        action='version',
        version="%(prog)s: Version " + gps_version + "\n",
        help='Output version to stderr, then exit',
    )
    parser.add_argument(
        'target',
        help='File or Serial port to read from',
    )

    options = parser.parse_args()

    options.speed = None
    options.parity = None
    options.stopbits = None
    if options.speed_str:
        if options.speed_str[-2] in ('N', 'E', 'O'):
            options.parity = options.speed_str[-2]
            options.stopbits = int(options.speed_str[-1])
            options.speed_str = options.speed_str[:-2]
        options.speed = int(options.speed_str)

    # adjust debug level
    options.debug += BASELEVEL
    if options.packetizer or options.typeflag:
        options.rawmode = False
    else:
        options.rawmode = True

    try:
        if "rfcomm" in options.target:     # Bluetooth special case
            s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM,
                              socket.BTPROTO_RFCOMM)
            s.connect((options.target, 1))
            tty = s.fileno()
        else:                            # Ordinary device
            try:
                tty = os.open(options.target, os.O_RDWR)
            except Exception as e:
                print("gpscat: ERROR: %s.\n" % e)
                sys.exit(1)

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

        if not options.rawmode:
            getter = sniffer.Device(tty)
            getter.register_report(reporter)
        seqno = 0
        while True:
            rlist, wlist, xlist = select.select([tty], [], [])
            if rlist == [tty]:
                if options.rawmode:
                    buf = os.read(tty, NMEA_MAX)
                    if not buf:
                        break
                    print(hexdump(buf))
                else:
                    (length, ptype, packet, counter) = getter.get()
                    seqno += 1
                    if 0 == length:
                        break
                    if options.typeflag:
                        try:
                            print('%d (%d@%d): %s\n' % (ptype, length,
                                  counter - length, hexdump(packet)))
                        except BrokenPipeError:
                            # stdout piped to a program that died.
                            # Like "head" does by default.
                            # die quietly
                            sys.exit(1)
                        except Exception as e:
                            sys.stderr.write("%s\n" % e)
                            sys.exit(1)
                    else:
                        print(hexdump(packet) + "\n")
    except KeyboardInterrupt:
        if options.rawmode:
            print("\n")
        sys.exit(0)

# Local variables:
# mode: python
# end:
# vim: set expandtab shiftwidth=4
