#
# This file is part of:
#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2011 Miguel Filgueiras migf@portugalmail.pt
#
#    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 3 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.
#
#########################################################################
#
#  This file (lowrance.tcl) - Author - Brian Baulch
#    (baulchb@hotkey.net.au) is based on the file garmin.tcl (Author
#     Miguel Filgueiras)
#  Copyright (c) Brian Baulch 1999, 2000
# 
#  Last change: 26 Jan 2002
#  
# All the text above and including these lines MUST be reproduced in full
#  by any person modifying or copying this file.
##########################################################################

# Includes changes/contributions by Miguel Filgueiras marked
#   "MF change" or "MF contribution"

# Date last modified: 19 June 2007

set SERIALLOG ~/.gpsman-log
set NotLogging 1
set LogMutex 0

proc Log {m} {
    global NotLogging LogFile LogMutex

    if { $NotLogging } { return }
    if { $LogMutex } {
	after 50 "Log {$m}"
	return
    }
    set LogMutex 1
    puts $LogFile $m
    flush $LogFile
    set LogMutex 0
    return
}

# Constants needed by the Mercator conversion routines.

set RADtoDEG 57.29577951308232087684
set PI 3.14159265358979

# Correction constant (in days) to convert GPSMan time to Lowrance time.

set GPSMANDAYS 1461

# The LSI100 command preamble - actually 0x8155, but must be in decimal
# format to allow comparison under TCL.
 
set PREAMBLE 33109

# Lowrance Products & Software

set PRODID {dummy GlobalMap AirMap AccuMap {GlobalNav 310} {Eagle View} \
    {Eagle Explorer} {GlobalNav 200} {Expedition II} {GlobalNav 212} \
    {GlobalMap 12} {Accumap 12}}

set PROTV {{Version 1.0} {Version 2.0} {Version 3}}

# Lowrance Commands:

set CMD(GetMEM) 8
set CMD(GetWP) 771
set CMD(GetRT) 773
set CMD(GetTRp) 775
set CMD(GetTR) 786
set CMD(GetTRd) 787
set CMD(GetICno) 776
set CMD(GetICpos) 777
set CMD(GetInfo) 782
set CMD(PutMEM) 9
set CMD(PutWP) 772
set CMD(PutRT) 774
set CMD(PutTRd) 789
set CMD(PutTR) 788
set CMD(PutICno) 778
set CMD(PutIC) 779

set PDTYPE(Header) {word word word byte}
set PDTYPE(GetMEM) {long word byte}
set PDTYPE(GetWP) {byte word byte byte long long charray=13 long}
set PDTYPE(PutWP) {word byte byte long long charray=13 long}
set PDTYPE(GetRT) {byte word byte charray=13}
set PDTYPE(PutRT) {word byte charray=13 wordarray}
set PDTYPE(GetICno) {byte word}
set PDTYPE(GetICpos) {byte word long long byte}
set PDTYPE(PutICno) {word}
set PDTYPE(PutIC) {word long long byte}
set PDTYPE(GetICgrn) {byte word}
set PDTYPE(GetICgr) {byte byte byte byte long}
set PDTYPE(GPSInfo) {byte word word word word word word word word \
	word byte byte byte long}
set PDTYPE(GetTR) {byte byte long long word}
set PDTYPE(GetTRd) {byte byte word}
set PDTYPE(PutTR) {byte long long word} 
set PDTYPE(PutTRd) {byte word}

set TIMEOUT 1000

#############################################################

proc ErrorMess {mess} {

    global MessSent MESS

    if { ! $MessSent} {
	set MessSent 1
	GMMessage $MESS($mess)
	set ReadState done
	ResetCursor .
	DisableGPS
	GPSOff
    }
    return
}

proc ReadProc {what} {

    global ReadState

    set data [ReadInterface $what]
    ProcessRead $what $data
    set ReadState done
    return
}

proc TimeOutProc {} {

    global Id ReadState

    if { [string compare $ReadState done] } {
	after cancel $Id
	ErrorMess {receiver}
    }
    return
}
	
proc GPSInterface {what data} {

    # what, as in Get?? Put?? etc. as above under CMD(???)

    global Id NotLogging ReadState TIMEOUT SRLFILE \

    set ReadState ""
    if {! $NotLogging} {
	foreach b $data {
	    binary scan $b "c" x
	    lappend log $x
	}
	Log "SP> $log"
    }  
    foreach b $data {
	puts -nonewline $SRLFILE "$b"
    }
    flush $SRLFILE
    set Id [after 0 ReadProc $what]
    after $TIMEOUT TimeOutProc
    while { [ string compare $ReadState "done"] } { update }
    return
}

proc ReadInterface {what} {

    global NotLogging PREAMBLE SRLFILE MessSent PDTYPE CMD

    if { ! $MessSent } {
	set header ""
	for { set i 1 } { $i < 9 } { incr i } {
	    set ch ""
	    while { [string length $ch] == 0 } { 
		set ch [read $SRLFILE 1]
		update
	    }
	    append header $ch
	}
	set inheader [split $header ""]
	if {! $NotLogging} {
	    set log ""
	    foreach b $inheader {
		binary scan $b "c" x
		lappend log $x
	    }
	    Log "SP< $log"
	}
	set sumin [lindex $inheader "end"]
	set inheader [lreplace $inheader "end" "end"]
	set sumcalc [CheckSum $inheader]
	if {$sumcalc != $sumin} {
	    ErrorMess {checksum1}
	    return
	}
	set header [UnPackData $inheader $PDTYPE(Header)]
	if {[lindex $header 0] != $PREAMBLE} {
	    ErrorMess {serial1}
	    return
	}
	if {[lindex $header 1] != ($CMD($what) + 128)} {
	    ErrorMess {serial2}
	    return
	}
	set bytecount [lindex $header 2]
	set data ""
	if {$bytecount > 0} {
 	    set bytecount [incr bytecount 2]
	    for { set i 1 } { $i < $bytecount } { incr i } {
		set ch ""
		while { [string length $ch] == 0 } { 
		    set ch [read $SRLFILE 1]
		    update
		}
		append bdata $ch
	    }
	    set data [split $bdata ""]
	    if {! $NotLogging} {
		set log ""
		foreach b $data {
		    binary scan $b "c" x
		    lappend log $x
		}
		Log $log
	    }
	    set sumin [lindex $data "end"]
	    set data [lreplace $data "end" "end"]
	    set sumcalc [CheckSum $data]
	    if {$sumcalc != $sumin} {
		ErrorMess {checksum2}
		set data ""
		return
	    }
	}
	after cancel TimeOutProc
    } 
    return $data
}

proc ProcessRead {what data} {

    switch $what {
	    GetWP {
		WayPoint $data
	    }
	    GetRT {
		Routes $data
	    }
	    GetTR {
		TrailOrigin $data
	    }
	    GetTRd {
		TrailDelta $data
	    }
	    GetICno {
		IconCount $data
	    }
	    GetICpos {
		IconPosn $data
	    }
	    GetInfo {
		ProductInfo $data
	    }
	    PutTR -
	    PutTRd {
		PutTrail
	    }
	}
return
}

proc WayPoint {data} {

global PDTYPE WNames WName WNum WSymb WLatitude WLongitude WDate

    set data [UnPackData $data $PDTYPE(GetWP)]
    if [lindex $data 2] {
	set name [lindex $data 6]
	set WNum($name) [lindex $data 1]
	lappend WNames $name
	set WName($WNum($name)) $name
	set WSymb($name) [lindex $data 3]
	set merclat [lindex $data 4]
	set WLatitude($name) [Merc2Lat $merclat]
	set merclong [lindex $data 5]
	set WLongitude($name) [Merc2Long $merclong]
	set WDate($name) [lindex $data 7]
#	ListWayPoints $name
    }
    return
}

proc ListWayPoints {} {

global WNames WNum WSymb WLatitude WLongitude WDate UnusedWPInx

    set bulkdata ""
    foreach name $WNames {
	set data $WNum($name)
	lappend data $WSymb($name) $WLatitude($name) $WLongitude($name) \
		$name $WDate($name)                
	lappend bulkdata $data
	set inx [lsearch -exact $UnusedWPInx $WNum($name)] 
	set UnusedWPInx [lreplace $UnusedWPInx $inx $inx]
    }
    return $bulkdata
}

proc Routes {data} {

global PDTYPE RNum RPoints RNames

    set pts ""
    set packdata [lrange $data 0 16]
    set data [lreplace $data 0 16] 
    set rtdata [UnPackData $packdata $PDTYPE(GetRT)]
    set points [lindex $rtdata 2]
    if { $points } {
	if { [string compare [lindex $rtdata 3] ""] } {
	    set name [lindex $rtdata 3]
	} else {
	    set num [expr [lindex $rtdata 1] + 1]
	    set name ROUTE$num
	}
	lappend RNames $name
	set RNum($name) [expr [lindex $rtdata 1] + 1] 
	set i 0 ; set x 0 ; set y 1
 	while {$i < $points} {
	   binary scan [join [list [lindex $data $x] \
		[lindex $data $y]] ""] "s" p  
	   lappend pts $p
	   incr x 2 ; incr y 2 ; incr i
	}
	set RPoints($name) $pts
    }
    return
}

proc ListRoutes {} {

global PDTYPE RNames RNum RPoints

    set bulkdata ""
    foreach n $RNames {
	set name $n
	set data $RNum($name)
	lappend data $name $RPoints($name)
	lappend bulkdata $data
    }
    return $bulkdata
}

proc IconCount {data} {

global PDTYPE BulkData

    set BulkData ""
    set data [UnPackData $data $PDTYPE(GetICno)]
    set count [lindex $data 1]
    for {set i 0} {$i < $count} {incr i} {
	SendData GetICpos $i
    }
    return
}
 
proc IconPosn {data} {

global PDTYPE UnusedICInx BulkData

    set d [UnPackData $data $PDTYPE(GetICpos)]
    set data [lindex $d 1]
    set merclat [lindex $d 2]
    set inx [lsearch -exact $UnusedICInx $data] 
    set UnusedICInx [lreplace $UnusedICInx $inx $inx]
    set merclong [lindex $d 3]
    lappend data [Merc2Lat $merclat] [Merc2Long $merclong] [lindex $d 4]
    lappend BulkData $data
    return
}
    
proc ProductInfo {data} {

global PDTYPE PRODID PROTV MAX Prod Protv

    set data [UnPackData $data $PDTYPE(GPSInfo)]
    set prod [lindex $data 1]
    set Prod [lindex $PRODID $prod]
    set vn [lindex $data 2]
    set Protv [lindex $PROTV $vn]
#    set MAX(WP) [lindex $data 6]
#    set MAX(RT) [lindex $data 8]
    # MF change: using the user defaults
    global MAXWPOINTS MAXROUTES
    set MAX(WP) $MAXWPOINTS
    set MAX(RT) $MAXROUTES
    #--
    set MAX(IC) [lindex $data 7]
    set MAXWPINROUTE) [lindex $data 9]
    set MAX(TR) [lindex $data 10]
    set MAXICG [lindex $data 11]
    return
}

proc PutTrail {} {

global PntCount CurrentTrack

    while {$PntCount > 0} {
	SendData PutTRd $CurrentTrack
    }
    return
}

proc TrailOrigin {data} {

global NIXDAYS PDTYPE TRNumDeltas Time Trail Xcurrent Ycurrent \
		BulkData TrailData

    set TrailData ""
    set Time [clock seconds]
    set data [UnPackData $data $PDTYPE(GetTR)]
    set TRNumDeltas [lindex $data 4]
    if {$TRNumDeltas > 0} then {
	set trail [lindex $data 1]
	set Trail [expr 1 + $trail]
#     set the current global trail points to the received XY origins.
	set Xcurrent [lindex $data 3]
	set Ycurrent [lindex $data 2]
	lappend TrailData [list "Trail$Trail" [TimeStamp $Time] $Time [Merc2Lat \
		$Ycurrent] [Merc2Long $Xcurrent] "1"]
	while {$TRNumDeltas > 0} {
	    SendData GetTRd $trail
	}
	lappend BulkData $TrailData
    }    
    return
}

proc TrailDelta {data} {

global Time PDTYPE TrailData Xcurrent Ycurrent Trail INTERVAL

    set ddata [UnPackData $data $PDTYPE(GetTRd)]
    set pnts [lindex $ddata 2]
    set data [lreplace $data 0 3]
    for {set p 0} {$p < $pnts} {incr p} {
	binary scan [join [list [lindex $data 0] \
		[lindex $data 1]] ""] "s" deltaY  
	set Ycurrent [expr ($Ycurrent + $deltaY)]
	binary scan [join [list [lindex $data 2] \
		[lindex $data 3]] ""] "s" deltaX
	set Xcurrent [expr ($Xcurrent + $deltaX)]
	set Time [expr ($Time + $INTERVAL)]
	lappend TrailData [list "Trail$Trail" [TimeStamp $Time] $Time \
		[Merc2Lat $Ycurrent] [Merc2Long $Xcurrent]]
# delete point just calc'ed & printed, ready for the next iteration.
	set data [lreplace $data 0 3]
    }
    return
}

proc Lat2Merc {data} {

global RADtoDEG PI

    set lat [expr (($data / $RADtoDEG) + $PI/2)/2]
    set tanlat [expr tan($lat)]
    set loglat [expr log($tanlat)]
    return [expr round ($loglat * 6356752.3142)]
}

proc Long2Merc {data} {

global RADtoDEG

    return [expr round (6356752.3142 * $data / $RADtoDEG)]
}

proc Merc2Lat {data} {

global PI RADtoDEG

    set temp [expr ($data / 6356752.3142)]
    set temp [expr exp($temp)]
    set temp [expr (2 * atan($temp)) - ($PI / 2)]
    set x [expr ($temp * $RADtoDEG)]
    return $x
}

proc Merc2Long {data} {

global RADtoDEG

    set temp [expr ($data / 6356752.3142)]
    return [expr ($temp * $RADtoDEG)]
}

proc UnPackData {data types} {
    # convert from list of bytes (as TCL characters) to list of elements
    #  conforming to the types in the list $types

global PDTYPE

    set vals ""
    foreach t $types {
	switch -glob $t {
	    byte {
		set n 1
		binary scan [lindex $data 0] "c" x
		set x [expr ($x+0x100)%0x100]
	    }
	    int {
		set n 2
		binary scan [join [list [lindex $data 0] [lindex $data 1]] \
			          ""] "s" x
	    }
	    word {
		set n 2
		binary scan [join [list [lindex $data 0] [lindex $data 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 [list [lindex $data 0] [lindex $data 1] \
                                   [lindex $data 2] [lindex $data 3]] ""] "i" 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 " "]
		regsub -all {:} $x "" x
	    }
	    ignored {
		return $vals
	    }
	    default {
		Log "unimplemented data type when unpacking: $t"
		set n 1 ; set x 0
	    }
	}
	# MF change: the following comments were taken from the switch body
	#  above
#
#	    double {
#		set n 8
#		binary scan [join [list [lindex $data 0] [lindex $data 1] \
#                                   [lindex $data 2] [lindex $data 3] \
#                                   [lindex $data 4] [lindex $data 5] \
#                                   [lindex $data 6] [lindex $data 7]] ""] "d" x
#	    }
	#--
	lappend vals $x
	set data [lrange $data $n end]
    }
    return $vals
}

proc SendData {command indx} {

    # transfer data to receiver
    # first group sent will be the Command Header. This will
    #  be followed by a data group for PUTS (& for some GETS) or will 
    #  trigger a reply header & data group for GETS.
    #  Second arg is the data index or zero, as appropriate. 

global WPName WPNum UnusedWPInx UnusedICInx RTWPoints RTList TRNumDeltas \
	PntCount NDeltas CMD CurrentTrack

    set data ""
    switch $command {
	GetWP -
	GetICpos - 
	GetRT -
	GetICgr {
	    set data [PacketHeader $CMD($command) 2]
	    lappend data [CheckSum $data]
	    set suffix [binary format "s" $indx]
	    set d [split $suffix ""]
	    lappend data [lindex $d 0] [lindex $d 1] 
	    lappend data [CheckSum $d]
	}    
	GetTR {
	    set data [PacketHeader $CMD($command) 1]
	    lappend data [CheckSum $data]
	    set suffix [binary format "c" $indx]
	    lappend data $suffix
	    lappend data [CheckSum $suffix]
	}
	GetTRd {
	    set deltaslice 20
	    set sdata [PacketHeader $CMD($command) 3]
	    lappend sdata [CheckSum $sdata]
	    set suffix [binary format "c" $indx]
	    if {$TRNumDeltas < $deltaslice} {
		set deltas [binary format "s" $TRNumDeltas]
		set TRNumDeltas 0
	    } else {
		set deltas [binary format "s" $deltaslice]
		set TRNumDeltas [expr ($TRNumDeltas - $deltaslice)]
	    }
	    set d [split $deltas ""]
	    lappend suffix [lindex $d 0] [lindex $d 1]
	    lappend suffix [CheckSum $suffix]
	    set data [concat $sdata $suffix]
	}
	PutWP {
	    if { $WPNum($indx) < 0} {
		set WPNum($indx) [lindex $UnusedWPInx 0]
		set UnusedWPInx [lreplace $UnusedWPInx 0 0]
	    }
	    set sdata [PacketHeader $CMD($command) 29]
	    lappend sdata [CheckSum $sdata]
	    set tvals [PrepData $command $indx]
	    set packdata [PackData $tvals]
	    lappend packdata [CheckSum $packdata]
	    set data [concat $sdata $packdata]
	}
	PutRT {
	    set count [array size WPName]
	    set RTList ""
	    foreach pnt $RTWPoints($indx) {
		set inx [IndexNamed WP $pnt]
		if { ! [string compare WPNum($inx) "" ] } {
		    if { [llength $unusedWPInx] == 0 } {
			GMMessage $MESS(noWPinx)
			ResetCursor .
		    } else {
			set WPNum($inx) [lindex $UnusedWPInx 0]
			set UnusedWPInx [lreplace $UnusedWPInx 0 0]
		    }
		}
		lappend RTList $WPNum($inx)
	    }
	    set PntCount [llength $RTList]
	    set numbytes [expr ($PntCount * 2) + 16]
	    set data [PacketHeader $CMD($command) $numbytes]
	    lappend data [CheckSum $data]
	    set tvals [PrepData $command $indx]
	    set packdata [PackData $tvals]
	    set chk [CheckSum $packdata]
	    set packdata [concat $packdata [list $chk]]
	    set data [concat $data $packdata]
	}
	PutIC {
	    foreach i { $indx } {
		if { ! [string compare WPNum($i) "" ] } {
		    if { [llength $unusedICInx] == 0 } {
			GMMessage $MESS(noICinx)
			ResetCursor .
		    } else {
			set WPNum($i) [lindex $UnusedICInx 0]
			set UnusedICInx [lreplace $UnusedICInx 0 0]
		    }
		}
	    }
	    set count [llength $indx]
	    set data [PacketHeader $CMD(PutICno) 2]
	    lappend data [CheckSum $data]
	    set suffix [binary format "s" $count]
	    set d [split $suffix ""]
	    lappend data [lindex $d 0] [lindex $d 1] [CheckSum $d]
	    GPSInterface PutICno $data
	    for {set i 0} {$i < $count} {incr i} {
		set inx [lindex $indx $i]
 		set data [PacketHeader $CMD(PutIC) 11]
		lappend data [CheckSum $data]
		set tvals [PrepData $command $inx]
		set packdata [PackData $tvals]
		lappend packdata [CheckSum $packdata]
		set data [concat $data $packdata]
		GPSInterface $command $data
	    }
	    return
	}
	PutTR {
	    set CurrentTrack $indx
	    set sdata [PacketHeader $CMD($command) 11]
	    lappend sdata [CheckSum $sdata]
	    set tvals [PrepData $command $indx]
	    set packdata [PackData $tvals]
	    lappend packdata [CheckSum $packdata]
	    set data [concat $sdata $packdata]
	}
	PutTRd {
	    if {$PntCount < 40} {
		set NDeltas $PntCount
		set PntCount 0
	    } else {
		set NDeltas 40
		set PntCount [expr $PntCount - 40]
	    }
	    set n [expr $NDeltas * 4 + 3]
	    set data [PacketHeader $CMD($command) $n]
	    lappend data [CheckSum $data]
	    set tvals [PrepData $command $CurrentTrack]
	    set packdata [PackData $tvals]
	    lappend packdata [CheckSum $packdata]
	    set data [concat $data $packdata]
	}
	default {
	    set data [PacketHeader $CMD($command) 0]
	    lappend data [CheckSum $data]
	}
    }
    GPSInterface $command $data
    return
}

proc PacketHeader {command count} {

# Formats the correct Packet Command header.
global PREAMBLE PDTYPE

    set headdata ""
    lappend headdata $PREAMBLE $command $count 0
    return [PackData [list $PDTYPE(Header) $headdata]]
}

proc CheckSum {data} {

    # Calculates the 2's complement checksum and returns checksum.

    set datasum 0 
    foreach ch $data {
	binary scan $ch "c" v
	incr datasum $v
    }
    set checksum [expr 256 - ($datasum & 255)]
    set chk [binary format "c" $checksum]
    return $chk
}

proc PrepData {cmd inx} {

    # prepare WPs, RTs, or TRs data to be transferred

global GPSMANDAYS PDTYPE WPName WPSymbol WPNum WPCommt WPPosn WPDatum WPDate \
	ICIdNumber ICSymbol ICPfrmt ICPosn ICDatum RTList RTIdNumber \
	RTCommt TRName TRDatum TRTPoints NDeltas TDeltas TRNumDeltas \
	PntCount Choice SYMBOLCODE YEAR0 MONTHNAMES DateFormat CurrentTrack

    switch $cmd {
	PutWP {
	    set p $WPPosn($inx)
	    if { [string compare $WPDatum($inx) "WGS 84"] } {
		set p [ConvertDatum [lindex $p 0] [lindex $p 1] \
			            $WPDatum($inx) "WGS 84" DDD]
	    }
	    set latd [lindex $p 0]
	    set longd [lindex $p 1] 
	    set sym $SYMBOLCODE($WPSymbol($inx)) 
	    if { ! [string compare $WPDate($inx) ""] } {
		set year0 $YEAR0 ; set YEAR0 1992
		set t { %Y %m %d %H %M %S }
		set s [clock format [clock seconds] -format $t]
		scan $s "%d %d %d %d %d %d" y m d h mn s
		set wpdate [DateToSecs $y $m $d $h $mn $s]
		set YEAR0 $year0
	    } else { 
		set date [split $WPDate($inx) ": -/."]
		if { ! [string compare $DateFormat "DDMMMYYYY"] } {
		    scan $date {%d %[a-zA-Z] %d %d %d %d} d mnth y h mn s
		    set m [lsearch -exact $MONTHNAMES $mnth]
		    set month [expr $m + 1]
		}
		if { ! [string compare $DateFormat "YYYY-MM-DD"] } {
		    scan $date {%d-%d%-d %d %d %d} month d y h mn s
		}
		if { ! [string compare $DateFormat "MMDDYYYY"] } {
		    scan $date {%d %d %d %d %d %d} month d y h mn s
		}
		if { ! [string compare $DateFormat "YYYYMMDD"] } {
		    scan $date {%d %d %d %d %d %d} y month d h mn s
		}
		set gpsdate [DateToSecs $y $month $d $h $mn $s]
		set wpdate [expr $gpsdate - ($GPSMANDAYS*24*3600)]
	    }
	    set wpnum [expr $WPNum($inx) - 1]
	    return [list $PDTYPE($cmd) [PrepWPData $WPNum($inx) $WPName($inx) \
		$sym $WPCommt($inx) $latd $longd $wpdate ]]
	}
	PutRT {
	    set pointlist ""
	    for {set i 0} {$i < $PntCount} {incr i} {
		lappend pointlist [lindex $RTList $i]
	    }
	    return [list $PDTYPE(PutRT) [PrepRTData $RTIdNumber($inx) \
		$PntCount $RTCommt($inx) $pointlist]]

	}
	PutIC { 
	    set p $WPPosn($inx)
	    if { [string compare $WPDatum($inx) "WGS 84"] } {
		set p [ConvertDatum [lindex $p 0] [lindex $p 1] \
			            $WPDatum($inx) "WGS 84" DDD]
	    }
	    set num [string range $WPName($inx) 4 end]
	    set sym $SYMBOLCODE($WPSymbol($inx)) 
	    set latd [lindex $p 0]
	    set longd [lindex $p 1] 
	    return [list $PDTYPE($cmd) [PrepICData $num \
		$sym $latd $longd]]
	}
	PutTR {
	    set TDeltas ""
	    set points ""
	    foreach p $TRTPoints($inx) {
		lappend points [list [lindex $p 0] [lindex $p 1]]
	    }
	    set pnts ""
	    if { [string compare $TRDatum($inx) "WGS 84"] } {
		foreach p $points {
		lappend pnts [ConvertDatum [lindex $p 0] \
			[lindex $p 1] $TRDatum($inx) "WGS 84" DDD]
		}
		set points $pnts
	    }
	    foreach p $points {
		set ymerc [Lat2Merc [lindex $p 0]]
		set xmerc [Long2Merc [lindex $p 1]]
		lappend tpoints [list $ymerc $xmerc] 
	    }
	    set PntCount [expr [llength $tpoints] -1]
	    set p [lindex $tpoints 0]
	    set y [lindex $p 0]	; set x [lindex $p 1]
	    set yo $y ; set xo $x
	    for {set i 1} {$i <= $PntCount} {incr i} {
		set p [lindex $tpoints $i]
		set y1 [lindex $p 0] ; set x1 [lindex $p 1]
		set dy [expr $y1 - $y] ; set dx [expr $x1 - $x]
		lappend TDeltas [list $dy $dx]
		set y $y1 ; set x $x1
	    }
	    return [list $PDTYPE($cmd) [list $inx $yo $xo $PntCount]]
	}
	PutTRd {
	    set deltas "" ; set type $PDTYPE($cmd)
	    for {set i 0} {$i < $NDeltas} {incr i} {
		set d [lindex $TDeltas 0]
		lappend deltas [lindex $d 0] [lindex $d 1]
		lappend type "word" "word"
		set TDeltas [lreplace $TDeltas 0 0] 
	    }
	    return [list $type [concat [list $CurrentTrack $NDeltas] $deltas ]]
	}

    }
}

proc PrepWPData {number name sym cmmt lat long date} {

    return [list $number 1 $sym [Lat2Merc $lat] [Long2Merc $long] \
		$name $date]
}

proc PrepICData {number sym lat long } {

    return [list $number [Lat2Merc $lat] [Long2Merc $long] $sym]
}

proc PrepRTData {id wpcount name points} {


    set id [expr ($id - 1)]
    return [list $id $wpcount $name $points]
}

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 PntCount

    set data ""
    foreach t $types v $vals {
	switch -glob $t {
	    byte {
		append data [binary format "c" $v]
	    }
	    word {
		append data [binary format "s" $v]
	    }
	    longword -
	    long {
		append data [binary format "i" $v]
	    }
	    wordarray {
		append data [binary format "s$PntCount" $v]
	    }
	    charray=* {
		regsub charray= $t "" n
		append data [binary format "A$n" $v]
	    }
	    default {
		Log "unimplemented data type when packing: $t"
	    }
	}
	# MF change: the following comments were taken from the switch body
	#  above
#	    double {
#		append data [binary format "d" $v]
#	    }
	#--
    }
    return $data
}


##### upper level

proc EndOutProt {} {
    # deal with end of output protocol

     CloseInProgrWindow
    return
}

proc EndInProt {what data} {
    # deal with end of input protocol

    CloseInProgrWindow
    switch $what {
	IC {InData$what $data}
        TR {InData$what $data}
    } 
    return
}

proc InDataWP {data} {
    # add WPs data from receiver to database

global Index Number WPRoute GetDispl GetSet WPDispl WPSymbol

    foreach d $data {
	set d [ConvWPData $d]
	set name [lindex $d 0]
	set ix [IndexNamed WP $name]
#	set name [lindex $d 0] ; set name [split $name " "]
#	set name [join $name ""] ; set ix [IndexNamed WP $name]
	set d [lreplace $d 0 0 $name]
	if {! [string compare $GetSet(WP) ""] || \
		[lsearch $GetSet(WP) $ix] != -1 } {
	    StoreWP $ix $name $d $GetDispl
	}
    }
return
}

proc ConvWPData {d} {
    # convert WPs data got from receiver into list of lists suitable for
    #  use with SetItem

global PositionFormat DEFAULTDISPOPT DATAFOR

    set t [PosType $PositionFormat]
    set wpnum [lindex $d 0]
    set wpname [lindex $d 4]
    set date [ConvLowrDate [lindex $d 5]]
    set s [lindex $d 1]
    set sym [NameForCodeOf SYMBOL $s]
    set p [CreatePos [lindex $d 2] [lindex $d 3] \
	$PositionFormat $t "WGS 84"]
    set all [list Name Num Posn Date Symbol]
    set r [list $wpname $wpnum $p $date $sym]
    set r [FormData WP $all $r]
    return $r
}

proc InDataIC {data} {
    # add Icon data from receiver to database
    # return list of set Icons

global Index Number GetDispl

    foreach d $data {
	set d [ConvICData $d]
	set id [lindex $d 1]
	set name "ICON$id"
	set d [lreplace $d 0 0 $name]
	set ix [IndexNamed WP $name]
	StoreWP $ix $name $d $GetDispl
    }
return
}

proc ConvICData {d} {
    # convert Icon data got from receiver into list of lists suitable for
    #  use with SetItem

global PositionFormat DEFAULTDISPOPT 

    set t [PosType $PositionFormat]
    set id [lindex $d 0]
    set name [lindex $d 4]
    set p [CreatePos [lindex $d 1] [lindex $d 2] $PositionFormat $t "WGS 84"]
    set s [lindex $d 3]
    set sym [NameForCodeOf SYMBOL $s]
    set all [list Name Num Posn Symbol]
    set r [list $name $id $p $sym]
    set r [FormData WP $all $r]
    return $r
}

proc InDataRT {data} {
    # add RT data from receiver to database

    global Index Number WName RTWPoints RTDispl GetDispl GetSet
    foreach d $data {
	set wps ""
	set id [lindex $d 0] 
	set name [lindex $d 1] ; set ix [IndexNamed RT $id]
 	foreach pnt [lindex $d 2] {
	    lappend wps $WName($pnt)
	}
	if { [string compare $wps ""] } {
	    set all [list IdNumber Commt WPoints]
	    set r [list $id $name $wps]
	    set r [FormData RT $all $r]
	    StoreRT $ix $name $r $wps $GetDispl
	}
        SetWPRoute $id $wps
    }
    return    
}

proc InDataTR {data} {
    # add TRs data from receiver to database

global Index Number GetDispl

    foreach p $data {
	set tps ""
	set f [lindex $p 0]
	set id [lindex  $f 0]
	set ix [IndexNamed TR $id]
	if {$ix == -1} then {
	    foreach d $p {
		set t [CreatePos [lindex $d 3] [lindex $d 4] \
			DMS latlong "WGS 84"]
		lappend t [lindex $d 1] [lindex $d 2]
		lappend tps $t
	    }
	    set trdata [list [lindex $d 0] $tps]
	    set all [list Name TPoints]
	    set r [FormData TR $all $trdata]
	    StoreTR $ix $id $r $GetDispl
	}
    }
    return
}


##### GPSMan interface

proc GPSChangeProtocol {prot} {
    # change current protocol
    #  $prot in {lowrance, nmeal}
    # must change GPSProtocolExt if successful
    global GPSProtocol GPSProtocolExt GPSState TXT 

    if { "$GPSProtocol" == "$prot" } { return }
    if { "$GPSState" == "online" } {
	set Eof 1
	close $SRLFILE
	DisableGPS
    }
    set GPSProtocol $prot ; set GPSProtocolExt $TXT($prot)
    return
}

proc ConvLowrDate {ld} {
    # converts Lowrance date (seconds since 1992.01.01 00:00:00) into
    # seconds from $YEAR0
    #  with date in current format and seconds since beginning of $YEAR0
    #  (assumed a leap year < 1990)

global YEAR0

    set dd 0
    for { set yy $YEAR0 } { $yy < 1990 } { incr yy 4 } { incr dd }
    set secs [expr ((1992-$YEAR0)*365+$dd)*24*3600+$ld]
    return [DateFromSecs $secs]
}

proc GPSConnection {data} {

    EnableGPS
    return
}

proc GPSBye {} {

    return
}

proc InitGPS {} {

    global MAX MESS

    StartGPS
    if { [FailsInProgrWindow $MESS(fillbuffWP)] } { return }
    SendData GetInfo 0
    BulkRead $MAX(WP) GetWP
    EndInProt WP 0
    if { [FailsInProgrWindow $MESS(fillbuffRT)] } { return }
    BulkRead $MAX(RT) GetRT
    EndInProt RT 0
    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 GetGPS {wh} {
    global GetSet

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

proc DoGetGPS {wh} {
    global MESS MAX BulkData Choice 

    if { [FailsInProgrWindow $MESS(get$wh)] } { return }
    switch $wh {
	WP {
	    set data [ListWayPoints]
	    InDataWP $data
	    CloseInProgrWindow
	}
	RT {
	    set data [ListRoutes]
	    InDataRT $data
	    CloseInProgrWindow
	}
	TR {
	    BulkRead $MAX(TR) Get$wh
	    EndInProt TR $BulkData
	}
	IC {
	    SendData GetICno 0
	    EndInProt IC $BulkData
	}
	Info {
	    SendData Get$wh 0
	} 
    }
    return
}

proc PutGPS {wh inx} {

global MESS  

    if { [FailsInProgrWindow $MESS(put$wh)] } { return }
    switch $wh {
	WP {
	    set indx [WPSort PutWP $inx]
	    if { [llength $indx] > 0 } {
		SendData PutIC $indx
	    }
	}
	default {
	    foreach p $inx {
		SendData Put$wh $p
	    }
	}
    }
    EndOutProt
    return
}

proc WPSort {what inx} {

# Sorts WP's from Icons and sends them to Receiver appropriately
    global WPName

    set data ""
    foreach p $inx {
	if { [string match "ICON*" $WPName($p)] } {
	    lappend data $p
	} else {
	    SendData PutWP $p
	}	
    }    
    return $data 
}
proc BulkRead {count wh} {

global BulkData GetSet WNames RNames 

    switch $wh {
	GetWP { set WNames "" }
	GetRT { set RNames "" }
    }
    set BulkData ""
    for {set i 0} {$i < $count} {incr i} {
	SendData $wh $i
    }
}

proc StartGPS {} {
   
global NotLogging LogFile MessSent SERIALLOG SRLFILE SERIALPORT

    if {! $NotLogging } {
	set LogFile [open $SERIALLOG w]
    }
    set MessSent 0
    set SRLFILE [open $SERIALPORT r+]
    fconfigure $SRLFILE -blocking 0 -mode 19200,n,8,1 -translation binary
#    fconfigure $SRLFILE -blocking 0 -mode 9600,n,8,1 -translation binary
    LowranceStdSymbols
    return
}

proc GPSOff {} {

    global SRLFILE

#    close $SRLFILE
    CloseInProgrWindow
    return
}

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

    if { [winfo exists .inprogr] } { Raise .inprogr ; bell ; return 1 }
    toplevel .inprogr
    wm protocol .inprogr WM_DELETE_WINDOW { bell }
    wm title .inprogr "GPS Manager: $TXT(commrec)"
    wm transient .inprogr
    wm geometry .inprogr +$EPOSX+$EPOSY

    frame .inprogr.fr -relief flat -borderwidth 5 -bg $COLOUR(messbg)
    label .inprogr.fr.title -text "..." -relief sunken
    label .inprogr.fr.text -text "$mess"
#    button .inprogr.fr.stop -text "STOP" -default active -command "NMEAOff"
    set fw .inprogr.fr.frc
    frame $fw -relief flat -borderwidth 0
    label $fw.tit -text "Working - please wait"
    button .inprogr.fr.ok -text "Stop" -state normal \
	-command NMEAOff
#    button .inprogr.fr.ok -text $TEXT(abort) -command NMEAOff
    pack $fw.tit -side left -padx 0
    pack .inprogr.fr -side top
    pack .inprogr.fr.title .inprogr.fr.text .inprogr.fr.frc \
	.inprogr.fr.ok -side top -pady 5
#    pack .inprogr.fr.stop -side top -pady 5
    update idletasks
    grab .inprogr.fr.ok
    RaiseWindow .inprogr
    return 0
}
