#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2008 Miguel Filgueiras mig@ncc.up.pt Universidade do Porto
#
#    This program is free software; you can redistribute it and/or modify
#      it under the terms of the GNU General Public License as published by
#      the Free Software Foundation; either version 2 of the License, or
#      (at your option) any later version.
#
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
#
#      You should have received a copy of the GNU General Public License
#      along with this program.
#
#  File: magellan.tcl
#  Last change:  25 March 2008
#
# This file contributed by Matt Martin (matt.martin@ieee.org)
#  Copyright (c) 2003 Matt Martin (matt.martin@ieee.org)
#   based on garmin.tcl
#
# Changes by Miguel Filgueiras marked "MF change"
#

# logging the communication: see serial.tcl

set MasterClock 0

#### no configurable values below this line!

set PDTYPE(Header) {word word word byte}
set PDTYPE(CMD) {string}
set PDTYPE(ACK) {word}
set PDTYPE(NAK) {word}
set PDTYPE("PMGNVER") {string string string}
set PDTYPE(WP) {magpos int string string string string }
set PDTYPE(WPIN) {magpos string string string string string string string string }
set PDTYPE(TR) {magpos int string hmss string string dmy }
set PDTYPE(TRIN) {dms dms float char string char string string }
set PDTYPE(RTIN) {string string string string string string string string}
set PDTYPE(RTs) {int int string int string string }
set PDTYPE(RT) {int int string int string string string string }

set MAG_SYMTAB [list WP_dot \
 square_green \
 house \
 avn_vortac \
 airport \
 amusement_park \
 casino \
 car_repair\
 boat \
 camping \
 exit \
 1st_aid \
 avn_vordme \
 buoy\
 fuel\
 deer\
 golf\
 lodging\
 fish\
 large_city\
 light\
 capitol_city\
 boat_ramp\
 medium_city\
 museum\
 danger\
 park\
 store\
 knife_fork\
 mountains\
 diver_down_1\
 RV_park\
 military\
 scenic\
 small_city\
 oil_field\
 stadium\
 info\
 truck_stop\
 drinking_water\
 wreck\
 zoo]

#"#"# 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 SInSum 0
set PkInState idle
set PkLastData ""

set NoInProc 1
set GetPVT 0
set STextPVT 0

##### link layer

proc MakeMagPacket {pid tvals} {
    global SInState SOutBusy PID ACK NAK \
	    SOutPID SOutBData SOutDSum SOutBPID SOutSize

    set SOutBData [concat [split "PMGN$pid" ""] "" [PackData $tvals] ]

    if { [set SOutSize [llength $SOutBData]] > 255 } {
	Log "SP> too much data ($SOutSize>255)"
	return
    }
    Log "SP> sending packet $pid with:"

    if { $pid=="CMD" } {
	set cmdname [lindex $tvals 1]
	if { $cmdname=="HANDON" || $cmdname=="HANDOFF"} { 
	    set SOutBusy 0 
	}
    }

    set SOutDSum 0 
    foreach ch $SOutBData {
	binary scan $ch "c" v
	set SOutDSum [expr $SOutDSum ^ $v]
#	Log "SP>  $ch $v csum is $SOutDSum"
    }

    set SOutBData [concat "\$" $SOutBData "*" [split [format "%02X" $SOutDSum]] "\\r" "\\n" ]

    return $SOutBData
}

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
    tdebug "SendPacket $pid $tvals, SOutBusy is $SOutBusy"
    while { $SOutBusy } {
	tdebug "waiting in sp"
	after 50 "SendPacket $pid {$tvals}"
	return
    }

    if { [catch {set SOutBPID $pid}] } {
	Log "SP> bad PID: $pid"
	return
    }

    set SOutBusy 1 ; set SOutPID $pid

    MakeMagPacket $pid $tvals
    
    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!="CSM" && "$SInState" == "get" } {
    # Wait for checksum
    #	after 50 DoSendPacket
    #    return
    #}

    tdebug "in dsp, running is $SOutBPID, $SOutBData"
    if { $SOutBPID!="CSM" && $SOutBPID!="placeholder" } {
	set SInPacketState start ; set SInState put
    }

    tdebug "DSP> sending packet $SOutPID, $SOutBData "

    foreach b $SOutBData {
	puts -nonewline $SRLFILE "$b"
    }

    flush $SRLFILE
    Log "DSP> size=$SOutSize, checksum=$SOutDSum [format "%2X" $SOutDSum]"
    if { $SOutBPID=="CSM" || $SOutBData=="HANDON" || $SOutBData=="HANDOFF"} { 
	# No more output expected
	set SOutBusy 0 
    }

    tdebug "dsp done, soutbusy is $SOutBusy"
    return
}

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

    #NAK is currently unsupported...
    set SOutBusy 0
    SendPacket "CSM" [list {string} [format "%02X" $checksum]]
    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 {
	"PMGNCSM" {
	    set bpid "CSM" 
	    #[UnPackData $data $PDTYPE(ACK)]
	    incr SRecACKs
	    if { $SOutBusy } {
		SentPacket $SOutPID
		set SOutBusy 0
	    } else {
		Log "PAN> ACK for $bpid when output is idle"
		set SOutBusy 0
	    }
	}
	NAK {
	    set bpid [UnPackData $data $PDTYPE(NAK)]
	    if { $SOutBusy } {
		incr SRecNAKs
		Log "PAN> NAK number $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 PacketDone GotCSum csum

    if { [binary scan $char "c" dec] != 1 } {
	Log "PC> scan failed: char=$char"
	return
    }
    set dec [expr ($dec+0x100)%0x100]
    tdebug "PC> got $dec $char; state=$SInState, $SInPacketState"

    if { "$SInState" == "idle" && ! $GetPVT } {
	    Log "PC> idle: discarded $dec" ; return
    }
    switch $SInPacketState {
	block {
	    Log "PC> bug: called when blocked!"
	}
	start {
	    if { $char == "$" } {
		set PacketID ""
		set PacketDone 0
		set GotCSum 0
		set SInSum 0
	    } elseif { $char != "," } {
		set SInSum [expr $SInSum ^ $dec]
		append PacketID $char
	    } else {
		Log "command header complete for $PacketID "
		set SInPacketState gotPID
		set SInSum [expr $SInSum ^ $dec]
		if { $PacketID != "PMGNCSM" } {
		    set NotACKNAK 1
		} else {
		    set NotACKNAK 0
		}
	    }
	}
	gotPID {
	    if { $PacketDone } {
		if { $GotCSum>1 } {
		    Log "PC> got checksum $csum"
		    if { $char == "\n" } {
			Log "sentcsum is $csum vs $SInSum, notacknak is $NotACKNAK"
			if { $csum == $SInSum } {
			    set SInPacketState block
			    set PacketData $SInBuffer
			    #[lrange $SInBuffer 1 $size]
			    if { $NotACKNAK } {
				ProcInPacket $PacketID "$PacketData"
				SendACKNAK $csum
		            } else {
				ProcACKNAK $PacketID "$PacketData"
			    }
			} else {
			    if { $NotACKNAK } {
				Log "Checksum Error!"
				SendACKNAK $csum
			    }
			}
			set SInPacketState start
			set SInBuffer ""
		    } else {
			Log "skipping char $dec"
		    }
		} else {
		    scan $char "%x" dec
		    set csum [expr $csum*0x10+$dec]
		    incr GotCSum 1
		}
	    } elseif { $char == "*" } {
		set PacketDone 1
		set csum 0
	    } else {
		lappend SInBuffer $char
		set SInSum [expr $SInSum ^ $dec]
	    }
	}
    }
    return
}


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

    set pidstr [concat $pid ]
    tdebug "for $PkInState processing packet $pidstr with data $data"
    
    if { $pidstr == "PMGNVER"} {
	EndInProt $pidstr [UnPackData $data {string string string} ]
    }

    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
	}
	"WP" {
	    # WayPoint data
	    if { [GoodPID $pid "PMGNWPL"] } {
		lappend PkInData [UnPackData $data $PDTYPE(WPIN)]
	    } else {
		# probably last packet ...
		EndOfTranfer $pid $data RT "WP" $PkInData
	    }

	}
	"RT" {
	    # Routes
	    if { [GoodPID $pid "PMGNRTE"] } {
		lappend PkInData [UnPackData $data $PDTYPE(RTIN) ]
	    } else {
		# trouble
		EndOfTranfer $pid $data RT "RT" $PkInData
	    }

	}

	"TR" {
	    # Track Data
	    if { [GoodPID $pid "PMGNTRK"]} {
		lappend PkInData [UnPackData $data {dms dms float char string char string string }]
	    } else {
		# probably last packet ...
		EndOfTranfer $pid $data RT "TR" $PkInData
	    }

	}
	default {
	    Log "$PkInState not supported"
	    set PkInState idle
	}
    }
    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

    tdebug "EOT $pid $data"
    if { [GoodPID $pid "PMGNCMD"] } {
	if { [GoodCMD [UnPackData $data string] "END"] } {
	    EndInProt $prot $recs
	    set PkInState idle
	}
    } else {
	# only for debuggin, kill this !
	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

    if { $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 PkInState SOutBusy CurrPSPID CMD Command CommArgs \
	    PkOutState PkOutData PkOutCount PkOutStart PkOutWhat \
	    PkOutSaved RTWPoints RTStages TRTPoints PkInData TRtot RTIdNumber

    set Command $type ; set CommArgs $args
    switch $type {
	product {
	    # query product type
	    SendPacket CMD [list $PDTYPE(CMD) "VERSION"]
	    tdebug "back from version query"
	    # setup GPS for handshaking
	    SendPacket CMD [list $PDTYPE(CMD) "HANDON"]
	    #SendPacket CMD [list $PDTYPE(CMD) "TON"]
	}
	abort {
	    tdebug "Sending stop cmd"
	    set SOutBusy 0
	    SendPacket CMD [list $PDTYPE(CMD) "END"]
	    SendPacket CMD [list $PDTYPE(CMD) "STOP"]
	    tdebug "Finished Sending stop cmd"
	}
	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 CMD [list $PDTYPE(CMD) "END"]
	}
	get {
	    # $args in {WP RT TR PosnData DtTmData}; not implemented: {AL Prx}
	    set getwhat [lindex $args 0]
	    #puts "get got $getwhat"
	    switch $getwhat {
		WP {
		    SendPacket CMD [list $PDTYPE(CMD) "WAYPOINT" ]
		    set PkInData ""
		}
		RT {
		    SendPacket CMD [list $PDTYPE(CMD) "ROUTE"]
		    set PkInData ""
		}
		TR {
		    SendPacket CMD [list $PDTYPE(CMD) "TRACK,2"]
		    set PkInData ""
		}
	    }
	}
	put {
	    set putwhat [lindex $args 0]
	    if { "$putwhat" == "Prx" || "$putwhat" == "AL" } {
		Log "put $putwhat not implemented"
		return
	    }
            tdebug "putwhat is $putwhat"
	    set PkOutWhat $putwhat

	    switch $putwhat {
		WP {
		    set ixs [lindex $args 1] ; set n [llength $ixs]
		    set PkOutStart XfrWP
		    set PkOutData $ixs ; set PkOutCount $n
		    set PkOutState WP
		    set PkOutSaved ""
		    SentPacket Records
		}
		TR {
		    set ixs [lindex $args 1]
		    set n 0
		    foreach tr $ixs {
			incr n [llength $TRTPoints($tr)]
		    }
		    set PkOutStart XfrTR
		    set PkOutData $ixs ; set PkOutCount $n
		    set PkOutState TR
		    SentPacket Records
		}
		RT {
		    set ixs [lindex $args 1]
		    set wpl ""
		    # count total # of route points, make a list of WPs
		    set TRtot 0
		    set PkOutSaved ""
		    foreach rt $ixs {
			# Make sure the ID is a number
			if { ! [CheckNumber Ignore $RTIdNumber($rt)] } {
			    continue
			}
			lappend PkOutSaved $rt
			incr TRtot [expr ([llength $RTWPoints($rt)]+1)/2]
			foreach wp $RTWPoints($rt) {
			    set wpn [IndexNamed WP $wp]
			    if { [lsearch -exact $wpl $wpn] == -1 } {
				lappend wpl $wpn
			    }
			}
		    }

		    # send the waypoints first
		    set PkOutData $wpl
		    set PkOutCount [llength $wpl]
		    set PkOutState WP
		    set PkOutWhat WP
		    # initiat WP send sequence
		    SentPacket Records

		}
	    }
	}
    }
    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 TRtot

    incr SPckts
    Log "StP> packet for $pid sent"
    switch $pid {
	CMD {
	    switch $Command {
		get {
		    Log "Command is $CommArgs"
		    if { "$PkInState" != "idle" } {
			lappend Jobs [after 50 "SentPacket $pid"]
			return
		    }
		    set PkInState $CommArgs 
		    #$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
		    }
		}
		stop {
		    EndOutProt $PkOutStart
		}
		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 WPL [PrepData]
		    }
		    RT {
			set PkOutSaved ""
			SendPacket RTE [PrepData]
		    }
		    TR {
			set PkOutSaved ""
			SendPacket TRK [PrepData]
		    }
		    default {
		    }
		}
	    }
	}
	WPL {
	    if { $PkOutCount == 0 } {
		# see if we have any remaining route data (send after last WP)
		if { [llength $PkOutSaved] } {
		    # Set up to send routes
		    # not the best way to structure this, but it works
		    set PkOutStart XfrRT
		    set PkOutData $PkOutSaved
		    set PkOutSaved ""
		    # pull total # of route points from temp storage
		    set PkOutCount $TRtot
		    set PkOutState RT
		    set PkOutWhat RT
		    # now send out the route def
		    SentPacket Records
		    #SendPacket RTE [PrepData]
		} else {
		    SendData stop
		}
	    } else {
		incr PkOutCount -1
		SendPacket WPL [PrepData]
	    }
	}
	TRK {
	    if { $PkOutCount == 0 } {
		SendData stop
	    } else {
		incr PkOutCount -1
		SendPacket TRK [PrepData]
	    }
	}
	RTE {
	    if { $PkOutCount == 0 } {
		SendData stop
	    } else {
		incr PkOutCount -1
		SendPacket RTE [PrepData]
	    }
	}
    }
    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 RTWPoints TRcnt TRtot TRnum

    switch $PkOutState {
	WP {
	    set ix [lindex $PkOutData 0]
	    set PkOutData [lreplace $PkOutData 0 0]
	    return [PrepMagWPData $ix]
	}
	TR {
	    if { "$PkOutSaved" != "" } {
		set p [lindex $PkOutSaved 0]
		set PkOutSaved [lreplace $PkOutSaved 0 0]
		set new 0
	    } else {
		# select a new track and covert all of its points
		set ix [lindex $PkOutData 0]
		#set PkOutData [lreplace $PkOutData 0 0]
		set p [lindex $TRTPoints($ix) 0]
		set PkOutSaved [lreplace $TRTPoints($ix) 0 0]
		if { "$TRDatum($ix)" != "WGS 84" } {
		    # MF change: calling FormatPosition instead of ConvertDatum
		    set tmpp [lindex [FormatPosition [lindex $p 0] \
					  [lindex $p 1] $TRDatum($ix) \
					  DDD "WGS 84"] 0]
		    set p [concat $tmpp [lrange $p 4 end]]
		    set PkOutSaved [ChangeTPsDatum $PkOutSaved \
					$TRDatum($ix) "WGS 84"]
		}
	    }
	    return [PrepMagTRData $p]
	}
	RT {
	    set pktype RT
	    # still working through one route
	    if { "$PkOutSaved" != "" } {
		incr TRcnt
		set PkOutSaved [lreplace $PkOutSaved 0 1]
	    }
	    if { "$PkOutSaved" == "" } {
		# go to the next route
		set ix [lindex $PkOutData 0]
		set TRtot [expr ([llength $RTWPoints($ix)] + 1) / 2]
		set TRcnt 1
		set TRnum $ix
		set PkOutData [lreplace $PkOutData 0 0]
		set PkOutSaved [Apply "$RTWPoints($ix)" IndexNamed WP]
	    }
	    return [PrepMagRTdata [lindex $PkOutSaved 0] [lindex $PkOutSaved 1] $TRtot $TRcnt $TRnum ]
	}
    }
}

proc get_mag_symnum {symname} {
    global MAG_SYMTAB
    
    set sn [lsearch -exact $MAG_SYMTAB $symname]
    if {$sn < 26} {
	set sc [format "%c" [expr $sn + 97]]
    } else {
	set sc [format "a%c" [expr $sn + 71]]
    }

    return $sc
}

proc PrepMagWPData {ix } {
    global DISPOPTCODE MAG_SYMTAB WPName WPCommt WPPosn WPDatum WPDate WPSymbol WPDispOpt WPAlt \
	    WPHidden RTIdNumber RTCommt TRName TRDatum TRTPoints TRHidden \
	    DataIndex RTWPoints TRcnt TRtot TRnum PDTYPE


    set p $WPPosn($ix)
    if { "$WPDatum($ix)" != "WGS 84" } {
	# MF change: calling FormatPosition instead of ConvertDatum
	set p [lindex [FormatPosition [lindex $p 0] [lindex $p 1] \
			   $WPDatum($ix) DDD "WGS 84"] 0]
    }

    # figure out the symbol code
    set sn [lsearch -exact $MAG_SYMTAB $WPSymbol($ix)]
    if {$sn < 26} {
	set sc [format "%c" [expr $sn + 97]]
    } else {
	set sc [format "a%c" [expr $sn + 71]]
    }

    set alt $WPAlt($ix) 
    if { "$alt" == "" } {
	set alt 0
    } else {
	set alt [expr round($alt)]
    }
    # set this to proper return vals
    return [list $PDTYPE(WP) [list $p $alt "M" $WPName($ix) $WPCommt($ix) $sc ]]
}

proc PrepMagTRData { p } {
    global DISPOPTCODE MAG_SYMTAB DataIndex TRcnt TRtot TRnum PDTYPE


    set dt [lindex $p $DataIndex(TPsecs)]
    set depth [lindex $p $DataIndex(TPdepth)]
    set alt [expr int([lindex $p $DataIndex(TPalt)])]
    return [list $PDTYPE(TR) \
		[list $p $alt M $dt A "" $dt ]
		    ]
}

proc PrepMagRTdata { t u TRtot TRcnt TRnum } {
    global WPName WPSymbol PDTYPE RTIdNumber

    # Set up a pair of route points for transmission
    set p $WPName($t)
    set psym [get_mag_symnum $WPSymbol($t)]
    set pktype RT
    if { $u == ""} {
		set pktype RTs
		return [list $PDTYPE($pktype) \
			   [list $TRtot $TRcnt c $RTIdNumber($TRnum) $p $psym]]
    } else {
		set q $WPName($u)
		set qsym [get_mag_symnum $WPSymbol($u)]
		return [list $PDTYPE($pktype) \
			    [list $TRtot $TRcnt c $RTIdNumber($TRnum) \
				 $p $psym $q $qsym]]
    }

}

### 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 2
		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 {
		set x 0
		set tmpstr [SubString $data]
		set n [string length $tmpstr]
		incr n
		scan $tmpstr "%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
		set size [llength $data]
		while { $n < $size } {
		    set c [lindex $data $n]
		    incr n
		    #binary scan $c c d
		    #if { $d == 0 } { break }
		    if { $c == "," } { 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
	    }

	    dms { # return float from DMS notation
		set tmpstr [SubString $data]
		set n [string length $tmpstr]

		# get NSEW
		set dircode [join [lrange $data [expr $n + 1] [expr $n + 1] ] ""]
		# Are we in pos or negative direction ?

		set dirsign 1
		switch $dircode {
		    "N" {
			set deglen 2
		    }
		    "S" {
			set deglen 2
			set dirsign -1
		    }
		    "E" {
			set deglen 3
		    }
		    "W" {
			set deglen 3
			set dirsign -1
		    }
		}
		
		set tmin 0
		set tdeg 0
		scan [string range $tmpstr 0 [expr $deglen - 1]] "%f" tdeg
		scan [string range $tmpstr $deglen $n] "%f" tmin

		incr n 3
		
		set x 0
		set x [expr ( $tdeg + $tmin / 60.0 ) * $dirsign ]
		
	    }

	    magpos {
		# return lat/long in degrees
		scan [join [lrange $data 0 1] ""] "%f" ldeg
		scan [join [lrange $data 2 8] ""] "%f" lmin
		set la [expr $ldeg + $lmin / 60.0 ]

		if { [lrange $data 9 9] == "S"} {
		    set la [expr -1 * $la]
		}

		scan [join [lrange $data 11 13] ""] "%f" ldeg
		scan [join [lrange $data 14 20] ""] "%f" lmin
		set lo [expr $ldeg + $lmin / 60.0 ]

		if { [lrange $data 21 21] == "W"} {
		    set lo [expr -1 * $lo]
		}

		set x "$la $lo"
		set n 22
	    }
	    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 SubString {data} {
    # Pull a string from a list of chars, terminated by a comma
    set sbstr "" ; set n 0
    set size [llength $data]
    while { $n < $size } {
	set c [lindex $data $n]
	incr n
	if { $c == "," } { break }
	append sbstr $c
    }
    return [join $sbstr ""]
}

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 {
	append data ","
	switch -glob $t {
	    char {
		append data [binary format "a" $v]
	    }
	    boolean -
	    byte {
		append data [binary format "c" $v]
	    }
	    word -
	    int {
		append data [format "%i" $v]
	    }
	    longword -
	    long {
		append data [binary format "i" $v]
	    }
	    float {
		append data [format "%f" $v]
	    }
	    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*" $v]
	    }
	    charray=* {
		regsub charray= $t "" n
		append data [binary format "A$n" $v]
	    }
	    dmy {
		append data [DateFromSecsFmt $v DDMMYY]
	    }
	    hmss {
		set s [expr $v%60] ; set x [expr ($v-$s)/60]
		set mn [expr $x%60] ; set h [expr (($x-$mn)/60)%24 ]
		set fv [expr $s+$mn*100+$h*10000]
		append data [format "%09.2f" $fv]
	    }
	    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]
		}
	    }
	    magpos {
		set lat [lindex $v 0]
		append data [format "%02d" [expr abs(int($lat))]]
		append data [format "%06.3f" [expr abs($lat - int($lat)) * 60.0 ]]
		if {$lat > 0} {
		    append data ",N,"
		} else {
		    append data ",S,"
		}

		set long [lindex $v 1]
		append data [format "%03d" [expr abs(int($long))]]
		append data [format "%06.3f" [expr abs($long - int($long)) * 60.0 ]]
		if {$long < 0} {
		    append data ",W"
		} else {
		    append data ",E"
		}
	    }
	    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
}

##### upper level

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
    tdebug " at endinprot $pid"
    switch $pid {
	"PMGNVER" {
	    # 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]
	    set MyProdDescr [format $MESS(connectedto) $descr]
	    set SInState idle
	    EndConnCheck gotprots
	    return
	}
    }
    CloseInProgrWindow
    ResetSerial

    switch -glob $Request {
	get* {
	    regsub get $Request "" wh
	    SetCursor . watch
	    if { "[lindex $data 0]" != "" } {
		InData$wh $data
		# MF change
		EndWPRenaming
	    } 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 AddMagWPT {d} {
    global GetDispl
    set name [lindex $d 0] ; set ix [IndexNamed WP $name]
    if { ! [CheckName Ignore $name] } {
	if { [set name [AskForName $name]] == "" } { return "" }
	# MF change: add remark with old name
	set d [AddOldNameToObs WP $d $name]

	set d [lreplace $d 0 0 $name]
    }
    # MF change: name may be replaced in StoreWP
    set name [StoreWP $ix $name $d $GetDispl]
    
    return $name
}

proc InDataWPRT {data pid} {
    # add WPs data from receiver to database
    # return list of names of WPs
    global CurrPSPID GetSet
 
    set wps ""
    foreach d [ConvWPData $data ] {
	#if { "$GetSet(WP)" == "" || [lsearch -exact $GetSet(WP) $ix] != -1 } {
	set name [AddMagWPT $d]
	if {$name == "" } {continue}
	lappend wps $name
	#}
    }
    return $wps
}

proc ConvWPData {data} {
    # convert WPs data got from receiver into list of lists suitable for
    #  use with SetItem
    global PositionFormat CREATIONDATE DATAFOR MAG_SYMTAB

    # MF change: no need for position type in proc CreatePos
    set r ""

    set fs {Name Posn Alt Commt Symbol}
    set ps {4 0 2 5 6}
    # MF change: using FormatPosition instead of CreatePos, needs position
    #  format in $all and $cnsts
    if { $CREATIONDATE } {
	set all [linsert $fs 0 Datum PFrmt Date]
	set cnsts [list "WGS 84" "" [Now]]
    } else {
	set all [linsert $fs 0 Datum PFrmt]
	set cnsts [list "WGS 84" ""]
    }
    foreach d $data {
	set vs $cnsts
	foreach f $fs p $ps {
	    set v [lindex $d $p]
	    switch $f {
		Posn {
		    # MF change: using FormatPosition instead of CreatePos
		    foreach "latd longd" $v { break }
		    foreach "v pfmt datum" \
			[FormatPosition $latd $longd "WGS 84" \
			     $PositionFormat "" DDD] {
			    break
		    }
		    set vs [lreplace $vs 0 1 $datum $pfmt]
		}
		Symbol {
		    #set v 1st_aid
		    set vx 71
		    binary scan $v cc v vx
		    # subtract the letter a
		    set v [lindex $MAG_SYMTAB [expr $v - 97 + $vx - 71 ]]
		}
		DispOpt {
		    set v [NameForCodeOf DISPOPT $v]
		}
		Alt {
		    scan $v "%f" 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

    #set hpid $CurrPSPID(RTHeader)

    # route summary data table of contents
    set fs "IdNumber Obs WPoints"

    while { "$data" != "" } {
	# get the next data point
	set wpd [lindex $data 0]

	# entry 1 means new route
	if {[lindex $wpd 1] == 1} {
	    # new route
	    set id [lindex $wpd 3] ; set ix [IndexNamed RT $id]
	    # store the route ID number in list of route summary data
	    set r [list $id] 
	    set wps ""
	    set wpcnt 0
	}

	# lines remaining
	set remlines [expr [lindex $wpd 0] - [lindex $wpd 1]]

	# process the waypoint names
	set linedat [lrange $wpd 4 7]
	while { "$linedat" != ""} {
	    incr wpcnt

	    if { "[lindex $linedat 0]" != "" } {
		lappend wps [lindex $linedat 0]
		#lappend wps  [IndexNamed WP [lindex $linedat 0]]
	    }
	    set linedat [lreplace $linedat 0 1]
	}

	if {! $remlines} {
	    # end of route
	    lappend r $wpcnt $wps
	    set d [FormData RT $fs $r]
	    StoreRT $ix $id $d $wps $GetDispl
	}
	
	set data [lreplace $data 0 0]
    }
    return    
}

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 ""

    foreach p $data {
	set vals $p

	set tpp [ConvTPData $vals]
	if { [lindex $tpp 0] == 1 && "$tps" != "" } {
	    lappend trs [list "" $tps]
	    set tps ""
	}
	lappend tps [lindex $tpp 1]
    }
    if { "$tps" != "" } {
	lappend trs [list "" $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 ConvTPData {data} {
    # convert TP data
    # return pair with flag indicating a new TR, and list obtained by  FormData

    set fs "" ; set vs ""

    #set p [ConvMagPos $data]
    # MF change: replacing call to CreatePos by call to FormatLatLong
    set p [FormatLatLong [lindex $data 0] [lindex $data 1] DMS]
    set dl [ConvMagDate [lindex $data 4] [lindex $data 7]]
    set alt [lindex $data 2]
    lappend fs latd longd latDMS longDMS alt date secs
    set tpdata [FormData TP $fs [concat $vs $p $alt $dl]]
    # is this new flag (set to 1) really needed ?
    return [list 0 $tpdata]
}

proc ConvMagPos {indat} {
		#Take input strings/values and return lat/long in degrees(+-)
                set la [lindex $indat 0 ]
		if { [lrange $indat 1 1] == "S"} {
		    set la [expr -1 * $la]
		}

                set lo [lindex $indat 2 ]

		if { [lrange $indat 3 3] == "W"} {
		    set lo [expr -1 * $lo]
		}

		return [list $la $lo]
	    }

proc ConvMagDate {gt gd} {
    # converts Magellan 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)

    #set secs [expr int($gt * 60.0)]
    global DateFormat

    set day 0
    scan [string range $gd 0 1] %d day
    set mon 0
    scan [string range $gd 2 3] %d mon
    set yr 0
    scan [string range $gd 4 5] %d yr
    incr yr 2000

    set hr 0
    scan [string range $gt 0 1] %d hr
    set mn 0
    scan [string range $gt 2 3] %d mn
    set sec 0
    scan [string range $gt 4 6] %d sec

    set secs [DateToSecs $yr $mon $day $hr $mn $sec]
    return [list [FormatDate $DateFormat $yr $mon $day $hr $mn $sec] $secs]
}

##### GPSMan interface

## not in use:
proc GPSChangeProtocol {prot} {
    # change current protocol
    #  $prot in {magellan}
    # 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 tdebug {msg} {
    global MasterClock
    Log "$msg  ([expr [clock clicks] - $MasterClock] clicks)"
    set MasterClock [clock clicks]

}

