#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2008 Miguel Filgueiras mig@ncc.up.pt Universidade do Porto
#
#    This program is free software; you can redistribute it and/or modify
#      it under the terms of the GNU General Public License as published by
#      the Free Software Foundation; either version 2 of the License, or
#      (at your option) any later version.
#
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
#
#      You should have received a copy of the GNU General Public License
#      along with this program.
#
#  File: garmin.tcl
#  Last change:  23 October 2008
#
# Includes contributions by
# - David Gardner (djgardner_AT_users.sourceforge.net) marked
#    "DJG contribution"
#

 # implemented using
 #- "Garmin GPS Interface Specification", May 6 1998, 001-00063-00 Rev. 1
 #  kindly provided by Garmin Corporation
 # updated to follow the descriptions available from www.garmin.com:
 #  "Garmin GPS Interface Specification", April 13 1999, 001-00063-00 Rev. 2
 #  "Garmin GPS Interface Specification", December 6 1999, 001-00063-00 Rev. 3
 #  "Garmin GPS Interface Specification", April 24 2004, 001-00063-00 Rev. A
 #  "Garmin GPS Interface Specification", September 16 2004, 001-000063-00
 #   Rev. B
 #  "Garmin GPS Interface Specification", May 19 2006, 001-000063-00 Rev. C
 #
 #- GPS 15H & 15L TECHNICAL SPECIFICATIONS, 190-00266-01, Rev. D, February 2006
 #

 # Garmin USB protocol is implemented assuming that the USB port is driven by
 #  the garmin_gps Linux kernel driver by Hermann Kneissel and part of the
 #  official 2.6 Linux kernel releases

# logging the communication: see serial.tcl

# get rid of identical packets that follow one another in the same input
#  operation. This is to solve a problem with Garmin 12Map.
set TestRepeated 0

#### no configurable values below this line!

### state of the communication
set Request idle
set Jobs ""

 # serial
set SInState idle
set SInPacketState start
set SOutBusy 0
set SRecACKs 0
set SRecNAKs 0
set PkInState idle
set PkLastData ""
set FirstACKNAK 1

 # general
set NoInProc 1
set GetPVT 0
set STextPVT 0

##### opening USB port

proc OpenUSBFailed {} {
    # open USB port to use the Garmin USB protocol, and log file if needs be
    #  initialise USB driver
    # return 1 on failure
    global SERIALPORT USBFILE GUSB USBPackets USBMissing USBBuff Eof MESS \
	USBHdrNeeded USBRecs

    if { ! [file exists $SERIALPORT] || \
	     ! [file readable $SERIALPORT] || \
	     [catch {set USBFILE [open $SERIALPORT r+]}] || \
	     [regexp {^-} $USBFILE] } {
	GMMessage [format $MESS(badserial) $SERIALPORT]
	return 1
    }
    fconfigure $USBFILE -blocking 0 -translation binary
    # initialise USB driver
    #  change to USB mode
    puts -nonewline $USBFILE \
	[DataToStr "long long long long" \
	     [list $GUSB(LAYERID,PRIVATE) $GUSB(PRIV,PID,SET,MODE) \
		  4 $GUSB(MODE,NATIVE)]]
    flush $USBFILE
    #  driver can be set to debug mode with the packet
    #    $GUSB(LAYERID,PRIVATE) $GUSB(PRIV,PID,SET,DEBUG) 4 0xffff

    # send start session request to receiver
    puts -nonewline $USBFILE \
	[DataToStr "long long long" \
	     [list $GUSB(LAYERID,TRANSPORT) \
		  $GUSB(PID,TRANSPORT,START,SESSION,REQ) 0]]
    flush $USBFILE
    # read and discard anything there is to be read
    # should have waited for a session started packet at the transport level
    after 10
    read $USBFILE
    # initialise input and open log file
    set Eof 0
    set USBPackets "" ; set USBMissing 0 ; set USBHdrNeeded 12 ; set USBBuff ""
    set USBRecs 0
    fileevent $USBFILE readable ReadUSB
    OpenSerialLog
    return 0
}

##### closing the serial or USB connection

proc CloseConnection {} {
    # close the connection after a turn off command or a change from
    #  the serial or USB Garmin protocols
    # receiver types and info are restored to the original lists
    #  as a different receiver may be connected next
    global GPSState Eof GPSProtocol USBFILE GUSB SRLFILE NoGarmin \
	RECTYPES RECINFO ORIGRECTYPES ORIGRECINFO

    Log "CC> closing down..."
    set GPSState offline
    set Eof 1
    ResetSerial
    if { $GPSProtocol == "garmin_usb" } {
	# change USB driver to serial mode
	Log "CC> changing to serial mode..."
	puts -nonewline $USBFILE \
	    [DataToStr "long long long long" \
		 [list $GUSB(LAYERID,PRIVATE) $GUSB(PRIV,PID,SET,MODE) \
		      4 $GUSB(MODE,GARMIN,SERIAL)]]
	flush $USBFILE
	close $USBFILE
    } else {
	close $SRLFILE
    }
    DisableGPS
    set NoGarmin 1
    set RECTYPES $ORIGRECTYPES ; set RECINFO $ORIGRECINFO
    Log "CC> closed\n"
    return
}

proc ReopenSerialPortFailed {baud} {
    # close and open the serial port
    # receiver types and info are kept assuming no change of receiver
    global Eof InBuff Polling SRLFILE SERIALPORT tcl_platform

    set Eof 1
    ResetSerial
    close $SRLFILE
    if { ! [file exists $SERIALPORT] || \
	     ! [file readable $SERIALPORT] || \
	     [catch {set SRLFILE [open $SERIALPORT r+]}] || \
	     [regexp {^-} $SRLFILE] } { return 1 }
    switch $tcl_platform(platform) {
	unix {
	    set Polling 0 ; set InBuff ""
	    fconfigure $SRLFILE -blocking 0 -mode $baud,n,8,1 \
		-translation binary
	    fileevent $SRLFILE readable ReadChar
	}
	windows {
	    # Tcl/Tk 8.0p2 does not support I/O from/to serial ports
	    set Polling 1 ; set InBuff ""
	    fconfigure $SRLFILE -blocking 0 -mode $baud,n,8,1 \
		-translation binary
	    # after 0 ReadPollChar
	    after 0 ReadChar
	}
    }
    set Eof 0
    return 0
}

##### low-level read procedures

# USB protocol

proc ReadUSB {} {
    # read bytes and form a complete Garmin USB packet
    # complete packets are kept in the list $USBPackets and
    #  proc UseUSBInput called to process them when needed
    global USBFILE Eof USBPackets NoInProc GUSB USBMissing USBHdrNeeded \
	USBBuff USBPId USBRecs RPID

    if { $Eof } { return }
    if { $USBMissing == 0 } {
	while 1 {
	    # read (rest of) USB packet header
	    if { [catch {set buff [read $USBFILE $USBHdrNeeded]}] || \
		 [set n [string length $buff]] == 0 } {
		# no more data to be read
		return
	    }
	    if { $n != $USBHdrNeeded } {
		Log "RU> only $n bytes not $USBHdrNeeded for header"
		set USBHdrNeeded [expr $USBHdrNeeded-$n]
		return
	    }
	    set USBHdrNeeded 12
	    foreach "layerid USBPId USBMissing" \
		[UnPackData [split $buff ""] "long long long"] { break }
	    Log "RU> layerid=$layerid, pid=$USBPId, length=$n"
	    if { $layerid == $GUSB(LAYERID,TRANSPORT) } {
		Log "RU> transport layer packet discarded"
		if { $USBMissing != 0 } {
		    if { $USBMissing > 0 } { read $USBFILE $USBMissing }
		    set USBMissing 0
		}
		continue
	    }
	    if { $layerid != $GUSB(LAYERID,APPL) || \
		     [catch {set USBPId $RPID($USBPId)}] } {
		Log "RU> bad layer id or pid; discarding all data to be read"
		read $USBFILE
		set USBMissing 0
		return
	    }
	    set USBBuff ""
	    break
	}
    }
    # read USB data
    set pdata [read $USBFILE $USBMissing]
    append USBBuff $pdata
    if { [set nn [string length $pdata]] != $USBMissing } {
	Log "RU> packet data, only $nn bytes not $USBMissing"
	set USBMissing [expr $USBMissing-$nn]
	return
    }
    Log "RU> complete packet got: length $nn"
    lappend USBPackets [list $USBPId $USBBuff]
    set USBMissing 0
    if { $USBRecs } {
	if { $USBPId == "XfrCmpl" } {
	    set USBRecs 0
	    if { $NoInProc } { after 0 UseUSBInput }
	}
    } elseif { $USBPId == "Records" } {
	set USBRecs 1
    } elseif { $NoInProc } { after 0 UseUSBInput }

    if { [set Eof [eof $USBFILE]] } {
	Log "RU> at eof"
	set GPSState offline
	close $USBFILE
    }
    return
}

# line-oriented protocols

proc ProcLineChar {char} {
    # process char received when expecting a line of input
    # call $ProcProcLine to process line (as string) when getting a line-feed
    # discard carriage-returns
    global ProcProcLine LInBuffer SInPacketState LineXOR

    if { [binary scan $char "c" dec] != 1 } {
	Log "PLC> scan failed: char=$char"
	return
    }
    set dec [expr ($dec+256)%256]
    if { $dec == 10 } {
	if { [string length $LInBuffer] != 0 } {
	    $ProcProcLine $LInBuffer $LineXOR
	    set LInBuffer ""
	    set LineXOR 0
	}
    } elseif { $dec != 13 } {
	append LInBuffer $char
	set LineXOR [expr $LineXOR ^ $dec]
    }
    return
}

##### link layer

# USB protocol

proc UseUSBInput {} {
    # use packets read from USB channel in list $USBPackets
    # loop until either a Records packet was detected by proc ReadUSB
    #  or no more packets were queued
    global USBPackets NoInProc USBRecs

    set NoInProc 0
    while 1 {
	set queue $USBPackets ; set USBPackets ""
	foreach pak $queue {
	    foreach "pid pdata" $pak {}
	    set pdata [split $pdata ""]
	    LogBytes "UUI> got packet pid=$pid; contents:" $pdata
	    ProcInPacket $pid $pdata
	    update
	}
	if { $USBRecs || $USBPackets == "" } { break }
    }
    set NoInProc 1
    return
}

proc SendUSBPacket {pid types vals} {
    # send a Garmin USB packet
    #  $pid is the packet identifier name
    #  $types is list of types aligned with
    #  $vals a list of values
    # assume that the packet is actually sent and call proc SentPacket
    #  unless the size exceeds 64K-1
    global USBFILE GUSB PID Eof

    if { $Eof } {
	Log "SUP> at eof: not sending packet $pid"
	return
    }
    if { [catch {set pdata [DataToStr $types $vals]}] } {
	Log "SUP> BUG, bad data: pid=$pid\n\ttypes=$types\nvals=$vals"
	return
    }
    if { [set size [string length $pdata]] > 65535 } {
	Log "SUP> too much data ($size>65535)"
	return
    }
    set pak [DataToStr "long long long" \
		 [list $GUSB(LAYERID,APPL) $PID($pid) $size]]
    append pak $pdata
    LogBytes "SUP> sending packet $pid with:" [split $pak ""]
    puts -nonewline $USBFILE $pak
    flush $USBFILE
    # to avoid a too deep recursion do not use a direct call
    after 0 "SentPacket $pid"
    return
}

# serial protocol

proc SendSPacket {pid types vals} {
    # initialize a send packet action to serial channel
    # wait for termination of a previous similar operation
    #  $pid is the packet identifier name
    #  $types is list of types aligned with
    #  $vals a list of values
    # uses the following global variable that may be reset elsewhere:
    #  $SOutBusy a flag set if a packet is being sent; it is
    #             cleared by DoSendSPacket (for ACK/NAK packets)
    #             or by ProcACKNAK (after a valid ACK is received)
    global SInState SOutBusy PID ACK NAK \
	    SOutPID SOutBData SOutDSum SOutBPID SOutSize Eof

    if { $Eof } {
	Log "SSP> at eof: not sending packet $pid"
	return
    }
    if { $SOutBusy } {
	after 50 "SendSPacket $pid {$types} {$vals}"
	return
    }
    if { [catch {set SOutBPID $PID($pid)}] } {
	Log "SSP> bad PID: $pid"
	return
    }
    set SOutBData [PackData $types $vals]
    if { [set SOutSize [llength $SOutBData]] > 255 } {
	Log "SSP> too much data ($SOutSize>255)"
	return
    }
    Log "SP> sending packet $pid with:"
    set SOutBusy 1 ; set SOutDSum 0 ; set SOutPID $pid
    foreach ch $SOutBData {
	binary scan $ch "c" v
	incr SOutDSum $v
	Log "SSP>   [expr ($v+256)%256]"
    }
    DoSendSPacket
    return
}

proc DoSendSPacket {} {
    # do the actual sending of the packet initiated by SendSPacket;
    # wait for termination of get operations in progress unless the
    #  packet is an ACK/NAK one
    # this proc may be called for re-sending the packet if a NAK is
    #  received
    global SInPacketState SInState SOutBusy DLE DLECHR ETX ACK NAK \
	    SOutPID SOutBData SOutDSum SOutBPID SOutSize SRLFILE

    if { $SOutBPID!=$ACK && $SOutBPID!=$NAK && $SInState == "get" } {
	after 50 DoSendSPacket
        return
    }
    Log "DSSP> sending packet $SOutPID"
    if { $SOutBPID!=$ACK && $SOutBPID!=$NAK } {
	set SInPacketState start ; set SInState put
    }
    set datasum [expr (-$SOutDSum-$SOutBPID-$SOutSize)&255]
    if { [catch {puts -nonewline $SRLFILE \
	              [binary format ccc $DLE $SOutBPID $SOutSize]}] } {
        Log "DSSP> error writing to $SRLFILE"
        set SOutBusy 0
        if { $SOutBPID!=$ACK && $SOutBPID!=$NAK } { set SInState idle }
	return
    }
    if { $SOutSize == $DLE } {
	puts -nonewline $SRLFILE "$DLECHR"
    }
    foreach b $SOutBData {
	puts -nonewline $SRLFILE "$b"
	if { $b == $DLECHR } {
	    puts -nonewline $SRLFILE "$DLECHR"
	}
    }
    if { $datasum == $DLE } {
	puts -nonewline $SRLFILE "$DLECHR"
    }
    puts -nonewline $SRLFILE [binary format "ccc" $datasum $DLE $ETX]
    flush $SRLFILE
    Log "DSSP> size=$SOutSize, checksum=$datasum"
    if { $SOutBPID==$ACK || $SOutBPID==$NAK } { set SOutBusy 0 }
    return
}

proc SendACKNAK {what pid} {
    # send an ACK or NAK packet
    #  $what is either ACK or NAK
    #  $pid is packet id name to not-/acknowledge
    global PID PDTYPE

    SendSPacket $what $PDTYPE($what) $PID($pid)
    return
}

proc FixACKNAKType {data} {
    # inspect incoming ACK/NAK packet data to decide on the data type
    #  to be used, as suggested by Klaus Ethgen (Klaus@Ethgen.de)
    # Garmin specifications versions < 3 have a 16-bit integer
    #  while version 3 has a byte
    global FirstACKNAK PDTYPE

    set FirstACKNAK 0
    if { [llength $data] == 1 } {
	set t byte
    } else { set t int }
    set PDTYPE(ACK) $t ; set PDTYPE(NAK) $t
    return
}

proc ProcACKNAK {pid data} {
    # process incoming ACK or NAK packet
    #  $pid is packet id name of the packet (in {ACK, NAK, error})
    #  $data is its data
    # after sending a packet, in case of an ACK for a packet id different
    #  from the one sent, or a NAK the packet will be re-sent; if an error
    #  (expecting an ACK/NAK but got a different packet id) the state is
    #  reset; otherwise the packet is assumed to have been received
    #  successfully and SentPacket is called
    # when the first ACK/NAK packet is received the protocol data type for
    #  these packets is fixed to be either a 16-bit int or a byte
    global SOutBusy SOutPID SOutBPID SRecACKs SRecNAKs PDTYPE MESS \
	    PkLastPID FirstACKNAK

    set PkLastPID -1
    switch $pid {
	ACK {
	    if { $FirstACKNAK } {
		FixACKNAKType $data
	    }
	    # only one byte is relevant even if there are more
	    set bpid [expr [UnPackData $data $PDTYPE(ACK)]&255]
	    if { $SOutBusy } {
		incr SRecACKs
		if { $bpid != $SOutBPID } {
		    Log "PAN> ACK for wrong packet $bpid, expected $SOutPID"
		    DoSendSPacket
		} else {
		    SentPacket $SOutPID
		    set SOutBusy 0
		}
	    } else {
		Log "PAN> ACK for $bpid when output is idle"
	    }
	}
	NAK {
	    if { $FirstACKNAK } {
		FixACKNAKType $data
	    }
	    # only one byte is relevant even if there are more
	    set bpid [expr [UnPackData $data $PDTYPE(NAK)]&255]
	    if { $SOutBusy } {
		incr SRecNAKs
		if { $bpid != $SOutBPID } {
		    Log "PAN> NAK for wrong packet $bpid, expected $SOutPID"
		}
		DoSendSPacket
	    } else {
		Log "PAN> NAK for $bpid when output is idle"
	    }
	}
	default {
	    # $SOutBusy must be 1
	    GMMessage $MESS(noACKNAK)
	    # Log "PAN> $pid when sending $SOutPID; resending"
	    # DoSendSPacket
	    Log "PAN> $pid when sending $SOutPID; resetting"
	    ResetSerial
	}
    }
    return
}

proc ProcChar {char} {
    # process character seen in the serial port
    # the following global variables control what is going on:
    #   $SInState  in {idle, get, put}
    #              when idle, discards all input unless $GetPVT is set
    #              the difference between get and put is that the put-state
    #               only expects ACK/NAK packets and always calls ProcACKNAK,
    #               while the get-state calls either ProcInPacket or ProcACKNAK
    #              PVT data packets are always processed by ProcInPacket
    #   $SInPacketState  in {block, start, got1stDLE, gotPID}
    #              a packet is considered finished only when a single DLE
    #               followed by a ETX is read in; the packet size is used
    #               only for checking the consistency of the data
    #   $Jobs      should be set to "", and will be a list of
    #               background jobs started for $Request
    #   $GetPVT    is set if PVT data packets are expected
    global SInBuffer SInState SInPacketState SInSum SInCount GetPVT \
	    SeenDLE PacketID PacketData NotACKNAK PID RPID DLE ETX \
	    Polling PrdDATA PkInState

    if { [binary scan $char "c" dec] != 1 } {
	Log "PC> scan failed: char=$char"
	return
    }
    set dec [expr ($dec+256)%256]
    Log "PC> got $dec; state=$SInState, $SInPacketState"
    if { $SInState == "idle" && ! $GetPVT } {
	    Log "PC> idle: discarded $dec" ; return
    }
    switch $SInPacketState {
	block {
	    Log "PC> bug: called when blocked!"
	}
	start {
	    if { $dec == $DLE } {
		set SInPacketState got1stDLE
		Log "PC> got 1st DLE"
	    } else {
		Log "PC> got $dec instead of DLE"
	    }
	}
	got1stDLE {
	    if { [catch {set PacketID $RPID($dec)}] } {
		# this covers the cases $dec in {$DLE, $ETX}
		Log "PC> wrong PID code=$dec; restarting"
		set SInPacketState start
	    } else {
		set NotACKNAK [expr $dec!=$PID(ACK) && $dec!=$PID(NAK)]
		Log "PC> packet: $PacketID"
		set SInPacketState gotPID
		set SInBuffer ""
		set SInSum $dec ; set SInCount 0 ; set SeenDLE 0
	    }
	}
	gotPID {
	    if { $SeenDLE } {
		if { $dec != $DLE } {
		    Log "PC> got last DLE"
		    if { $dec == $ETX } {
			Log "PC> got ETX"
			if { [PacketOK] } {
			    set SInPacketState block
			    if { $NotACKNAK } {
				SendACKNAK ACK $PacketID
				ProcInPacket $PacketID "$PacketData"
		            } else {
				ProcACKNAK $PacketID "$PacketData"
			    }
			} else {
			    if { $NotACKNAK } {
				SendACKNAK NAK $PacketID
			    }
			}
		    } else {
			Log "PC> got $dec instead of ETX"
			if { $NotACKNAK } {
			    SendACKNAK NAK $PacketID
			}
		    }
		    set SInPacketState start
		} else {
		    set SeenDLE 0
		    lappend SInBuffer $char
		    incr SInSum $dec ; incr SInCount
		}
	    } elseif { $dec == $DLE } {
		set SeenDLE 1
	    } else {
		lappend SInBuffer $char
		incr SInSum $dec ; incr SInCount
	    }
	}
    }
    return
}

proc PacketOK {} {
    # test consistency of packet just read in (mainly size and checksum)
    global SInBuffer SInSum SInCount PacketID PacketData

    set PacketData ""
    if { $PacketID == "error" } { return 0 }
    set size [expr $SInCount-2]
    binary scan [lindex $SInBuffer 0] "c" expsize
    set expsize [expr ($expsize+256)%256]
    if { $expsize != $size } {
	Log "PO> bad length $size instead of $expsize"
	return 0
    }
    if { [expr $SInSum&255] != 0 } {
	Log "PO> bad checksum [expr $SInSum&255]"
	return 0
    }
    set PacketData [lrange $SInBuffer 1 $size]
    return 1
}

##### application layer: input

proc ProcInPacket {pid data} {
    # process incoming packet
    #  $pid is packet identifier name
    #  $data is list of bytes (as TCL characters)
    # uses the following global variables that may be set elsewhere:
    #  $PkInState   should be set externally either to idle (discarding
    #                packets) or to name of protocol the incoming packet is
    #                is expected to conform to
    #  $GetPVT      is set if PVT data packets are expected
    global PkInState PkInPrevState PkInData PkInRTsData PkInCount CurrPSPID \
	    PDTYPE SPckts GetPVT PkLastPID PkLastData Jobs \
	    TestRepeated PrdDATA Request

    if { $pid == "PVTData" } {
	# PVT data protocol
	if { ! $GetPVT } {
	    Log "PP> discarding PVT packet"
	    return
	}
	set p $CurrPSPID(PVTData)
	ProcPVTData $p [UnPackData $data $PDTYPE($p)]
	return
    }
    if { $TestRepeated && \
	    $pid == $PkLastPID && $data == $PkLastData } {
	Log "PP> discarding repeated packet for $pid"
	return
    }
    set PkLastPID $pid ; set PkLastData $data
    incr SPckts
    switch $PkInState {
	idle {
	    Log "PP> discarding $pid packet"
	}
	N/A {
	    Log "PP> asking for non-available protocol"
	    set PkInState idle
	}
	T001 {
	    # changing baud rate (not sure this is the T001 protocol)
	    # step 3: get proposed baud rate
	    if { [GoodPID $pid BaudAcptData] } {
		# $data is proposed baud with an error of +-5%
		set baud 0
		if { [set pbaud [UnPackData $data long]] >= 18240 } {
		    foreach b "19200 38400 57600 115200 230400" {
			if { abs($pbaud-$b) <= 0.05*$b } {
			    set baud $b
			    break
			}
		    }
		}
		if { $baud == 0 } {
		    # do noting: keep baud rate at the 9600 default
		    Log "PP> proposed baud not accepted: $pbaud"
		    AbortComm baudchgfailed
		} else {
		    # accept anything else
		    after 100
		    if { [ReopenSerialPortFailed $baud] } {
			AbortComm baudchgfailed
		    } else {
			set PkInState idle
			set Request chgbaudcf=1
			after 0 "SendData chgbaudcf"
		    }		    
		}
		return
	    }
	}
	A000 {
	    # product data protocol
	    if { [GoodPID $pid PrdData] } {
		# save product data
		set PrdDATA $data
		# wait for the Protocol Capability packet
		set PkInState A001
		# the data will be dealt with either when A001 terminates
		# (see below) or fails to show up (see proc AbortComm)
		return
	    }
	}
	A001 {
	    # protocol capability protocol
	    if { $pid == "PrdExtData" } {
		# this data is to be discarded; keep
		#  waiting for the protocol capability packet
		return
	    }
	    if { [GoodPID $pid PArray] } {
		# the Product Data information must be processed now
		# kill timeout alarm
		after cancel [lindex $Jobs 0]
		set PkInState idle
		EndInProt A000 [UnPackData $PrdDATA $PDTYPE(PrdData)]
		EndInProt A001 [UnPackData $data $PDTYPE(PArray)]
	    }
	}
	A010 {
	    # device command protocol 1
	    if { [GoodPID $pid CmdData] } {
		set PkInState idle
		EndInProt A010 [UnPackData $data int]
	    }
	}
	A100 {
	    # WP transfer protocol
	    ExpectRecords $pid $data WPData
	}
	A100EXF {
	    # expecting end of transfer on A100 protocol
	    EndOfTransfer $pid $data WP A100 $PkInData
	}
	D100 -  D101 -  D102 -  D103 -  D104 -  D105 -  D106 -
	D107 -  D108 -  D109 -  D110 -  D150 -  D151 -  D152 -  D154 -  D155 {
	    # WP data; product specific
	    if { $PkInPrevState == $CurrPSPID(RT) } {
		if { $pid == "RTHeader" } {
		    lappend PkInRTsData $PkInData \
			"[UnPackData $data $PDTYPE($CurrPSPID(RTHeader))]"
		    set PkInData ""
		    if { $PkInCount == 1 } {
			set PkInState ${PkInPrevState}EXF
		    } else { incr PkInCount -1 }
		    return
		}
		set d RTWPData
		set nolk [string compare $PkInPrevState A201]
	    } else {
		set d WPData ; set nolk 1
	    }		
	    if { [GoodPID $pid $d] } {
		lappend PkInData [UnPackData $data $PDTYPE($PkInState)]
		if { $PkInCount == 1 } {
		    set PkInState ${PkInPrevState}EXF
		} else {
		    incr PkInCount -1
		    if { ! $nolk } {
			set PkInState $CurrPSPID(RTLinkData)
		    }
		}
	    }
	}
	A200 -  A201 {
	    # RT transfer protocol (without/with RT link data)
	    ExpectRecords $pid $data RTHeader
	    set PkInRTsData ""
	}
	A200EXF {
	    # expecting end of transfer on A200 protocol
	    lappend PkInRTsData $PkInData
	    EndOfTransfer $pid $data RT A200 $PkInRTsData
	}
	A201EXF {
	    # expecting end of transfer on A201 protocol
	    lappend PkInRTsData $PkInData
	    EndOfTransfer $pid $data RT A201 $PkInRTsData
	}
	D200 -  D201 -  D202 {
	    # RT header; product specific
	    if { [GoodPID $pid RTHeader] } {
		lappend PkInRTsData "[UnPackData $data $PDTYPE($PkInState)]"
		set PkInData ""
		if { $PkInCount == 1 } {
		    Log "PP> RT header as last element of RT protocol"
		    set PkInState ${PkInPrevState}EXF
		} else {
		    incr PkInCount -1
		    set PkInState $CurrPSPID(RTWPData)
		}
	    }
	}
	D210 {
	    # RT link type; product specific
	    if { $pid == "RTHeader" } {
		lappend PkInRTsData $PkInData \
			"[UnPackData $data $PDTYPE($CurrPSPID(RTHeader))]"
		set PkInData ""
		if { $PkInCount == 1 } {
		    set PkInState ${PkInPrevState}EXF
		} else {
		    incr PkInCount -1
		    set PkInState $CurrPSPID(RTWPData)
		}
		return
	    }
	    if { [GoodPID $pid RTLinkData] } {
		lappend PkInData "[UnPackData $data $PDTYPE($PkInState)]"
		if { $PkInCount == 1 } {
		    Log "PP> RT link as last element of RT"
		    set PkInState ${PkInPrevState}EXF
		} else {
		    incr PkInCount -1
		    set PkInState $CurrPSPID(RTWPData)
		}
	    }
	}
    	A300 {
	    # TR transfer protocol
	    ExpectRecords $pid $data TRData
	}
	A301 -  A302 {
	    # TR transfer protocol
	    ExpectRecords $pid $data TRHeader
	}
	A300EXF {
	    # expecting end of transfer on A300 protocol
	    EndOfTransfer $pid $data TR A300 $PkInData
	}
	A301EXF -  A302EXF {
	    # expecting end of transfer on A301, A302 protocols
	    regsub {EXF$} $PkInState "" prot
	    EndOfTransfer $pid $data TR $prot $PkInData
	}
	D300 -  D301 -  D302 -  D303 -  D304 {
	    # TR data; product specific
	    if { $pid == "TRHeader" } {
		set PkInState $CurrPSPID(TRHeader)
		ProcInPacket $pid $data
		return
	    } elseif { [GoodPID $pid TRData] } {
		lappend PkInData \
			[list data [UnPackData $data $PDTYPE($PkInState)]]
		if { $PkInCount == 1 } {
		    set PkInState ${PkInPrevState}EXF
		} else { incr PkInCount -1 }
	    }
	}
	D310 -  D311 -  D312 {
	    # TR header; product specific
	    if { [GoodPID $pid TRHeader] } {
		lappend PkInData \
			[list header [UnPackData $data $PDTYPE($PkInState)]]
		if { $PkInCount == 1 } {
		    set PkInState ${PkInPrevState}EXF
		} else {
		    set PkInState $CurrPSPID(TRData)
		    incr PkInCount -1
		}
	    }
	}
    	A500 {
	    # AL transfer protocol
	    ExpectRecords $pid $data ALData
	}
	A500EXF {
	    # expecting end of transfer on A500 protocol
	    EndOfTransfer $pid $data AL A500 $PkInData
	}
	D500 -  D501 -  D550 -  D551 {
	    # AL data; product specific
	    if { [GoodPID $pid ALData] } {
		lappend PkInData "[UnPackData $data $PDTYPE($PkInState)]"
		if { $PkInCount == 1 } {
		    set PkInState ${PkInPrevState}EXF
		} else { incr PkInCount -1 }
	    }
	}
	D600 {
	    # date and time initialization protocol
	    if { [GoodPID $pid DtTmData] } {
		set PkInState idle
		EndInProt D600 [UnPackData $data $PDTYPE($CurrPSPID(DtTmData))]
	    }
	}
	D700 {
	    # position initialization protocol
	    if { [GoodPID $pid PosnData] } {
		set PkInState idle
		EndInProt D700 [UnPackData $data $PDTYPE($CurrPSPID(PosnData))]
	    }
	}
	A906 {
	    # LAP transfer protocol (from receiver)
	    ExpectRecords $pid $data LAPData
	}
	A906EXF {
	    # expecting end of transfer on A906 protocol
	    EndOfTransfer $pid $data LAP A906 $PkInData
	}
	D906 {
	    # LAP data; product specific
	    if { [GoodPID $pid LAPData] } {
		lappend PkInData [UnPackData $data $PDTYPE($PkInState)]
		if { $PkInCount == 1 } {
		    set PkInState ${PkInPrevState}EXF
		} else { incr PkInCount -1 }
	    }
	}
	A011 -
	A101 -
	A400 -  A600 -  A650 -  A700 -
	default {
	    Log "$PkInState not supported"
	    set PkInState idle
	}
    }
    return
}

proc ExpectRecords {pid data type} {
    # check if records packet received and set global variables if yes
    #  for protocol $PkInState
    global PkInData PkInCount PkInState PkInPrevState CurrPSPID

    if { [GoodPID $pid Records] } {
	set PkInData ""
	if { [set PkInCount [UnPackData $data int]] } {
	    set PkInPrevState $PkInState
	    set PkInState $CurrPSPID($type)
	} else {
	    set PkInState ${PkInState}EXF
	}
    }
    return
}

proc EndOfTransfer {pid data wh prot recs} {
    # end of transfer after records for protocol $prot
    #  $recs is data collected from the records
    global PkInData

    if { [GoodPID $pid XfrCmpl] } {
	if { [GoodCMD [UnPackData $data int] Xfr$wh] } {
	    EndInProt $prot $recs
	    set PkInState idle
	}
    }
    return
}

proc GoodPID {pid wanted} {
    # check that packet id name is what is wanted

    if { $pid != $wanted } {
	BadPacket "PID $pid instead of $wanted"
	return 0
    }
    return 1
}

proc GoodCMD {cmd wanted} {
    # check that command code is what is wanted
    global CMD

    if { $cmd != $CMD($wanted) } {
	BadPacket "CMD $cmd not good for $wanted"
	return 0
    }
    return 1
}

### application layer: output

proc SendData {type args} {
    # start transfer to receiver
    # first packet sent will, in serial mode, hopefully be ACK-ed, what
    #  will fire up (in sequence) ProcChar, ProcACKNAK, SentPacket; in
    #  USB mode SentPacket will be called after the packet is sent
    #   $type in {product, abort, turnOff, get, put, start, stop, chgbaud,
    #             chgbaudcf}
    #   $args void unless
    #       $type==get: in {WP RT TR AL PosnData DtTmData}; not yet
    #                    in {Prx}
    #       $type==put and 1st arg in {WP RT TR PosnData DtTmData}; not yet
    #                    in {AL Prx}
    #                   2nd arg is list of indices
    #       $type in {start, stop}: in {PVT}
    # set global GPSOpResult to 1 (error) if put operation fails
    global PDTYPE PRTHNID PkInState SOutBusy CurrPSPID CMD Command CommArgs \
	PkOutState PkOutData PkOutCount PkOutStart PkOutWhat PkTRSegs \
	PkOutSaved PkOutInfo RTIdNumber RTWPoints RTStages TRTPoints \
	TRSegStarts PrdDATA MESS PTRHNID ProcSendPacket PROTNOOUTPUT \
	GPSOpResult
    # DJG contribution
    global RTNextNumber AutoNumRts RTSendNumber
    #--

    set Command $type ; set CommArgs $args
    switch $type {
	product {
	    set PrdDATA ""
	    $ProcSendPacket PrdReq $PDTYPE(PrdReq) 0
	}
	abort {
	    $ProcSendPacket CmdData int [list $CMD(Abort)]
	}
	turnOff {
	    $ProcSendPacket CmdData int $CMD(TurnOff)
	    # do not wait for an answer
	    CloseConnection
	}
	chgbaud {
	    # step 1: turn off all requests
	    $ProcSendPacket RqstData int 0
	}
	chgbaudcf {
	    # step 4: send ping for confirmation
	    $ProcSendPacket CmdData int $CMD(AckPing)
	}
	start {
	    # $args in {PVT}
	    $ProcSendPacket CmdData int $CMD(Start$args)
	}
	stop {
	    # $args in {PVT}
	    $ProcSendPacket CmdData int $CMD(Stop$args)
	}
	get {
	    # $args in {WP RT TR AL PosnData DtTmData}
	    #  not implemented: {Prx}
	    $ProcSendPacket CmdData int $CMD(Xfr$args)
	}
	put {
	    set putwhat [lindex $args 0]
	    set ixs [lindex $args 1]
	    foreach z $PROTNOOUTPUT {
		if { [lindex $z 0] == $putwhat } {
		    if { [set z [lindex $z 1]] == "" } {
			Log "SD> put $putwhat not implemented"
			set GPSOpResult 1
			return
		    }
		    foreach zz $z {
			foreach "zt zid" $zz {}
			if { ! [catch {set zc $CurrPSPID($zt)}] && \
				 $zc == $zid } {
			    Log "SD> put: $zt protocol $zid not implemented"
			    set GPSOpResult 1
			    return
			}
		    }
		}
	    }
	    set PkOutWhat $putwhat
	    set p $CurrPSPID($putwhat)
	    switch $putwhat {
		WP {
		    set PkOutStart XfrWP
		    switch $p {
			A100 {
			    set PkOutData $ixs
			    set PkOutCount [llength $ixs]
			    set PkOutState $CurrPSPID(WPData)
			    $ProcSendPacket Records int $PkOutCount
			}
			default {
			    Log "protocol $p not implemented"
			    return
			}
		    }
		}
		RT {
		    set nolk [string compare $CurrPSPID(RT) A201]
		    set n 0 ; set badrts 0
		    set data ""
		    # DJG contribution: do autonumbering of RTs if the device 
		    # can't cope with names; and try to do it the proper way...
		    # If the user has numbered their routes (silly thing to
		    # do in my book, as my GPS can only cope with so many
		    # of the things...) then we obey that. Otherwise we
		    # assign numbers in a first-come first served way. If
		    # some routes are numbered, then we cope with that too by
		    # preallocating those numbers 
                    if { $AutoNumRts } {
			foreach rt [ array names RTIdNumber ] {
			    set num $RTIdNumber($rt) 
			    if { [regexp {^[0-9]+$} $num] } {
				set $taken($num) 1
				# MF change: no need for this here as
				#  it must appear below when no $AutoNumRTs
				# set $RTSendNumber($rt) $num
			    }
			}
		    }
		    #--
		    foreach rt $ixs {
			# DJG contribution: auto-numbering
			if { $PRTHNID } {
			    if { ! [regexp {^[0-9]+$} $RTIdNumber($rt)] } {
				# AutoNumRts is a user option
			    	if { ! $AutoNumRts } {
				    incr badrts
				    continue
				}
				if { [catch {set RTSendNumber($rt)}] } {
				    while { ! [catch \
						{set taken($RTNextNumber)}] } {
					incr RTNextNumber
				    }
				    set RTSendNumber($rt) $RTNextNumber
				    incr RTNextNumber
				}
			    } else {
				# Alternative taken care of above
				# MF change: ... only if $AutoNumRts
				set RTSendNumber($rt) $RTIdNumber($rt)
			    }
			} else {
			    set RTSendNumber($rt) $RTIdNumber($rt)
			}
			#--
			set k [llength $RTWPoints($rt)]
			if { ! $nolk } {
			    incr k [expr $k-1]
			}
			lappend data $rt $k $nolk
			incr n [expr $k+1]
		    }
		    if { $badrts > 0 } {
			GMMessage [format $MESS(cantsaveRTid) $badrts]
			if { $n == 0 } return
		    }
		    set PkOutStart XfrRT
		    switch $p {
			A200 -
			A201 {
			    set PkOutData $data ; set PkOutCount $n
			    set PkOutState $CurrPSPID(RTHeader)
			    $ProcSendPacket Records int $PkOutCount
			}
			default {
			    Log "protocol $p not implemented"
			    return
			}
		    }
		}
		TR {
		    set n 0 ; set badtrs 0
		    set PkTRSegs 0
		    foreach tr $ixs {
			if { $PTRHNID && ! [regexp {^[0-9]+$} $TRName($ix)] } {
			    incr badtrs
			    continue
			}
			incr n [llength $TRTPoints($tr)]
			if { $TRSegStarts($tr) != "" } {
			    incr PkTRSegs
			}
		    }
		    if { $badtrs > 0 } {
			GMMessage [format $MESS(cantsaveTRid) $badtrs]
			if { $n == 0 } return
		    }
		    set PkOutStart XfrTR
		    set PkOutData $ixs
		    switch $p {
			A300 {
			    # TR segments not supported
			    set PkTRSegs 0
			    set PkOutCount $n
			    set PkOutState TRData
			}
			A301 -  A302 {
			    # $PkTRSegs is count of TRs having segments
			    set PkOutCount [expr $n+[llength $ixs]]
			    set PkOutState TRHeader
			}
			default {
			    Log "protocol $p not implemented"
			    return
			}

		    }
		    $ProcSendPacket Records int $n
		}
		PosnData -
		DtTmData {
		    Log "PosnData/DtTmData not implemented"
		    return
		}
	    }
	}
    }
    return
}

proc SentPacket {pid} {
    # deal with continuation after a packet has successfully been sent
    global Command CommArgs PkInState CurrPSPID SInState \
	     PkOutState PkOutWhat PkOutCount PkOutData PkOutStart PkOutSaved \
	     CMD RTWPoints RTStages Jobs Request SPckts ProcSendPacket

    incr SPckts
    Log "StP> packet for $pid sent"
    switch $pid {
	PrdReq {
	    if { $PkInState != "idle" } {
		lappend Jobs [after 50 "SentPacket $pid"]
		return
	    }
	    set PkInState A000
	    set SInState get
	}
	CmdData {
	    switch $Command {
		get {
		    if { $PkInState != "idle" } {
			lappend Jobs [after 50 "SentPacket $pid"]
			return
		    }
		    set PkInState $CurrPSPID($CommArgs)
		    set SInState get
		}
		abort {
		    if { [string first check $Request] != 0 } {
			# called this way so that it may cancel pending jobs
			after 0 AbortComm
		    }
		}
		chgbaudcf {
		    regsub chgbaudcf= $Request "" n
		    if { $n == 1 } {
			set Request chgbaudcf=2
			SendData chgbaudcf
			return
		    }
		    EndChangeGarminBaud
		}
		default {
		    # do nothing (?); should it report to layer above?
		}
	    }
	}
	RqstData {
	    switch -glob -- $Request {
		chgbaud=* {
		    # step 2: send request for changing baud
		    regsub chgbaud= $Request "" baud
		    $ProcSendPacket BaudRqstData long $baud
		}
		default {
		    Log "StP> bad request for RqstData: $Request"
		    after 0 AbortComm
		}
	    }
	}
	BaudRqstData {
	    set PkInState T001
	    set SInState get
	}
	Records {
	    if { $PkOutCount == 0 } {
		$ProcSendPacket XfrCmpl int $CMD($PkOutStart)
	    } else {
		incr PkOutCount -1
		switch $PkOutWhat {
		    WP {
			foreach "ts vs" [PrepData] {}
			$ProcSendPacket WPData $ts $vs
		    }
		    RT {
			foreach "ts vs" [PrepData] {}
			$ProcSendPacket RTHeader $ts $vs
		    }
		    TR {
			set PkOutSaved ""
			set pid $PkOutState
			set PkOutState $CurrPSPID($pid)
			foreach "ts vs" [PrepData] {}
			$ProcSendPacket $pid $ts $vs
		    }
		}
	    }
	}
	WPData {
	    if { $PkOutCount == 0 } {
		$ProcSendPacket XfrCmpl int $CMD($PkOutStart)
	    } else {
		incr PkOutCount -1
		foreach "ts vs" [PrepData] {}
		$ProcSendPacket $pid $ts $vs
	    }
	}
	RTHeader {
	    set rt [lindex $PkOutData 0]
	    set n [lindex $PkOutData 1]
	    set nolk [lindex $PkOutData 2]
	    set PkOutSaved [list [expr $PkOutCount-$n] $PkOutState \
		                 [lreplace $PkOutData 0 2]]
	    set PkOutState $CurrPSPID(RTWPData)
	    set PkOutData [Apply "$RTWPoints($rt)" IndexNamed WP]
	    if { ! $nolk } {
		set k 1
		if { $RTStages($rt) == "" } {
		    foreach nwp [lrange $PkOutData 1 end] {
			set PkOutData [linsert $PkOutData $k ""]
			incr k 2
		    }
		} else {
		    foreach st $RTStages($rt) {
			set PkOutData [linsert $PkOutData $k $st]
			incr k 2
		    }
		}
	    }
	    if { [set PkOutCount $n] } {
		incr PkOutCount -1
		foreach "ts vs" [PrepData] {}
		$ProcSendPacket RTWPData $ts $vs
	    } else {
		$ProcSendPacket XfrCmpl int $CMD($PkOutStart)
	    }
	}
	RTWPData -
	RTLinkData {
	    if { $PkOutCount == 0 } {
		if { [set PkOutCount [lindex $PkOutSaved 0]] } {
		    incr PkOutCount -1
		    set PkOutState [lindex $PkOutSaved 1]
		    set PkOutData [lindex $PkOutSaved 2]
		    foreach "ts vs" [PrepData] {}
		    $ProcSendPacket RTHeader $ts $vs
		} else {
		    $ProcSendPacket XfrCmpl int $CMD($PkOutStart)
		}
	    } else {
		incr PkOutCount -1
		if { $CurrPSPID(RT) != "A201" } {
		    set p RTWPData
		} elseif { $pid != "RTLinkData" } {
		    set PkOutState $CurrPSPID(RTLinkData)
		    set p RTLinkData
		} else {
		    set PkOutState $CurrPSPID(RTWPData)
		    set p RTWPData
		}
		foreach "ts vs" [PrepData] {}
		$ProcSendPacket $p $ts $vs
	    }
	}
	TRHeader {
	    if { $PkOutCount == 0 } {
		Log "StP> count 0 after TRHeader"
		$ProcSendPacket XfrCmpl int $CMD($PkOutStart)
	    } else {
		incr PkOutCount -1
		set PkOutState $CurrPSPID(TRData)
		foreach "ts vs" [PrepData] {}
		$ProcSendPacket TRData $ts $vs
	    }
	}
	TRData {
	    if { $PkOutCount == 0 } {
		$ProcSendPacket XfrCmpl int $CMD($PkOutStart)
	    } else {
		incr PkOutCount -1
		if { $PkOutSaved == "" } {
		    switch $CurrPSPID(TR) {
			A301 -  A302 {
			    set pid TRHeader
			    set PkOutState $CurrPSPID(TRHeader)
			}
		    }
		}
		foreach "ts vs" [PrepData] {}
		$ProcSendPacket $pid $ts $vs
	    }
	}
	XfrCmpl {
	    EndOutProt $PkOutStart
	}
    }
    return
}

proc PrepData {} {
    # prepare WPs, RTs, or TRs data to be transferred to receiver
    global PkOutState PkOutData PkOutInfo PkOutSaved PkTRSegs PkSegData \
	    PkTRTPn PDTYPE WPName WPCommt WPPosn WPDatum WPDate WPSymbol \
	    WPDispOpt WPAlt WPHidden RTIdNumber RTCommt TRName TRDatum \
	    TRTPoints TRSegStarts TRColour TRHidden DataIndex
    #DJG contribution: Autonumber globals
    global RTSendNumber RTsSent
    #--

    switch $PkOutState {
	D100 -	D101 -	D102 -	D103 -	D104 -	D105 -	D106 -	D107 -
	D108 -	D109 -  D110 -  D150 -  D151 -	D152 -	D154 -  D155 {
	    set ix [lindex $PkOutData 0]
	    set PkOutData [lreplace $PkOutData 0 0]
	    set p $WPPosn($ix)
	    if { $WPDatum($ix) != "WGS 84" } {
		set p [ToDatum [lindex $p 0] [lindex $p 1] \
			   $WPDatum($ix) "WGS 84"]
	    }
	    set alt [lindex $WPAlt($ix) 0]
	    return [list $PDTYPE($PkOutState) \
			[PrepWPData $PkOutState \
			     $WPName($ix) $WPCommt($ix) $p \
			     $WPDate($ix) $WPSymbol($ix) \
			     $WPDispOpt($ix) $alt $WPHidden($ix)]]
	}
	D200 -  D201 -  D202 {
	    # RT header
	    set ix [lindex $PkOutData 0]
	    # DJG contribution: Use RTSendNumber in place of RTIdNumber
	    return [list $PDTYPE($PkOutState) \
		       [PrepRTHeaderData $PkOutState \
		            $RTSendNumber($ix) $RTCommt($ix)]]
	}
	D210 {
	    # RT link
	    set st [lindex $PkOutData 0]
	    set PkOutData [lreplace $PkOutData 0 0]
	    return [list $PDTYPE($PkOutState) [PrepRSData $PkOutState $st]]
	}
	D300 -  D301 -  D302 -  D303 -  D304 {
	    if { $PkOutSaved != "" } {
		# $PkOutSaved is list of TPs
		set p [lindex $PkOutSaved 0]
		set alt [lindex [lindex $p $DataIndex(TPalt)] 0]
		set depth [lindex $p $DataIndex(TPdepth)]
		set PkOutSaved [lreplace $PkOutSaved 0 0]
		set new 0
		if { $PkTRSegs && $PkSegData != "" && \
			[lindex $PkSegData 0] == $PkTRTPn } {
		    set new 1
		    if { [set PkSegData [lreplace $PkSegData 0 0]] == "" } {
			incr PkTRSegs -1
		    }
		}
	    } else {
		# this is needed if no TRHeader is used: start new TR
		set ix [lindex $PkOutData 0]
		set PkOutData [lreplace $PkOutData 0 0]
		set p [lindex $TRTPoints($ix) 0]
		set PkOutSaved [lreplace $TRTPoints($ix) 0 0]
		set PkTRTPn 0
		if { $PkTRSegs } {
		    # assuming 0 is never in $TRSegStarts($ix)
		    set PkSegData $TRSegStarts($ix)
		}
		set alt [lindex $p $DataIndex(TPalt)]
		set depth [lindex $p $DataIndex(TPdepth)]
		if { $TRDatum($ix) != "WGS 84" } {
		    set p [ToDatum [lindex $p 0] [lindex $p 1] \
			       $TRDatum($ix) "WGS 84"]
		    set PkOutSaved [ChangeTPsDatum $PkOutSaved \
			                           $TRDatum($ix) "WGS 84"]
		}
		set new 1
	    }
	    incr PkTRTPn
	    return [list $PDTYPE($PkOutState) \
		             [PrepTRData $PkOutState $p 0 $new $alt $depth]]
	}
	D310 -  D311 -  D312 {
	    # TRHeader
	    set ix [lindex $PkOutData 0]
	    set PkOutData [lreplace $PkOutData 0 0]
	    set PkOutSaved $TRTPoints($ix)
	    set PkTRTPn 0
	    if { $PkTRSegs } {
		set PkSegData $TRSegStarts($ix)
	    }
	    if { $TRDatum($ix) != "WGS 84" } {
		set PkOutSaved [ChangeTPsDatum $PkOutSaved \
			                      $TRDatum($ix) "WGS 84"]
	    }
	    return [list $PDTYPE($PkOutState) \
		        [PrepTRHeader $PkOutState $TRName($ix) \
			              $TRColour($ix) $TRHidden($ix)]]
	}
    }
}

proc PrepWPData {pid name commt posn date symbol dispopt alt hidden} {
    #  $symbol only used in D101-9, D110, D154-5
    #  $dispopt only in D103-4, D107-9, D155
    #  $alt only in D108-9, D110 (as float), D150-5 (as integer)
    global SYMBOLCODE DISPOPTCODE

    set sc $SYMBOLCODE($symbol)
    set do $DISPOPTCODE($dispopt)
    if { $alt == "" } {
	if { [regexp {^D1((0[89])|10)} $pid] } {
	    # D108-D110
	    set alt 1.0e25
	} else { set alt 0 }
    } elseif { ! [regexp {^D1((0[89])|10)} $pid] } {
	    # not D108-D110
	set alt [expr round($alt)]
    }
    switch $pid {
	D100 {
	    return [list $name $posn 0 $commt]
	}
	D101 -  D102 {
	    return [list $name $posn 0 $commt 0 $sc]
	}
	D103 {
	    return [list $name $posn 0 $commt $sc $do]
	}
	D104 {
	    return [list $name $posn 0 $commt 0 $sc $do]
	}
	D105 {
	    return [list $posn $sc $name]
	}
	D106 {
	    set ps "2 3 4" ; set vs [list $posn $sc $name]
	}
	D107 {
	    set ps "0 1 2 3 4 5 6"
	    set vs [list $name $posn 0 $commt $sc $do 0]
	}
	D108 {
	    set ps "2 4 6 7 9 12 13"
	    set vs [list $do $sc $posn $alt 0 $name $commt]
	}
	D109 {
	    set ps "2 4 6 7 9 13 14"
	    ### merging display option and colour
	    set colour [HiddenFindVal D109 colour $hidden]
	    set dclr [expr ($do<<5)|($colour&0x1f)]
	    set vs [list $dclr $sc $posn $alt 0 $name $commt]
	}
	D110 {
	    set ps "2 4 6 7 9 16 17"
	    ### merging display option and colour
	    set colour [HiddenFindVal D110 colour $hidden]
	    set dclr [expr ($do<<5)|($colour&0x1f)]
	    set vs [list $dclr $sc $posn $alt 0 $name $commt]
	}
	D150 {
	    set ps "0 3 4 8" ; set vs [list $name $posn $alt $commt]
	}
	D151 -
	D152 {
	    set ps "0 1 2 3 4 8 10"
	    set vs [list $name $posn 0 $commt 0 $alt 0]
	}
	D154 {
	    set ps "0 1 2 3 4 8 10 12"
	    set vs [list $name $posn 0 $commt 0 $alt 0 $sc]
	}
	D155 {
	    set ps "0 1 2 3 4 8 10 12 13"
	    set vs [list $name $posn 0 $commt 0 $alt 0 $sc $do]
	}
    }
    return [MergeData [HiddenVals $pid $hidden] $ps $vs]
}

proc PrepRTHeaderData {pid number cmmt} {

    switch $pid {
	D200 {
	    return [list $number]
	}
	D201 {
	    return [list $number $cmmt]
	}
	D202 {
	    return [list $number]
	}
    }
}

proc PrepRSData {pid stage} {
    global DataIndex

    set commt [lindex $stage $DataIndex(RScommt)]
    set label [lindex $stage $DataIndex(RSlabel)]
    set hidden [lindex $stage $DataIndex(RShidden)]
    switch $pid {
	D210 {
	    set ps "2" ; set vs [list $commt]
	}
    }
    return [MergeData [HiddenVals $pid $hidden] $ps $vs]
}

proc PrepTRHeader {pid name colour hidden} {

    switch $pid {
	D310 -  D312 {
	    set ps "1 2" ; set vs [list [ToGarminColour $pid $colour] $name]
	}
	D311 {
	    set ps 0 ; set vs $name
	}
    }
    return [MergeData [HiddenVals $pid $hidden] $ps $vs]
}

proc PrepTRData {pid posn date new alt depth} {

    if { $alt != "" } {
	set altundef $alt
    } else { set altundef 1.0e25 }
    if { $depth != "" } {
	set depthundef $depth
    } else { set depthundef 1.0e25 }
    switch $pid {
	D300 {
	    return [list $posn $date $new]
	}
	D301 {
	    return [list $posn $date $altundef $depthundef $new]
	}
	D302 {
	    # temperature is always sent as undefined
	    return [list $posn $date $altundef $depthundef 1.0e25 $new]
	}
	D303 {
	    # heart rate is always sent as undefined
	    # no information on new segments
	    return [list $posn $date $altundef 0]
	}
	D304 {
	    # heart rate, cadence always sent as undefined and sensor as 0
	    # no information on new segments
	    return [list $posn $date $altundef 1.025 0 0xff 0]
	}
    }
}

### data types

proc UnPackData {data types} {
    # convert from list of bytes (as TCL characters) to list of elements
    #  conforming to the types in the list $types
    # delete leading and trailing spaces of strings and char arrays
    global PacketDataRest PDTSIZE tcl_platform

    set vals ""
    foreach t $types {
	switch -glob $t {
	    char {
		set n 1
		binary scan [join [lrange $data 0 0] ""] "a1" x
	    }
	    boolean -
	    byte {
		set n 1
		binary scan [join [lrange $data 0 0] ""] "c" x
		set x [expr ($x+256)%256]
	    }
	    int {
		set n 2
		binary scan [join [lrange $data 0 1] ""] "s" x
	    }
	    word {
		set n 2
		binary scan [join [lrange $data 0 1] ""] "s" x
		set x [expr ($x+0x10000)%0x10000]
	    }
	    long -
	    longword {
		       # longword cannot be represented in Tcl as unsigned!
		set n 4
		binary scan [join [lrange $data 0 3] ""] "i" x
	    }
	    float {
		# this only works with machines following the
		#  IEEE standard floating point representation
		set n 4
		if { $tcl_platform(byteOrder) == "littleEndian" } {
		    binary scan [join [lrange $data 0 3] ""] "f" x
		} else {
		    set id ""
		    foreach k "3 2 1 0" {
			lappend id [lindex $data $k]
		    }
		    binary scan [join $id ""] "f" x
		}
	    }
	    double {
		# this only works with machines following the
		#  IEEE standard floating point representation
		set n 8
		if { $tcl_platform(byteOrder) == "littleEndian" } {
		    binary scan [join [lrange $data 0 7] ""] "d" x
		} else {
		    set id ""
		    foreach k "7 6 5 4 3 2 1 0" {
			lappend id [lindex $data $k]
		    }
		    binary scan [join $id ""] "d" x
		}
	    }
	    string {
		set x "" ; set n 0
		while { 1 } {
		    set c [lindex $data $n]
		    incr n
		    binary scan $c c d
		    if { $d == 0 } { break }
		    append x $c
		}
		if { ! [regexp {^ +$} $x] } {
		    set x [string trim $x " "]
		}
	    }
	    string<* {
		# null-terminated string of at most *-1 characters
		# to be truncated if it exceeds the limit
		regsub string< $t "" n
		incr n -1
		set x ""
		for { set i 0 } { $i < $n } { incr i } {
		    set c [lindex $data $i]
		    binary scan $c c d
		    if { $d == 0 } { break }
		    append x $c
		}
		if { $d != 0 } {
		    # find the actual length
		    while 1 {
			set c [lindex $data $i]
			binary scan $c c d
			if { $d == 0 } { break }
			incr i
		    }
		}
		set n [expr $i+1]
	    }
	    charray=* {
		regsub charray= $t "" n
		set x ""
		for { set i 0 } { $i < $n } { incr i } {
		    set c [lindex $data $i]
		    binary scan $c c d
		    if { $d == 0 } { set c " " }
		    append x $c
		}
		if { ! [regexp {^ +$} $x] } {
		    set x [string trim $x " "]
		}
	    }
	    bytes=* {
		# result is list of bytes
		regsub bytes= $t "" n
		set x ""
		for { set i 0 } { $i < $n } { incr i } {
		    set c [lindex $data $i]
		    binary scan $c c v
		    lappend x [expr ($v+256)%256]
		}
	    }
	    starray=* {
		# return list of lists with structure fields
		regsub starray= $t "" ets
		set ts [split $ets ","]
		set x ""
		while { $data != "" } {
		    lappend x [UnPackData $data $ts]
		    set data $PacketDataRest
		}
		set n 0
	    }
	    semicircle {
		# return lat/long in degrees
		set la [expr [UnPackData $data long]/ \
			     11930464.7111111111111]
		set lo [expr [UnPackData [lrange $data 4 7] long]/ \
			     11930464.7111111111111]
		set x [list $la $lo]
		set n 8
	    }
	    radian {
		# return lat/long in degrees
		set la [expr [UnPackData $data double]* \
			     57.29577951308232087684]
		set lo [expr [UnPackData [lrange $data 8 15] double]* \
                             57.29577951308232087684]
		set x [list $la $lo]
		set n 16
	    }
	    unused=* {
		regsub unused= $t "" n
		set x UNUSED
	    }
	    union=* {
		# can only appear if $data is a singleton and types in the
		#  union are all of different lengths; no checks on this
		regsub union= $t "" l
		set size [llength $data]
		foreach ut [split $l ,] {
		    if { $PDTSIZE($ut) == $size } {
			return [UnPackData $data $ut]
		    }
		}
		Log "no types of size $size in $t; using byte"
		return [UnPackData $data byte]
	    }
	    ignored {
		set PacketDataRest ""
		return $vals
	    }
	    default {
		Log "unimplemented data type when unpacking: $t"
		set n 1 ; set x 0
	    }
	}
	lappend vals $x
	set data [lrange $data $n end]
    }
    set PacketDataRest $data
    return $vals
}

proc PackData {types vals} {
    # convert from a list of types and a list of values into a
    #  list of bytes (as TCL characters)

    return [split [DataToStr $types $vals] ""]
}

proc DataToStr {types vals} {
    # convert from list of elements conforming to the types in $types to
    #  a TCL string
    global tcl_platform

    if { [llength $types] != [llength $vals] } {
	Log "DataToStr> bad lengths: types=\{$types\}, vals=\{$vals\}"
	# BUG DataToStr: bad lengths
    }
    set data ""
    foreach t $types v $vals {
	switch -glob $t {
	    char {
		append data [binary format "a" $v]
	    }
	    boolean -
	    byte {
		append data [binary format "c" $v]
	    }
	    word -
	    int {
		append data [binary format "s" $v]
	    }
	    longword -
	    long {
		append data [binary format "i" $v]
	    }
	    float {
		# this only works with machines following the
		#  IEEE standard floating point representation
		set s [binary format "f" $v]
		if { $tcl_platform(byteOrder) != "littleEndian" } {
		    set l [split "$s" ""]
		    set s ""
		    foreach k "3 2 1 0" {
			append s [lindex $l $k]
		    }
		}
		append data $s
	    }
	    double {
		# this only works with machines following the
		#  IEEE standard floating point representation
		set s [binary format "d" $v]
		if { $tcl_platform(byteOrder) != "littleEndian" } {
		    set l [split "$s" ""]
		    set s ""
		    foreach k "7 6 5 4 3 2 1 0" {
			append s [lindex $l $k]
		    }
		}
		append data $s
	    }
	    string {
		append data [binary format "a*x" $v]
	    }
	    string<* {
		# null-terminated string of at most *-1 characters
		# to be truncated if it exceeds the limit
		regsub string< $t "" n
		incr n -1
		if { [string length $v] > $n } {
		    set v [string range $v 0 $n]
		}
		append data [binary format "a*x" $v]
	    }
	    charray=* {
		regsub charray= $t "" n
		append data [binary format "A$n" $v]
	    }
	    bytes=* {
		# $v is list of bytes
		regsub bytes= $t "" n
		foreach e $v {
		    append data [binary format "c" $e]
		    if { [incr n -1] == 0 } { break }
		}
		# complete with 0s if not enough data
		while { $n > 0 } {
		    append data [binary format "c" 0]
		    incr n -1
		}
	    }
	    starray=* {
		# $v must be a list of lists with structure fields
		regsub starray= $t "" ets
		set ts [split $ets ","]
		foreach st $v {
		    append data [DataToStr $ts $st]
		}
	    }
	    semicircle {
		set sla [expr round([lindex $v 0]*11930464.7111111111111)]
		set slo [expr round([lindex $v 1]*11930464.7111111111111)]
		append data [DataToStr "long long" [list $sla $slo]]
	    }
	    radian {
		set rla [expr [lindex $v 0]/57.29577951308232087684]
		set rlo [expr [lindex $v 1]/57.29577951308232087684]
		append data [DataToStr "double double" [list $rla $rlo]]
	    }
	    unused=* {
		regsub unused= $t "" n
		append data [binary format "x$n"]
	    }
	    union=* {
		# use the first type in the comma-separated list of types
		regsub union= $t "" l
		set t [string range $l 0 [expr [string first , $l]-1]]
		append data [DataToStr $t $v]
	    }
	    ignored {
		# no need for this:
		# just to ensure a non-empty packet data
		#append data [binary format "c" 0]
	    }
	    default {
		Log "DataToStr> unimplemented data type when packing: $t"
	    }
	}
    }
    return $data
}

proc ToGarminColour {pid colour} {
    # convert from user colour to Garmin code by finding best-match
    #  in set of colours allowed by $pid
    #  $colour expected to be in hexadecimal
    global GARMINCOLOURS

    foreach "cx cy cz" [ColourToDec $colour] {}
    set cs "" ; set c 0
    foreach n $GARMINCOLOURS($pid) {
	foreach "x y z" [ColourToDec $n] {}
	if { $x == -1 } { continue }
	if { $x == $cx && $y == $cy && $z == $cz } { return $c }
	lappend cs $c $x $y $z
	incr c
    }
    return [ColourMatch $cx $cy $cz $cs]
}

proc FromGarminColour {pid code} {
    # convert from Garmin code to GPSMan colour (in hexadecimal)
    global GARMINCOLOURS RGBNamed

    if { [set name [lindex $GARMINCOLOURS($pid) $code]] == "" || \
	     $name == "default" } {
	set defvar $GARMINCOLOURS($pid,dvar)
	global $defvar
	return [set $defvar]
    }
    return [eval DecToColour $RGBNamed($name)]
}

##### upper level

proc EndInProt {pid data} {
    # deal with end of input protocol
    #  $pid in {A000 A001 A010 A100 A200-201 A300-302 A500 A600 A650
    #           A700 A906}
    #    (cf. ProcInPacket) but {A010 A500 A600 A650 A700} not supported here
    # global $Request==get$wh  where $wh in {WP RT TR LAP AL PosnData
    #                          DtTmData} (cf. SendData)
    #                         GPSOpResult is set to 0 on success or 1 on error
    #                ==check=$args  where 1st arg should be executed as
    #                          connection is ok
    global Request Jobs MESS TXT MyProdId MyProdDescr MyProdVersion \
	    PRTCLVERSION GPSOpResult

    switch $pid {
	A000 {
	    # Product Data
	    # executed after waiting for Protocol Capability packet
	    Log "EIP> product data=$data"
	    set MyProdId [lindex $data 0]
	    set MyProdVersion [lindex $data 1] ; set descr [lindex $data 2]
	    if { ! [catch {set pvs $PRTCLVERSION($MyProdId)}] } {
		foreach p $pvs {
		    # compare version and set variant id if needs be
		    if { [expr $MyProdVersion [lindex $p 0]] } {
			set MyProdId [lindex $p 1]
			break
		    }
		}
	    }
	    set MyProdDescr [format $MESS(connectedto) $descr]
	    return
	}
	A001 {
	    # Protocol Capability
	    if { [InitGivenProtocols $data] } {
		set SInState idle
		EndConnCheck gotprots
	    } else {
		AbortComm badprots
	    }
	    return
	}
    }
    CloseInProgrWindow
    ResetSerial
    switch -glob $Request {
	get* {
	    regsub get $Request "" wh
	    SetCursor . watch
	    if { [lindex $data 0] != "" } {
		InData$wh $data
		EndWPRenaming
		set GPSOpResult 0
	    } else {
		GMMessage [format $MESS(nodata) $TXT($wh)]
		set GPSOpResult 1
	    }
	    ResetCursor .
	}
	default {
	    Log ">EIP: wrong request ($Request)"
	}
    }
    set Jobs ""
    return
}

proc InDataWP {data} {

    InDataWPRT $data WPData
    return
}

proc InDataWPRT {data pid} {
    # add WPs data from receiver to database not overwriting non-user WPs
    #  with same name and different positions
    # return list of names of WPs; it may be void if WPs were discarded because
    #  of unacceptable data fields or because the user cancelled renaming
    global CurrPSPID GetDispl GetSet

    set wps ""
    foreach hcd [ConvWPData $data $CurrPSPID($pid)] {
	foreach "hclass d" $hcd { break }
	set name [lindex $d 0]
	if { ! [CheckName Ignore $name] } {
	    if { [set nname [AskForName $name]] == "" } { continue }
	    set d [AddOldNameToObs WP $d $name]
	    set d [lreplace $d 0 0 $nname]
	    set name $nname
	}
	if { [set ix [IndexNamed WP $name]] != -1 && $hclass != "user" && \
		 [SamePosn $ix $d] != 1 } {
	    # do not overwrite non-user WPs with same name but different
	    #  positions
	    set ix -1
	    set d [AddOldNameToObs WP $d $name]
	    set name [NewName WP]
	    set d [lreplace $d 0 0 $name]
	}
	if { $GetSet(WP) == "" || [lsearch -exact $GetSet(WP) $ix] != -1 } {
	    set name [StoreWP $ix $name $d $GetDispl]
	    lappend wps $name
	}
    }
    return $wps
}

proc ConvWPData {data pid} {
    # convert WPs data got from receiver into list of lists whose heads are
    #  either "user" or the string given by proc HiddenFormatVal for the class
    #  attribute (may be "user"), and whose rests are suitable for use with
    #  proc SetItem
    # result may be an empty list
    global PositionFormat CREATIONDATE DATAFOR HiddenClass

    set r ""
    set fs $DATAFOR($pid,ns) ; set ps $DATAFOR($pid,ps)
    if { $CREATIONDATE } {
	set all [linsert $fs 0 Datum PFrmt Date Hidden]
	set cnsts [list "WGS 84" "" [Now]]
    } else {
	set all [linsert $fs 0 Datum PFrmt Hidden]
	set cnsts [list "WGS 84" ""]
    }
    foreach d $data {
	set vs $cnsts
	lappend vs [set hvs [HiddenGet $pid $d]]
	set ok 1
	foreach f $fs p $ps {
	    set v [lindex $d $p]
	    switch $f {
		Posn {
		    foreach "latd longd" $v { break }
		    if { ( $pid == "D110" && abs($latd) > 0x40000000 ) || \
			     abs($latd) > 90 || abs($longd) > 180 } {
			# discard
			Log "CWPD> WP bad position, $pid: $latd $longd"
			set ok 0
			break
		    }
		    foreach "v pfmt datum" \
			[FormatPosition $latd $longd "WGS 84" \
			     $PositionFormat "" DDD] {
			    break
		    }
		    set vs [lreplace $vs 0 1 $datum $pfmt]
		}
		Symbol {
		    set v [NameForCodeOf SYMBOL $v]
		}
		DispOpt {
		    if { $pid == "D109" || $pid == "D110" } {
			# colour has been dealt with by HiddenGet
			set v [expr $v>>5]
		    }
		    set v [NameForCodeOf DISPOPT $v]
		}
		Alt {
		    switch $pid {
			D108 -  D109 -  D110 {
			    if { $v < 1e20 } {
				set v [format %.1f $v]
			    } else { set v "" }
			}
			D150 -  D151 -  D152 -  D154 -  D155 {
			    # this depends on what HiddenGet does!
			    set k [lsearch -glob $hvs G*:class=*]
			    if { $k != -1 && \
				    "G${pid}:class=[HiddenCode int 0]" == \
				    [lindex $hvs $k] } {
				set v ""
			    }
			}
		    }
		}
	    }
	    lappend vs $v
	}
	if { $ok } { lappend r [list $HiddenClass [FormData WP $all $vs]] }
    }
    return $r
}

proc InDataRT {data} {
    # add RT data from receiver to database
    #  $data is a list with in turn
    #    RT header data as returned by UnPackData
    #    list of WP data or list with in turn WP data and RS data
    global CurrPSPID GetDispl GetSet MESS

    if { [set nolk [string compare $CurrPSPID(RT) A201]] } {
	set fs "IdNumber Commt WPoints"
    } else {
	set fs "IdNumber Commt Stages WPoints"
    }
    set hpid $CurrPSPID(RTHeader)
    while { $data != "" } {
	set r [ConvRTHeaderData [lindex $data 0] $hpid]
	set id [lindex $r 0] ; set ix [IndexNamed RT $id]
	if { $GetSet(RT) == "" || [lsearch -exact $GetSet(RT) $ix] != -1 } {
	    set GetSet(WP) ""
	    set wpd [lindex $data 1]
	    if { ! $nolk } {
		set wps "" ; set sts ""
		while { $wpd != "" } {
		    lappend wps [lindex $wpd 0]
		    if { [set sd [lindex $wpd 1]] != "" } {
			lappend sts [ConvRSData $sd $CurrPSPID(RTLinkData)]
		    }
		    set wpd [lreplace $wpd 0 1]
		}
		set wpd $wps
		lappend r $sts
	    }
	    set wps [InDataWPRT $wpd RTWPData]
	    # $wps can be void if the user cancelled replacement of names
	    #  or $wps were discarded
	    if { $wps != "" } {
		lappend r $wps
		set d [FormData RT $fs $r]
		StoreRT $ix $id $d $wps $GetDispl
	    } else { GMMessage "$MESS(voidRT): $id" }
	}
	set data [lreplace $data 0 1]
    }
    return    
}

proc ConvRTHeaderData {data pid} {
    # convert RT header data got from receiver into list of id number and
    #  comment

    switch $pid {
	D200 -  D202 {
	    lappend data ""
	    return $data
	}
	D201 {
	    return $data
	}
    }
}

proc ConvRSData {data pid} {
    # convert RT stage data got from receiver into list using FormData

    switch $pid {
	D210 {
	    return [FormData RS "commt hidden" \
		    [list [lindex $data 2] [HiddenGet D210 $data]]]
	}
    }
}

proc InDataTR {data} {
    # add TRs data from receiver to database
    #  $data is a list of pairs with kind (in {header, data}) and list of
    # values
    global CurrPSPID GetDispl SegPrefix

    # $trs is a list of triples with TR header info, a list of TP info, and
    #  a list of segment starters, where the TR header info is a pair with
    #  field names and values, and each TP info is a list obtained by FormData
    set trs "" ; set tps ""
    set pidd $CurrPSPID(TRData)
    switch $CurrPSPID(TR) {
	A300 {
	    # no support for segments
	    foreach p $data {
		set vals [lindex $p 1]
		if { [lindex $p 0] != "data" } {
		    Log "IDTR> got a non-data pair: [lindex $p 0]"
		    return
		}
		if { [set tpp [ConvTPData $vals $pidd]] == "" } {
		    continue
		}
		if { [lindex $tpp 0] == 1 && $tps != "" } {
		    lappend trs [list "" $tps ""]
		    set tps ""
		}
		lappend tps [lindex $tpp 1]
	    }
	    if { $tps != "" } {
		lappend trs [list "" $tps ""]
	    }
	}
	A301 -  A302 {
	    # segments are supported
	    set pidh $CurrPSPID(TRHeader)
	    set phdr "" ; set segsts "" ; set tpn 0
	    # for dealing with segments marked with invalid packets
	    set SegPrefix 0
	    foreach p $data {
		set vals [lindex $p 1]
		if { [lindex $p 0] != "header" } {
		    if { [set tpp [ConvTPData $vals $pidd]] == "" } {
			continue
		    }
		    lappend tps [lindex $tpp 1]
		    if { [lindex $tpp 0] && $tpn != 0 } {
			lappend segsts $tpn
		    }
		    incr tpn
		} else {
		    if { $tps != "" } {
			if { $phdr == "" } {
			    Log "IDTR> TPs without header"
			    return
			}
			lappend trs [list $phdr $tps $segsts]
			set tps ""
		    } elseif { $phdr != "" } {
			Log "IDTR> TR header without TPs; discarding"
		    }
		    if { [set phdr [ConvTRHeaderData $vals $pidh]] == -1 } {
			return
		    }
		    set tpn 0 ; set segsts ""
		}
	    }
	    if { $tps != "" && $phdr != "" } {
		lappend trs [list $phdr $tps $segsts]
	    }
	}
    }
    foreach tr $trs {
	set phdr [lindex $tr 0]
	set fs [lindex $phdr 0] ; set vs [lindex $phdr 1]
	if { [set k [lsearch -exact $fs Name]] == -1 } {
	    lappend fs Name ; lappend vs [set id [NewName TR]]
	} else {
	    set id [lindex $vs $k]
	}
	lappend fs Datum TPoints SegStarts
	lappend vs "WGS 84" [lindex $tr 1] [lindex $tr 2]
	StoreTR [IndexNamed TR $id] $id [FormData TR $fs $vs] $GetDispl
    }
    return
}

proc ConvTRHeaderData {data pid} {
    # convert TR header data
    # return pair of lists with field names and values

    switch $pid {
	D310 -  D312 {
	    set fs "Name Colour Hidden"
	    set vs [list [lindex $data 2] \
		    [FromGarminColour $pid [lindex $data 0]] \
		    [HiddenGet $pid $data]]
	}
	D311 {
	    set fs Name ; set vs [lindex $data 0]
	}
    }
    return [list $fs $vs]
}

proc ConvTPData {data pid} {
    # convert TP data
    # return empty list if invalid packet, otherwise pair with flag indicating
    #  a new TR or segment, and list obtained by FormData
    global SegPrefix

    set fs "" ; set vs ""
    # common fields: position, date
    set badpos 0
    foreach "latd longd" [lindex $data 0] {}
    if { abs($latd) > 90 || abs($longd) > 180 } { incr badpos }
    set d [lindex $data 1]
    switch $pid {
	D300 {
	    if { $badpos } {
		Log "CTPD> TP bad position: $latd $longd"
		return ""
	    }
	    set new [lindex $data 2]
	}
	D301 -  D302 {
	    if { $badpos } {
		Log "CTPD> TP bad position: $latd $longd"
		return ""
	    }
	    if { [set alt [lindex $data 2]] < 1e10 } {
		lappend fs alt ; lappend vs $alt
	    }
	    if { [set dep [lindex $data 3]] < 1e10 } {
		lappend fs depth ; lappend vs $dep
	    }
	    if { $pid == "D302" } {
		# discard temperature
		set new [lindex $data 5]
	    } else {
		# D301
		set new [lindex $data 4]
	    }
	}
	D303 -  D304 {
	    # segments are prefixed with 2 packets whose fields are all invalid
	    set prefix $badpos
	    if { [set alt [lindex $data 2]] < 1e10 } {
		lappend fs alt ; lappend vs $alt
		set prefix 0
	    }
	    if { $pid == "D303" } {
		if { [set heartrate [lindex $data 3]] != 0 } { set prefix 0 }
	    } else {
		# D304
		foreach "dist heartrate cadence sensor" [lreplace $data 0 2] {}
		if { $heartrate != 0 || abs($dist-1.0e25) > 1 || \
			 $sensor != 0xff } { set prefix 0 }
	    }
	    if { $prefix } {
		Log "CTPD> segment prefix discarded"
		incr SegPrefix
		return ""
	    }
	    if { $badpos } {
		Log "CTPD> TP bad position: $latd $longd"
		set SegPrefix 0
		return ""
	    }
	    # discard all information except position, date and altitude
	    if { $SegPrefix > 1 } {
		set new 1
	    } else { set new 0 }
	    set SegPrefix 0
	}
    }
    if { [catch {set np [FormatLatLong $latd $longd DMS]}] } {
	Log "CTPD> bad position: $latd $longd\ndata=$data"
	GMMessage "Error getting TPs; please report to mig@ncc.up.pt"
	return [list 1 {}]
    }
    set dl [ConvGarminDate $d]
    lappend fs latd longd latDMS longDMS date secs
    set tpdata [FormData TP $fs [concat $vs $np $dl]]
    return [list $new $tpdata]
}

proc InDataLAP {data} {
    # add LAPs data from receiver to database
    # create GRs with runs
    #  $data is a list of lists of values
    global CurrPSPID GetSet LAPName LAPTRIx TXT

    set pidd $CurrPSPID(LAPData)
    set ixs ""
    switch $CurrPSPID(LAP) {
	A906 {
	    foreach d [ConvLAPData $data $pidd] {
		set name [lindex $d 0] ; set ix [IndexNamed LAP $name]
		if { $GetSet(LAP) == "" || \
			 [lsearch -exact $GetSet(LAP) $ix] != -1 } {
		    set ix [StoreLAP $ix $name $d]
		    lappend ixs $ix
		}
	    }
	}
    }
    # build runs: create a GR for each one
    set run "" ; set n 0
    foreach ix $ixs {
	lappend run $LAPName($ix)
	if { [set trix $LAPTRIx($ix)] < 255 } {
	    set n "$TXT(lapsrun) [lindex $run 0]"
	    CreateGRFor "@$n" "" [list [list LAP $run]]
	    set run ""
	}
    }
    if { $run != "" && $trix == 255 } {
	set n "$TXT(lapsrun) [lindex $run 0]"
	CreateGRFor "@$n" "" [list [list LAP $run]]
    }
    return
}

proc ConvLAPData {data pid} {
    # convert LAPs data got from receiver into list of lists suitable for
    #  use with SetItem
    global PositionFormat DATAFOR

    set r ""
    set fs $DATAFOR($pid,ns) ; set ps $DATAFOR($pid,ps)
    set all [linsert $fs 0 Datum PFrmt Name]
    set cnsts [list "WGS 84" $PositionFormat ""]
    foreach d $data {
	set vs $cnsts
	set ix 0
	foreach f $fs p $ps {
	    set v [lindex $d $p]
	    switch -glob $f {
		Start {
		    set v [ConvGarminDate $v]
		    set name [lindex $v 0]
		}
		Dur {
		    set v [FormatTime [expr $v*0.01] 2]
		}
		*Posn {
		    foreach "latd longd" $v { break }
		    if { abs($latd) > 90 || abs($longd) > 180 } {
			set v ""
			set $f ""
		    } else {
			foreach "v pfmt datum" \
			    [FormatPosition $latd $longd "WGS 84" \
				 $PositionFormat "" DDD] { break }
			# BegPosn and EndPosn must have same format and datum
			set $f [list $pfmt $datum $latd $longd $ix]
		    }
		}
	    }
	    lappend vs $v
	}
	incr ix
	if { $BegPosn != "" || $EndPosn != "" } {
	    if { $BegPosn != "" && $EndPosn != "" && \
		     [lrange $BegPosn 0 1] != [lrange $EndPosn 0 1] } {
		# incompatible position format or datum: use DDD for both
		foreach k [list $BegPosn $EndPosn] {
		    set i [lindex $k 4]
		    set vs [lreplace $vs $i $i \
				[eval FormatLatLong [lrange $k 2 3] DDD]]
		}
		set pfmt DDD ; set datum "WGS 84"
	    }
	    set vs [lreplace $vs 0 1 $datum $pfmt]
	}
	set vs [lreplace $vs 2 2 $name]
	lappend r [FormData LAP $all $vs]
    }
    return $r
}

proc InDataAL {data} {
    # show almanac data to user or, if in command-line mode, store it as
    #  text in global ALDataTxt
    #  $data is a list of lists of values
    global CurrPSPID DATAFOR TXT CMDLINE ALDataTxt

    set pid $CurrPSPID(ALData)
    set title "$TXT(nameAL)\n" ; set sep ""
    foreach n $DATAFOR($pid,ns) {
	append title $sep $TXT(alm_$n)
	set sep ", "
    }
    if { $CMDLINE } {
	set ALDataTxt "$title\n"
    } else { DisplayInfo $title }
    foreach lst $data {
	set line "" ; set sep ""
	foreach v $lst {
	    append line $sep $v
	    set sep " "
	}
	if { $CMDLINE } {
	    append ALDataTxt $line "\n"
	} else { DisplayInfo $line }
    }
    return
}

proc ConvGarminDate {gd} {
    # converts Garmin date (seconds since 1990.01.01 00:00:00) into list
    #  with date in current format and seconds since beginning of $YEAR0,
    #  not necessarily a leap year, but < 1990
    global YEAR0 TimeOffset

    if { $gd == 0x7fffffff || $gd == 0xffffffff } { set gd 0 }
    set dd -1
    set yy [expr $YEAR0+$YEAR0%4]
    while { $yy < 1990 } {
	if { $yy%100!=0 || $yy%400==0 } { incr dd }
	incr yy 4
    }
    set secs [expr round((((1990-$YEAR0)*365+$dd)*24+$TimeOffset-12)*3600+$gd)]
    return [list [DateFromSecs $secs] $secs]
}

proc InDataPosnData {data} {

    GMMessage "InDataPosnData not implemented"
    return
}

proc InDataDtTmData {data} {

    GMMessage "InDataDtTmData not implemented"
    return
}

proc ProcPVTData {pid data} {
    # process position, velocity and time data

    if { [set data [InDataPVT $data $pid]] != "" } {
	UseRealTimeData $data
    }
    return
}

proc InDataPVT {data pid} {
    # get position, velocity and time data from receiver
    # return "" on error, or list with (any element may be "_" for undefined)
    #  UTC time - as a list with y m d h mn s
    #  position - as a list with latd, longd (for datum WGS 84)
    #  pos fix  - as defined in array GarminPVTStatus
    #  pos error - as a list with
    #              estimated position error EPE in meters
    #              estimated horizontal error EPH in meters
    #              estimated vertical error in meters
    #  altitude - signed number in meters
    #  velocity vector - as list with vlat vlong valt, in m/s
    #  true course (track/course over ground) - degrees
    #  number of satellites in view
    #  HDOP - horizontal dilution of precision
    #  speed - scalar in km/h
    #  
    global DAYSOF GarminPVTStatus

    switch $pid {
	D800 {
	    foreach "alt epe eph epv fix tow posn vlong vlat valt mslh \
		       lsecs wnds" \
		    $data { break }
	    # some old receivers may use a fix value that is 1 more than this
	    #  (Garmin specs 1 rev A); that is not dealt with here!!!
	    if { $fix < 2 } { return "" }
	    foreach "latd longd" $posn {}
	    if { abs($latd) > 90 || abs($longd) > 180 } { return "" }
	    if { $fix%2 == 0 } {
		set alt "_" ; set valt "_"
	    } else { set alt [expr $alt+$mslh] }
	    if { [set utcs [expr round($tow-$lsecs)]] < 0 } {
		set nd -1 ; incr utcs 86400
	    } else {
		# integer division
		set nd [expr $utcs/86400]
		set utcs [expr $utcs%86400]
	    }
	    set s [expr $utcs%60] ; set x [expr $utcs/60]
	    set mn [expr $x%60] ; set h [expr $x/60]
	    incr wnds $nd
	    set y [expr $wnds/365+1990] ; set d [expr $wnds%365]
	    for { set yy 1992 } { $yy < $y } { incr yy 4 } {
		if { $yy%100 != 0 || $yy%400 == 0 } { incr d -1 }
	    }
	    if { $d < 0 } {
		incr y -1 ; incr d 365
	    }
	    set b [expr $y%4 == 0 && ($y%100 != 0 || $y%400 == 0)]
	    for { set m 1 } { $d > $DAYSOF($m) } { incr m } {
		if { $b && $m == 2 } {
		    if { $d == 29 } { break }
		    incr d -1
		}
		incr d -$DAYSOF($m)
	    }
	    set r [list [list $y $m $d $h $mn $s] $posn \
		    $GarminPVTStatus($fix) [list $epe $eph $epv] $alt \
		    [list $vlat $vlong $valt] _ _ _ _]
	}
    }
    return $r
}

##### PVT simulator

proc SimulPVTOn {} {
    # simulate PVT by randomly generating fake PVT data lists and calls to
    #  proc UseRealTimeData
    global SimulPVTData

    set SimulPVTData [list \
	    {2002 6 30 14 0 20} {45.02 -8.5} simul {23.2 34.3 56.5} 123.43 \
	    {-1.3 4.5 1.6} 23.4 _ _ 0]
    SimulPVT
    return
}

proc SimulPVT {} {
    # generate a fake PVT data list and a call to proc UseRealTimeData
    # the previous data list is changed in some random, arbitrary way
    global SimulPVTData RealTimeLogOn

    if { $SimulPVTData != "" } {
	if { $RealTimeLogOn } { UseRealTimeData $SimulPVTData }
	foreach "date pos fix errv alt velv trk" $SimulPVTData { break }
	foreach "lat long" $pos {}
	set min [lindex $date 4]
	if { [set secs [expr [lindex $date end]+1]] > 59 } {
	    incr min ; set secs 0
	}
	set date [lreplace $date 4 5 $min $secs]
	set dlat [expr 0.001*rand()-0.0005]
	set dlong [expr 0.002*rand()-0.001]
	set lat2 [expr $lat+$dlat]
	if { [expr abs($dlat)] < 1e-20 } {
	    if { [expr abs($dlong)] < 1e-20 } {
		set trk 0
	    } elseif { $dlong < 0 } {
		set trk 270
	    } else { set trk 90 }
	} elseif { [expr abs($dlong)] < 1e-20 } {
	    if { $dlat < 0 } {
		set trk 180
	    } else { set trk 0 }
	} else {
	    set trk [expr round(atan(sin($dlong)/ \
 		               (tan($lat2)*cos($lat)-sin($lat)*cos($dlong))) \
 			  *57.29577951308232087684)]
	    if { $trk < 0 } {
		if { $dlong < 0 } { incr trk 360 } else { incr trk 180 }
	    } elseif { $dlong < 0 } { incr trk 180 }
	}
	set vel_z [expr 3*rand()-1.5]
	# assuming 1 sec interval and 100km/degree in lat or long
	set vel_x [expr $dlat*1e5] ; set vel_y [expr $dlong*1e5]
	set SimulPVTData [list $date \
		[list $lat2 [expr $long+$dlong]] simul $errv \
		[expr $alt+$vel_z] [list $vel_x $vel_y $vel_z] $trk _ _ _]
	after 1000 SimulPVT
    }
    return
}

proc SimulPVTOff {} {
    global SimulPVTData

    set SimulPVTData ""
    return
}

## de/coding of hidden attribute values

proc HiddenGet {pid data} {
    # form list of hidden attributes from $data got from receiver using
    #  protocol $pid
    # set global HiddenClass either to "user" or to string given by
    #  proc HiddenFormatVal for class attribute (may be "user")
    # dependencies on what this proc does in proc ConvWPData !
    global HIDDENFOR HiddenClass

    set HiddenClass "user"
    if { [catch {set HIDDENFOR($pid,ns)}] } { return "" }
    set fs $HIDDENFOR($pid,ns) ; set ps $HIDDENFOR($pid,ps)
    if { ! [catch {set $HIDDENFOR($pid,pp)}] } {
	# preprocess values in $data
	foreach pp $HIDDENFOR($pid,pp) {
	    set pos [lindex $ps [lsearch -exact $fs [lindex $pp 0]]]
	    set pexpr [lindex $pp 1]
	    set v [lindex $data $pos]
	    set data [lreplace $data $pos $pos [expr $v [set pexpr]]]
	}
    }
    foreach f $fs p $ps {
	set $f [lindex $data $p]
	if { $f == "class" } {
	    set HiddenClass [HiddenFormatVal class $pid $class]
	}
    }
    set undef ""
    foreach c $HIDDENFOR($pid,cs) { eval $c }
    set h ""
    foreach f $fs t $HIDDENFOR($pid,ts) {
	if { [lsearch -exact $undef $f] == -1 } {
	    lappend h G${pid}:${f}=[HiddenCode $t [set $f]]
	}
    }
    return $h
}

proc HiddenFindVal {pid name hidden} {
    # find value of a hidden attribute under $name
    # use default value if needs be
    global HIDDENFOR MESS

    set fs $HIDDENFOR($pid,ns)
    set posHF [lsearch -exact $fs $name]
    set pos [lindex $HIDDENFOR($pid,ps) $posHF]
    set type [lindex $HIDDENFOR($pid,ts) $posHF]
    set v [lindex $HIDDENFOR($pid,vs) $posHF]
    foreach hh $hidden {
	if { [string first G${pid}: $hh] == 0 } {
	    set i [string first : $hh]
	    set h [string range $hh [expr $i+1] end]
	    if { [set i [string first = $h]] > 0 } {
		if { [string range $h 0 [expr $i-1]] == $name } {
		    set hd [HiddenDecode $type \
			[string range $h [expr $i+1] end]]
		    if { [lindex $hd 0] } {
			return [lindex $hd 1]
		    }
		    GMMessage "$MESS(badhidden): $hh" ; break
		}
	    } else { GMMessage "$MESS(badhidden): $hh" ; break }
	} else { Log "hidden not for $pid discarded when finding: $hh" }
    }
    return $v
}

proc HiddenVals {pid hidden} {
    # build list of values for all hidden attributes of $pid from $hidden
    # use default values if needs be
    # list is built according to the positions in $HIDDENFOR($pid,ps)
    #  with empty elements if they are not consecutive
    global HIDDENFOR MESS

    set fs $HIDDENFOR($pid,ns)
    foreach f $fs v $HIDDENFOR($pid,vs) t $HIDDENFOR($pid,ts) {
	set $f $v ; set type($f) $t
    }
    foreach hh $hidden {
	if { [string first G${pid}: $hh] == 0 } {
	    set i [string first : $hh]
	    set h [string range $hh [expr $i+1] end]
	    if { [set i [string first = $h]] > 0 } {
		set f [string range $h 0 [expr $i-1]]
		set hd [HiddenDecode $type($f) \
			[string range $h [expr $i+1] end]]
		if { [lindex $hd 0] } {
		    set $f [lindex $hd 1]
		} else { GMMessage "$MESS(badhidden): $hh" }
	    } else { GMMessage "$MESS(badhidden): $hh" }
	} else { Log "hidden not for $pid discarded when building: $hh" }
    }
    set vs "" ; set k 0
    foreach f $fs p $HIDDENFOR($pid,ps) {
	while { $k != $p } {
	    lappend vs "" ; incr k
	}
	lappend vs [set $f] ; incr k
    }
    return $vs
}

proc HiddenData {wh hidden} {
    # build list of pairs with name of field and its value (in
    #  a suitable form for displaying) for the given hidden information
    #  $hidden cannot be empty
    #  $wh is type of item
    # assume a single protocol was used to get the information
    global HIDDENFOR MESS

    if { $hidden == "" } { return "" }
    set hh [lindex $hidden 0]
    if { ! [regexp {^G([A-Za-z0-9]+):} $hh x pid] } {
	GMMessage "$MESS(badhidden): $hh"
	return ""
    }
    set fs $HIDDENFOR($pid,ns)
    foreach f $fs v $HIDDENFOR($pid,vs) t $HIDDENFOR($pid,ts) {
	set type($f) $t
    }
    set fvs ""
    foreach hh $hidden {
	if { [string first G${pid}: $hh] == 0 } {
	    set i [string first : $hh]
	    set h [string range $hh [expr $i+1] end]
	    if { [set i [string first = $h]] > 0 } {
		set f [string range $h 0 [expr $i-1]]
		set hd [HiddenDecode $type($f) \
			[string range $h [expr $i+1] end]]
		if { [lindex $hd 0] } {
		    lappend fvs [HiddenFormatVal $f $pid [lindex $hd 1]]
		} else { GMMessage "$MESS(badhidden): $hh" }
	    } else { GMMessage "$MESS(badhidden): $hh" }
	} else { Log "hidden not for $pid when displaying: $hh" }
    }
    return $fvs
}

proc HiddenFormatVal {field pid val} {
    # format hidden value for given $field and $pid
    # return pair with field title and formatted value
    global HIDDENFOR TXT

    foreach t $HIDDENFOR($pid,fm) {
	if { [lindex $t 0] == $field } {
	    set xs [lindex $t 2] ; set x ""
	    switch [lindex $t 1] {
		enum {
		    # enumeration from 0
		    set x [lindex $xs $val]
		}
		enumd {
		    # enumeration from 0, but last element is the default
		    if { $val < 0 || $val >= [llength $xs]-1 } {
			set x [lindex $xs end]
		    } else { set x [lindex $xs $val] }
		}
		envals {
		    # enumeration with values ($xs is list of pairs)
		    foreach p $xs {
			if { [lindex $p 0] == $val } {
			    set x [lindex $p 1] ; break
			}
		    }
		}
	    }
	    if { $x != "" } { set val $TXT(Ghidden_$x) }
	    break
	}
    }
    return [list $TXT(Ghidden_$field) $val]
}

proc HiddenRecover {wh avs hidden} {
    # convert old hidden attribute-value pairs to open attribute-value
    #  pairs when reading files in old format
    #  $wh in $TYPES
    #  $avs  open attribute=value pairs
    #  $hidden  hidden data
    # return pair with all open atribute-value pairs and remaining hidden
    #  information
    # see also proc LoadAttrPairs (files.tcl)
    global OLDHIDDENFOR FATTRPAIRS MESS DataIndex

    switch $wh {
	TR {
	    # D310 colour used to be hidden (up to 6.0.1)
	    # this should be generalized if there are other similar cases
	    if { [set ix [lsearch -glob $hidden GD310:colour=*]] != -1 } {
		set h [lindex $hidden $ix]
		set hidden [lreplace $hidden $ix $ix]
		foreach "type fattr conv" $OLDHIDDENFOR(D310:colour) { break }
		set f 1
		foreach fd $FATTRPAIRS(TR) {
		    if { [lindex $fd 0] == $fattr } { set f 0 ; break }
		}
		if { $f } { BUG Bad file attr name in OLDHIDDENFOR }
		set dvar [lindex $fd 2]
		global $dvar
		set default [set $dvar]
		regsub {GD310:colour=} $h "" hc
		set colour [HiddenDecode $type $hc]
		if { [lindex $colour 0] == 0 } {
		    GMMessage "$MESS(badhidden): $h"
		    set colour $default
		} else {
		    set colour [lindex $colour 1]
		    eval $conv
		}
		if { [[lindex $fd 3] $colour] } {
		    GMMessage [format $MESS(badattrval) $wh $h]
		} else {
		    lappend avs [list $DataIndex([lindex $fd 1]) $colour]
		}
	    }
	}
    }
    return [list $avs $hidden]
}

proc HiddenCode {type val} {
    # return codification of $val (with $type) as an ASCII string with all
    #  characters in the range [!-~] (codes 33 to 126)
    # codification is as follows:
    #   - 4 shift levels: normal (initial), control, upper, upper control
    #   - shift sequences:
    #      "|c" control, "|C" upper control, "|_" normal, "~" upper
    #   - "|" and "~" escaped: "||" and "|~"
    #   - code in 33..126 (except "|", "~"): normal, as self
    #   - code in 0-32: control as code+33 ("!" up to "A")
    #   - code 127: normal, as "|Z"
    #   - code in 161-255: upper, as code-128
    #   - code in 128-160: upper control, as code-128+33 ("!" up to "A")
    #   - repeated codes can be coded as:
    #      "|R" preceded by shift sequence if any and followed by count as
    #       character with code in (32+1..94), and code proper

    set chars [PackData $type [list $val]]
    set r ""
    set shift 0
    while { $chars != "" } {
	for { set c [lindex $chars 0] ; set n 1 } \
		{ $chars != "" && $c == [lindex $chars $n] } \
		{ incr n } {
	    continue
	}
	binary scan $c "c" x
	set x [expr ($x+256)%256]
	set sh ""
	switch [HiddenCharCat $x] {
	    copy {
		if { $shift } { set sh "|_" ; set shift 0 }
		set code $c
	    }
	    escape {
		if { $shift } { set sh "|_" ; set shift 0 }
		set code "|$c"
	    }
	    control {
		if { $shift != 1 } { set sh "|c" ; set shift 1 }
		set code ""
		append code [binary format "c" [expr $x+33]]
	    }
	    rubout {
		if { $shift } { set sh "|_" ; set shift 0 }
		set code "|Z"
	    }
	    upper {
		if { $shift != 2 } { set sh "~" ; set shift 2 }
		incr x -128
		set c "" ; append c [binary format "c" $x]
		switch [HiddenCharCat $x] {
		    copy { set code $c }
		    escape { set code "|$c" }
		    rubout { set code "|Z" }
		}
	    }
	    upper_control {
		if { $shift != 3 } { set sh "|C" ; set shift 3 }
		set code ""
		append code [binary format "c" [expr $x+33-128]]
	    }
	}
	if { $n<95 && [string length $code]*($n-1) > 3 } {
	    set k "" ; append k [binary format "c" [expr $n+32]]
	    set code "|R${k}$code"
	} else {
	    set fc $code
	    for { set i 1 } { $i < $n } { incr i } {
		set code "${fc}$code"
	    }
	}
	set r "${r}${sh}$code"
	set chars [lrange $chars $n end]
    }
    return $r
}

proc HiddenCharCat {b} {
    # find category of char with ASCII code $b

    if { $b>32 && $b<127 } {
	if { $b==124 || $b==126 } { return escape }
	return copy
    }
    if { $b < 33 } { return control }
    if { $b == 127 } { return rubout }
    if { $b < 161 } { return upper_control }
    return upper
}

proc HiddenDecode {type coded} {
    # decode $coded to get value of $type
    # return 0 on error, otherwise list with 1 and value

    set d ""
    set shift 0 ; set x 1
    array set dshift { 1 -33  2 128  3 95 }
    set lc [split $coded ""]
    while { $lc != "" } {
	set info 1
	switch -- [set c [lindex $lc 0]] {
	    "|" {
		set lc [lreplace $lc 0 0]
		if { $lc == "" } {
		    Log "HD> nothing after |"
		    return 0
		}
		set info 0
		switch -- [set c [lindex $lc 0]] {
		    "|" -
		    "~" {
			set info 1
			if { $shift == 2 } {
			    binary scan $c "c" v
			    set c ""
			    append c [binary format "c" [expr $v+128]]
			}
		    }
		    "c" { set shift 1 }
		    "C" { set shift 3 }
		    "_" { set shift 0 }
		    "Z" {
			set info 1
			if { $shift == 2 } {
			    set v 255
			} else { set v 128 }
			set c ""
			append c [binary format "c" $v]
		    }
		    "R" {
			set lc [lreplace $lc 0 0]
			if { $lc == "" } {
			    Log "HD> nothing after |R"
			    return 0
			}
			binary scan [lindex $lc 0] "c" x
			if { [set x [expr ($x+256)%256-32]] > 94 } {
			    Log "HD> |R with count $x>94"
			}
		    }
		    default {
			Log "HD> | followed by $c"
			return 0
		    }
		}
	    }
	    "~" { set info 0 ; set shift 2 }
	    default {
		if { $shift } {
		    binary scan $c "c" v
		    set c ""
		    append c [binary format "c" [expr $v+$dshift($shift)]]
		}
	    }  
	}
	if { $info } {
	    while { $x != 1 } {
		incr x -1 ; lappend d $c
	    }
	    lappend d $c
	}
	set lc [lreplace $lc 0 0]
    }
    if { $x != 1 } {
	Log "HD> repeat $x not followed by data"
	return 0
    } elseif { ! $info } {
	Log "HD> no data after command"
	return 0
    }
    # UnPackData returns a list that will have a single element here
    return [list 1 [lindex [UnPackData "$d" $type] 0]]
}

## initializing protocol definitions

proc InitGivenProtocols {data} {
    # initialize global description of product specific protocols as
    #  obtained from the receiver
    #  $data is list with list of lists each with a tag and a number for
    #    a protocol each protocol is followed by those it requires if any
    # if there is no information on this receiver in the table of protocols,
    #  there is no file $USERDIR/$MyProdId.prt and $NotLogging, create that
    #  file and ask the user to send it to mig@ncc.up.pt
    # return 0 on error
    global CurrPSPID PROTTAG PROTCAT PROTREQ PSPROTOCOLS MyProdId MyProdDescr \
	MyProdVersion NotLogging USERDIR PRTCLDEF MESS VERSION PDTYPE

    set npf [file join $USERDIR $MyProdId.prt]
    if { $NotLogging && [catch {set PRTCLDEF($MyProdId)}] && \
	    ! [file exists $npf] && ! [catch {set pfile [open $npf w]}] } {
	set outpfile 1
	puts $pfile "$MESS(written) GPSManager $VERSION [NowTZ]"
	puts $pfile ""
	puts $pfile "Please send this file to  mig@ncc.up.pt!"
	puts $pfile "It contains the list of protocols of your receiver"
	puts $pfile " which is not part of the table of protocols in GPSMan."
	puts $pfile ""
	puts $pfile "Keep this file to avoid GPSMan messages about this!"
	puts $pfile ""
	puts $pfile $MyProdDescr
	puts $pfile "Product id: $MyProdId, version: $MyProdVersion"
	set pfmess "Please read file $npf and send it to mig@ncc.up.pt!"
    } else { set outpfile 0 }

    foreach p $PSPROTOCOLS {
	set CurrPSPID($p) N/A
    }
    set data [lindex $data 0]
    set odata $data
    set undef 0 ; set error 0
    while { $data != "" } {
	set st [lindex $data 0]
	set t [lindex $st 0] ; set n [format %03d [lindex $st 1]]
	set p ${t}$n
	set data [lreplace $data 0 0]
	if { [catch {set tag $PROTTAG($t)}] } {
	    if { $outpfile } { puts $pfile "Unknown tag: $t\n>>$st<<" }
	    Log "IGP> unknown tag: $t\n>>$st<<"
	    incr error
	    continue
	}
	switch $tag {
	    nospec -  Physical {
		set undef 0
		if { $outpfile } { puts $pfile "$tag: $p" }
		Log "IGP> $tag: $p"
	    }
	    Link {
		set undef 0
		set CurrPSPID(Link) L$n
		if { $outpfile } { puts $pfile "Link: $p" }
		Log "IGP> Link: $p"
	    }
	    Transmission {
		if { $n == 1 } {
		    set undef 0
		    set CurrPSPID(Baud) T$n
		    if { $outpfile } { puts $pfile "Baud: $p" }
		    Log "IGP> Baud: $p"
		} else {
		    set undef 1
		    if { $outpfile } {
			puts $pfile "Unknown protocol: $p\n>>$st<<"
		    }
		    Log "IGP> unknown protocol: $p\n>>$st<<"
		}
	    }
	    Application {
		if { [catch {set ct $PROTCAT($p)}] } {
		    set undef 1
		    if { $outpfile } {
			puts $pfile "Unknown protocol: $p\n>>$st<<"
		    }
		    Log "IGP> unknown protocol: $p\n>>$st<<"
		} else {
		    set undef 0
		    if { $ct == "nospec" } {
			set info " (no spec)"
		    } else {
			if { $ct == "FleetManagement" && \
				 $CurrPSPID($ct) != "N/A" } {
			    append CurrPSPID($ct) "+" $p
			} else { set CurrPSPID($ct) $p }
			set info ""
		    }
		    if { $outpfile } { puts $pfile "Application: $p$info" }
		    Log "IGP> Application: $p$info"
		    set notav 0
		    foreach dp $PROTREQ($p) {
			set st [lindex $data 0]
			set t [lindex $st 0]
			set n [format %03d [lindex $st 1]]
			if { [catch {set tag $PROTTAG($t)}] } {
			    if { $outpfile } {
				puts $pfile "Unknown tag: $t\n>>$st<<"
			    }
			    Log "IGP> unknown tag: $t\n>>$st<<"
			    incr error
			    set data [lreplace $data 0 0]
			    continue
			}
			if { [string first "?" $dp] == 0 } {
			    if { $tag != "Data" } {
				# non-mandatory protocol missing
				break
			    }
			    set dp [string replace $dp 0 0]
			}
			set data [lreplace $data 0 0]
			if { $tag != "Data" } {
			    if { $outpfile } {
				puts $pfile \
				  "Error: missing data protocol for $p: $odata"
			    }
			    Log "IGP> missing data protocol for $p: $odata"
			    incr error
			    continue
			}
			set dprot "$t$n"
			if { [catch {set dtdp $PDTYPE($dprot)}] } {
			    if { $outpfile } {
				puts $pfile \
				  "Error: unknown data protocol $dprot: $odata"
			    }
			    Log "IGP> unknown data protocol $dprot: $odata"
			    incr notav
			} elseif { $dtdp == "nospec" } {
			    if { $outpfile } {
				puts $pfile \
				  "Data: $dprot (nospec)"
			    }
			    Log "IGP> Data: $dprot (nospec)"
			    incr notav
			} else {
			    if { $dp == "FMData" && \
				     ! [catch {set CurrPSPID($dp)}] } {
				append CurrPSPID($dp) "+" $dprot
			    } else { set CurrPSPID($dp) $dprot }
			    if { $outpfile } { puts $pfile "Data: $dprot" }
			    Log "IGP> Data: $dprot"
			}
		    }
		    if { $notav } {
			set CurrPSPID($ct) N/A
			if { $outpfile } { puts $pfile "... $p N/A" }
			Log "IGP> ... $p N/A"
		    }
		}
	    }
	    Data {
		if { $undef } {
		    set mess "discarding data protocol $st: $odata"
		} else {
		    set mess "spurious data protocol $st: $odata"
		}
		if { $outpfile } { puts $pfile "Error: $mess" }
		Log "IGP> $mess"
	    }	    
	}
    }
    if { $error } {
	if { $outpfile } {
	    puts $pfile "Aborting due to previous error(s)"
	    close $pfile
	    GMMessage $pfmess
	}
	Log "IGP> aborting due to previous error(s)"
	return 0
    }
    if { $CurrPSPID(Link) == "N/A" || $CurrPSPID(DevCmd) == "N/A" } {
	if { $outpfile } {
	    puts $pfile "Error: no Link or DevCmd protocol(s): $odata"
	    close $pfile
	    GMMessage $pfmess
	}
	Log "IGP> no Link or DevCmd protocol(s): $odata"
	return 0
    }
    if { $outpfile } {
	puts $pfile "Done"
	close $pfile
	GMMessage $pfmess
    }
    Log "IGP> done"
    RecAdjustToProtocols
    return 1
}

proc InitProtocols {prodid} {
    # initialize global description of product specific protocols in use
    #  from the default table
    #  $prodid is the product identifier
    global PRTCLDEF PSPROTOCOLS PSPID PSDIFF CurrPSPID PROTREQ PDTYPE

    switch -glob $PRTCLDEF($prodid) {
	array {
	    foreach p $PSPROTOCOLS {
		if { ! [catch {set pid $PSPID($prodid,$p)}] } {
		    set CurrPSPID($p) $pid
		    foreach dp $PROTREQ($pid) {
			set dprot $PSPID($prodid,$dp)
			if { [catch {set dtdp $PDTYPE($dprot)}] || \
				 $dtdp == "nospec" } {
			    Log "IP> unknown/nospec protocol $dprot for $p"
			    set CurrPSPID($p) N/A
			    break
			}
			set CurrPSPID($dp) $dprot
		    }
		} else { set CurrPSPID($p) N/A }
	    }
	}
	see=* {
	    regsub see= $PRTCLDEF($prodid) "" prod
	    InitProtocols $prod
	}
	diff {
	    InitProtocols [lindex $PSDIFF($prodid) 0]
	    foreach d [lreplace $PSDIFF($prodid) 0 0] {
		set CurrPSPID([lindex $d 0]) [lindex $d 1]
	    }
	}
    }
    RecAdjustToProtocols
    return
}

proc RecAdjustToProtocols {} {
    # set up protocol descriptions and adjust receiver parameters according to
    #  its protocols
    global PSPDEF PID RPID PSCMDDEF CMD CurrPSPID RECTYPES RECINFO PRTNUMID \
	PRTHNID PTRNUMID PTRHNID GPSProtocol RecCanChgBaud

    # set up protocol descriptions
    foreach d $PSPDEF($CurrPSPID(Link)) {
	set p [lindex $d 0] ; set v [lindex $d 1]
	set PID($p) $v ; set RPID($v) $p
    }
    foreach d $PSCMDDEF($CurrPSPID(DevCmd)) {
	set CMD([lindex $d 0]) [lindex $d 1]
    }
    # can the receiver change its baud rate?
    if { $GPSProtocol == "garmin" && ! [catch {set p $CurrPSPID(Baud)}] && \
	     $p != "N/A" } {
	set RecCanChgBaud 1
    } else { set RecCanChgBaud 0 }
    # is there a protocol for AL data?
    if { [catch {set p $CurrPSPID(ALData)}] || $p == "N/A" } {
	set RECINFO [Delete $RECINFO AL]
    }
    # $RECTYPES does not contain LAP if $SUPORTLAPS is not set
    foreach t $RECTYPES {
	# there are models without RTs, TRs or LAPs
	#  $CurrPSPID(LAP) may not be defined
	if { $t != "GR" && \
		 ( [catch {set p $CurrPSPID($t)}] || $p == "N/A" ) } {
	    set RECTYPES [Delete $RECTYPES $t]
	} else {
	    switch $t {
		RT {
		    if { ! [catch {set p $CurrPSPID(RTHeader)}] && \
			     [lsearch -exact $PRTNUMID $p] != -1 } {
			# RT needs numeric id
			set PRTHNID 1
		    } else { set PRTHNID 0 }
		}
		TR {
		    if { ! [catch {set p $CurrPSPID(TRHeader)}] && \
			     [lsearch -exact $PTRNUMID $p] != -1 } {
			# TR needs numeric id
			set PTRHNID 1
		    } else { set PTRHNID 0 }
		}
	    }
	}
    }
    return
}

proc NoProtCapability {} {
    # receiver does not support the Protocol Capability Protocol
    #  so the default definitions should be used
    global MyProdId PRTCLDEF

    Log "NPC> no Protocol Capability protocol"
    if { [catch {set PRTCLDEF($MyProdId)}] } {
	AbortComm recnotsuppd
	return
    }
    InitProtocols $MyProdId
    EndConnCheck defprots
    return
}

##### Simple Text Output protocol

proc ProcSimpleTextLine {line lxor} {
    # process a line of Garmin's Simple Text Output Format
    #  and call the higher level procs
    #  $lxor is the XOR of all chars in $line (not used here)

    if { [set data [DecodeSimpleText $line]] == 0 } {
	Log "PSTL> bad line: $line"
	return
    }
    Log "PSTL> got line: $line"
    UseRealTimeData $data
    return
}

proc DecodeSimpleText {line} {
    # decode a line (as string) of Garmin's Simple Text Output Format
    # return 0 on error, otherwise list with
    #  UTC time - as a list with y m d h mn s
    #  position - as a list with latd, longd (for datum WGS 84)
    #  pos fix  - as defined in array SimpleTextPStatus
    #  pos error - as a list with
    #              "_" for undefined estimated position error EPE in meters
    #              estimated horizontal error EPH in meters
    #              "_" for undefined estimated vertical error in meters
    #  altitude - signed number in meters
    #  velocity vector - as list with vlat vlong valt, in m/s
    #  bearing (track over ground, course made good, track made good) - degrees
    # any value may be "_" for non-available data
    # assume EPH and altitude to be given as integers
    global SimpleTextBegs SimpleTextEnds SimpleTextPStatus

    if { [string index $line 0] != "@" } { return 0 }
    foreach f "y m d h mn s hlat dlat mlat hlong dlong mlong pst eph as alt \
	       hvlong vlong hvlat vlat hvalt valt" \
	    type "int int int int int int NS int int/1000 EW int int/1000 \
	          dDgGS int +- int EW int/10 NS int/10 UD int/100" \
	    ix $SimpleTextBegs ixn $SimpleTextEnds {
	set v [string range $line $ix $ixn]
	if { [string first "_" $v] != -1 } {
	    set v "_"
	} else {
	    switch -glob $type {
		int/* {
		    if { ! [regexp {[0-9]+} $v] } { return 0 }
		    regsub {int/} $type "" div
		    scan $v %0d v
		    set v [expr 1.0*$v/$div]
		}
		int {
		    if { ! [regexp {[0-9]+} $v] } { return 0 }
		    scan $v %0d v
		}
		default {
		    if { [string first $v $type] == -1 } { return 0 }
		}
	    }
	    set $f $v
	}
    }
    if { $y != "_" } {
	if { $y < 89 } { incr y 2000 } else { incr y 1900 }
    }
    if { [set pst $SimpleTextPStatus($pst)] == "_" } {
	set latd "_" ; set longd "_"
    } else {
	foreach dim "lat long" neg "S W" {
	    if { [set v [set h$dim]] == "_" || [set xd [set d$dim]] == "" || \
		    [set xm [set m$dim]] == "_" } {
		set ${dim}d "_"
	    } else {
		if { $v == $neg } { set sign -1 } else { set sign 1 }
		set ${dim}d [expr $sign*($xd+$xm/60.0)]
	    }
	}
    }
    if { $alt != "_" } {
	if { $as == "_" } {
	    set alt "_"
	} elseif { $as == "-" } { set alt [expr -$alt] }
    }
    foreach dim "lat long alt" neg "S W D" {
	if { [set v [set v$dim]] != "_" } {
	    if { [set hv [set hv$dim]] == "_" } {
		set v$dim "_"
	    } elseif { $hv == $neg } {
		set v$dim [expr -$v]
	    } else { set v$dim $v }
	}
    }
    return [list [list $y $m $d $h $mn $s] \
	    [list $latd $longd] $pst [list _ $eph _] $alt \
	    [list $vlat $vlong $valt] _]
}

##### GPSMan interface

proc StartGPS {} {
    global NoGarmin

    ResetAutoNumberRT
    set NoGarmin 1
    return
}

proc ResetAutoNumberRT {} {
    # reset counter for renumbering RTs sent to receiver
    # based on DJG contribution
    global RTNextNumber RTSendNumber

    set RTNextNumber 1
    catch {unset RTSendNumber}
    return
}

proc GPSChangeProtocol {prot} {
    # change current protocol
    #  $prot in {garmin, garmin_usb, nmea, stext, simul}
    # must change GPSProtocolExt if successful
    global GPSProtocol GPSProtocolExt GPSState NoGarmin RealTimeLogOn TXT \
	GPSProtocolButton

    if { $GPSProtocol == $prot } { return }
    if { ! $NoGarmin } {
	# change cannot be to garmin
	Log "GCP> changing to protocol $prot"
	set NoGarmin 1
    }
    if { $GPSState == "online" } {
	CloseConnection
    }
    if { $RealTimeLogOn } {
	GPSRealTimeLogOnOff
    }
    set GPSProtocol $prot ; set GPSProtocolExt $TXT($prot)
    set GPSProtocolButton $prot
    return
}

proc AbortComm {args} {
    # abort communication in progress
    #  $args either void or a message id to be shown
    # global $Request==get$wh  where $wh in {WP RT TR LAP PosnData
    #                          DtTmData} (cf. SendData)
    #                         GPSOpResult is set to 1 (error)
    #                ==check=$cargs  where 2nd arg should be executed as
    #                         connection is apparently down;
    #                         GPSOpResult is set to 1 (error)
    #                ==chgbaud=$baud
    global Request Jobs MESS GPSOpResult PrdDATA PDTYPE

    CloseInProgrWindow
    foreach j $Jobs {
	catch { after cancel $j }
    }
    ResetSerial
    if { $args != "" } { GMMessage $MESS($args) }
    set Jobs ""
    switch -glob -- $Request {
	get* {
	    set Request abort
	    SendData abort
	    # after which SentPacket will call AbortComm again
	    set GPSOpResult 1
	}
	check=* {
	    if { $PrdDATA == "" } {
		# assume connection is down
		regsub check= $Request "" as
		eval [lindex $as 1]
		set GPSOpResult 1
	    } else {
		# A001 protocol failed: deal with Product Data from A000
		EndInProt A000 [UnPackData $PrdDATA $PDTYPE(PrdData)]
		# this will call either AbortComm or EndConnCheck
		set PrdDATA ""
		NoProtCapability
	    }
	}
	chgbaud*=* {
	    set Request idle
	    set GPSOpResult 1
	}
	abort {
	    set Request idle
	}
    }
    return
}

proc GPSChangeBaud {baud} {
    # called by user to try to change receiver baud rate
    #  $baud in $RECBAUDS, set in recdefs.tcl
    # only called when connected and $RecCanChgBaud is true
    #  see proc RecAdjustToProtocols
    # if port cannot be reopened restores interface
    global GetPVT MESS

    if { $GetPVT } {
	GMMessage $MESS(nmeainuse)
	return
    }
    ChangeGarminBaud $baud
    return
}

proc ChangeGarminBaud {baud} {
    # starts change of receiver baud rate
    #  $baud in $RECBAUDS, set in recdefs.tcl
    # only called when connected and $RecCanChgBaud is true
    #  see proc RecAdjustToProtocols
    # based on the Garmin 15H & 15L Specs, assuming this is the T001 protocol
    global MESS Request Jobs

    if { [FailsInProgrWindow [format $MESS(chgbaudto) $baud]] } {
	GMMessage $MESS(busytrylater)
	return
    }
    Log "FBC> Trying new baud rate $baud"
    # global Request will change according to what step is going on
    set Request chgbaud=$baud
    set Jobs [list [after 10000 "AbortComm baudchgfailed"] \
		   [after 0 "SendData chgbaud"]]
    return
}

proc EndChangeGarminBaud {} {
    # change of receiver baud rate was successful
    global GPSOpResult

    ResetSerial
    Log "ECGB> baud rate successfully changed"
    CloseInProgrWindow
    set GPSOpResult 0
    return
}

### real time log control

proc GarminStartPVT {} {
    # start real time logging with PVT protocol
    # return 0 on failure
    global NoGarmin MESS CurrPSPID GetPVT

    if { $NoGarmin } {
	GMMessage $MESS(mustconn1st)
	return 0
    }
    if { [catch {set CurrPSPID(PVTData)}] || $CurrPSPID(PVTData) == "N/A" } {
	GMMessage $MESS(rltmnotsupp)
	return 0
    }
    if { ! $GetPVT } {
	SendData start PVT
	set GetPVT 1
    }
    return 1
}

proc GarminStopPVT {} {
    global GetPVT

    if { $GetPVT } {
	SendData stop PVT
	set GetPVT 0
    }
    return
}

proc StartLineProtocol {procline baud} {
    # open connection for line-by-line protocol
    #  $procline is name of proc to call to process a line
    #  $baud rate
    # return 0 on failure
    global ProcProcChar ProcProcLine SInBuffer LineXOR Jobs GPSState
    
    set ProcProcChar ProcLineChar
    set ProcProcLine $procline
    ResetSerial
    set SInBuffer "" ; set LineXOR 0
    set Jobs ""
    if { [OpenSerialFailed $baud] } { return 0 }
    set GPSState online
    return 1
}

proc StopLineProtocol {} {
    global SRLFILE Eof GPSState

    # make sure the read char proc called by fileevent stops
    set Eof 1
    if { $GPSState == "online" } {
	close $SRLFILE
    }
    DisableGPS
    return
}

proc StartSimpleText {} {
    # start real time logging with Simple Text output protocol
    # return 0 on failure
    global STextPVT

    if { ! $STextPVT } {
	set STextPVT [StartLineProtocol ProcSimpleTextLine 9600]
	return $STextPVT
    }
    return 1
}

proc StopSimpleText {} {
    global STextPVT

    StopLineProtocol
    set STextPVT 0
    return
}



