#!/usr/bin/python

# tz_gh615.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       June 29, 2007           bar
#       June 30, 2007
#       July 1, 2007            bar
#       July 2, 2007            bar
#       July 3, 2007            bar     send_waypoints
#       July 4, 2007            bar     find likely COM port in registry
#       July 10, 2007           bar     --strip_spaces
#       July 29, 2007           bar     option to not put out the timed KML file (default yes)
#                                       option to put out kmz file
#                                       errorlevel 101 if something went wrong
#       August 17, 2007         bar     better notes about the watch's time zone situation
#       October 20, 2007        bar     move the file writing code to tz_gps
#       October 25, 2007        bar     altitudes are signed 16's
#       October 28, 2007        bar     extend tz_gps.a_track
#       November 18, 2007       bar     turn on doxygen
#       November 27, 2007       bar     insert boilerplate copyright
#       December 1, 2007        bar     use tzlib's elapsed_time for timing
#       January 5, 2008         bar     read_ghd_file()
#       January 6, 2008         bar     comment
#       May 4, 2008             bar     better duration and end-time for track string
#       May 17, 2008            bar     email adr
#       August 4, 2008          bar     linux serial port (/dev/ttyUSB... with /var/log/messages to tell us the port)
#                                       get the baud rate correct (we hope)
#       August 4, 2008          bar     --port_list (but remember that our method of finding the port on linux doesn't work unless the device is recently plugged in)
#       August 21, 2008         bar     make the linus usb finding logic work faster when it's not finding much
#       August 29, 2008         bar     basestring instead of StringType because of unicode strings and others
#       September 3, 2008       bar     linux ge
#       September 5, 2008       bar     uh, that's sleep(), not time.wait()
#                                       --tt
#       September 25, 2008      bar     catch a ge error
#       --eodstamps--
##      \file
#
#
#       Communicate with a GH615 GPS device and such-like.
#
#       TODO:
#
#           Add the data to the output files rather than replacing them.
#
#           Put all the track and profile information in .gpx <extensions> so that .gpx files can be used for history/database
#             July 29, 2007 maybe only profile info is lacking now
#
#           Cmd line to put waypoints to device (read from .txt .kml .gpx)
#
#           Put tracks to device (how?)
#
#           Figure out how to get partial tracks from the device (so aborted tracks can be restarted in the middle)
#
#           Find a way to recover when the device goes south. (the current logic only works sometimes)
#
#           Figure out what an \x83 command to the watch returns (satellite data?) (see b.py)
#
#

import  copy
import  os
import  re
import  struct
import  sys
import  time
import  traceback

have_win32                  = False
usb                         = None
if  sys.platform == 'win32' :

    import  win32api
    import  win32con
    import  pywintypes

    have_win32              = True

else :

    try :
        import  usb
    except ImportError :
        usb                 = None
    pass




import  tz_google_earth
import  tz_gps
import  tzlib
import  tz_parse_time



TEST_ME                     = False


SHOW_EXCEPTIONS             = False
SHOW_DEEP_EXCEPTIONS        = False
SHOW_ERROR_LOG              = False
SHOW_PROGRESS               = False

if  TEST_ME :
    SHOW_EXCEPTIONS         = True
    SHOW_DEEP_EXCEPTIONS    = True
    SHOW_ERROR_LOG          = True
    SHOW_PROGRESS           = False


RETRIES                     = 8


VALIDATE_PROFILE            = True


TEST_SEND_WAYPOINTS         = False


def hexify(s) :
    h   = [ "%02x" % ( ord(c) ) for c in s ]
    return(" ".join(h))



def checksum(s, i = 0, to = None) :
    """
        Run a longitudinal parity on the given string.
    """

    if  to == None :
        to  = len(s)

    cs  = 0
    for si in xrange(i, to) :
        cs ^= ord(s[si])

    return(cs)



msg_b1  = "\x02"


def u32(vs) :
    if  isinstance(vs, basestring) :
        return((ord(vs[0]) << 24) | (ord(vs[1]) << 16) | (ord(vs[2]) << 8) | ord(vs[3]))
    return(chr((vs >> 24) & 0xff) + chr((vs >> 16) & 0xff) + chr((vs >> 8) & 0xff) + chr(vs & 0xff))

def i32(vs) :
    if  isinstance(vs, basestring) :
        v   = (ord(vs[0]) << 24) | (ord(vs[1]) << 16) | (ord(vs[2]) << 8) | ord(vs[3])
        if  ord(vs[0]) & 0x80 :
            v   = v - 0x100000000L
        return(v)
    return(chr((vs >> 24) & 0xff) + chr((vs >> 16) & 0xff) + chr((vs >> 8) & 0xff) + chr(vs & 0xff))


def u16(vs) :
    if  isinstance(vs, basestring) :
        return((ord(vs[0]) << 8) | ord(vs[1]))
    return(chr((vs >> 8) & 0xff) + chr(vs & 0xff))

def i16(vs) :
    if  isinstance(vs, basestring) :
        v   = (ord(vs[0]) << 8) | ord(vs[1])
        if  ord(vs[0]) & 0x80 :
            v   = v - 0x10000
        return(v)
    return(chr((vs >> 8) & 0xff) + chr(vs & 0xff))


def u8(vs) :
    if  isinstance(vs, basestring) :
        return(ord(vs[0]))
    return(chr(vs & 0xff))

def i8(vs) :
    if  isinstance(vs, basestring) :
        v   = ord(vs[0])
        if  v & 0x80 :
            v   = v - 0x100
        return(v)
    return(chr(vs & 0xff))



def zstring(s, offset = 0) :
    try :
        return(s[offset : s.index('\0', offset)])
    except ValueError :
        pass

    return(s[offset:])





class   a_gh615_exception(Exception) :
        pass

class   a_gh615_timeout_exception(a_gh615_exception) :
        pass




def show_deep_exception() :
    if  SHOW_DEEP_EXCEPTIONS :
        e       = sys.exc_info()
        traceback.print_exception(e[0], e[1], e[2])
    pass





class   a_gh615_profile :

    def _clear(me) :
        me.device_name      = ""
        me.firmware_version = ""

        me.name             = ""            # A-Z + - _ and space are in the list of choices on the device

        me.age              = 0

        me.weight_pounds    = 0
        me.weight_kilos     = 0

        me.hite_inches      = 0
        me.hite_centimeters = 0

        me.birth_year       = 0
        me.birth_month      = 0
        me.birth_day        = 0

        me.sex              = 'M'

        me.trackpoint_count = 0
        me.waypoint_count   = 0



    def from_msg(me, msg) :
        if  len(msg) <= 69 :
            raise a_gh615_exception("short profile/info msg (%u)" % ( len(msg) ) )

        try :
            me.device_name      = zstring(msg, 0)
            me.firmware_version = zstring(msg, 25)
            me.name             = zstring(msg, 42)
            me.sex              = u8(msg[53])
            me.age              = u8(msg[54])
            me.weight_pounds    = u16(msg[55:57])
            me.weight_kilos     = u16(msg[57:59])
            me.hite_inches      = u16(msg[59:61])
            me.hite_centimeters = u16(msg[61:63])
            me.waypoint_count   = u8(msg[63])
            me.trackpoint_count = u8(msg[64])
            me.birth_year       = u16(msg[66:68])
            me.birth_month      = u8(msg[68])       + 1
            me.birth_day        = u8(msg[69])       - 1         # bug in watch?

            if  me.validate_profile :
                if  me.sex > 1 :
                    raise a_gh615_exception("Bad profile sex: %u" % ( me.sex ) )

                me.sex              = [ 'male', 'female' ][me.sex]

                if  not (1 <= me.birth_month <= 12) :
                    raise a_gh615_exception("Bad profile birth month: %u" % ( me.birth_month ) )

                if  not (1 <= me.birth_day   <= 31) :
                    raise a_gh615_exception("Bad profile birth day %u" % ( me.birth_day ) )

                #
                #   Fix the birth year in a rational way
                #
                if  0 <= me.birth_year <= 100 :
                    if  me.birth_year  <= 10 :
                        me.birth_year  += 100
                    me.birth_year      += 1900

                if  not (1900 <= me.birth_year <= 2010) :
                    raise a_gh615_exception("Bad profile year %u" % ( me.birth_year ) )

                #
                #   The device computes the age by using the low byte of birth year minus the current year, which is wrong in at least two ways.
                #   We'll simply figure the age.
                #
                byt     = time.mktime( [ 1970, me.birth_month, me.birth_day, 0, 0, 1, 0, 0, -1 ] )
                tm      = list(time.localtime())
                age     = tm[0] - me.birth_year
                ny      = tm[0]
                tm[0]   = 1970
                t       = time.mktime(tm)
                if  t < byt :
                    age -= 1

                if  not (0 <= age < 120) :
                    raise a_gh615_exception("Bad profile computed age: %u:%u" % ( age, me.age ) )

                me.age  = age

                pass
            pass

        except :
            show_deep_exception()
            me._clear()
            raise a_gh615_exception("Bad profile msg")

        me.birth_day           += 1

        try :
            me.__str__()                                                    # generate our exception if the data doesn't decode without fuss
        except :
            me._clear()
            raise a_gh615_exception("Odd profile msg (%u)" % ( len(msg) ) )
        pass



    def __init__(me, msg = None, validate_profile = VALIDATE_PROFILE) :
        me._clear()
        me.validate_profile = validate_profile
        if  msg :
            me.from_msg(msg)
        pass



    def __str__(me) :
        bm      = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ][me.birth_month - 1]

        return("%s %u year %s born %s %u %04u  %u:%u pounds:kilos  %u:%u inches:centimeters  %u waypoints  %u tracks  %s %s" % (
                me.name,
                me.age,
                me.sex,
                bm,
                me.birth_day,
                me.birth_year,
                me.weight_pounds,
                me.weight_kilos,
                me.hite_inches,
                me.hite_centimeters,
                me.waypoint_count,
                me.trackpoint_count,
                me.device_name,
                me.firmware_version
                )
               )
        pass



    pass        # a_gh615_profile





class   a_gh615_track(tz_gps.a_track)  :

    def clear(me) :
        tz_gps.a_track.clear(me)

        me.duration     = 0.0
        me.distance     = 0
        me.calories     = 0
        me.top_speed    = 0.0
        me.point_count  = 0



    def from_msg(me, msg_part) :
        try :
            #
            #
            #       Messed up if this date is from a different time zone or not in the same daylight savings situation as the PC this script runs on is currently set.
            #
            #       The watch returns the time in the time zone in which the track is started recording.
            #       And, I can't find any way to find out what that time zone is.
            #
            #       If anything can ever be done about this, don't forget that tz_parse_time.py now has a make_utime() routine - mktime for GMT.
            #
            #
            # print "when", 2000 + u8(msg_part[0]), u8(msg_part[1]), u8(msg_part[2]), u8(msg_part[3]), u8(msg_part[4]), u8(msg_part[5])

            me.when         = time.mktime( [ 2000 + u8(msg_part[0]), u8(msg_part[1]), u8(msg_part[2]), u8(msg_part[3]), u8(msg_part[4]), u8(msg_part[5]), 0, 0, -1 ] )
            me.duration     = u32(msg_part[ 6:10]) /  10.0
            me.distance     = u32(msg_part[10:14])
            me.calories     = u16(msg_part[14:16])
            me.top_speed    = u16(msg_part[16:18]) / 100.0
            me.point_count  = u32(msg_part[18:22])
            me.id_num       = u16(msg_part[22:24])

            me.points       = []

        except :
            show_deep_exception()
            me.clear()
            raise a_gh615_exception("Bad track info msg part (%u)" % ( len(msg_part) ) )

        try :
            me.__str__()                                                    # generate our exception if the data doesn't decode without fuss
        except :
            show_deep_exception()
            me.clear()
            raise a_gh615_exception("Odd track info msg part (%u)" % ( len(msg_part) ) )
        pass



    def __init__(me, id_or_msg_part, when = None, duration = 0.0, distance = 0, calories = 0, top_speed = 0, point_count = 0) :

        me.clear()

        if  isinstance(id_or_msg_part, basestring) :
            me.from_msg(id_or_msg_part)
        else :
            me.id_num       = id_num        + 0
            me.when         = when          + 0.0
            me.duration     = duration      + 0.0
            me.distance     = distance      + 0         # in meters
            me.calories     = calories      + 0
            me.top_speed    = top_speed     + 0.0       # in kilometers per hour
            me.point_count  = point_count   + 0

            me.points       = []
        pass



    def append(me, point) :
        me.points.append(point)



    def __str__(me) :
        return("%s  %03u  duration:%.1f:%u:%02u:%02u until %s distance:%u  calories:%u  top speed:%.2f  %u points" % ( time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(me.when)),
                                                                                                                       me.id_num,
                                                                                                                       me.duration,
                                                                                                                       int( me.duration / 3600),
                                                                                                                       int((me.duration / 60) % 60),
                                                                                                                       int( me.duration       % 60),
                                                                                                                       time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(me.when + me.duration)),
                                                                                                                       me.distance,
                                                                                                                       me.calories,
                                                                                                                       me.top_speed,
                                                                                                                       me.point_count
                                                                                                                     )
              )
        #######


    def __cmp__(me, other) :
        ms  = str(me)
        so  = str(other)

        return(cmp(ms, so))



    pass        # a_gh615_track





class   a_gh615_trackpoint(tz_gps.a_point)  :

    def __init__(me, msg, prev_gmt) :
        tz_gps.a_point.__init__(me)

        try :
            me.lat          = i32(msg[0:4])    / 1000000.0
            me.lon          = i32(msg[4:8])    / 1000000.0
            me.altitude     = i16(msg[8:10])   + 0
            me.speed        = u16(msg[10:12])  / 100.0
            me.heart_rate   = u8(msg[12])      + 0
            me.duration     = u16(msg[13:15])  / 10.0

            me.when         = prev_gmt + me.duration                        # the duration refers to time that's passed since the last point
        except :
            show_deep_exception()
            me.clear()
            raise a_gh615_exception("Bad trackpoint msg part (%u)" % ( len(msg) ) )

        try :
            me.__str__()                                                    # generate our exception if the data doesn't decode without fuss
        except :
            show_deep_exception()
            me.clear()
            raise a_gh615_exception("Odd trackpoint msg part (%u)" % ( len(msg) ) )
        pass


    pass        # a_gh615_trackpoint





class   a_gh615_waypoint(tz_gps.a_point)  :

    #
    #
    #   The device gives these values back as zero-based. We add one on them coming and and subtract one going to the device.
    #
    #   The device can store numbers up to 255, but all numbers from (zero-based) 36 and up are displayed as blank
    #
    #

    TYP_DOT                 =  1
    TYP_HOUSE               =  2
    TYP_TRIANGLE            =  3
    TYP_TUNNEL              =  4
    TYP_CROSS               =  5
    TYP_FISH                =  6
    TYP_LIGHT               =  7
    TYP_CAR                 =  8
    TYP_COMM                =  9
    TYP_RED_CROSS           = 10
    TYP_TREE                = 11
    TYP_BUS                 = 12
    TYP_COP_CAR             = 13
    TYP_TREES               = 14
    TYP_RESTAURANT          = 15
    TYP_SEVEN               = 16
    TYP_PARKING             = 17
    TYP_REPAIRS             = 18
    TYP_MAIL                = 19
    TYP_DOLLAR              = 20
    TYP_GOV_OFFICE          = 21
    TYP_CHURCH              = 22
    TYP_GROCERY             = 23
    TYP_HEART               = 24
    TYP_BOOK                = 25
    TYP_GAS                 = 26
    TYP_GRILL               = 27
    TYP_LOOKOUT             = 28
    TYP_FLAG                = 29
    TYP_PLANE               = 30
    TYP_BIRD                = 31
    TYP_DAWN                = 32
    TYP_RESTROOM            = 33
    TYP_WTF                 = 34
    TYP_MANTA_RAY           = 35
    TYP_INFORMATION         = 36
    TYP_BLANK               = 37

    def __init__(me, msg) :
        tz_gps.a_point.__init__(me)

        try :
            me.name         = zstring(msg[0:7]).strip()
            me.typ          = u8(msg[7])        + 1                         # in fact, the value from the device is from 0..35, it appears.  It's associated with a tiny icon.
            me.altitude     = i16(msg[8:10])    + 0
            me.lat          = i32(msg[10:14])   / 1000000.0
            me.lon          = i32(msg[14:18])   / 1000000.0
        except :
            show_deep_exception()
            me.clear()
            raise a_gh615_exception("Bad waypoint msg part (%u)" % ( len(msg) ) )

        try :
            me.__str__()                                                    # generate our exception if the data doesn't decode without fuss
        except :
            show_deep_exception()
            me.clear()
            raise a_gh615_exception("Odd waypoint msg part (%u)" % ( len(msg) ) )
        pass


    pass        # a_gh615_waypoint








def msg_length(s) :
    return(u16(len(s)))



class   a_gh615_comm :

    def __init__(me, io, timeout = 2.0) :

        me.io       = io
        me.timeout  = timeout

        me.ibuf     = ""

        me.log      = ""

        me.track    = None                                                  # used so that others can monitor get_track() progress - only valid during get_track()



    def flush_error_log(me) :
        s           = me.log
        me.log      = ""

        return(s)


    def rx(me, how_many = 1) :
        return(me.io.read(how_many))


    def read(me, how_many = 1, timeout = None) :
        timeout         = timeout or me.timeout

        t               = tzlib.elapsed_time()
        r               = ""
        while True :
            rr          = me.ibuf[0:how_many]
            me.ibuf     = me.ibuf[how_many:]
            how_many   -= len(rr)
            r          += rr


            rr          = me.rx(how_many)
            how_many   -= len(rr)
            r          += rr

            if  not how_many :
                break

            if  tzlib.elapsed_time() - t >= timeout :
                me.log += "read timeout\n"
                raise a_gh615_timeout_exception("read")
            pass

        # print "rxing", len(r), "%04x" % ( len(r) ), hexify(r)

        return(r)


    def rx_i_ready(me) :
        if  not me.io :
            raise a_gh615_exception("rx_i_ready no connection!")

        return(me.io.inWaiting() > 0)


    def rx_ready(me) :
        if  len(me.ibuf) > 0 :
            return(True)

        return(me.rx_i_ready())


    def flush_rx(me) :
        me.ibuf = ""
        while   me.rx_ready() :
            me.rx(1000)
        pass


    def write(me, what) :
        if  not me.io :
            raise a_gh615_exception("Write to no connection!")

        me.io.write(what)



    def close(me) :
        me.ibuf     = ""
        if  me.io :
            c       = me.io
            me.io = None
            try :
                c.close()
            except socket.error, msg :
                raise a_gh615_exception("Close error!")
            pass
        pass



    def blind_close(me) :
        try :
            me.close()
        except a_gh615_exception :
            pass
        pass









    def send_msg(me, s) :
        """ Messages start with 0x02, then have a u16 (big endian) message content length, then the content (starting with an 0x8x or 0x7x), then a 1 byte checksum. """

        if  len(s) >= 0xfffc :
            raise a_gh615_exception("Msg too long : %u" % ( len(msg) ) )

        s   = msg_length(s) + s
        s   = msg_b1        + s + u8(checksum(s))

        # print "sending", hexify(s)

        me.write(s)


    def recv_msg(me, som, timeout = None) :
        if  isinstance(som, basestring) :
            som     = [ som ]

        try :
            while True :
                r1  = me.read(1, timeout)
                # print "som", hexify(r1)
                if  len(r1) and (r1 in som) :

                    r   = me.read(2)            # get the msg length

                    ln  = u16(r)

                    # print "got lgnth", str(ln), u16(ln)

                    msg = me.read(ln)
                    cs  = u8(me.read(1))

                    if  cs != checksum(u16(ln) + msg) :
                        me.log += "bad checksum (%s should be %s)\n" % ( hexify(u8(cs)), hexify(u8(checksum(u16(ln) + msg))) )

                        me.ibuf = u16(ln) + msg + u8(cs)            # try again with the 1st byte dropped
                        break

                    if  som.index(r1) :
                        msg     = "alternate " + r1 + msg           # in all but the first allowed response, we'll put the response byte at the top of the message so the caller can tell that it's an unusual msg and what it is

                    return(msg)

                pass
            pass
        except a_gh615_timeout_exception :
            pass

        return(None)                                                # Return None to distinguish non-reception from a zero length message



    def try_to_get_back_in_contact(me) :
        me.send_msg('\x81')                                         # the watch goes away if comm is aborted while getting track points. this is an attempt to get it back without the user having to "ESC / Data link to PC" again
        me.send_msg('\x82')

        time.sleep(1.0)
        me.flush_rx()
        me.flush_error_log()


    def get_profile(me) :
        me.flush_error_log()
        me.flush_rx()
        for t in xrange(RETRIES) :
            me.send_msg('\x85')
            msg = me.recv_msg('\x85')
            if  msg and (len(msg) >= 70) :
                try :
                    return(a_gh615_profile(msg))

                except a_gh615_exception :
                    es  = "Profile with bad data!"
                    if  SHOW_EXCEPTIONS :
                        print "except", es
                    pass
                pass
            el  = me.flush_error_log()
            if  SHOW_ERROR_LOG :
                print "log:", el
            time.sleep(0.5)
            me.flush_rx()

        return(None)



    def get_waypoints(me) :
        me.flush_error_log()
        me.flush_rx()
        for t in xrange(RETRIES) :
            me.send_msg('\x77')
            msg = me.recv_msg('\x77')
            if  msg != None :
                if  not msg :
                    return( [] )                                # no waypoints

                try :
                    waypoints  = []
                    for i in xrange(0, len(msg), 18) :
                        p   = a_gh615_waypoint(msg[i:i+18])
                        waypoints.append(p)

                    return(waypoints)

                except a_gh615_exception :
                    es  = "Waypoint list with bad data!"
                    if  SHOW_EXCEPTIONS :
                        print "except", es
                    pass
                pass
            el  = me.flush_error_log()
            if  SHOW_ERROR_LOG :
                print "log:", el
            time.sleep(0.5)
            me.flush_rx()

        return(None)



    def send_waypoints(me, points) :
        """
            Send the given points to the device as waypoints.
            If a point has no name, then give it a number as best we can.
        """

        me.flush_error_log()
        me.flush_rx()

        n2snd       = 100

        cnt         = 0
        while points :
            scnt        = 0
            smsg        = ""
            for p in points[0:n2snd] :
                scnt   += 1

                if  not p.name :
                    s   = "%08x" % ( tzlib.blkcrc(0, str(p)) )              # this could actually be at least base 36, I think. The device handles lower case and is probably case-sensitive, but I've not checked
                else :
                    s   = p.name
                s       = s      + '\0\0\0\0\0\0'
                s       = s[0:6] + '\0'

                s      += u8(((p.typ or 1) - 1) % ( a_gh615_waypoint.TYP_BLANK ) )
                s      += u16(p.altitude or 0)
                s      += u32(int(p.lat * 1000000.0))
                s      += u32(int(p.lon * 1000000.0))

                smsg   += s
            smsg        = "\x76" + u16(scnt) + smsg

            ok          = False
            for t in xrange(RETRIES) :

                me.send_msg(smsg)
                msg = me.recv_msg('\x76', timeout = 15.0)
                if  msg != None :
                    if  len(msg) == 2 :
                        n       = u16(msg)
                        if  n  <= scnt :

                            ok      = True
                            cnt    += u16(msg)                              # note: the device can hold 100 waypoints, but it substitutes in to name-dupes when they go down to the device and counts them as having been sent
                            break

                        elif SHOW_EXCEPTIONS :
                            print "Bogus waypoint accept count: %u which could by up to %u" % ( n, scnt )
                        pass
                    elif SHOW_EXCEPTIONS :
                        print "Send waypoints reply len: %u should be 2" % ( len(msg or "") )
                    pass

                el  = me.flush_error_log()
                if  SHOW_ERROR_LOG :
                    print "log:", el
                time.sleep(0.5)
                me.flush_rx()

            if  not ok :
                break

            points      = points[n2snd:]

        return(cnt)



    def get_track_list(me) :
        me.flush_error_log()
        me.flush_rx()
        for t in xrange(RETRIES) :
            me.send_msg('\x78')
            msg = me.recv_msg('\x78')
            if  msg != None :
                if  not msg :
                    return( [] )                                # no tracks

                try :
                    tracks  = []
                    for i in xrange(0, len(msg), 24) :
                        t   = a_gh615_track(msg[i:i+24])
                        tracks.append(t)

                    return(tracks)

                except a_gh615_exception :
                    es  = "Track list with bad data!"
                    if  SHOW_EXCEPTIONS :
                        print "except", es
                    pass
                pass
            el  = me.flush_error_log()
            if  SHOW_ERROR_LOG :
                print "log:", el
            time.sleep(0.5)
            me.flush_rx()

        return(None)


    def get_track(me, track_info) :
        me.flush_error_log()
        me.flush_rx()
        me.track        = copy.deepcopy(track_info)

        when    = pwhen = me.track.when

        emsg    = smsg  = '\x80' + u16(1) + u16(track_info.id_num)                                                      # I fooled a bit with the u16(1) here, but could not make sense out of it. Non-1 values tend to get the second tracklist, but 50 or 100 got my 4th out of 4. So. Who knows.
        errs    = 0
        altr    = []
        while   errs < RETRIES :
            errs   += 1

            delay   = True

            me.send_msg(smsg)
            msg = me.recv_msg([ smsg[0] ] + altr)                                                                       # note: it seems that if the watch doesn't send the 8A response, it can lock up comm until [ ESC / Data link to PC ] is done on the watch

            if  msg :
                if  ('\x8a' in altr) and (msg == "alternate \x8a") :
                    #
                    #   what about if the tracks we've read are not the same number as the info's point count ????
                    #

                    track       = me.track
                    me.track    = None

                    return(track)

                if  ('\x80' in altr) and msg.startswith("alternate \x80") :
                    msg = msg[11:]

                try :
                    # print "ascii msg", tzlib.printable(msg)[0:300]
                    # print "hex   msg", hexify(msg)[0:300]

                    ti          =   a_gh615_track(msg)
                    ti.id_num   =   track_info.id_num
                    if  not cmp(ti, track_info) :
                        fidx    =   u16(msg[22:24])
                        cnt     =   u16(msg[24:26]) + 1 - fidx
                        mcnt    =   (len(msg) - 26) / 15
                        if  cnt ==  mcnt :
                            if  fidx        < len(me.track.points) :
                                me.track.points = me.track.points[:fidx]

                            if  fidx       == len(me.track.points) :
                                for i in xrange(26, len(msg), 15) :
                                    t       = a_gh615_trackpoint(msg[i:i+15], when)
                                    when    = t.when
                                    # print "adding point", str(t)
                                    me.track.append(t)
                                if  SHOW_PROGRESS :
                                    print cnt, "points added to", fidx, "points"

                                pwhen       = when                                                                      # remember the most recently corrent point time
                                smsg        = '\x81'                                                                    # after we've gotton one valid message, loop to get subsequent ones
                                emsg        = '\x82'
                                altr        = [ '\x8a', '\x80' ]
                                delay       = False
                                errs        = 0
                            else :
                                es  = "points for wrong track: msg has %u, we expect %u" % ( fidx, len(me.track.points) )
                                if  SHOW_EXCEPTIONS :
                                    print "except", es
                                raise a_gh615_exception(es)                                                             # note: this actually just pops out to the bad-msg exception a couple of code lines down
                            pass
                        else :
                            es          = "wrong number of points %u, msg has %u" % ( mcnt, cnt )
                            if  SHOW_EXCEPTIONS :
                                print "except", es
                            raise a_gh615_exception(es)                                                                 # note: this actually just pops out to the bad-msg exception a couple of code lines down
                        pass
                    else :
                        es  = "unmatched track\n  %s\n  %s" % ( str(track_info), ti )
                        if  SHOW_EXCEPTIONS :
                            print "except", es
                        raise a_gh615_exception(es)                                                                     # note: this actually just pops out to the bad-msg exception a couple of code lines down
                    pass
                except a_gh615_exception :                                                                              # either the first msg reply was bogus, or some subsequent one was
                    show_deep_exception()
                    smsg    = emsg                                                                                      # either send the first request (if we've not gotten the first response, or send the repeat (this poor guy was thrashing in his comm work, the recovery should be a normal request msg that tells which point to start with, and how many to get. And the response msgs should be tagged with the point number of the 1st point in the msg. Therefore, no 8A is required at the end.)
                    when    = pwhen                                                                                     # go back to when the most recently correct point was
                    altr    = [ '\x80' ]
                pass
            else :
                smsg    = emsg                                                                                          # either send the first request (if we've not gotten the first response, or send the repeat (this poor guy was thrashing in his comm work, the recovery should be a normal request msg that tells which point to start with, and how many to get. And the response msgs should be tagged with the point number of the 1st point in the msg. Therefore, no 8A is required at the end.)
                when    = pwhen                                                                                         # go back to when the most recently correct point was
                altr    = [ '\x80' ]

            if  delay :
                el  = me.flush_error_log()
                if  SHOW_EXCEPTIONS :
                    print "waiting", el
                time.sleep(0.5)
                me.flush_rx()
            pass

        return(None)



    def delete_trackpoints(me) :
        me.flush_error_log()
        me.flush_rx()
        for t in xrange(RETRIES) :
            me.send_msg('\x79\x00\x64')
            msg = me.recv_msg('\x79', timeout = 25.3)
            if  msg == "" :
                return(True)

            el  = me.flush_error_log()
            if  SHOW_ERROR_LOG :
                print "log:", el
            time.sleep(0.5)
            me.flush_rx()

        return(False)






    pass        # a_gh615_comm



def find_likely_COM_ports(list_level = 0) :
    """
        There is probably an API to do this, but finding it and using it seems to be really something.
    """

    ports   =   []

    if  sys.platform == 'win32' :

        def get_all_usb_friendly_names() :
            """
                There is probably an API to do this, but finding it and using it seems to be really something.
            """


            def search_keys(nms, k, n) :

                try :
                    ( obj, val )    = win32api.RegQueryValueEx(k, r"FriendlyName")
                    nms.append(str(obj))
                except :
                    # e   = sys.exc_info()
                    # print "fnam", e[0], e[1], n
                    pass

                i   = 0
                while True :
                    try :
                        kn  = "....."
                        s   = win32api.RegEnumKey(k, i)

                        kn  = n + "\\" + s
                        sk  = win32api.RegOpenKey(k, s, win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_READ)

                        search_keys(nms, sk, kn)

                        win32api.RegCloseKey(sk)
                    except :
                        # e   = sys.exc_info()
                        # print "fnam", e[0], e[1], n
                        break

                    i      += 1

                pass


            n   = r"SYSTEM\CURRENTCONTROLSET\ENUM\USB"
            k   = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, n, win32con.KEY_ENUMERATE_SUB_KEYS + win32con.KEY_READ)

            nms = []
            search_keys(nms, k, n)

            win32api.RegCloseKey(k)

            return(nms)



        names   =   get_all_usb_friendly_names()

        for n in names :
            fnd = 0
            g   = re.search("Prolific.*\(COM(\d+)\)", n)
            if  g :
                fnd = 1
                ports.append(int(g.group(1)))
            if  list_level + fnd >= 2 :
                print "%s%u" % ( ps, n )
            pass

        pass

    elif usb  :

        fd  = None
        for bus in usb.busses() :
            for dev in bus.devices :
                if  (dev.idVendor == 0x067b) and (dev.idProduct == 0x2303) :
                    # print dir(dev.configurations[0].interfaces[0][0]), dev.configurations[0].interfaces[0][0], dev.configurations[0].interfaces[0][0].interfaceProtocol
                    # print dev.configurations[0].interfaces[0][0].endpoints[0].address, dev.configurations[0].interfaces[0][0].endpoints[0].type
                    if  (dev.deviceClass == 0) and (dev.deviceSubClass == 0) and (dev.deviceProtocol == 0) :
                        # print "found bus:", bus.dirname, "device:", dev.filename
                        #
                        # ?     how to find which ttyUSB%u it is, and how to find which ttyS%u that is (done with "ln -b /dev/ttyUSB0 /dev/ttyS0" where -b makes a ~ backup of the old link or whatever it is)
                        #       OR BETTER YET: we now take the command line parm /dev/ttyUSB%u and it works,
                        #          so all we need to do is to tie the usb device to the ttyUSB
                        #       hwinfo has enough info to find out:
                        #          bus.dirname and dev.filename give the hwinfo usb.bus_number and usb.linux.device_number
                        #          then linux.sysfs_path looks like this:
                        #              linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.7/usb8/8-2/8-2.2/8-2.2:1.0'
                        #          and that ties back to a previous device that has:
                        #              linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.7/usb8/8-2/8-2.2/8-2.2:1.0/ttyUSB1/tty/ttyUSB1'
                        #              serial.device = '/dev/ttyUSB1'
                        #              serial.port = 1 (0x1)
                        #              linux.device_file = '/dev/ttyUSB1'
                        #
                        #        Also (later, after the devices have moved) dmesg (/var/log/messages) has this, looking from the bottom to the top:
                        #             [30309.568340] usb 8-2.2: new full speed USB device using ehci_hcd and address 36
                        #             [30309.660907] usb 8-2.2: configuration #1 chosen from 1 choice
                        #             [30309.661169] pl2303 8-2.2:1.0: pl2303 converter detected
                        #             [30309.661246] usb 8-2.2: pl2303 converter now attached to ttyUSB0
                        #          So it appears that the bus.dirname is in the "usb BUS.DIRNAME-2.2...
                        #             and the dev.filename is "address DEV.FILENAME"
                        #        !!!! code uses this method
                        #          Problem with this is that the log may have been flushed since the device was plugged in.
                        #
                        #        Also, The /sys/devices directory can be searched for the device. The files seem to have the information that lsusb and hwinfo output.
                        #          And, apparently, udev rules can be used to assign things to stable device names or whatever.
                        #
                        ps  = "Bus: %s  Device: %s" % ( str(bus.dirname), str(dev.filename) )
                        fnd = 0
                        if  not fd :
                            try :
                                fd  = tzlib.read_whole_text_file("/var/log/messages")
                            except IOError :
                                fd  = ""
                            pass
                        if  not fd :
                            break

                        ba  = str(int(str(bus.dirname),  10))
                        da  = str(int(str(dev.filename), 10))
                        fre = re.compile(r" usb " + ba + r"-\d[^\n]+? and address " + da + r".*? usb " + ba + r"-\d[^\n]+? now attached to (\S+)", re.DOTALL)
                        fa  = fre.findall(fd)
                        if  fa  :
                            ps  = "/dev/" + fa[-1]
                            ports.append(ps)
                            fnd = 1
                        if  list_level + fnd >= 2 :
                            print ps
                        pass
                    pass
                pass
            pass
        pass

    return(ports)





def read_ghd_file(file_name_or_file) :
    """ Return an array of a_track with the information in a .ghd file. """

    #                                         number of waypoints
    #                                            number of subfiles
    #                                               waypoint data
    #                                                   what's this date/time?
    #                                                                                                        first subfile header and all the rest of the file
    start_re    = re.compile(r"^GH6151234\x00(..)(..)(.*?)\d\d\d\d-\d\d-\d\d-\d{1,2}:\d{1,2}:\d{1,2}\0.{1,50}(\d\d\d:\d\d\d\d-\d{1,2}-\d{1,2}:\d{1,2}:\d{1,2}:\d{1,2}.*)", re.DOTALL)

    #                     ascii(z)-name alt lon   lat                 in same binary format as trackpoints, one after the other for as many as are spec'd
    waypoint_re = re.compile(r"........(..)(....)(....)", re.DOTALL)

    #                            subfile number
    #                                     year
    #                                                month
    #                                                          day
    #                                                                    hour
    #                                                                             minute
    #                                                                                       second
    #                                                                                                    y  m  d  h  m  s ???   trackpoint count
    #                                                                                                                              trackpoints and rest of file
    subfile_re  = re.compile(r"^(\d\d\d):(\d\d\d\d)-(\d{1,2})-(\d{1,2}):(\d{1,2}):(\d{1,2}):(\d{1,2})\0+(.)(.)(.)(.)(.)(.).{22}(..)(.*)$", re.DOTALL)


    file_name       = None
    if  isinstance(file_name_or_file, basestring) :
        fi          = open(file_name_or_file, "rb")
        fd          = fi.read()
        fi.close()
        file_name   = file_name_or_file
    else  :
        fd          = file_name_or_file.read()

    g           = start_re.match(fd)
    if  not g :
        raise ValueError("File not a .GHD file!")

    tracks      = []

    wpcnt       = ord(g.group(1)[0]) + 256 * ord(g.group(1)[1])
    subfcnt     = ord(g.group(2)[0]) + 256 * ord(g.group(2)[1])

    wpd         = g.group(3)


    fd          = g.group(4)

    fn          = 0
    while len(fd) > 16 :
        fn     += 1

        g       =   subfile_re.match(fd)
        if  not g :
            raise ValueError("Something wrong with a subfile (%u) %u [%s]!" % ( fn, len(fd), tzlib.c_string(fd[0:32]) ) )

        filn    = int(g.group(1))

        year    = int(g.group(2))
        month   = int(g.group(3))
        day     = int(g.group(4))
        hour    = int(g.group(5))
        minute  = int(g.group(6))
        second  = int(g.group(7))

        ft      = tz_parse_time.make_utime(month, day, year, hour, minute, second)
        tt      = ft

        v   = ord(g.group(8))
        if  v != year - 2000 :
            raise ValueErorr("Binary year bad on subfile (%u) %u is not %u!"    % ( fn, v, year   ) )

        v   = ord(g.group(9))
        if  v != month :
            raise ValueErorr("Binary month bad on subfile (%u) %u is not %u!"   % ( fn, v, month  ) )

        v   = ord(g.group(10))
        if  v != day :
            raise ValueErorr("Binary day bad on subfile (%u) %u is not %u!"     % ( fn, v, day    ) )

        v   = ord(g.group(11))
        if  v != hour :
            raise ValueErorr("Binary hour bad on subfile (%u) %u is not %u!"    % ( fn, v, hour   ) )

        v   = ord(g.group(12))
        if  v != minute :
            raise ValueErorr("Binary minute bad on subfile (%u) %u is not %u!"  % ( fn, v, minute ) )

        v   = ord(g.group(13))
        if  v != second :
            raise ValueErorr("Binary second bad on subfile (%u) %u is not %u!"  % ( fn, v, second ) )

        v   = g.group(14)
        rc  = ord(v[0]) + ord(v[1]) * 256

        fd  = g.group(15)

        pts = []

        for ri in xrange(rc) :
            #
            #       Note:   It appears the file has the duration and speed reversed in position from what the device puts out.
            #

            vals    = struct.unpack("<llHHHH", fd[ri * 16 : ri * 16 + 16])

            lat     = vals[0] / 1000000.0
            lon     = vals[1] / 1000000.0
            alt     = vals[2]
            dur     = vals[3] / 10.0
            hrt     = vals[4]
            spd     = vals[5] / 100.0

            ft     += dur

            pts.append(tz_gps.a_point(when = ft, lat = lat, lon = lon, altitude = alt, duration = dur, heart_rate = hrt, speed = spd))

        tracks.append(tz_gps.a_track(pts, id_num = fn - 1, when = tt, file_name = file_name))

        fd  = fd[rc * 16 : ]

        pass                            # loop for next subfile


    tracks  = tz_gps.color_tracks(tracks)


    waypoints   = []

    wn          = 0
    while wn < wpcnt :

        msg     = wpd[wn * 18 : wn * 18 + 18]

        name    = zstring(msg[0:7]).strip()

        typ     = ord(msg[7]) + 1                                       # in fact, the value from the device is from 0..35, it appears.  It's associated with a tiny icon.

        vals    = struct.unpack("<Hll", msg[8:])
        alt     = vals[0]
        lat     = vals[1] / 1000000.0
        lon     = vals[2] / 1000000.0

        waypoints.append(tz_gps.a_point(name = name, lat = lat, lon = lon, altitude = alt, typ = typ))

        wn     += 1


    return( ( waypoints, tracks ) )







def _program_version_str(ident) :
     return("V" + re.sub(r".*<program_version>([\d\.]+)</program_version>.*", r"\1", ident))




def main(ident) :

    import  subprocess

    import  TZCommandLineAtFile

    import  serial              # from pySerial


    if  ident :
        sys.argv.insert(1, ident)
        sys.argv.insert(1, '--ident')

    program_path    = sys.argv.pop(0)
    program_name    = os.path.split(program_path)[1]

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    ident       = ""
    port        = 0             # my COM port, not yours
    clear       = False
    do_ge       = False
    do_tt       = False
    strip_s     = False
    pure_kml    = False
    kml_ext     = ".kml"
    port_list   = 0


    help_str    = """
    %s (options) output_files_base_name

    I get the profile, waypoints and trackpoints from a GH615 GPS watch.

    I output the waypoints and trackpoints to 3 files:
        output_files_base_name.km(lz)           (Google maps/Earth input file)
        output_files_base_name.gpx              (Generic GPS data file)
        output_files_base_name.nmea             (GPS trackpoint stream)
    for use by other programs.

Options:

    --port  port_number     Set the COM port number
    --port_list             List possible ports (twice, list all available ports)
    --clear                 After successful output to files,
                              delete all "Activity Info"
                              trackpoints from GH615.
    --ge                    Show trackpoints in Google Earth after files written.
    --strip_spaces          Strip leading spaces on KML/GPX text lines.
    --pure_kml              Don't put out time information to KML.
    --kmz                   Output a KMZ file instead of KML.

    --version               Print the program version number.


""" % ( program_path )


    oi  = tzlib.array_find(sys.argv, [ "--help", "-?", "?", "-h", "/h", "/?", "?" ] )
    if  oi >= 0 :
        print help_str
        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--ident", "-i" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        if  (oi >= len(sys.argv)) or not len(sys.argv[oi]) :
            print "Program IDENT info not given!"
            sys.exit(101)
        ident       = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--version", "-v" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        print "version", _program_version_str(ident)
        if  not sys.argv :
            sys.exit(0)
        pass


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--port", "-p" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        if  (oi >= len(sys.argv)) or not len(sys.argv[oi]) :
            print "No COM port given!"
            sys.exit(102)
        port        = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--port_list" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        port_list  += 1


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--clear", "-c" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        clear       = True


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--pure_kml" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        pure_kml    = True


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--kmz", "-z" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        kml_ext     = ".kmz"


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--google_earth", "--ge", "-g" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        do_ge       = True


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--trodtrack", "--tt" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        do_tt       = True


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--strip_spaces", "-s" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        strip_s     = True



    if  len(sys.argv) < 1 :
        print "Please tell me a base name for the output files (--help for options)!"
        sys.exit(104)

    ofile_name  = sys.argv.pop(0)
    ifile_name  = ""

    if  ofile_name.lower().endswith(".ghd") :
        if  len(sys.argv) != 1 :
            print "I need an output file to convert to from the input .ghd file, %s!" % ( ofile_name )
            sys.exit(105)

        ifile_name  = ofile_name
        ofile_name  = sys.argv.pop(0)


    if  ifile_name.startswith('-') :
        print
        print
        print
        print "\aInputing from ", ifile_name, "   I hope you're sure about that name."
        print
        print
        print

    if  ofile_name.startswith('-') :
        print
        print
        print
        print "\aOutputing to ", ofile_name, "   I hope you're sure about that name."
        print
        print
        print

    if  len(sys.argv) :
        print "I don't understand %s (--help for options)!" % ( sys.argv )
        sys.exit(104)






    if  ifile_name :

        #
        #       Convert .ghd file to usuable format(s)
        #

        ( waypoints, tracks )   = read_ghd_file(ifile_name)

        un      = ""
        if  have_win32 :
            un  = win32api.GetUserName()

        tz_gps.write_gpx_file( ofile_name + ".gpx",  "From " + ifile_name + " by " + program_name, un, waypoints, tracks, strip_spaces = strip_s, timed = True)
        tz_gps.write_kml_file( ofile_name + kml_ext, "From " + ifile_name + " by " + program_name, un, waypoints, tracks, strip_spaces = strip_s, timed = True)
        tz_gps.write_nmea_file(ofile_name + ".nmea", "From " + ifile_name + " by " + program_name, un, waypoints, tracks, strip_spaces = strip_s, timed = True)

        sys.exit(0)



    if  port_list :
        find_likely_COM_ports(port_list)


    if  not port :
        port    = find_likely_COM_ports()
        if  port :
            print   "Using port", port[0],
            if  len(port) > 1 :
                print "found port", port,
            print
            port    = port[0]
        pass

    if  not port :
        print "Please tell me a COM port to use with the --port option (e.g. --port 2 )!"
        sys.exit(103)

    try :
        port    = int(port)
        cport   = port - 1
    except ValueError :
        cport   = port

    try :
        io  = serial.Serial(port = cport, baudrate = 57600, timeout = 0.001)                # serial.Serial() multiplies this by 1000 before passing to windows (This 1 mill is minimum for windows. I don't know about other OS's.)
    except serial.SerialException :
        print "Port " + str(port) + " cannot be opened!"
        sys.exit(111)

    me      = a_gh615_comm(io)

    failed  = False

    profile = me.get_profile()
    if  not profile :
        print "Trying to contact your device."
        print "You may need to take your device in to ... Configuration / Data Link to PC"
        me.try_to_get_back_in_contact()
        profile    = me.get_profile()

    if  not profile :
        print "Could not get profile - is it connected to the PC?"
    else :
        print profile

        all_points  = []

        waypoints   = me.get_waypoints()
        if  waypoints == None :
            print "Could not get waypoints - is your device in PC link mode?"
            failed  = True
        elif waypoints :

            all_points += waypoints

            print "Waypoints:"
            for p in waypoints :
                print " ", p
            print



        tracks          = []

        track_list      = me.get_track_list()
        if  track_list == None :
            print "Could not get track list - is your device in PC link mode?"
        else :
            if  track_list :
                print "Tracks:"
                for t in track_list :
                    print "  ", t
                print

            have_trackpoints    = False

            for t in track_list :
                track   = me.get_track(t)
                if  not track :
                    print "Failed to get track", t
                    failed      = True
                else :
                    tracks.append(track)
                    all_points         += track.points
                    have_trackpoints    = True

                    print
                    print
                    print
                    print "track received", track.point_count, len(track.points), "points from"
                    print "  ", track.points[0]
                    print " to"
                    print "  ", track.points[-1]
                    print
                    print
                    print
                pass
            pass

        if  all_points and ofile_name :
            tz_gps.write_gpx_file( ofile_name + ".gpx",  program_name, profile.name, waypoints, tracks, strip_spaces = strip_s, timed = True)
            tz_gps.write_kml_file( ofile_name + kml_ext, program_name, profile.name, waypoints, tracks, strip_spaces = strip_s, timed = True)
            tz_gps.write_nmea_file(ofile_name + ".nmea", program_name, profile.name, waypoints, tracks, strip_spaces = strip_s, timed = True)

            print "All files written."

            if  clear :
                print

                if  have_trackpoints :

                    if failed :
                        print "Something went wrong,"
                        print "so I have NOT cleared/deleted trackpoints from your device!"
                        sys.exit(198)

                    print "Clearing all trackpoints from your device. This may take some time..."

                    print
                    if  not me.delete_trackpoints() :
                        print "Waypoints and trackpoints are stored in files,"
                        print "but there was a problem comminicating with the device."
                        print "Trackpoints may not be cleared/deleted from your device!"
                        sys.exit(199)
                    else :
                        print "Trackpoints are cleared/deleted from your device."
                    pass

                else :
                    print "I found no trackpoints on your device,"
                    print "so I have not made any attempt to clear/delete them."

                pass

            elif TEST_SEND_WAYPOINTS and waypoints :

                import  random

                wpts        = []
                for i in xrange(10) :
                    wpts   += copy.deepcopy(waypoints)

                for i in xrange(len(wpts)) :
                    wpts[i].name    = "%03u" % ( i )
                    wpts[i].lat     = random.random() * 10 - 5.0
                    wpts[i].typ     = i # random.randint(0, 80)
                cnt = me.send_waypoints(wpts)
                print "Sent", cnt, "waypoints"

            pass

            dt      = 0
            if  do_ge :
                kfn = os.path.abspath(ofile_name + kml_ext)
                try :
                    ge  = tz_google_earth.a_google_earth()
                    ge.load_kml_file(kfn)
                except tz_google_earth.a_google_earth_exception :
                    try :
                        subprocess.Popen( [ "googleearth", kfn ] )
                        dt  = 3.0
                    except OSError :
                        print "Could not show trackpoints in Google Earth!"
                    pass
                except pywintypes.com_error :
                    print "Could not show trackpoints in Google Earth!"
                pass

            if  do_tt :
                gfn = os.path.abspath(ofile_name + ".gpx")
                try :
                    subprocess.Popen( [ "trodtrack", gfn ] )
                    dt  = 3.0
                except OSError :
                    print "Could not show trackpoints in TrodTrack!"
                pass

            if  dt :
                time.sleep(dt)                                      # give a chance for ge or trodtrack to fuss about already being open
            pass

        else :
            print
            print "No points found, so I have not output any file data."
            if  clear :
                print "And I have not cleared/deleted trackpoints from your device."

            pass

        pass

    print me.flush_error_log()

    me.blind_close()

    if  failed or not profile :
        sys.exit(101)

    pass


if  __name__ == '__main__' :

    main("")


#
#
# eof
