#!/usr/bin/python
#
# This file is Copyright (c) 2010 by the GPSD project
# BSD terms apply: see the file COPYING in the distribution root for details.
#
# Hotplug script for gpsd by Eric S. Raymond, March 2005
# This script is part of the gpsd distribution: see http://gpsd.berlios.de
# Can be called like "gpsd.hotplug [add|remove] /dev/ttyUSB0" for test
# purposes.
import sys, time, os, syslog, glob, socket, stat

CONTROL_SOCKET = os.getenv('GPSD_SOCKET') or "/var/run/gpsd.sock"
GPSD_OPTIONS = os.getenv('GPSD_OPTIONS') or ""

WHEREAMI = __file__

def gpsd_control_connect(complain=True):
    "Acquire a connection to the GPSD control socket."
    if not os.path.exists(CONTROL_SOCKET):
        syslog.syslog("socket %s doesn't exist" % CONTROL_SOCKET)
        return None
    try:
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
        sock.connect(CONTROL_SOCKET)
    except socket.error, msg:
        if complain:
            syslog.syslog("socket %s creation failure: %s" % (CONTROL_SOCKET, msg))
        if sock:
            sock.close()
        sock = None
    #else:
    #    syslog.syslog("socket %s created OK" % CONTROL_SOCKET)
    return sock

def gpsd_control(action, argument):
    "Pass a command to gpsd; start the daemon if not already running."
    syslog.syslog("gpsd_control(action=%s, arg=%s)" % (action, argument))
    connect = gpsd_control_connect(complain=False)
    if connect:
        syslog.syslog("reached a running gpsd")
    elif action == 'add':
        gpsdcmd = "gpsd %s -F %s" % (GPSD_OPTIONS, CONTROL_SOCKET)
        syslog.syslog("launching %s" % gpsdcmd)
        os.system(gpsdcmd)
        connect = gpsd_control_connect(complain=True)
    if not connect:
        syslog.syslog("can't reach gpsd")
        return None
    # We've got a live connection to the gpsd control socket.  No
    # need to parse the response, because gpsd will lock on to the
    # device if it's really a GPS and ignore it if it's not.
    if action == 'add':
        # Force the group-read & group-write bits on, so gpsd will still be
        # able to use this device after dropping root privileges.
        os.chmod(argument, stat.S_IMODE(os.stat(argument)[stat.ST_MODE])|0660)
        connect.sendall("+%s\r\n" % argument)
        connect.recv(12)
    elif action == 'remove':
        connect.sendall("-%s\r\n" % argument)
        connect.recv(12)
    elif action == 'send':
        connect.sendall("%s\r\n" % argument)
        connect.recv(12)
    connect.close()
    #syslog.syslog("gpsd_control ends")
    return action

def hotplug(action, devpath):
    #syslog.syslog("ACTION=%s DEVPATH=%s" % (action,devpath))
    if not devpath:
        syslog.syslog("No device")
    else:
        subnodes = glob.glob("/sys" + devpath + "/*")
        subnodes = map(os.path.basename, subnodes)
        subnodes = filter(lambda s: s.startswith("ttyUSB"), subnodes) 
        if len(subnodes) == 0:
            syslog.syslog("no ttyUSB device under " + devpath)
            return
        elif len(subnodes) > 1:
            syslog.syslog("too many ttyUSB devices under " + devpath)
            return
        else:
            tty = "/dev/" + subnodes[0]

            syslog.syslog("waiting for " + tty)
            while not os.path.exists(tty):
                time.sleep(1)
            syslog.syslog(tty + " has gone active")

            gpsd_control(action, tty)

            remover = os.getenv("REMOVER")
            #syslog.syslog("REMOVER=%s" % remover)
            fp = open(remover, "w")
            fp.write(WHEREAMI + " remove " + tty)
            fp.close()
            os.chmod(remover, stat.S_IRUSR|stat.S_IXUSR|stat.S_IRGRP|stat.S_IXGRP)
            return

if __name__ == '__main__':
    # In recent versions of udev, the gpsd script runs in series with
    # the task that creates the real /dev/ttyUSB0 device
    # node. Unfortunately, the gpsd script runs BEFORE the creation of
    # the node, and the node is not created until after you kill the
    # gpsd script, because the gpsd script waits forever for the node
    # to appear.
    # 
    # This is a race condition, and is best fixed by running the
    # actual wait/hotplug portion in the background.
    pid = os.fork()
    if not pid:
        syslog.openlog('gpsd.hotplug', 0, syslog.LOG_DAEMON)
        try: 
            if len(sys.argv) == 1:	# Called as hotplug script
                hotplug(os.getenv("ACTION"), os.getenv("DEVPATH"))
            else:			# Called by hand for testing
                gpsd_control(sys.argv[1], sys.argv[2])
        except:
            (exc_type, exc_value, exc_traceback) = sys.exc_info()
            syslog.syslog("gpsd.hotplug: exception %s yields %s" % (exc_type, exc_value))
            raise exc_type, exc_value, exc_traceback
        #syslog.syslog("gpsd.hotplug ends")
        syslog.closelog()
        
