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

 # 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 with the serial port (for debugging purposes)
set SERIALLOG $USERDIR/logfile
set NotLogging 1

# 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 ErrorCount 0

set LogFile ""
set NoInProc 1
set GetPVT 0
set STextPVT 0

set MAXTRIALS 5

##### low-level read procedures

## the following two procs do not work under MS-Windows...

proc ReadChar {} {
    # read chars when using file events or polling
    # call $ProcProcChar to process it
    global SRLFILE Eof InBuff NoInProc Polling GPSState

    if { $Eof } { return }
    set buff [read $SRLFILE]
    if { "$buff" != "" } {
	append InBuff $buff
	if { $NoInProc } { after 0 UseReadChars }
    }
    if { [set Eof [eof $SRLFILE]] } {
	Log "RP> at eof"
	set GPSState offline
	close $SRLFILE
    } elseif { $Polling } { update ; after 2 ReadChar }
    return
}

proc UseReadChars {} {
    # use chars read from serial channel in string $InBuff
    # call $ProcProcChar for each character read in
    global ProcProcChar InBuff NoInProc SInPacketState

    set NoInProc 0 ; set buff $InBuff ; set InBuff ""
    while { [set n [string length $buff]] } {
        for { set i 0 } { $i < $n } { incr i } {
	    while { "$SInPacketState" == "block" } { update ; after 1 }
	    $ProcProcChar [string index $buff $i]
	}
	set buff $InBuff ; set InBuff ""
    }
    set NoInProc 1
    return
}

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+0x100)%0x100]
    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
}

## special procs for use in MS-Windows...
# adapted from GPSMan 5.2
# implement fileevent readable with polling
#      as Tcl/Tk 8.0 and 8.1 do not support it for MS-Windows

proc ReadPollChar {} {
    # poll and read from serial channel
    global Eof InBuff SRLFILE

    if { $Eof } { return }
    set InBuff [read $SRLFILE]
    if { [set Eof [eof $SRLFILE]] } {
	Log "RPoll> at EOF"
	close $SRLFILE
    }
    UsePollChars 0 [string length $InBuff]
    return
}

proc UsePollChars {i n} {
    # use chars read from serial channel by polling routine
    #  $i is index of next char in buffer
    #  $n is length of buffer
    # call $ProcProcChar for each character read in
    global InBuff Eof SInPacketState ProcProcChar

    while { $i != $n } {
      if { "$SInPacketState" == "block" } {
	  after 5 "UsePollChars $i $n"
	  return
      }
      $ProcProcChar [string index $InBuff $i]
      incr i
    }
    if { ! $Eof } {
	after 1 ReadPollChar
    }
    return
}

##### logging and resetting

proc Log {m} {
    global NotLogging LogFile

    if { $NotLogging } { return }
    puts $LogFile $m
    flush $LogFile
    return
}

proc ResetSerial {} {
    global SOutBusy SInState SInPacketState SInBuffer LInBuffer \
	    PkInState PkLastPID ErrorCount

    set SOutBusy 0
    set SInPacketState start ; set SInState idle
    set SInBuffer "" ; set LInBuffer ""
    set PkInState idle ; set PkLastPID -1
    set ErrorCount 0
    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>   $v"
    }
    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 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
    global SOutBusy SOutPID SOutBPID SRecACKs SRecNAKs PDTYPE MESS \
	    PkLastPID

    set PkLastPID -1
    switch $pid {
	ACK {
	    set bpid [UnPackData $data $PDTYPE(ACK)]
	    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 {
	    set bpid [UnPackData $data $PDTYPE(NAK)]
	    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

    if { [binary scan $char "c" dec] != 1 } {
	Log "PC> scan failed: char=$char"
	return
    }
    set dec [expr ($dec+0x100)%0x100]
    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+0x100)%0x100]
    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 A001TOut GetPVT PkLastPID PkLastData Jobs \
	    TestRepeated

    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
	}
	A000 {
	    # product data protocol
	    if { [GoodPID $pid PrdData] } {
		# if supported, A001 will be immediately initiated
		set PkInState A001
		EndInProt A000 [UnPackData $data $PDTYPE(PrdData)]
		lappend Jobs [set A001TOut [after 500 "NoProtCapability"]]
	    }
	}
	A001 {
	    # protocol capability protocol
	    if { [GoodPID $pid PArray] } {
		set PkInState idle
		after cancel $A001TOut
		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 -  D150 -  D151 -  D152 -  D154 -  D155 {
	    # WP data; product specific
	    if { "$PkInPrevState" == "$CurrPSPID(RT)" } {
		set d RTWPData
		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 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 { [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
}

proc BadPacket {mess} {
    # count errors in receiving packets and abort operation if too many
    global ErrorCount MAXTRIALS MESS

    Log "BP> $mess"
    incr ErrorCount
    if { $ErrorCount == $MAXTRIALS } {
	GMMessage $MESS(toomanyerr)
	AbortComm
    }
    return
}

### 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 PkInState SOutBusy CurrPSPID CMD Command CommArgs \
	    PkOutState PkOutData PkOutCount PkOutStart PkOutWhat \
	    PkOutSaved RTWPoints RTStages TRTPoints

    set Command $type ; set CommArgs $args
    switch $type {
	product {
	    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 data ""
		    foreach rt $ixs {
			set k [llength $RTWPoints($rt)]
			if { ! $nolk } {
			    incr k [expr $k-1]
			}
			lappend data $rt $k $nolk
			incr n [expr $k+1]
		    }
		    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
		    foreach tr $ixs {
			incr n [llength $TRTPoints($tr)]
		    }
		    set PkOutStart XfrTR
		    set PkOutData $ixs
		    switch $p {
			A300 {
			    set PkOutCount $n
			    set PkOutState TRData
			}
			A301 {
			    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 PDTYPE \
	    WPName WPCommt WPPosn WPDatum WPDate WPSymbol WPDispOpt WPAlt \
	    WPHidden RTIdNumber RTCommt TRName TRDatum TRTPoints TRHidden \
	    DataIndex

    switch $PkOutState {
	D100 -	D101 -	D102 -	D103 -	D104 -	D105 -	D106 -	D107 -
	D108 -	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" != "" } {
		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
	    } else {
		# this is needed if no TRHeader is used
		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 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
	    }
	    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)
	    if { "$TRDatum($ix)" != "WGS 84" } {
		set PkOutSaved [ChangeTPsDatum $PkOutSaved \
			                      $TRDatum($ix) "WGS 84"]
	    }
	    return [list $PDTYPE($PkOutState) \
		        [PrepTRHeader $PkOutState $TRName($ix) $TRHidden($ix)]]
	}
    }
}

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

    set sc $SYMBOLCODE($symbol)
    set do $DISPOPTCODE($dispopt)
    if { "$alt" == "" } {
	if { "$pid" == "D108" } {
	    set alt 1.0e25
	} else { set alt 0 }
    } elseif { "$pid" != "D108" } {
	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]
	}
	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 hidden} {

    switch $pid {
	D310 {
	    set dc [HiddenVals D310 $hidden]
	    lappend dc $name
	    return $dc
	}
    }
}

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+0x100)%0x100]
	    }
	    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
		}
		set x [string trim $x " "]
		# not sure that this is really needed:
		# regsub -all {:} $x "" 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
		}
		set x [string trim $x " "]
		# not sure that this is really needed:
		# regsub -all {:} $x "" 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+0x100)%0x100]
		}
	    }
	    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

    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 "unimplemented data type when packing: $t"
	    }
	}
    }
    return $data
}

proc BadFloats {} {
    # check whether conversions of bytes to float are working correctly
    #  in this machine
    global MESS

    if { abs([UnPackData "A B C D" float]-781.035217285) > 1e-3 } {
	return [expr ! [GMConfirm $MESS(badfloats)]]
    }
    return 0
}

##### upper level

proc EndOutProt {pid} {
    # deal with end of output protocol
    #  $pid in {XfrWP XfrRT XfrTR}; not being used
    global Jobs

    CloseInProgrWindow
    set Jobs ""
    ResetSerial
    return
}

proc EndInProt {pid data} {
    # deal with end of input protocol
    #  $pid in {A000 A001 A010 A100 A200 A201 A300 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)
    #                ==check=$args  where 1st arg should be executed as
    #                          connection is ok
    global Request Jobs MESS TXT MyProdId MyProdDescr MyProdVersion

    switch $pid {
	A000 {
	    # Product Data
	    # kill timeout alarm
	    after cancel [lindex $Jobs 0]
	    Log "EIP> product data=$data"
	    set MyProdId [lindex $data 0]
	    set MyProdVersion [lindex $data 1] ; set descr [lindex $data 2]
	    if { $MyProdId == 77 && $MyProdVersion < 301 } {
		set MyProdId 77a
	    }
	    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
	    } else {
		GMMessage [format $MESS(nodata) $TXT($wh)]
	    }
	    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 name [AskForName $name]]" == "" } { continue }
		set d [lreplace $d 0 0 $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 t [PosType $PositionFormat]
    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 $t "WGS 84"]
		}
		Symbol {
		    set v [NameForCodeOf SYMBOL $v]
		}
		DispOpt {
		    set v [NameForCodeOf DISPOPT $v]
		}
		Alt {
		    switch $pid {
			D108 {
			    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 pairs with TR header info and list of TP info where
    #  the former is a pair with field names and values, and the latter is
    #  a list obtained by FormData
    set trs "" ; set tps ""
    set pidd $CurrPSPID(TRData)
    switch $CurrPSPID(TR) {
	A300 {
	    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 {
	    set pidh $CurrPSPID(TRHeader)
	    set phdr ""
	    foreach p $data {
		set vals [lindex $p 1]
		if { "[lindex $p 0]" != "header" } {
		    set tpp [ConvTPData $vals $pidd]
		    lappend tps [lindex $tpp 1]
		} else {
		    if { "$tps" != "" } {
			if { "$phdr" == "" } {
			    Log "IDTR> TPs without header"
			    return
			}
			lappend trs [list $phdr $tps]
			set tps ""
		    } elseif { "$phdr" != "" } {
			Log "IDTR> TR header without TPs; discarding"
		    }
		    if { [set phdr [ConvTRHeaderData $vals $pidh]] == -1 } {
			return
		    }
		}
	    }
	    if { "$tps" != "" && "$phdr" != "" } {
		lappend trs [list $phdr $tps]
	    }
	}
    }
    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 ; lappend vs "WGS 84" [lindex $tr 1]
	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 Hidden"
	    set vs [list [lindex $data 2] [HiddenGet D310 $data]]
	}
    }
    return [list $fs $vs]
}

proc ConvTPData {data pid} {
    # convert TP data
    # return pair with flag indicating a new TR, 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 latlong "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
    #  (assumed a leap year < 1990)
    global YEAR0 TimeOffset

    if { $gd == 0x7fffffff || $gd == 0xffffffff } { set gd 0 }
    set dd -1
    for { set yy $YEAR0 } { $yy < 1990 } { incr yy 4 } { incr dd }
    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

    set data [InDataPVT $data $pid]
    UsePVTData $data
    return
}

proc InDataPVT {data pid} {
    # get position, velocity and time data from receiver
    # return 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
    #  bearing (track over ground, course made good, track made good) - degrees
    global DAYSOF GarminPVTStatus

    switch $pid {
	D800 {
	    foreach f "alt epe eph epv fix tow posn vlong vlat valt mslh \
		       lsecs wnds" \
		    v $data {
		set $f $v
	    }
	    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 generating fake PVT data lists and calls to
    #  proc UsePVTData
    global SimulPVTData

    set SimulPVTData [list \
	    {2000 11 6 0 0 0} {45.02 -8.5} simul {23.2 34.3 56.5} 123.43 \
	    {-1.3 4.5 6.6} 23.4]
    SimulPVT
    return
}

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

    if { "$SimulPVTData" != "" } {
	if { $RealTimeLogOn } { UsePVTData $SimulPVTData }
	set date [lindex $SimulPVTData 0] ; set min [lindex $date 4]
	if { [set secs [expr [lindex $date end]+1]] > 59 } {
	    incr min ; set secs 0
	}
	set p [lindex $SimulPVTData 1]
	set p [list [expr 0.001*rand()-0.0005+[lindex $p 0]] \
		[expr (6*rand()-3)/10000.0+[lindex $p 1]]]
	set nd [lreplace $date 4 5 $min $secs]
	set SimulPVTData [lreplace $SimulPVTData 0 1 $nd $p]
	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)
    foreach f $fs p $HIDDENFOR($pid,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 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 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 $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+0x100)%0x100]
	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+0x100)%0x100-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
    }
    return [list 1 [UnPackData "$d" $type]]
}

## 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
    # return 0 on error
    global CurrPSPID PROTTAG PROTCAT PROTREQ PSPROTOCOLS PSPDEF PID RPID \
	    PSCMDDEF CMD

    foreach p $PSPROTOCOLS {
	set CurrPSPID($p) N/A
    }
    set data [lindex $data 0]
    set odata $data
    set undef 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]
	switch $PROTTAG($t) {
	    Physical {
		set undef 0
		Log "IGP> Physical: $p"
	    }
	    Link {
		set undef 0
		set CurrPSPID(Link) L$n
		Log "IGP> Link: $p"
	    }
	    Application {
		if { "[set ct $PROTCAT($p)]" == "undef" } {
		    set undef 1
		    Log "IGP> unknown protocol: $p"
		} else {
		    set undef 0
		    set CurrPSPID($PROTCAT($p)) $p
		    Log "IGP> Application: $p"
		    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 { "$PROTTAG($t)" != "Data" } {
			    Log "IGP> missing data protocol for $p: $odata"
			    return 0
			}
			set CurrPSPID($dp) ${t}$n
			Log "IGP> Data: ${t}$n"
		    }
		}
	    }
	    Data {
		if { $undef } {
		    Log "IGP> discarding data protocol $st: $odata"
		} else {
		    Log "IGP> spurious data protocol $st: $odata"
		}
		return 0
	    }	    
	}
    }
    if { "$CurrPSPID(Link)" == "N/A" || "$CurrPSPID(DevCmd)" == "N/A" } {
	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]
    }
    Log "IGP> done"
    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

    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]
	    }
	}
    }
    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"
    UsePVTData $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 OpenSerialFailed {baud} {
    # open serial port at given baud rate, and log file if needs be
    # return 1 on failure
    global MESS SRLFILE SERIALPORT InBuff Polling Eof tcl_platform \
	    NotLogging LogFile SERIALLOG

    if { [catch "set SRLFILE [open $SERIALPORT r+]"] } {
	GMMessage [format $MESS(badserial) $SERIALPORT]
	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
	}
	default {
	    GMMessage $MESS(badplatform)
	    return 1
	}
    }
    set Eof 0
    if {! $NotLogging && "$LogFile" == "" } { set LogFile [open $SERIALLOG w] }
    return 0
}

proc GPSConnection {args} {
    # check connection with receiver if protocol is garmin
    #  open serial port if needs be
    #  if connected the 1st argument will be called, else the 2nd one;
    #  continuation is either to SentPacket and EndConnCheck/AbortComm, or to
    #  AbortComm
    global Jobs Request MESS NoGarmin GPSProtocol ProcProcChar

    if { "$GPSProtocol" != "garmin" } {
	GMMessage $MESS(cantchkprot)
	return
    }
    if { $NoGarmin } {
	if { [BadFloats] } { return }
	if { [OpenSerialFailed 9600] } {
	    eval [lindex $args 1]
	    return
	}
	set ProcProcChar ProcChar
	set NoGarmin 0
    }
    if { [FailsInProgrWindow $MESS(check)] } {
	eval [lindex $args 1]
	return
    }
    set Request "check=$args"
    set Jobs [list [after 10000 AbortComm] [after 0 "SendData product"]]
    return
}

proc EndConnCheck {messid} {
    # end connection check successfully
    global Request MESS CurrPSPID NotLogging

    CloseInProgrWindow
    SymbolsDOForProtocol $CurrPSPID(WPData)
    ResetSerial
    Log "ECC> $MESS($messid)"
    regsub check= $Request "" as
    eval [lindex $as 0]
    return
}

proc GPSOff {} {
    global Request

    set Request turnOff
    SendData turnOff
    return
}

proc GPSBye {} {

    return
}

proc InitGPS {} {

    return
}

proc StartGPS {} {
    global NoGarmin

    set NoGarmin 1
    return
}

proc GetGPS {wh} {
    global GetSet

    set GetSet($wh) ""
    DoGetGPS $wh
    return
}

proc GetGPSIn {wh ixs} {
    # get data of type $wh (in {WP, RT}), but only to replace items with
    #  given indices $ixs (that may contain -1, meaning that new items
    #  can be created)
    global GetSet

    set GetSet($wh) $ixs
    DoGetGPS $wh
    return
}

proc DoGetGPS {wh} {
    global Request MESS ReplacedNames PkLastPID

    set PkLastPID -1
    if { [FailsInProgrWindow $MESS(get$wh)] } { return }
    set ReplacedNames ""
    set Request get$wh
    SendData get $wh
    return
}

proc PutGPS {wh ixs} {
    global Request MESS PkLastPID

    set PkLastPID -1
    if { [FailsInProgrWindow $MESS(put$wh)] } { return }
    set Request put$wh
    SendData put $wh $ixs
    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)
    #                ==check=$args  where 2nd arg should be executed as
    #                          connection is apparently down
    global Request Jobs MESS

    CloseInProgrWindow
    # should it be more cautious concerning these cancelations?
    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
	}
	check=* {
	    # assume connection is down
	    regsub check= $Request "" as
	    eval [lindex $as 1]
	}
    }
    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
}

### input/output progress window

proc CloseInProgrWindow {} {

    destroy .inprogr
    return
}

proc FailsInProgrWindow {mess} {
    # create dialog for signaling operation in progress
    # fail if window already exists
    #  single button: Abort; no bindings
    global COLOUR EPOSX EPOSY TXT SRecACKs SRecNAKs SPckts

    if { [winfo exists .inprogr] } { Raise .inprogr ; bell ; return 1 }

    set SRecACKs 0 ; set SRecNAKs 0 ; set SPckts 0
    toplevel .inprogr
    wm protocol .inprogr WM_DELETE_WINDOW { AbortComm }
    wm title .inprogr "GPS Manager: $TXT(commrec)"
    wm transient .inprogr
    wm geometry .inprogr +$EPOSX+$EPOSY

    frame .inprogr.fr -borderwidth 5 -bg $COLOUR(messbg)
    label .inprogr.fr.title -text "..." -relief sunken
    label .inprogr.fr.text -text "$mess"
    foreach f "A N P" v "SRecACKs SRecNAKs SPckts" t "ACKs NAKs packets" {
	set fw .inprogr.fr.frc$f
	frame $fw
	label $fw.val -textvariable $v -width 3
	label $fw.tit -text $TXT($t)
	pack $fw.val $fw.tit -side left -padx 0
    }
    button .inprogr.fr.ok -text $TXT(abort) -command AbortComm
    pack .inprogr.fr -side top
    pack .inprogr.fr.title .inprogr.fr.text .inprogr.fr.frcA .inprogr.fr.frcN \
	    .inprogr.fr.frcP .inprogr.fr.ok -side top -pady 5
    update idletasks
    grab .inprogr
    RaiseWindow .inprogr
    return 0
}



