#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
#  Copyright (c) 2004 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:  7 April 2004
#

 # implemented using
 #  "Garmin GPS Interface Specification", May 6 1998, 001-00063-00 Rev. 1
 #  kindly provided by Garmin Corporation
 # updated to follow the description in
 #  "Garmin GPS Interface Specification", April 13 1999, 001-00063-00 Rev. 2
 #  available from www.garmin.com
 # updated to follow the description in
 #  "Garmin GPS Interface Specification", December 6 1999, 001-00063-00 Rev. 3
 #  available from www.garmin.com


# 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 ""
set SInState idle
set SInPacketState start
set SOutBusy 0
set SRecACKs 0
set SRecNAKs 0
set PkInState idle
set PkLastData ""
set FirstACKNAK 1

set NoInProc 1
set GetPVT 0
set STextPVT 0

##### low-level read procedures

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

proc SendPacket {pid tvals} {
    # initialize a send packet action
    # wait for termination of a previous similar operation
    #  $pid is the packet identifier name
    #  $tvals is a pair with a list of types and 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 DoSendPacket (for ACK/NAK packets)
    #             or by ProcACKNAK (after a valid ACK is received)
    global SInState SOutBusy PID ACK NAK \
	    SOutPID SOutBData SOutDSum SOutBPID SOutSize

    if { $SOutBusy } {
	after 50 "SendPacket $pid {$tvals}"
	return
    }
    if { [catch {set SOutBPID $PID($pid)}] } {
	Log "SP> bad PID: $pid"
	return
    }
    set SOutBData [PackData $tvals]
    if { [set SOutSize [llength $SOutBData]] > 255 } {
	Log "SP> 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 "SP>   [expr ($v+256)%256]"
    }
    DoSendPacket
    return
}

proc DoSendPacket {} {
    # do the actual sending of the packet initiated by SendPacket;
    # 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 RPID DLE DLECHR ETX ACK NAK \
	    SOutPID SOutBData SOutDSum SOutBPID SOutSize SRLFILE

    if { $SOutBPID!=$ACK && $SOutBPID!=$NAK && "$SInState" == "get" } {
	after 50 DoSendPacket
        return
    }
    Log "DSP> 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 "DSP> 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 "DSP> 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

    SendPacket $what [list $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"
		    DoSendPacket
		} 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"
		}
		DoSendPacket
	    } 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"
	    # DoSendPacket
	    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 \
	    Jobs 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 } {
				if { $PacketID == "PrdData" } {
				    # save product data
				    set PrdDATA $PacketData
				    # wait for the Protocol Capability packet
				    set PkInState A001
				    SendACKNAK ACK PrdData
				} else {
				    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
    # this proc is not used anymore for A000 whose data is dealt with
    #  when A001 terminates (see below) or fails to show up (see proc
    #  AbortComm)
    global PkInState PkInPrevState PkInData PkInRTsData PkInCount CurrPSPID \
	    PDTYPE SPckts GetPVT PkLastPID PkLastData Jobs \
	    TestRepeated PrdDATA

    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
	}
	A001 {
	    # protocol capability protocol
	    if { [GoodPID $pid PArray] } {
		# the Product Data packet must be processed now
		# kill timeout alarm
		after cancel [lindex $Jobs 0]
		set PkInState idle
		global PrdDATA
		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 tranfer on A100 protocol
	    EndOfTranfer $pid $data WP A100 $PkInData
	}
	D100 -  D101 -  D102 -  D103 -  D104 -  D105 -  D106 -
	D107 -  D108 -  D109 -  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 tranfer on A200 protocol
	    lappend PkInRTsData $PkInData
	    EndOfTranfer $pid $data RT A200 $PkInRTsData
	}
	A201EXF {
	    # expecting end of tranfer on A201 protocol
	    lappend PkInRTsData $PkInData
	    EndOfTranfer $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 {
	    # TR transfer protocol
	    ExpectRecords $pid $data TRHeader
	}
	A300EXF {
	    # expecting end of tranfer on A300 protocol
	    EndOfTranfer $pid $data TR A300 $PkInData
	}
	A301EXF {
	    # expecting end of tranfer on A301 protocol
	    EndOfTranfer $pid $data TR A301 $PkInData
	}
	D300 -  D301 {
	    # 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 {
	    # 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 tranfer on A500 protocol
	    EndOfTranfer $pid $data AL A500 $PkInData
	}
	D500 -  D501 {
	    # 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))]
	    }
	}
	A011 -
	A400 -
	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 EndOfTranfer {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 hopefully be ACK-ed, what will fire up
    #  (in sequence) ProcChar, ProcACKNAK, SentPacket
    #   $type in {product, abort, turnOff, get, put, start, stop}
    #   $args void unless
    #       $type==get: in {WP RT TR PosnData DtTmData}; not yetin {AL Prx}
    #       $type==put: 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}
    global PDTYPE PRTHNID PkInState SOutBusy CurrPSPID CMD Command CommArgs \
	    PkOutState PkOutData PkOutCount PkOutStart PkOutWhat PkTRSegs \
	    PkOutSaved RTIdNumber RTWPoints RTStages TRTPoints TRSegStarts \
	    PrdDATA MESS

    set Command $type ; set CommArgs $args
    switch $type {
	product {
	    set PrdDATA ""
	    SendPacket PrdReq [list $PDTYPE(PrdReq) 0]
	}
	abort {
	    SendPacket CmdData [list int $CMD(Abort)]
	}
	turnOff {
	    SendPacket CmdData [list int $CMD(TurnOff)]
	    # the receiver may not send an ACK before turning off
	}
	start {
	    # $args in {PVT}
	    SendPacket CmdData [list int $CMD(Start$args)]
	}
	stop {
	    # $args in {PVT}
	    SendPacket CmdData [list int $CMD(Stop$args)]
	}
	get {
	    # $args in {WP RT TR PosnData DtTmData}; not implemented: {AL Prx}
	    SendPacket CmdData [list int $CMD(Xfr$args)]
	}
	put {
	    set putwhat [lindex $args 0]
	    if { "$putwhat" == "Prx" || "$putwhat" == "AL" } {
		Log "put $putwhat not implemented"
		return
	    }
	    set PkOutWhat $putwhat
	    set p $CurrPSPID($putwhat)
	    switch $putwhat {
		WP {
		    set ixs [lindex $args 1] ; set n [llength $ixs]
		    set PkOutStart XfrWP
		    switch $p {
			A100 {
			    set PkOutData $ixs ; set PkOutCount $n
			    set PkOutState $CurrPSPID(WPData)
			    SendPacket Records [list int $n]
			}
			default {
			    Log "protocol $p not implemented"
			    return
			}
		    }
		}
		RT {
		    set nolk [string compare $CurrPSPID(RT) A201]
		    set ixs [lindex $args 1]
		    set n 0 ; set badrts 0
		    set data ""
		    foreach rt $ixs {
			if { $PRTHNID && \
				! [regexp {^[0-9]+$} $RTIdNumber($rt)] } {
			    incr badrts
			    continue
			}
			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)
			    SendPacket Records [list int $n]
			}
			default {
			    Log "protocol $p not implemented"
			    return
			}
		    }
		}
		TR {
		    set ixs [lindex $args 1]
		    set n 0
		    set PkTRSegs 0
		    foreach tr $ixs {
			incr n [llength $TRTPoints($tr)]
			if { "$TRSegStarts($tr)" != "" } {
			    incr PkTRSegs
			}
		    }
		    set PkOutStart XfrTR
		    set PkOutData $ixs
		    switch $p {
			A300 {
			    # TR segments not supported
			    set PkTRSegs 0
			    set PkOutCount $n
			    set PkOutState TRData
			}
			A301 {
			    # $PkTRSegs is count of TRs having segments
			    set PkOutCount [expr $n+[llength $ixs]]
			    set PkOutState TRHeader
			}
			default {
			    Log "protocol $p not implemented"
			    return
			}

		    }
		    SendPacket Records [list int $n]
		}
		PosnData -
		DtTmData {
		    Log "PosnData/DtTmData not implemented"
		    return
		}
	    }
	}
    }
    return
}

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

    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
		    }
		}
		default {
		    # do nothing (?); should it report to layer above?
		}
	    }
	}
	Records {
	    if { $PkOutCount == 0 } {
		SendPacket XfrCmpl [list int $CMD($PkOutStart)]
	    } else {
		incr PkOutCount -1
		switch $PkOutWhat {
		    WP {
			SendPacket WPData [PrepData]
		    }
		    RT {
			SendPacket RTHeader [PrepData]
		    }
		    TR {
			set PkOutSaved ""
			set pid $PkOutState
			set PkOutState $CurrPSPID($pid)
			SendPacket $pid [PrepData]
		    }
		    default {
		    }
		}
	    }
	}
	WPData {
	    if { $PkOutCount == 0 } {
		SendPacket XfrCmpl [list int $CMD($PkOutStart)]
	    } else {
		incr PkOutCount -1
		SendPacket $pid [PrepData]
	    }
	}
	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
		SendPacket RTWPData [PrepData]
	    } else {
		SendPacket XfrCmpl [list 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]
		    SendPacket RTHeader [PrepData]
		} else {
		    SendPacket XfrCmpl [list int $CMD($PkOutStart)]
		}
	    } else {
		incr PkOutCount -1
		if { "$CurrPSPID(RT)" != "A201" } {
		    SendPacket RTWPData [PrepData]
		} elseif { "$pid" != "RTLinkData" } {
		    set PkOutState $CurrPSPID(RTLinkData)
		    SendPacket RTLinkData [PrepData]
		} else {
		    set PkOutState $CurrPSPID(RTWPData)
		    SendPacket RTWPData [PrepData]
		}
	    }
	}
	TRHeader {
	    if { $PkOutCount == 0 } {
		Log "StP> count 0 after TRHeader"
		SendPacket XfrCmpl [list int $CMD($PkOutStart)]
	    } else {
		incr PkOutCount -1
		set PkOutState $CurrPSPID(TRData)
		SendPacket TRData [PrepData]
	    }
	}
	TRData {
	    if { $PkOutCount == 0 } {
		SendPacket XfrCmpl [list int $CMD($PkOutStart)]
	    } else {
		incr PkOutCount -1
		if { "$PkOutSaved" == "" } {
		    switch $CurrPSPID(TR) {
			A301 {
			    set pid TRHeader
			    set PkOutState $CurrPSPID(TRHeader)
			}
		    }
		}
		SendPacket $pid [PrepData]
	    }
	}
	XfrCmpl {
	    EndOutProt $PkOutStart
	}
    }
    return
}

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

    switch $PkOutState {
	D100 -	D101 -	D102 -	D103 -	D104 -	D105 -	D106 -	D107 -
	D108 -	D109 -  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 [ConvertDatum [lindex $p 0] [lindex $p 1] \
			            $WPDatum($ix) "WGS 84" DDD]
	    }
	    return [list $PDTYPE($PkOutState) \
		             [PrepWPData $PkOutState \
			          $WPName($ix) $WPCommt($ix) $p \
				  $WPDate($ix) $WPSymbol($ix) \
				  $WPDispOpt($ix) $WPAlt($ix) $WPHidden($ix)]]
	}
	D200 -  D201 -  D202 {
	    # RT header
	    set ix [lindex $PkOutData 0]
	    return [list $PDTYPE($PkOutState) \
		       [PrepRTHeaderData $PkOutState \
		            $RTIdNumber($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 {
	    if { "$PkOutSaved" != "" } {
		# $PkOutSaved is list of TPs
		set p [lindex $PkOutSaved 0]
		set alt [lindex $p $DataIndex(TPalt)]
		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 [ConvertDatum [lindex $p 0] [lindex $p 1] \
			                $TRDatum($ix) "WGS 84" DDD]
		    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 {
	    # 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, D154-5
    #  $dispopt only in D103-4, D107-9, D155
    #  $alt only in D108-9 (as float), D150-5 (as integer)
    global SYMBOLCODE DISPOPTCODE

    set sc $SYMBOLCODE($symbol)
    set do $DISPOPTCODE($dispopt)
    if { "$alt" == "" } {
	if { "$pid" == "D108" || "$pid" == "D109" } {
	    set alt 1.0e25
	} else { set alt 0 }
    } elseif { "$pid" != "D108" || "$pid" == "D109" } {
	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]
	}
	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 {
	    set ps "0 2" ; set vs [list [ToGarminColour $pid $colour] $name]
	}
    }
    return [MergeData [HiddenVals $pid $hidden] $ps $vs]
}

proc PrepTRData {pid posn date new alt depth} {

    switch $pid {
	D300 {
	    return [list $posn $date $new]
	}
	D301 {
	    if { "$alt" == "" } { set alt 1.0e25 }
	    if { "$depth" == "" } { set depth 1.0e25 }
	    return [list $posn $date $alt $depth $new]
	}
    }
}

### 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 " "]
		}
	    }
	    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 "$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 "$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 {tvals} {
    # convert from a pair with a list of types and a list of values into a
    #  list of bytes (as TCL characters)

    return [split [DataToStr [lindex $tvals 0] [lindex $tvals 1]] ""]
}

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]
	    }
	    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" "$sla $slo"]
	    }
	    radian {
		set rla [expr [lindex $v 0]/57.29577951308232087684]
		set rlo [expr [lindex $v 1]/57.29577951308232087684]
		append data [DataToStr "double double" "$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 {
		# 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 == $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]] == "" } {
	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 A201 A300 A301 A500 A600 A700} (cf.
    #    ProcInPacket) but {A010 A500} not supported here
    # global $Request==get$wh  where $wh in {WP RT TR 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
    # return list of names of WPs
    global CurrPSPID GetDispl GetSet

    set wps ""
    foreach d [ConvWPData $data $CurrPSPID($pid)] {
	set name [lindex $d 0] ; set ix [IndexNamed WP $name]
	if { "$GetSet(WP)" == "" || [lsearch -exact $GetSet(WP) $ix] != -1 } {
	    if { ! [CheckName Ignore $name] } {
		if { [set nname [AskForName $name]] == "" } { continue }
		set d [AddOldNameToObs $d $name]
		set d [lreplace $d 0 0 $nname]
		set name $nname
	    }
	    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 suitable for
    #  use with SetItem
    global PositionFormat CREATIONDATE DATAFOR

    set r ""
    set fs $DATAFOR($pid,ns) ; set ps $DATAFOR($pid,ps)
    if { $CREATIONDATE } {
	set all [linsert $fs 0 Datum Date Hidden]
	set cnsts [list "WGS 84" [Now]]
    } else {
	set all [linsert $fs 0 Datum Hidden]
	set cnsts [list "WGS 84"]
    }
    foreach d $data {
	set vs $cnsts
	lappend vs [set hvs [HiddenGet $pid $d]]
	foreach f $fs p $ps {
	    set v [lindex $d $p]
	    switch $f {
		Posn {
		    set v [CreatePos [lindex $v 0] [lindex $v 1] \
			    $PositionFormat "WGS 84"]
		}
		Symbol {
		    set v [NameForCodeOf SYMBOL $v]
		}
		DispOpt {
		    if { "$pid" == "D109" } {
			# colour has been dealt with by HiddenGet
			set v [expr $v>>5]
		    }
		    set v [NameForCodeOf DISPOPT $v]
		}
		Alt {
		    switch $pid {
			D108 -  D109 {
			    if { $v > 1e10 } { 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
	}
	lappend r [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
	    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 {
	    return [list $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

    # $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
		}
		set tpp [ConvTPData $vals $pidd]
		if { [lindex $tpp 0] == 1 && "$tps" != "" } {
		    lappend trs [list "" $tps ""]
		    set tps ""
		}
		lappend tps [lindex $tpp 1]
	    }
	    if { "$tps" != "" } {
		lappend trs [list "" $tps ""]
	    }
	}
	A301 {
	    # segments are supported
	    set pidh $CurrPSPID(TRHeader)
	    set phdr "" ; set segsts "" ; set tpn 0
	    foreach p $data {
		set vals [lindex $p 1]
		if { "[lindex $p 0]" != "header" } {
		    set tpp [ConvTPData $vals $pidd]
		    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 {
	    set fs "Name Colour Hidden"
	    set vs [list [lindex $data 2] \
		    [FromGarminColour D310 [lindex $data 0]] \
		    [HiddenGet D310 $data]]
	}
    }
    return [list $fs $vs]
}

proc ConvTPData {data pid} {
    # convert TP data
    # return pair with flag indicating a new TR or segment, and list obtained
    #  by FormData

    set fs "" ; set vs ""
    switch $pid {
	D300 {
	    set p [lindex $data 0]
	    set d [lindex $data 1]
	    set new [lindex $data 2]
	}
	D301 {
	    set p [lindex $data 0]
	    set d [lindex $data 1]
	    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
	    }
	    set new [lindex $data 4]
	}
    }
    set p [CreatePos [lindex $p 0] [lindex $p 1] DMS "WGS 84"]
    set dl [ConvGarminDate $d]
    lappend fs latd longd latDMS longDMS date secs
    set tpdata [FormData TP $fs [concat $vs $p $dl]]
    return [list $new $tpdata]
}

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)*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 }
	    if { $fix < 2 } { 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
    # dependencies on what this proc does in proc ConvWPData !
    global HIDDENFOR

    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] }
    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 { GMMessage "$MESS(badhidden): $hh" ; break }
    }
    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 { GMMessage "$MESS(badhidden): $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 { GMMessage "$MESS(badhidden): $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 [list $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 }
		    escaped { 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 PSPDEF PID RPID \
	    PSCMDDEF CMD MyProdId MyProdDescr MyProdVersion NotLogging \
	    USERDIR PRTCLDEF MESS VERSION PRTNUMID PRTHNID

    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"
	    }
	    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 {
			set CurrPSPID($ct) $p ; set info ""
		    }
		    if { $outpfile } { puts $pfile "Application: $p$info" }
		    Log "IGP> Application: $p$info"
		    foreach dp $PROTREQ($p) {
			set st [lindex $data 0]
			set t [lindex $st 0]
			set n [format %03d [lindex $st 1]]
			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
			} elseif { $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 CurrPSPID($dp) ${t}$n
			if { $outpfile } { puts $pfile "Data: ${t}$n" }
			Log "IGP> Data: ${t}$n"
		    }
		}
	    }
	    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
    }
    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]
    }
    if { $outpfile } {
	puts $pfile "Done"
	close $pfile
	GMMessage $pfmess
    }
    Log "IGP> done"
    if { [lsearch -exact $PRTNUMID $CurrPSPID(RTHeader)] != -1 } {
	# RT needs numeric id
	set PRTHNID 1
    } else { set PRTHNID 0 }
    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 PSPDEF PSCMDDEF CurrPSPID \
	    CMD PID RPID PROTREQ PRTNUMID PRTHNID

    switch -glob $PRTCLDEF($prodid) {
	array {
	    foreach p $PSPROTOCOLS {
		set CurrPSPID($p) [set pid $PSPID($prodid,$p)]
		foreach dp $PROTREQ($pid) {
		    set CurrPSPID($dp) $PSPID($prodid,$dp)
		}
	    }
	    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]
	    }
	}
	see=* {
	    regsub see= $PRTCLDEF($prodid) "" prod
	    InitProtocols $prod
	}
	diff {
	    InitProtocols [lindex $PSDIFF($prodid) 0]
	    foreach d [lrange $PSDIFF($prodid) 1 end] {
		set CurrPSPID([lindex $d 0]) [lindex $d 1]
	    }
	}
    }
    if { [lsearch -exact $PRTNUMID $CurrPSPID(RTHeader)] != -1 } {
	# RT needs numeric id
	set PRTHNID 1
    } else { set PRTHNID 0 }
    return
}

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

    Log "NPC> no Protocol Capability protocol"
    if { [catch {set PRTCLDEF($MyProdId)}] } {
	AbortComm recnotsuppd
	return
    }
    InitProtocols $MyProdId
    if { [lsearch -exact $PRTNUMID $CurrPSPID(RTHeader)] != -1 } {
	# RT needs numeric id
	set PRTHNID 1
    } else { set PRTHNID 0 }
    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 GPSChangeProtocol {prot} {
    # change current protocol
    #  $prot in {garmin, nmea, stext, simul} (for Garmin)
    # must change GPSProtocolExt if successful
    global GPSProtocol GPSProtocolExt GPSState TXT NoGarmin SRLFILE Eof \
	    RealTimeLogOn

    if { "$GPSProtocol" == "$prot" } { return }
    if { ! $NoGarmin } {
	# change cannot be to garmin
	Log "GCP> changing to protocol $prot"
	set NoGarmin 1
    }
    if { "$GPSState" == "online" } {
	set Eof 1
	set GPSState offline
	close $SRLFILE
	DisableGPS
    }
    if { $RealTimeLogOn } {
	GPSRealTimeLogOnOff
    }
    set GPSProtocol $prot ; set GPSProtocolExt $TXT($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 PosnData DtTMData}
    #                          (cf. SendData)
    #                         GPSOpResult is set to 1 (error)
    #                ==check=$args  where 2nd arg should be executed as
    #                         connection is apparently down;
    #                         GPSOpResult is set to 1 (error)
    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
	    }
	}
	abort { }
    }
    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
}



