[hamradio-commits] [direwolf] 01/04: Imported Upstream version 1.1

Iain Learmonth irl-guest at moszumanska.debian.org
Mon Feb 2 00:46:25 UTC 2015


This is an automated email from the git hooks/post-receive script.

irl-guest pushed a commit to branch master
in repository direwolf.

commit 5178f71341511829a77329ea84d8ed758e8bc144
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Sun Feb 1 20:50:50 2015 +0000

    Imported Upstream version 1.1
---
 APRStt-Implementation-Notes.pdf |  Bin 1034778 -> 1035197 bytes
 CHANGES.txt                     |   85 +-
 Makefile.linux                  |  134 ++-
 Makefile.win                    |   64 +-
 Raspberry-Pi-APRS-Tracker.pdf   |  Bin 0 -> 716558 bytes
 Raspberry-Pi-APRS.pdf           |  Bin 835647 -> 1278049 bytes
 User-Guide.pdf                  |  Bin 2728705 -> 3108791 bytes
 aclients.c                      |    5 +-
 aprs_tt.c                       |   17 +-
 aprs_tt.h                       |    9 +-
 atest.c                         |   64 +-
 audio.c                         |   82 +-
 audio.h                         |    8 +-
 audio_win.c                     |    2 +-
 ax25_pad.c                      |  664 +++++++++-----
 ax25_pad.h                      |   49 +-
 beacon.c                        |  173 +++-
 beacon.h                        |    2 +
 config.c                        |  315 ++++++-
 config.h                        |   28 +-
 decode_aprs.c                   | 1833 ++++++++++++++++++++++++---------------
 decode_aprs.h                   |  103 ++-
 demod_afsk.c                    |    7 +-
 digipeater.c                    |   42 +-
 digipeater.h                    |    5 +
 direwolf.c                      |  148 +++-
 direwolf.conf                   |  136 +--
 direwolf.h                      |   22 +-
 dsp.c                           |   22 +-
 dwgps.c                         |  100 ++-
 encode_aprs.c                   |   87 +-
 encode_aprs.h                   |    2 +-
 fsk_demod_state.h               |   44 +
 grm_sym.h                       |  501 +++++++++++
 hdlc_rec.c                      |    1 +
 hdlc_rec2.c                     |  592 +++++++++++--
 hdlc_rec2.h                     |   64 +-
 kiss.c                          |  207 +++--
 kiss_frame.c                    |  400 +++++++--
 kiss_frame.h                    |   14 +-
 kissnet.c                       |   49 +-
 latlong.c                       |  242 +++++-
 latlong.h                       |    7 +
 log.c                           |  362 ++++++++
 log.h                           |   16 +
 log2gpx.c                       |  535 ++++++++++++
 mgn_icon.h                      |  267 ++++++
 misc/README-dire-wolf.txt       |    1 +
 multi_modem.c                   |  136 ++-
 nmea.c                          | 1037 ++++++++++++++++++++++
 nmea.h                          |   19 +
 ptt.c                           |  185 +++-
 rdq.c                           |   18 +-
 regex/README-dire-wolf.txt      |    1 +
 rpack.h                         |   94 ++
 rrbb.c                          |   72 +-
 rrbb.h                          |   11 +-
 server.c                        |  432 +++++----
 symbols-new.txt                 |   68 +-
 symbols.c                       |   50 +-
 symbolsX.txt                    |  144 +--
 telemetry.c                     | 1038 ++++++++++++++++++++++
 telemetry.h                     |   15 +
 textcolor.c                     |   28 +-
 tocalls.txt                     |   11 +-
 utm/README.txt                  |    6 +-
 version.h                       |    4 +-
 xmit.c                          |   55 +-
 xmit.h                          |    2 +-
 69 files changed, 9002 insertions(+), 1934 deletions(-)

diff --git a/APRStt-Implementation-Notes.pdf b/APRStt-Implementation-Notes.pdf
index 7ac41bc..31e5028 100755
Binary files a/APRStt-Implementation-Notes.pdf and b/APRStt-Implementation-Notes.pdf differ
diff --git a/CHANGES.txt b/CHANGES.txt
index e76dbb6..48a1b67 100755
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,78 @@ Revision history
 
 
 -----------
+Version 1.1  -- December 2014
+-----------
+
+* Changes since beta test version.
+
+It is still highly recommended, but no longer mandatory, that
+beaconing be enabled for digipeating to work.
+
+
+* Known problems.
+
+Sometimes kissattach fails to connect with "direwolf -p".
+The User Guide and Raspberry Pi APRS document have a couple work-arounds.
+
+
+
+-----------
+Version 1.1 -- Beta Test 1 -- November 2014
+-----------
+
+* New Features:
+
+Logging of received packets and utility to convert log file
+into GPX format.
+
+AGW network port formerly allowed only one connection at a 
+time.  It can now accept 3 client applications at the same time.  
+(Same has not yet been done for network KISS port.)
+
+Frequency / offset / tone standard formats are now recognized.
+Non-standard attempts, in the comment, are often detected and
+a message suggests the correct format.
+
+Telemetry is now recognized.  Messages are printed for
+usage that does not adhere to the published standard.
+
+Tracker function transmits location from GPS position.
+New configuration file options: TBEACON and SMARTBEACONING.
+(For Linux only.   Warning - has not been well tested.)
+
+Experimental packet regeneration feature for HF use.
+Will be documented later if proves to be useful...
+
+Several enhancements for trying to fix incorrect CRC.
+- Additional types of attempts to fix a bad CRC.
+- Optimized code to reduce execution time.
+- Improved detection of duplicate packets from different fixup attempts.
+- Set limit on number of packets in fix up later queue.
+
+Beacon positions can be specified in either latitude / longitude
+or UTM coordinates.
+
+
+* Bugs fixed:
+
+For Windows version, maximum serial port was COM9.
+It is now possible to use COM10 and higher.
+
+Fixed issue with KISS protocol decoder state that showed up
+only with "binary" data in packets (e.g.  RMS Express).
+
+An extra 00 byte was being appended to packets from AGW
+network protocol 'K' messages.
+
+Invalid data from an AGW client application could cause an
+application crash.
+
+OSS (audio interface for non-Linux versions of Unix) should 
+be better now.
+
+
+-----------
 Version 1.0a	May 2014
 -----------
 
@@ -36,6 +108,13 @@ Improved support for UTF-8 character set.
 
 Improved troubleshooting display for APRStt macros.
 
+In earlier versions, the DTMF decoder was always active because it 
+took a negligible amount of CPU time.  Unfortunately this sometimes 
+resulted in too many false positives from some other types of digital 
+transmissions heard on HF. Starting in version 1.0, the DTMF decoder 
+is enabled only when the APRStt gateway is configured.
+
+
 
 
 -----------
@@ -107,7 +186,7 @@ APRStt-Implementation-Notes.pdf
 
 
 -----------
-Version 0.6
+Version 0.6	February 2013
 -----------
 
 
@@ -152,7 +231,7 @@ run this in another window:
 
 
 -----------
-Version 0.5
+Version 0.5	March 2012
 -----------
 
 
@@ -160,7 +239,7 @@ More error checking and messages for invalid APRS data.
 
 
 -----------
-Version 0.4
+Version 0.4	September 2011
 -----------
 
 First general availability.
diff --git a/Makefile.linux b/Makefile.linux
index 5a2bfec..e5a92e1 100755
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -2,7 +2,7 @@
 # Makefile for Linux version of Dire Wolf.
 #
 
-all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients
+all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx
 
 CC = gcc
 
@@ -81,18 +81,30 @@ CC = gcc
 arch := $(shell echo | gcc -E -dM - | grep __i386__)
 
 ifneq ($(arch),)
-CFLAGS := -DUSE_ALSA -O3 -march=pentium3 -pthread
+# You might see improvement with -march fine tuned to your hardware.
+# Probably should keep pentium3 if you will be redistributing binaries
+# to other people.
+CFLAGS := -O3 -march=pentium3 -pthread -Iutm
 else
-CFLAGS := -DUSE_ALSA -O3 -pthread
+CFLAGS := -O3 -pthread -Iutm
 endif
 
 
-# Uncomment following lines to enable GPS interface.
-# DO NOT USE THIS.  Still needs more work.
+
+# If you want to use OSS (for FreeBSD, OpenBSD) instead of
+# ALSA (for Linux), comment out the two lines below.
+
+CFLAGS += -DUSE_ALSA
+LDLIBS += -lasound
+
+
+# Uncomment following lines to enable GPS interface & tracker function.
+
 #CFLAGS += -DENABLE_GPS
 #LDLIBS += -lgps
 
 
+
 # Name of current directory.
 # Used to generate zip file name for distribution.
 
@@ -107,9 +119,9 @@ direwolf : direwolf.o config.o  demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec
 		decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
 		gen_tone.o audio.o digipeater.o dedupe.o tq.o xmit.o \
 		ptt.o beacon.o dwgps.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \
-		dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o \
+		dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o log.o telemetry.o \
 		utm.a
-	$(CC) $(CFLAGS) -o $@ $^ -lpthread -lrt -lasound $(LDLIBS) -lm
+	$(CC) $(CFLAGS) -o $@ $^ -lpthread -lrt $(LDLIBS) -lm
 
 
 # Optimization for slow processors.
@@ -132,40 +144,51 @@ LatLong-UTMconversion.o : utm/LatLong-UTMconversion.c
 	$(CC) $(CFLAGS) -c -o $@ $^
 
 
-# Optional install step. 
+# Optional installation into /usr/local/...
+# Needs to be run as root or with sudo.
+
 # TODO: Review file locations.
-# TODO: Handle Linux variations correctly.
-# The Raspberry Pi has ~/Desktop but Ubuntu does not.
-# For now, just put reference to it at the end so only last step fails.
 
 install : direwolf decode_aprs tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
-	sudo install direwolf /usr/local/bin
-	sudo install decode_aprs /usr/local/bin
-	sudo install text2tt /usr/local/bin
-	sudo install tt2text /usr/local/bin
-	sudo install ll2utm /usr/local/bin
-	sudo install utm2ll /usr/local/bin
-	sudo install aclients /usr/local/bin
-	sudo install -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt
-	sudo install -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt
-	sudo install -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt
-	sudo install -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png
-	sudo install -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop
-	cp direwolf.conf ~
+	install direwolf /usr/local/bin
+	install decode_aprs /usr/local/bin
+	install text2tt /usr/local/bin
+	install tt2text /usr/local/bin
+	install ll2utm /usr/local/bin
+	install utm2ll /usr/local/bin
+	install aclients /usr/local/bin
+	install log2gpx /usr/local/bin
+	install -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt
+	install -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt
+	install -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt
+	install -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png
+	install -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop
+	install -D --mode=644 CHANGES.txt /usr/local/share/doc/direwolf/CHANGES.txt
+	install -D --mode=644 LICENSE-dire-wolf.txt /usr/local/share/doc/direwolf/LICENSE-dire-wolf.txt
+	install -D --mode=644 LICENSE-other.txt /usr/local/share/doc/direwolf/LICENSE-other.txt
+	install -D --mode=644 User-Guide.pdf /usr/local/share/doc/direwolf/User-Guide.pdf
+	install -D --mode=644 Raspberry-Pi-APRS.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS.pdf
+	install -D --mode=644 Raspberry-Pi-APRS-Tracker.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
+	install -D --mode=644 APRStt-Implementation-Notes.pdf /usr/local/share/doc/direwolf/APRStt-Implementation-Notes.pdf
+	install -D --mode=644 Quick-Start-Guide-Windows.pdf /usr/local/share/doc/direwolf/Quick-Start-Guide-Windows.pdf
+
+
+# The Raspberry Pi has ~/Desktop but Ubuntu does not.
+
+# TODO: Handle Linux variations correctly.
+
+
+install-rpi : dw-start.sh
 	cp dw-start.sh ~
-	sudo install -D --mode=644 CHANGES.txt /usr/local/share/doc/direwolf/CHANGES.txt
-	sudo install -D --mode=644 LICENSE-dire-wolf.txt /usr/local/share/doc/direwolf/LICENSE-dire-wolf.txt
-	sudo install -D --mode=644 LICENSE-other.txt /usr/local/share/doc/direwolf/LICENSE-other.txt
-	sudo install -D --mode=644 User-Guide.pdf /usr/local/share/doc/direwolf/User-Guide.pdf
-	sudo install -D --mode=644 Raspberry-Pi-APRS.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS.pdf
-	sudo install -D --mode=644 APRStt-Implementation-Notes.pdf /usr/local/share/doc/direwolf/APRStt-Implementation-Notes.pdf
-	sudo install -D --mode=644 Quick-Start-Guide-Windows.pdf /usr/local/share/doc/direwolf/Quick-Start-Guide-Windows.pdf
 	ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop
 
+install-conf : direwolf.conf
+	cp direwolf.conf ~
+
 
 # Separate application to decode raw data.
 
-decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c
+decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.c telemetry.o
 	$(CC) $(CFLAGS) -o decode_aprs -DTEST $^ -lm
 
 
@@ -182,23 +205,29 @@ tt2text : tt_text.c
 # Convert between Latitude/Longitude and UTM coordinates.
 
 ll2utm : ll2utm.c utm.a
-	$(CC) $(CFLAGS) -I utm -o $@ $^ -lm
+	$(CC) $(CFLAGS) -o $@ $^ -lm
 
 utm2ll : utm2ll.c utm.a
-	$(CC) $(CFLAGS) -I utm -o $@ $^ -lm
+	$(CC) $(CFLAGS) -o $@ $^ -lm
 
 
+# Convert from log file to GPX.
+
+log2gpx : log2gpx.c 
+	$(CC) $(CFLAGS) -o $@ $^ -lm
+
 
 # Test application to generate sound.
 
 gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c textcolor.c 
-	$(CC) $(CFLAGS) -o $@ $^ -lasound -lm
+	$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm
 
 demod.o : tune.h
 demod_afsk.o : tune.h
 demod_9600.o : tune.h
 
-testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o fcs_calc.c ax25_pad.c decode_aprs.c symbols.c tune.h textcolor.c
+testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \
+		fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tune.h textcolor.c
 	$(CC) $(CFLAGS) -o atest $^ -lm
 	./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
 
@@ -206,8 +235,9 @@ testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o
 # Unit test for AFSK demodulator
 
 
-atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c
-	$(CC) $(CFLAGS) -o $@ $^ -lm
+atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \
+		fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c textcolor.c
+	$(CC) $(CFLAGS) -o $@ $^ -lm -lrt
 	time ./atest ../direwolf-0.2/02_Track_2.wav 
 
 # Unit test for inner digipeater algorithm
@@ -233,6 +263,14 @@ udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec
 	./udptest
 
 
+# Unit test for telemetry decoding.
+
+
+etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
+	$(CC) $(CFLAGS) -o $@ $^ -lm -lrt
+	./etest
+	
+
 # Multiple AGWPE network or serial port clients to test TNCs side by side.
 
 aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c
@@ -241,7 +279,7 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c
 
 SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c multi_modem.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \
 		server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio.c \
-		digipeater.c dedupe.c tq.c xmit.c beacon.c encode_aprs.c latlong.c encode_aprs.c latlong.c
+		digipeater.c dedupe.c tq.c xmit.c beacon.c encode_aprs.c latlong.c encode_aprs.c latlong.c telemetry.c log.c
 
 
 depend : $(SRCS)
@@ -256,12 +294,12 @@ clean :
 # Package it up for distribution.
 
 dist-src : CHANGES.txt  User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \
-		direwolf.desktop dw-start.sh 
+		Raspberry-Pi-APRS-Tracker.pdf direwolf.desktop dw-start.sh 
 	rm -f fsk_fast_filter.h
 	echo " " > tune.h
 	rm -f ../$z-src.zip
 	(cd .. ; zip $z-src.zip $z/CHANGES.txt $z/LICENSE* \
-		$z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf $z/Raspberry-Pi-APRS.pdf \
+		$z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf $z/Raspberry-Pi-APRS.pdf Raspberry-Pi-APRS-Tracker.pdf \
 		$z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \
 		$z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
 		$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
@@ -277,12 +315,26 @@ dist-src : CHANGES.txt  User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-P
 #Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx
 #	echo "***** Raspberry-Pi-APRS.pdf is out of date *****" 
 
+#Raspberry-Pi-APRS-Tracker.pdf : Raspberry-Pi-APRS-Tracker.docx
+#	echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****" 
+
 
 backup :
 	mkdir /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
 	cp -r . /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
 
 #
+# The locations below appear to be the most recent.
+# The copy at http://www.aprs.org/tocalls.txt is out of date.
+#
+
+tocalls-symbols :
+	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
+	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
+	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
+
+
+#
 # The following is updated by "make depend"
 #
 # DO NOT DELETE
diff --git a/Makefile.win b/Makefile.win
index b879533..e54b539 100755
--- a/Makefile.win
+++ b/Makefile.win
@@ -16,7 +16,7 @@
 #
 
 
-all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients
+all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx
 
 
 # People say we need -mthreads option for threads to work properly.
@@ -25,8 +25,8 @@ all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients
 #TODO: put -Ofast back in.
 
 CC = gcc
-#CFLAGS = -g -Wall -Ofast -march=pentium3 -msse -Iregex -mthreads -DUSE_REGEX_STATIC
-CFLAGS = -g -Wall -march=pentium3 -msse -Iregex -mthreads -DUSE_REGEX_STATIC
+#CFLAGS = -g -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -mthreads -DUSE_REGEX_STATIC
+CFLAGS = -g -Wall -march=pentium3 -msse -Iregex -Iutm -mthreads -DUSE_REGEX_STATIC
 AR = ar
 
 
@@ -77,7 +77,7 @@ direwolf : direwolf.o config.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.
 		decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
 		gen_tone.o audio_win.o digipeater.o dedupe.o tq.o xmit.o \
 		ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \
-		dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o \
+		dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o log.o telemetry.o \
 		dw-icon.o regex.a misc.a utm.a
 	$(CC) $(CFLAGS) -g -o $@ $^ -lwinmm -lws2_32
 
@@ -137,7 +137,7 @@ strcasestr.o : misc/strcasestr.c
 
 # Separate application to decode raw data.
 
-decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c regex.a misc.a
+decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.o telemetry.o regex.a misc.a utm.a
 	$(CC) $(CFLAGS) -o decode_aprs -DTEST $^
 
 
@@ -153,10 +153,16 @@ tt2text : tt_text.c
 # Convert between Latitude/Longitude and UTM coordinates.
 
 ll2utm : ll2utm.c utm.a
-	$(CC) $(CFLAGS) -I utm -o $@ $^
+	$(CC) $(CFLAGS) -o $@ $^
 
 utm2ll : utm2ll.c utm.a
-	$(CC) $(CFLAGS) -I utm -o $@ $^
+	$(CC) $(CFLAGS) -o $@ $^
+
+
+# Convert from log file to GPX.
+
+log2gpx : log2gpx.c misc/strsep.c misc/strtok_r.c
+	$(CC) $(CFLAGS) -o $@ $^
 
 
 # Test application to generate sound.
@@ -172,7 +178,7 @@ demod_afsk.o : tune.h
 
 
 testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
-		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c regex.a misc.a \
+		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c telemetry.c regex.a misc.a \
 		fsk_demod_agc.h
 	rm -f atest.exe
 	$(CC) $(CFLAGS) -DNOFIX -o atest $^
@@ -183,7 +189,7 @@ noisy3.wav : gen_packets
 	./gen_packets -B 300 -n 100 -o noisy3.wav
 
 testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
-		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c regex.a misc.a \
+		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c telemetry.c regex.a misc.a \
 		tune.h 
 	rm -f atest.exe
 	$(CC) $(CFLAGS) -o atest $^
@@ -194,7 +200,7 @@ noisy96.wav : gen_packets
 	./gen_packets -B 9600 -n 100 -o noisy96.wav
 
 testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
-		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c regex.a misc.a \
+		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c telemetry.c regex.a misc.a \
 		tune.h 
 	rm -f atest.exe
 	$(CC) $(CFLAGS) -o atest $^
@@ -206,14 +212,14 @@ testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.
 
 
 atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
-		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c misc.a regex.a \
+		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c misc.a regex.a \
 		fsk_fast_filter.h
 	$(CC) $(CFLAGS) -o $@ $^
 	echo " " > tune.h
 	./atest ..\\direwolf-0.2\\02_Track_2.wav 
 
 atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
-		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c misc.a regex.a \
+		rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c misc.a regex.a \
 		fsk_fast_filter.h
 	$(CC) $(CFLAGS) -o $@ $^
 	./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
@@ -247,6 +253,14 @@ udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec
 	./udptest
 
 
+# Unit test for telemetry decoding.
+
+
+etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
+	$(CC) $(CFLAGS) -DTEST -o $@ $^
+	./etest
+	
+
 # Multiple AGWPE network or serial port clients to test TNCs side by side.
 
 aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
@@ -258,7 +272,7 @@ SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c \
 		fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \
 		server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio_win.c \
 		digipeater.c dedupe.c tq.c xmit.c beacon.c \
-		encode_aprs.c latlong.c \
+		encode_aprs.c latlong.c telemetry.c \
 		dtmf.c aprs_tt.c tt_text.c igate.c
 
 
@@ -275,15 +289,15 @@ clean :
 
 
 dist-win : direwolf.exe decode_aprs.exe CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \
-			Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf
+			Raspberry-Pi-APRS.pdf Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf
 	rm -f ../$z-win.zip
 	zip ../$z-win.zip CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \
-		Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf LICENSE* *.conf \
+		Raspberry-Pi-APRS.pdf Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf LICENSE* *.conf \
 		direwolf.exe decode_aprs.exe tocalls.txt symbols-new.txt symbolsX.txt \
-		text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe aclients.exe
+		text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe aclients.exe log2gpx.exe
 
 dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \
-		APRStt-Implementation-Notes.pdf \
+		Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf \
 		direwolf.desktop dw-start.sh \
 		tocalls.txt symbols-new.txt symbolsX.txt
 	rm -f fsk_fast_filter.h
@@ -292,7 +306,7 @@ dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi
 	(cd .. ; zip $z-src.zip \
 		$z/CHANGES.txt $z/LICENSE* \
 		$z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf \
-		$z/Raspberry-Pi-APRS.pdf $z/APRStt-Implementation-Notes.pdf \
+		$z/Raspberry-Pi-APRS.pdf $z/Raspberry-Pi-APRS-Tracker.pdf $z/APRStt-Implementation-Notes.pdf \
 		$z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \
 		$z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
 		$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
@@ -309,6 +323,10 @@ Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx
 Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx
 	echo "***** Raspberry-Pi-APRS.pdf is out of date *****" 
 
+Raspberry-Pi-APRS-Tracker.pdf : Raspberry-Pi-APRS-Tracker.docx
+	echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****" 
+
+
 APRStt-Implementation-Notes.pdf : APRStt-Implementation-Notes.docx
 	echo "***** APRStt-Implementation-Notes.pdf is out of date *****"
 
@@ -318,6 +336,16 @@ backup :
 	cp -r . /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
 
 #
+# The locations below appear to be the most recent.
+# The copy at http://www.aprs.org/tocalls.txt is out of date.
+#
+
+tocalls-symbols :
+	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
+	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
+	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
+
+#
 # The following is updated by "make depend"
 #
 # DO NOT DELETE
diff --git a/Raspberry-Pi-APRS-Tracker.pdf b/Raspberry-Pi-APRS-Tracker.pdf
new file mode 100755
index 0000000..f4f581b
Binary files /dev/null and b/Raspberry-Pi-APRS-Tracker.pdf differ
diff --git a/Raspberry-Pi-APRS.pdf b/Raspberry-Pi-APRS.pdf
index ef01cce..50e85dc 100755
Binary files a/Raspberry-Pi-APRS.pdf and b/Raspberry-Pi-APRS.pdf differ
diff --git a/User-Guide.pdf b/User-Guide.pdf
index ff3b46f..e1a7501 100755
Binary files a/User-Guide.pdf and b/User-Guide.pdf differ
diff --git a/aclients.c b/aclients.c
index 7f8b722..9b55205 100755
--- a/aclients.c
+++ b/aclients.c
@@ -519,7 +519,7 @@ static void * client_thread_net (void *arg)
 #if __WIN32__	      
 	send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
 #else
-	(void)write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
+	err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
 #endif
 
 
@@ -649,6 +649,8 @@ static void * client_thread_serial (void *arg)
 	DCB dcb;
 	int ok;
 
+	// Bug: Won't work for ports above COM9.
+	// http://support.microsoft.com/kb/115831
 
 	fd = CreateFile(port[my_index], GENERIC_READ | GENERIC_WRITE, 
 			0, NULL, OPEN_EXISTING, 0, NULL);
@@ -671,6 +673,7 @@ static void * client_thread_serial (void *arg)
 
 	/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
 
+	dcb.DCBlength = sizeof(DCB);
 	dcb.BaudRate = 9600;
 	dcb.fBinary = 1;
 	dcb.fParity = 0;
diff --git a/aprs_tt.c b/aprs_tt.c
index 1b4a791..e3702d5 100755
--- a/aprs_tt.c
+++ b/aprs_tt.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2013,2014  John Langner, WB2OSZ
+//    Copyright (C) 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -32,6 +32,9 @@
  *
  *---------------------------------------------------------------*/
 
+#define APRS_TT_C 1
+
+
 // TODO:  clean up terminolgy.  
 // "Message" has a specific meaning in APRS and this is not it.  
 // Touch Tone sequence might be appropriate.
@@ -872,6 +875,7 @@ static int parse_symbol (char *e)
  *
  * Outputs:	m_latitude
  *		m_longitude
+ *		m_dao
  *
  * Returns:	0 for success or one of the TT_ERROR_... codes.
  *
@@ -915,6 +919,9 @@ static int parse_location (char *e)
 	m_dao[3] = e[1];	/* Type of location.  e.g.  !TB6! */
 				/* Will be changed by point types. */
 
+				/* If this ever changes, be sure to update corresponding */
+				/* section in process_comment() in decode_aprs.c */
+
 	len = strlen(e);
 
 	ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr);
@@ -1320,7 +1327,13 @@ static void raw_tt_data_to_app (int chan, char *msg)
  * thru the multi modem duplicate processing.
  */
 
-	app_process_rec_packet (chan, -1, pp, -2, RETRY_NONE, "tt");
+	if (pp != NULL) {
+	  app_process_rec_packet (chan, -1, pp, -2, RETRY_NONE, "tt");
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not convert \"%s\" into APRS packet.\n", raw_tt_msg);
+	}
 
 #endif
 }
diff --git a/aprs_tt.h b/aprs_tt.h
index 9826e5f..a54f3b4 100755
--- a/aprs_tt.h
+++ b/aprs_tt.h
@@ -4,6 +4,7 @@
 #ifndef APRS_TT_H
 #define APRS_TT_H 1
 
+
 /*
  * For holding location format specifications from config file.
  * Same thing is also useful for macro definitions.
@@ -52,7 +53,7 @@ struct ttloc_s {
 };
 
 /* 
- * Configuratin options for APRStt.
+ * Configuration options for APRStt.
  */
 
 #define TT_MAX_XMITS 10
@@ -77,6 +78,8 @@ struct tt_config_s {
 	int corral_ambiguity;
 };
 
+
+
 	
 void aprs_tt_init (struct tt_config_s *p_config);
 
@@ -95,6 +98,10 @@ void aprs_tt_button (int chan, char button);
 #define TT_ERROR_NO_CALL	9	/* No call or object name included. */
 
 
+#define APRSTT_LOC_DESC_LEN 32		/* Need at least 26 */
+
+void aprs_tt_dao_to_desc (char *dao, char *str);
+
 #endif
 
 /* end aprs_tt.h */
\ No newline at end of file
diff --git a/atest.c b/atest.c
index 4fbea80..3564bc6 100755
--- a/atest.c
+++ b/atest.c
@@ -145,14 +145,64 @@ int main (int argc, char *argv[])
 	modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC;	
 	modem.bits_per_sample = DEFAULT_BITS_PER_SAMPLE;	
 
+	// Results v0.9: 
+	//
+	// fix_bits = 0 	971 packets, 69 sec
+	// fix_bits = SINGLE	990          64
+	// fix_bits = DOUBLE    992          65
+	// fix_bits = TRIPLE    992          67
+	// fix_bits = TWO_SEP   1004        476 
+
+	// Essentially no difference in time for those with order N time.
+	// Time increases greatly for the one with order N^2 time.
+
+
+	// Results: version 1.1, decoder C, modem.fix_bits = RETRY_MAX - 1
+	//
+	// 971 NONE
+	// +19 SINGLE
+	// +2  DOUBLE
+	// +12 TWO_SEP
+	// +3  REMOVE_MANY
+	// ----
+	// 1007 Total in 1008 sec.   More than twice as long as earlier version.
+
+	// Results: version 1.1, decoders ABC, modem.fix_bits = RETRY_MAX - 1
+	//
+	// 976 NONE
+	// +21  SINGLE
+	// +1   DOUBLE
+	// +22  TWO_SEP
+	// +1   MANY
+	// +3   REMOVE_MANY
+	// ----
+	// 1024 Total in 2042 sec.
+	// About 34 minutes of CPU time for a roughly 40 minute CD.
+	// Many computers wouldn't be able to keep up.
+
+	// The SINGLE and TWO_SEP techniques are the most effective.
+	// Should we reorder the enum values so that TWO_SEP
+	// comes after SINGLE?   That way "FIX_BITS 2" would 
+	// use the two most productive techniques and not waste
+	// time on the others.  People with plenty of CPU power
+	// to spare can still specify larger numbers for the other
+	// techniques with less return on investment.
+
+
+
+// TODO: tabulate results from atest2.c.txt
+
+
 	/* TODO: should have a command line option for this. */
-	/* Results v0.9: 971/69, 990/64, 992/65, 992/67, 1004/476 */
 
 	modem.fix_bits = RETRY_NONE;
-	modem.fix_bits = RETRY_SINGLE;
-	modem.fix_bits = RETRY_DOUBLE;
-	//modem.fix_bits = RETRY_TRIPLE;
-	//modem.fix_bits = RETRY_TWO_SEP;
+	//modem.fix_bits = RETRY_SWAP_SINGLE;
+	//modem.fix_bits = RETRY_SWAP_DOUBLE;
+	//modem.fix_bits = RETRY_SWAP_TRIPLE;
+	//modem.fix_bits = RETRY_INSERT_DOUBLE;
+	modem.fix_bits = RETRY_SWAP_TWO_SEP;
+	//modem.fix_bits = RETRY_REMOVE_MANY;
+	//modem.fix_bits = RETRY_MAX - 1;
 
 	for (channel=0; channel<MAX_CHANS; channel++) {
 
@@ -162,6 +212,7 @@ int main (int argc, char *argv[])
 	  modem.space_freq[channel] = DEFAULT_SPACE_FREQ;		
 	  modem.baud[channel] = DEFAULT_BAUD;	
 	  strcpy (modem.profiles[channel], "C");	
+	  //strcpy (modem.profiles[channel], "ABC");	
 	// temp	
 	// strcpy (modem.profiles[channel], "F");		
 	  modem.num_subchan[channel] = strlen(modem.profiles[channel]);	
@@ -334,7 +385,6 @@ int main (int argc, char *argv[])
                 /* process_rec_frame, below, is called. */
 
 	}
-
 	text_color_set(DW_COLOR_INFO);
 	printf ("\n\n");
 	printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time));
@@ -415,7 +465,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 
 	text_color_set(DW_COLOR_DEBUG);
 	printf ("\n");
-
+	printf("DECODED[%d] ", packets_decoded );
 	if (h != AX25_SOURCE) {
 	  printf ("Digipeater ");
 	}
diff --git a/audio.c b/audio.c
index 7e52f64..082dd24 100755
--- a/audio.c
+++ b/audio.c
@@ -50,15 +50,13 @@
  *
  *		http://www.alsa-project.org/main/index.php/Asoundrc
  *
- * Credits:	Fabrice FAURE contributed code for the SDR UDP interface.
+ * Credits:	Release 1.0: Fabrice FAURE contributed code for the SDR UDP interface.
  *
  *		Discussion here:  http://gqrx.dk/doc/streaming-audio-over-udp
  *
- *
- * Future:	Will probably rip out the OSS code.
- *		ALSA was added to Linux kernel 10 years ago.
- *		Cygwin doesn't have it but I see no reason to support Cygwin
- *		now that we have a native Windows version.
+ *		Release 1.1:  Gabor Berczi provided fixes for the OSS code
+ *		which had fallen into decay.
+ *		
  *
  *---------------------------------------------------------------*/
 
@@ -81,8 +79,16 @@
 #if USE_ALSA
 #include <alsa/asoundlib.h>
 #else
+#ifdef __OpenBSD__
+#include <soundcard.h>
+#else
 #include <sys/soundcard.h>
 #endif
+#endif
+
+#ifdef __FreeBSD__
+#include <errno.h>
+#endif
 
 #include "direwolf.h"
 #include "audio.h"
@@ -101,6 +107,7 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *name, c
 //static void alsa_select_device (char *pick_dev, int direction, char *result);
 #else
 
+static int set_oss_params (int fd, struct audio_s *pa);
 static int oss_audio_device_fd = -1;	/* Single device, both directions. */
 
 #endif
@@ -180,11 +187,11 @@ int audio_open (struct audio_s *pa)
 	int err;
 	int chan;
 
-#if USE_ALSA
-
 	char audio_in_name[30];
 	char audio_out_name[30];
 
+#if USE_ALSA
+
 	assert (audio_in_handle == NULL);
 	assert (audio_out_handle == NULL);
 
@@ -234,19 +241,18 @@ int audio_open (struct audio_s *pa)
 	outbuf_ptr = NULL;
 	outbuf_len = 0;
 
-#if USE_ALSA
-
 /*
  * Determine the type of audio input.
  */
+
 	audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; 	
-	
+
 	if (strcasecmp(pa->adevice_in, "stdin") == 0 || strcmp(pa->adevice_in, "-") == 0) {
 	  audio_in_type = AUDIO_IN_TYPE_STDIN;
 	  /* Change - to stdin for readability. */
 	  strcpy (pa->adevice_in, "stdin");
 	}
-	else if (strncasecmp(pa->adevice_in, "udp:", 4) == 0) {
+	if (strncasecmp(pa->adevice_in, "udp:", 4) == 0) {
 	  audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
 	  /* Supply default port if none specified. */
 	  if (strcasecmp(pa->adevice_in,"udp") == 0 ||
@@ -285,7 +291,7 @@ int audio_open (struct audio_s *pa)
  * Soundcard - ALSA.
  */
 	  case AUDIO_IN_TYPE_SOUNDCARD:
-
+#if USE_ALSA
 	    err = snd_pcm_open (&audio_in_handle, audio_in_name, SND_PCM_STREAM_CAPTURE, 0);
 	    if (err < 0) {
 	      text_color_set(DW_COLOR_ERROR);
@@ -296,6 +302,23 @@ int audio_open (struct audio_s *pa)
 
 	    inbuf_size_in_bytes = set_alsa_params (audio_in_handle, pa, audio_in_name, "input");
 	    break;
+#else // OSS
+	oss_audio_device_fd = open (pa->adevice_in, O_RDWR);
+
+	if (oss_audio_device_fd < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("%s:\n", pa->adevice_in);
+//	  sprintf (message, "Could not open audio device %s", pa->adevice_in);
+//	  perror (message);
+	  return (-1);
+	}
+
+	outbuf_size_in_bytes = inbuf_size_in_bytes = set_oss_params (oss_audio_device_fd, pa);
+
+	if (inbuf_size_in_bytes <= 0 || outbuf_size_in_bytes <= 0) {
+	  return (-1);
+	}
+#endif
 
 /*
  * UDP.
@@ -353,6 +376,8 @@ int audio_open (struct audio_s *pa)
 /*
  * Output device.  Only "soundcard" is supported at this time. 
  */
+
+#if USE_ALSA
 	err = snd_pcm_open (&audio_out_handle, audio_out_name, SND_PCM_STREAM_PLAYBACK, 0);
 
 	if (err < 0) {
@@ -368,34 +393,7 @@ int audio_open (struct audio_s *pa)
 	  return (-1);
 	}
 
-
-
-
-#else /* end of ALSA case */
-
-
-#error OSS support will probably be removed.  Complain if you still care about OSS.
-
-	oss_audio_device_fd = open (pa->adevice_in, O_RDWR);
-
-	if (oss_audio_device_fd < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("%s:\n", pa->adevice_in);
-	  sprintf (message, "Could not open audio device %s", pa->adevice_in);
-	  perror (message);
-	  return (-1);
-	}
-
-	outbuf_size_in_bytes = inbuf_size_in_bytes = set_oss_params (oss_audio_device_fd, pa);
-
-	if (inbuf_size_in_bytes <= 0 || outbuf_size_in_bytes <= 0) {
-	  return (-1);
-	}
-
-
-
-#endif	/* end of OSS case */
-
+#endif
 
 /*
  * Finally allocate buffer for each direction.
@@ -811,7 +809,7 @@ int audio_get (void)
 	        this_time = time(NULL);
 	        if (this_time >= last_time + duration) {
 	          text_color_set(DW_COLOR_DEBUG);
-	          dw_printf ("\nPast %d seconds, %d audio samples, %d errors.\n\n", 
+	          dw_printf ("\nPast %d seconds, %d audio samples processed, %d errors.\n\n", 
 			duration, sample_count, error_count);
 	          last_time = this_time;
 	          sample_count = 0;
diff --git a/audio.h b/audio.h
index a6eb535..a700238 100755
--- a/audio.h
+++ b/audio.h
@@ -22,7 +22,8 @@
 enum ptt_method_e { 
 	PTT_METHOD_NONE,	/* VOX or no transmit. */
 	PTT_METHOD_SERIAL,	/* Serial port RTS or DTR. */
-	PTT_METHOD_GPIO };	/* General purpos I/O. */
+	PTT_METHOD_GPIO,	/* General purpose I/O, Linux only. */
+	PTT_METHOD_LPT };	/* Parallel printer port, Linux only. */
 
 typedef enum ptt_method_e ptt_method_t;
 
@@ -93,6 +94,9 @@ struct audio_s {
 					/* PTT_RTS, PTT_DTR. */
 
 	int ptt_gpio[MAX_CHANS];	/* GPIO number. */
+	
+	int ptt_lpt_bit[MAX_CHANS];	/* Bit number for parallel printer port.  */
+					/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
 
 	int ptt_invert[MAX_CHANS];	/* Invert the output. */
 
@@ -153,7 +157,7 @@ struct audio_s {
 
 #define DEFAULT_BITS_PER_SAMPLE	16
 
-#define DEFAULT_FIX_BITS RETRY_SINGLE
+#define DEFAULT_FIX_BITS RETRY_SWAP_SINGLE
 
 /* 
  * Standard for AFSK on VHF FM. 
diff --git a/audio_win.c b/audio_win.c
index fcd7a53..20d2c46 100755
--- a/audio_win.c
+++ b/audio_win.c
@@ -744,7 +744,7 @@ int audio_get (void)
 	        this_time = time(NULL);
 	        if (this_time >= last_time + duration) {
 	          text_color_set(DW_COLOR_DEBUG);
-	          dw_printf ("\nPast %d seconds, %d audio samples, %d errors.\n\n", 
+	          dw_printf ("\nPast %d seconds, %d audio samples processed, %d errors.\n\n", 
 			duration, sample_count, error_count);
 	          last_time = this_time;
 	          sample_count = 0;
diff --git a/ax25_pad.c b/ax25_pad.c
index 1bd3451..e182cbf 100755
--- a/ax25_pad.c
+++ b/ax25_pad.c
@@ -1,10 +1,8 @@
-// TODO:  Shouldn't this be using dw_printf???
-
 
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011,2013  John Langner, WB2OSZ
+//    Copyright (C) 2011 , 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -39,7 +37,7 @@
  *		(b) print in human-readable text.
  *		(c) take it apart piece by piece.
  *
- *		Looking at the more general case, we might want to modify
+ *		Looking at the more general case, we also want to modify
  *		an existing packet.  For instance an APRS repeater might 
  *		want to change "WIDE2-2" to "WIDE2-1" and retransmit it.
  *
@@ -56,8 +54,8 @@
  *					and decrementing the remaining count in
  *					WIDEn-n, TRACEn-n, etc.?   
  *					NO.  The limit is 8 when transmitting AX.25 over the radio.
- *					However, communication with an IGate server
- *					could have a longer VIA path but that is in text form.)
+ *					However, communication with an IGate server could have 
+ *					a longer VIA path but that is only in text form, not here.)
  *
  *	Each address is composed of:
  *
@@ -113,6 +111,8 @@
  *	Next we have:
  *
  *	* One byte Control Field 	- APRS uses 3 for UI frame
+ *					   The more general AX.25 frame can have two.
+ *
  *	* One byte Protocol ID 		- APRS uses 0xf0 for no layer 3
  *
  *	Finally the Information Field of 1-256 bytes.
@@ -164,6 +164,9 @@ char *strtok_r(char *str, const char *delim, char **saveptr);
 static int new_count = 0;
 static int delete_count = 0;
 
+#define CLEAR_LAST_ADDR_FLAG  this_p->frame_data[this_p->num_addr*7-1] &= ~ SSID_LAST_MASK
+#define SET_LAST_ADDR_FLAG  this_p->frame_data[this_p->num_addr*7-1] |= SSID_LAST_MASK
+
 
 /*------------------------------------------------------------------------------
  *
@@ -200,6 +203,7 @@ static packet_t ax25_new (void)
 	this_p = calloc(sizeof (struct packet_s), (size_t)1);
 	this_p->magic1 = MAGIC;
 	this_p->magic2 = MAGIC;
+	this_p->num_addr = (-1);
 	return (this_p);
 }
 
@@ -239,6 +243,12 @@ void ax25_delete (packet_t this_p)
  *		strict	- True to enforce rules for packets sent over the air.
  *			  False to be more lenient for packets from IGate server.
  *
+ *			  Messages from an IGate server can have longer 
+ *		 	  addresses after qAC.  Up to 9 observed so far. 
+ *
+ *			  We can just truncate the name because we will only
+ *			  end up discarding it.    TODO:  check on this.
+ *
  * Returns:	Pointer to new packet object in the current implementation.
  *
  * Outputs:	Use the "get" functions to retrieve information in different ways.
@@ -266,13 +276,14 @@ packet_t ax25_from_text (char *monitor, int strict)
 	int keep_going;
 	char temp[512];
 	int ssid_temp, heard_temp;
-
+	char atemp[AX25_MAX_ADDR_LEN];
 
 	
 	packet_t this_p = ax25_new ();
 
 	/* Is it possible to have a nul character (zero byte) in the */
 	/* information field of an AX.25 frame? */
+	/* Yes, but it would be difficult in the from-text case. */
 
 	strcpy (stuff, monitor);
 
@@ -323,6 +334,24 @@ packet_t ax25_from_text (char *monitor, int strict)
 #endif
 
 /*
+ * Initialize the packet with two addresses and control/pid
+ * for APRS.
+ */
+	memset (this_p->frame_data + AX25_DESTINATION*7, ' ' << 1, 6);
+	this_p->frame_data[AX25_DESTINATION*7+6] = SSID_H_MASK | SSID_RR_MASK;
+ 
+	memset (this_p->frame_data + AX25_SOURCE*7, ' ' << 1, 6);
+	this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK;
+
+	this_p->frame_data[14] = AX25_UI_FRAME;
+	this_p->frame_data[15] = AX25_NO_LAYER_3;
+
+	this_p->frame_len = 7 + 7 + 1 + 1;
+	this_p->num_addr = (-1);
+	assert (ax25_get_num_addr(this_p) == 2);
+
+
+/*
  * Separate the addresses from the rest.
  */
 	pinfo = strchr (stuff, ':');
@@ -341,16 +370,11 @@ packet_t ax25_from_text (char *monitor, int strict)
 	  pinfo[AX25_MAX_INFO_LEN] = '\0';
 	}
 	
-	strcpy ((char*)(this_p->the_rest + 2), pinfo);
-	this_p->the_rest_len = strlen(pinfo) + 2;
-
 /*
- * Now separate the addresses.
+ * Separate the addresses.
  * Note that source and destination order is swappped.
  */
 
-	this_p->num_addr = 2;
-
 /*
  * Source address.
  * Don't use traditional strtok because it is not thread safe.
@@ -363,14 +387,16 @@ packet_t ax25_from_text (char *monitor, int strict)
 	  return (NULL);
 	}
 
-	if ( ! ax25_parse_addr (pa, strict, this_p->addrs[AX25_SOURCE], &ssid_temp, &heard_temp)) {
+	if ( ! ax25_parse_addr (pa, strict, atemp, &ssid_temp, &heard_temp)) {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf ("Failed to create packet from text.  Bad source address\n");
 	  ax25_delete (this_p);
 	  return (NULL);
 	}
 
-	this_p->ssid_etc[AX25_SOURCE] = SSID_H_MASK | SSID_RR_MASK;
+	ax25_set_addr (this_p, AX25_SOURCE, pa);
+	ax25_set_h (this_p, AX25_SOURCE);	// c/r in this position
+
 	ax25_set_ssid (this_p, AX25_SOURCE, ssid_temp);
 
 /*
@@ -385,14 +411,16 @@ packet_t ax25_from_text (char *monitor, int strict)
 	  return (NULL);
 	}
 
-	if ( ! ax25_parse_addr (pa, strict, this_p->addrs[AX25_DESTINATION], &ssid_temp, &heard_temp)) {
+	if ( ! ax25_parse_addr (pa, strict, atemp, &ssid_temp, &heard_temp)) {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf ("Failed to create packet from text.  Bad destination address\n");
 	  ax25_delete (this_p);
 	  return (NULL);
 	}
 
-	this_p->ssid_etc[AX25_DESTINATION] = SSID_H_MASK | SSID_RR_MASK;
+	ax25_set_addr (this_p, AX25_DESTINATION, pa);
+	ax25_set_h (this_p, AX25_DESTINATION);	// c/r in this position
+
 	ax25_set_ssid (this_p, AX25_DESTINATION, ssid_temp);
 
 /*
@@ -405,16 +433,17 @@ packet_t ax25_from_text (char *monitor, int strict)
 
 	  k = this_p->num_addr;
 
-	  this_p->num_addr++;
+	  // JWL 10:38 this_p->num_addr++;
 
-	  if ( ! ax25_parse_addr (pa, strict, this_p->addrs[k], &ssid_temp, &heard_temp)) {
+	  if ( ! ax25_parse_addr (pa, strict, atemp, &ssid_temp, &heard_temp)) {
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf ("Failed to create packet from text.  Bad digipeater address\n");
 	    ax25_delete (this_p);
 	    return (NULL);
 	  }
 
-	  this_p->ssid_etc[k] = SSID_RR_MASK;
+	  ax25_set_addr (this_p, k, pa);
+	  
 	  ax25_set_ssid (this_p, k, ssid_temp);
 
 	  // Does it have an "*" at the end? 
@@ -427,9 +456,13 @@ packet_t ax25_from_text (char *monitor, int strict)
 	    }
 	  }
         }
-	
-	this_p->the_rest[0] = AX25_UI_FRAME;
-	this_p->the_rest[1] = AX25_NO_LAYER_3;
+
+/*
+ * Append the info part.  
+ */
+	strcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo);
+	this_p->frame_len += strlen(pinfo);
+
 
 	return (this_p);
 }
@@ -470,7 +503,16 @@ packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel)
  *
  *	We are not concerned with the FCS (CRC) because someone else checked it.
  *
- * Is is possible to have zero length for info?  No.
+ * Is is possible to have zero length for info?  
+ *
+ * In the original version, assuming APRS, the answer was no.
+ * We always had at least 3 octets after the address part:
+ * control, protocol, and first byte of info part for data type.
+ *
+ * In later versions, this restriction was relaxed so other
+ * variations of AX.25 could be used.  Now the minimum length
+ * is 7+7 for addresses plus 1 for control.
+ *
  */
 
 	if (flen < AX25_MIN_PACKET_LEN || flen > AX25_MAX_PACKET_LEN)
@@ -482,149 +524,16 @@ packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel)
 
 	this_p = ax25_new ();
 
-/*
- * Extract the addresses.
- * The last one has '1' in the LSB of the last byte.
- */
-
-#if 1
-
-/* 
- * 0.9 - Try new strategy that will allow KISS mode 
- * to handle non AX.25 frame. 
- */
-
-	this_p->num_addr = 0;		/* Number of addresses extracted. */
-	
-	addr_bytes = 0;
-	for (a = 0; a < flen && addr_bytes == 0; a++) {
-	  if (fbuf[a] & 0x01) {
-	    addr_bytes = a + 1;
-	  }
-	}
+/* Copy the whole thing intact. */
 
-	if (addr_bytes % 7 == 0) {
-	  int addrs = addr_bytes / 7;
-	  if (addrs >= AX25_MIN_ADDRS && addrs <= AX25_MAX_ADDRS) {
-	    this_p->num_addr = addrs;
-	    
-	    for (a = 0; a < addrs; a++){ 
-	      unsigned char *pin;
-	      char *pout;
-	      int j;
-	      char ch;
-	
-	      pin = fbuf + a * 7;
-              pout = & this_p->addrs[a][0];
-
-	      for (j=0; j<6; j++) 
-	      {
-	        ch = *pin++ >> 1;
-	        if (ch != ' ')
-	        {
-	          *pout++ = ch;
-	        }
-	      }
-	      *pout = '\0';
-
-	      this_p->ssid_etc[a] = *pin & ~ SSID_LAST_MASK;
-	    }
-	  }
-	}
-	
-	pf = fbuf + this_p->num_addr * 7;
+	memcpy (this_p->frame_data, fbuf, flen);
+	this_p->frame_data[flen] = 0;
+	this_p->frame_len = flen;
 
-#else 
-
-	pf = fbuf;		/* Transmitted form from here. */
-
-	this_p->num_addr = 0;		/* Number of addresses extracted. */
-	found_last = 0;
-
-	while (this_p->num_addr < AX25_MAX_ADDRS && ! found_last) {
-
-	  unsigned char *pin;
-	  char *pout;
-	  int j;
-	  char ch;
+/* Find number of addresses. */
 	
-	  pin = pf;
-          pout = & this_p->addrs[this_p->num_addr][0];
-
-	  for (j=0; j<6; j++) 
-	  {
-	    ch = *pin++ >> 1;
-	    if (ch != ' ')
-	    {
-	      *pout++ = ch;
-	    }
-	  }
-	  *pout = '\0';
-
-	  this_p->ssid_etc[this_p->num_addr] = pf[6] & ~ SSID_LAST_MASK;
-	
-  	  this_p->num_addr++;
-
-	  if (pf[6] & SSID_LAST_MASK) {	/* Is this the last one? */
-	    found_last = 1;
-          }
-	  else {
-	    pf += 7;			/* Get ready for next one. */
-	  }
-	}
-
-	if (this_p->num_addr < 2) {
-	  int k;
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Frame format error detected in ax25_from_frame, %s, line %d.\n", __FILE__, __LINE__);
-	  dw_printf ("Did not find a minimum of two addresses at beginning of AX.25 frame.\n");
-	  for (k=0; k<14; k++) {
-	    dw_printf (" %02x", fbuf[k]);
-	  }
-	  dw_printf ("\n");
-	  /* Should we keep going or delete the packet? */
-	}
-
-/*
- * pf still points to the last address (repeater or source).
- *
- * Verify that it has the last address bit set.
- */
-	if ((pf[6] & SSID_LAST_MASK) == 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Last address in header does not have LSB set.\n");
-	  ax25_delete (this_p);
-	  return (NULL);
-	}
-
-	pf += 7;
-
-#endif
-
-	if (this_p->num_addr * 7 > flen - 1) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Frame too short to include control field.\n");
-	  ax25_delete (this_p);
-	  return (NULL);
-	}
-
-
-
-
-/*
- * pf should now point to control field.
- * Previously we separated out control, PID, and Info here.
- *
- * Now (version 0.8) we save control, PID, and info together.
- * This makes it easier to act as a dumb KISS TNC
- * for AX.25-based protocols other than APRS.
- */
-	this_p->the_rest_len = flen - (pf - fbuf);
-
-	assert (this_p->the_rest_len >= 1);
-
-	memcpy (this_p->the_rest, pf, (size_t)this_p->the_rest_len);
-	this_p->the_rest[this_p->the_rest_len] = '\0';
+	this_p->num_addr = (-1);
+	(void) ax25_get_num_addr (this_p);
 
 	return (this_p);
 }
@@ -700,6 +609,8 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i
 	*out_ssid = 0;
 	*out_heard = 0;
 
+	//dw_printf ("ax25_parse_addr in: %s\n", in_addr);
+
 	maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1);
 	p = in_addr;
 	i = 0;
@@ -755,6 +666,8 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i
 	  return 0;
 	}
 
+	//dw_printf ("ax25_parse_addr out: %s %d %d\n", out_addr, *out_ssid, *out_heard);
+
 	return (1);
 
 } /* end ax25_parse_addr */
@@ -803,33 +716,66 @@ packet_t ax25_unwrap_third_party (packet_t from_pp)
  *
  * Inputs:	n	- Index of address.   Use the symbols 
  *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ *			  Must be either an existing address or one greater
+ *			  than the final which causes a new one to be added.
+ *
  *		ad	- Address with optional dash and substation id.
  *
  * Assumption:	ax25_from_text or ax25_from_frame was called first.
  *
- * TODO:  ax25_from_text could use this.
+ * TODO:  	ax25_from_text could use this.
  *
  * Returns:	None.
  *		
- *
  *------------------------------------------------------------------------------*/
 
 void ax25_set_addr (packet_t this_p, int n, char *ad)
 {
 	int ssid_temp, heard_temp;
+	char atemp[AX25_MAX_ADDR_LEN];
+	int i;
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
 	assert (n >= 0 && n < AX25_MAX_ADDRS);
 	assert (strlen(ad) < AX25_MAX_ADDR_LEN);
 
-	if (n+1 > this_p->num_addr) {
-	  this_p->num_addr = n+1;
-	  this_p->ssid_etc[n] = SSID_RR_MASK;
+	//dw_printf ("ax25_set_addr (%d, %s) num_addr=%d\n", n, ad, this_p->num_addr);
+
+	if (n >= 0 && n < this_p->num_addr) {
+
+	  //dw_printf ("ax25_set_addr , existing case\n");
+/* 
+ * Set existing address position. 
+ */
+	  ax25_parse_addr (ad, 0, atemp, &ssid_temp, &heard_temp);
+
+	  memset (this_p->frame_data + n*7, ' ' << 1, 6);
+
+	  for (i=0; i<6 && atemp[i] != '\0'; i++) {
+	    this_p->frame_data[n*7+i] = atemp[i] << 1;
+	  }
+	  ax25_set_ssid (this_p, n, ssid_temp);
 	}
+	else if (n == this_p->num_addr) {		
 
-	ax25_parse_addr (ad, 0, this_p->addrs[n], &ssid_temp, &heard_temp);
-	ax25_set_ssid (this_p, n, ssid_temp);
+	  //dw_printf ("ax25_set_addr , appending case\n");
+/* 
+ * One beyond last position, process as insert.
+ */
+
+	  ax25_insert_addr (this_p, n, ad);
+	}
+	else { 
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error, ax25_set_addr, bad position %d for '%s'\n", n, ad);
+	}
+
+	//dw_printf ("------\n");
+	//dw_printf ("dump after ax25_set_addr (%d, %s)\n", n, ad);
+	//ax25_hex_dump (this_p);
+	//dw_printf ("------\n");
 }
 
 
@@ -864,12 +810,17 @@ void ax25_insert_addr (packet_t this_p, int n, char *ad)
 {
 	int k;
 	int ssid_temp, heard_temp;
+	char atemp[AX25_MAX_ADDR_LEN];
+	int i;
+	int expect;
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
 	assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS);
 	assert (strlen(ad) < AX25_MAX_ADDR_LEN);
 
+	//dw_printf ("ax25_insert_addr (%d, %s)\n", n, ad);
+
 	/* Don't do it if we already have the maximum number. */
 	/* Should probably return success/fail code but currently the caller doesn't care. */
 
@@ -877,18 +828,33 @@ void ax25_insert_addr (packet_t this_p, int n, char *ad)
 	  return;
 	}
 
-	/* Shift the current occupant and others up. */
-
-	for (k=this_p->num_addr; k>n; k--) {
-	  strcpy (this_p->addrs[k], this_p->addrs[k-1]);
-	  this_p->ssid_etc[k] = this_p->ssid_etc[k-1];
-	}
+	CLEAR_LAST_ADDR_FLAG;
 
 	this_p->num_addr++;
 
-	ax25_parse_addr (ad, 0, this_p->addrs[n], &ssid_temp, &heard_temp);
-	this_p->ssid_etc[n] = SSID_RR_MASK;
+	memmove (this_p->frame_data + (n+1)*7, this_p->frame_data + n*7, this_p->frame_len - (n*7));
+	memset (this_p->frame_data + n*7, ' ' << 1, 6);
+	this_p->frame_len += 7;
+	this_p->frame_data[n*7+6] = SSID_RR_MASK;
+
+	SET_LAST_ADDR_FLAG;
+
+	ax25_parse_addr (ad, 0, atemp, &ssid_temp, &heard_temp);
+	memset (this_p->frame_data + n*7, ' ' << 1, 6);
+	for (i=0; i<6 && atemp[i] != '\0'; i++) {
+	  this_p->frame_data[n*7+i] = atemp[i] << 1;
+	}
+	
 	ax25_set_ssid (this_p, n, ssid_temp);
+
+	// Sanity check after messing with number of addresses.
+
+	expect = this_p->num_addr;
+	this_p->num_addr = (-1);
+	if (expect != ax25_get_num_addr (this_p)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error ax25_remove_addr expect %d, actual %d\n", expect, this_p->num_addr);
+	}
 }
 
 
@@ -914,6 +880,7 @@ void ax25_insert_addr (packet_t this_p, int n, char *ad)
 void ax25_remove_addr (packet_t this_p, int n)
 {
 	int k;
+	int expect; 
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
@@ -921,12 +888,23 @@ void ax25_remove_addr (packet_t this_p, int n)
 
 	/* Shift those beyond to fill this position. */
 
+	CLEAR_LAST_ADDR_FLAG;
+
 	this_p->num_addr--;
 
-	for (k = n; k < this_p->num_addr; k++) {
-	  strcpy (this_p->addrs[k], this_p->addrs[k+1]);
-	  this_p->ssid_etc[k] = this_p->ssid_etc[k+1];
+	memmove (this_p->frame_data + n*7, this_p->frame_data + (n+1)*7, this_p->frame_len - ((n+1)*7));
+	this_p->frame_len -= 7;
+	SET_LAST_ADDR_FLAG;
+
+	// Sanity check after messing with number of addresses.
+
+	expect = this_p->num_addr;
+	this_p->num_addr = (-1);
+	if (expect != ax25_get_num_addr (this_p)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error ax25_remove_addr expect %d, actual %d\n", expect, this_p->num_addr);
 	}
+
 }
 
 
@@ -947,8 +925,38 @@ void ax25_remove_addr (packet_t this_p, int n)
 
 int ax25_get_num_addr (packet_t this_p)
 {
+	unsigned char *pf;
+	int a;
+	int addr_bytes;
+
+
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
+
+/* Use cached value if already set. */
+
+	if (this_p->num_addr >= 0) {
+	  return (this_p->num_addr);
+	}
+
+/* Otherwise, determine the number ofaddresses. */
+
+	this_p->num_addr = 0;		/* Number of addresses extracted. */
+	
+	addr_bytes = 0;
+	for (a = 0; a < this_p->frame_len && addr_bytes == 0; a++) {
+	  if (this_p->frame_data[a] & SSID_LAST_MASK) {
+	    addr_bytes = a + 1;
+	  }
+	}
+
+	if (addr_bytes % 7 == 0) {
+	  int addrs = addr_bytes / 7;
+	  if (addrs >= AX25_MIN_ADDRS && addrs <= AX25_MAX_ADDRS) {
+	    this_p->num_addr = addrs;
+	  }
+	}
+	
 	return (this_p->num_addr);
 }
 
@@ -1004,17 +1012,11 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station)
 {	
 	int ssid;
 	char sstr[4];
+	int i;
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
 
-/*
- * This assert failure popped up once and it is not clear why.
- * Let's print out more information about the situation so we 
- * might have a clue about the root cause.
- * Try to keep going instead of dying at this point.
- */
-	//assert (n >= 0 && n < this_p->num_addr);
 
 	if (n < 0) {
 	  text_color_set(DW_COLOR_ERROR);
@@ -1032,13 +1034,20 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station)
 	  return;
 	}
 
-	strcpy (station, this_p->addrs[n]);
+	memset (station, 0, 7);
+	for (i=0; i<6; i++) {
+	  unsigned char ch;
+
+	  ch = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
+	  if (ch <= ' ') break;
+	  station[i] = ch;
+	}
 
 	ssid = ax25_get_ssid (this_p, n);
 	if (ssid != 0) {
 	  sprintf (sstr, "-%d", ssid);
 	  strcat (station, sstr);
-	}    
+	}   
 }
 
 
@@ -1051,24 +1060,26 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station)
  * Inputs:	n	- Index of address.   Use the symbols 
  *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
  *
- * Warning:	No bounds checking is performed.  Be careful.
- *		  
  * Assumption:	ax25_from_text or ax25_from_frame was called first.
  *
  * Returns:	Substation id, as integer 0 .. 15.
  *
- * Bugs:	Rewrite to keep call and SSID separate internally.
- *
  *------------------------------------------------------------------------------*/
 
 int ax25_get_ssid (packet_t this_p, int n)
 {
-
+	
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
-	assert (n >= 0 && n < this_p->num_addr);
-
-	return ((this_p->ssid_etc[n] & SSID_SSID_MASK) >> SSID_SSID_SHIFT);
+	
+	if (n >= 0 && n < this_p->num_addr) {
+	  return ((this_p->frame_data[n*7+6] & SSID_SSID_MASK) >> SSID_SSID_SHIFT);
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: ax25_get_ssid(%d), num_addr=%d\n", n, this_p->num_addr);
+	  return (0);
+	}
 }
 
 
@@ -1083,8 +1094,6 @@ int ax25_get_ssid (packet_t this_p, int n)
  *
  *		ssid	- New SSID.  Must be in range of 0 to 15.
  *
- * Warning:	No bounds checking is performed.  Be careful.
- *		  
  * Assumption:	ax25_from_text or ax25_from_frame was called first.
  *
  * Bugs:	Rewrite to keep call and SSID separate internally.
@@ -1096,10 +1105,16 @@ void ax25_set_ssid (packet_t this_p, int n, int ssid)
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
-	assert (n >= 0 && n < this_p->num_addr);
 
-	this_p->ssid_etc[n] =   (this_p->ssid_etc[n] & ~ SSID_SSID_MASK) |
+
+	if (n >= 0 && n < this_p->num_addr) {
+	  this_p->frame_data[n*7+6] =   (this_p->frame_data[n*7+6] & ~ SSID_SSID_MASK) |
 		((ssid << SSID_SSID_SHIFT) & SSID_SSID_MASK) ;
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: ax25_set_ssid(%d,%d), num_addr=%d\n", n, ssid, this_p->num_addr);
+	}
 }
 
 
@@ -1127,7 +1142,14 @@ int ax25_get_h (packet_t this_p, int n)
 	assert (this_p->magic2 == MAGIC);
 	assert (n >= 0 && n < this_p->num_addr);
 
-	return ((this_p->ssid_etc[n] & SSID_H_MASK) >> SSID_H_SHIFT);
+	if (n >= 0 && n < this_p->num_addr) {
+	  return ((this_p->frame_data[n*7+6] & SSID_H_MASK) >> SSID_H_SHIFT);
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: ax25_get_h(%d), num_addr=%d\n", n, this_p->num_addr);
+	  return (0);
+	}
 }
 
 
@@ -1153,9 +1175,14 @@ void ax25_set_h (packet_t this_p, int n)
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
-	assert (n >= 0 && n < this_p->num_addr);
 
-	this_p->ssid_etc[n] |= SSID_H_MASK;
+	if (n >= 0 && n < this_p->num_addr) {
+	  this_p->frame_data[n*7+6] |= SSID_H_MASK;
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: ax25_set_hd(%d), num_addr=%d\n", n, this_p->num_addr);
+	}
 }
 
 
@@ -1182,6 +1209,7 @@ int ax25_get_heard(packet_t this_p)
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
+
 	result = AX25_SOURCE;
 
 	for (i = AX25_REPEATER_1; i < ax25_get_num_addr(this_p); i++) {
@@ -1217,6 +1245,7 @@ int ax25_get_first_not_repeated(packet_t this_p)
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
+
 	for (i = AX25_REPEATER_1; i < ax25_get_num_addr(this_p); i++) {
 	
 	  if ( ! ax25_get_h(this_p,i)) {
@@ -1246,18 +1275,36 @@ int ax25_get_first_not_repeated(packet_t this_p)
 
 int ax25_get_info (packet_t this_p, unsigned char **paddr)
 {
+	unsigned char *info_ptr;
+	int info_len;
+
+
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
 
 	if (this_p->num_addr >= 2) {
-	  *paddr = this_p->the_rest + ax25_get_info_offset(this_p);
-	  return (ax25_get_num_info(this_p));
+
+	  /* AX.25 */
+
+	  info_ptr = this_p->frame_data + ax25_get_info_offset(this_p);
+	  info_len = ax25_get_num_info(this_p);
+	}
+	else {
+
+	  /* Not AX.25.  Treat Whole packet as info. */
+
+	  info_ptr = this_p->frame_data;
+	  info_len = this_p->frame_len;
 	}
 
-	/* Not AX.25.  Whole packet is info. */
+	/* Add nul character in case caller treats as printable string. */
+	
+	assert (info_len >= 0);
+
+	info_ptr[info_len] = '\0';
 
-	*paddr = this_p->the_rest;
-	return (this_p->the_rest_len);
+	*paddr = info_ptr;
+	return (info_len);
 }
 
 
@@ -1281,7 +1328,7 @@ int ax25_get_dti (packet_t this_p)
 	assert (this_p->magic2 == MAGIC);
 
 	if (this_p->num_addr >= 2) {
-	  return (this_p->the_rest[ax25_get_info_offset(this_p)]);
+	  return (this_p->frame_data[ax25_get_info_offset(this_p)]);
 	}
 	return (' ');
 }
@@ -1417,51 +1464,175 @@ void ax25_format_addrs (packet_t this_p, char *result)
  *
  * Errors:	Returns -1.
  *
- *
  *------------------------------------------------------------------*/
 
 int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) 
 {
-	int j, k;
-	unsigned char *pout;
-	int len;
 
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
 
-	pout = result;
+	assert (this_p->frame_len > 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
 
-	for (j=0; j<this_p->num_addr; j++) {
+	memcpy (result, this_p->frame_data, this_p->frame_len);
 
-	  char *s;
+	return (this_p->frame_len);
+}
 
-	  memset (pout, ' ' << 1, (size_t)6);
 
-	  s = this_p->addrs[j];
-	  for (k=0; *s != '\0'; k++, s++) {
-	    pout[k] = *s << 1;	    
-	  }
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_hex_dump
+ *
+ * Purpose:	Print out packet in hexadecimal for debugging.
+ *
+ * Inputs:	fptr		- Pointer to frame data.
+ *
+ *		flen		- Frame length, bytes.  Does not include CRC.
+ *		
+ *------------------------------------------------------------------*/
 
-	  if (j == this_p->num_addr - 1) {
-	    pout[6] = this_p->ssid_etc[j] | SSID_LAST_MASK;
+static void hex_dump (unsigned char *p, int len) 
+{
+	int n, i, offset;
+
+	offset = 0;
+	while (len > 0) {
+	  n = len < 16 ? len : 16; 
+	  dw_printf ("  %03x: ", offset);
+	  for (i=0; i<n; i++) {
+	    dw_printf (" %02x", p[i]);
 	  }
-	  else {
-	   pout[6] = this_p->ssid_etc[j] & ~ SSID_LAST_MASK;
+	  for (i=n; i<16; i++) {
+	    dw_printf ("   ");
 	  }
-	  pout += 7;
+	  dw_printf ("  ");
+	  for (i=0; i<n; i++) {
+	    dw_printf ("%c", isprint(p[i]) ? p[i] : '.');
+	  }
+	  dw_printf ("\n");
+	  p += 16;
+	  offset += 16;
+	  len -= 16;
 	}
+}
 
-	memcpy (pout, this_p->the_rest, (size_t)this_p->the_rest_len);
-	pout += this_p->the_rest_len;
+/* Text description of control octet. */
 
-	len = pout - result;
+static void ctrl_to_text (int c, char *out)
+{
+	if      ((c & 1) == 0)       { sprintf (out, "I frame: n(r)=%d, p=%d, n(s)=%d",  (c>>5)&7, (c>>4)&1, (c>>1)&7); }
+	else if ((c & 0xf) == 0x01)  { sprintf (out, "S frame RR: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xf) == 0x05)  { sprintf (out, "S frame RNR: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xf) == 0x09)  { sprintf (out, "S frame REJ: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xf) == 0x0D)  { sprintf (out, "S frame sREJ: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xef) == 0x6f) { sprintf (out, "U frame SABME: p=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x2f) { sprintf (out, "U frame SABM: p=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x43) { sprintf (out, "U frame DISC: p=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x0f) { sprintf (out, "U frame DM: f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x63) { sprintf (out, "U frame UA: f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x87) { sprintf (out, "U frame FRMR: f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x03) { sprintf (out, "U frame UI: p/f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0xAF) { sprintf (out, "U frame XID: p/f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0xe3) { sprintf (out, "U frame TEST: p/f=%d", (c>>4)&1); }
+	else                         { sprintf (out, "Unknown frame type for control = 0x%02x", c); }
+}
+
+/* Text description of protocol id octet. */
 
-	assert (len <= AX25_MAX_PACKET_LEN);
+static void pid_to_text (int p, char *out)
+{
 
-	return (len);
+	if      ((p & 0x30) == 0x10) { sprintf (out, "AX.25 layer 3 implemented."); }
+	else if ((p & 0x30) == 0x20) { sprintf (out, "AX.25 layer 3 implemented."); }
+	else if (p == 0x01)          { sprintf (out, "ISO 8208/CCITT X.25 PLP"); }
+	else if (p == 0x06)          { sprintf (out, "Compressed TCP/IP packet. Van Jacobson (RFC 1144)"); }
+	else if (p == 0x07)          { sprintf (out, "Uncompressed TCP/IP packet. Van Jacobson (RFC 1144)"); }
+	else if (p == 0x08)          { sprintf (out, "Segmentation fragment"); }
+	else if (p == 0xC3)          { sprintf (out, "TEXNET datagram protocol"); }
+	else if (p == 0xC4)          { sprintf (out, "Link Quality Protocol"); }
+	else if (p == 0xCA)          { sprintf (out, "Appletalk"); }
+	else if (p == 0xCB)          { sprintf (out, "Appletalk ARP"); }
+	else if (p == 0xCC)          { sprintf (out, "ARPA Internet Protocol"); }
+	else if (p == 0xCD)          { sprintf (out, "ARPA Address resolution"); }
+	else if (p == 0xCE)          { sprintf (out, "FlexNet"); }
+	else if (p == 0xCF)          { sprintf (out, "NET/ROM"); }
+	else if (p == 0xF0)          { sprintf (out, "No layer 3 protocol implemented."); }
+	else if (p == 0xFF)          { sprintf (out, "Escape character. Next octet contains more Level 3 protocol information."); }
+	else                         { sprintf (out, "Unknown protocol id = 0x%02x", p); }
 }
 
 
+
+void ax25_hex_dump (packet_t this_p) 
+{
+	int n;
+	unsigned char *fptr = this_p->frame_data;
+	int flen = this_p->frame_len;
+
+
+	
+	if (this_p->num_addr >= AX25_MIN_ADDRS && this_p->num_addr <= AX25_MAX_ADDRS) {
+	  int c, p;
+	  char cp_text[120];
+	  char l_text[20];
+
+	  c = fptr[this_p->num_addr*7];
+	  p = fptr[this_p->num_addr*7+1];
+
+	  ctrl_to_text (c, cp_text);
+
+	  if ( (c & 0x01) == 0 ||				/* I   xxxx xxx0 */
+	     	c == 0x03 || c == 0x13) {			/* UI  000x 0011 */
+
+	    char p_text[100];
+
+	    pid_to_text (p, p_text);
+
+	    strcat (cp_text, ", ");
+	    strcat (cp_text, p_text);
+
+	  }
+
+	  sprintf (l_text, ", length = %d", flen);
+	  strcat (cp_text, l_text);
+
+	  dw_printf ("%s\n", cp_text);
+	}
+
+
+	dw_printf (" dest    %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", 
+				fptr[0]>>1, fptr[1]>>1, fptr[2]>>1, fptr[3]>>1, fptr[4]>>1, fptr[5]>>1,
+				(fptr[6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT,
+				(fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT, 
+				(fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT,
+				fptr[6]&SSID_LAST_MASK);
+
+	dw_printf (" source  %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", 
+				fptr[7]>>1, fptr[8]>>1, fptr[9]>>1, fptr[10]>>1, fptr[11]>>1, fptr[12]>>1,
+				(fptr[13]&SSID_SSID_MASK)>>SSID_SSID_SHIFT,
+				(fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT, 
+				(fptr[13]&SSID_RR_MASK)>>SSID_RR_SHIFT,
+				fptr[13]&SSID_LAST_MASK);
+
+	for (n=2; n<this_p->num_addr; n++) {	
+
+	  dw_printf (" digi %d  %c%c%c%c%c%c %2d   h=%d res=%d last=%d\n", 
+				n - 1,
+				fptr[n*7+0]>>1, fptr[n*7+1]>>1, fptr[n*7+2]>>1, fptr[n*7+3]>>1, fptr[n*7+4]>>1, fptr[n*7+5]>>1,
+				(fptr[n*7+6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT,
+				(fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT, 
+				(fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT,
+				fptr[n*7+6]&SSID_LAST_MASK);
+
+	}
+
+	hex_dump (fptr, flen);
+
+} /* end ax25_hex_dump */
+
+
+
 /*------------------------------------------------------------------
  *
  * Function:	ax25_is_aprs
@@ -1486,12 +1657,21 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
 
 int ax25_is_aprs (packet_t this_p) 
 {
+	int ctrl, pid, is_aprs;
+
 	assert (this_p->magic1 == MAGIC);
 	assert (this_p->magic2 == MAGIC);
 
-	return (this_p->num_addr >= 2 &&
-		ax25_get_control(this_p) == AX25_UI_FRAME && 
-		ax25_get_pid(this_p) == AX25_NO_LAYER_3);
+	ctrl = ax25_get_control(this_p);
+	pid = ax25_get_pid(this_p);
+
+	is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_NO_LAYER_3;
+
+#if 0 
+        text_color_set(DW_COLOR_ERROR);
+        dw_printf ("ax25_is_aprs(): ctrl=%02x, pid=%02x, is_aprs=%d\n", ctrl, pid, is_aprs);
+#endif
+	return (is_aprs);
 }
 
 /*------------------------------------------------------------------
@@ -1514,7 +1694,7 @@ int ax25_get_control (packet_t this_p)
 	assert (this_p->magic2 == MAGIC);
 
 	if (this_p->num_addr >= 2) {
-	  return (this_p->the_rest[ax25_get_control_offset(this_p)]);
+	  return (this_p->frame_data[ax25_get_control_offset(this_p)]);
 	}
 	return (-1);
 }
@@ -1530,6 +1710,10 @@ int ax25_get_control (packet_t this_p)
  * Returns:	APRS uses 0xf0 for no layer 3.
  *		This could also be used in other situations.
  *
+ * AX.25:	"The Protocol Identifier (PID) field appears in information
+ *		 frames (I and UI) only. It identifies which kind of
+ *		 Layer 3 protocol, if any, is in use."
+ *
  *------------------------------------------------------------------*/
 
 
@@ -1539,7 +1723,7 @@ int ax25_get_pid (packet_t this_p)
 	assert (this_p->magic2 == MAGIC);
 
 	if (this_p->num_addr >= 2) {
-	  return (this_p->the_rest[ax25_get_pid_offset(this_p)]);
+	  return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
 	}
 	return (-1);
 }
@@ -1717,6 +1901,6 @@ void ax25_safe_print (char *pstr, int len, int ascii_only)
 
 } /* end ax25_safe_print */
 
-/* end ax25_pad.c */
 
 
+/* end ax25_pad.c */
diff --git a/ax25_pad.h b/ax25_pad.h
index 347a394..0611881 100755
--- a/ax25_pad.h
+++ b/ax25_pad.h
@@ -49,7 +49,8 @@
  * #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN)
  */
 
-/* the more general case. */
+/* The more general case. */
+/* An AX.25 frame can have a control byte and no protocol. */
 
 #define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 )
 
@@ -77,20 +78,17 @@ struct packet_s {
 
 	struct packet_s *nextp;	/* Pointer to next in queue. */
 
-	int num_addr;		/* Number of elements used in two below. */
-				/* Range of 0 .. AX25_MAX_ADDRS. */	
+	int num_addr;		/* Number of addresses in frame. */
+				/* Range of AX25_MIN_ADDRS .. AX25_MAX_ADDRS for AX.25. */	
+				/* It will be 0 if it doesn't look like AX.25. */
+				/* -1 is used temporarily at allocation to mean */
+				/* not determined yet. */
 
-	char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
-				/* Contains the address without the ssid. */
-				/* Why is it larger than 7? */
-				/* Messages from an IGate server can have longer */
-				/* addresses after qAC.  Up to 9 observed so far. */
 
-				/* usual human readable form.  e.g.  WB20SZ-15 */
-
-	unsigned char ssid_etc[AX25_MAX_ADDRS];		/*  SSID octet from each address. */
 
 				/* 
+ 				 * The 7th octet of each address contains:
+			         *
 				 * Bits:   H  R  R  SSID  0
 				 *
 				 *   H 		for digipeaters set to 0 intially.
@@ -118,13 +116,12 @@ struct packet_s {
 #define SSID_LAST_MASK	0x01
 
 
-	int the_rest_len;	/* Frame length minus the address part. */
+	int frame_len;		/* Frame length without CRC. */
+
 
-	unsigned char the_rest[2 + 3 + AX25_MAX_INFO_LEN + 1];
-				/* The rest after removing the addresses. */
-				/* Includes control, protocol ID, Information, */
-				/* and throw in one more for a character */
-				/* string nul terminator. */
+	unsigned char frame_data[AX25_MAX_PACKET_LEN+1];
+				/* Raw frame contents, without the CRC. */
+				
 
 	int magic2;		/* Will get stomped on if above overflows. */
 };
@@ -158,7 +155,8 @@ typedef struct packet_s *packet_t;
 
 static inline int ax25_get_control_offset (packet_t this_p) 
 {
-	return (0);
+	//return (0);
+	return (this_p->num_addr*7);
 }
 
 static inline int ax25_get_num_control (packet_t this_p)
@@ -174,7 +172,7 @@ static inline int ax25_get_num_control (packet_t this_p)
 
 static inline int ax25_get_pid_offset (packet_t this_p) 
 {
-	return (ax25_get_num_control(this_p));
+	return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p));
 }
 
 static int ax25_get_num_pid (packet_t this_p)
@@ -182,12 +180,12 @@ static int ax25_get_num_pid (packet_t this_p)
 	int c;
 	int pid;
 
-	c = this_p->the_rest[ax25_get_control_offset(this_p)];
+	c = this_p->frame_data[ax25_get_control_offset(this_p)];
 
 	if ( (c & 0x01) == 0 ||				/* I   xxxx xxx0 */
 	     c == 0x03 || c == 0x13) {			/* UI  000x 0011 */
 
-	  pid = this_p->the_rest[ax25_get_pid_offset(this_p)];
+	  pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
 	  if (pid == 0xff) {
 	    return (2);			/* pid 1111 1111 means another follows. */
 	  }
@@ -210,17 +208,20 @@ static int ax25_get_num_pid (packet_t this_p)
 
 static inline int ax25_get_info_offset (packet_t this_p) 
 {
-	return (ax25_get_num_control(this_p) + ax25_get_num_pid(this_p));
+	return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p));
 }
 
 static int ax25_get_num_info (packet_t this_p)
 {
 	int len;
+	
+	/* assuming AX.25 frame. */
 
-	len = this_p->the_rest_len - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
+	len = this_p->frame_len - this_p->num_addr * 7 - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
 	if (len < 0) {
 	  len = 0;		/* print error? */
 	}
+
 	return (len);
 }
 
@@ -278,6 +279,8 @@ extern void ax25_format_addrs (packet_t pp, char *);
 
 extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
 
+extern void ax25_hex_dump (packet_t this_p);
+
 extern int ax25_is_aprs (packet_t pp);
 
 extern int ax25_get_control (packet_t this_p); 
diff --git a/beacon.c b/beacon.c
index 085dcc8..011f203 100755
--- a/beacon.c
+++ b/beacon.c
@@ -57,6 +57,7 @@
 #include "beacon.h"
 #include "latlong.h"
 #include "dwgps.h"
+#include "log.h"
 
 
 
@@ -83,6 +84,16 @@ static unsigned __stdcall beacon_thread (void *arg);
 static void * beacon_thread (void *arg);
 #endif
 
+static int g_tracker_debug_level = 0;	// 1 for data from gps.
+					// 2 + Smart Beaconing logic.
+					// 3 + Send transmissions to log file.
+
+
+void beacon_tracker_set_debug (int level)
+{
+	g_tracker_debug_level = level;
+}
+
 
 
 /*-------------------------------------------------------------------
@@ -141,7 +152,7 @@ void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi)
  * table entry should be ignored later on.
  */
 	for (j=0; j<g_misc_config_p->num_beacons; j++) {
-	  int chan = g_misc_config_p->beacon[j].chan;
+	  int chan = g_misc_config_p->beacon[j].sendto_chan;
 
 	  if (chan < 0) chan = 0;	/* For IGate, use channel 0 call. */
 
@@ -177,7 +188,7 @@ void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi)
 
 	        case BEACON_TRACKER:
 
-#if defined(GPS_ENABLED) || defined(DEBUG_SIM)
+#if defined(ENABLE_GPS) || defined(DEBUG_SIM)
 		  g_using_gps++;
 #else
 	          text_color_set(DW_COLOR_ERROR);
@@ -322,8 +333,6 @@ void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi)
  *
  *--------------------------------------------------------------------*/
 
-#define KNOTS_TO_MPH 1.150779
-
 #define MIN(x,y) ((x) < (y) ? (x) : (y))
 
 
@@ -360,7 +369,8 @@ static void * beacon_thread (void *arg)
 	float  my_course = 0;		/* degrees */
 	float  my_speed_knots = 0;
 	float  my_speed_mph = 0;
-	float  my_alt = 0;		/* meters */
+	float  my_alt_m = G_UNKNOWN;		/* meters */
+	int    my_alt_ft = G_UNKNOWN;
 
 /*
  * SmartBeaconing state.
@@ -439,15 +449,40 @@ static void * beacon_thread (void *arg)
 	    fprintf (stderr, "Can't read /tmp/cs.\n");
 	  }
 	  fix = 3;
-	  my_speed_mph = KNOTS_TO_MPH * my_speed_knots;
+	  my_speed_mph = DW_KNOTS_TO_MPH(my_speed_knots);
 	  my_lat = 42.99;
 	  my_lon = 71.99;
-	  my_alt = 100;
+	  my_alt_m = 100;
 #else
 	  if (g_using_gps) {
 
-	    fix = dwgps_read (&my_lat, &my_lon, &my_speed_knots, &my_course, &my_alt);
-	    my_speed_mph = KNOTS_TO_MPH * my_speed_knots;
+	    fix = dwgps_read (&my_lat, &my_lon, &my_speed_knots, &my_course, &my_alt_m);
+	    my_speed_mph = DW_KNOTS_TO_MPH(my_speed_knots);
+
+	    if (g_tracker_debug_level >= 1) {
+	      struct tm tm;
+	      char hms[20];
+
+	      localtime_r (&now, &tm);
+	      strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
+	      text_color_set(DW_COLOR_DEBUG);
+	      if (fix == 3) {
+	        dw_printf ("%s  3D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0, %.1f m\n", hms, my_lat, my_lon, my_speed_mph, my_course, my_alt_m);
+	      }
+	      else if (fix == 2) {
+	        dw_printf ("%s  2D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0\n", hms, my_lat, my_lon, my_speed_mph, my_course);
+	      }
+	      else {
+	        dw_printf ("%s  No GPS fix\n", hms);
+	      }
+	    }
+
+	    /* Transmit altitude only if 3D fix and user asked for it. */
+
+	    my_alt_ft = G_UNKNOWN;
+	    if (fix >= 3 && my_alt_m != G_UNKNOWN && g_misc_config_p->beacon[j].alt_m != G_UNKNOWN) {
+	      my_alt_ft = DW_METERS_TO_FEET(my_alt_m);
+	    }
 
 	    /* Don't complain here for no fix. */
 	    /* Possibly at the point where about to transmit. */
@@ -460,14 +495,26 @@ static void * beacon_thread (void *arg)
 	  if (g_misc_config_p->sb_configured && g_using_gps && fix >= 2) {
 
 	    if (my_speed_mph > g_misc_config_p->sb_fast_speed) {
-		sb_every = g_misc_config_p->sb_fast_rate;
+	      sb_every = g_misc_config_p->sb_fast_rate;
+	      if (g_tracker_debug_level >= 2) {
+	      	 text_color_set(DW_COLOR_DEBUG);
+		 dw_printf ("my speed %.1f > fast %d mph, interval = %d sec\n", my_speed_mph, g_misc_config_p->sb_fast_speed, sb_every);
+	      } 
 	    }
 	    else if (my_speed_mph < g_misc_config_p->sb_slow_speed) {
 	      sb_every = g_misc_config_p->sb_slow_rate;
+	      if (g_tracker_debug_level >= 2) {
+	      	 text_color_set(DW_COLOR_DEBUG);
+		 dw_printf ("my speed %.1f < slow %d mph, interval = %d sec\n", my_speed_mph, g_misc_config_p->sb_slow_speed, sb_every);
+	      } 
 	    }
 	    else {
 	      /* Can't divide by 0 assuming sb_slow_speed > 0. */
 	      sb_every = ( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / my_speed_mph;
+	      if (g_tracker_debug_level >= 2) {
+	      	 text_color_set(DW_COLOR_DEBUG);
+		 dw_printf ("my speed %.1f mph, interval = %d sec\n", my_speed_mph, sb_every);
+	      } 
 	    }
 
 #if DEBUG_SIM
@@ -493,6 +540,13 @@ static void * beacon_thread (void *arg)
 	      if (heading_change(my_course, sb_prev_course) > turn_threshold &&
 		  now >= sb_prev_time + g_misc_config_p->sb_turn_time) {
 
+	        if (g_tracker_debug_level >= 2) {
+	      	   text_color_set(DW_COLOR_DEBUG);
+		   dw_printf ("heading change (%.0f, %.0f) > threshold %d and %d since last >= turn time %d\n",
+				my_course, sb_prev_course, turn_threshold, 
+				(int)(now - sb_prev_time), g_misc_config_p->sb_turn_time);
+	        } 
+
 		/* Send it now. */
 	        for (j=0; j<g_misc_config_p->num_beacons; j++) {
                   if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
@@ -517,6 +571,7 @@ static void * beacon_thread (void *arg)
 	      char beacon_text[AX25_MAX_PACKET_LEN];
 	      packet_t pp = NULL;
 	      char mycall[AX25_MAX_ADDR_LEN];
+	      int alt_ft;
 
 /*
  * Obtain source call for the beacon.
@@ -524,16 +579,16 @@ static void * beacon_thread (void *arg)
  * When sending to IGate server, use call from first radio channel.
  *
  * Check added in version 1.0a.  Previously used index of -1.
+ *
+ * Version 1.1 - channel should now be 0 for IGate.  
+ * Type of destination is encoded separately.
  */
 	      strcpy (mycall, "NOCALL");
 
-	      if (g_misc_config_p->beacon[j].chan == -1) {
-		strcpy (mycall, g_digi_config_p->mycall[0]);
-	      }
-	      else {
-		strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].chan]);
-	      }
+	      assert (g_misc_config_p->beacon[j].sendto_chan >= 0);
 
+	      strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].sendto_chan]);
+	      
 	      if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) {
 	        text_color_set(DW_COLOR_ERROR);
 	        dw_printf ("MYCALL not set for beacon in config file line %d.\n", g_misc_config_p->beacon[j].lineno);
@@ -542,13 +597,22 @@ static void * beacon_thread (void *arg)
 
 /* 
  * Prepare the monitor format header. 
+ *
+ * 	src > dest [ , via ]
  */
 
 	      strcpy (beacon_text, mycall);
 	      strcat (beacon_text, ">");
-	      sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
-	      strcat (beacon_text, stemp);
-	      if (g_misc_config_p->beacon[j].via) {
+
+	      if (g_misc_config_p->beacon[j].dest != NULL) {
+	        strcat (beacon_text, g_misc_config_p->beacon[j].dest);
+	      } 
+	      else {
+	         sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
+	         strcat (beacon_text, stemp);
+	      }
+
+	      if (g_misc_config_p->beacon[j].via != NULL) {
 	        strcat (beacon_text, ",");
 	        strcat (beacon_text, g_misc_config_p->beacon[j].via);
 	      }
@@ -561,7 +625,10 @@ static void * beacon_thread (void *arg)
 
 		case BEACON_POSITION:
 
-		  encode_position (g_misc_config_p->beacon[j].compress, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, 
+		  alt_ft = DW_METERS_TO_FEET(g_misc_config_p->beacon[j].alt_m);
+
+		  encode_position (g_misc_config_p->beacon[j].messaging, 
+			g_misc_config_p->beacon[j].compress, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, alt_ft,
 			g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol, 
 			g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
 			0, 0, /* course, speed */	
@@ -594,8 +661,9 @@ static void * beacon_thread (void *arg)
 		    if (coarse == 0) {
 		      coarse = 360;
 		    }
-		    encode_position (g_misc_config_p->beacon[j].compress, 
-			my_lat, my_lon, 
+		    encode_position (g_misc_config_p->beacon[j].messaging, 
+			g_misc_config_p->beacon[j].compress, 
+			my_lat, my_lon, my_alt_ft,
 			g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol, 
 			g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
 			coarse, (int)roundf(my_speed_knots),	
@@ -617,6 +685,34 @@ static void * beacon_thread (void *arg)
 	            else {
 	              g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
 	            }
+
+		    /* Write to log file for testing. */
+		    /* The idea is to run log2gpx and map the result rather than */
+		    /* actually transmitting and relying on someone else to receive */
+		    /* the signals. */
+
+	            if (g_tracker_debug_level >= 3) {
+		
+		      decode_aprs_t A;
+
+		      memset (&A, 0, sizeof(A));
+	  	      A.g_freq   = G_UNKNOWN;
+	  	      A.g_offset = G_UNKNOWN;
+	  	      A.g_tone   = G_UNKNOWN;
+	  	      A.g_dcs    = G_UNKNOWN;
+
+		      strcpy (A.g_src, mycall);
+		      A.g_symbol_table = g_misc_config_p->beacon[j].symtab;
+		      A.g_symbol_code = g_misc_config_p->beacon[j].symbol;
+		      A.g_lat = my_lat;
+		      A.g_lon = my_lon;
+		      A.g_speed = DW_KNOTS_TO_MPH(my_speed_knots);
+		      A.g_course = coarse;
+		      A.g_altitude = my_alt_ft;
+
+		      /* Fake channel of 999 to distinguish from real data. */
+		      log_write (999, &A, NULL, 0, 0);
+		    }
 	 	  }
 	          else {
 		    g_misc_config_p->beacon[j].next = now + 2;
@@ -650,18 +746,33 @@ static void * beacon_thread (void *arg)
 
               if (pp != NULL) {
 
-		/* Send to IGate server or radio. */
+		/* Send to desired destination. */
+
+	        switch (g_misc_config_p->beacon[j].sendto_type) {
+
+	          case SENDTO_IGATE:
+
 
-	        if (g_misc_config_p->beacon[j].chan == -1) {
 #if 1
-	  	  text_color_set(DW_COLOR_XMIT);
-	  	  dw_printf ("[ig] %s\n", beacon_text);
+	  	    text_color_set(DW_COLOR_XMIT);
+	  	    dw_printf ("[ig] %s\n", beacon_text);
 #endif
-		  igate_send_rec_packet (0, pp);
-		  ax25_delete (pp);
-	 	}
-		else {
-	          tq_append (g_misc_config_p->beacon[j].chan, TQ_PRIO_1_LO, pp);
+		    igate_send_rec_packet (0, pp);
+		    ax25_delete (pp);
+	            break;
+
+		  case SENDTO_XMIT:
+		  default:
+
+	            tq_append (g_misc_config_p->beacon[j].sendto_chan, TQ_PRIO_1_LO, pp);
+		    break;
+
+		  case SENDTO_RECV:
+
+		    // TODO:  Put into receive queue rather than calling directly.
+
+		    app_process_rec_packet (g_misc_config_p->beacon[j].sendto_chan, 0, pp, -1, 0, "");
+	            break; 
 		}
 	      }
 	      else {
diff --git a/beacon.h b/beacon.h
index 97f3dd5..609f9a2 100755
--- a/beacon.h
+++ b/beacon.h
@@ -2,3 +2,5 @@
 /* beacon.h */
 
 void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi);
+
+void beacon_tracker_set_debug (int level);
diff --git a/config.c b/config.c
index d3f7eda..c208b7c 100755
--- a/config.c
+++ b/config.c
@@ -54,6 +54,7 @@
 #include "igate.h"
 #include "latlong.h"
 #include "symbols.h"
+#include "LatLong-UTMconversion.h"
 
 
 //#include "tq.h"
@@ -415,6 +416,7 @@ void config_init (char *fname, struct audio_s *p_modem,
 	  strcpy (p_modem->ptt_device[channel], "");
 	  p_modem->ptt_line[channel] = PTT_LINE_RTS;
 	  p_modem->ptt_gpio[channel] = 0;
+	  p_modem->ptt_lpt_bit[channel] = 0;
 	  p_modem->ptt_invert[channel] = 0;
 
 	  p_modem->slottime[channel] = DEFAULT_SLOTTIME;				
@@ -477,6 +479,8 @@ void config_init (char *fname, struct audio_s *p_modem,
 	
 	//strcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM);
 	strcpy (p_misc_config->nullmodem, "");
+	strcpy (p_misc_config->nmea_port, "");
+	strcpy (p_misc_config->logdir, "");
 
 
 /* 
@@ -642,16 +646,28 @@ void config_init (char *fname, struct audio_s *p_modem,
 	      continue;
 	    }
 	    else {
-	      char *p;
 
-	      strncpy (p_digi_config->mycall[channel], t, sizeof(p_digi_config->mycall[channel])-1);
+	      // Definitely set for current channel.
+	      // Set for other channels which have not been set yet.
 
-	      for (p = p_digi_config->mycall[channel]; *p != '\0'; p++) {
-	        if (islower(*p)) {
-		  *p = toupper(*p);	/* silently force upper case. */
+	      int c;
+
+	      for (c = 0; c < MAX_CHANS; c++) {
+
+	        if (c == channel || strlen(p_digi_config->mycall[c]) == 0 || strcasecmp(p_digi_config->mycall[c], "NOCALL") == 0) {
+
+	          char *p;
+
+	          strncpy (p_digi_config->mycall[c], t, sizeof(p_digi_config->mycall[c])-1);
+
+	          for (p = p_digi_config->mycall[c]; *p != '\0'; p++) {
+	            if (islower(*p)) {
+		      *p = toupper(*p);	/* silently force upper case. */
+	            }
+	          }
+	          // TODO: additional checks if valid
 	        }
 	      }
-	      // TODO: additional checks if valid
 	    }
 	  }
 
@@ -898,6 +914,7 @@ void config_init (char *fname, struct audio_s *p_modem,
  *
  * PTT  serial-port [-]rts-or-dtr
  * PTT  GPIO  [-]gpio-num
+ * PTT  LPT  [-]bit-num
  */
 
 	  else if (strcasecmp(t, "PTT") == 0) {
@@ -910,7 +927,60 @@ void config_init (char *fname, struct audio_s *p_modem,
 	      continue;
 	    }
 
-	    if (strcasecmp(t, "GPIO") != 0) {
+	    if (strcasecmp(t, "GPIO") == 0) {
+
+/* GPIO case, Linux only. */
+
+#if __WIN32__
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: PTT with GPIO is only available on Linux.\n", line);
+#else		
+	      t = strtok (NULL, " ,\t\n\r");
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing GPIO number.\n", line);
+	        continue;
+	      }
+
+	      if (*t == '-') {
+	        p_modem->ptt_gpio[channel] = atoi(t+1);
+		p_modem->ptt_invert[channel] = 1;
+	      }
+	      else {
+	        p_modem->ptt_gpio[channel] = atoi(t);
+		p_modem->ptt_invert[channel] = 0;
+	      }
+	      p_modem->ptt_method[channel] = PTT_METHOD_GPIO;
+#endif
+	    }
+	    else if (strcasecmp(t, "LPT") == 0) {
+
+/* Parallel printer case, x86 Linux only. */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+	      t = strtok (NULL, " ,\t\n\r");
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing LPT bit number.\n", line);
+	        continue;
+	      }
+
+	      if (*t == '-') {
+	        p_modem->ptt_lpt_bit[channel] = atoi(t+1);
+		p_modem->ptt_invert[channel] = 1;
+	      }
+	      else {
+	        p_modem->ptt_lpt_bit[channel] = atoi(t);
+		p_modem->ptt_invert[channel] = 0;
+	      }
+	      p_modem->ptt_method[channel] = PTT_METHOD_LPT;
+#else
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: PTT with LPT is only available on x86 Linux.\n", line);
+#endif		
+	    }
+	    else  {
 
 /* serial port case. */
 
@@ -949,34 +1019,7 @@ void config_init (char *fname, struct audio_s *p_modem,
 
 	      p_modem->ptt_method[channel] = PTT_METHOD_SERIAL;
 	    }
-	    else {
-
-/* GPIO case, Linux only. */
-
-// TODO:
-#if 0
-//#if __WIN32__
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file line %d: PTT with GPIO is only available on Linux.\n", line);
-#else		
-	      t = strtok (NULL, " ,\t\n\r");
-	      if (t == NULL) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file line %d: Missing GPIO number.\n", line);
-	        continue;
-	      }
 
-	      if (*t == '-') {
-	        p_modem->ptt_gpio[channel] = atoi(t+1);
-		p_modem->ptt_invert[channel] = 1;
-	      }
-	      else {
-	        p_modem->ptt_gpio[channel] = atoi(t);
-		p_modem->ptt_invert[channel] = 0;
-	      }
-	      p_modem->ptt_method[channel] = PTT_METHOD_GPIO;
-#endif
-	    }
 	  }
 
 
@@ -1193,6 +1236,48 @@ void config_init (char *fname, struct audio_s *p_modem,
    	    }
 	  }
 
+/*
+ * REGEN 		- Signal regeneration.
+ */
+
+	  else if (strcasecmp(t, "regen") == 0) {
+	    int from_chan, to_chan;
+	    //int e;
+	    //char message[100];
+	    	    
+
+	    t = strtok (NULL, " ,\t\n\r");
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
+	      continue;
+	    }
+	    from_chan = atoi(t);
+	    if (from_chan < 0 || from_chan > p_digi_config->num_chans-1) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", 
+							p_digi_config->num_chans-1, line);
+	      continue;
+	    }
+
+	    t = strtok (NULL, " ,\t\n\r");
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
+	      continue;
+	    }
+	    to_chan = atoi(t);
+	    if (to_chan < 0 || to_chan > p_digi_config->num_chans-1) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", 
+							p_digi_config->num_chans-1, line);
+	      continue;
+	    }
+	
+
+	    p_digi_config->regen[from_chan][to_chan] = 1;
+
+	  }
 
 /*
  * ==================== APRStt gateway ==================== 
@@ -2017,6 +2102,36 @@ void config_init (char *fname, struct audio_s *p_modem,
 	  }
 
 /*
+ * NMEA		- Device name for communication with NMEA device.
+ */
+	  else if (strcasecmp(t, "nmea") == 0) {
+	    t = strtok (NULL, " ,\t\n\r");
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing device name for NMEA port on line %d.\n", line);
+	      continue;
+	    }
+	    else {
+	      strncpy (p_misc_config->nmea_port, t, sizeof(p_misc_config->nmea_port)-1);
+	    }
+	  }
+
+/*
+ * LOGDIR	- Directory name for storing log files.  Use "." for current working directory.
+ */
+	  else if (strcasecmp(t, "logdir") == 0) {
+	    t = strtok (NULL, " ,\t\n\r");
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing directory name for LOGDIR on line %d.\n", line);
+	      continue;
+	    }
+	    else {
+	      strncpy (p_misc_config->logdir, t, sizeof(p_misc_config->logdir)-1);
+	    }
+	  }
+
+/*
  * FIX_BITS 		- Attempt to fix frames with bad FCS. 
  */
 
@@ -2029,7 +2144,7 @@ void config_init (char *fname, struct audio_s *p_modem,
 	      continue;
 	    }
 	    n = atoi(t);
-            if (n >= RETRY_NONE && n <= RETRY_TWO_SEP) {
+            if (n >= RETRY_NONE && n <= RETRY_REMOVE_TWO_SEP) {
 	      p_modem->fix_bits = (retry_t)n;
 	    }
 	    else {
@@ -2050,7 +2165,8 @@ void config_init (char *fname, struct audio_s *p_modem,
 	    	    
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf ("Config file, line %d: Old style 'BEACON' has been replaced with new commands.\n", line);
-	    
+	    dw_printf ("Use PBEACON, OBEACON, or CBEACON instead.\n");
+  
 	  }
 
 
@@ -2067,7 +2183,21 @@ void config_init (char *fname, struct audio_s *p_modem,
 		   strcasecmp(t, "OBEACON") == 0 ||
 		   strcasecmp(t, "TBEACON") == 0 ||
 		   strcasecmp(t, "CBEACON") == 0) {
+#if __WIN32__
+	    if (strcasecmp(t, "TBEACON") == 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: TBEACON is available only in Linux version.\n", line);
+	      continue;
+	    }
+#endif
 
+#ifndef ENABLE_GPS
+	    if (strcasecmp(t, "TBEACON") == 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: Rebuild with GPS support for TBEACON to be available.\n", line);
+	      continue;
+	    }
+#endif
 	    if (p_misc_config->num_beacons < MAX_BEACONS) {
 
 	      memset (&(p_misc_config->beacon[p_misc_config->num_beacons]), 0, sizeof(struct beacon_s));
@@ -2201,12 +2331,14 @@ void config_init (char *fname, struct audio_s *p_modem,
 
 	      b = 0;
 	      for (k=0; k<p_misc_config->num_beacons; k++) {
-	        if (p_misc_config->beacon[p_misc_config->num_beacons].chan == j) b++;
+	        if (p_misc_config->beacon[p_misc_config->num_beacons].sendto_chan == j) b++;
 	      }
 	      if (b == 0) {
 	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", i); 
-	        p_digi_config->enabled[i][j] = 0;
+	        dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", i);
+		// It's a recommendation, not a requirement.
+		// Was there some good reason to turn it off in earlier version?
+	        //p_digi_config->enabled[i][j] = 0;	      
 	      }
 	    }
 	  }
@@ -2245,16 +2377,22 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line)
 	int q;
 	char temp_symbol[100];
 	int ok;
-	
+	char zone[8];
+	double easting = G_UNKNOWN;
+	double northing = G_UNKNOWN;
+
 	strcpy (temp_symbol, "");
+	strcpy (zone, "");
 
-	b->chan = 0;
+	b->sendto_type = SENDTO_XMIT;
+	b->sendto_chan = 0;
 	b->delay = 60;
 	b->every = 600;
-	//b->delay = 6;		// TODO: temp. remove
+	//b->delay = 6;		// temp test.
 	//b->every = 3600;
 	b->lat = G_UNKNOWN;
 	b->lon = G_UNKNOWN;
+	b->alt_m = G_UNKNOWN;
 	b->symtab = '/';
 	b->symbol = '-';	/* house */
 
@@ -2353,10 +2491,31 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line)
 	  }
 	  else if (strcasecmp(keyword, "SENDTO") == 0) {
 	    if (value[0] == 'i' || value[0] == 'I') {
-	       b->chan = -1;
+	       b->sendto_type = SENDTO_IGATE;
+	       b->sendto_chan = 0;
+	    }
+	    else if (value[0] == 'r' || value[0] == 'R') {
+	       b->sendto_type = SENDTO_RECV;
+	       b->sendto_chan = atoi(value+1);
+	    }
+	    else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') {
+	       b->sendto_type = SENDTO_XMIT;
+	       b->sendto_chan = atoi(value+1);
 	    }
 	    else {
-	       b->chan = atoi(value);
+	       b->sendto_type = SENDTO_XMIT;
+	       b->sendto_chan = atoi(value);
+	    }
+	  }
+	  else if (strcasecmp(keyword, "DEST") == 0) {
+	    b->dest = strdup(value);
+	    for (p = b->dest; *p != '\0'; p++) {
+	      if (islower(*p)) {
+	        *p = toupper(*p);	/* silently force upper case. */
+	      }
+	    }
+	    if (strlen(b->dest) > 9) {
+	       b->dest[9] = '\0';
 	    }
 	  }
 	  else if (strcasecmp(keyword, "VIA") == 0) {
@@ -2379,6 +2538,18 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line)
 	  else if (strcasecmp(keyword, "LONG") == 0 || strcasecmp(keyword, "LON") == 0) {
 	    b->lon = parse_ll (value, LON, line);
 	  }
+	  else if (strcasecmp(keyword, "ALT") == 0 || strcasecmp(keyword, "ALTITUDE") == 0) {
+	    b->alt_m = atof(value);
+	  }
+	  else if (strcasecmp(keyword, "ZONE") == 0) {
+	    strncpy(zone, value, sizeof(zone));
+	  }
+	  else if (strcasecmp(keyword, "EAST") == 0 || strcasecmp(keyword, "EASTING") == 0) {
+	    easting = atof(value);
+	  }
+	  else if (strcasecmp(keyword, "NORTH") == 0 || strcasecmp(keyword, "NORTHING") == 0) {
+	    northing = atof(value);
+	  }
 	  else if (strcasecmp(keyword, "SYMBOL") == 0) {
 	    /* Defer processing in case overlay appears later. */
 	    strcpy (temp_symbol, value);
@@ -2419,6 +2590,9 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line)
 	  else if (strcasecmp(keyword, "COMPRESS") == 0 || strcasecmp(keyword, "COMPRESSED") == 0) {
 	    b->compress = atoi(value);
 	  }
+	  else if (strcasecmp(keyword, "MESSAGING") == 0) {
+	    b->messaging = atoi(value);
+	  }
 	  else {
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf ("Config file, line %d: Invalid option keyword, %s.\n", line, keyword);
@@ -2427,6 +2601,59 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line)
 	}
 
 /*
+ * Convert UTM coordintes to lat / long.
+ */
+	if (strlen(zone) > 0 || easting != G_UNKNOWN || northing != G_UNKNOWN) {
+
+	  if (strlen(zone) > 0 && easting != G_UNKNOWN && northing != G_UNKNOWN) {
+
+	    int znum;
+	    char *zlet;
+
+            znum = strtoul(zone, &zlet, 10);
+
+            if (znum >= 1 && znum <= 60) {
+
+              //printf ("zlet = %c 0x%02x\n", *zlet, *zlet);
+
+              if (*zlet == '\0' || strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) {
+
+                if (easting >= 0 && easting <= 999999) {
+
+                 if (northing >= 0 && northing <= 9999999) {
+ 
+                    UTMtoLL (WSG84, northing, easting, zone, &b->lat, &b->lon);
+
+                    // printf ("config UTM debug: latitude = %.6f, longitude = %.6f\n", b->lat, b->lon);
+
+		  }
+	          else {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Config file, line %d: Northing value is out of range.\n", line);
+                  }
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Config file, line %d: Easting value is out of range.\n", line);
+                }
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file, line %d: Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n", line);
+              }
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: UTM zone is out of range.\n", line);
+            }
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file, line %d: When any of ZONE, EASTING, NORTHING specifed, they must all be specified.\n", line);
+	  }
+	}
+
+/*
  * Process symbol now that we have any later overlay.
  */
 	if (strlen(temp_symbol) > 0) {
diff --git a/config.h b/config.h
index 8ac91b6..f7f3af4 100755
--- a/config.h
+++ b/config.h
@@ -25,6 +25,9 @@
 
 enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM };
 
+enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV };
+
+
 #define MAX_BEACONS 30
 
 struct misc_config_s {
@@ -40,6 +43,11 @@ struct misc_config_s {
 	char nullmodem[40];	/* Serial port name for our end of the */
 				/* virtual null modem for native Windows apps. */
 
+	char nmea_port[40];	/* Serial port name for NMEA communication with GPS */
+				/* receiver and/or mapping application. */
+
+	char logdir[80];	/* Directory for saving activity logs. */
+
 	int sb_configured;	/* TRUE if SmartBeaconing is configured. */
 	int sb_fast_speed;	/* MPH */
 	int sb_fast_rate;	/* seconds */
@@ -58,7 +66,18 @@ struct misc_config_s {
 
 	  int lineno;		/* Line number from config file for later error messages. */
 
-	  int chan;		/* Send to Channel for transmission. -1 for IGate.  */
+	  enum sendto_type_e sendto_type;
+
+				/* SENDTO_XMIT	- Usually beacons go to a radio transmitter. */
+				/*		  chan, below is the channel number. */
+				/* SENDTO_IGATE	- Send to IGate, probably to announce my position */
+				/* 		  rather than relying on someone else to hear */
+				/* 		  me on the radio and report me. */
+				/* SENDTO_RECV	- Pretend this was heard on the specified */
+				/* 		  radio channel.  Mostly for testing. It is a */
+				/* 		  convenient way to send packets to attached apps. */
+
+	  int sendto_chan;	/* Transmit or simulated receive channel for above.  Should be 0 for IGate. */
 
 	  int delay;		/* Seconds to delay before first transmission. */
 
@@ -68,6 +87,9 @@ struct misc_config_s {
 
 	  time_t next;		/* Unix time to transmit next one. */
 
+	  char *dest;		/* NULL or explicit AX.25 destination to use */
+				/* instead of the software version such as APDW11. */
+
 	  int compress;		/* Use more compact form? */
 
 	  char objname[10];	/* Object name.  Any printable characters. */
@@ -77,8 +99,12 @@ struct misc_config_s {
 	  char *custom_info;	/* Info part for handcrafted custom beacon. */
 				/* Ignore the rest below if this is set. */
 
+	  int messaging;	/* Set messaging attribute for position report. */
+				/* i.e. Data Type Indicator of '=' rather than '!' */
+
 	  double lat;		/* Latitude and longitude. */
 	  double lon;
+	  float alt_m;		/* Altitude in meters. */
 
 	  char symtab;		/* Symbol table: / or \ or overlay character. */
 	  char symbol;		/* Symbol code. */
diff --git a/decode_aprs.c b/decode_aprs.c
index 089ba8b..9cb845d 100755
--- a/decode_aprs.c
+++ b/decode_aprs.c
@@ -28,12 +28,9 @@
  *		This is a fairly complete implementation with error messages
  *		pointing out various specication violations. 
  *
- *
- *
  * Assumptions:	ax25_from_frame() has been called to 
  *		separate the header and information.
  *
- *
  *------------------------------------------------------------------*/
 
 #include <stdio.h>
@@ -58,16 +55,15 @@ char *strsep(char **stringp, const char *delim);
 #include "textcolor.h"
 #include "symbols.h"
 #include "latlong.h"
+//#include "nmea.h"
+#include "decode_aprs.h"
+#include "telemetry.h"
+
 
 #define TRUE 1
 #define FALSE 0
 
 
-#define METERS_TO_FEET(x) ((x) * 3.2808399)
-#define KNOTS_TO_MPH(x) ((x) * 1.15077945)
-#define KM_TO_MILES(x) ((x) * 0.621371192)
-#define MBAR_TO_INHG(x) ((x) * 0.0295333727)
-
 
 /* Position & symbol fields common to several message formats. */
 
@@ -96,188 +92,109 @@ typedef struct {
 	} compressed_position_t;
 
 
-static void print_decoded (void);
-
-static void aprs_ll_pos (unsigned char *, int);
-static void aprs_ll_pos_time (unsigned char *, int);
-static void aprs_raw_nmea (unsigned char *, int);
-static void aprs_mic_e (packet_t, unsigned char *, int);
-//static void aprs_compressed_pos (unsigned char *, int);
-static void aprs_message (unsigned char *, int);
-static void aprs_object (unsigned char *, int);
-static void aprs_item (unsigned char *, int);
-static void aprs_station_capabilities (char *, int);
-static void aprs_status_report (char *, int);
-static void aprs_telemetry (char *, int);
-static void aprs_raw_touch_tone (char *, int);
-static void aprs_morse_code (char *, int);
-static void aprs_positionless_weather_report (unsigned char *, int);
-static void weather_data (char *wdata, int wind_prefix);
-static void aprs_ultimeter (char *, int);
-static void third_party_header (char *, int);
-
-
-static void decode_position (position_t *ppos);
-static void decode_compressed_position (compressed_position_t *ppos);
-
+/* Range of digits for Base 91 representation. */
+
+#define B91_MIN '!'
+#define B91_MAX '{'
+#define isdigit91(c) ((c) >= B91_MIN && (c) <= B91_MAX)
+
+
+//static void print_decoded (decode_aprs_t *A);
+static void aprs_ll_pos (decode_aprs_t *A, unsigned char *, int);
+static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *, int);
+static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *, int);
+static void aprs_mic_e (decode_aprs_t *A, packet_t, unsigned char *, int);
+//static void aprs_compressed_pos (decode_aprs_t *A, unsigned char *, int);
+static void aprs_message (decode_aprs_t *A, unsigned char *, int);
+static void aprs_object (decode_aprs_t *A, unsigned char *, int);
+static void aprs_item (decode_aprs_t *A, unsigned char *, int);
+static void aprs_station_capabilities (decode_aprs_t *A, char *, int);
+static void aprs_status_report (decode_aprs_t *A, char *, int);
+static void aprs_telemetry (decode_aprs_t *A, char *, int);
+static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int);
+static void aprs_morse_code (decode_aprs_t *A, char *, int);
+static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
+static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix);
+static void aprs_ultimeter (decode_aprs_t *A, char *, int);
+static void third_party_header (decode_aprs_t *A, char *, int);
+static void decode_position (decode_aprs_t *A, position_t *ppos);
+static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *ppos);
 static double get_latitude_8 (char *p);
 static double get_longitude_9 (char *p);
-
-static double get_latitude_nmea (char *pstr, char *phemi);
-static double get_longitude_nmea (char *pstr, char *phemi);
-
-static time_t get_timestamp (char *p);
-static int get_maidenhead (char *p);
-
-static int data_extension_comment (char *pdext);
-static void decode_tocall (char *dest);
-//static void get_symbol (char dti, char *src, char *dest);
-static void process_comment (char *pstart, int clen);
-
-
-/*
- * Information extracted from the message.
- */
-
-/* for unknown values. */
-
-//#define G_UNKNOWN -999999
-
-
-static char g_msg_type[30];		/* Message type. */
-
-static char g_symbol_table;		/* The Symbol Table Identifier character selects one */
-					/* of the two Symbol Tables, or it may be used as */
-					/* single-character (alpha or numeric) overlay, as follows: */
-					
-					/*	/ 	Primary Symbol Table (mostly stations) */
-
-					/* 	\ 	Alternate Symbol Table (mostly Objects) */
-
-					/*	0-9 	Numeric overlay. Symbol from Alternate Symbol */
-					/*		Table (uncompressed lat/long data format) */
-
-					/*	a-j	Numeric overlay. Symbol from Alternate */
-					/*		Symbol Table (compressed lat/long data */
-					/*		format only). i.e. a-j maps to 0-9 */
-
-					/*	A-Z	Alpha overlay. Symbol from Alternate Symbol Table */
+static time_t get_timestamp (decode_aprs_t *A, char *p);
+static int get_maidenhead (decode_aprs_t *A, char *p);
+static int data_extension_comment (decode_aprs_t *A, char *pdext);
+static void decode_tocall (decode_aprs_t *A, char *dest);
+//static void get_symbol (decode_aprs_t *A, char dti, char *src, char *dest);
+static void process_comment (decode_aprs_t *A, char *pstart, int clen);
 
 
-static char g_symbol_code;		/* Where the Symbol Table Identifier is 0-9 or A-Z (or a-j */
-					/* with compressed position data only), the symbol comes from */
-					/* the Alternate Symbol Table, and is overlaid with the */
-					/* identifier (as a single digit or a capital letter). */
-
-static double g_lat, g_lon;		/* Location, degrees.  Negative for South or West. */
-					/* Set to G_UNKNOWN if missing or error. */
-
-static char g_maidenhead[9];		/* 4 or 6 (or 8?) character maidenhead locator. */
-
-static char g_name[20];			/* Object or item name. */
-
-static float g_speed;			/* Speed in MPH.  */
-
-static float g_course;			/* 0 = North, 90 = East, etc. */
-	
-static int g_power;			/* Transmitter power in watts. */
-
-static int g_height;			/* Antenna height above average terrain, feet. */
-
-static int g_gain;			/* Antenna gain in dB. */
-
-static char g_directivity[10];		/* Direction of max signal strength */
-
-static float g_range;			/* Precomputed radio range in miles. */
-
-static float g_altitude;		/* Feet above median sea level.  */
-
-static char g_mfr[80];			/* Manufacturer or application. */
-
-static char g_mic_e_status[30];		/* MIC-E message. */
-
-static char g_freq[40];			/* Frequency, tone, xmit offset */
-
-static char g_comment[256];		/* Comment. */
 
 /*------------------------------------------------------------------
  *
  * Function:	decode_aprs
  *
- * Purpose:	Optionally print packet then decode it.
- *
- * Inputs:	src	- Source Station.
- *
- *			  The SSID is used as a last resort for the
- *			  displayed symbol if not specified in any other way.
- *
- *		dest	- Destination Station.
- *
- *			  Certain destinations (GPSxxx, SPCxxx, SYMxxx) can
- *			  be used to specify the display symbol.
- *			  For the MIC-E format (used by Kenwood D7, D700), the
- *			  "destination" is really the latitude.
+ * Purpose:	Split APRS packet into separate properties that it contains.
  *
- *		pinfo 	- pointer to information field.
- *		info_len - length of the information field.
+ * Inputs:	pp	- APRS packet object.
  *
- * Outputs:	Variables above:
- *
- *			g_symbol_table, g_symbol_code,
+ * Outputs:	A->	g_symbol_table, g_symbol_code,
  *			g_lat, g_lon, 
  *			g_speed, g_course, g_altitude,
  *			g_comment
- *			... and others...
- *
- *		Other functions are then called to retrieve the information.
+ *			... and many others...
  *
- * Bug:		This is not thread-safe because it uses static data and strtok.
+ * Major Revisions: 1.1	Reorganized so parts are returned in a structure.
+ *			Print function is now called separately.
  *
  *------------------------------------------------------------------*/
 
-void decode_aprs (packet_t pp)
+void decode_aprs (decode_aprs_t *A, packet_t pp)
 {
-	//int naddr;
-	//int err;
-	char src[AX25_MAX_ADDR_LEN], dest[AX25_MAX_ADDR_LEN];
-	//char *p;
-	//int ssid;
+
+	char dest[AX25_MAX_ADDR_LEN];
 	unsigned char *pinfo;
 	int info_len;
 
 
   	info_len = ax25_get_info (pp, &pinfo);
 
-	sprintf (g_msg_type, "Unknown message type %c", *pinfo);
+	memset (A, 0, sizeof (*A));
 
-	g_symbol_table = '/';
-	g_symbol_code = ' ';		/* What should we have for default? */
+	sprintf (A->g_msg_type, "Unknown message type %c", *pinfo);
 
-	g_lat = G_UNKNOWN;
-	g_lon = G_UNKNOWN;
-	strcpy (g_maidenhead, "");
+	A->g_symbol_table = '/';		/* Default to primary table. */
+	A->g_symbol_code = ' ';		/* What should we have for default symbol? */
 
-	strcpy (g_name, "");
-	g_speed = G_UNKNOWN;
-	g_course = G_UNKNOWN;
+	A->g_lat = G_UNKNOWN;
+	A->g_lon = G_UNKNOWN;
+	//strcpy (A->g_maidenhead, "");
 
-	g_power = G_UNKNOWN;
-	g_height = G_UNKNOWN;
-	g_gain = G_UNKNOWN;
-	strcpy (g_directivity, "");
+	//strcpy (A->g_name, "");
+	A->g_speed = G_UNKNOWN;
+	A->g_course = G_UNKNOWN;
 
-	g_range = G_UNKNOWN;
-	g_altitude = G_UNKNOWN;
-	strcpy(g_mfr, "");
-	strcpy(g_mic_e_status, "");
-	strcpy(g_freq, "");
-	strcpy (g_comment, "");
+	A->g_power = G_UNKNOWN;
+	A->g_height = G_UNKNOWN;
+	A->g_gain = G_UNKNOWN;
+	//strcpy (A->g_directivity, "");
+
+	A->g_range = G_UNKNOWN;
+	A->g_altitude = G_UNKNOWN;
+	//strcpy(A->g_mfr, "");
+	//strcpy(A->g_mic_e_status, "");
+	A->g_freq = G_UNKNOWN;
+	A->g_tone = G_UNKNOWN;
+	A->g_dcs = G_UNKNOWN;
+	A->g_offset = G_UNKNOWN;
+	//strcpy (A->g_weather, "");
+	//strcpy (A->g_comment, "");
 
 /*
  * Extract source and destination including the SSID.
  */
 	
-	ax25_get_addr_with_ssid (pp, AX25_SOURCE, src);
+	ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
 	ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
 
 
@@ -290,11 +207,11 @@ void decode_aprs (packet_t pp)
 
 	      if (strncmp((char*)pinfo, "!!", 2) == 0)
 	      {
-		aprs_ultimeter ((char*)pinfo, info_len);
+		aprs_ultimeter (A, (char*)pinfo, info_len);
 	      }
 	      else
 	      {	     
-	        aprs_ll_pos (pinfo, info_len);
+	        aprs_ll_pos (A, pinfo, info_len);
 	      }
 	      break;
 
@@ -307,75 +224,75 @@ void decode_aprs (packet_t pp)
 		
 	      if (strncmp((char*)pinfo, "$ULTW", 5) == 0)
 	      {
-		aprs_ultimeter ((char*)pinfo, info_len);
+		aprs_ultimeter (A, (char*)pinfo, info_len);
 	      }
 	      else
 	      {
-	        aprs_raw_nmea (pinfo, info_len);
+	        aprs_raw_nmea (A, pinfo, info_len);
 	      }
 	      break;
 
 	    case '\'':		/* Old Mic-E Data (but Current data for TM-D700) */
 	    case '`':		/* Current Mic-E Data (not used in TM-D700) */
 
-	      aprs_mic_e (pp, pinfo, info_len);
+	      aprs_mic_e (A, pp, pinfo, info_len);
 	      break;
 
 	    case ')':		/* Item. */
 
-	      aprs_item (pinfo, info_len);
+	      aprs_item (A, pinfo, info_len);
 	      break;
 		
 	    case '/':		/* Position with timestamp (no APRS messaging) */
 	    case '@':		/* Position with timestamp (with APRS messaging) */
 
-	      aprs_ll_pos_time (pinfo, info_len);
+	      aprs_ll_pos_time (A, pinfo, info_len);
 	      break;
 
 
 	    case ':':		/* Message */
 
-	      aprs_message (pinfo, info_len);
+	      aprs_message (A, pinfo, info_len);
 	      break;
 
 	    case ';':		/* Object */
 
-	      aprs_object (pinfo, info_len);
+	      aprs_object (A, pinfo, info_len);
 	      break;
 
 	    case '<':		/* Station Capabilities */
 
-	      aprs_station_capabilities ((char*)pinfo, info_len);
+	      aprs_station_capabilities (A, (char*)pinfo, info_len);
 	      break;
 
 	    case '>':		/* Status Report */
 
-	      aprs_status_report ((char*)pinfo, info_len);
+	      aprs_status_report (A, (char*)pinfo, info_len);
 	      break;
 
 	    //case '?':		/* Query */
 	      //break;
 		
 	    case 'T':		/* Telemetry */
-	      aprs_telemetry ((char*)pinfo, info_len);
+	      aprs_telemetry (A, (char*)pinfo, info_len);
 	      break;
 
 	    case '_':		/* Positionless Weather Report */
 
-	      aprs_positionless_weather_report (pinfo, info_len);
+	      aprs_positionless_weather_report (A, pinfo, info_len);
 	      break;
 
 	    case '{':		/* user defined data */
 				/* http://www.aprs.org/aprs11/expfmts.txt */
 
 	      if (strncmp((char*)pinfo, "{tt", 3) == 0) {
-	        aprs_raw_touch_tone (pinfo, info_len);
+	        aprs_raw_touch_tone (A, (char*)pinfo, info_len);
 	      }
 	      else if (strncmp((char*)pinfo, "{mc", 3) == 0) {
-	        aprs_morse_code ((char*)pinfo, info_len);
+	        aprs_morse_code (A, (char*)pinfo, info_len);
 	      }
 	      else {
-	        //aprs_user_defined (pinfo, info_len);
+	        //aprs_user_defined (A, pinfo, info_len);
 	      }
 	      break;
 
@@ -384,7 +301,7 @@ void decode_aprs (packet_t pp)
 				/* to an application that might want to interpret them. */
 				/* Might move into user defined data, above. */
 
-	      aprs_raw_touch_tone ((char*)pinfo, info_len);
+	      aprs_raw_touch_tone (A, (char*)pinfo, info_len);
 	      break;
 
 	    case 'm':		/* Morse Code data - NOT PART OF STANDARD */
@@ -393,12 +310,12 @@ void decode_aprs (packet_t pp)
 				/* other uses such as CW ID for station. */
 				/* Might move into user defined data, above. */
 
-	      aprs_morse_code ((char*)pinfo, info_len);
+	      aprs_morse_code (A, (char*)pinfo, info_len);
 	      break;
 
 	    case '}':		/* third party header */
 
-	      third_party_header ((char*)pinfo, info_len);
+	      third_party_header (A, (char*)pinfo, info_len);
 	      break;
 
 
@@ -417,9 +334,9 @@ void decode_aprs (packet_t pp)
  * Look in other locations if not found in information field.
  */
 
-	if (g_symbol_table == ' ' || g_symbol_code == ' ') {
+	if (A->g_symbol_table == ' ' || A->g_symbol_code == ' ') {
 
-	  symbols_from_dest_or_src (*pinfo, src, dest, &g_symbol_table, &g_symbol_code);
+	  symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
 	}
 
 /*
@@ -434,21 +351,17 @@ void decode_aprs (packet_t pp)
 	    break;
 
 	  default:
-	    decode_tocall (dest);
+	    decode_tocall (A, dest);
 	    break;
 	}
 	
-/*
- * Print it all out in human readable format.
- */
-	print_decoded ();
-}
+} /* end decode_aprs */
 
 
-static void print_decoded (void) {
+void decode_aprs_print (decode_aprs_t *A) {
 
 	char stemp[200];
-	char tmp2[2];
+	//char tmp2[2];
 	double absll;
 	char news;
 	int deg;
@@ -467,40 +380,40 @@ static void print_decoded (void) {
  * - mic-e status
  * - power/height/gain, range
  */
-	strcpy (stemp, g_msg_type);
+	strcpy (stemp, A->g_msg_type);
 
-	if (strlen(g_name) > 0) {
+	if (strlen(A->g_name) > 0) {
 	  strcat (stemp, ", \"");
-	  strcat (stemp, g_name);
+	  strcat (stemp, A->g_name);
 	  strcat (stemp, "\"");
 	}
 
-	symbols_get_description (g_symbol_table, g_symbol_code, symbol_description);	
+	symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description);	
 	strcat (stemp, ", ");
 	strcat (stemp, symbol_description);
 
-	if (strlen(g_mfr) > 0) {
+	if (strlen(A->g_mfr) > 0) {
 	  strcat (stemp, ", ");
-	  strcat (stemp, g_mfr);
+	  strcat (stemp, A->g_mfr);
 	}
 
-	if (strlen(g_mic_e_status) > 0) {
+	if (strlen(A->g_mic_e_status) > 0) {
 	  strcat (stemp, ", ");
-	  strcat (stemp, g_mic_e_status);
+	  strcat (stemp, A->g_mic_e_status);
 	}
 
 
-	if (g_power > 0) {
+	if (A->g_power > 0) {
 	  char phg[100];
 
-	  sprintf (phg, ", %d W height=%d %ddBi %s", g_power, g_height, g_gain, g_directivity);
+	  sprintf (phg, ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity);
 	  strcat (stemp, phg);
 	}
 
-	if (g_range > 0) {
+	if (A->g_range > 0) {
 	  char rng[100];
 
-	  sprintf (rng, ", range=%.1f", g_range);
+	  sprintf (rng, ", range=%.1f", A->g_range);
 	  strcat (stemp, rng);
 	}
 	text_color_set(DW_COLOR_DECODED);
@@ -526,45 +439,45 @@ static void print_decoded (void) {
  * Bug: This does not check for invalid values.
  */
 
-	if (strlen(g_maidenhead) > 0) {
-	  dw_printf("Grid square = %s, ", g_maidenhead);
+	if (strlen(A->g_maidenhead) > 0) {
+	  dw_printf("Grid square = %s, ", A->g_maidenhead);
 
-	  if (g_lat == G_UNKNOWN && g_lon == G_UNKNOWN) {
+	  if (A->g_lat == G_UNKNOWN && A->g_lon == G_UNKNOWN) {
 	
-	    g_lon = (toupper(g_maidenhead[0]) - 'A') * 20 - 180;
-	    g_lat = (toupper(g_maidenhead[1]) - 'A') * 10 - 90;
+	    A->g_lon = (toupper(A->g_maidenhead[0]) - 'A') * 20 - 180;
+	    A->g_lat = (toupper(A->g_maidenhead[1]) - 'A') * 10 - 90;
 
-	    g_lon += (g_maidenhead[2] - '0') * 2;
-	    g_lat += (g_maidenhead[3] - '0');
+	    A->g_lon += (A->g_maidenhead[2] - '0') * 2;
+	    A->g_lat += (A->g_maidenhead[3] - '0');
 
-	    if (strlen(g_maidenhead) >=6) {
-	      g_lon += (toupper(g_maidenhead[4]) - 'A') * 5.0 / 60.0;
-	      g_lat += (toupper(g_maidenhead[5]) - 'A') * 2.5 / 60.0;
+	    if (strlen(A->g_maidenhead) >=6) {
+	      A->g_lon += (toupper(A->g_maidenhead[4]) - 'A') * 5.0 / 60.0;
+	      A->g_lat += (toupper(A->g_maidenhead[5]) - 'A') * 2.5 / 60.0;
 
-	      g_lon += 2.5 / 60.0;	/* Move from corner to center of square */
-	      g_lat += 1.25 / 60.0;
+	      A->g_lon += 2.5 / 60.0;	/* Move from corner to center of square */
+	      A->g_lat += 1.25 / 60.0;
 	    }
 	    else {
-	      g_lon += 1.0;	/* Move from corner to center of square */
-	      g_lat += 0.5;
+	      A->g_lon += 1.0;	/* Move from corner to center of square */
+	      A->g_lat += 0.5;
 	    }
 	  }
 	}
 
 	strcpy (stemp, "");
 
-	if (g_lat != G_UNKNOWN || g_lon != G_UNKNOWN) {
+	if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) {
 
 // Have location but it is posible one part is invalid.
 
-	  if (g_lat != G_UNKNOWN) {
+	  if (A->g_lat != G_UNKNOWN) {
   
-	    if (g_lat >= 0) {
-	      absll = g_lat;
+	    if (A->g_lat >= 0) {
+	      absll = A->g_lat;
 	      news = 'N';
 	    }
 	    else {
-	      absll = - g_lat;
+	      absll = - A->g_lat;
 	      news = 'S';
 	    }
 	    deg = (int) absll;
@@ -575,14 +488,14 @@ static void print_decoded (void) {
 	    strcpy (s_lat, "Invalid Latitude");
 	  }
 
-	  if (g_lon != G_UNKNOWN) {
+	  if (A->g_lon != G_UNKNOWN) {
 
-	    if (g_lon >= 0) {
-	      absll = g_lon;
+	    if (A->g_lon >= 0) {
+	      absll = A->g_lon;
 	      news = 'E';
 	    }
 	    else {
-	      absll = - g_lon;
+	      absll = - A->g_lon;
 	      news = 'W';
 	    }
 	    deg = (int) absll;
@@ -596,35 +509,73 @@ static void print_decoded (void) {
 	  sprintf (stemp, "%s, %s", s_lat, s_lon);
 	}
 
-	if (g_speed != G_UNKNOWN) {
+	if (strlen(A->g_aprstt_loc) > 0) {
+	  if (strlen(stemp) > 0) strcat (stemp, ", ");
+	  strcat (stemp, A->g_aprstt_loc);
+	};
+
+	if (A->g_speed != G_UNKNOWN) {
 	  char spd[20];
 
 	  if (strlen(stemp) > 0) strcat (stemp, ", ");
-	  sprintf (spd, "%.0f MPH", g_speed);
+	  sprintf (spd, "%.0f MPH", A->g_speed);
 	  strcat (stemp, spd);
 	};
 
-	if (g_course != G_UNKNOWN) {
+	if (A->g_course != G_UNKNOWN) {
 	  char cse[20];
 
 	  if (strlen(stemp) > 0) strcat (stemp, ", ");
-	  sprintf (cse, "course %.0f", g_course);
+	  sprintf (cse, "course %.0f", A->g_course);
 	  strcat (stemp, cse);
 	};
 
-	if (g_altitude != G_UNKNOWN) {
+	if (A->g_altitude != G_UNKNOWN) {
 	  char alt[20];
 
 	  if (strlen(stemp) > 0) strcat (stemp, ", ");
-	  sprintf (alt, "alt %.0f ft", g_altitude);
+	  sprintf (alt, "alt %.0f ft", A->g_altitude);
 	  strcat (stemp, alt);
 	};
 
-	if (strlen(g_freq) > 0) {
-	  strcat (stemp, ", ");
-	  strcat (stemp, g_freq);
+	if (A->g_freq != G_UNKNOWN) {
+	  char ftemp[30];
+
+	  sprintf (ftemp, ", %.3f MHz", A->g_freq);
+	  strcat (stemp, ftemp);
 	}
 
+	if (A->g_offset != G_UNKNOWN) {
+	  char ftemp[30];
+
+	  if (A->g_offset % 1000 == 0) {
+	    sprintf (ftemp, ", %+dM", A->g_offset/1000);
+	  }
+	  else {
+	    sprintf (ftemp, ", %+dk", A->g_offset);
+	  }
+	  strcat (stemp, ftemp);
+	}
+
+	if (A->g_tone != G_UNKNOWN) {
+	  if (A->g_tone == 0) {
+	    strcat (stemp, ", no PL");
+	  }
+	  else {
+	    char ftemp[30];
+
+	    sprintf (ftemp, ", PL %.1f", A->g_tone);
+	    strcat (stemp, ftemp);
+	  }
+	}
+
+	if (A->g_dcs != G_UNKNOWN) {
+
+	  char ftemp[30];
+
+	  sprintf (ftemp, ", DCS %03o", A->g_dcs);
+	  strcat (stemp, ftemp);
+	}
 
 	if (strlen (stemp) > 0) {
 	  text_color_set(DW_COLOR_DECODED);
@@ -633,28 +584,52 @@ static void print_decoded (void) {
 
 
 /*
- * Third line has:
- * - comment or weather
+ * Finally, any weather and/or comment.
  *
  * Non-printable characters are changed to safe hexadecimal representations.
  * For example, carriage return is displayed as <0x0d>.
  *
- * Drop annoying trailing CR LF.  Anyone who cares can see it in the raw data.
+ * Drop annoying trailing CR LF.  Anyone who cares can see it in the raw datA->
  */
 
-	n = strlen(g_comment);
-	if (n >= 1 && g_comment[n-1] == '\n') {
-	  g_comment[n-1] = '\0';
+	n = strlen(A->g_weather);
+	if (n >= 1 && A->g_weather[n-1] == '\n') {
+	  A->g_weather[n-1] = '\0';
 	  n--;
 	}
-	if (n >= 1 && g_comment[n-1] == '\r') {
-	  g_comment[n-1] = '\0';
+	if (n >= 1 && A->g_weather[n-1] == '\r') {
+	  A->g_weather[n-1] = '\0';
+	  n--;
+	}
+	if (n > 0) {
+	  //int j;
+
+	  ax25_safe_print (A->g_weather, -1, 0);
+	  dw_printf("\n");
+	}
+
+
+	if (strlen(A->g_telemetry) > 0) {
+	  //int j;
+
+	  ax25_safe_print (A->g_telemetry, -1, 0);
+	  dw_printf("\n");
+	}
+
+
+	n = strlen(A->g_comment);
+	if (n >= 1 && A->g_comment[n-1] == '\n') {
+	  A->g_comment[n-1] = '\0';
+	  n--;
+	}
+	if (n >= 1 && A->g_comment[n-1] == '\r') {
+	  A->g_comment[n-1] = '\0';
 	  n--;
 	}
 	if (n > 0) {
 	  int j;
 
-	  ax25_safe_print (g_comment, -1, 0);
+	  ax25_safe_print (A->g_comment, -1, 0);
 	  dw_printf("\n");
 
 /*
@@ -665,14 +640,14 @@ static void print_decoded (void) {
  * To be part of a valid UTF-8 sequence, it would need to be followed by 10xxxxxx.
  */
 	  for (j=0; j<n; j++) {
-	    if ((unsigned)g_comment[j] == (char)0xb0 &&  (j == 0 || ! (g_comment[j-1] & 0x80))) {
+	    if ((unsigned)A->g_comment[j] == (char)0xb0 &&  (j == 0 || ! (A->g_comment[j-1] & 0x80))) {
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n");
 	      dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
 	    }	    	
 	  }
 	  for (j=0; j<n; j++) {
-	    if ((unsigned)g_comment[j] == (char)0xf8 && (j == n-1 || (g_comment[j+1] & 0xc0) != 0xc0)) {
+	    if ((unsigned)A->g_comment[j] == (char)0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) {
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n");
 	      dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");	    	
@@ -694,7 +669,7 @@ static void print_decoded (void) {
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude.
+ * Outputs:	A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed, A->g_course, A->g_altitude.
  *
  * Description:	Type identifier '=' has APRS messaging.
  *		Type identifier '!' does not have APRS messaging.
@@ -713,7 +688,7 @@ static void print_decoded (void) {
  *
  *------------------------------------------------------------------*/
 
-static void aprs_ll_pos (unsigned char *info, int ilen) 
+static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 
 	struct aprs_ll_pos_s {
@@ -729,46 +704,46 @@ static void aprs_ll_pos (unsigned char *info, int ilen)
 	} *q;
 
 
-	strcpy (g_msg_type, "Position");
+	strcpy (A->g_msg_type, "Position");
 
 	p = (struct aprs_ll_pos_s *)info;
 	q = (struct aprs_compressed_pos_s *)info;
 	
 	if (isdigit((unsigned char)(p->pos.lat[0]))) 	/* Human-readable location. */
         {
-	  decode_position (&(p->pos));
+	  decode_position (A, &(p->pos));
 
-	  if (g_symbol_code == '_') {
+	  if (A->g_symbol_code == '_') {
 	    /* Symbol code indidates it is a weather report. */
 	    /* In this case, we expect 7 byte "data extension" */
 	    /* for the wind direction and speed. */
 
-	    strcpy (g_msg_type, "Weather Report");
-	    weather_data (p->comment, TRUE);
+	    strcpy (A->g_msg_type, "Weather Report");
+	    weather_data (A, p->comment, TRUE);
 	  } 
 	  else {
 	    /* Regular position report. */
 
-	    data_extension_comment (p->comment);
+	    data_extension_comment (A, p->comment);
 	  }
 	}
 	else					/* Compressed location. */
 	{
-	  decode_compressed_position (&(q->cpos));
+	  decode_compressed_position (A, &(q->cpos));
 
-	  if (g_symbol_code == '_') {
+	  if (A->g_symbol_code == '_') {
 	    /* Symbol code indidates it is a weather report. */
 	    /* In this case, the wind direction and speed are in the */
 	    /* compressed data so we don't expect a 7 byte "data */
 	    /* extension" for them. */
 
-	    strcpy (g_msg_type, "Weather Report");
-	    weather_data (q->comment, FALSE);
+	    strcpy (A->g_msg_type, "Weather Report");
+	    weather_data (A, q->comment, FALSE);
 	  } 
 	  else {
 	    /* Regular position report. */
 
-	    process_comment (q->comment, -1);
+	    process_comment (A, q->comment, -1);
 	  }
 	}
 
@@ -790,7 +765,7 @@ static void aprs_ll_pos (unsigned char *info, int ilen)
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude.
+ * Outputs:	A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed, A->g_course, A->g_altitude.
  *
  * Description:	Type identifier '@' has APRS messaging.
  *		Type identifier '/' does not have APRS messaging.
@@ -814,7 +789,7 @@ static void aprs_ll_pos (unsigned char *info, int ilen)
 
 
 
-static void aprs_ll_pos_time (unsigned char *info, int ilen) 
+static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 
 	struct aprs_ll_pos_time_s {
@@ -832,7 +807,7 @@ static void aprs_ll_pos_time (unsigned char *info, int ilen)
 	} *q;
 
 
-	strcpy (g_msg_type, "Position with time");
+	strcpy (A->g_msg_type, "Position with time");
 
 	time_t ts = 0;
 
@@ -843,42 +818,42 @@ static void aprs_ll_pos_time (unsigned char *info, int ilen)
 
 	if (isdigit((unsigned char)(p->pos.lat[0]))) 		/* Human-readable location. */
         {
-	  ts = get_timestamp (p->time_stamp);
-	  decode_position (&(p->pos));
+	  ts = get_timestamp (A, p->time_stamp);
+	  decode_position (A, &(p->pos));
 
-	  if (g_symbol_code == '_') {
+	  if (A->g_symbol_code == '_') {
 	    /* Symbol code indidates it is a weather report. */
 	    /* In this case, we expect 7 byte "data extension" */
 	    /* for the wind direction and speed. */
 
-	    strcpy (g_msg_type, "Weather Report");
-	    weather_data (p->comment, TRUE);
+	    strcpy (A->g_msg_type, "Weather Report");
+	    weather_data (A, p->comment, TRUE);
 	  } 
 	  else {
 	    /* Regular position report. */
 
-	    data_extension_comment (p->comment);
+	    data_extension_comment (A, p->comment);
 	  }
 	}
 	else					/* Compressed location. */
 	{
-	  ts = get_timestamp (p->time_stamp);
+	  ts = get_timestamp (A, p->time_stamp);
 
-	  decode_compressed_position (&(q->cpos));
+	  decode_compressed_position (A, &(q->cpos));
 
-	  if (g_symbol_code == '_') {
+	  if (A->g_symbol_code == '_') {
 	    /* Symbol code indidates it is a weather report. */
 	    /* In this case, the wind direction and speed are in the */
 	    /* compressed data so we don't expect a 7 byte "data */
 	    /* extension" for them. */
 
-	    strcpy (g_msg_type, "Weather Report");
-	    weather_data (q->comment, FALSE);
+	    strcpy (A->g_msg_type, "Weather Report");
+	    weather_data (A, q->comment, FALSE);
 	  } 
 	  else {
 	    /* Regular position report. */
 
-	    process_comment (q->comment, -1);
+	    process_comment (A, q->comment, -1);
 	  }
 	}
 
@@ -943,14 +918,14 @@ static void nmea_checksum (char *sent)
         *p = '\0';      // Remove the checksum.
 }
 
-static void aprs_raw_nmea (unsigned char *info, int ilen) 
+static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 	char stemp[256];
 	char *ptype;
 	char *next;
 
 
-	strcpy (g_msg_type, "Raw NMEA");
+	strcpy (A->g_msg_type, "Raw NMEA");
 
 	strncpy (stemp, (char *)info, ilen);
 	stemp[ilen] = '\0';
@@ -988,13 +963,13 @@ static void aprs_raw_nmea (unsigned char *info, int ilen)
 	  /* Process time??? */
 
 	  if (plat != NULL && strlen(plat) > 0) {
-	    g_lat = get_latitude_nmea(plat, pns);
+	    A->g_lat = latitude_from_nmea(plat, pns);
 	  }
 	  if (plon != NULL && strlen(plon) > 0) {
-	    g_lon = get_longitude_nmea(plon, pew);
+	    A->g_lon = longitude_from_nmea(plon, pew);
 	  }
 	  if (paltitude != NULL && strlen(paltitude) > 0) {
-	    g_altitude = METERS_TO_FEET(atof(paltitude));
+	    A->g_altitude = DW_METERS_TO_FEET(atof(paltitude));
 	  }
 	}
 	else if (strcmp(ptype, "$GPGLL") == 0)
@@ -1012,10 +987,10 @@ static void aprs_raw_nmea (unsigned char *info, int ilen)
 	  pew = strsep(&next, ",");
 
 	  if (plat != NULL && strlen(plat) > 0) {
-	    g_lat = get_latitude_nmea(plat, pns);
+	    A->g_lat = latitude_from_nmea(plat, pns);
 	  }
 	  if (plon != NULL && strlen(plon) > 0) {
-	    g_lon = get_longitude_nmea(plon, pew);
+	    A->g_lon = longitude_from_nmea(plon, pew);
 	  }
 
 	}
@@ -1049,16 +1024,16 @@ static void aprs_raw_nmea (unsigned char *info, int ilen)
 	  /* process time ??? date ??? */
 
 	  if (plat != NULL && strlen(plat) > 0) {
-	    g_lat = get_latitude_nmea(plat, pns);
+	    A->g_lat = latitude_from_nmea(plat, pns);
 	  }
 	  if (plon != NULL && strlen(plon) > 0) {
-	    g_lon = get_longitude_nmea(plon, pew);
+	    A->g_lon = longitude_from_nmea(plon, pew);
 	  }
 	  if (pknots != NULL && strlen(pknots) > 0) {
-	    g_speed = KNOTS_TO_MPH(atof(pknots));
+	    A->g_speed = DW_KNOTS_TO_MPH(atof(pknots));
 	  }
 	  if (pcourse != NULL && strlen(pcourse) > 0) {
-	    g_course = atof(pcourse);
+	    A->g_course = atof(pcourse);
 	  }
 	}
 	else if (strcmp(ptype, "$GPVTG") == 0)
@@ -1088,10 +1063,10 @@ static void aprs_raw_nmea (unsigned char *info, int ilen)
 	  pmode	 = strsep(&next, ",");	
 
 	  if (pknots != NULL && strlen(pknots) > 0) {
-	    g_speed = KNOTS_TO_MPH(atof(pknots));
+	    A->g_speed = DW_KNOTS_TO_MPH(atof(pknots));
 	  }
 	  if (ptcourse != NULL && strlen(ptcourse) > 0) {
-	    g_course = atof(ptcourse);
+	    A->g_course = atof(ptcourse);
 	  }
 
 	}
@@ -1113,10 +1088,10 @@ static void aprs_raw_nmea (unsigned char *info, int ilen)
 	  pident = strsep(&next, ",");
 
 	  if (plat != NULL && strlen(plat) > 0) {
-	    g_lat = get_latitude_nmea(plat, pns);
+	    A->g_lat = latitude_from_nmea(plat, pns);
 	  }
 	  if (plon != NULL && strlen(plon) > 0) {
-	    g_lon = get_longitude_nmea(plon, pew);
+	    A->g_lon = longitude_from_nmea(plon, pew);
 	  }
 
 	  /* do something with identifier? */
@@ -1168,6 +1143,65 @@ static void aprs_raw_nmea (unsigned char *info, int ilen)
  *
  *------------------------------------------------------------------*/
 
+/* a few test cases
+
+# example from http://www.aprs.org/aprs12/mic-e-examples.txt produces 4 errors.
+# TODO:  Analyze all the bits someday and possibly report problem with document.
+
+N0CALL>ABCDEF:'abc123R/text
+
+# Let's use an actual valid location and concentrate on the manufacturers
+# as listed in http://www.aprs.org/aprs12/mic-e-types.txt
+
+N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Jeff Mobile_%
+
+N1ZZN-9>T2SP0W:`c_Vm6hk/ "49}Originl Mic-E (leading space)
+
+N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D7A walkie Talkie
+N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D72 walkie Talkie=
+N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D700 MObile Radio
+N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D710 Mobile Radio=
+
+# Note: next line has trailing space character after _
+
+N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu VX-8_ 
+N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FTM-350_"
+N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu VX-8G_#
+N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FT1D_$
+N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FTM-400DR_%
+
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack3|3
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack4|4
+
+# The next group starts with metacharacter "T" which can be any of space > ] ` '
+# But space is for original Mic-E, # > and ] are for Kenwood, 
+# so ` ' would probably be less ambigous choices but any appear to be valid.
+
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Hamhud\9
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Argent/9
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}HinzTec anyfrog^9
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO*9
+N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}OTHER~9
+
+
+# TODO:  Why is manufacturer unknown?  Should we explicitly say unknown?
+
+[0] VE2VL-9>TU3V0P,VE2PCQ-3,WIDE1,W1UWS-1,UNCAN,WIDE2*:`eB?l")v/"3y}
+MIC-E, VAN, En Route
+
+[0] VE2VL-9>TU3U5Q,VE2PCQ-3,WIDE1,W1UWS-1,N1NCI-3,WIDE2*:`eBgl"$v/"42}73 de Julien, Tinytrak 3
+MIC-E, VAN, En Route
+
+[0] W1ERB-9>T1SW8P,KB1AEV-15,N1NCI-3,WIDE2*:`dI8l!#j/"3m}
+MIC-E, JEEP, In Service
+
+[0] W1ERB-9>T1SW8Q,KB1AEV-15,N1NCI-3,WIDE2*:`dI6l{^j/"4+}IntheJeep..try146.79(PVRA)
+"146.79" in comment looks like a frequency in non-standard format.
+For most systems to recognize it, use exactly this form "146.790MHz" at beginning of comment.
+MIC-E, JEEP, In Service
+
+*/
+
 static int mic_e_digit (char c, int mask, int *std_msg, int *cust_msg)
 {
 
@@ -1210,7 +1244,7 @@ static int mic_e_digit (char c, int mask, int *std_msg, int *cust_msg)
 }
 
 
-static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen) 
+static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int ilen) 
 {
 	struct aprs_mic_e_s {
 	  char dti;			/* ' or ` */
@@ -1230,7 +1264,7 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 	const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" }; 
 	unsigned char *pfirst, *plast;
 
-	strcpy (g_msg_type, "MIC-E");
+	strcpy (A->g_msg_type, "MIC-E");
 
 	p = (struct aprs_mic_e_s *)info;
 
@@ -1239,7 +1273,7 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 
 	ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
 
-	g_lat = mic_e_digit(dest[0], 4, &std_msg, &cust_msg) * 10 + 
+	A->g_lat = mic_e_digit(dest[0], 4, &std_msg, &cust_msg) * 10 + 
 		mic_e_digit(dest[1], 2, &std_msg, &cust_msg) +
 		(mic_e_digit(dest[2], 1, &std_msg, &cust_msg) * 1000 + 
 		 mic_e_digit(dest[3], 0, &std_msg, &cust_msg) * 100 + 
@@ -1251,7 +1285,7 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 
 	if ((dest[3] >= '0' && dest[3] <= '9') || dest[3] == 'L') {
 	  /* South */
-	  g_lat = ( - g_lat);
+	  A->g_lat = ( - A->g_lat);
 	}
 	else if (dest[3] >= 'P' && dest[3] <= 'Z') 
 	{
@@ -1292,28 +1326,28 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 
 	if (offset && ch >= 118 && ch <= 127) 
 	{
-	    g_lon = ch - 118;			/* 0 - 9 degrees */
+	    A->g_lon = ch - 118;			/* 0 - 9 degrees */
 	}
 	else if ( ! offset && ch >= 38 && ch <= 127)
 	{
-	    g_lon = (ch - 38) + 10;		/* 10 - 99 degrees */
+	    A->g_lon = (ch - 38) + 10;		/* 10 - 99 degrees */
 	}
 	else if (offset && ch >= 108 && ch <= 117)
 	{
-	    g_lon = (ch - 108) + 100;		/* 100 - 109 degrees */
+	    A->g_lon = (ch - 108) + 100;		/* 100 - 109 degrees */
 	}
 	else if (offset && ch >= 38 && ch <= 107)
 	{
-	    g_lon = (ch - 38) + 110;		/* 110 - 179 degrees */
+	    A->g_lon = (ch - 38) + 110;		/* 110 - 179 degrees */
 	}
 	else 
 	{
-	    g_lon = G_UNKNOWN;
+	    A->g_lon = G_UNKNOWN;
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf("Invalid character 0x%02x for MIC-E Longitude Degrees.\n", ch);
 	}
 
-/* Second character of information field is g_longitude minutes. */
+/* Second character of information field is A->g_longitude minutes. */
 /* These are all printable characters. */
 
 /* 
@@ -1330,20 +1364,20 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
  * or anything else to corrupt the message.
  */
 
-	if (g_lon != G_UNKNOWN) 
+	if (A->g_lon != G_UNKNOWN) 
 	{
 	  ch = p->lon[1];
 
 	  if (ch >= 88 && ch <= 97)
 	  {
-	    g_lon += (ch - 88) / 60.0;	/* 0 - 9 minutes*/
+	    A->g_lon += (ch - 88) / 60.0;	/* 0 - 9 minutes*/
 	  }
 	  else if (ch >= 38 && ch <= 87)
 	  {
-    	    g_lon += ((ch - 38) + 10) / 60.0;	/* 10 - 59 minutes */
+    	    A->g_lon += ((ch - 38) + 10) / 60.0;	/* 10 - 59 minutes */
 	  }
 	  else {
-	    g_lon = G_UNKNOWN;
+	    A->g_lon = G_UNKNOWN;
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf("Invalid character 0x%02x for MIC-E Longitude Minutes.\n", ch);
 	  }
@@ -1352,16 +1386,16 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 /* There are 100 possible values, from 0 to 99. */
 /* Note that the range includes 4 unprintable control characters and DEL. */
 
-	  if (g_lon != G_UNKNOWN) 
+	  if (A->g_lon != G_UNKNOWN) 
 	  {
 	    ch = p->lon[2];
 
 	    if (ch >= 28 && ch <= 127) 
 	    {
-	      g_lon += ((ch - 28) + 0) / 6000.0;	/* 0 - 99 hundredths of minutes*/
+	      A->g_lon += ((ch - 28) + 0) / 6000.0;	/* 0 - 99 hundredths of minutes*/
 	    }
 	    else {
-	      g_lon = G_UNKNOWN;
+	      A->g_lon = G_UNKNOWN;
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf("Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes.\n", ch);
 	    }
@@ -1370,14 +1404,23 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 
 /* 6th character of destintation indicates east / west. */
 
+/*
+ * Example of apparently invalid encoding.  6th character missing.
+ *
+ * [0] KB1HOZ-9>TTRW5,KQ1L-2,WIDE1,KQ1L-8,UNCAN,WIDE2*:`aFo"]|k/]"4m}<0x0d>
+ * Invalid character "Invalid MIC-E E/W encoding in 6th character of destination.
+ * MIC-E, truck, Kenwood TM-D700, Off Duty
+ * N 44 27.5000, E 069 42.8300, 76 MPH, course 196, alt 282 ft
+ */
+
 	if ((dest[5] >= '0' && dest[5] <= '9') || dest[5] == 'L') {
 	  /* East */
 	}
 	else if (dest[5] >= 'P' && dest[5] <= 'Z') 
 	{
 	  /* West */
-	  if (g_lon != G_UNKNOWN) {
-	    g_lon = ( - g_lon);
+	  if (A->g_lon != G_UNKNOWN) {
+	    A->g_lon = ( - A->g_lon);
 	  }
 	}
 	else 
@@ -1388,30 +1431,30 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 
 /* Symbol table and codes like everyone else. */
 
-	g_symbol_table = p->sym_table_id;
-	g_symbol_code = p->symbol_code;
+	A->g_symbol_table = p->sym_table_id;
+	A->g_symbol_code = p->symbol_code;
 
-	if (g_symbol_table != '/' && g_symbol_table != '\\' 
-		&& ! isupper(g_symbol_table) && ! isdigit(g_symbol_table))
+	if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
+		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
 	{
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n");	
-	  g_symbol_table = '/';
+	  A->g_symbol_table = '/';
 	}
 
 /* Message type from two 3-bit codes. */
 
 	if (std_msg == 0 && cust_msg == 0) {
-	  strcpy (g_mic_e_status, "Emergency");
+	  strcpy (A->g_mic_e_status, "Emergency");
 	}
 	else if (std_msg == 0 && cust_msg != 0) {
-	  strcpy (g_mic_e_status, cust_text[cust_msg]);
+	  strcpy (A->g_mic_e_status, cust_text[cust_msg]);
 	}
 	else if (std_msg != 0 && cust_msg == 0) {
-	  strcpy (g_mic_e_status, std_text[std_msg]);
+	  strcpy (A->g_mic_e_status, std_text[std_msg]);
 	}
 	else {
-	  strcpy (g_mic_e_status, "Unknown MIC-E Message Type");
+	  strcpy (A->g_mic_e_status, "Unknown MIC-E Message Type");
 	}
 
 /* Speed and course from next 3 bytes. */
@@ -1419,7 +1462,7 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 	n = ((p->speed_course[0] - 28) * 10) + ((p->speed_course[1] - 28) / 10);
 	if (n >= 800) n -= 800;
 
-	g_speed = KNOTS_TO_MPH(n); 
+	A->g_speed = DW_KNOTS_TO_MPH(n); 
 
 	n = ((p->speed_course[1] - 28) % 10) * 100 + (p->speed_course[2] - 28);
 	if (n >= 400) n -= 400;
@@ -1428,47 +1471,62 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 	/* Convert to 0 - 360 and reserved value for unknown. */
 
 	if (n == 0) 
-	  g_course = G_UNKNOWN;
+	  A->g_course = G_UNKNOWN;
 	else if (n == 360)
-	  g_course = 0;
+	  A->g_course = 0;
 	else
-	  g_course = n;
+	  A->g_course = n;
 
 
 /* Now try to pick out manufacturer and other optional items. */
 /* The telemetry field, in the original spec, is no longer used. */
   
+	strcpy (A->g_mfr, "Unknown manufacturer");
+
 	pfirst = info + sizeof(struct aprs_mic_e_s);
 	plast = info + ilen - 1;
 
 /* Carriage return character at the end is not mentioned in spec. */
 /* Remove if found because it messes up extraction of manufacturer. */
+/* Don't drop trailing space because that is used for Yaesu VX-8. */
+/* As I recall, the IGate function trims trailing spaces.  */
+/* That would be bad for this particular model. Maybe I'm mistaken? */
+
 
 	if (*plast == '\r') plast--;
 
-	if (*pfirst == ' ' || *pfirst == '>' || *pfirst == ']' || *pfirst == '`' || *pfirst == '\'') {
+#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
+
+
+	if (isT(*pfirst)) {
 	
-	  if (*pfirst == ' ') { strcpy (g_mfr, "Original MIC-E"); pfirst++; }
+	  if (*pfirst == ' ') { strcpy (A->g_mfr, "Original MIC-E"); pfirst++; }
+
+	  else if (*pfirst == '>' && *plast == '=') { strcpy (A->g_mfr, "Kenwood TH-D72"); pfirst++; plast--; }
+	  else if (*pfirst == '>') { strcpy (A->g_mfr, "Kenwood TH-D7A"); pfirst++; }
+
+	  else if (*pfirst == ']' && *plast == '=') { strcpy (A->g_mfr, "Kenwood TM-D710"); pfirst++; plast--; }
+	  else if (*pfirst == ']') { strcpy (A->g_mfr, "Kenwood TM-D700"); pfirst++; }
 
-	  else if (*pfirst == '>' && *plast == '=') { strcpy (g_mfr, "Kenwood TH-D72"); pfirst++; plast--; }
-	  else if (*pfirst == '>') { strcpy (g_mfr, "Kenwood TH-D7A"); pfirst++; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strcpy (A->g_mfr, "Yaesu VX-8"); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strcpy (A->g_mfr, "Yaesu FTM-350"); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strcpy (A->g_mfr, "Yaesu VX-8G"); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strcpy (A->g_mfr, "Yaesu FT1D"); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strcpy (A->g_mfr, "Yaesu FTM-400DR"); pfirst++; plast-=2; }
 
-	  else if (*pfirst == ']' && *plast == '=') { strcpy (g_mfr, "Kenwood TM-D710"); pfirst++; plast--; }
-	  else if (*pfirst == ']') { strcpy (g_mfr, "Kenwood TM-D700"); pfirst++; }
+	  else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strcpy (A->g_mfr, "Byonics TinyTrack3"); pfirst++; plast-=2; }
+	  else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strcpy (A->g_mfr, "Byonics TinyTrack4"); pfirst++; plast-=2; }
 
-	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strcpy (g_mfr, "Yaesu VX-8"); pfirst++; plast-=2; }
-	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strcpy (g_mfr, "Yaesu FTM-350"); pfirst++; plast-=2; }
-	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strcpy (g_mfr, "Yaesu VX-8G"); pfirst++; plast-=2; }
-	  else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strcpy (g_mfr, "Byonics TinyTrack3"); pfirst++; plast-=2; }
-	  else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strcpy (g_mfr, "Byonics TinyTrack4"); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '\\') { strcpy (A->g_mfr, "Hamhud ?"); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '/') { strcpy (A->g_mfr, "Argent ?"); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '^') { strcpy (A->g_mfr, "HinzTec anyfrog"); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '*') { strcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '~') { strcpy (A->g_mfr, "OTHER"); pfirst++; plast-=2; }
 
-	  else if (*(plast-1) == '\\') { strcpy (g_mfr, "Hamhud ?"); pfirst++; plast-=2; }
-	  else if (*(plast-1) == '/') { strcpy (g_mfr, "Argent ?"); pfirst++; plast-=2; }
-	  else if (*(plast-1) == '^') { strcpy (g_mfr, "HinzTec anyfrog"); pfirst++; plast-=2; }
-	  else if (*(plast-1) == '~') { strcpy (g_mfr, "OTHER"); pfirst++; plast-=2; }
+	  // Should Original Mic-E and Kenwood be moved down to here?
 
-	  else if (*pfirst == '`') { strcpy (g_mfr, "Mic-Emsg"); pfirst++; plast-=2; }
-	  else if (*pfirst == '\'') { strcpy (g_mfr, "McTrackr"); pfirst++; plast-=2; }
+	  else if (*pfirst == '`') { strcpy (A->g_mfr, "Mic-Emsg"); pfirst++; plast-=2; }
+	  else if (*pfirst == '\'') { strcpy (A->g_mfr, "McTrackr"); pfirst++; plast-=2; }
 	}
 
 /*
@@ -1494,22 +1552,20 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
 
 	if (plast > pfirst && pfirst[3] == '}') {
 
-	  g_altitude = METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000);
+	  A->g_altitude = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000);
 
-	  if (pfirst[0] < '!' || pfirst[0] > '{' ||
-	      pfirst[1] < '!' || pfirst[1] > '{' ||
-	      pfirst[2] < '!' || pfirst[2] > '{' ) 
+	  if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2])) 
 	  {
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf("Invalid character in MIC-E altitude.  Must be in range of '!' to '{'.\n");
-	    dw_printf("Bogus altitude of %.0f changed to unknown.\n", g_altitude);
-	    g_altitude = G_UNKNOWN;
+	    dw_printf("Bogus altitude of %.0f changed to unknown.\n", A->g_altitude);
+	    A->g_altitude = G_UNKNOWN;
 	  }
 	  
 	  pfirst += 4;
 	}
 
-	process_comment ((char*)pfirst, (int)(plast - pfirst) + 1);
+	process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1);
 
 }
 
@@ -1537,7 +1593,7 @@ static void aprs_mic_e (packet_t pp, unsigned char *info, int ilen)
  *
  *------------------------------------------------------------------*/
 
-static void aprs_message (unsigned char *info, int ilen) 
+static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 
 	struct aprs_message_s {
@@ -1548,14 +1604,55 @@ static void aprs_message (unsigned char *info, int ilen)
 					/* { followed by 1-5 characters for message number */
 	} *p;
 
+	char addressee[AX25_MAX_ADDR_LEN];
+	int i;
+
 
 	p = (struct aprs_message_s *)info;
 
-	sprintf (g_msg_type, "APRS Message for \"%9.9s\"", p->addressee);
+	memset (addressee, 0, sizeof(addressee));
+	strncpy (addressee, p->addressee, sizeof(p->addressee));
 
-	/* No location so don't use  process_comment () */
+	/* Trim trailing spaces. */
+	i = strlen(addressee) - 1;
+	while (i >= 0 && addressee[i] == ' ') {
+	  addressee[i--] = '\0';
+	}
 
-	strcpy (g_comment, p->message);
+/*
+ * Special message formats contain telemetry metadata.
+ * It applies to the addressee, not the sender.
+ * Makes no sense to me that it would not apply to sender instead.
+ * Wouldn't the sender be describing his own data?
+ * 
+ * I also don't understand the reasoning for putting this in a "message."
+ * Telemetry data always starts with "#" after the "T" data type indicator.
+ * Why not use other characters after the "T" for metadata?
+ */
+
+	if (strncmp(p->message,"PARM.",5) == 0) {
+	  sprintf (A->g_msg_type, "Telemetry Parameter Name Message for \"%s\"", addressee);
+	  telemetry_name_message (addressee, p->message+5);
+	}
+	else if (strncmp(p->message,"UNIT.",5) == 0) {
+	  sprintf (A->g_msg_type, "Telemetry Unit/Label Message for \"%s\"", addressee);
+	  telemetry_unit_label_message (addressee, p->message+5);
+	}
+	else if (strncmp(p->message,"EQNS.",5) == 0) {
+	  sprintf (A->g_msg_type, "Telemetry Equation Coefficents Message for \"%s\"", addressee);
+	  telemetry_coefficents_message (addressee, p->message+5);
+	}
+	else if (strncmp(p->message,"BITS.",5) == 0) {
+	  sprintf (A->g_msg_type, "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
+	  telemetry_bit_sense_message (addressee, p->message+5);
+	}
+	else {
+	  sprintf (A->g_msg_type, "APRS Message for \"%s\"", addressee);
+
+	  /* No location so don't use  process_comment () */
+
+	  strcpy (A->g_comment, p->message);
+	}
 
 }
 
@@ -1570,7 +1667,7 @@ static void aprs_message (unsigned char *info, int ilen)
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_object_name, g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude.
+ * Outputs:	A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed, A->g_course, A->g_altitude.
  *
  * Description:	Message has a 9 character object name which could be quite different than
  *		the source station.
@@ -1585,7 +1682,7 @@ static void aprs_message (unsigned char *info, int ilen)
  *
  *------------------------------------------------------------------*/
 
-static void aprs_object (unsigned char *info, int ilen) 
+static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 
 	struct aprs_object_s {
@@ -1614,57 +1711,57 @@ static void aprs_object (unsigned char *info, int ilen)
 	p = (struct aprs_object_s *)info;
 	q = (struct aprs_compressed_object_s *)info;
 
-	strncpy (g_name, p->name, 9);
-	g_name[9] = '\0';
-	i = strlen(g_name) - 1;
-	while (i >= 0 && g_name[i] == ' ') {
-	  g_name[i--] = '\0';
+	strncpy (A->g_name, p->name, 9);
+	A->g_name[9] = '\0';
+	i = strlen(A->g_name) - 1;
+	while (i >= 0 && A->g_name[i] == ' ') {
+	  A->g_name[i--] = '\0';
 	}
 
 	if (p->live_killed == '*')
-	  strcpy (g_msg_type, "Object");
+	  strcpy (A->g_msg_type, "Object");
 	else if (p->live_killed == '_')
-	  strcpy (g_msg_type, "Killed Object");
+	  strcpy (A->g_msg_type, "Killed Object");
 	else
-	  strcpy (g_msg_type, "Object - invalid live/killed");
+	  strcpy (A->g_msg_type, "Object - invalid live/killed");
 
-	ts = get_timestamp (p->time_stamp);
+	ts = get_timestamp (A, p->time_stamp);
 
 	if (isdigit((unsigned char)(p->pos.lat[0]))) 	/* Human-readable location. */
         {
-	  decode_position (&(p->pos));
+	  decode_position (A, &(p->pos));
 
-	  if (g_symbol_code == '_') {
+	  if (A->g_symbol_code == '_') {
 	    /* Symbol code indidates it is a weather report. */
 	    /* In this case, we expect 7 byte "data extension" */
 	    /* for the wind direction and speed. */
 
-	    strcpy (g_msg_type, "Weather Report with Object");
-	    weather_data (p->comment, TRUE);
+	    strcpy (A->g_msg_type, "Weather Report with Object");
+	    weather_data (A, p->comment, TRUE);
 	  } 
 	  else {
 	    /* Regular object. */
 
-	    data_extension_comment (p->comment);
+	    data_extension_comment (A, p->comment);
 	  }
 	}
 	else					/* Compressed location. */
 	{
-	  decode_compressed_position (&(q->cpos));
+	  decode_compressed_position (A, &(q->cpos));
 
-	  if (g_symbol_code == '_') {
+	  if (A->g_symbol_code == '_') {
 	    /* Symbol code indidates it is a weather report. */
 	    /* The spec doesn't explicitly mention the combination */
 	    /* of weather report and object with compressed */
 	    /* position. */
 
-	    strcpy (g_msg_type, "Weather Report with Object");
-	    weather_data (q->comment, FALSE);
+	    strcpy (A->g_msg_type, "Weather Report with Object");
+	    weather_data (A, q->comment, FALSE);
 	  } 
 	  else {
 	    /* Regular position report. */
 
-	    process_comment (q->comment, -1);
+	    process_comment (A, q->comment, -1);
 	  }
 	}
 
@@ -1680,7 +1777,7 @@ static void aprs_object (unsigned char *info, int ilen)
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_object_name, g_lat, g_lon, g_symbol_table, g_symbol_code, g_speed, g_course, g_altitude.
+ * Outputs:	A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed, A->g_course, A->g_altitude.
  *
  * Description:	An "item" is very much like an "object" except 
  *
@@ -1692,7 +1789,7 @@ static void aprs_object (unsigned char *info, int ilen)
  *
  *------------------------------------------------------------------*/
 
-static void aprs_item (unsigned char *info, int ilen) 
+static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 
 	struct aprs_item_s {
@@ -1722,34 +1819,34 @@ static void aprs_item (unsigned char *info, int ilen)
 
 	i = 0;
 	while (i < 9 && p->name[i] != '!' && p->name[i] != '_') {
-	  g_name[i] = p->name[i];
+	  A->g_name[i] = p->name[i];
 	  i++;
-	  g_name[i] = '\0';
+	  A->g_name[i] = '\0';
 	}
 
 	if (p->name[i] == '!')
-	  strcpy (g_msg_type, "Item");
+	  strcpy (A->g_msg_type, "Item");
 	else if (p->name[i] == '_')
-	  strcpy (g_msg_type, "Killed Item");
+	  strcpy (A->g_msg_type, "Killed Item");
 	else {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf("Item name too long or not followed by ! or _.\n");
-	  strcpy (g_msg_type, "Object - invalid live/killed");
+	  strcpy (A->g_msg_type, "Object - invalid live/killed");
 	}
 
 	ppos = p->name + i + 1;
  
 	if (isdigit(*ppos)) 		/* Human-readable location. */
         {
-	  decode_position ((position_t*) ppos);
+	  decode_position (A, (position_t*) ppos);
 
-	  data_extension_comment (ppos + sizeof(position_t));
+	  data_extension_comment (A, ppos + sizeof(position_t));
 	}
 	else					/* Compressed location. */
 	{
-	  decode_compressed_position ((compressed_position_t*)ppos);
+	  decode_compressed_position (A, (compressed_position_t*)ppos);
 
-	  process_comment (ppos + sizeof(compressed_position_t), -1);
+	  process_comment (A, ppos + sizeof(compressed_position_t), -1);
 	}
 
 }
@@ -1775,14 +1872,14 @@ static void aprs_item (unsigned char *info, int ilen)
  *
  *------------------------------------------------------------------*/
 
-static void aprs_station_capabilities (char *info, int ilen) 
+static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen) 
 {
 
-	strcpy (g_msg_type, "Station Capabilities");
+	strcpy (A->g_msg_type, "Station Capabilities");
 
 	// 	Is process_comment() applicable?
 
-	strcpy (g_comment, info+1);
+	strcpy (A->g_comment, info+1);
 }
 
 
@@ -1831,7 +1928,7 @@ static void aprs_station_capabilities (char *info, int ilen)
  *	
  *------------------------------------------------------------------*/
 
-static void aprs_status_report (char *info, int ilen) 
+static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) 
 {
 	struct aprs_status_time_s {
 	  char dti;			/* > */
@@ -1863,7 +1960,7 @@ static void aprs_status_report (char *info, int ilen)
 	} *ps;
 
 
-	strcpy (g_msg_type, "Status Report");
+	strcpy (A->g_msg_type, "Status Report");
 
 	pt = (struct aprs_status_time_s *)info;
 	pm4 = (struct aprs_status_m4_s *)info;
@@ -1881,26 +1978,26 @@ static void aprs_status_report (char *info, int ilen)
 	    isdigit(pt->ztime[5]) &&
 	    pt->ztime[6] == 'z') {
 
-	  strcpy (g_comment, pt->comment);
+	  strcpy (A->g_comment, pt->comment);
 	}
 
 /*
  * Do we have format with 6 character Maidenhead locator?
  */
-	else if (get_maidenhead (pm6->mhead6) == 6) {
+	else if (get_maidenhead (A, pm6->mhead6) == 6) {
 
-	  strncpy (g_maidenhead, pm6->mhead6, 6);
-	  g_maidenhead[6] = '\0';
+	  strncpy (A->g_maidenhead, pm6->mhead6, 6);
+	  A->g_maidenhead[6] = '\0';
 
-	  g_symbol_table = pm6->sym_table_id;
-	  g_symbol_code = pm6->symbol_code;
+	  A->g_symbol_table = pm6->sym_table_id;
+	  A->g_symbol_code = pm6->symbol_code;
 
-	  if (g_symbol_table != '/' && g_symbol_table != '\\' 
-		&& ! isupper(g_symbol_table) && ! isdigit(g_symbol_table))
+	  if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
+		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
 	  {
 	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", g_symbol_table);	
-	    g_symbol_table = '/';
+	    dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", A->g_symbol_table);	
+	    A->g_symbol_table = '/';
 	  }
 
 	  if (pm6->space != ' ' && pm6->space != '\0') {
@@ -1908,26 +2005,26 @@ static void aprs_status_report (char *info, int ilen)
 	    dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space);	
 	  }
 
-	  strcpy (g_comment, pm6->comment);
+	  strcpy (A->g_comment, pm6->comment);
 	}
 
 /*
  * Do we have format with 4 character Maidenhead locator?
  */
-	else if (get_maidenhead (pm4->mhead4) == 4) {
+	else if (get_maidenhead (A, pm4->mhead4) == 4) {
 
-	  strncpy (g_maidenhead, pm4->mhead4, 4);
-	  g_maidenhead[4] = '\0';
+	  strncpy (A->g_maidenhead, pm4->mhead4, 4);
+	  A->g_maidenhead[4] = '\0';
 
-	  g_symbol_table = pm4->sym_table_id;
-	  g_symbol_code = pm4->symbol_code;
+	  A->g_symbol_table = pm4->sym_table_id;
+	  A->g_symbol_code = pm4->symbol_code;
 
-	  if (g_symbol_table != '/' && g_symbol_table != '\\' 
-		&& ! isupper(g_symbol_table) && ! isdigit(g_symbol_table))
+	  if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
+		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
 	  {
 	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", g_symbol_table);	
-	    g_symbol_table = '/';
+	    dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", A->g_symbol_table);	
+	    A->g_symbol_table = '/';
 	  }
 
 	  if (pm4->space != ' ' && pm4->space != '\0') {
@@ -1935,14 +2032,14 @@ static void aprs_status_report (char *info, int ilen)
 	    dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space);	
 	  }
 
-	  strcpy (g_comment, pm4->comment);
+	  strcpy (A->g_comment, pm4->comment);
 	}
 
 /*
  * Whole thing is status text.
  */
 	else {
-	  strcpy (g_comment, ps->comment);
+	  strcpy (A->g_comment, ps->comment);
 	}
 
 
@@ -1950,8 +2047,8 @@ static void aprs_status_report (char *info, int ilen)
  * Last 3 characters can represent beam heading and ERP.
  */
 
-	if (strlen(g_comment) >= 3) {
-	  char *hp = g_comment + strlen(g_comment) - 3;
+	if (strlen(A->g_comment) >= 3) {
+	  char *hp = A->g_comment + strlen(A->g_comment) - 3;
 	
 	  if (*hp == '^') {
 
@@ -1972,7 +2069,7 @@ static void aprs_status_report (char *info, int ilen)
 	    }
 
 	// TODO:  put result somewhere.
-	// could use g_directivity and need new variable for erp.
+	// could use A->g_directivity and need new variable for erp.
 
 	    *hp = '\0';
 	  }
@@ -2000,15 +2097,12 @@ static void aprs_status_report (char *info, int ilen)
  *	
  *------------------------------------------------------------------*/
 
-static void aprs_telemetry (char *info, int ilen) 
+static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen) 
 {
 
-	strcpy (g_msg_type, "Telemetry");
+	strcpy (A->g_msg_type, "Telemetry");
 
-	/* It's pretty much human readable already. */
-	/* Just copy the info field. */
-
-	strcpy (g_comment, info);
+	telemetry_data_original (A->g_src, info, A->g_telemetry, A->g_comment);
 
 
 } /* end aprs_telemetry */
@@ -2018,7 +2112,7 @@ static void aprs_telemetry (char *info, int ilen)
  *
  * Function:	aprs_raw_touch_tone
  *
- * Purpose:	Decode raw touch tone data.
+ * Purpose:	Decode raw touch tone datA->
  *
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
@@ -2030,17 +2124,17 @@ static void aprs_telemetry (char *info, int ilen)
  *		
  *------------------------------------------------------------------*/
 
-static void aprs_raw_touch_tone (char *info, int ilen) 
+static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen) 
 {
 
-	strcpy (g_msg_type, "Raw Touch Tone Data");
+	strcpy (A->g_msg_type, "Raw Touch Tone Data");
 
 	/* Just copy the info field without the message type. */
 
 	if (*info == '{') 
-	  strcpy (g_comment, info+3);
+	  strcpy (A->g_comment, info+3);
 	else
-	  strcpy (g_comment, info+1);
+	  strcpy (A->g_comment, info+1);
 
 
 } /* end aprs_raw_touch_tone */
@@ -2061,17 +2155,17 @@ static void aprs_raw_touch_tone (char *info, int ilen)
  *		
  *------------------------------------------------------------------*/
 
-static void aprs_morse_code (char *info, int ilen) 
+static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen) 
 {
 
-	strcpy (g_msg_type, "Morse Code Data");
+	strcpy (A->g_msg_type, "Morse Code Data");
 
 	/* Just copy the info field without the message type. */
 
 	if (*info == '{') 
-	  strcpy (g_comment, info+3);
+	  strcpy (A->g_comment, info+3);
 	else
-	  strcpy (g_comment, info+1);
+	  strcpy (A->g_comment, info+1);
 
 
 } /* end aprs_morse_code */
@@ -2086,7 +2180,7 @@ static void aprs_morse_code (char *info, int ilen)
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_symbol_table, g_symbol_code.
+ * Outputs:	A->g_symbol_table, A->g_symbol_code.
  *
  * Description:	Type identifier '_' is a weather report without a position.
  *
@@ -2094,7 +2188,7 @@ static void aprs_morse_code (char *info, int ilen)
 
 
 
-static void aprs_positionless_weather_report (unsigned char *info, int ilen) 
+static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *info, int ilen) 
 {
 
 	struct aprs_positionless_weather_s {
@@ -2104,16 +2198,16 @@ static void aprs_positionless_weather_report (unsigned char *info, int ilen)
 	} *p;
 
 
-	strcpy (g_msg_type, "Positionless Weather Report");
+	strcpy (A->g_msg_type, "Positionless Weather Report");
 
 	time_t ts = 0;
 
 
 	p = (struct aprs_positionless_weather_s *)info;
 	
-	// not yet implemented for 8 character format // ts = get_timestamp (p->time_stamp);
+	// not yet implemented for 8 character format // ts = get_timestamp (A, p->time_stamp);
 
-	weather_data (p->comment, FALSE);
+	weather_data (A, p->comment, FALSE);
 }
 
 
@@ -2132,17 +2226,17 @@ static void aprs_positionless_weather_report (unsigned char *info, int ilen)
  *				  forgiving in what is accepted.)
  * TODO: call this context instead and have 3 enumerated values.
  *
- * Global In:	g_course	- Wind info for compressed location.
- *		g_speed
+ * Global In:	A->g_course	- Wind info for compressed location.
+ *		A->g_speed
  *
- * Outputs:	g_comment
+ * Outputs:	A->g_weather
  *
  * Description:	Extract weather details and format into a comment.
  *
  *		For human-readable locations, we expect wind direction
  *		and speed in a format like this:  999/999.
  *		For compressed location, this has already been 
- * 		processed and put in g_course and g_speed.
+ * 		processed and put in A->g_course and A->g_speed.
  *		Otherwise, for positionless weather data, the 
  *		wind is in the form c999s999.
  *
@@ -2205,7 +2299,7 @@ static int getwdata (char **wpp, char ch, int dlen, float *val)
 	return (1); 
 }	
 
-static void weather_data (char *wdata, int wind_prefix) 
+static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) 
 {
 	int n;
 	float fval;
@@ -2221,21 +2315,21 @@ static void weather_data (char *wdata, int wind_prefix)
 	    // Fine point:  Officially, should be values of 001-360.
 	    // "000" or "..." or "   " means unknown. 
 	    // In practice we see do see "000" here.
-	    g_course = n;
+	    A->g_course = n;
 	  }
 	  if (sscanf (wp+4, "%3d", &n))
 	  {
-	    g_speed = KNOTS_TO_MPH(n);  /* yes, in knots */
+	    A->g_speed = DW_KNOTS_TO_MPH(n);  /* yes, in knots */
 	  }
 	  wp += 7;
 	}
-	else if ( g_speed == G_UNKNOWN) {
+	else if ( A->g_speed == G_UNKNOWN) {
 
-	  if ( ! getwdata (&wp, 'c', 3, &g_course)) {
+	  if ( ! getwdata (&wp, 'c', 3, &A->g_course)) {
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf("Didn't find wind direction in form c999.\n");
 	  }
-	  if ( ! getwdata (&wp, 's', 3, &g_speed)) {	/* MPH here */
+	  if ( ! getwdata (&wp, 's', 3, &A->g_speed)) {	/* MPH here */
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf("Didn't find wind speed in form s999.\n");
 	  }
@@ -2244,19 +2338,19 @@ static void weather_data (char *wdata, int wind_prefix)
 // At this point, we should have the wind direction and speed
 // from one of three methods.
 
-	if (g_speed != G_UNKNOWN) {
+	if (A->g_speed != G_UNKNOWN) {
 	  char ctemp[30];
 
-	  sprintf (g_comment, "wind %.1f mph", g_speed);
-	  if (g_course != G_UNKNOWN) {
-	    sprintf (ctemp, ", direction %.0f", g_course);
-	    strcat (g_comment, ctemp);
+	  sprintf (A->g_weather, "wind %.1f mph", A->g_speed);
+	  if (A->g_course != G_UNKNOWN) {
+	    sprintf (ctemp, ", direction %.0f", A->g_course);
+	    strcat (A->g_weather, ctemp);
 	  }
 	}
 
 	/* We don't want this to show up on the location line. */
-	g_speed = G_UNKNOWN;
-	g_course = G_UNKNOWN;
+	A->g_speed = G_UNKNOWN;
+	A->g_course = G_UNKNOWN;
 
 /*
  * After the mandatory wind direction and speed (in 1 of 3 formats), the
@@ -2268,7 +2362,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	  if (fval != G_UNKNOWN) {
 	    char ctemp[30];
 	    sprintf (ctemp, ", gust %.0f", fval);
-	    strcat (g_comment, ctemp);
+	    strcat (A->g_weather, ctemp);
 	  }
 	}
 	else {
@@ -2280,7 +2374,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	  if (fval != G_UNKNOWN) {
 	    char ctemp[30];
 	    sprintf (ctemp, ", temperature %.0f", fval);
-	    strcat (g_comment, ctemp);
+	    strcat (A->g_weather, ctemp);
 	  }
 	}
 	else {
@@ -2301,7 +2395,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", rain %.2f in last hour", fval / 100.);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'p', 3, &fval)) {	
@@ -2311,7 +2405,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", rain %.2f in last 24 hours", fval / 100.);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'P', 3, &fval)) {	
@@ -2321,7 +2415,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", rain %.2f since midnight", fval / 100.);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'h', 2, &fval)) {	
@@ -2332,7 +2426,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	      char ctemp[30];
 	      if (fval == 0) fval = 100;
 	      sprintf (ctemp, ", humidity %.0f", fval);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'b', 5, &fval)) {	
@@ -2342,9 +2436,9 @@ static void weather_data (char *wdata, int wind_prefix)
 
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
-	      fval = MBAR_TO_INHG(fval * 0.1);
+	      fval = DW_MBAR_TO_INHG(fval * 0.1);
 	      sprintf (ctemp, ", barometer %.2f", fval);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'L', 3, &fval)) {	
@@ -2354,7 +2448,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", %.0f watts/m^2", fval);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'l', 3, &fval)) {	
@@ -2364,7 +2458,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", %.0f watts/m^2", fval + 1000);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 's', 3, &fval)) {	
@@ -2377,7 +2471,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", %.1f snow in 24 hours", fval);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 's', 3, &fval)) {	
@@ -2387,7 +2481,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", raw rain counter %.f", fval);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 	  else if (getwdata (&wp, 'X', 3, &fval)) {	
@@ -2401,7 +2495,7 @@ static void weather_data (char *wdata, int wind_prefix)
 	    if (fval != G_UNKNOWN) {
 	      char ctemp[30];
 	      sprintf (ctemp, ", nuclear Radiation %.f", fval);
-	      strcat (g_comment, ctemp);
+	      strcat (A->g_weather, ctemp);
 	    }
 	  }
 
@@ -2423,22 +2517,22 @@ static void weather_data (char *wdata, int wind_prefix)
  *  / {UIV32N}
  */
 
-	strcat (g_comment, ", \"");
-	strcat (g_comment, wp);
+	strcat (A->g_weather, ", \"");
+	strcat (A->g_weather, wp);
 /*
  * Drop any CR / LF character at the end.
  */
-	n = strlen(g_comment);
-	if (n >= 1 && g_comment[n-1] == '\n') {
-	  g_comment[n-1] = '\0';
+	n = strlen(A->g_weather);
+	if (n >= 1 && A->g_weather[n-1] == '\n') {
+	  A->g_weather[n-1] = '\0';
 	}
 
-	n = strlen(g_comment);
-	if (n >= 1 && g_comment[n-1] == '\r') {
-	  g_comment[n-1] = '\0';
+	n = strlen(A->g_weather);
+	if (n >= 1 && A->g_weather[n-1] == '\r') {
+	  A->g_weather[n-1] = '\0';
 	}
 
-	strcat (g_comment, "\"");
+	strcat (A->g_weather, "\"");
 
 	return;
 }
@@ -2453,7 +2547,7 @@ static void weather_data (char *wdata, int wind_prefix)
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_comment
+ * Outputs:	A->g_weather
  *
  * Description:	http://www.peetbros.com/shop/custom.aspx?recid=7 
  *
@@ -2474,7 +2568,7 @@ static void weather_data (char *wdata, int wind_prefix)
  *
  *------------------------------------------------------------------*/
 
-static void aprs_ultimeter (char *info, int ilen) 
+static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) 
 {
 
 				// Header = $ULTW 
@@ -2500,7 +2594,7 @@ static void aprs_ultimeter (char *info, int ilen)
 
 	int n;
 
-	strcpy (g_msg_type, "Ultimeter");
+	strcpy (A->g_msg_type, "Ultimeter");
 
 	if (*info == '$')
  	{
@@ -2523,13 +2617,13 @@ static void aprs_ultimeter (char *info, int ilen)
 
 	    float windpeak, wdir, otemp, baro, ohumid;
 
-	    windpeak = KM_TO_MILES(h_windpeak * 0.1);
+	    windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
 	    wdir = (h_wdir & 0xff) * 360. / 256.;
 	    otemp = h_otemp * 0.1;
-	    baro = MBAR_TO_INHG(h_baro * 0.1);
+	    baro = DW_MBAR_TO_INHG(h_baro * 0.1);
 	    ohumid = h_ohumid * 0.1;
 	  
-	    sprintf (g_comment, "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f",
+	    sprintf (A->g_weather, "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f",
 			windpeak, wdir, otemp, baro, ohumid);
 	  }
 	}
@@ -2566,11 +2660,11 @@ static void aprs_ultimeter (char *info, int ilen)
 
 	    float windpeak, wdir, otemp;
 
-	    windpeak = KM_TO_MILES(h_windpeak * 0.1);
+	    windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
 	    wdir = (h_wdir & 0xff) * 360. / 256.;
 	    otemp = h_otemp * 0.1;
 	  
-	    sprintf (g_comment, "wind %.1f mph, direction %.0f, temperature %.1f\n",
+	    sprintf (A->g_weather, "wind %.1f mph, direction %.0f, temperature %.1f\n",
 			windpeak, wdir, otemp);
 	  }
 
@@ -2588,17 +2682,17 @@ static void aprs_ultimeter (char *info, int ilen)
  * Inputs:	info 	- Pointer to Information field.
  *		ilen 	- Information field length.
  *
- * Outputs:	g_comment
+ * Outputs:	A->g_comment
  *
  * Description:	
  *
  *------------------------------------------------------------------*/
 
-static void third_party_header (char *info, int ilen) 
+static void third_party_header (decode_aprs_t *A, char *info, int ilen) 
 {
 	int n;
 
-	strcpy (g_msg_type, "Third Party Header");
+	strcpy (A->g_msg_type, "Third Party Header");
 
 	/* more later? */
 
@@ -2614,10 +2708,10 @@ static void third_party_header (char *info, int ilen)
  *
  * Inputs:	ppos 	- Pointer to position & symbol fields.
  *
- * Returns:	g_lat
- *		g_lon
- *		g_symbol_table
- *		g_symbol_code
+ * Returns:	A->g_lat
+ *		A->g_lon
+ *		A->g_symbol_table
+ *		A->g_symbol_code
  *
  * Description:	This provides resolution of about 60 feet.
  *		This can be improved by using !DAO! in the comment.
@@ -2625,14 +2719,14 @@ static void third_party_header (char *info, int ilen)
  *------------------------------------------------------------------*/
 
 
-static void decode_position (position_t *ppos)
+static void decode_position (decode_aprs_t *A, position_t *ppos)
 {
 
-	  g_lat = get_latitude_8 (ppos->lat);
-	  g_lon = get_longitude_9 (ppos->lon);
+	  A->g_lat = get_latitude_8 (ppos->lat);
+	  A->g_lon = get_longitude_9 (ppos->lon);
 
-	  g_symbol_table = ppos->sym_table_id;
-	  g_symbol_code = ppos->symbol_code;
+	  A->g_symbol_table = ppos->sym_table_id;
+	  A->g_symbol_code = ppos->symbol_code;
 }
 
 /*------------------------------------------------------------------
@@ -2643,15 +2737,15 @@ static void decode_position (position_t *ppos)
  *
  * Inputs:	ppos 	- Pointer to compressed position & symbol fields.
  *
- * Returns:	g_lat
- *		g_lon
- *		g_symbol_table
- *		g_symbol_code
+ * Returns:	A->g_lat
+ *		A->g_lon
+ *		A->g_symbol_table
+ *		A->g_symbol_code
  *
  *		One of the following:
- *			g_course & g_speeed
- *			g_altitude
- *			g_range
+ *			A->g_course & A->g_speeed
+ *			A->g_altitude
+ *			A->g_range
  *
  * Description:	The compressed position provides resolution of around ???
  *		This also includes course/speed or altitude.
@@ -2673,68 +2767,62 @@ static void decode_position (position_t *ppos)
  *------------------------------------------------------------------*/
 
 
-static void decode_compressed_position (compressed_position_t *pcpos)
+static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *pcpos)
 {
-	if (pcpos->y[0] >= '!' && pcpos->y[0] <= '{' &&
-	    pcpos->y[1] >= '!' && pcpos->y[1] <= '{' &&
-	    pcpos->y[2] >= '!' && pcpos->y[2] <= '{' &&
-	    pcpos->y[3] >= '!' && pcpos->y[3] <= '{' ) 
+	if (isdigit91(pcpos->y[0]) && isdigit91(pcpos->y[1]) && isdigit91(pcpos->y[2]) && isdigit91(pcpos->y[3]))
 	{
-	  g_lat = 90 - ((pcpos->y[0]-33)*91*91*91 + (pcpos->y[1]-33)*91*91 + (pcpos->y[2]-33)*91 + (pcpos->y[3]-33)) / 380926.0;
+	  A->g_lat = 90 - ((pcpos->y[0]-33)*91*91*91 + (pcpos->y[1]-33)*91*91 + (pcpos->y[2]-33)*91 + (pcpos->y[3]-33)) / 380926.0;
 	}
 	else
  	{
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf("Invalid character in compressed latitude.  Must be in range of '!' to '{'.\n");
-	  g_lat = G_UNKNOWN;
+	  A->g_lat = G_UNKNOWN;
 	}
 	  
-	if (pcpos->x[0] >= '!' && pcpos->x[0] <= '{' &&
-	    pcpos->x[1] >= '!' && pcpos->x[1] <= '{' &&
-	    pcpos->x[2] >= '!' && pcpos->x[2] <= '{' &&
-	    pcpos->x[3] >= '!' && pcpos->x[3] <= '{' ) 
+	if (isdigit91(pcpos->x[0]) && isdigit91(pcpos->x[1]) && isdigit91(pcpos->x[2]) && isdigit91(pcpos->x[3]))
 	{
-	  g_lon = -180 + ((pcpos->x[0]-33)*91*91*91 + (pcpos->x[1]-33)*91*91 + (pcpos->x[2]-33)*91 + (pcpos->x[3]-33)) / 190463.0;
+	  A->g_lon = -180 + ((pcpos->x[0]-33)*91*91*91 + (pcpos->x[1]-33)*91*91 + (pcpos->x[2]-33)*91 + (pcpos->x[3]-33)) / 190463.0;
 	}
 	else 
 	{
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf("Invalid character in compressed longitude.  Must be in range of '!' to '{'.\n");
-	  g_lon = G_UNKNOWN;
+	  A->g_lon = G_UNKNOWN;
 	}
 
 	if (pcpos->sym_table_id == '/' || pcpos->sym_table_id == '\\' || isupper((int)(pcpos->sym_table_id))) {
 	  /* primary or alternate or alternate with upper case overlay. */
-	  g_symbol_table = pcpos->sym_table_id;
+	  A->g_symbol_table = pcpos->sym_table_id;
    	}
 	else if (pcpos->sym_table_id >= 'a' && pcpos->sym_table_id <= 'j') {
 	  /* Lower case a-j are used to represent overlay characters 0-9 */
 	  /* because a digit here would mean normal (non-compressed) location. */
-	  g_symbol_table = pcpos->sym_table_id - 'a' + '0';
+	  A->g_symbol_table = pcpos->sym_table_id - 'a' + '0';
 	}
 	else {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf("Invalid symbol table id for compressed position.\n");
-	  g_symbol_table = '/';
+	  A->g_symbol_table = '/';
 	}
 
-	g_symbol_code = pcpos->symbol_code;
+	A->g_symbol_code = pcpos->symbol_code;
 
 	if (pcpos->c == ' ') {
 	  ; /* ignore other two bytes */
 	}
 	else if (((pcpos->t - 33) & 0x18) == 0x10) {
-	  g_altitude = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33);
+	  A->g_altitude = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33);
 	}
 	else if (pcpos->c == '{')
 	{
-	  g_range = 2.0 * pow(1.08, pcpos->s - 33);
+	  A->g_range = 2.0 * pow(1.08, pcpos->s - 33);
 	}
 	else if (pcpos->c >= '!' && pcpos->c <= 'z')
 	{
 	  /* For a weather station, this is wind information. */
-	  g_course = (pcpos->c - 33) * 4;
-	  g_speed = KNOTS_TO_MPH(pow(1.08, pcpos->s - 33) - 1.0);
+	  A->g_course = (pcpos->c - 33) * 4;
+	  A->g_speed = DW_KNOTS_TO_MPH(pow(1.08, pcpos->s - 33) - 1.0);
 	}
 
 }
@@ -3074,7 +3162,7 @@ double get_longitude_9 (char *p)
  *------------------------------------------------------------------*/
 
 
-time_t get_timestamp (char *p)
+time_t get_timestamp (decode_aprs_t *A, char *p)
 {
 	struct dhm_s {
 	  char day[2];
@@ -3181,7 +3269,7 @@ time_t get_timestamp (char *p)
  *------------------------------------------------------------------*/
 
 
-int get_maidenhead (char *p)
+int get_maidenhead (decode_aprs_t *A, char *p)
 {
 
 	if (toupper(p[0]) >= 'A' && toupper(p[0]) <= 'R' &&
@@ -3215,126 +3303,12 @@ int get_maidenhead (char *p)
 }
 
 
-/*------------------------------------------------------------------
- *
- * Function:	get_latitude_nmea
- *
- * Purpose:	Convert NMEA latitude encoding to degrees.
- *
- * Inputs:	pstr 	- Pointer to numeric string.
- *		phemi	- Pointer to following field.  Should be N or S.
- *
- * Returns:	Double precision value in degrees.  Negative for South.
- *
- * Description:	Latitude field has
- *			2 digits for degrees
- *			2 digits for minutes
- *			period
- *			Variable number of fractional digits for minutes.
- *			I've seen 2, 3, and 4 fractional digits.
- *
- *
- * Bugs:	Very little validation of data.
- *
- * Errors:	Return constant G_UNKNOWN for any type of error.
- *		Could we use special "NaN" code?
- *
- *------------------------------------------------------------------*/
-
-
-static double get_latitude_nmea (char *pstr, char *phemi)
-{
-
-	double lat;
-
-	if ( ! isdigit((unsigned char)(pstr[0]))) return (G_UNKNOWN);
-
-	if (pstr[4] != '.') return (G_UNKNOWN);
-
-
-	lat = (pstr[0] - '0') * 10 + (pstr[1] - '0') + atof(pstr+2) / 60.0;
-
-	if (lat < 0 || lat > 90) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("Error: Latitude not in range of 0 to 90.\n");	  
-	}
-
-	// Saw this one time:
-	//	$GPRMC,000000,V,0000.0000,0,00000.0000,0,000,000,000000,,*01
-
-	// If location is unknown, I think the hemisphere should be
-	// an empty string.  TODO: Check on this.
-
-	if (*phemi != 'N' && *phemi != 'S' && *phemi != '\0') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("Error: Latitude hemisphere should be N or S.\n");	  
-	}
-
-	if (*phemi == 'S') lat = ( - lat);
-
-	return (lat);
-}
-
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	get_longitude_nmea
- *
- * Purpose:	Convert NMEA longitude encoding to degrees.
- *
- * Inputs:	pstr 	- Pointer to numeric string.
- *		phemi	- Pointer to following field.  Should be E or W.
- *
- * Returns:	Double precision value in degrees.  Negative for West.
- *
- * Description:	Longitude field has
- *			3 digits for degrees
- *			2 digits for minutes
- *			period
- *			Variable number of fractional digits for minutes
- *
- *
- * Bugs:	Very little validation of data.
- *
- * Errors:	Return constant G_UNKNOWN for any type of error.
- *		Could we use special "NaN" code?
- *
- *------------------------------------------------------------------*/
-
-
-static double get_longitude_nmea (char *pstr, char *phemi)
-{
-	double lon;
-
-	if ( ! isdigit((unsigned char)(pstr[0]))) return (G_UNKNOWN);
-
-	if (pstr[5] != '.') return (G_UNKNOWN);
-
-	lon = (pstr[0] - '0') * 100 + (pstr[1] - '0') * 10 + (pstr[2] - '0') + atof(pstr+3) / 60.0;
-
-	if (lon < 0 || lon > 180) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("Error: Longitude not in range of 0 to 180.\n");	  
-	}
-	
-	if (*phemi != 'E' && *phemi != 'W' && *phemi != '\0') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("Error: Longitude hemisphere should be E or W.\n");	  
-	}
-
-	if (*phemi == 'W') lon = ( - lon);
-
-	return (lon);
-}
-
 
 /*------------------------------------------------------------------
  *
  * Function:	data_extension_comment
  *
- * Purpose:	A fixed length 7-byte field may follow APRS position data.
+ * Purpose:	A fixed length 7-byte field may follow APRS position datA->
  *
  * Inputs:	pdext	- Pointer to optional data extension and comment.
  *
@@ -3342,17 +3316,17 @@ static double get_longitude_nmea (char *pstr, char *phemi)
  *
  * Outputs:	One or more of the following, depending the data found:
  *	
- *			g_course
- *			g_speed
- *			g_power 
- *			g_height 
- *			g_gain 
- *			g_directivity 
- *			g_range
+ *			A->g_course
+ *			A->g_speed
+ *			A->g_power 
+ *			A->g_height 
+ *			A->g_gain 
+ *			A->g_directivity 
+ *			A->g_range
  *
  *		Anything left over will be put in 
  *
- *			g_comment			
+ *			A->g_comment			
  *
  * Description:	
  *
@@ -3362,12 +3336,12 @@ static double get_longitude_nmea (char *pstr, char *phemi)
 
 const char *dir[9] = { "omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N" };
 
-static int data_extension_comment (char *pdext)
+static int data_extension_comment (decode_aprs_t *A, char *pdext)
 {
 	int n;
 
 	if (strlen(pdext) < 7) {
-	  strcpy (g_comment, pdext);
+	  strcpy (A->g_comment, pdext);
 	  return 0;
 	}
 
@@ -3378,7 +3352,7 @@ static int data_extension_comment (char *pdext)
 	 	pdext[4] == 'C')
 	{
 	  /* not decoded at this time */
-	  process_comment (pdext+7, -1);
+	  process_comment (A, pdext+7, -1);
 	  return 1;
 	}
 
@@ -3390,21 +3364,21 @@ static int data_extension_comment (char *pdext)
 	{
 	  if (sscanf (pdext, "%3d", &n))
 	  {
-	    g_course = n;
+	    A->g_course = n;
 	  }
 	  if (sscanf (pdext+4, "%3d", &n))
 	  {
-	    g_speed = KNOTS_TO_MPH(n);
+	    A->g_speed = DW_KNOTS_TO_MPH(n);
 	  }
 
 	  /* Bearing and Number/Range/Quality? */
 
 	  if (pdext[7] == '/' && pdext[11] == '/') 
 	  {
-	    process_comment (pdext + 7 + 8, -1);
+	    process_comment (A, pdext + 7 + 8, -1);
 	  }
 	  else {
-	    process_comment (pdext+7, -1);
+	    process_comment (A, pdext+7, -1);
 	  }
 	  return 1;
 	}
@@ -3413,14 +3387,14 @@ static int data_extension_comment (char *pdext)
 
 	if (strncmp(pdext, "PHG", 3) == 0)
 	{
-	  g_power = (pdext[3] - '0') * (pdext[3] - '0');
-	  g_height = (1 << (pdext[4] - '0')) * 10;
-	  g_gain = pdext[5] - '0';
+	  A->g_power = (pdext[3] - '0') * (pdext[3] - '0');
+	  A->g_height = (1 << (pdext[4] - '0')) * 10;
+	  A->g_gain = pdext[5] - '0';
 	  if (pdext[6] >= '0' && pdext[6] <= '8') {
-	    strcpy (g_directivity, dir[pdext[6]-'0']);
+	    strcpy (A->g_directivity, dir[pdext[6]-'0']);
 	  }
 
-	  process_comment (pdext+7, -1);
+	  process_comment (A, pdext+7, -1);
 	  return 1;
 	}
 
@@ -3430,9 +3404,9 @@ static int data_extension_comment (char *pdext)
 	{
 	  if (sscanf (pdext+3, "%4d", &n))
 	  {
-	    g_range = n;
+	    A->g_range = n;
 	  }
-	  process_comment (pdext+7, -1);
+	  process_comment (A, pdext+7, -1);
 	  return 1;
 	}
 
@@ -3440,18 +3414,18 @@ static int data_extension_comment (char *pdext)
 
 	if (strncmp(pdext, "DFS", 3) == 0)
 	{
-	  //g_strength = pdext[3] - '0';
-	  g_height = (1 << (pdext[4] - '0')) * 10;
-	  g_gain = pdext[5] - '0';
+	  //A->g_strength = pdext[3] - '0';
+	  A->g_height = (1 << (pdext[4] - '0')) * 10;
+	  A->g_gain = pdext[5] - '0';
 	  if (pdext[6] >= '0' && pdext[6] <= '8') {
-	    strcpy (g_directivity, dir[pdext[6]-'0']);
+	    strcpy (A->g_directivity, dir[pdext[6]-'0']);
 	  }
 
-	  process_comment (pdext+7, -1);
+	  process_comment (A, pdext+7, -1);
 	  return 1;
 	}
 
-	process_comment (pdext, -1);
+	process_comment (A, pdext, -1);
 	return 0;
 }
 
@@ -3465,7 +3439,7 @@ static int data_extension_comment (char *pdext)
  * Inputs:	dest	- Destination address.
  *			Don't care if SSID is present or not.
  *
- * Outputs:	g_mfr
+ * Outputs:	A->g_mfr
  *
  * Description:	For maximum flexibility, we will read the
  *		data file at run time rather than compiling it in.
@@ -3497,7 +3471,7 @@ static int tocall_cmp (const struct tocalls_s *x, const struct tocalls_s *y)
 	return (strcmp(x->prefix, y->prefix));
 }
 
-static void decode_tocall (char *dest)
+static void decode_tocall (decode_aprs_t *A, char *dest)
 {
 	FILE *fp;
 	int n;
@@ -3603,7 +3577,7 @@ static void decode_tocall (char *dest)
  * models before getting to the more generic APY.
  */
 
-#if __WIN32__
+#if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__)
 	    qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp);
 #else
 	    qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), (__compar_fn_t)tocall_cmp);
@@ -3622,8 +3596,8 @@ static void decode_tocall (char *dest)
 
 	for (n=0; n<num_tocalls; n++) {
 	  if (strncmp(dest, tocalls[n].prefix, tocalls[n].len) == 0) {
-	    strncpy (g_mfr, tocalls[n].description, sizeof(g_mfr)-1);
-	    g_mfr[sizeof(g_mfr)-1] = '\0';
+	    strncpy (A->g_mfr, tocalls[n].description, sizeof(A->g_mfr)-1);
+	    A->g_mfr[sizeof(A->g_mfr)-1] = '\0';
 	    return;
 	  }
 	}
@@ -3631,6 +3605,38 @@ static void decode_tocall (char *dest)
 } /* end decode_tocall */ 
 
 
+
+/*------------------------------------------------------------------
+ *
+ * Function:	substr_se
+ *
+ * Purpose:	Extract substring given start and end+1 offset.
+ *
+ * Inputs:	src		- Source string
+ *
+ *		start		- Start offset.
+ *		
+ *		endp1		- End offset+1 for ease of use with regexec result.
+ *
+ * Outputs:	dest		- Destination for substring.
+ *
+ *------------------------------------------------------------------*/
+
+static void substr_se (char *dest, const char *src, int start, int endp1)
+{
+	int len = endp1 - start;
+
+	if (start < 0 || endp1 < 0 || len <= 0) {
+	  strcpy (dest, "");
+	  return;
+	}
+	memcpy (dest, src + start, len);
+	dest[len] = '\0';
+
+} /* end substr_se */
+	
+
+
 /*------------------------------------------------------------------
  *
  * Function:	process_comment
@@ -3641,7 +3647,15 @@ static void decode_tocall (char *dest)
  *
  *		clen		- Length of comment or -1 to take it all.
  *
- * Outputs:	g_comment
+ * Outputs:	A->g_telemetry	- Base 91 telemetry |ss1122|
+ *		A->g_altitude	- from /A=123456
+ *		A->g_lat	- Might be adjusted from !DAO!
+ *		A->g_lon	- Might be adjusted from !DAO!
+ *		A->g_aprstt_loc	- Private extension to !DAO!
+ *		A->g_freq
+ *		A->g_tone
+ *		A->g_offset
+ *		A->g_comment	- Anything left over after extracting above.
  *
  * Description:	After processing fixed and possible optional parts
  *		of the message, everything left over is a comment.
@@ -3651,7 +3665,7 @@ static void decode_tocall (char *dest)
  *		There are could be some other pieces of data, with 
  *		particular formats, buried in there.
  *		Pull out those special items and put everything 
- *		else into g_comment.
+ *		else into A->g_comment.
  *
  * References:	http://www.aprs.org/info/freqspec.txt
  *
@@ -3668,19 +3682,59 @@ static void decode_tocall (char *dest)
  *
  *------------------------------------------------------------------*/
 
+/* CTCSS tones in various formats to avoid conversions every time. */
+
+#define NUM_CTCSS 50
+
+static const int i_ctcss[NUM_CTCSS] = {
+         67,  69,  71,  74,  77,  79,  82,  85,  88,  91,
+         94,  97, 100, 103, 107, 110, 114, 118, 123, 127,
+        131, 136, 141, 146, 151, 156, 159, 162, 165, 167,
+        171, 173, 177, 179, 183, 186, 189, 192, 196, 199,
+        203, 206, 210, 218, 225, 229, 233, 241, 250, 254 };
+
+static const float f_ctcss[NUM_CTCSS] = {
+         67.0,  69.3,  71.9,  74.4,  77.0,  79.7,  82.5,  85.4,  88.5,  91.5,
+         94.8,  97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3,
+        131.8, 136.5, 141.3, 146.2, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9,
+        171.3, 173.8, 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
+        203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1 };
+
+static const char * s_ctcss[NUM_CTCSS] = {
+         "67.0",  "69.3",  "71.9",  "74.4",  "77.0",  "79.7",  "82.5",  "85.4",  "88.5",  "91.5",
+         "94.8",  "97.4", "100.0", "103.5", "107.2", "110.9", "114.8", "118.8", "123.0", "127.3",
+        "131.8", "136.5", "141.3", "146.2", "151.4", "156.7", "159.8", "162.2", "165.5", "167.9",
+        "171.3", "173.8", "177.3", "179.9", "183.5", "186.2", "189.9", "192.8", "196.6", "199.5",
+        "203.5", "206.5", "210.7", "218.1", "225.7", "229.1", "233.6", "241.8", "250.3", "254.1" };
+
+
 #define sign(x) (((x)>=0)?1:(-1))
 
-static void process_comment (char *pstart, int clen)
+static void process_comment (decode_aprs_t *A, char *pstart, int clen)
 {
 	static int first_time = 1;
-	static regex_t freq_re;	/* These must be static! */
-	static regex_t dao_re;	/* These must be static! */
-	static regex_t alt_re;	/* These must be static! */
+	static regex_t std_freq_re;	/* Frequency in standard format. */
+	static regex_t std_tone_re;	/* Tone in standard format. */
+	static regex_t std_toff_re;	/* Explicitly no tone. */
+	static regex_t std_dcs_re;	/* Digital codes squelch in standard format. */
+	static regex_t std_offset_re;	/* Xmit freq offset in standard format. */
+	static regex_t std_range_re;	/* Range in standard format. */
+
+	static regex_t dao_re;		/* DAO */
+	static regex_t alt_re;		/* /A= altitude */
+
+	static regex_t bad_freq_re;	/* Likely frequency, not standard format */
+	static regex_t bad_tone_re;	/* Likely tone, not standard format */
+
+	static regex_t base91_tel_re;	/* Base 91 compressed telemetry data. */
+
 	int e;
 	char emsg[100];
-#define MAXMATCH 1
+#define MAXMATCH 4
 	regmatch_t match[MAXMATCH];
 	char temp[256];
+	int keep_going;
+
 
 /*
  * No sense in recompiling the patterns and freeing every time.
@@ -3688,17 +3742,58 @@ static void process_comment (char *pstart, int clen)
 	if (first_time) 
 	{
 /*
- * Present, frequency must be at the at the beginning.
+ * Frequency must be at the at the beginning.
  * Others can be anywhere in the comment.
  */
-		/* incomplete */
-	  e = regcomp (&freq_re, "^[0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ]MHz( [TCDtcd][0-9][0-9][0-9]| Toff)?( [+-][0-9][0-9][0-9])?", REG_EXTENDED);
+		
+	  //e = regcomp (&freq_re, "^[0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ]MHz( [TCDtcd][0-9][0-9][0-9]| Toff)?( [+-][0-9][0-9][0-9])?", REG_EXTENDED);
+
+	  // Freq optionally preceded by space or /.
+	  // Third fractional digit can be space instead.
+	  // "MHz" should be exactly that capitalization.  
+	  // Print warning later it not.
+
+	  e = regcomp (&std_freq_re, "^[/ ]?([0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ])([Mm][Hh][Zz])", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &std_freq_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+	  // If no tone, we might gobble up / after any data extension,
+	  // We could also have a space but it's not required.
+	  // I don't understand the difference between T and C so treat the same for now.
+	  // We can also have "off" instead of number to explicitly mean none.
+
+	  e = regcomp (&std_tone_re, "^[/ ]?([TtCc][012][0-9][0-9])", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &std_tone_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+	  e = regcomp (&std_toff_re, "^[/ ]?[TtCc][Oo][Ff][Ff]", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &std_toff_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+	  e = regcomp (&std_dcs_re, "^[/ ]?[Dd]([0-7][0-7][0-7])", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &std_dcs_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+	  e = regcomp (&std_offset_re, "^[/ ]?([+-][0-9][0-9][0-9])", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &std_offset_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+	  e = regcomp (&std_range_re, "^[/ ]?[Rr]([0-9][0-9])([mk])", REG_EXTENDED);
 	  if (e) {
-	    regerror (e, &freq_re, emsg, sizeof(emsg));
+	    regerror (e, &std_range_re, emsg, sizeof(emsg));
 	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
 	  }
 
-	  e = regcomp (&dao_re, "!([A-Z][0-9 ][0-9 ]|[a-z][!-} ][!-} ])!", REG_EXTENDED);
+	  e = regcomp (&dao_re, "!([A-Z][0-9 ][0-9 ]|[a-z][!-{ ][!-{ ]|T[0-9 B][0-9 ])!", REG_EXTENDED);
 	  if (e) {
 	    regerror (e, &dao_re, emsg, sizeof(emsg));
 	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
@@ -3710,54 +3805,260 @@ static void process_comment (char *pstart, int clen)
 	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
 	  }
 
+	  e = regcomp (&bad_freq_re, "[0-9][0-9][0-9]\\.[0-9][0-9][0-9]?", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &bad_freq_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+	  e = regcomp (&bad_tone_re, "(^|[^0-9.])([6789][0-9]\\.[0-9]|[12][0-9][0-9]\\.[0-9]|67|77|100|123)($|[^0-9.])", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &bad_tone_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+// TODO:  Would like to restrict to even length something like this:  ([!-{][!-{]){2,7}
+
+	  e = regcomp (&base91_tel_re, "\\|([!-{]{4,14})\\|", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &base91_tel_re, emsg, sizeof(emsg));
+	    dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
 	  first_time = 0;
 	}
 
 	if (clen >= 0) {
-	  assert (clen < sizeof(g_comment));
-	  memcpy (g_comment, pstart, (size_t)clen);
-	  g_comment[clen] = '\0';
+	  assert (clen < sizeof(A->g_comment));
+	  memcpy (A->g_comment, pstart, (size_t)clen);
+	  A->g_comment[clen] = '\0';
 	}
 	else {
-	  strcpy (g_comment, pstart);
+	  strcpy (A->g_comment, pstart);
 	}
-	//dw_printf("\nInitial comment='%s'\n", g_comment);
+	//dw_printf("\nInitial comment='%s'\n", A->g_comment);
 
 
 /*
- * Frequency.
- * Just pull it out from comment. 
- * No futher interpretation at this time.
+ * Look for frequency in the standard format at start of comment.
+ * If that fails, try to obtain from object name.
  */
 
-	if (regexec (&freq_re, g_comment, MAXMATCH, match, 0) == 0) 
+	if (regexec (&std_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) 
 	{
+	  char sftemp[30];
+	  char smtemp[10];
 
-          //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
+          //dw_printf("matches= %d - %d, %d - %d, %d - %d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo), 
+	  //						    (int)(match[1].rm_so), (int)(match[1].rm_eo),
+	  //						    (int)(match[2].rm_so), (int)(match[2].rm_eo) );
+
+	  substr_se (sftemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
+	  substr_se (smtemp, A->g_comment, match[2].rm_so, match[2].rm_eo);
+	
+	  switch (sftemp[0]) {
+	    case 'A': A->g_freq =  1200 + atof(sftemp+1); break;
+	    case 'B': A->g_freq =  2300 + atof(sftemp+1); break;
+	    case 'C': A->g_freq =  2400 + atof(sftemp+1); break;
+	    case 'D': A->g_freq =  3400 + atof(sftemp+1); break;
+	    case 'E': A->g_freq =  5600 + atof(sftemp+1); break;
+	    case 'F': A->g_freq =  5700 + atof(sftemp+1); break;
+	    case 'G': A->g_freq =  5800 + atof(sftemp+1); break;
+	    case 'H': A->g_freq = 10100 + atof(sftemp+1); break;
+	    case 'I': A->g_freq = 10200 + atof(sftemp+1); break;
+	    case 'J': A->g_freq = 10300 + atof(sftemp+1); break;
+	    case 'K': A->g_freq = 10400 + atof(sftemp+1); break;
+	    case 'L': A->g_freq = 10500 + atof(sftemp+1); break;
+	    case 'M': A->g_freq = 24000 + atof(sftemp+1); break;
+	    case 'N': A->g_freq = 24100 + atof(sftemp+1); break;
+	    case 'O': A->g_freq = 24200 + atof(sftemp+1); break;
+	    default:  A->g_freq =         atof(sftemp);   break;
+	  }
+
+	  if (strncmp(smtemp, "MHz", 3) != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Warning: \"%s\" has non-standard capitalization and might not be recognized by some systems.\n", smtemp);
+	    dw_printf("For best compatibility, it should be exactly like this: \"MHz\"  (upper,upper,lower case)\n");
+	  }
+
+	  strcpy (temp, A->g_comment + match[0].rm_eo);
+	  strcpy (A->g_comment + match[0].rm_so, temp);
+	}
+	else if (strlen(A->g_name) > 0) {
+
+	  // Try to extract sensible number from object/item name.
+
+	  double x = atof (A->g_name);
+
+	  if ((x >= 144 && x <= 148) ||
+	      (x >= 222 && x <= 225) ||
+	      (x >= 420 && x <= 450) ||
+	      (x >= 902 && x <= 928)) { 
+	    A->g_freq = x;
+	  }
+	}
+
+/*
+ * Next, look for tone, DCS code, and range.
+ * Examples always have them in same order but it's not clear
+ * whether any order is allowed after possible frequency.
+ *
+ * TODO: Convert integer tone to original value for display.
+ * TODO: samples in zfreq-test3.txt
+ */
+
+	keep_going = 1;
+	while (keep_going) {
+
+	  if (regexec (&std_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    char sttemp[10];	/* includes leading letter */
+	    int f;
+	    int i;
+
+	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
+
+	    // Try to convert from integer to proper value.
+
+	    f = atoi(sttemp+1);
+	    for (i = 0; i < NUM_CTCSS; i++) {
+	      if (f == i_ctcss[i]) {
+	        A->g_tone = f_ctcss[i];
+	        break;
+	      }
+	    }
+	    if (A->g_tone == G_UNKNOWN) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Bad CTCSS/PL specification: \"%s\"\n", sttemp);
+	      dw_printf("Integer does not correspond to standard tone.\n");
+	    }
+
+	    strcpy (temp, A->g_comment + match[0].rm_eo);
+	    strcpy (A->g_comment + match[0].rm_so, temp);
+	  }
+	  else if (regexec (&std_toff_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    printf ("NO tone\n");
+	    A->g_tone = 0;
+
+	    strcpy (temp, A->g_comment + match[0].rm_eo);
+	    strcpy (A->g_comment + match[0].rm_so, temp);
+	  }
+	  else if (regexec (&std_dcs_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    char sttemp[10];	/* three octal digits */
+	    int f;
+	    int i;
+
+	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
+
+	    A->g_dcs = strtoul (sttemp, NULL, 8);
+
+	    strcpy (temp, A->g_comment + match[0].rm_eo);
+	    strcpy (A->g_comment + match[0].rm_so, temp);
+	  }
+	  else if (regexec (&std_offset_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    char sttemp[10];	/* includes leading sign */
+	    int f;
 
-	  strcpy (temp, g_comment + match[0].rm_eo);
+	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
 
-	  g_comment[match[0].rm_eo] = '\0';
-          strcpy (g_freq, g_comment + match[0].rm_so);
+	    A->g_offset = 10 * atoi(sttemp);
 
-	  strcpy (g_comment + match[0].rm_so, temp);
+	    strcpy (temp, A->g_comment + match[0].rm_eo);
+	    strcpy (A->g_comment + match[0].rm_so, temp);
+	  }
+	  else if (regexec (&std_range_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    char sttemp[10];	/* should be two digits */
+	    char sutemp[10];	/* m for miles or k for km */
+	    int f;
+
+	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
+	    substr_se (sutemp, A->g_comment, match[2].rm_so, match[2].rm_eo);
+
+	    if (strcmp(sutemp, "m") == 0) {
+	      A->g_range = atoi(sttemp);
+	    }
+	    else {
+	      A->g_range = DW_KM_TO_MILES(atoi(sttemp));
+	    }
+
+	    strcpy (temp, A->g_comment + match[0].rm_eo);
+	    strcpy (A->g_comment + match[0].rm_so, temp);
+	  }
+	  else {
+	    keep_going = 0;
+	  }
 	}
 
 /*
+ * Telemetry data, in base 91 compressed format appears as 2 to 7 pairs
+ * of base 91 digits, surrounded by | at start and end.
+ */
+
+
+	if (regexec (&base91_tel_re, A->g_comment, MAXMATCH, match, 0) == 0) 
+	{
+
+	  char tdata[30];	/* Should be 4 to 14 characters. */
+
+          //dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
+
+	  substr_se (tdata, A->g_comment, match[1].rm_so, match[1].rm_eo);
+
+          //dw_printf("compressed telemetry data = \"%s\"\n", tdata);
+
+	  telemetry_data_base91 (A->g_src, tdata, A->g_telemetry);
+
+	  strcpy (temp, A->g_comment + match[0].rm_eo);
+	  strcpy (A->g_comment + match[0].rm_so, temp);
+	}
+
+
+/*
  * Latitude and Longitude in the form DD MM.HH has a resolution of about 60 feet.
- * The !DAO! option allows another digit or [almost two] for greater resolution.
+ * The !DAO! option allows another digit or almost two for greater resolution.
+ *
+ * This would not make sense to use this with a compressed location which
+ * already has much greater resolution.
+ *
+ * It surprized me to see this in a MIC-E message.
+ * MIC-E has resolution of .01 minute so it would make sense to have it as an option.
  */
 
-	if (regexec (&dao_re, g_comment, MAXMATCH, match, 0) == 0) 
+	if (regexec (&dao_re, A->g_comment, MAXMATCH, match, 0) == 0) 
 	{
 
-	  int d = g_comment[match[0].rm_so+1];
-	  int a = g_comment[match[0].rm_so+2];
-	  int o = g_comment[match[0].rm_so+3];
+	  int d = A->g_comment[match[0].rm_so+1];
+	  int a = A->g_comment[match[0].rm_so+2];
+	  int o = A->g_comment[match[0].rm_so+3];
 
           //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
 
-	  if (isupper(d)) 
+
+/*
+ * Private extension for APRStt
+ */
+
+	  if (d == 'T') {
+
+	    if (a == ' ' && o == ' ') {
+	      sprintf (A->g_aprstt_loc, "APRStt corral location");
+	    }
+	    else if (isdigit(a) && o == ' ') {
+	      sprintf (A->g_aprstt_loc, "APRStt location %c of 10", a);
+	    }
+	    else if (isdigit(a) && isdigit(o)) {
+	      sprintf (A->g_aprstt_loc, "APRStt location %c%c of 100", a, o);
+	    }
+	    else if (a == 'B' && isdigit(o)) {
+	      sprintf (A->g_aprstt_loc, "APRStt location %c%c...", a, o);
+	    }
+
+	  }
+	  else if (isupper(d)) 
 	  {
 /*
  * This adds one extra digit to each.  Dao adds extra digit like:
@@ -3766,10 +4067,10 @@ static void process_comment (char *pstart, int clen)
  *		Lon:	DDD HH.HHo
  */
  	    if (isdigit(a)) {
-	      g_lat += (a - '0') / 60000.0 * sign(g_lat);
+	      A->g_lat += (a - '0') / 60000.0 * sign(A->g_lat);
 	    }
  	    if (isdigit(o)) {
-	      g_lon += (o - '0') / 60000.0 * sign(g_lon);
+	      A->g_lon += (o - '0') / 60000.0 * sign(A->g_lon);
 	    }
 	  }
 	  else if (islower(d)) 
@@ -3780,40 +4081,129 @@ static void process_comment (char *pstart, int clen)
  *		Lat:	 DD MM.HHxx
  *		Lon:	DDD HH.HHxx
  *
- * The original character range '!' to '}' is first converted
+ * The original character range '!' to '{' is first converted
  * to an integer in range of 0 to 90.  It is multiplied by 1.1
  * to stretch the numeric range to be 0 to 99.
  */
- 	    if (a >= '!' && a <= '}') {
-	      g_lat += (a - '!') * 1.1 / 600000.0 * sign(g_lat);
+
+/* 
+ * The spec appears to be wrong.  It says '}' is the maximum value when it should be '{'. 
+ */
+
+
+ 	    if (isdigit91(a)) {
+	      A->g_lat += (a - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lat);
 	    }
- 	    if (o >= '!' && o <= '}') {
-	      g_lon += (o - '!') * 1.1 / 600000.0 * sign(g_lon);
+ 	    if (isdigit91(o)) {
+	      A->g_lon += (o - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lon);
 	    }
 	  }
 
-	  strcpy (temp, g_comment + match[0].rm_eo);
-	  strcpy (g_comment + match[0].rm_so, temp);
+	  strcpy (temp, A->g_comment + match[0].rm_eo);
+	  strcpy (A->g_comment + match[0].rm_so, temp);
 	}
 
 /*
  * Altitude in feet.  /A=123456
  */
 
-	if (regexec (&alt_re, g_comment, MAXMATCH, match, 0) == 0) 
+	if (regexec (&alt_re, A->g_comment, MAXMATCH, match, 0) == 0) 
 	{
 
           //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
 
-	  strcpy (temp, g_comment + match[0].rm_eo);
+	  strcpy (temp, A->g_comment + match[0].rm_eo);
 
-	  g_comment[match[0].rm_eo] = '\0';
-          g_altitude = atoi(g_comment + match[0].rm_so + 3);
+	  A->g_comment[match[0].rm_eo] = '\0';
+          A->g_altitude = atoi(A->g_comment + match[0].rm_so + 3);
 
-	  strcpy (g_comment + match[0].rm_so, temp);
+	  strcpy (A->g_comment + match[0].rm_so, temp);
 	}
 
-	//dw_printf("Final comment='%s'\n", g_comment);
+	//dw_printf("Final comment='%s'\n", A->g_comment);
+
+/*
+ * Finally look for something that looks like frequency or CTCSS tone
+ * in the remaining comment.  Point this out and suggest the 
+ * standardized format.
+ */
+	if (regexec (&bad_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) 
+	{
+	  char bad[30];
+	  char good[30];
+	  double x;
+
+	  substr_se (bad, A->g_comment, match[0].rm_so, match[0].rm_eo);
+	  x = atof(bad);
+
+	  if ((x >= 144 && x <= 148) ||
+	      (x >= 222 && x <= 225) ||
+	      (x >= 420 && x <= 450) ||
+	      (x >= 902 && x <= 928)) { 
+
+	    sprintf (good, "%07.3fMHz", x);
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("\"%s\" in comment looks like a frequency in non-standard format.\n", bad);
+	    dw_printf("For most systems to recognize it, use exactly this form \"%s\" at beginning of comment.\n", good);
+
+	    if (A->g_freq == G_UNKNOWN) {
+	      A->g_freq = x;
+	    }
+	  }
+	}
+
+	if (regexec (&bad_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) 
+	{
+	  char bad1[30];	/* original 99.9 or 999.9 format or one of 67 77 100 123 */
+	  char bad2[30];	/* 99.9 or 999.9 format.  ".0" appended for special cases. */
+	  char good[30];
+	  int i;
+
+	  substr_se (bad1, A->g_comment, match[2].rm_so, match[2].rm_eo);
+	  strcpy (bad2, bad1);
+	  if (strcmp(bad2, "67") == 0 || strcmp(bad2, "77") == 0 || strcmp(bad2, "100") == 0 || strcmp(bad2, "123") == 0) {
+	    strcat (bad2, ".0");
+	  }
+
+// TODO:  Why wasn't freq/PL recognized here?
+// Should we recognize some cases of single decimal place as frequency?
+
+//DECODED[194] N8VIM audio level = 27   [NONE]
+//[0] N8VIM>BEACON,WIDE2-2:!4240.85N/07133.99W_PHG72604/ Pepperell, MA-> WX. 442.9+ PL100<0x0d>
+//Didn't find wind direction in form c999.
+//Didn't find wind speed in form s999.
+//Didn't find wind gust in form g999.
+//Didn't find temperature in form t999.
+//Weather Report, WEATHER Station (blue)
+//N 42 40.8500, W 071 33.9900
+//, "PHG72604/ Pepperell, MA-> WX. 442.9+ PL100"
+
+
+	  for (i = 0; i < NUM_CTCSS; i++) {
+	    if (strcmp (s_ctcss[i], bad2) == 0) {
+
+              sprintf (good, "T%03d", i_ctcss[i]);
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("\"%s\" in comment looks like it might be a CTCSS tone in non-standard format.\n", bad1);
+	      dw_printf("For most systems to recognize it, use exactly this form \"%s\" at near beginning of comment, after any frequency.\n", good);
+
+	      if (A->g_tone == G_UNKNOWN) {
+	        A->g_tone = atof(bad2);
+	      }
+	      break;
+	    }
+	  }
+	}
+
+	if ((A->g_offset == 6000 || A->g_offset == -6000) && A->g_freq >= 144 && A->g_freq <= 148) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("A transmit offset of 6 MHz on the 2 meter band doesn't seem right.\n");
+	  dw_printf("Each unit is 10 kHz so you should probably be using \"-060\" or \"+060\"\n");
+	}
+
+/*
+ * TODO: samples in zfreq-test4.txt
+ */
 
 }
 
@@ -3821,6 +4211,8 @@ static void process_comment (char *pstart, int clen)
 
 
 
+
+
 /*------------------------------------------------------------------
  *
  * Function:	main
@@ -3868,6 +4260,15 @@ static void process_comment (char *pstart, int clen)
 
 #if TEST
 
+/* Stub for stand-alone decoder. */
+
+void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol,
+                 float alt, float course, float speed, char *comment)
+{
+	return;
+}
+
+
 
 int main (int argc, char *argv[]) 
 {
@@ -3930,7 +4331,21 @@ int main (int argc, char *argv[])
 	    pp = ax25_from_text(stuff, 1);
 	    if (pp != NULL) 
             {
-	      decode_aprs (pp);
+	      decode_aprs_t A;
+
+	      // log directory option someday?
+	      decode_aprs (&A, pp);
+
+	      //Print it all out in human readable format.
+
+	      decode_aprs_print (&A);
+
+	      // Send to log file?
+
+	      // if (logdir != NULL && *logdir != '\0') {
+	      //   log_write (&A, pp, logdir);
+	      // }
+
 	      ax25_delete (pp);
 	    }
 	    else 
diff --git a/decode_aprs.h b/decode_aprs.h
index 68bb3ae..226f887 100755
--- a/decode_aprs.h
+++ b/decode_aprs.h
@@ -1,5 +1,106 @@
 
 /* decode_aprs.h */
 
-extern void decode_aprs (packet_t pp);
 
+#ifndef DECODE_APRS_H
+
+#define DECODE_APRS_H 1
+
+
+
+#ifndef G_UNKNOWN
+#include "latlong.h"
+#endif
+
+#ifndef AX25_MAX_ADDR_LEN
+#include "ax25_pad.h"
+#endif 
+
+#ifndef APRSTT_LOC_DESC_LEN
+#include "aprs_tt.h"
+#endif
+
+typedef struct decode_aprs_s {
+
+        char g_src[AX25_MAX_ADDR_LEN];
+
+        char g_msg_type[60];		/* Message type.  Telemetry descriptions get pretty long. */
+
+        char g_symbol_table;		/* The Symbol Table Identifier character selects one */
+					/* of the two Symbol Tables, or it may be used as */
+					/* single-character (alpha or numeric) overlay, as follows: */
+					
+					/*	/ 	Primary Symbol Table (mostly stations) */
+
+					/* 	\ 	Alternate Symbol Table (mostly Objects) */
+
+					/*	0-9 	Numeric overlay. Symbol from Alternate Symbol */
+					/*		Table (uncompressed lat/long data format) */
+
+					/*	a-j	Numeric overlay. Symbol from Alternate */
+					/*		Symbol Table (compressed lat/long data */
+					/*		format only). i.e. a-j maps to 0-9 */
+
+					/*	A-Z	Alpha overlay. Symbol from Alternate Symbol Table */
+
+
+        char g_symbol_code;		/* Where the Symbol Table Identifier is 0-9 or A-Z (or a-j */
+					/* with compressed position data only), the symbol comes from */
+					/* the Alternate Symbol Table, and is overlaid with the */
+					/* identifier (as a single digit or a capital letter). */
+
+	char g_aprstt_loc[APRSTT_LOC_DESC_LEN];		/* APRStt location from !DAO! */
+
+        double g_lat, g_lon;		/* Location, degrees.  Negative for South or West. */
+					/* Set to G_UNKNOWN if missing or error. */
+
+        char g_maidenhead[9];		/* 4 or 6 (or 8?) character maidenhead locator. */
+
+        char g_name[20];			/* Object or item name. */
+
+        float g_speed;			/* Speed in MPH.  */
+
+        float g_course;			/* 0 = North, 90 = East, etc. */
+	
+        int g_power;			/* Transmitter power in watts. */
+
+        int g_height;			/* Antenna height above average terrain, feet. */
+
+        int g_gain;			/* Antenna gain in dB. */
+
+        char g_directivity[10];		/* Direction of max signal strength */
+
+        float g_range;			/* Precomputed radio range in miles. */
+
+        float g_altitude;		/* Feet above median sea level.  */
+
+        char g_mfr[80];			/* Manufacturer or application. */
+
+        char g_mic_e_status[30];		/* MIC-E message. */
+
+        double g_freq;			/* Frequency, MHz */
+
+        float g_tone;			/* CTCSS tone, Hz, one fractional digit */
+
+        int g_dcs;			/* Digital coded squelch, print as 3 octal digits. */
+
+        int g_offset;			/* Transmit offset, KHz */
+
+        char g_weather[500];		/* Weather.  Can get quite long. Rethink max size. */
+
+        char g_telemetry[256];		/* Telemetry data.  Rethink max size. */
+
+        char g_comment[256];		/* Comment. */
+
+} decode_aprs_t;
+
+
+
+
+
+extern void decode_aprs (decode_aprs_t *A, packet_t pp);
+
+extern void decode_aprs_print (decode_aprs_t *A);
+
+
+#endif
\ No newline at end of file
diff --git a/demod_afsk.c b/demod_afsk.c
index fb7e8f2..a3199b8 100755
--- a/demod_afsk.c
+++ b/demod_afsk.c
@@ -92,12 +92,7 @@ static inline float z (float x, float y)
 __attribute__((hot))
 static inline void push_sample (float val, float *buff, int size)
 {
-	int j;
-
-	// TODO: memmove any faster?  
-	for (j = size - 1; j >= 1; j--) {
-	  buff[j] = buff[j-1];
-	}
+	memmove(buff+1,buff,(size-1)*sizeof(float));
 	buff[0] = val; 
 }
 
diff --git a/digipeater.c b/digipeater.c
index dff36e9..b64189f 100755
--- a/digipeater.c
+++ b/digipeater.c
@@ -459,6 +459,44 @@ static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit
 
 
 
+/*------------------------------------------------------------------------------
+ *
+ * Name:	digi_regen
+ * 
+ * Purpose:	Send regenerated copy of what we received.
+ *
+ * Inputs:	chan	- Radio channel where it was received.
+ *		
+ * 		pp	- Packet object.
+ *		
+ * Returns:	None.
+ *
+ * Description:	TODO...
+ *		
+ *------------------------------------------------------------------------------*/
+
+void digi_regen (int from_chan, packet_t pp)
+{
+	int to_chan;
+	packet_t result;
+
+	// dw_printf ("digi_regen()\n");
+	
+	assert (from_chan >= 0 && from_chan < my_config.num_chans);
+
+	for (to_chan=0; to_chan<my_config.num_chans; to_chan++) {
+	  if (my_config.regen[from_chan][to_chan]) {
+	    result = ax25_dup (pp); 
+	    if (result != NULL) {
+	      // TODO:  if AX.25 and has been digipeated, put in HI queue?
+	      tq_append (to_chan, TQ_PRIO_1_LO, result);
+	    }
+	  }
+	}
+
+} /* end dig_regen */
+
+
 /*-------------------------------------------------------------------------
  *
  * Name:	main
@@ -509,7 +547,7 @@ static void test (char *in, char *out)
 
 	if (strcmp(in, rec) != 0) {
 	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Text/internal/text error %s -> %s\n", in, rec);
+	  dw_printf ("Text/internal/text error-1 %s -> %s\n", in, rec);
 	}
 
 /*
@@ -527,7 +565,7 @@ static void test (char *in, char *out)
 
 	if (strcmp(in, rec) != 0) {
 	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("internal/frame/internal/text error %s -> %s\n", in, rec);
+	  dw_printf ("internal/frame/internal/text error-2 %s -> %s\n", in, rec);
 	}
 
 /*
diff --git a/digipeater.h b/digipeater.h
index 48f4e1c..9e71114 100755
--- a/digipeater.h
+++ b/digipeater.h
@@ -41,6 +41,8 @@ struct digi_config_s {
 
 	enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
 
+	int regen[MAX_CHANS][MAX_CHANS];	// Regenerate packet.  
+						// Sort of like digipeating but passed along unchanged.
 };
 
 /*
@@ -56,6 +58,9 @@ extern void digipeater_init (struct digi_config_s *p_digi_config);
 
 extern void digipeater (int from_chan, packet_t pp);
 
+void digi_regen (int from_chan, packet_t pp);
+
+
 #endif 
 
 /* end digipeater.h */
diff --git a/direwolf.c b/direwolf.c
index 2edf403..c38ce8e 100755
--- a/direwolf.c
+++ b/direwolf.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011, 2012, 2013  John Langner, WB2OSZ
+//    Copyright (C) 2011, 2012, 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -42,6 +42,7 @@
 #include <assert.h>
 #include <string.h>
 #include <signal.h>
+#include <ctype.h>
 
 #if __WIN32__
 #else
@@ -49,7 +50,11 @@
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
+#ifdef __OpenBSD__
+#include <soundcard.h>
+#else
 #include <sys/soundcard.h>
+#endif
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netdb.h>
@@ -72,6 +77,7 @@
 #include "server.h"
 #include "kiss.h"
 #include "kissnet.h"
+#include "nmea.h"
 #include "gen_tone.h"
 #include "digipeater.h"
 #include "tq.h"
@@ -86,7 +92,11 @@
 #include "igate.h"
 #include "symbols.h"
 #include "dwgps.h"
+#include "nmea.h"
+#include "log.h"
+
 
+//static int idx_decoded = 0;
 
 #if __WIN32__
 static BOOL cleanup_win (int);
@@ -130,8 +140,10 @@ static void __cpuid(int cpuinfo[4], int infotype){
 
 static 	struct audio_s modem;
 
-static int d_u_opt = 0;			/* "-d u" command line option. */
+static int d_u_opt = 0;			/* "-d u" command line option to print UTF-8 also in hexadecimal. */
+static int d_p_opt = 0;			/* "-d p" option for dumping packets over radio. */				
 
+static struct misc_config_s misc_config;
 
 
 int main (int argc, char *argv[])
@@ -145,11 +157,18 @@ int main (int argc, char *argv[])
 	struct digi_config_s digi_config;
 	struct tt_config_s tt_config;
 	struct igate_config_s igate_config;
-	struct misc_config_s misc_config;
 	int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0;	/* Command line options. */
+	char l_opt[80];
 	char input_file[80];
 	
-	int t_opt = 1;							/* Text color option. */
+	int t_opt = 1;		/* Text color option. */				
+	int d_k_opt = 0;	/* "-d k" option for serial port KISS.  Can be repeated for more detail. */					
+	int d_n_opt = 0;	/* "-d n" option for Network KISS.  Can be repeated for more detail. */	
+	int d_t_opt = 0;	/* "-d t" option for Tracker.  Can be repeated for more detail. */	
+			
+	
+
+	strcpy(l_opt, "");
 
 
 #if __WIN32__
@@ -195,12 +214,9 @@ int main (int argc, char *argv[])
 
 	text_color_init(t_opt);
 	text_color_set(DW_COLOR_INFO);
-	//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 2\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
-	//dw_printf ("Dire Wolf version %d.%d (%s) Development version\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
-
-	// Note "a" for fix with beacon sent to IGate Server.
-
-	dw_printf ("Dire Wolf version %d.%da\n", MAJOR_VERSION, MINOR_VERSION);
+	//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 1\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
+	//dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "M", __DATE__);
+	dw_printf ("Dire Wolf version %d.%d, December 2014\n", MAJOR_VERSION, MINOR_VERSION);
 
 
 #if __WIN32__
@@ -272,6 +288,7 @@ int main (int argc, char *argv[])
           int this_option_optind = optind ? optind : 1;
           int option_index = 0;
 	  int c;
+	  char *p;
           static struct option long_options[] = {
             {"future1", 1, 0, 0},
             {"future2", 0, 0, 0},
@@ -281,7 +298,7 @@ int main (int argc, char *argv[])
 
 	  /* ':' following option character means arg is required. */
 
-          c = getopt_long(argc, argv, "B:D:c:pxr:b:n:d:t:U",
+          c = getopt_long(argc, argv, "B:D:c:pxr:b:n:d:t:Ul:",
                         long_options, &option_index);
           if (c == -1)
             break;
@@ -380,12 +397,26 @@ int main (int argc, char *argv[])
 
 	  case 'd':				/* Set debug option. */
 	
-	    switch (optarg[0]) {	
+	    /* New in 1.1.  Can combine multiple such as "-d pkk" */
+
+	    for (p=optarg; *p!='\0'; p++) {
+	     switch (*p) {
+	
 	      case 'a':  server_set_debug(1); break;
-	      case 'k':  kiss_serial_set_debug (1); break;
-	      case 'n':  kiss_net_set_debug (1); break;
+
+	      case 'k':  d_k_opt++; kiss_serial_set_debug (d_k_opt); break;
+	      case 'n':  d_n_opt++; kiss_net_set_debug (d_n_opt); break;
+
 	      case 'u':  d_u_opt = 1; break;
+
+		// separate out gps & waypoints.
+
+	      case 't':  d_t_opt++; beacon_tracker_set_debug (d_t_opt); break;
+
+	      case 'w':	 nmea_set_debug (1); break;		// not documented yet.
+	      case 'p':  d_p_opt = 1; break;			// TODO: packet dump for xmit side.
 	      default: break;
+	     }
 	    }
 	    break;
 	      
@@ -403,6 +434,10 @@ int main (int argc, char *argv[])
 	    exit (0);
 	    break;
 
+          case 'l':				/* -l for log file directory name */
+
+	    strncpy (l_opt, optarg, sizeof(l_opt)-1);
+            break;
 
           default:
 
@@ -471,6 +506,10 @@ int main (int argc, char *argv[])
 	    modem.decimate[0] = D_opt;
 	}
 
+	if (strlen(l_opt) > 0) {
+	  strncpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir)-1);
+	}
+
 	misc_config.enable_kiss_pt = enable_pseudo_terminal;
 
 	if (strlen(input_file) > 0) {
@@ -521,7 +560,7 @@ int main (int argc, char *argv[])
  * Initialize the transmit queue.
  */
 
-	xmit_init (&modem);
+	xmit_init (&modem, d_p_opt);
 
 /*
  * If -x option specified, transmit alternating tones for transmitter
@@ -565,6 +604,11 @@ int main (int argc, char *argv[])
  */
 	kiss_init (&misc_config);
 
+/*
+ * Open port for communication with GPS.
+ */
+	nmea_init (&misc_config);
+
 /* 
  * Create thread for trying to salvage frames with bad FCS.
  */
@@ -576,6 +620,8 @@ int main (int argc, char *argv[])
 	beacon_init (&misc_config, &digi_config);
 
 
+	log_init(misc_config.logdir);	
+
 /*
  * Get sound samples and decode them.
  * Use hot attribute for all functions called for every audio sample.
@@ -662,7 +708,8 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 
 	assert (chan >= 0 && chan < MAX_CHANS);
 	assert (subchan >= -1 && subchan < MAX_SUBCHANS);
-	     
+	assert (pp != NULL);	// 1.1J+
+     
 	  
 	ax25_format_addrs (pp, stemp);
 
@@ -687,6 +734,8 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 
 	  text_color_set(DW_COLOR_DEBUG);
 	  dw_printf ("\n");
+          //idx_decoded++;				
+          //dw_printf ("DECODED[%d] " , idx_decoded);
 
 	  if (h != -1 && h != AX25_SOURCE) {
 	    dw_printf ("Digipeater ");
@@ -720,6 +769,8 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 	  }
 	}
 
+
+
 // Display non-APRS packets in a different color.
 
 // Display subchannel only when multiple modems configured for channel.
@@ -746,10 +797,17 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 	}
 
 	dw_printf ("%s", stemp);			/* stations followed by : */
-	ax25_safe_print ((char *)pinfo, info_len, 0);
+
+	// for APRS we generally want to display non-ASCII to see UTF-8.
+	// for other, probably want to restrict to ASCII only because we are
+	// more likely to have compressed data than UTF-8 text.
+
+	// TODO: Might want to use d_u_opt for transmitted frames too.
+
+	ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
 	dw_printf ("\n");
 
-// Display in pure ASCII if non-ASCII characters and "-d u" option specified.
+// Also display in pure ASCII if non-ASCII characters and "-d u" option specified.
 
 	if (d_u_opt) {
 
@@ -767,12 +825,44 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 	  }
 	}
 
+/* Optional hex dump of packet. */
+
+	if (d_p_opt) {
+
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("------\n");
+	  ax25_hex_dump (pp);
+	  dw_printf ("------\n");
+	}
+
+
 /* Decode the contents of APRS frames and display in human-readable form. */
 
 	if (ax25_is_aprs(pp)) {
-	  decode_aprs (pp);
+
+	  decode_aprs_t A;
+
+	  decode_aprs (&A, pp);
+
+	  //Print it all out in human readable format.
+
+	  decode_aprs_print (&A);
+
+	  // Send to log file.
+
+	  log_write (chan, &A, pp, alevel, retries);
+
+	  // Convert to NMEA waypoint sentence.
+
+ 	  if (A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) {
+	    nmea_send_waypoint (strlen(A.g_name) > 0 ? A.g_name : A.g_src, 
+		A.g_lat, A.g_lon, A.g_symbol_table, A.g_symbol_code, 
+		DW_FEET_TO_METERS(A.g_altitude), A.g_course, DW_MPH_TO_KNOTS(A.g_speed), 
+		A.g_comment);
+	  }
 	}
 
+
 /* Send to another application if connected. */
 
 	int flen;
@@ -791,6 +881,11 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 	  igate_send_rec_packet (chan, pp);
 	}
 
+/* Send out a regenerated copy. Applies to all types, not just APRS. */
+
+	digi_regen (chan, pp);
+
+
 /* Note that packet can be modified in place so this is the last thing we should do with it. */
 /* Again, use only those with correct CRC. */
 /* We don't want to spread corrupted data! */
@@ -800,6 +895,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret
 	  digipeater (chan, pp);
 	}
 
+
 	ax25_delete (pp);
 	
 } /* end app_process_rec_packet */
@@ -814,6 +910,7 @@ static BOOL cleanup_win (int ctrltype)
 	if (ctrltype == CTRL_C_EVENT || ctrltype == CTRL_CLOSE_EVENT) {
 	  text_color_set(DW_COLOR_INFO);
 	  dw_printf ("\nQRT\n");
+	  log_term ();
 	  ptt_term ();
 	  dwgps_term ();
 	  SLEEP_SEC(1);
@@ -829,8 +926,10 @@ static void cleanup_linux (int x)
 {
 	text_color_set(DW_COLOR_INFO);
 	dw_printf ("\nQRT\n");
+	log_term ();
 	ptt_term ();
 	dwgps_term ();
+	SLEEP_SEC(1);
 	exit(0);
 }
 
@@ -848,6 +947,7 @@ static void usage (char **argv)
 	dw_printf ("Usage: direwolf [options]\n");
 	dw_printf ("Options:\n");
 	dw_printf ("    -c fname       Configuration file name.\n");
+	dw_printf ("    -l logdir      Directory name for log files.  Use . for current.\n");
 
 	dw_printf ("    -r n           Audio sample rate, per sec.\n");
 	dw_printf ("    -n n           Number of audio channels, 1 or 2.\n");
@@ -857,11 +957,13 @@ static void usage (char **argv)
 	dw_printf ("                     If > 2400, K9NG/G3RUH style encoding is used.\n");
 	dw_printf ("                     Otherwise, AFSK tones are set to 1200 & 2200.\n");
 
-	dw_printf ("    -d             Debug communication with client application, one of\n");
-	dw_printf ("       a             a = AGWPE network protocol.\n");
-	dw_printf ("       k             k = KISS serial port.\n");
-	dw_printf ("       n             n = KISS network.\n");
+	dw_printf ("    -d             Debug options:\n");
+	dw_printf ("       a             a = AGWPE network protocol client.\n");
+	dw_printf ("       k             k = KISS serial port client.\n");
+	dw_printf ("       n             n = KISS network client.\n");
 	dw_printf ("       u             u = Display non-ASCII text in hexadecimal.\n");
+	dw_printf ("       p             p = dump Packets in hexadecimal.\n");
+	dw_printf ("       t             t = gps Tracker.\n");
 
 	dw_printf ("    -t n           Text colors.  1=normal, 0=disabled.\n");
 
diff --git a/direwolf.conf b/direwolf.conf
index 87aa694..220c0e4 100755
--- a/direwolf.conf
+++ b/direwolf.conf
@@ -24,7 +24,7 @@
 #	(3)	DIGIPEATER  -  configure digipeating rules.
 #
 #			Look for lines starting with DIGIPEATER.
-#			Most people will probably use the first example.
+#			Most people will probably use the given example.
 #			Just remove the "#" from the start of the line
 #			to enable it.
 #
@@ -113,7 +113,8 @@
 # Something different must be specified for output.
 
 # ADEVICE - plughw:1,0
-# ADEVICE UDP:7355 default
+# ADEVICE UDP:7355 default
+
 
 #
 # This is the sound card audio sample rate.
@@ -163,13 +164,15 @@ CHANNEL 0
 # Example (don't use this unless you are me):  MYCALL	WB2OSZ-5
 #
 
-MYCALL NOCALL
+MYCALL NOCALL
+
+
 
 #
 # VHF FM operation normally uses 1200 baud data with AFSK tones of 1200 and 2200 Hz.
 # 
 
-MODEM 1200 1200 2200 
+MODEM 1200 1200 2200
 
 #
 # 200 Hz shift is normally used for 300 baud HF SSB operation.
@@ -254,7 +257,7 @@ CHANNEL 1
 
 MYCALL NOCALL
 
-MODEM 1200 1200 2200 
+MODEM 1200 1200 2200
 
 #
 # For this example, we use the same serial port for both
@@ -277,7 +280,7 @@ TXTAIL 10
 #
 # Dire Wolf acts as a virtual TNC and can communicate with
 # two different protocols:
-#	- the �AGW TCPIP Socket Interface� - default port 8000
+#	- the "AGW TCPIP Socket Interface" - default port 8000
 #	- KISS TNC via serial port
 #	- KISS protocol over TCP socket - default port 8001
 #
@@ -321,15 +324,12 @@ KISSPORT 8001
 
 
 #
-# Version 0.6 adds a new feature where it is sometimes possible
-# to recover frames with a bad FCS.  Several levels of effort
-# are possible.  
+# It is sometimes possible to recover frames with a bad FCS.  
 #
-#	0 [NONE] - Don't try to repair.
-#	1 [SINGLE] - Attempt to fix single bit error.  (default)
-#	2 [DOUBLE] - Also attempt to fix two adjacent bits.
-#	3 [TRIPLE] - Also attempt to fix three adjacent bits.
-#	4 [TWO_SEP] - Also attempt to fix two non-adjacent (separated) bits.
+#	0  [NONE] - Don't try to repair.
+#	1  [SINGLE] - Attempt to fix single bit error.  (default)
+#	2  [DOUBLE] - Also attempt to fix two adjacent bits.
+#	... see User Guide for more values and in-depth discussion.
 #
 
 FIX_BITS 1
@@ -358,10 +358,16 @@ FIX_BITS 1
 # The others are kept local.
 #
 
+#PBEACON delay=00:10 every=0:30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 
+
 #PBEACON delay=00:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 
 #PBEACON delay=10:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
 #PBEACON delay=20:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
 
+# With UTM coordinates instead of latitude and longitude.
+
+#PBEACON delay=00:15 every=10 overlay=S symbol="digi" zone=19T easting=306130 northing=4726010 
+
 #
 # Modify this for your particular situation before removing 
 # the # comment character from the beginning of the lines above.
@@ -375,97 +381,15 @@ FIX_BITS 1
 #############################################################
 
 #
-# Digipeating is activated with commands of the form:
-#
-#	DIGIPEAT from-chan to-chan aliases wide [ preemptive ]
-#
-# where,
-#
-#	from-chan	is the channel where the packet is received.
-#
-#	to-chan		is the channel where the packet is to be re-transmitted.
-#
-#	aliases		is a pattern for digipeating ONCE.  Anything matching
-#			this pattern is effectively treated like WIDE1-1.
-#			'MYCALL' for the receiving channel is an implied
-#			member of this list.
-#
-#	wide		is the pattern for normal WIDEn-N digipeating
-#			where the ssid is decremented. 
-#
-#	preemptive	is the "preemptive" digipeating option.  See
-#			User Guide for more details.
-#
-# Pattern matching uses "extended regular expressions."  Rather than listing 
-# all the different possibilities (such as "WIDE3-3,WIDE4-4,WIDE5-5,WIDE6-6,WIDE7-7"),
-# a pattern can be specified such as "^WIDE[34567]-[1-7]$".  This means:
-#
-#	^	beginning of call.  Without this, leading characters 
-#		don't need to match and ZWIDE3-3 would end up matching.
-#
-#	WIDE	is an exact literal match of upper case letters W I D E.
-#
-#	[34567]	means ANY ONE of the characters listed.
-#
-#	-	is an exact literal match of the "-" character (when not
-#		found inside of []).
-#
-# 	[1-7]	is an alternative form where we have a range of characters
-#		rather than listing them all individually.
-#
-#	$	means end of call.  Without this, trailing characters don't
-#		need to match.  As an example, we would end up matching 
-#		WIDE3-15 besides WIDE3-1.
-#
-# Google "Extended Regular Expressions" for more information.
-#
-
-#
-# If the first unused digipeater field, in the received packet,
-# matches the first pattern, it is treated the same way you
-# would expect WIDE1-1 to behave.
-#
-# The digipeater name is replaced by MYCALL of the destination channel.
-#
-# Example:	W1ABC>APRS,WIDE7-7
-# Becomes:	W1ABC>APRS,WB2OSZ-5*
-#
-# In this example, we trap large values of N as recommended in 
-# http://www.aprs.org/fix14439.html
-#
-
-#
-# If not caught by the first pattern, see if it matches the second pattern.
-#
-# Matches will be processed with the usual WIDEn-N rules.
-# 
-# If N >= 2, the N value is decremented and MYCALL (of the destination
-# 		channel) is inserted if enough room.
-#
-# Example:	W1ABC>APRS,WIDE2-2
-# Becomes:	W1ABC>APRS,WB2OSZ-5*,WIDE2-1
-#
-# If N = 1, we don't want to keep WIDEn-0 in the digipeater list so
-#		the station is replaced by MYCALL.
-#
-# Example:	W1ABC>APRS,W9XYZ*,WIDE2-1
-# Becomes:	W1ABC>APRS,W9XYZ,WB2OSZ-5*
-#
-
-#-------------------------------------------------------
-# ----------  Example 1:  Typical digipeater  ----------
-#-------------------------------------------------------
-
-#
 # For most common situations, use something like this by removing
-# the "#" from the beginning of the line.
-# To disable digipeating, put # at the beginning of the line.
-# 
-
-# DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE
-
+# the "#" from the beginning of the line below.
+#
 
+#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE
 
+# See User Guide for more explanation of what this means and how
+# it can be customized for your particular needs.
+ 
 
 #############################################################
 #                                                           #
@@ -512,7 +436,7 @@ FIX_BITS 1
 
 #IGFILTER m/50 
 
-# Finally, we don�t want to flood the radio channel.  
+# Finally, we don't want to flood the radio channel.  
 # The IGate function will limit the number of packets transmitted 
 # during 1 minute and 5 minute intervals.   If a limit would 
 # be exceeded, the packet is dropped and message is displayed in red.
@@ -530,7 +454,7 @@ IGTXLIMIT 6 10
 # Dire Wolf can receive DTMF (commonly known as Touch Tone)
 # messages and convert them to packet objects.
 #
-# See "APRStt-Implementation-Notes" document for details.
+# See separate "APRStt-Implementation-Notes" document for details.
 #
 
 #
@@ -571,8 +495,8 @@ TTUTM  B6xxxyyy  19T  10  300000  4720000
 TTCORRAL   37^55.50N  81^7.00W  0^0.02N
 
 # Compact messages - Fixed locations xx and object yyy where 
-#   	Object numbers 100 � 199	= bicycle	
-#	Object numbers 200 � 299	= fire truck
+#   	Object numbers 100 - 199	= bicycle	
+#	Object numbers 200 - 299	= fire truck
 #	Others				= dog
 
 TTMACRO  xx1yy  B9xx*AB166*AA2B4C5B3B0A1yy
diff --git a/direwolf.h b/direwolf.h
index e78c3a5..18fe639 100755
--- a/direwolf.h
+++ b/direwolf.h
@@ -29,11 +29,29 @@
 #define SLEEP_MS(n) usleep((n)*1000)
 #endif
 
-#endif
 
 #if __WIN32__
 #define PTW32_STATIC_LIB
 #include "pthreads/pthread.h"
 #else
 #include <pthread.h>
-#endif
\ No newline at end of file
+#endif
+
+
+/* Not sure where to put these. */
+
+/* Prefix with DW_ because /usr/include/gps.h uses a couple of these names. */
+
+
+#define DW_METERS_TO_FEET(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 3.2808399)
+#define DW_FEET_TO_METERS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.3048)
+#define DW_KM_TO_MILES(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.621371192)
+
+#define DW_KNOTS_TO_MPH(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 1.15077945)
+#define DW_KNOTS_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.51444444444)
+#define DW_MPH_TO_KNOTS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.868976)
+#define DW_MPH_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.44704)
+
+#define DW_MBAR_TO_INHG(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.0295333727)
+
+#endif   /* ifndef DIREWOLF_H */
\ No newline at end of file
diff --git a/dsp.c b/dsp.c
index 9f58726..95f113a 100755
--- a/dsp.c
+++ b/dsp.c
@@ -128,7 +128,7 @@ float window (bp_window_t type, int size, int j)
 void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype)
 {
 	int j;
-	float lp_sum;
+	float G;
 
 
 #if DEBUG1
@@ -165,12 +165,12 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype
 /*
  * Normalize lowpass for unity gain.
  */
-	lp_sum = 0;
+	G = 0;
         for (j=0; j<filter_size; j++) {
-	  lp_sum += lp_filter[j];
+	  G += lp_filter[j];
 	}
         for (j=0; j<filter_size; j++) {
-	  lp_filter[j] = lp_filter[j] / lp_sum;
+	  lp_filter[j] = lp_filter[j] / G;
 	}
 }
 
@@ -198,7 +198,8 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype
 void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
 {
 	int j;
-	float bp_sum;
+	float w;
+	float G;
 
 
 #if DEBUG1
@@ -235,13 +236,18 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win
 
 /*
  * Normalize bandpass for unity gain.
+ * TODO:  This is not right.  
+ * Can't use same technique as for lowpass.
+ * Instead compute gain in middle of passband.
+ * See http://dsp.stackexchange.com/questions/4693/fir-filter-gain
  */
-	bp_sum = 0;
+	w = 2 * M_PI * (f1 + f2) / 2;
+	G = 0;
         for (j=0; j<filter_size; j++) {
-	  bp_sum += bp_filter[j];
+	  G += bp_filter[j];	// TBD
 	}
         for (j=0; j<filter_size; j++) {
-	  bp_filter[j] = bp_filter[j] / bp_sum;
+	  bp_filter[j] = bp_filter[j] / G;
 	}
 }
 
diff --git a/dwgps.c b/dwgps.c
index 2f5904a..ceb2f07 100755
--- a/dwgps.c
+++ b/dwgps.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2013  John Langner, WB2OSZ
+//    Copyright (C) 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -108,6 +108,10 @@ int dwgps_init (void)
 
 #if __WIN32__
 
+/*
+ * Windows version.  Not implemented yet.
+ */
+
 	text_color_set(DW_COLOR_ERROR);
 	dw_printf ("GPS interface not yet available in Windows version.\n");
 	init_status = INIT_FAILED;
@@ -117,11 +121,39 @@ int dwgps_init (void)
 
 	int err;
 
+#if USE_GPS_SHM
+
+/*
+ * Linux - Shared memory interface to gpsd.
+ *
+ * I wanted to use this method because it is simpler and more efficient.
+ *
+ * The current version of gpsd, supplied with Raspian, is 3.6 from back in 
+ * May 2012, is missing support for the shared memory interface.  
+ * https://github.com/raspberrypi/linux/issues/523
+ *
+ * I tried to download a newer source and build with shared memory support
+ * but ran into a couple other issues.
+ * 
+ * 	sudo apt-get install libncurses5-dev
+ * 	sudo apt-get install scons
+ * 	cd ~
+ * 	wget http://download-mirror.savannah.gnu.org/releases/gpsd/gpsd-3.11.tar.gz
+ * 	tar xfz gpsd-3.11.tar.gz
+ * 	cd gpsd-3.11
+ * 	scons prefix=/usr libdir=lib/arm-linux-gnueabihf shm_export=True python=False
+ * 	sudo scons udev-install
+ * 
+ * For now, we will use the socket interface.
+ * Maybe get back to this again someday.
+ */
+
 	err = gps_open (GPSD_SHARED_MEMORY, NULL, &gpsdata);
 	if (err != 0) {
 	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Unable to connect to GPS receiver.\n");
+	  dw_printf ("Unable to connect to GPSD shared memory interface, status=%d.\n", err);
 	  if (err == NL_NOHOST) {
+	    // I don't think this is right but we are not using it anyhow.
 	    dw_printf ("Shared memory interface is not enabled in libgps.\n");
 	    dw_printf ("Download the gpsd source and build with 'shm_export=True' option.\n");
 	  }
@@ -133,8 +165,31 @@ int dwgps_init (void)
 	}
 	init_status = INIT_SUCCESS;
 	return (0);
+
 #else
 
+/* 
+ * Linux - Socket interface to gpsd.
+ */
+
+	err = gps_open ("localhost", DEFAULT_GPSD_PORT, &gpsdata);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Unable to connect to GPSD stream, status%d.\n", err);
+	  dw_printf ("%s\n", gps_errstr(errno));
+	  init_status = INIT_FAILED;
+	  return (-1);
+	}
+
+	gps_stream(&gpsdata, WATCH_ENABLE | WATCH_JSON, NULL);
+
+	init_status = INIT_SUCCESS;
+	return (0);
+
+#endif 
+
+#else	/* end ENABLE_GPS */
+
 	text_color_set(DW_COLOR_ERROR);
 	dw_printf ("GPS interface not enabled in this version.\n");
 	dw_printf ("See documentation on how to rebuild with ENABLE_GPS.\n");
@@ -160,7 +215,7 @@ int dwgps_init (void)
  *		*palt		- Altitude, meters.
  *
  * Returns:	-1 = error
- *		0 = data not available (no fix)
+ *		0 = location currently not available (no fix)
  *		2 = 2D fix, lat/lon, speed, and course are set.
  *		3 - 3D fix, altitude is also set.
  *
@@ -184,12 +239,42 @@ int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float
 	  return (-1);
 	}
 
+#if USE_GPS_SHM
+
+/*
+ * Shared memory version.
+ */
+
 	err = gps_read (&gpsdata);
 
 #if DEBUG
 	dw_printf ("gps_read returns %d bytes\n", err);
 #endif
+
+#else
+
+/* 
+ * Socket version.
+ */
+
+	// Wait for up to 1000 milliseconds.
+	// This should only happen in the beaconing thread so 
+	// I'm not worried about other functions hanging.
+
+        if (gps_waiting(&gpsdata, 1000)) {
+
+	  err = gps_read (&gpsdata);
+	}
+	else {
+	  gps_stream(&gpsdata, WATCH_ENABLE | WATCH_JSON, NULL);
+	  sleep (1);
+	}
+
+#endif
+
 	if (err > 0) {
+	  /* Data is available. */
+
 	  if (gpsdata.status >= STATUS_FIX && gpsdata.fix.mode >= MODE_2D) {
 
 	     *plat = gpsdata.fix.latitude;
@@ -208,10 +293,12 @@ int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float
 	   return (0);
 	}
 	else if (err == 0) {
-	   /* No data available */
+
+	   /* No data available at the present time. */
 	   return (0);
 	}
 	else {
+
 	  /* More serious error. */
 	  return (-1);
 	}
@@ -244,6 +331,10 @@ void dwgps_term (void) {
 #elif ENABLE_GPS
 
 	if (init_status == INIT_SUCCESS) {
+
+#ifndef USE_GPS_SHM
+	  gps_stream(&gpsdata, WATCH_DISABLE, NULL);
+#endif
 	  gps_close (&gpsdata);
 	}
 #else 
@@ -264,6 +355,7 @@ void dwgps_term (void) {
  * Description: Compile with -DTEST option.
  *
  *			gcc -DTEST dwgps.c textcolor.c -lgps
+ *			./a.out
  *
  *--------------------------------------------------------------------*/
 
diff --git a/encode_aprs.c b/encode_aprs.c
index 638ac2d..307c11a 100755
--- a/encode_aprs.c
+++ b/encode_aprs.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2013  John Langner, WB2OSZ
+//    Copyright (C) 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -443,9 +443,12 @@ static int frequency_spec (float freq, float tone, float offset, char *presult)
  *
  * Purpose:     Construct info part for position report format.
  *
- * Inputs:      compressed - Send in compressed form?
+ * Inputs:      messaging - This determines whether the data type indicator 
+ *			   is set to '!' (false) or '=' (true).
+ *		compressed - Send in compressed form?
  *		lat	- Latitude.
  *		lon	- Longitude.
+ *		alt_ft	- Altitude in feet.
  *		symtab	- Symbol table id or overlay.
  *		symbol	- Symbol id.
  *
@@ -494,7 +497,7 @@ typedef struct aprs_compressed_pos_s {
 } aprs_compressed_pos_t;
 
 
-int encode_position (int compressed, double lat, double lon, 
+int encode_position (int messaging, int compressed, double lat, double lon, int alt_ft, 
 		char symtab, char symbol, 
 		int power, int height, int gain, char *dir,
 		int course, int speed,
@@ -507,7 +510,7 @@ int encode_position (int compressed, double lat, double lon,
 	if (compressed) {
 	  aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult;
 
-	  p->dti = '!';
+	  p->dti = messaging ? '=' : '!';
 	  set_comp_position (symtab, symbol, lat, lon, 
 		power, height, gain, 
 		course, speed,
@@ -517,7 +520,7 @@ int encode_position (int compressed, double lat, double lon,
 	else {
 	  aprs_ll_pos_t *p = (aprs_ll_pos_t *)presult;
 
-	  p->dti = '!';
+	  p->dti = messaging ? '=' : '!';
 	  set_norm_position (symtab, symbol, lat, lon, &(p->pos));
 	  result_len = 1 + sizeof (p->pos);
 
@@ -540,6 +543,19 @@ int encode_position (int compressed, double lat, double lon,
 
 	presult[result_len] = '\0';
 
+/* Altitude.  Can be anywhere in comment. */
+
+	if (alt_ft != G_UNKNOWN) {
+	  char salt[12];
+	  /* Not clear if altitude can be negative. */
+	  /* Be sure it will be converted to 6 digits. */
+	  if (alt_ft < 0) alt_ft = 0;
+	  if (alt_ft > 999999) alt_ft = 999999;
+	  sprintf (salt, "/A=%06d", alt_ft);
+	  strcat (presult, salt);
+	  result_len += strlen(salt);
+	}
+
 /* Finally, comment text. */
 	
 	if (comment != NULL) {
@@ -696,17 +712,13 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
  *
  * Description:	Just a smattering, not an organized test.
  *
- * 		$ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c ; ./a.exe
+ * 		$ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c textcolor.c ; ./a.exe
  *
  *----------------------------------------------------------------*/
 
 
 #if EN_MAIN
 
-void text_color_set ( enum dw_color_e c )
-{
-        return;
-} 
 
 int main (int argc, char *argv[])
 {
@@ -716,75 +728,86 @@ int main (int argc, char *argv[])
 
 /***********  Position  ***********/
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!4234.61ND07126.47W&") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
 /* with PHG. */
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
 /* with freq. */
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		0, 0, 0, NULL, 0, 0, 146.955, 74.4, -0.6, NULL, result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
 /* with course/speed, freq, and comment! */
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
 /* Course speed, no tone, + offset */
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 River flooding") != 0) dw_printf ("ERROR!\n");
-
+	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 River flooding") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
+/* Course speed, no tone, + offset + altitude */
 
+	encode_position (0, 42+34.61/60, -(71+26.47/60), 12345, 'D', '&', 
+		0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result);
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 /A=012345River flooding") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
 
 /*********** Compressed position. ***********/
 
-	encode_position (1, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (1, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!D8yKC<Hn[&   ") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!D8yKC<Hn[&  !") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
 
-/* with PHG. In this case it is converted to precomputed radio range. */
+/* with PHG. In this case it is converted to precomputed radio range.  TODO: check on this.  Is 27.4 correct? */
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (1, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&PHG7368   TBD ???") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!D8yKC<Hn[&{CG") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
-/* with course/speed, freq, and comment! */
+/* with course/speed, freq, and comment!  TODO:  check on this 55 knots should be 63 MPH.  we get 62. */
 
-	encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
+	encode_position (1, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', 
 		0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, "!D8yKC<Hn[&  !146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
+//$ echo 'A>B:!D8yKC<Hn[&NUG146.955MHz T074 -060 River flooding' | decode_aprs
 
+//A>B:!D8yKC<Hn[&NUG146.955MHz T074 -060 River flooding
+//Position, I=Igte IGate R=RX T=1hopTX 2=2hopTX w/overlay D
+//N 42 34.6100, W 071 26.4700, 62 MPH, course 180, 146.955 MHz, -600k, PL 74.4
+// River flooding
 
-/*********** Object. ***********/
+// TODO:  test alt; cs+alt
 
+/*********** Object. ***********/
 
 	encode_object ("WB1GOF-C", 0, 0, 42+34.61/60, -(71+26.47/60), 'D', '&', 
-		0, 0, 0, NULL, result);
+		0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result);
 	dw_printf ("%s\n", result);
-	if (strcmp(result, ";WB1GOF-C *111111z4234.61ND07126.47W&   TBD???") != 0) dw_printf ("ERROR!\n");
+	if (strcmp(result, ";WB1GOF-C *111111z4234.61ND07126.47W&") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
 
+// TODO: need more tests.
 
 	return(0);
 
diff --git a/encode_aprs.h b/encode_aprs.h
index 1d3c828..198cecf 100755
--- a/encode_aprs.h
+++ b/encode_aprs.h
@@ -1,5 +1,5 @@
 
-int encode_position (int compressed, double lat, double lon, 
+int encode_position (int messaging, int compressed, double lat, double lon, int alt_ft,
 		char symtab, char symbol, 
 		int power, int height, int gain, char *dir,
 		int course, int speed,
diff --git a/fsk_demod_state.h b/fsk_demod_state.h
index 9185361..769f4e5 100755
--- a/fsk_demod_state.h
+++ b/fsk_demod_state.h
@@ -2,6 +2,9 @@
 
 #ifndef FSK_DEMOD_STATE_H
 
+#include "rpack.h"
+
+
 /*
  * Demodulator state.
  * Different copy is required for each channel & subchannel being processed concurrently.
@@ -166,6 +169,47 @@ struct demodulator_state_s
 	float lev_prev_peak;
 	float lev_prev_ave;
 
+/* 
+ * Special for Rino decoder only.
+ * One for each possible signal polarity.
+ */
+
+#if 1
+
+	struct gr_state_s {
+
+	  signed int data_clock_pll;		// PLL for data clock recovery.
+						// It is incremented by pll_step_per_sample
+						// for each audio sample.
+  
+	  signed int prev_d_c_pll;		// Previous value of above, before
+						// incrementing, to detect overflows.
+
+	  float gr_minus_peak;	// For automatic gain control.
+	  float gr_plus_peak;
+
+	  int gr_sync;		// Is sync pulse present?
+	  int gr_prev_sync;	// Previous state to detect leading edge.
+
+	  int gr_first_sample;	// Index of starting sample index for debugging.
+
+	  int gr_dcd;		// Data carrier detect.  i.e. are we 
+				// currently decoding a message.
+
+	  float gr_early_sum;	// For averaging bit values in two regions.
+	  int gr_early_count;
+	  float gr_late_sum;
+	  int gr_late_count;
+	  float gr_sync_sum;
+	  int gr_sync_count;
+
+	  int gr_bit_count;	// Bit index into message.
+
+	  struct rpack_s rpack;	// Collection of bits.
+
+	} gr_state[2];
+#endif
+
 };
 
 #define FSK_DEMOD_STATE_H 1
diff --git a/grm_sym.h b/grm_sym.h
new file mode 100755
index 0000000..838a4ef
--- /dev/null
+++ b/grm_sym.h
@@ -0,0 +1,501 @@
+
+/*
+ * grm_sym.h
+ *
+ * Symbol codes for use in $PGRMWPL sentence. 
+ *
+ * Copied from 
+ *	Garmin Device Interface Specification
+ *	May 19, 2006
+ *	Drawing Number: 001-00063-00 Rev. C
+ */
+
+
+typedef unsigned short symbol_type_t;
+
+enum symbol_type_e
+{
+/*---------------------------------------------------------------
+Marine symbols
+---------------------------------------------------------------*/
+sym_anchor = 0, /* white anchor symbol */
+sym_bell = 1, /* white bell symbol */
+sym_diamond_grn = 2, /* green diamond symbol */
+sym_diamond_red = 3, /* red diamond symbol */
+sym_dive1 = 4, /* diver down flag 1 */
+sym_dive2 = 5, /* diver down flag 2 */
+sym_dollar = 6, /* white dollar symbol */
+sym_fish = 7, /* white fish symbol */
+sym_fuel = 8, /* white fuel symbol */
+sym_horn = 9, /* white horn symbol */
+sym_house = 10, /* white house symbol */
+sym_knife = 11, /* white knife & fork symbol */
+sym_light = 12, /* white light symbol */
+sym_mug = 13, /* white mug symbol */
+sym_skull = 14, /* white skull and crossbones symbol*/
+sym_square_grn = 15, /* green square symbol */
+sym_square_red = 16, /* red square symbol */
+sym_wbuoy = 17, /* white buoy waypoint symbol */
+sym_wpt_dot = 18, /* waypoint dot */
+sym_wreck = 19, /* white wreck symbol */
+sym_null = 20, /* null symbol (transparent) */
+sym_mob = 21, /* man overboard symbol */
+sym_buoy_ambr = 22, /* amber map buoy symbol */
+sym_buoy_blck = 23, /* black map buoy symbol */
+sym_buoy_blue = 24, /* blue map buoy symbol */
+sym_buoy_grn = 25, /* green map buoy symbol */
+sym_buoy_grn_red = 26, /* green/red map buoy symbol */
+sym_buoy_grn_wht = 27, /* green/white map buoy symbol */
+sym_buoy_orng = 28, /* orange map buoy symbol */
+sym_buoy_red = 29, /* red map buoy symbol */
+sym_buoy_red_grn = 30, /* red/green map buoy symbol */
+sym_buoy_red_wht = 31, /* red/white map buoy symbol */
+sym_buoy_violet = 32, /* violet map buoy symbol */
+sym_buoy_wht = 33, /* white map buoy symbol */
+sym_buoy_wht_grn = 34, /* white/green map buoy symbol */
+sym_buoy_wht_red = 35, /* white/red map buoy symbol */
+sym_dot = 36, /* white dot symbol */
+sym_rbcn = 37, /* radio beacon symbol */
+sym_boat_ramp = 150, /* boat ramp symbol */
+sym_camp = 151, /* campground symbol */
+sym_restrooms = 152, /* restrooms symbol */
+sym_showers = 153, /* shower symbol */
+sym_drinking_wtr = 154, /* drinking water symbol */
+sym_phone = 155, /* telephone symbol */
+sym_1st_aid = 156, /* first aid symbol */
+sym_info = 157, /* information symbol */
+sym_parking = 158, /* parking symbol */
+sym_park = 159, /* park symbol */
+sym_picnic = 160, /* picnic symbol */
+sym_scenic = 161, /* scenic area symbol */
+sym_skiing = 162, /* skiing symbol */
+sym_swimming = 163, /* swimming symbol */
+sym_dam = 164, /* dam symbol */
+sym_controlled = 165, /* controlled area symbol */
+sym_danger = 166, /* danger symbol */
+sym_restricted = 167, /* restricted area symbol */
+sym_null_2 = 168, /* null symbol */
+sym_ball = 169, /* ball symbol */
+sym_car = 170, /* car symbol */
+sym_deer = 171, /* deer symbol */
+sym_shpng_cart = 172, /* shopping cart symbol */
+sym_lodging = 173, /* lodging symbol */
+sym_mine = 174, /* mine symbol */
+sym_trail_head = 175, /* trail head symbol */
+sym_truck_stop = 176, /* truck stop symbol */
+sym_user_exit = 177, /* user exit symbol */
+sym_flag = 178, /* flag symbol */
+sym_circle_x = 179, /* circle with x in the center */
+sym_open_24hr = 180, /* open 24 hours symbol */
+sym_fhs_facility = 181, /* U Fishing Hot Spots� Facility */
+sym_bot_cond = 182, /* Bottom Conditions */
+sym_tide_pred_stn = 183, /* Tide/Current Prediction Station */
+sym_anchor_prohib = 184, /* U anchor prohibited symbol */
+sym_beacon = 185, /* U beacon symbol */
+sym_coast_guard = 186, /* U coast guard symbol */
+sym_reef = 187, /* U reef symbol */
+sym_weedbed = 188, /* U weedbed symbol */
+sym_dropoff = 189, /* U dropoff symbol */
+sym_dock = 190, /* U dock symbol */
+sym_marina = 191, /* U marina symbol */
+sym_bait_tackle = 192, /* U bait and tackle symbol */
+sym_stump = 193, /* U stump symbol */
+/*---------------------------------------------------------------
+User customizable symbols
+The values from sym_begin_custom to sym_end_custom inclusive are
+reserved for the identification of user customizable symbols.
+---------------------------------------------------------------*/
+sym_begin_custom = 7680, /* first user customizable symbol */
+sym_end_custom = 8191, /* last user customizable symbol */
+/*---------------------------------------------------------------
+Land symbols
+---------------------------------------------------------------*/
+sym_is_hwy = 8192, /* interstate hwy symbol */
+sym_us_hwy = 8193, /* us hwy symbol */
+sym_st_hwy = 8194, /* state hwy symbol */
+sym_mi_mrkr = 8195, /* mile marker symbol */
+sym_trcbck = 8196, /* TracBack (feet) symbol */
+sym_golf = 8197, /* golf symbol */
+sym_sml_cty = 8198, /* small city symbol */
+sym_med_cty = 8199, /* medium city symbol */
+sym_lrg_cty = 8200, /* large city symbol */
+sym_freeway = 8201, /* intl freeway hwy symbol */
+sym_ntl_hwy = 8202, /* intl national hwy symbol */
+sym_cap_cty = 8203, /* capitol city symbol (star) */
+sym_amuse_pk = 8204, /* amusement park symbol */
+sym_bowling = 8205, /* bowling symbol */
+sym_car_rental = 8206, /* car rental symbol */
+sym_car_repair = 8207, /* car repair symbol */
+sym_fastfood = 8208, /* fast food symbol */
+sym_fitness = 8209, /* fitness symbol */
+sym_movie = 8210, /* movie symbol */
+sym_museum = 8211, /* museum symbol */
+sym_pharmacy = 8212, /* pharmacy symbol */
+sym_pizza = 8213, /* pizza symbol */
+sym_post_ofc = 8214, /* post office symbol */
+sym_rv_park = 8215, /* RV park symbol */
+sym_school = 8216, /* school symbol */
+sym_stadium = 8217, /* stadium symbol */
+sym_store = 8218, /* dept. store symbol */
+sym_zoo = 8219, /* zoo symbol */
+sym_gas_plus = 8220, /* convenience store symbol */
+sym_faces = 8221, /* live theater symbol */
+sym_ramp_int = 8222, /* ramp intersection symbol */
+sym_st_int = 8223, /* street intersection symbol */
+sym_weigh_sttn = 8226, /* inspection/weigh station symbol */
+sym_toll_booth = 8227, /* toll booth symbol */
+sym_elev_pt = 8228, /* elevation point symbol */
+sym_ex_no_srvc = 8229, /* exit without services symbol */
+sym_geo_place_mm = 8230, /* Geographic place name, man-made */
+sym_geo_place_wtr = 8231, /* Geographic place name, water */
+sym_geo_place_lnd = 8232, /* Geographic place name, land */
+sym_bridge = 8233, /* bridge symbol */
+sym_building = 8234, /* building symbol */
+sym_cemetery = 8235, /* cemetery symbol */
+sym_church = 8236, /* church symbol */
+sym_civil = 8237, /* civil location symbol */
+sym_crossing = 8238, /* crossing symbol */
+sym_hist_town = 8239, /* historical town symbol */
+sym_levee = 8240, /* levee symbol */
+sym_military = 8241, /* military location symbol */
+sym_oil_field = 8242, /* oil field symbol */
+sym_tunnel = 8243, /* tunnel symbol */
+sym_beach = 8244, /* beach symbol */
+sym_forest = 8245, /* forest symbol */
+sym_summit = 8246, /* summit symbol */
+sym_lrg_ramp_int = 8247, /* large ramp intersection symbol */
+sym_lrg_ex_no_srvc = 8248, /* large exit without services smbl */
+sym_badge = 8249, /* police/official badge symbol */
+sym_cards = 8250, /* gambling/casino symbol */
+sym_snowski = 8251, /* snow skiing symbol */
+sym_iceskate = 8252, /* ice skating symbol */
+sym_wrecker = 8253, /* tow truck (wrecker) symbol */
+sym_border = 8254, /* border crossing (port of entry) */
+sym_geocache = 8255, /* geocache location */
+sym_geocache_fnd = 8256, /* found geocache */
+sym_cntct_smiley = 8257, /* Rino contact symbol, "smiley" */
+sym_cntct_ball_cap = 8258, /* Rino contact symbol, "ball cap" */
+sym_cntct_big_ears = 8259, /* Rino contact symbol, "big ear" */
+sym_cntct_spike = 8260, /* Rino contact symbol, "spike" */
+sym_cntct_goatee = 8261, /* Rino contact symbol, "goatee" */
+sym_cntct_afro = 8262, /* Rino contact symbol, "afro" */
+sym_cntct_dreads = 8263, /* Rino contact symbol, "dreads" */
+sym_cntct_female1 = 8264, /* Rino contact symbol, "female 1" */
+sym_cntct_female2 = 8265, /* Rino contact symbol, "female 2" */
+sym_cntct_female3 = 8266, /* Rino contact symbol, "female 3" */
+sym_cntct_ranger = 8267, /* Rino contact symbol, "ranger" */
+sym_cntct_kung_fu = 8268, /* Rino contact symbol, "kung fu" */
+sym_cntct_sumo = 8269, /* Rino contact symbol, "sumo" */
+sym_cntct_pirate = 8270, /* Rino contact symbol, "pirate" */
+sym_cntct_biker = 8271, /* Rino contact symbol, "biker" */
+sym_cntct_alien = 8272, /* Rino contact symbol, "alien" */
+sym_cntct_bug = 8273, /* Rino contact symbol, "bug" */
+sym_cntct_cat = 8274, /* Rino contact symbol, "cat" */
+sym_cntct_dog = 8275, /* Rino contact symbol, "dog" */
+sym_cntct_pig = 8276, /* Rino contact symbol, "pig" */
+sym_hydrant = 8282, /* water hydrant symbol */
+sym_flag_blue = 8284, /* blue flag symbol */
+sym_flag_green = 8285, /* green flag symbol */
+sym_flag_red = 8286, /* red flag symbol */
+sym_pin_blue = 8287, /* blue pin symbol */
+sym_pin_green = 8288, /* green pin symbol */
+sym_pin_red = 8289, /* red pin symbol */
+sym_block_blue = 8290, /* blue block symbol */
+sym_block_green = 8291, /* green block symbol */
+sym_block_red = 8292, /* red block symbol */
+sym_bike_trail = 8293, /* bike trail symbol */
+sym_circle_red = 8294, /* red circle symbol */
+sym_circle_green = 8295, /* green circle symbol */
+sym_circle_blue = 8296, /* blue circle symbol */
+sym_diamond_blue = 8299, /* blue diamond symbol */
+sym_oval_red = 8300, /* red oval symbol */
+sym_oval_green = 8301, /* green oval symbol */
+sym_oval_blue = 8302, /* blue oval symbol */
+sym_rect_red = 8303, /* red rectangle symbol */
+sym_rect_green = 8304, /* green rectangle symbol */
+sym_rect_blue = 8305, /* blue rectangle symbol */
+sym_square_blue = 8308, /* blue square symbol */
+sym_letter_a_red = 8309, /* red letter 'A' symbol */
+sym_letter_b_red = 8310, /* red letter 'B' symbol */
+sym_letter_c_red = 8311, /* red letter 'C' symbol */
+sym_letter_d_red = 8312, /* red letter 'D' symbol */
+sym_letter_a_green = 8313, /* green letter 'A' symbol */
+sym_letter_c_green = 8314, /* green letter 'C' symbol */
+sym_letter_b_green = 8315, /* green letter 'B' symbol */
+sym_letter_d_green = 8316, /* green letter 'D' symbol */
+sym_letter_a_blue = 8317, /* blue letter 'A' symbol */
+sym_letter_b_blue = 8318, /* blue letter 'B' symbol */
+sym_letter_c_blue = 8319, /* blue letter 'C' symbol */
+sym_letter_d_blue = 8320, /* blue letter 'D' symbol */
+sym_number_0_red = 8321, /* red number '0' symbol */
+sym_number_1_red = 8322, /* red number '1' symbol */
+sym_number_2_red = 8323, /* red number '2' symbol */
+sym_number_3_red = 8324, /* red number '3' symbol */
+sym_number_4_red = 8325, /* red number '4' symbol */
+sym_number_5_red = 8326, /* red number '5' symbol */
+sym_number_6_red = 8327, /* red number '6' symbol */
+sym_number_7_red = 8328, /* red number '7' symbol */
+sym_number_8_red = 8329, /* red number '8' symbol */
+sym_number_9_red = 8330, /* red number '9' symbol */
+sym_number_0_green = 8331, /* green number '0' symbol */
+sym_number_1_green = 8332, /* green number '1' symbol */
+sym_number_2_green = 8333, /* green number '2' symbol */
+sym_number_3_green = 8334, /* green number '3' symbol */
+sym_number_4_green = 8335, /* green number '4' symbol */
+sym_number_5_green = 8336, /* green number '5' symbol */
+sym_number_6_green = 8337, /* green number '6' symbol */
+sym_number_7_green = 8338, /* green number '7' symbol */
+sym_number_8_green = 8339, /* green number '8' symbol */
+sym_number_9_green = 8340, /* green number '9' symbol */
+sym_number_0_blue = 8341, /* blue number '0' symbol */
+sym_number_1_blue = 8342, /* blue number '1' symbol */
+sym_number_2_blue = 8343, /* blue number '2' symbol */
+sym_number_3_blue = 8344, /* blue number '3' symbol */
+sym_number_4_blue = 8345, /* blue number '4' symbol */
+sym_number_5_blue = 8346, /* blue number '5' symbol */
+sym_number_6_blue = 8347, /* blue number '6' symbol */
+sym_number_7_blue = 8348, /* blue number '7' symbol */
+sym_number_8_blue = 8349, /* blue number '8' symbol */
+sym_number_9_blue = 8350, /* blue number '9' symbol */
+sym_triangle_blue = 8351, /* blue triangle symbol */
+sym_triangle_green = 8352, /* green triangle symbol */
+sym_triangle_red = 8353, /* red triangle symbol */
+sym_food_asian = 8359, /* asian food symbol */
+sym_food_deli = 8360, /* deli symbol */
+sym_food_italian = 8361, /* italian food symbol */
+sym_food_seafood = 8362, /* seafood symbol */
+sym_food_steak = 8363, /* steak symbol */
+/*---------------------------------------------------------------
+Aviation symbols
+---------------------------------------------------------------*/
+sym_airport = 16384, /* airport symbol */
+sym_int = 16385, /* intersection symbol */
+sym_ndb = 16386, /* non-directional beacon symbol */
+sym_vor = 16387, /* VHF omni-range symbol */
+sym_heliport = 16388, /* heliport symbol */
+sym_private = 16389, /* private field symbol */
+sym_soft_fld = 16390, /* soft field symbol */
+sym_tall_tower = 16391, /* tall tower symbol */
+sym_short_tower = 16392, /* short tower symbol */
+sym_glider = 16393, /* glider symbol */
+sym_ultralight = 16394, /* ultralight symbol */
+sym_parachute = 16395, /* parachute symbol */
+sym_vortac = 16396, /* VOR/TACAN symbol */
+sym_vordme = 16397, /* VOR-DME symbol */
+sym_faf = 16398, /* first approach fix */
+sym_lom = 16399, /* localizer outer marker */
+sym_map = 16400, /* missed approach point */
+sym_tacan = 16401, /* TACAN symbol */
+sym_seaplane = 16402, /* Seaplane Base */
+};
+
+
+
+/*
+ * Mapping from APRS symbols to Garmin.
+ */
+
+// TODO:  NEEDS MORE WORK!!!
+
+
+#define SYMTAB_SIZE 95
+
+#define sym_default sym_diamond_grn
+
+
+static const symbol_type_t grm_primary_symtab[SYMTAB_SIZE] =  {
+
+	sym_default,		//     00  	 --no-symbol--
+	sym_cntct_ranger,	//  !  01  	 Police, Sheriff
+	sym_default,		//  "  02  	 reserved  (was rain)
+	sym_rbcn,		//  #  03  	 DIGI (white center)
+	sym_phone,		//  $  04  	 PHONE
+	sym_rbcn,		//  %  05  	 DX CLUSTER
+	sym_rbcn,		//  &  06  	 HF GATEway
+	sym_glider,		//  '  07  	 Small AIRCRAFT
+	sym_rbcn,		//  (  08  	 Mobile Satellite Station
+	sym_default,		//  )  09  	 Wheelchair (handicapped)
+	sym_car,		//  *  10  	 SnowMobile
+	sym_1st_aid,		//  +  11  	 Red Cross
+	sym_cntct_ball_cap,	//  ,  12  	 Boy Scouts
+	sym_house,		//  -  13  	 House QTH (VHF)
+	sym_default,		//  .  14  	 X
+	sym_default,		//  /  15  	 Red Dot
+	sym_default,		//  0  16  	 # circle (obsolete)
+	sym_default,		//  1  17  	 TBD
+	sym_default,		//  2  18  	 TBD
+	sym_default,		//  3  19  	 TBD
+	sym_default,		//  4  20  	 TBD
+	sym_default,		//  5  21  	 TBD
+	sym_default,		//  6  22  	 TBD
+	sym_default,		//  7  23  	 TBD
+	sym_default,		//  8  24  	 TBD
+	sym_default,		//  9  25  	 TBD
+	sym_default,		//  :  26  	 FIRE
+	sym_camp,		//  ;  27  	 Campground (Portable ops)
+	sym_cntct_biker,	//  <  28  	 Motorcycle
+	sym_default,		//  =  29  	 RAILROAD ENGINE
+	sym_car,		//  >  30  	 CAR
+	sym_default,		//  ?  31  	 SERVER for Files
+	sym_default,		//  @  32  	 HC FUTURE predict (dot)
+	sym_1st_aid,		//  A  33  	 Aid Station
+	sym_rbcn,		//  B  34  	 BBS or PBBS
+	sym_boat_ramp,		//  C  35  	 Canoe
+	sym_default,		//  D  36  	 
+	sym_default,		//  E  37  	 EYEBALL (Eye catcher!)
+	sym_default,		//  F  38  	 Farm Vehicle (tractor)
+	sym_default,		//  G  39  	 Grid Square (6 digit)
+	sym_lodging,		//  H  40  	 HOTEL (blue bed symbol)
+	sym_rbcn,		//  I  41  	 TcpIp on air network stn
+	sym_default,		//  J  42  	 
+	sym_school,		//  K  43  	 School
+	sym_default,		//  L  44  	 PC user
+	sym_default,		//  M  45  	 MacAPRS
+	sym_default,		//  N  46  	 NTS Station
+	sym_parachute,		//  O  47  	 BALLOON
+	sym_cntct_ranger,	//  P  48  	 Police
+	sym_default,		//  Q  49  	 TBD
+	sym_rv_park,		//  R  50  	 REC. VEHICLE
+	sym_glider,		//  S  51  	 SHUTTLE
+	sym_default,		//  T  52  	 SSTV
+	sym_car,		//  U  53  	 BUS
+	sym_cntct_biker,	//  V  54  	 ATV
+	sym_default,		//  W  55  	 National WX Service Site
+	sym_default,		//  X  56  	 HELO
+	sym_default,		//  Y  57  	 YACHT (sail)
+	sym_default,		//  Z  58  	 WinAPRS
+	sym_cntct_smiley,	//  [  59  	 Human/Person (HT)
+	sym_triangle_green,	//  \  60  	 TRIANGLE(DF station)
+	sym_default,		//  ]  61  	 MAIL/PostOffice(was PBBS)
+	sym_glider,		//  ^  62  	 LARGE AIRCRAFT
+	sym_default,		//  _  63  	 WEATHER Station (blue)
+	sym_rbcn,		//  `  64  	 Dish Antenna
+	sym_1st_aid,		//  a  65  	 AMBULANCE
+	sym_cntct_biker,	//  b  66  	 BIKE
+	sym_default,		//  c  67  	 Incident Command Post
+	sym_hydrant,		//  d  68  	 Fire dept
+	sym_deer,		//  e  69  	 HORSE (equestrian)
+	sym_hydrant,		//  f  70  	 FIRE TRUCK
+	sym_glider,		//  g  71  	 Glider
+	sym_1st_aid,		//  h  72  	 HOSPITAL
+	sym_default,		//  i  73  	 IOTA (islands on the air)
+	sym_car,		//  j  74  	 JEEP
+	sym_car,		//  k  75  	 TRUCK
+	sym_default,		//  l  76  	 Laptop
+	sym_rbcn,		//  m  77  	 Mic-E Repeater
+	sym_default,		//  n  78  	 Node (black bulls-eye)
+	sym_default,		//  o  79  	 EOC
+	sym_cntct_dog,		//  p  80  	 ROVER (puppy, or dog)
+	sym_default,		//  q  81  	 GRID SQ shown above 128 m
+	sym_rbcn,		//  r  82  	 Repeater
+	sym_default,		//  s  83  	 SHIP (pwr boat)
+	sym_truck_stop,		//  t  84  	 TRUCK STOP
+	sym_truck_stop,		//  u  85  	 TRUCK (18 wheeler)
+	sym_car,		//  v  86  	 VAN
+	sym_drinking_wtr,	//  w  87  	 WATER station
+	sym_default,		//  x  88  	 xAPRS (Unix)
+	sym_tall_tower,		//  y  89  	 YAGI @ QTH
+	sym_default,		//  z  90  	 TBD
+	sym_default,		//  {  91  	 
+	sym_default,		//  |  92  	 TNC Stream Switch
+	sym_default,		//  }  93  	 
+	sym_default };		//  ~  94  	 TNC Stream Switch
+
+static const symbol_type_t grm_alternate_symtab[SYMTAB_SIZE] =  {
+
+	sym_default,		//     00  	 --no-symbol--
+	sym_default,		//  !  01  	 EMERGENCY (!)
+	sym_default,		//  "  02  	 reserved
+	sym_default,		//  #  03  	 OVERLAY DIGI (green star)
+	sym_default,		//  $  04  	 Bank or ATM  (green box)
+	sym_default,		//  %  05  	 Power Plant with overlay
+	sym_rbcn,		//  &  06  	 I=Igte IGate R=RX T=1hopTX 2=2hopTX
+	sym_default,		//  '  07  	 Crash (& now Incident sites)
+	sym_default,		//  (  08  	 CLOUDY (other clouds w ovrly)
+	sym_hydrant,		//  )  09  	 Firenet MEO, MODIS Earth Obs.
+	sym_default,		//  *  10  	 SNOW (& future ovrly codes)
+	sym_default,		//  +  11  	 Church
+	sym_cntct_female1,	//  ,  12  	 Girl Scouts
+	sym_house,		//  -  13  	 House (H=HF) (O = Op Present)
+	sym_default,		//  .  14  	 Ambiguous (Big Question mark)
+	sym_default,		//  /  15  	 Waypoint Destination
+	sym_default,		//  0  16  	 CIRCLE (E/I/W=IRLP/Echolink/WIRES)
+	sym_default,		//  1  17  	 
+	sym_default,		//  2  18  	 
+	sym_default,		//  3  19  	
+	sym_default,		//  4  20  
+	sym_default,		//  5  21 
+	sym_default,		//  6  22
+	sym_default,		//  7  23
+	sym_default,		//  8  24  	 802.11 or other network node
+	sym_default,		//  9  25  	 Gas Station (blue pump)
+	sym_default,		//  :  26  	 Hail (& future ovrly codes)
+	sym_park,		//  ;  27  	 Park/Picnic area
+	sym_default,		//  <  28  	 ADVISORY (one WX flag)
+	sym_rbcn,		//  =  29  	 APRStt Touchtone (DTMF users)
+	sym_car,		//  >  30  	 OVERLAYED CAR
+	sym_default,		//  ?  31  	 INFO Kiosk  (Blue box with ?)
+	sym_default,		//  @  32  	 HURICANE/Trop-Storm
+	sym_default,		//  A  33  	 overlayBOX DTMF & RFID & XO
+	sym_default,		//  B  34  	 Blwng Snow (& future codes)
+	sym_coast_guard,	//  C  35  	 Coast Guard
+	sym_default,		//  D  36  	 Drizzle (proposed APRStt)
+	sym_default,		//  E  37  	 Smoke (& other vis codes)
+	sym_default,		//  F  38  	 Freezng rain (&future codes)
+	sym_default,		//  G  39  	 Snow Shwr (& future ovrlys)
+	sym_default,		//  H  40  	 Haze (& Overlay Hazards)
+	sym_default,		//  I  41  	 Rain Shower
+	sym_default,		//  J  42  	 Lightening (& future ovrlys)
+	sym_rbcn,		//  K  43  	 Kenwood HT (W)
+	sym_light,		//  L  44  	 Lighthouse
+	sym_default,		//  M  45  	 MARS (A=Army,N=Navy,F=AF)
+	sym_default,		//  N  46  	 Navigation Buoy
+	sym_default,		//  O  47  	 Rocket
+	sym_default,		//  P  48  	 Parking
+	sym_default,		//  Q  49  	 QUAKE
+	sym_default,		//  R  50  	 Restaurant
+	sym_rbcn,		//  S  51  	 Satellite/Pacsat
+	sym_default,		//  T  52  	 Thunderstorm
+	sym_default,		//  U  53  	 SUNNY
+	sym_default,		//  V  54  	 VORTAC Nav Aid
+	sym_default,		//  W  55  	 # NWS site (NWS options)
+	sym_pharmacy,		//  X  56  	 Pharmacy Rx (Apothicary)
+	sym_rbcn,		//  Y  57  	 Radios and devices
+	sym_default,		//  Z  58  	 
+	sym_default,		//  [  59  	 W.Cloud (& humans w Ovrly)
+	sym_default,		//  \  60  	 New overlayable GPS symbol
+	sym_default,		//  ]  61  	 
+	sym_glider,		//  ^  62  	 # Aircraft (shows heading)
+	sym_default,		//  _  63  	 # WX site (green digi)
+	sym_default,		//  `  64  	 Rain (all types w ovrly)
+	sym_default,		//  a  65  	 ARRL, ARES, WinLINK
+	sym_default,		//  b  66  	 Blwng Dst/Snd (& others)
+	sym_default,		//  c  67  	 CD triangle RACES/SATERN/etc
+	sym_default,		//  d  68  	 DX spot by callsign
+	sym_default,		//  e  69  	 Sleet (& future ovrly codes)
+	sym_default,		//  f  70  	 Funnel Cloud
+	sym_default,		//  g  71  	 Gale Flags
+	sym_default,		//  h  72  	 Store. or HAMFST Hh=HAM store
+	sym_default,		//  i  73  	 BOX or points of Interest
+	sym_default,		//  j  74  	 WorkZone (Steam Shovel)
+	sym_car,		//  k  75  	 Special Vehicle SUV,ATV,4x4
+	sym_default,		//  l  76  	 Areas      (box,circles,etc)
+	sym_default,		//  m  77  	 Value Sign (3 digit display)
+	sym_default,		//  n  78  	 OVERLAY TRIANGLE
+	sym_default,		//  o  79  	 small circle
+	sym_default,		//  p  80  	 Prtly Cldy (& future ovrlys)
+	sym_default,		//  q  81  	 
+	sym_restrooms,		//  r  82  	 Restrooms
+	sym_default,		//  s  83  	 OVERLAY SHIP/boat (top view)
+	sym_default,		//  t  84  	 Tornado
+	sym_car,		//  u  85  	 OVERLAYED TRUCK
+	sym_car,		//  v  86  	 OVERLAYED Van
+	sym_default,		//  w  87  	 Flooding
+	sym_wreck,		//  x  88  	 Wreck or Obstruction ->X<-
+	sym_default,		//  y  89  	 Skywarn
+	sym_default,		//  z  90  	 OVERLAYED Shelter
+	sym_default,		//  {  91  	 Fog (& future ovrly codes)
+	sym_default,		//  |  92  	 TNC Stream Switch
+	sym_default,		//  }  93  	 
+	sym_default };		//  ~  94  	 TNC Stream Switch
+
diff --git a/hdlc_rec.c b/hdlc_rec.c
index 0a5c7f3..6a16e19 100755
--- a/hdlc_rec.c
+++ b/hdlc_rec.c
@@ -367,6 +367,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram
 	    int alevel = demod_get_audio_level (chan, subchan);
 
 	    rrbb_set_audio_level (H->rrbb, alevel);
+	    rrbb_set_fix_bits (H->rrbb, H->fix_bits);
 	    hdlc_rec2_block (H->rrbb, H->fix_bits);
 	    	/* Now owned by someone else who will free it. */
 	    H->rrbb = rrbb_new (chan, subchan, is_scrambled, descram_state); /* Allocate a new one. */
diff --git a/hdlc_rec2.c b/hdlc_rec2.c
index 629fa31..ec5dcbd 100755
--- a/hdlc_rec2.c
+++ b/hdlc_rec2.c
@@ -1,7 +1,6 @@
-//
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011, 2012, 2013  John Langner, WB2OSZ
+//    Copyright (C) 2011, 2012, 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -26,12 +25,32 @@
  *		else has done the work of pulling it out from between
  *		the special "flag" sequences.
  *
+ *
+ * New in version 1.1:
+ *
+ *		Several enhancements provided by Fabrice FAURE:
+ *
+ *		- Additional types of attempts to fix a bad CRC.
+ *		- Optimized code to reduce execution time.
+ *		- Improved detection of duplicate packets from different fixup attempts.
+ *		- Set limit on number of packets in fix up later queue.
+ *
+ *		One of the new recovery attempt cases recovers three additional 
+ *		packets that were lost before.  The one thing I disagree with is
+ *		use of the word "swap" because that sounds like two things 
+ *		are being exchanged for each other.  I would prefer "flip"
+ *		or "invert" to describe changing a bit to the opposite state.
+ *		I took "swap" out of the user-visible messages but left the 
+ *		rest of the source code as provided.
+ *
  *******************************************************************************/
 
 #include <stdio.h>
 #include <assert.h>
 #include <ctype.h>
 
+//Optimize processing by accessing directly to decoded bits
+#define RRBB_C 1
 #include "direwolf.h"
 #include "hdlc_rec2.h"
 #include "fcs_calc.h"
@@ -40,6 +59,9 @@
 #include "rrbb.h"
 #include "rdq.h"
 #include "multi_modem.h"
+//#define DEBUG 1
+//#define DEBUGx 1
+//#define DEBUG_LATER 1
 
 
 /* 
@@ -82,13 +104,13 @@ struct hdlc_state_s {
 
 };
 
+#define MAX_RETRY_SWAP_BITS 24		/* Maximum number of contiguous bits to swap */
+#define MAX_RETRY_REMOVE_SEPARATED_BITS 24	/* Maximum number of contiguous bits to remove */
 
-
-
-static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t bits_flipped, int flip_a, int flip_b, int flip_c);
+static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_conf_t retry_conf);
 static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits);
 static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped);
-#if DEBUG
+#if DEBUG_LATER
 static double dtime_now (void);
 #endif
 
@@ -161,8 +183,19 @@ void hdlc_rec2_block (rrbb_t block, retry_t fix_bits)
 	rrbb_set_slice_val (block, 0);
 
 #else /* not SLICENDICE */
-
-	ok = try_decode (block, chan, subchan, alevel, RETRY_NONE, -1, -1, -1);
+	/* Create an empty retry configuration */
+	retry_conf_t retry_cfg;
+
+	/* By default we don't try to alter any bits */
+	retry_cfg.type = RETRY_TYPE_NONE;
+	retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
+	retry_cfg.retry = RETRY_NONE;
+	retry_cfg.u_bits.contig.nr_bits = 0;
+	retry_cfg.u_bits.contig.bit_idx = 0;
+	/* Prepare the decoded bits in an array for faster processing 
+	 *(at cost of memory but 1 or 2 kbytes is nothing compared to processing time ) */
+	rrbb_compute_bits(block);
+	ok = try_decode (block, chan, subchan, alevel, retry_cfg);
 
 	if (ok) {
 #if DEBUG
@@ -183,7 +216,7 @@ void hdlc_rec2_block (rrbb_t block, retry_t fix_bits)
  * Put in queue for retrying later at lower priority.
  */
 
-	if (fix_bits < RETRY_TWO_SEP) {
+	if (fix_bits < RETRY_SWAP_TWO_SEP) {
 	  rrbb_delete (block); 
 	  return;
 	}
@@ -196,20 +229,29 @@ void hdlc_rec2_block (rrbb_t block, retry_t fix_bits)
 static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits)
 {
 	int ok;
-	int len, i;
+	int len, i,j;
 
 
 	len = rrbb_get_len(block);
-
+	/* Prepare the retry configuration */
+        retry_conf_t retry_cfg;
+	/* Will modify only contiguous bits*/
+	retry_cfg.mode = RETRY_MODE_CONTIGUOUS; 
 /* 
  * Try fixing one bit.   
  */
-	if (fix_bits < RETRY_SINGLE) {
+	if (fix_bits < RETRY_SWAP_SINGLE) {
 	  return 0;
 	}
+	/* Try to swap one bit */
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_SWAP_SINGLE;
+	retry_cfg.u_bits.contig.nr_bits = 1;
 
 	for (i=0; i<len; i++) {
-	  ok = try_decode (block, chan, subchan, alevel, RETRY_SINGLE, i, -1, -1);
+	  /* Set the index of the bit to swap */
+	  retry_cfg.u_bits.contig.bit_idx = i;
+	  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
 	  if (ok) {
 #if DEBUG
 	    text_color_set(DW_COLOR_ERROR);
@@ -222,12 +264,17 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel
 /* 
  * Try fixing two adjacent bits.  
  */
-	if (fix_bits < RETRY_DOUBLE) {
+	if (fix_bits < RETRY_SWAP_DOUBLE) {
 	  return 0;
 	}
+	/* Try to swap two contiguous bits */
+	retry_cfg.retry = RETRY_SWAP_DOUBLE;
+	retry_cfg.u_bits.contig.nr_bits = 2;
+
 
 	for (i=0; i<len-1; i++) {
-	  ok = try_decode (block, chan, subchan, alevel, RETRY_DOUBLE, i, i+1, -1);
+	  retry_cfg.u_bits.contig.bit_idx = i;
+	  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
 	  if (ok) {
 #if DEBUG
 	    text_color_set(DW_COLOR_ERROR);
@@ -240,13 +287,16 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel
 /*
  * Try fixing adjacent three bits.
  */
-	if (fix_bits < RETRY_TRIPLE) {
+	if (fix_bits < RETRY_SWAP_TRIPLE) {
 	  return 0;
 	}
+	/* Try to swap three contiguous bits */
+	retry_cfg.retry = RETRY_SWAP_TRIPLE;
+	retry_cfg.u_bits.contig.nr_bits = 3;
 
-	len = rrbb_get_len(block);
 	for (i=0; i<len-2; i++) {
-	  ok = try_decode (block, chan, subchan, alevel, RETRY_TRIPLE, i, i+1, i+2);
+	  retry_cfg.u_bits.contig.bit_idx = i;
+	  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
 	  if (ok) {
 #if DEBUG
 	    text_color_set(DW_COLOR_ERROR);
@@ -256,71 +306,362 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel
 	  }
 	}
 
+	if (fix_bits < RETRY_REMOVE_SINGLE) {
+	  return 0;
+	}
+
+/* 
+ * Try removing one bit.   
+ */
+		retry_cfg.type = RETRY_TYPE_REMOVE;
+		retry_cfg.retry = RETRY_REMOVE_SINGLE;
+		retry_cfg.u_bits.contig.nr_bits = 1;
+
+		for (i=0; i<len; i++) {
+		  retry_cfg.u_bits.contig.bit_idx = i;
+		  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+		  if (ok) {
+#if DEBUG
+		    text_color_set(DW_COLOR_ERROR);
+		    dw_printf ("*** Success by removing SINGLE bit %d of %d ***\n", i, len);
+#endif
+		    return 1;
+		  }
+		}
+	if (fix_bits < RETRY_REMOVE_DOUBLE) {
+	  return 0;
+	}
+
+
+/* 
+ * Try removing two contiguous bits.   
+ */
+#if DEBUG
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf ("*** Try removing DOUBLE bits *** for %d bits\n", len);
+#endif
+		retry_cfg.retry = RETRY_REMOVE_DOUBLE;
+		retry_cfg.u_bits.contig.nr_bits = 2;
+
+		for (i=0; i<len-1; i++) {
+	  	  retry_cfg.u_bits.contig.bit_idx = i;
+		  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+		  if (ok) {
+#if DEBUG
+		    text_color_set(DW_COLOR_ERROR);
+		    dw_printf ("*** Success by removing DOUBLE bits %d of %d ***\n", i, len);
+#endif
+		    return 1;
+		  }
+		}
+	if (fix_bits < RETRY_REMOVE_TRIPLE) {
+	  return 0;
+	}
+
+/* 
+ * Try removing three contiguous bits.
+ */
+#if DEBUG
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf ("*** Try removing TRIPLE bits *** for %d bits\n", len);
+#endif
+		retry_cfg.retry = RETRY_REMOVE_TRIPLE;
+		retry_cfg.u_bits.contig.nr_bits = 3;
+
+		for (i=0; i<len-2; i++) {
+	  	  retry_cfg.u_bits.contig.bit_idx = i;
+		  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+		  if (ok) {
+#if DEBUG
+		    text_color_set(DW_COLOR_ERROR);
+		    dw_printf ("*** Success by removing TRIPLE bits %d of %d ***\n", i, len);
+#endif
+		    return 1;
+		  }
+		}
+	if (fix_bits < RETRY_INSERT_SINGLE) {
+	  return 0;
+	}
+
+/* 
+ * Try inserting one bit (two values possibles for this inserted bit).   
+ */
+#if DEBUG
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf ("*** Try inserting SINGLE bit *** for %d bits\n", len);
+#endif
+		retry_cfg.type = RETRY_TYPE_INSERT;
+		retry_cfg.retry = RETRY_INSERT_SINGLE;
+		retry_cfg.u_bits.contig.nr_bits = 1;
+
+
+		for (i=0; i<len; i++) {
+	  	  retry_cfg.u_bits.contig.bit_idx = i;
+		  retry_cfg.insert_value=0;
+		  ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+		  if (!ok) {
+		    retry_cfg.insert_value=1;
+		    ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+		  }
+		  if (ok) {
+#if DEBUG
+		    text_color_set(DW_COLOR_ERROR);
+		    dw_printf ("*** Success by inserting SINGLE bit %d of %d ***\n", i, len);
+#endif
+		    return 1;
+		  }
+		}
+	if (fix_bits < RETRY_INSERT_DOUBLE) {
+	  return 0;
+	}
+
+/* 
+ * Try inserting two contiguous bits (4 possible values for two bits).   
+ */
+#if DEBUG
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf ("*** Try inserting DOUBLE bits *** for %d bits\n", len);
+#endif
+		retry_cfg.retry = RETRY_INSERT_DOUBLE;
+		retry_cfg.u_bits.contig.nr_bits = 2;
+
+
+		for (i=0; i<len-1; i++) {
+	  	  retry_cfg.u_bits.contig.bit_idx = i;
+		  for (j=0;j<4;j++) {
+		    retry_cfg.insert_value=j;
+		    ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+
+		    if (ok) {
+#if DEBUG
+		      text_color_set(DW_COLOR_ERROR);
+		      dw_printf ("*** Success by inserting DOUBLE bits %d of %d ***\n", i, len);
+#endif
+		      return 1;
+		    }
+		  }
+		}
 	return 0;
 }
 
 void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel)
 {
 	int ok;
-	int len, i;
-#if DEBUG
+	int len, i, j;
+	retry_t fix_bits;
+#if DEBUG_LATER
 	double tstart, tend;
 #endif
-
+	retry_conf_t retry_cfg;
 	len = rrbb_get_len(block);
+	fix_bits = rrbb_get_fix_bits (block);
 
+	if (fix_bits < RETRY_SWAP_TWO_SEP) {
+	  return ;
+	}
+
+
+	retry_cfg.mode = RETRY_MODE_SEPARATED; 
 /*
  * Two  non-adjacent ("separated") single bits.
  * It chews up a lot of CPU time.  Test takes 4 times longer to run.
  *
- * Ran up to 4.82 seconds for 1040 bits before giving up.
+ * Ran up to xx seconds (TODO check again with optimized code) seconds for 1040 bits before giving up .
  * Processing time is order N squared so time goes up rapidly with larger frames.
  */
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_SWAP_TWO_SEP;
+	retry_cfg.u_bits.sep.bit_idx_c = -1;
 
-#if DEBUG
+#ifdef DEBUG_LATER
 	tstart = dtime_now();
+	dw_printf ("*** Try flipping TWO SEPARATED BITS %d bits\n", len);
 #endif
 	len = rrbb_get_len(block);
 	for (i=0; i<len-2; i++) {
+	  retry_cfg.u_bits.sep.bit_idx_a = i;
 	  int j;
 
 	  ok = 0;
 	  for (j=i+2; j<len; j++) {
-	    ok = try_decode (block, chan, subchan, alevel, RETRY_TWO_SEP, i, j, -1);  
-	    if (ok)
+	    retry_cfg.u_bits.sep.bit_idx_b = j;
+	    ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+	    if (ok) {
 	      break;
+	    }
+
 	  }	  
 	  if (ok) {
 #if DEBUG
-	    tend = dtime_now();
 	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("*** Success by flipping TWO SEPARATED bits %d and %d of %d *** %.3f sec.\n", i, j, len, tend-tstart);
+	    dw_printf ("*** Success by flipping TWO SEPARATED bits %d and %d of %d \n", i, j, len);
 #endif
 	    return;
 	  }
 	}
-#if DEBUGx
+#if DEBUG_LATER
 	tend = dtime_now();
 	text_color_set(DW_COLOR_ERROR);
 	dw_printf ("*** No luck flipping TWO SEPARATED bits of %d *** %.3f sec.\n", len, tend-tstart);
 #endif
 
-	return;
-}
+	if (fix_bits < RETRY_SWAP_MANY) {
+	  return ;
+	}
+	/* Try to swap many contiguous bits */
+	retry_cfg.mode = RETRY_MODE_CONTIGUOUS; 
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_SWAP_MANY;
+
+#ifdef DEBUG_LATER
+	tstart = dtime_now();
+	dw_printf ("*** Try swapping many BITS %d bits\n", len);
+#endif
+	len = rrbb_get_len(block);
+	for (i=0; i<len; i++) {
+	  for (j=1; j<len-i && j < MAX_RETRY_SWAP_BITS;j++) {
+	    retry_cfg.u_bits.contig.bit_idx = i;
+	    retry_cfg.u_bits.contig.nr_bits = j;
+//	    dw_printf ("*** Trying swapping %d bits starting at %d of %d ***\n", j,i, len);
+	    ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+	    if (ok) {
+#if DEBUG
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("*** Success by swapping %d bits starting at %d of %d ***\n", j,i, len);
+#endif
+	      return ;
+	    }
+	  }
+	}
+#if DEBUG_LATER
+	tend = dtime_now();
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("*** No luck swapping many bits for len %d  in %.3f sec.\n",len, tend-tstart);
+#endif
+
+	if (fix_bits < RETRY_REMOVE_MANY) {
+	  return ;
+	}
+
+
+	/* Try to remove many contiguous bits */
+	retry_cfg.type = RETRY_TYPE_REMOVE;
+	retry_cfg.retry = RETRY_REMOVE_MANY;
+#ifdef DEBUG_LATER
+	tstart = dtime_now();
+	dw_printf ("*** Trying removing many bits for len\n", len);
+#endif
+
 
+	len = rrbb_get_len(block);
+	for (i=0; i<2; i++) {
+	  for (j=1; j<len-i && j<len/2;j++) {
+	    retry_cfg.u_bits.contig.bit_idx = i;
+	    retry_cfg.u_bits.contig.nr_bits = j;
+#ifdef DEBUG
+	    dw_printf ("*** Trying removing %d bits starting at %d of %d ***\n", j,i, len);
+#endif
+	    ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+	    if (ok) {
+#if DEBUG
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("*** Success by removing %d bits starting at %d of %d ***\n", j,i, len);
+#endif
+	      return ;
+	    }
+	  }
+	}
+#if DEBUG_LATER
+	tend = dtime_now();
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("*** No luck removing many bits for len %d *** in %.3f sec.\n", len, tend-tstart);
+#endif
+
+	if (fix_bits < RETRY_REMOVE_TWO_SEP) {
+	  return ;
+	}
+
+/*
+ * Try to remove Two  non-adjacent ("separated") single bits.
+ */
+	retry_cfg.mode = RETRY_MODE_SEPARATED; 
+	retry_cfg.type = RETRY_TYPE_REMOVE;
+	retry_cfg.retry = RETRY_REMOVE_TWO_SEP;
+	retry_cfg.u_bits.sep.bit_idx_c = -1;
+
+#if DEBUG_LATER 
+	tstart = dtime_now();
+	dw_printf ("*** Try removing TWO SEPARATED BITS %d bits\n", len);
+#endif
+	len = rrbb_get_len(block);
+	for (i=0; i<len-2; i++) {
+	  retry_cfg.u_bits.sep.bit_idx_a = i;
+	  int j;
+	  ok = 0;
+	  for (j=i+2; j<len && j - i < MAX_RETRY_REMOVE_SEPARATED_BITS; j++) {
+	    retry_cfg.u_bits.sep.bit_idx_b = j;
+	    ok = try_decode (block, chan, subchan, alevel, retry_cfg);
+	    if (ok) {
+	      break;
+	    }
 
+	  }	  
+	  if (ok) {
+#if DEBUG_LATER
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("*** Success by removing TWO SEPARATED bits %d and %d of %d \n", i, j, len);
+#endif
+	    return;
+	  }
+	}
+#if DEBUG_LATER
+	tend = dtime_now();
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("*** No luck removing TWO SEPARATED bits of %d *** %.3f sec.\n", len, tend-tstart);
+#endif
+	return;
+}
+/* Check if the specified index of bit has been modified with the current type of configuration
+ * Provide a specific implementation for contiguous mode to optimize number of tests done in the loop */
+inline static char is_contig_bit_modified(int bit_idx, retry_conf_t retry_conf) {
+	  int cont_bit_idx = retry_conf.u_bits.contig.bit_idx;
+	  int cont_nr_bits = retry_conf.u_bits.contig.nr_bits;
+
+	  if (bit_idx >= cont_bit_idx && (bit_idx < cont_bit_idx + cont_nr_bits )) 
+		return 1;
+	  else 
+		return 0;
+}
+/* Check  if the specified index of bit has been modified with the current type of configuration in separated bit index mode
+ * Provide a specific implementation for separated mode to optimize number of tests done in the loop */
+inline static char is_sep_bit_modified(int bit_idx, retry_conf_t retry_conf) {
+	  if (bit_idx == retry_conf.u_bits.sep.bit_idx_a || 
+	      bit_idx == retry_conf.u_bits.sep.bit_idx_b ||
+	      bit_idx == retry_conf.u_bits.sep.bit_idx_c)
+	    return 1;
+	  else
+	    return 0;
+}
 
+/* Get the bit value from a precalculated array to optimize access time in the loop */
+inline static unsigned int get_bit (const rrbb_t b,const unsigned int ind)
+{
+	return b->computed_data[ind];
+}
 
-static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t bits_flipped, int flip_a, int flip_b, int flip_c)
+static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_conf_t retry_conf)
 {
 	struct hdlc_state_s H;	
 	int blen;			/* Block length in bits. */
 	int i;
-	int raw;			/* From demodulator. */
-	int dbit;			/* Data bit after undoing NRZI. */
+	unsigned int raw;			/* From demodulator. */
+	int crc_failed = 1;
+	int retry_conf_mode = retry_conf.mode;
+	int retry_conf_type = retry_conf.type;
+	int retry_conf_retry = retry_conf.retry;
 
 
-	H.prev_raw = rrbb_get_bit (block, 0);	  /* Actually last bit of the */
+	H.prev_raw = get_bit (block, 0);	  /* Actually last bit of the */
 					/* opening flag so we can derive the */
 					/* first data bit.  */
 
@@ -328,8 +669,8 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t
 	/* This is the last bit of the "flag" pattern. */
 	/* If it was corrupted we wouldn't have detected */
 	/* the start of frame. */
-
-	if (0 == flip_a || 0 == flip_b || 0 == flip_c){
+	if (retry_conf.mode == RETRY_MODE_CONTIGUOUS && is_contig_bit_modified(0, retry_conf) ||
+	    retry_conf.mode == RETRY_MODE_SEPARATED && is_sep_bit_modified(0, retry_conf)) {
 	  H.prev_raw = ! H.prev_raw;
 	}
 
@@ -338,90 +679,127 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t
 	H.olen = 0;
 	H.frame_len = 0;
 
-	blen = rrbb_get_len (block);
+	blen = rrbb_get_len(block);
+	/* Prepare space for the inserted bits in contiguous mode (separated mode for insert is not supported yet) */
+	if (retry_conf.type == RETRY_TYPE_INSERT && retry_conf.mode == RETRY_MODE_CONTIGUOUS)
+		blen+=retry_conf.u_bits.contig.nr_bits;
 
 #if DEBUGx
 	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("try_decode: blen=%d\n", blen);
+        if (retry_conf.type == RETRY_TYPE_NONE) 
+        	dw_printf ("try_decode: blen=%d\n", blen);
 #endif
-
 	for (i=1; i<blen; i++) {
+	  /* Get the value for the current bit */
+	  raw = get_bit (block, i);
+	  /* If swap two sep mode , swap the bit if needed */
+	  if (retry_conf_retry == RETRY_SWAP_TWO_SEP) {
+	      if (is_sep_bit_modified(i, retry_conf))
+	        raw = ! raw;
+	  /* Else if remove two sep bits mode , remove the bit if needed */
+	  } else if (retry_conf_retry == RETRY_REMOVE_TWO_SEP) {
+	      if (is_sep_bit_modified(i, retry_conf))
+	         //Remove (ignore) this bit from the buffer!
+                 continue;
+	  }
+	  /* Else handle all the others contiguous modes */
+	  else if (retry_conf_mode == RETRY_MODE_CONTIGUOUS) {
+	    /* If contiguous remove, ignore this bit from the buffer */
+   	    if (retry_conf_type == RETRY_TYPE_REMOVE)  {
+	      if ( is_contig_bit_modified(i, retry_conf))
+	         //Remove (ignore) this bit from the buffer!
+                 continue;
+	    }
+	    /* If insert bits mode */
+            else if (retry_conf_type == RETRY_TYPE_INSERT) {
+	        int nr_bits = retry_conf.u_bits.contig.nr_bits;
+	        int bit_idx = retry_conf.u_bits.contig.bit_idx;
+		/* If bit is after the index to insert, use the existing bit value (but shifted from the array) */
+	        if (i >= bit_idx + nr_bits)
+	          raw = get_bit (block, i-nr_bits);
+		/* Else if this is a bit to insert, calculate the value of the bit from insert_value */
+	        else if (is_contig_bit_modified(i, retry_conf)) {
+	          raw = (retry_conf.insert_value >> (i-bit_idx)) & 1;
+/*        	  dw_printf ("raw is %d for i %d bit_idx %d insert_value %d\n", 
+	            raw, i, bit_idx, retry_conf.insert_value);*/
+	        /* Else use the original bit value from the buffer */
+	        } else {
+	          /* Already set before */
+		}
+	    /* If in swap mode */
+            } else if (retry_conf_type == RETRY_TYPE_SWAP) {
+	        /* If this is the bit to swap */
+	        if (is_contig_bit_modified(i, retry_conf))
+	          raw = ! raw;
+            } 
 
-	  raw = rrbb_get_bit (block, i);
-
-	  if (i == flip_a || i == flip_b || i == flip_c){
-	    raw = ! raw;
+	  } else {
 	  }
+/*
+ * Octets are sent LSB first.
+ * Shift the most recent 8 bits thru the pattern detector.
+ */
+	    H.pat_det >>= 1;
 
 /*
  * Using NRZI encoding,
  *   A '0' bit is represented by an inversion since previous bit.
  *   A '1' bit is represented by no change.
+ *   Note: this code can be factorized with the raw != H.prev_raw code at the cost of processing time 
  */
-
-	  dbit = (raw == H.prev_raw);
-	  H.prev_raw = raw;
-
-/*
- * Octets are sent LSB first.
- * Shift the most recent 8 bits thru the pattern detector.
- */
-	  H.pat_det >>= 1;
-	  if (dbit) {
-	    H.pat_det |= 0x80;
-	  }
-
-	  if (H.pat_det == 0x7e) {
-	    /* The special pattern 01111110 indicates beginning and ending of a frame. */
+	    if (raw == H.prev_raw) {
+	      H.pat_det |= 0x80;
+	      /* Valid data will never have 7 one bits in a row: exit. */
+	      if (H.pat_det == 0xfe) {
 #if DEBUGx
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("try_decode: found flag, i=%d\n", i);
+	        text_color_set(DW_COLOR_DEBUG);
+	        dw_printf ("try_decode: found abort, i=%d\n", i);
 #endif
-	    return 0;
-	  }
-	  else if (H.pat_det == 0xfe) {
-	    /* Valid data will never have 7 one bits in a row. */
+	        return 0;
+	      }
+	      H.oacc >>= 1;
+	      H.oacc |= 0x80;
+	    } else {
+	      H.prev_raw = raw;
+	      /* The special pattern 01111110 indicates beginning and ending of a frame: exit. */
+	      if (H.pat_det == 0x7e) {
 #if DEBUGx
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("try_decode: found abort, i=%d\n", i);
+	        text_color_set(DW_COLOR_DEBUG);
+	        dw_printf ("try_decode: found flag, i=%d\n", i);
 #endif
- 	    return 0;
-	  }
-	  else if ( (H.pat_det & 0xfc) == 0x7c ) {
+	      return 0;
 /*
  * If we have five '1' bits in a row, followed by a '0' bit,
  *
- *	0111110xx
+ *	011111xx
  *
  * the current '0' bit should be discarded because it was added for 
  * "bit stuffing."
  */
-	    ;
-	  } else {
+	
+	      } else if ( (H.pat_det >> 2) == 0x1f ) {
+	        continue;
+	      }
+	      H.oacc >>= 1;
+	    }
 
 /*
- * In all other cases, accumulate bits into octets, and complete octets
+ * Now accumulate bits into octets, and complete octets
  * into the frame buffer.
  */
 
-	    H.oacc >>= 1;
-	    if (dbit) {
-	      H.oacc |= 0x80;
-	    }
 	    H.olen++;
 
-	    if (H.olen == 8) {
+	    if (H.olen & 8) {
 	      H.olen = 0;
 
 	      if (H.frame_len < MAX_FRAME_LEN) {
-		H.frame_buf[H.frame_len] = H.oacc;
+	        H.frame_buf[H.frame_len] = H.oacc;
 		H.frame_len++;
+	      
 	      }
 	    }
-	  }
-
-	}	/* end of loop on all bits in block */
-
+	  }	/* end of loop on all bits in block */
 /* 
  * Do we have a minimum number of complete bytes?
  */
@@ -436,6 +814,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t
 	  unsigned short actual_fcs, expected_fcs;
 
 #if DEBUGx 
+        if (retry_conf.type == RETRY_TYPE_NONE) {
 	  int j;
 	  text_color_set(DW_COLOR_DEBUG);
 	  dw_printf ("NEW WAY: frame len = %d\n", H.frame_len);
@@ -443,6 +822,8 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t
 	    dw_printf ("  %02x", H.frame_buf[j]);
 	  }
 	  dw_printf ("\n");
+
+        }
 #endif
 	  /* Check FCS, low byte first, and process... */
 
@@ -457,7 +838,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t
 
 	  expected_fcs = fcs_calc (H.frame_buf, H.frame_len - 2);
 
-	  if (actual_fcs == expected_fcs && sanity_check (H.frame_buf, H.frame_len - 2, bits_flipped)) {
+	  if (actual_fcs == expected_fcs && sanity_check (H.frame_buf, H.frame_len - 2, retry_conf.retry)) {
 
 	
 	      // TODO: Shouldn't be necessary to pass chan, subchan, alevel into
@@ -468,10 +849,47 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t
 	      assert (rrbb_get_subchan(block) == subchan);
 	      assert (rrbb_get_audio_level(block) == alevel);
 
-	      multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, bits_flipped);   /* len-2 to remove FCS. */
+	      multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry);   /* len-2 to remove FCS. */
 	      return 1;		/* success */
-	  }
+	  } else {
+
+              goto failure;
+          }
+	} else {
+              crc_failed = 0;
+              goto failure;
 	}
+failure:
+#if DEBUGx
+        if (retry_conf.type == RETRY_TYPE_NONE ) {
+              int j;
+	      text_color_set(DW_COLOR_ERROR);
+              if (crc_failed)
+	            dw_printf ("CRC failed\n");
+	      if (H.olen != 0) 
+		      dw_printf ("Bad olen: %d \n", H.olen);
+	      else if (H.frame_len < MIN_FRAME_LEN) {
+		      dw_printf ("Frame too small\n");
+                      goto end;
+	      }
+
+	      dw_printf ("FAILURE with frame: frame len = %d\n", H.frame_len);
+	      dw_printf ("\n");
+	      for (j=0; j<H.frame_len; j++) {
+                      dw_printf (" %02x", H.frame_buf[j]);
+	      }
+	  dw_printf ("\nDEC\n");
+	  for (j=0; j<H.frame_len; j++) {
+	    dw_printf ("%c", H.frame_buf[j]>>1);
+	  }
+	  dw_printf ("\nORIG\n");
+          for (j=0; j<H.frame_len; j++) {
+	    dw_printf ("%c", H.frame_buf[j]);
+	  }
+	  dw_printf ("\n");
+        }
+#endif
+end:
 	return 0;	/* failure. */
 
 } /* end try_decode */
@@ -634,7 +1052,6 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped)
 /* only use this to calculate elapsed time. */
 
 
-#if DEBUG
 
 static double dtime_now (void)
 {
@@ -658,4 +1075,3 @@ static double dtime_now (void)
 #endif
 }
 
-#endif 
diff --git a/hdlc_rec2.h b/hdlc_rec2.h
index 9cef6ca..1acc2cc 100755
--- a/hdlc_rec2.h
+++ b/hdlc_rec2.h
@@ -3,15 +3,59 @@
 #define HDLC_REC2_H 1
 
 
-#include "rrbb.h"
 #include "ax25_pad.h"	/* for packet_t */
+#include "rrbb.h"
 
 typedef enum retry_e {
 		RETRY_NONE=0,
-		RETRY_SINGLE=1,
-		RETRY_DOUBLE=2,
-		RETRY_TRIPLE=3,
-		RETRY_TWO_SEP=4 } retry_t;
+		RETRY_SWAP_SINGLE=1,
+		RETRY_SWAP_DOUBLE=2,
+		RETRY_SWAP_TRIPLE=3,
+		RETRY_REMOVE_SINGLE=4,
+		RETRY_REMOVE_DOUBLE=5,
+		RETRY_REMOVE_TRIPLE=6,
+		RETRY_INSERT_SINGLE=7,
+		RETRY_INSERT_DOUBLE=8,
+		RETRY_SWAP_TWO_SEP=9,
+		RETRY_SWAP_MANY=10,
+		RETRY_REMOVE_MANY=11,
+		RETRY_REMOVE_TWO_SEP=12,
+		RETRY_MAX = 13}  retry_t;
+
+typedef enum retry_mode_e {
+		RETRY_MODE_CONTIGUOUS=0,
+		RETRY_MODE_SEPARATED=1,
+		}  retry_mode_t;
+
+typedef enum retry_type_e {
+		RETRY_TYPE_NONE=0,
+		RETRY_TYPE_SWAP=1,
+		RETRY_TYPE_REMOVE=2,
+		RETRY_TYPE_INSERT=3}  retry_type_t;
+
+typedef struct retry_conf_s {
+	retry_t      retry;
+        retry_mode_t mode;
+        retry_type_t type;
+        union {
+                struct {
+                        int bit_idx_a; /*  */
+                        int bit_idx_b; /*  */
+                        int bit_idx_c; /*  */
+                } sep;       /* RETRY_MODE_SEPARATED */
+
+                struct {
+                        int bit_idx;
+			int nr_bits;
+                } contig;  /* RETRY_MODE_CONTIGUOUS */
+
+        } u_bits;
+	int insert_value;
+
+} retry_conf_t;
+
+
+
 
 #if defined(DIREWOLF_C) || defined(ATEST_C) || defined(UDPTEST_C)
 
@@ -20,7 +64,15 @@ static const char * retry_text[] = {
 		"SINGLE",
 		"DOUBLE",
 		"TRIPLE",
-		"TWO_SEP" };
+		"REMOVE_SINGLE",
+		"REMOVE_DOUBLE",
+		"REMOVE_TRIPLE",
+		"INSERT_SINGLE",
+		"INSERT_DOUBLE",
+		"TWO_SEP",
+		"MANY",
+		"REMOVE_MANY",
+		"REMOVE_SEP"};
 #endif
 
 void hdlc_rec2_block (rrbb_t block, retry_t fix_bits);
diff --git a/kiss.c b/kiss.c
index b3a10b4..4a4cc9b 100755
--- a/kiss.c
+++ b/kiss.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011,2013  John Langner, WB2OSZ
+//    Copyright (C) 2011, 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -129,8 +129,12 @@
 #include <termios.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
+#ifdef __OpenBSD__
+#include <errno.h>
+#else
 #include <sys/errno.h>
 #endif
+#endif
 
 #include <assert.h>
 #include <string.h>
@@ -164,14 +168,16 @@ static kiss_frame_t kf;		/* Accumulated KISS frame and state of decoder. */
 
 static MYFDTYPE pt_master_fd = MYFDERROR;	/* File descriptor for my end. */
 
-static MYFDTYPE pt_slave_fd = MYFDERROR;	/* File descriptor for pseudo terminal */
-						/* for use by application. */
+static char pt_slave_name[32];			/* Pseudo terminal slave name  */
+						/* like /dev/pts/999 */
+
+
 
 /*
  * Symlink to pseudo terminal name which changes.
  */
 
-#define DEV_KISS_TNC "/tmp/kisstnc"
+#define TMP_KISSTNC_SYMLINK "/tmp/kisstnc"
 
 #endif
 
@@ -186,9 +192,14 @@ static MYFDTYPE nullmodem_fd = MYFDERROR;
 #endif
 
 
+// TODO:  define in one place, use everywhere.
+#if __WIN32__
+#define THREAD_F unsigned __stdcall
+#else 
+#define THREAD_F void *
+#endif
 
-
-static void * kiss_listen_thread (void *arg);
+static THREAD_F kiss_listen_thread (void *arg);
 
 
 
@@ -262,7 +273,7 @@ void kiss_init (struct misc_config_s *mc)
 	  pt_master_fd = kiss_open_pt ();
 
 	  if (pt_master_fd != MYFDERROR) {
-	    e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kiss_listen_thread, (void*)(long)pt_master_fd);
+	    e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kiss_listen_thread, NULL);
 	    if (e != 0) {
 	      text_color_set(DW_COLOR_ERROR);
 	      perror("Could not create kiss listening thread for Linux pseudo terminal");
@@ -300,14 +311,14 @@ void kiss_init (struct misc_config_s *mc)
 
 	  if (nullmodem_fd != MYFDERROR) {
 #if __WIN32__
-	    kiss_nullmodem_listen_th = _beginthreadex (NULL, 0, kiss_listen_thread, (void*)(long)nullmodem_fd, 0, NULL);
+	    kiss_nullmodem_listen_th = (HANDLE)_beginthreadex (NULL, 0, kiss_listen_thread, NULL, 0, NULL);
 	    if (kiss_nullmodem_listen_th == NULL) {
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf ("Could not create kiss nullmodem thread\n");
 	      return;
 	    }
 #else
-	    e = pthread_create (&kiss_nullmodem_listen_tid, NULL, kiss_listen_thread, (void*)(long)nullmodem_fd);
+	    e = pthread_create (&kiss_nullmodem_listen_tid, NULL, kiss_listen_thread, NULL);
 	    if (e != 0) {
 	      text_color_set(DW_COLOR_ERROR);
 	      perror("Could not create kiss listening thread for Windows virtual COM port.");
@@ -341,7 +352,7 @@ void kiss_init (struct misc_config_s *mc)
 static MYFDTYPE kiss_open_pt (void)
 {
 	int fd;
-	char *slave_device;
+	char *pts;
 	struct termios ts;
 	int e;
 	//int flags;
@@ -357,12 +368,13 @@ static MYFDTYPE kiss_open_pt (void)
 	if (fd == MYFDERROR
 	    || grantpt (fd) == MYFDERROR
 	    || unlockpt (fd) == MYFDERROR
-	    || (slave_device = ptsname (fd)) == NULL) {
+	    || (pts = ptsname (fd)) == NULL) {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n");
 	  return (MYFDERROR);
 	}
 
+	strcpy (pt_slave_name, pts);
 
 	e = tcgetattr (fd, &ts);
 	if (e != 0) { 
@@ -416,9 +428,29 @@ static MYFDTYPE kiss_open_pt (void)
 	}
 #endif
 	text_color_set(DW_COLOR_INFO);
-	dw_printf("Virtual KISS TNC is available on %s\n", slave_device);
+	dw_printf("Virtual KISS TNC is available on %s\n", pt_slave_name);
 	dw_printf("WARNING - Dire Wolf will hang eventually if nothing is reading from it.\n");
 	
+
+#if 1
+	// Sample code shows this. Why would we open it here?
+	// On Ubuntu, the slave side disappears after a few
+	// seconds if no one opens it.  Same on Raspian which
+	// is also based on Debian.
+	// Need to revisit this.  
+
+	MYFDTYPE pt_slave_fd;
+
+	pt_slave_fd = open(pt_slave_name, O_RDWR|O_NOCTTY);
+
+	if (pt_slave_fd < 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Can't open %s\n", pt_slave_name);	
+	    perror ("");
+	    return MYFDERROR;
+	}
+#endif
+
 /*
  * The device name is not the same every time.
  * This is inconvenient for the application because it might
@@ -427,31 +459,21 @@ static MYFDTYPE kiss_open_pt (void)
  * does not need to change when the pseudo terminal name changes.
  */
 
-//TODO: remove symlink on exit.
-	unlink (DEV_KISS_TNC);
+	unlink (TMP_KISSTNC_SYMLINK);
 
-	if (symlink (slave_device, DEV_KISS_TNC) == 0) {
-	    dw_printf ("Created symlink %s -> %s\n", DEV_KISS_TNC, slave_device);
+
+// TODO: Is this removed when application exits?
+
+	if (symlink (pt_slave_name, TMP_KISSTNC_SYMLINK) == 0) {
+	    dw_printf ("Created symlink %s -> %s\n", TMP_KISSTNC_SYMLINK, pt_slave_name);
 	}
 	else {
 	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Failed to create symlink %s\n", DEV_KISS_TNC);	
+	    dw_printf ("Failed to create symlink %s\n", TMP_KISSTNC_SYMLINK);	
 	    perror ("");
 	}
 
-#if 1
-	// Sample code shows this. Why would we open it here?
-	// On Ubuntu, the slave side disappears after a few
-	// seconds if no one opens it.
-
-	pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY);
-
-	if (pt_slave_fd < 0)
-	   return MYFDERROR;
-#endif
 	return (fd);
-
-
 }
 
 #endif
@@ -470,8 +492,8 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
 
 	MYFDTYPE fd;
 	DCB dcb;
-	int ok;
-
+	int ok;	
+	char bettername[50];
 
 #if DEBUG
 	text_color_set(DW_COLOR_DEBUG);
@@ -487,8 +509,20 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
 
 // Read http://support.microsoft.com/kb/156932 
 
+// Bug fix in release 1.1 - Need to munge name for COM10 and up.
+// http://support.microsoft.com/kb/115831
 
-	fd = CreateFile(devicename, GENERIC_READ | GENERIC_WRITE, 
+	strcpy (bettername, devicename);
+	if (strncasecmp(devicename, "COM", 3) == 0) {
+	  int n;
+	  n = atoi(devicename+3);
+	  if (n >= 10) {
+	    strcpy (bettername, "\\\\.\\");
+	    strcat (bettername, devicename);
+	  }
+	}
+	
+	fd = CreateFile(bettername, GENERIC_READ | GENERIC_WRITE, 
 			0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
 
 	if (fd == MYFDERROR) {
@@ -510,7 +544,8 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
 
 	/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
 
-	// dcb.BaudRate ? shouldn't matter 
+	dcb.DCBlength = sizeof(DCB);
+	dcb.BaudRate = CBR_9600;	// shouldn't matter 
 	dcb.fBinary = 1;
 	dcb.fParity = 0;
 	dcb.fOutxCtsFlow = 0;
@@ -609,7 +644,7 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
 
 void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen)
 {
-	unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
+	unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
 	int kiss_len;
 	int j;
 	int err;
@@ -637,31 +672,28 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen)
 	}
 	else {
 
-	  kiss_len = 0;
-	  kiss_buff[kiss_len++] = FEND;
-	  kiss_buff[kiss_len++] = chan << 4;
 
-	  for (j=0; j<flen; j++) {
+	  unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
+	 
+	  assert (flen < sizeof(stemp));
 
-	    if (fbuf[j] == FEND) {
-	      kiss_buff[kiss_len++] = FESC;
-	      kiss_buff[kiss_len++] = TFEND;
-	    }
-	    else if (fbuf[j] == FESC) {
-	      kiss_buff[kiss_len++] = FESC;
-	      kiss_buff[kiss_len++] = TFESC;
-	    }
-	    else {
-	      kiss_buff[kiss_len++] = fbuf[j];
-	    }
-	    assert (kiss_len < sizeof (kiss_buff));
+	  stemp[0] = (chan << 4) + 0;
+	  memcpy (stemp+1, fbuf, flen);
+
+	  if (kiss_debug >= 2) {
+	    /* AX.25 frame with the CRC removed. */
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("\n");
+	    dw_printf ("Packet content before adding KISS framing and any escapes:\n");
+	    hex_dump ((char*)fbuf, flen);
 	  }
-	  kiss_buff[kiss_len++] = FEND;
 
-	  /* This has the escapes but not the surrounding FENDs. */
+	  kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff);
+
+	  /* This has KISS framing and escapes for sending to client app. */
 
 	  if (kiss_debug) {
-	    kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
+	    kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
 	  }
 
 	}
@@ -732,7 +764,7 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen)
 	      //nullmodem_fd = MYFDERROR;
 	    }
 	  }
-	  else if (nwritten != flen) 
+	  else if (nwritten != kiss_len)
 	  {
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf ("\nError sending KISS message to client application thru null modem.  Only %d of %d written.\n\n", (int)nwritten, kiss_len);
@@ -770,22 +802,16 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen)
  *
  * Purpose:     Wait for messages from an application.
  *
- * Inputs:	arg		- File descriptor for reading.
- *
- * Outputs:	pt_slave_fd	- File descriptor for communicating with client app.
+ * Global In:	nullmodem_fd or pt_master_fd
  *
  * Description:	Process messages from the client application.
  *
  *--------------------------------------------------------------------*/
 
-//TODO: should pass fd by reference so it can be zapped.
-//BUG: If we close it here, that fact doesn't get back 
-// to the main receiving thread.
-
 /* Return one byte (value 0 - 255) or terminate thread on error. */
 
 
-static int kiss_get (MYFDTYPE fd)
+static int kiss_get (/* MYFDTYPE fd*/ void )
 {
 	unsigned char ch;
 
@@ -808,7 +834,7 @@ static int kiss_get (MYFDTYPE fd)
 
   	while (n == 0) {
 
-	  if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd)) 
+	  if ( ! ReadFile (nullmodem_fd, &ch, 1, &n, &ov_rd)) 
 	  {
 	    int err1 = GetLastError();
 
@@ -818,7 +844,7 @@ static int kiss_get (MYFDTYPE fd)
 
 	      if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0) 
 	      {
-	        if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1))
+	        if ( ! GetOverlappedResult (nullmodem_fd, &ov_rd, &n, 1))
 	        {
 	          int err3 = GetLastError();
 
@@ -835,8 +861,8 @@ static int kiss_get (MYFDTYPE fd)
 	    {
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1);
-	      //CloseHandle (fd);
-	      //fd = MYFDERROR;
+	      CloseHandle (nullmodem_fd);
+	      nullmodem_fd = MYFDERROR;
 	      //pthread_exit (NULL);
 	    }
 	  }
@@ -857,18 +883,32 @@ static int kiss_get (MYFDTYPE fd)
 
 #else		/* Linux/Cygwin version */
 
-	int n;
+	int n = 0;
 
-	n = read(fd, &ch, (size_t)1);
+	while ( n == 0 ) {
 
-	if (n != 1) {
-	  //text_color_set(DW_COLOR_ERROR);
-	  //dw_printf ("\nError receiving kiss message from client application.  Closing connection %d.\n\n", fd);
+	  n = read(pt_master_fd, &ch, (size_t)1);
+
+	  if (n != 1) {
 
-	  close (fd);
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("\nError receiving kiss message from client application.  Closing %s.\n\n", pt_slave_name);
+	    perror ("");
+
+	    /* Message added between 1.1 beta test and final version 1.1 */
+
+	    /* TODO: Determine root cause and find proper solution. */
 
-	  fd = MYFDERROR;
-	  pthread_exit (NULL);
+	    dw_printf ("This is a known problem that sometimes shows up when using with kissattach.\n");
+	    dw_printf ("There are a couple work-arounds described in the Dire Wolf User Guide\n");
+	    dw_printf ("and the Raspberry Pi APRS documents.\n");
+
+	    close (pt_master_fd);
+
+	    pt_master_fd = MYFDERROR;
+	    unlink (TMP_KISSTNC_SYMLINK);
+	    pthread_exit (NULL);
+	  }
 	}
 
 #endif
@@ -897,10 +937,8 @@ static int kiss_get (MYFDTYPE fd)
 
 
 
-static void * kiss_listen_thread (void *arg)
+static THREAD_F kiss_listen_thread (void *arg)
 {
-	MYFDTYPE fd = (MYFDTYPE)(long)arg;
-
 	unsigned char ch;
 			
 #if DEBUG
@@ -910,14 +948,15 @@ static void * kiss_listen_thread (void *arg)
 
 
 	while (1) {
-	  ch = kiss_get(fd);
-
-	  if (kiss_frame (&kf, ch, kiss_debug, kiss_send_rec_packet)) { 
-	    kiss_process_msg (&kf, kiss_debug);
-	  }
-	}	/* while (1) */
+	  ch = kiss_get();
+	  kiss_rec_byte (&kf, ch, kiss_debug, kiss_send_rec_packet);
+	}
 
-	return (NULL);	/* Unreachable but avoids compiler warning. */
+#if __WIN32__
+	return(0);
+#else
+	return;	/* Unreachable but avoids compiler warning. */
+#endif
 }
 
 /* end kiss.c */
diff --git a/kiss_frame.c b/kiss_frame.c
index 5d3d066..a2496c5 100755
--- a/kiss_frame.c
+++ b/kiss_frame.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2013  John Langner, WB2OSZ
+//    Copyright (C) 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -84,13 +84,181 @@
 #include "tq.h"
 #include "xmit.h"
 
+/* In server.c.  Should probably move to some misc. function file. */
+void hex_dump (unsigned char *p, int len);
+
+
+static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug);
+
+
+#if TEST
+
+#define dw_printf printf
+
+void text_color_set (dw_color_t c)
+{
+	return;
+}
+
+#endif
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_encapsulate 
+ *
+ * Purpose:     Ecapsulate a frame into KISS format.
+ *
+ * Inputs:	in	- Address of input block.
+ *			  First byte is the "type indicator" with type and 
+ *			  channel but we don't care about that here.
+ *			  Note that this is "binary" data and can contain
+ *			  nul (0x00) values.   Don't treat it like a text string!
+ *
+ *		ilen	- Number of bytes in input block.
+ *
+ * Outputs:	out	- Address where to place the KISS encoded representation.
+ *			  The sequence is:
+ *				FEND		- Magic frame separator.
+ *				data		- with certain byte values replaced so
+ *						  FEND will never occur here.
+ *				FEND		- Magic frame separator.
+ *
+ * Returns:	Number of bytes in the output.
+ *		Absolute max length will be twice input plus 2.
+ *
+ *-----------------------------------------------------------------*/
+
+int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out)
+{
+	int olen;
+	int j;
+
+	olen = 0;
+	out[olen++] = FEND;
+	for (j=0; j<ilen; j++) {
+
+	  if (in[j] == FEND) {
+	    out[olen++] = FESC;
+	    out[olen++] = TFEND;
+	  }
+	  else if (in[j] == FESC) {
+	    out[olen++] = FESC;
+	    out[olen++] = TFESC;
+	  }
+	  else {
+	    out[olen++] = in[j];
+	  }
+	}
+	out[olen++] = FEND;
+	
+	return (olen);
+
+}  /* end kiss_encapsulate */
+
 
 
 /*-------------------------------------------------------------------
  *
- * Name:        kiss_frame 
+ * Name:        kiss_unwrap 
+ *
+ * Purpose:     Extract original data from a KISS frame.
+ *
+ * Inputs:	in	- Address of the received the KISS encoded representation.
+ *			  The sequence is:
+ *				FEND		- Magic frame separator, optional.
+ *				data		- with certain byte values replaced so
+ *						  FEND will never occur here.
+ *				FEND		- Magic frame separator.
+ *		ilen	- Number of bytes in input block.
+ *
+ * Inputs:	out	- Where to put the resulting frame without
+ *			  the escapes or FEND.
+ *			  First byte is the "type indicator" with type and 
+ *			  channel but we don't care about that here.
+ *			  Note that this is "binary" data and can contain
+ *			  nul (0x00) values.   Don't treat it like a text string!
  *
- * Purpose:     Extract a KISS frame from byte stream.
+ * Returns:	Number of bytes in the output.
+ *
+ *-----------------------------------------------------------------*/
+
+static int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
+{
+	int olen;
+	int j;
+	int escaped_mode;
+
+	olen = 0;
+	escaped_mode = 0;
+
+	if (ilen < 2) {
+	  /* Need at least the "type indicator" byte and FEND. */
+	  /* Probably more. */
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("KISS message less than minimum length.\n");
+	  return (0);
+	}
+
+	if (in[ilen-1] == FEND) {
+	  ilen--;	/* Don't try to process below. */
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("KISS frame should end with FEND.\n");
+	}
+
+	if (in[0] == FEND) {
+	  j = 1;	/* skip over optional leading FEND. */
+	}
+	else {
+	  j = 0;
+	}
+
+	for ( ; j<ilen; j++) {
+
+	  if (in[j] == FEND) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("KISS frame should not have FEND in the middle.\n");
+	  }
+
+	  if (escaped_mode) {
+
+	    if (in[j] == TFESC) {
+	      out[olen++] = FESC;
+	    }
+	    else if (in[j] == TFEND) {
+	      out[olen++] = FEND;
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("KISS protocol error.  Found 0x%02x after FESC.\n", in[j]);
+	    }
+	    escaped_mode = 0;
+	  }
+	  else if (in[j] == FESC) {
+	    escaped_mode = 1;
+	  }
+	  else {
+	    out[olen++] = in[j];
+	  }
+	}
+	
+	return (olen);
+
+}  /* end kiss_unwrap */
+
+
+#ifndef TEST
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_rec_byte 
+ *
+ * Purpose:     Process one byte from a KISS client app.
  *
  * Inputs:	kf	- Current state of building a frame.
  *		ch	- A byte from the input stream.
@@ -100,10 +268,8 @@
  * Outputs:	kf	- Current state is updated.
  *
  * Returns:	TRUE when a complete frame is ready for processing.
+		// TODO: void later
  *
- * Bug:		For send, the debug output shows exactly what is
- *		being sent including the surrounding FEND and any
- *		escapes.  For receive, we don't show those.
  *
  *-----------------------------------------------------------------*/
 
@@ -131,12 +297,18 @@
  * Let's try to keep it happy by sending back a command prompt.
  */
 
-int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)) 
+
+
+
+void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)) 
 {
+
+	//printf ("kiss_frame ( %c %02x ) \n", ch, ch);
 	
 	switch (kf->state) {
 	 
   	  case KS_SEARCHING:		/* Searching for starting FEND. */
+	  default:
 
 	    if (ch == FEND) {
 	      
@@ -150,8 +322,9 @@ int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(i
 	      }
 	      
 	      kf->kiss_len = 0;
+	      kf->kiss_msg[kf->kiss_len++] = ch;
 	      kf->state = KS_COLLECTING;
-	      return 0;
+	      return;
 	    }
 
 	    /* Noise to be rejected. */
@@ -165,7 +338,7 @@ int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(i
 	   	kf->noise[kf->noise_len] = '\0';
 	      }
 
-	      /* Try to appease it by sending something back. */
+	      /* Try to appease client app by sending something back. */
 	      if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 ||
 		    strcasecmp("reset\r", (char*)(kf->noise)) == 0) {
 	   	  (*sendfun) (0, (unsigned char *)"\xc0\xc0", -1);
@@ -175,24 +348,51 @@ int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(i
 	      }
 	      kf->noise_len = 0;
 	    }
-	    return 0;
+	    return;
+	    break;
 
 	  case KS_COLLECTING:		/* Frame collection in progress. */
 
+     
 	    if (ch == FEND) {
 	      
+	      unsigned char unwrapped[AX25_MAX_PACKET_LEN];
+	      int ulen;
+
 	      /* End of frame. */
 
 	      if (kf->kiss_len == 0) {
+		/* Empty frame.  Starting a new one. */
+	        kf->kiss_msg[kf->kiss_len++] = ch;
+	        return;
+	      }
+	      if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) {
 		/* Empty frame.  Just go on collecting. */
-	        return 0;
+	        return;
 	      }
 
+	      kf->kiss_msg[kf->kiss_len++] = ch;
 	      if (debug) {
+		/* As received over the wire from client app. */
 	        kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len);
 	      }
+
+	      ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped);
+
+	      if (debug >= 2) {
+	        /* Append CRC to this and it goes out over the radio. */
+	        text_color_set(DW_COLOR_DEBUG);
+	        dw_printf ("\n");
+	        dw_printf ("Packet content after removing KISS framing and any escapes:\n");
+	        /* Don't include the "type" indicator. */
+		/* It contains the radio channel and type should always be 0 here. */
+	        hex_dump (unwrapped+1, ulen-1);
+	      }
+
+	      kiss_process_msg (unwrapped, ulen, debug);
+
 	      kf->state = KS_SEARCHING;
-	      return 1;
+	      return;
 	    }
 
 	    if (kf->kiss_len < MAX_KISS_LEN) {
@@ -202,58 +402,40 @@ int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(i
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf ("KISS message exceeded maximum length.\n");
 	    }	      
-	    return 0;
-
-	  case KS_ESCAPE:		/* Expecting TFESC or TFEND. */
-
-	    if (kf->kiss_len >= MAX_KISS_LEN) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("KISS message exceeded maximum length.\n");
-	      kf->state = KS_COLLECTING;
-	      return 0;
-	    }	      
-
-	    if (ch == TFESC) {
-	      kf->kiss_msg[kf->kiss_len++] = FESC;
-	    }
-	    else if (ch == TFEND) {
-	      kf->kiss_msg[kf->kiss_len++] = FEND;
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("KISS protocol error.  TFESC or TFEND expected.\n");
-	    }
-	    
-	    kf->state = KS_COLLECTING;
-	    return 0;
+	    return;
+	    break;
 	}
 	
-	return 0;	/* unreachable but suppress compiler warning. */
+	return;	/* unreachable but suppress compiler warning. */
 
-} /* end kiss_frame */   
+} /* end kiss_rec_byte */   
 	      	    
 
+
+
 /*-------------------------------------------------------------------
  *
  * Name:        kiss_process_msg 
  *
  * Purpose:     Process a message from the KISS client.
  *
- * Inputs:	kf	- Current state of building a frame.
- *			  Should be complete.
+ * Inputs:	kiss_msg	- Kiss frame with FEND and escapes removed.
+ *				  The first byte contains channel and command.
+ *
+ *		kiss_len	- Number of bytes including the command.
  *
  *		debug	- Debug option is selected.
  *
  *-----------------------------------------------------------------*/
 
-void kiss_process_msg (kiss_frame_t *kf, int debug)
+static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
 {
 	int port;
 	int cmd;
 	packet_t pp;
 
-	port = (kf->kiss_msg[0] >> 4) & 0xf;
-	cmd = kf->kiss_msg[0] & 0xf;
+	port = (kiss_msg[0] >> 4) & 0xf;
+	cmd = kiss_msg[0] & 0xf;
 
 	switch (cmd) 
 	{
@@ -262,12 +444,12 @@ void kiss_process_msg (kiss_frame_t *kf, int debug)
 	    /* Special hack - Discard apparently bad data from Linux AX25. */
 
 	    if ((port == 2 || port == 8) && 
-		 kf->kiss_msg[1] == 'Q' << 1 &&
-		 kf->kiss_msg[2] == 'S' << 1 &&
-		 kf->kiss_msg[3] == 'T' << 1 &&
-		 kf->kiss_msg[4] == ' ' << 1 &&
-		 kf->kiss_msg[15] == 3 &&
-		 kf->kiss_msg[16] == 0xcd) {
+		 kiss_msg[1] == 'Q' << 1 &&
+		 kiss_msg[2] == 'S' << 1 &&
+		 kiss_msg[3] == 'T' << 1 &&
+		 kiss_msg[4] == ' ' << 1 &&
+		 kiss_msg[15] == 3 &&
+		 kiss_msg[16] == 0xcd) {
 	        
 	      if (debug) {
 	         text_color_set(DW_COLOR_ERROR);
@@ -275,62 +457,71 @@ void kiss_process_msg (kiss_frame_t *kf, int debug)
 	      }
 	      return;
 	    }
+
+	    // Should really check if single or dual channel mode.
+	    // Do more thoroughly in 1.2.
+
+	    if (port != 0 && port != 1) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Invalid channel %d from KISS client app.\n", port);
+	      return;
+	    }
 	
-	    pp = ax25_from_frame (kf->kiss_msg+1, kf->kiss_len-1, -1);
+	    pp = ax25_from_frame (kiss_msg+1, kiss_len-1, -1);
 	    if (pp == NULL) {
 	       text_color_set(DW_COLOR_ERROR);
 	       dw_printf ("ERROR - Invalid KISS data frame from client app.\n");
 	    }
 	    else {
 
-	    /* How can we determine if it is an original or repeated message? */
-	    /* If there is at least one digipeater in the frame, AND */
-	    /* that digipeater has been used, it should go out quickly thru */
-	    /* the high priority queue. */
-	    /* Otherwise, it is an original for the low priority queue. */
+	      /* How can we determine if it is an original or repeated message? */
+	      /* If there is at least one digipeater in the frame, AND */
+	      /* that digipeater has been used, it should go out quickly thru */
+	      /* the high priority queue. */
+	      /* Otherwise, it is an original for the low priority queue. */
 
-	    if (ax25_get_num_repeaters(pp) >= 1 &&
-	      ax25_get_h(pp,AX25_REPEATER_1)) {
-	      tq_append (port, TQ_PRIO_0_HI, pp);
-	    }
-	    else {
-	      tq_append (port, TQ_PRIO_1_LO, pp);
+	      if (ax25_get_num_repeaters(pp) >= 1 &&
+	      		ax25_get_h(pp,AX25_REPEATER_1)) {
+	        tq_append (port, TQ_PRIO_0_HI, pp);
+	      }
+	      else {
+	        tq_append (port, TQ_PRIO_1_LO, pp);
+	      }
 	    }
-	  }
-	  break;
+	    break;
 
         case 1:				/* TXDELAY */
 
           text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set TXDELAY = %d, port %d\n", kf->kiss_msg[1], port);
-	  xmit_set_txdelay (port, kf->kiss_msg[1]);
+	  dw_printf ("KISS protocol set TXDELAY = %d, port %d\n", kiss_msg[1], port);
+	  xmit_set_txdelay (port, kiss_msg[1]);
 	  break;
 
         case 2:				/* Persistence */
 
           text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set Persistence = %d, port %d\n", kf->kiss_msg[1], port);
-	  xmit_set_persist (port, kf->kiss_msg[1]);
+	  dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port);
+	  xmit_set_persist (port, kiss_msg[1]);
 	  break;
 
         case 3:				/* SlotTime */
 
           text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set SlotTime = %d, port %d\n", kf->kiss_msg[1], port);
-	  xmit_set_slottime (port, kf->kiss_msg[1]);
+	  dw_printf ("KISS protocol set SlotTime = %d, port %d\n", kiss_msg[1], port);
+	  xmit_set_slottime (port, kiss_msg[1]);
 	  break;
 
         case 4:				/* TXtail */
 
           text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set TXtail = %d, port %d\n", kf->kiss_msg[1], port);
-	  xmit_set_txtail (port, kf->kiss_msg[1]);
+	  dw_printf ("KISS protocol set TXtail = %d, port %d\n", kiss_msg[1], port);
+	  xmit_set_txtail (port, kiss_msg[1]);
 	  break;
 
         case 5:				/* FullDuplex */
 
           text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kf->kiss_msg[1], port);
+	  dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kiss_msg[1], port);
 	  break;
 
         case 6:				/* TNC specific */
@@ -348,7 +539,7 @@ void kiss_process_msg (kiss_frame_t *kf, int debug)
         default:			
           text_color_set(DW_COLOR_DEBUG);
 	  dw_printf ("KISS Invalid command %d\n", cmd);
-          kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len);
+          kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len);
 	  break;
 	}
 
@@ -369,12 +560,6 @@ void kiss_process_msg (kiss_frame_t *kf, int debug)
  *--------------------------------------------------------------------*/
 
 
-/* In server.c.  Should probably move to some misc. function file. */
-
-void hex_dump (unsigned char *p, int len);
-
-
-
 void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len)
 {
 	const char *direction [2] = { "from", "to" };
@@ -390,18 +575,69 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
 	dw_printf ("\n");
 
 	if (special == NULL) {
+	  unsigned char *p;	/* to skip over FEND if present. */
+
+	  p = pmsg;
+	  if (*p == FEND) p++;
+
 	  dw_printf ("%s %s %s KISS client application, port %d, total length = %d\n",
-			prefix[(int)fromto], function[pmsg[0] & 0xf], direction[(int)fromto], 
-			(pmsg[0] >> 4) & 0xf, msg_len);
+			prefix[(int)fromto], function[p[0] & 0xf], direction[(int)fromto], 
+			(p[0] >> 4) & 0xf, msg_len);
 	}
 	else {
 	  dw_printf ("%s %s %s KISS client application, total length = %d\n",
 			prefix[(int)fromto], special, direction[(int)fromto], 
 			msg_len);
 	}
-	hex_dump ((char*)pmsg, msg_len);
+	hex_dump (pmsg, msg_len);
 
 } /* end kiss_debug_print */
 
 
+#endif
+
+
+/* Quick unit test for encapsulate & unwrap */
+
+// $ gcc -DTEST kiss_frame.c ; ./a
+// Quick KISS test passed OK.
+
+
+#if TEST
+
+
+main ()
+{
+	unsigned char din[512];
+	unsigned char kissed[520];
+	unsigned char dout[520];
+	int klen;
+	int dlen;
+	int k;
+
+	for (k = 0; k < 512; k++) {
+	  if (k < 256) {
+	    din[k] = k;
+	  }
+	  else {
+	    din[k] = 511 - k;
+	  }
+	}
+
+	klen = kiss_encapsulate (din, 512, kissed);
+	assert (klen == 512 + 6);
+
+	dlen = kiss_unwrap (kissed, klen, dout);
+	assert (dlen == 512);
+	assert (memcmp(din, dout, 512) == 0);
+
+	dlen = kiss_unwrap (kissed+1, klen-1, dout);
+	assert (dlen == 512);
+	assert (memcmp(din, dout, 512) == 0);
+
+	printf ("Quick KISS test passed OK.\n");
+}
+
+#endif
+
 /* end kiss_frame.c */
diff --git a/kiss_frame.h b/kiss_frame.h
index 4ecd0a4..0ce2bf7 100755
--- a/kiss_frame.h
+++ b/kiss_frame.h
@@ -12,13 +12,14 @@
 #define TFESC 0xDD
 
 
-
 enum kiss_state_e {
 	KS_SEARCHING,		/* Looking for FEND to start KISS frame. */
-	KS_COLLECTING,		/* In process of collecting KISS frame. */
-	KS_ESCAPE };		/* FESC found in frame. */
+	KS_COLLECTING};		/* In process of collecting KISS frame. */
+
 
 #define MAX_KISS_LEN 2048	/* Spec calls for at least 1024. */
+				/* Might want to make it longer to accomodate */
+				/* maximum packet length. */
 
 #define MAX_NOISE_LEN 100
 
@@ -27,6 +28,8 @@ typedef struct kiss_frame_s {
 	enum kiss_state_e state;
 
 	unsigned char kiss_msg[MAX_KISS_LEN];
+				/* Leading FEND is optional. */
+				/* Contains escapes and ending FEND. */
 	int kiss_len;
 
 	unsigned char noise[MAX_NOISE_LEN];
@@ -36,9 +39,10 @@ typedef struct kiss_frame_s {
 
 
 
-int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)); 
+int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out);
+
+void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)); 
  
-void kiss_process_msg (kiss_frame_t *kf, int debug);
 
 typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
 
diff --git a/kissnet.c b/kissnet.c
index 91712ee..3f61ac7 100755
--- a/kissnet.c
+++ b/kissnet.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011-2013  John Langner, WB2OSZ
+//    Copyright (C) 2011-2014  John Langner, WB2OSZ
 //
 //    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
@@ -125,6 +125,7 @@
 
 
 static kiss_frame_t kf;		/* Accumulated KISS frame and state of decoder. */
+				// TODO: multiple instances if multiple KISS network clients!
 
 
 static int client_sock;		/* File descriptor for socket for */
@@ -471,31 +472,28 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
 	}
 	else {
 
-	  kiss_len = 0;
-	  kiss_buff[kiss_len++] = FEND;
-	  kiss_buff[kiss_len++] = chan << 4;
-
-	  for (j=0; j<flen; j++) {
-
-	    if (fbuf[j] == FEND) {
-	      kiss_buff[kiss_len++] = FESC;
-	      kiss_buff[kiss_len++] = TFEND;
-	    }
-	    else if (fbuf[j] == FESC) {
-	      kiss_buff[kiss_len++] = FESC;
-	      kiss_buff[kiss_len++] = TFESC;
-	    }
-	    else {
-	      kiss_buff[kiss_len++] = fbuf[j];
-	    }
-	    assert (kiss_len < sizeof (kiss_buff));
+
+	  unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
+	 
+	  assert (flen < sizeof(stemp));
+
+	  stemp[0] = (chan << 4) + 0;
+	  memcpy (stemp+1, fbuf, flen);
+
+	  if (kiss_debug >= 2) {
+	    /* AX.25 frame with the CRC removed. */
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("\n");
+	    dw_printf ("Packet content before adding KISS framing and any escapes:\n");
+	    hex_dump ((char*)fbuf, flen);
 	  }
-	  kiss_buff[kiss_len++] = FEND;
 
-	  /* Bug: This has the escapes but not the surrounding FENDs. */
+	  kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff);
+
+	  /* This has the escapes and the surrounding FENDs. */
 
 	  if (kiss_debug) {
-	    kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
+	    kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
 	  }
 	}
 
@@ -658,11 +656,8 @@ static void * kissnet_listen_thread (void *arg)
 
 	while (1) {
 	  ch = kiss_get();
-
-	  if (kiss_frame (&kf, ch, kiss_debug, kissnet_send_rec_packet)) { 
-	    kiss_process_msg (&kf, kiss_debug);
-	  }
-	}  /* while (1) */
+	  kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet);
+	}  
 
 	return (NULL);	/* to suppress compiler warning. */
 
diff --git a/latlong.c b/latlong.c
index a31adb6..7d4c4dd 100755
--- a/latlong.c
+++ b/latlong.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2013  John Langner, WB2OSZ
+//    Copyright (C) 2013,2014  John Langner, WB2OSZ
 //
 //    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
@@ -279,3 +279,243 @@ void longitude_to_comp_str (double dlong, char *clon)
 	clon[2] = x2 + 33;
 	clon[3] = x3 + 33;
 }
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        latitude_to_nmea
+ *
+ * Purpose:     Convert numeric latitude to strings for NMEA sentence.
+ *
+ * Inputs:      dlat		- Floating point degrees.
+ *
+ * Outputs:	slat		- String in format ddmm.mmmm
+ *		hemi		- Hemisphere or empty string.
+ *
+ * Returns:     None
+ *
+ *----------------------------------------------------------------*/
+
+void latitude_to_nmea (double dlat, char *slat, char *hemi)
+{
+	int ideg;	/* whole number of degrees. */
+	double dmin;	/* Minutes after removing degrees. */
+	char smin[10];	/* Minutes in format mm.mmmm */
+	
+	if (dlat == G_UNKNOWN) {
+	  strcpy (slat, "");
+	  strcpy (hemi, "");
+	  return;
+	}
+
+	if (dlat < -90.) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Latitude is less than -90.  Changing to -90.n");
+	  dlat = -90.;
+	}
+	if (dlat > 90.) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Latitude is greater than 90.  Changing to 90.n");
+	  dlat = 90.;
+	}
+
+	if (dlat < 0) {
+	  dlat = (- dlat);
+	  strcpy (hemi, "S");
+	}
+	else {
+	  strcpy (hemi, "N");
+	}
+
+	ideg = (int)dlat;
+	dmin = (dlat - ideg) * 60.;
+
+	sprintf (smin, "%07.4f", dmin);
+	/* Due to roundoff, 59.99999 could come out as "60.0000" */
+	if (smin[0] == '6') {
+	  smin[0] = '0';
+	  ideg++;
+	}
+
+	sprintf (slat, "%02d%s", ideg, smin);
+
+} /* end latitude_to_str */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        longitude_to_nmea
+ *
+ * Purpose:     Convert numeric longitude to strings for NMEA sentence.
+ *
+ * Inputs:      dlong		- Floating point degrees.
+ *
+ * Outputs:	slong		- String in format dddmm.mmmm
+ *		hemi		- Hemisphere or empty string.
+ *
+ * Returns:     None
+ *
+ *----------------------------------------------------------------*/
+
+void longitude_to_nmea (double dlong, char *slong, char *hemi)
+{
+	int ideg;	/* whole number of degrees. */
+	double dmin;	/* Minutes after removing degrees. */
+	char smin[10];	/* Minutes in format mm.mmmm */
+	
+	if (dlong == G_UNKNOWN) {
+	  strcpy (slong, "");
+	  strcpy (hemi, "");
+	  return;
+	}
+
+	if (dlong < -180.) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("longitude is less than -180.  Changing to -180.n");
+	  dlong = -180.;
+	}
+	if (dlong > 180.) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("longitude is greater than 180.  Changing to 180.n");
+	  dlong = 180.;
+	}
+
+	if (dlong < 0) {
+	  dlong = (- dlong);
+	  strcpy (hemi, "W");
+	}
+	else {
+	  strcpy (hemi, "E");
+	}
+
+	ideg = (int)dlong;
+	dmin = (dlong - ideg) * 60.;
+
+	sprintf (smin, "%07.4f", dmin);
+	/* Due to roundoff, 59.99999 could come out as "60.0000" */
+	if (smin[0] == '6') {
+	  smin[0] = '0';
+	  ideg++;
+	}
+
+	sprintf (slong, "%03d%s", ideg, smin);
+
+} /* end longitude_to_nmea */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	latitude_from_nmea
+ *
+ * Purpose:	Convert NMEA latitude encoding to degrees.
+ *
+ * Inputs:	pstr 	- Pointer to numeric string.
+ *		phemi	- Pointer to following field.  Should be N or S.
+ *
+ * Returns:	Double precision value in degrees.  Negative for South.
+ *
+ * Description:	Latitude field has
+ *			2 digits for degrees
+ *			2 digits for minutes
+ *			period
+ *			Variable number of fractional digits for minutes.
+ *			I've seen 2, 3, and 4 fractional digits.
+ *
+ *
+ * Bugs:	Very little validation of data.
+ *
+ * Errors:	Return constant G_UNKNOWN for any type of error.
+ *		Could we use special "NaN" code?
+ *
+ *------------------------------------------------------------------*/
+
+
+double latitude_from_nmea (char *pstr, char *phemi)
+{
+
+	double lat;
+
+	if ( ! isdigit((unsigned char)(pstr[0]))) return (G_UNKNOWN);
+
+	if (pstr[4] != '.') return (G_UNKNOWN);
+
+
+	lat = (pstr[0] - '0') * 10 + (pstr[1] - '0') + atof(pstr+2) / 60.0;
+
+	if (lat < 0 || lat > 90) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Error: Latitude not in range of 0 to 90.\n");	  
+	}
+
+	// Saw this one time:
+	//	$GPRMC,000000,V,0000.0000,0,00000.0000,0,000,000,000000,,*01
+
+	// If location is unknown, I think the hemisphere should be
+	// an empty string.  TODO: Check on this.
+	// 'V' means void, so sentence should be discarded rather than
+	// trying to extract any data from it.
+
+	if (*phemi != 'N' && *phemi != 'S' && *phemi != '\0') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Error: Latitude hemisphere should be N or S.\n");	  
+	}
+
+	if (*phemi == 'S') lat = ( - lat);
+
+	return (lat);
+}
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	longitude_from_nmea
+ *
+ * Purpose:	Convert NMEA longitude encoding to degrees.
+ *
+ * Inputs:	pstr 	- Pointer to numeric string.
+ *		phemi	- Pointer to following field.  Should be E or W.
+ *
+ * Returns:	Double precision value in degrees.  Negative for West.
+ *
+ * Description:	Longitude field has
+ *			3 digits for degrees
+ *			2 digits for minutes
+ *			period
+ *			Variable number of fractional digits for minutes
+ *
+ *
+ * Bugs:	Very little validation of data.
+ *
+ * Errors:	Return constant G_UNKNOWN for any type of error.
+ *		Could we use special "NaN" code?
+ *
+ *------------------------------------------------------------------*/
+
+
+double longitude_from_nmea (char *pstr, char *phemi)
+{
+	double lon;
+
+	if ( ! isdigit((unsigned char)(pstr[0]))) return (G_UNKNOWN);
+
+	if (pstr[5] != '.') return (G_UNKNOWN);
+
+	lon = (pstr[0] - '0') * 100 + (pstr[1] - '0') * 10 + (pstr[2] - '0') + atof(pstr+3) / 60.0;
+
+	if (lon < 0 || lon > 180) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Error: Longitude not in range of 0 to 180.\n");	  
+	}
+	
+	if (*phemi != 'E' && *phemi != 'W' && *phemi != '\0') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Error: Longitude hemisphere should be E or W.\n");	  
+	}
+
+	if (*phemi == 'W') lon = ( - lon);
+
+	return (lon);
+}
diff --git a/latlong.h b/latlong.h
index c55b590..dd0e12d 100755
--- a/latlong.h
+++ b/latlong.h
@@ -9,5 +9,12 @@
 
 void latitude_to_str (double dlat, int ambiguity, char *slat);
 void longitude_to_str (double dlong, int ambiguity, char *slong);
+
 void latitude_to_comp_str (double dlat, char *clat);
 void longitude_to_comp_str (double dlon, char *clon);
+
+void latitude_to_nmea (double dlat, char *slat, char *hemi);
+void longitude_to_nmea (double dlong, char *slong, char *hemi);
+
+double latitude_from_nmea (char *pstr, char *phemi);
+double longitude_from_nmea (char *pstr, char *phemi);
diff --git a/log.c b/log.c
new file mode 100755
index 0000000..8660c21
--- /dev/null
+++ b/log.c
@@ -0,0 +1,362 @@
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014  John Langner, WB2OSZ
+//
+//    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.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * File:	log.c
+ *
+ * Purpose:	Save received packets to a log file.
+ *
+ * Description: Rather than saving the raw, sometimes rather cryptic and
+ *		unreadable, format, write separated properties into 
+ *		CSV format for easy reading and later processing.
+ *
+ *
+ *------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <time.h>
+#include <assert.h>
+#include <stdlib.h>	
+#include <string.h>	
+#include <ctype.h>	
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "decode_aprs.h"
+#include "log.h"
+
+
+/*
+ * CSV format needs quotes if value contains comma or quote.
+ */
+
+static void quote_for_csv (char *out, const char *in) {
+	const char *p;
+	char *q = out;
+	int need_quote = 0;
+
+	for (p = in; *p != '\0'; p++) {
+	  if (*p == ',' || *p == '"') {
+	    need_quote = 1;
+	    break;
+	  }
+	}
+
+	if (need_quote) {
+	  *q++ = '"';
+	  for (p = in; *p != '\0'; p++) {
+	    if (*p == '"') {	
+	      *q++ = *p;
+	    }
+	    *q++ = *p;
+	  }
+	  *q++ = '"';
+	  *q = '\0';
+	}
+	else {
+	  strcpy (out, in);
+	}
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	log_init
+ *
+ * Purpose:	Initialization at start of application.
+ *
+ * Inputs:	path		- Path of log file directory.
+ *				  Use "." for current directory.
+ *				  Empty string disables feature.
+ *
+ * Global Out:	g_log_dir 	- Save directory here for later use.
+ *		g_log_fp	- File pointer for writing.
+ *		g_open_fname	- Name of currently open file.
+ *
+ *------------------------------------------------------------------*/
+
+static char g_log_dir[80];
+static FILE *g_log_fp;
+static char g_open_fname[20];
+
+
+void log_init (char *path) 
+{ 
+	struct stat st;
+
+	strcpy (g_log_dir, "");
+	g_log_fp = NULL;
+	strcpy (g_open_fname, "");
+
+	if (strlen(path) == 0) {
+	  return;
+	}
+
+	if (stat(path,&st) == 0) {
+	  // Exists, but is it a directory?
+	  if (S_ISDIR(st.st_mode)) {
+	    // Specified directory exists.
+	    strcpy (g_log_dir, path);
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Log file location \"%s\" is not a directory.\n", path);
+	    dw_printf ("Using current working directory \".\" instead.\n");
+	    strcpy (g_log_dir, ".");
+	  }
+	}
+	else {
+	  // Doesn't exist.  Try to create it.
+	  // parent directory must exist.
+	  // We don't create multiple levels like "mkdir -p"
+#if __WIN32__
+	  if (_mkdir (path) == 0) {
+#else
+	  if (mkdir (path, 0777) == 0) {
+#endif
+	    // Success.
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf ("Log file location \"%s\" has been created.\n", path);
+	    strcpy (g_log_dir, path);
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Failed to create log file location \"%s\".\n", path);
+	    dw_printf ("%s\n", strerror(errno));
+	    dw_printf ("Using current working directory \".\" instead.\n");
+	    strcpy (g_log_dir, ".");
+	  }
+	}
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	log_write
+ *
+ * Purpose:	Save information to log file.
+ *
+ * Inputs:	chan	- Radio channel where heard.
+ *
+ *		A	- Explode information from APRS packet.
+ *
+ *		pp	- Received packet object.
+ *
+ * 		alevel	- audio level.
+ *
+ *		retries	- Amount of effort to get a good CRC.
+ *
+ *------------------------------------------------------------------*/
+
+void log_write (int chan, decode_aprs_t *A, packet_t pp, int alevel, retry_t retries)
+{
+	time_t now; 		// make 'now' a parameter so we can process historical data ???
+	char fname[20];
+	struct tm tm;
+
+
+	if (strlen(g_log_dir) == 0) return;
+
+	// Generate the file name from current date, UTC.
+
+	now = time(NULL);
+	gmtime_r (&now, &tm);	
+
+	// Microsoft doesn't recognize %F as equivalent to %Y-%m-%d
+
+	strftime (fname, sizeof(fname), "%Y-%m-%d.log", &tm);
+
+	// Close current file if name has changed
+
+	if (g_log_fp != NULL && strcmp(fname, g_open_fname) != 0) {
+	  log_term ();
+	}
+
+	// Open for append if not already open.
+
+	if (g_log_fp == NULL) {
+	  char full_path[120];
+	  struct stat st;
+	  int already_there;
+
+	  strcpy (full_path, g_log_dir);
+#if __WIN32__
+	  strcat (full_path, "\\");
+#else
+	  strcat (full_path, "/");
+#endif
+	  strcat (full_path, fname);
+
+	  // See if it already exists.
+	  // This is used later to write a header if it did not exist already.
+
+	  already_there = stat(full_path,&st) == 0;
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf("Opening log file \"%s\".\n", fname);
+
+	  g_log_fp = fopen (full_path, "a");
+
+	  if (g_log_fp != NULL) {
+	    strcpy (g_open_fname, fname);
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Can't open log file \"%s\" for write.\n", full_path);
+	    dw_printf ("%s\n", strerror(errno));
+	    strcpy (g_open_fname, "");
+	    return;
+	  }
+
+	  // Write a header suitable for importing into a spreadsheet
+	  // only if this will be the first line.
+	
+	  if ( ! already_there) {
+	    fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,comment\n");
+	  }
+	}
+
+	if (g_log_fp != NULL) {
+
+	  char itime[24];
+	  char heard[AX25_MAX_ADDR_LEN+1];
+	  int h;
+	  char stemp[256];
+	  char slat[16], slon[16], sspd[12], scse[12], salt[12];
+	  char sfreq[20], soffs[10], stone[10];
+	  char sdti[10];
+	  char sname[24];
+	  char ssymbol[8];
+	  char smfr[60];
+	  char sstatus[40];
+	  char stelemetry[200];
+	  char scomment[256];
+
+
+	  // Microsoft doesn't recognize %T as equivalent to %H:%M:%S
+
+	  strftime (itime, sizeof(itime), "%Y-%m-%dT%H:%M:%SZ", &tm);
+
+          /* Who are we hearing?   Original station or digipeater? */
+	  /* Similar code in direwolf.c.  Combine into one function? */
+
+	  strcpy(heard, "");
+	  if (pp != NULL) {
+	    if (ax25_get_num_addr(pp) == 0) {
+	      /* Not AX.25. No station to display below. */
+	      h = -1;
+	      strcpy (heard, "");
+	    }
+	    else {
+	      h = ax25_get_heard(pp);
+              ax25_get_addr_with_ssid(pp, h, heard);
+	    }
+	   
+	    if (h >= AX25_REPEATER_2 && 
+	        strncmp(heard, "WIDE", 4) == 0 &&
+	        isdigit(heard[4]) &&
+	        heard[5] == '\0') {
+
+	      ax25_get_addr_with_ssid(pp, h-1, heard);
+	      strcat (heard, "?");
+	    }
+	  }
+
+	  // Might need to quote anything that could contain comma or quote.
+
+	  strcpy(sdti, "");
+	  if (pp != NULL) {
+	    stemp[0] = ax25_get_dti(pp);
+	    stemp[1] = '\0';
+	    quote_for_csv (sdti, stemp);
+	  }
+
+	  quote_for_csv (sname, (strlen(A->g_name) > 0) ? A->g_name : A->g_src);
+
+	  stemp[0] = A->g_symbol_table;
+	  stemp[1] = A->g_symbol_code;
+	  stemp[2] = '\0';
+	  quote_for_csv (ssymbol, stemp);
+
+	  quote_for_csv (smfr, A->g_mfr);
+	  quote_for_csv (sstatus, A->g_mic_e_status);
+	  quote_for_csv (stelemetry, A->g_telemetry);
+	  quote_for_csv (scomment, A->g_comment);
+
+	  strcpy (slat, "");  if (A->g_lat != G_UNKNOWN)      sprintf (slat, "%.6f", A->g_lat);
+	  strcpy (slon, "");  if (A->g_lon != G_UNKNOWN)      sprintf (slon, "%.6f", A->g_lon);
+	  strcpy (sspd, "");  if (A->g_speed != G_UNKNOWN)    sprintf (sspd, "%.1f", DW_MPH_TO_KNOTS(A->g_speed));
+	  strcpy (scse, "");  if (A->g_course != G_UNKNOWN)   sprintf (scse, "%.1f", A->g_course);
+	  strcpy (salt, "");  if (A->g_altitude != G_UNKNOWN) sprintf (salt, "%.1f", DW_FEET_TO_METERS(A->g_altitude));
+
+	  strcpy (sfreq, "");  if (A->g_freq   != G_UNKNOWN) sprintf (sfreq, "%.3f", A->g_freq);
+	  strcpy (soffs, "");  if (A->g_offset != G_UNKNOWN) sprintf (soffs, "%+d", A->g_offset);
+	  strcpy (stone, "");  if (A->g_tone   != G_UNKNOWN) sprintf (stone, "%.1f", A->g_tone);
+	                       if (A->g_dcs    != G_UNKNOWN) sprintf (stone, "D%03o", A->g_dcs);
+
+	  fprintf (g_log_fp, "%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", 
+			chan, (int)now, itime, 
+			A->g_src, heard, alevel, (int)retries, sdti,
+			sname, ssymbol,
+			slat, slon, sspd, scse, salt, 
+			sfreq, soffs, stone, 
+			smfr, sstatus, stelemetry, scomment);
+	  fflush (g_log_fp);
+	}
+
+} /* end log_write */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	log_term
+ *
+ * Purpose:	Close any open log file.
+ *		Called when exiting or when date changes.
+ *
+ *------------------------------------------------------------------*/
+
+
+void log_term (void)
+{
+	if (g_log_fp != NULL) {
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf("Closing log file \"%s\".\n", g_open_fname);
+
+	  fclose (g_log_fp);
+
+	  g_log_fp = NULL;
+	  strcpy (g_open_fname, "");
+	}
+
+} /* end log_term */
+
+
+/* end log.c */
diff --git a/log.h b/log.h
new file mode 100755
index 0000000..040e5ce
--- /dev/null
+++ b/log.h
@@ -0,0 +1,16 @@
+
+/* log.h */
+
+
+#include "hdlc_rec2.h"		// for retry_t
+
+#include "decode_aprs.h"	// for decode_aprs_t
+
+
+
+
+void log_init (char *path);	
+
+void log_write (int chan, decode_aprs_t *A, packet_t pp, int alevel, retry_t retries);
+
+void log_term (void); 	
\ No newline at end of file
diff --git a/log2gpx.c b/log2gpx.c
new file mode 100755
index 0000000..5570120
--- /dev/null
+++ b/log2gpx.c
@@ -0,0 +1,535 @@
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014  John Langner, WB2OSZ
+//
+//    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.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+#endif
+
+
+/*
+ * Information we gather for each thing.
+ */
+
+typedef struct thing_s {
+	double lat;
+	double lon;
+	float alt;		/* Meters above average sea level. */
+	float course;
+	float speed;		/* Meters per second. */
+	char time[20+1+3];
+	char name[9+1+2];
+	char desc[32];		/* freq/offset/tone something like 146.955 MHz -600k PL 74.4 */
+	char comment[80];	/* Combined mic-e status and comment text */
+} thing_t;
+
+static thing_t *things;		/* Dynamically sized array. */
+static int max_things;		/* Current size. */
+static int num_things;		/* Number of elements currently in use. */
+
+#define UNKNOWN_VALUE (-999)	/* Special value to indicate unknown altitude, speed, course. */
+
+#define KNOTS_TO_METERS_PER_SEC(x) ((x)*0.51444444444)
+
+
+static void read_csv(FILE *fp);
+static void unquote (char *in, char *out);
+static int compar(const void *a, const void *b);
+static void process_things (int first, int last);
+
+
+int main (int argc, char *argv[]) 
+{
+	int first, last;
+	
+
+/*
+ * Allocate array for data.
+ * Expand it as needed if initial size is inadequate.
+ */
+
+	num_things = 0;
+	max_things = 1000;
+	things = malloc (max_things * sizeof(thing_t));
+
+/*
+ * Read files listed or stdin if none.
+ */
+
+	if (argc == 1) {
+	  read_csv (stdin);
+	}
+	else {
+	  int n;
+
+	  for (n=1; n<argc; n++) {
+	    if (strcmp(argv[n], "-") == 0) {
+	      read_csv (stdin);
+	    }
+	    else {
+	      FILE *fp;
+
+	      fp = fopen (argv[n], "r");
+	      if (fp != NULL) {
+	        read_csv (fp);
+	        fclose (fp);
+	      }
+	      else {
+	        fprintf (stderr, "Can't open %s for read.\n", argv[n]);
+	        exit (1);
+	      }
+	    }
+	  }
+	}
+	
+	if (num_things == 0) {
+	  fprintf (stderr, "Nothing to process.\n");
+	  exit (1);
+	}
+
+/*
+ * Sort the data so everything for the same name is adjacent and
+ * in order of time.
+ */
+
+	qsort (things, num_things, sizeof(thing_t), compar);
+
+	//for (i=0; i<num_things; i++) {
+	//  printf ("%d: %s %.6f %.6f %.1f %s\n", 
+	//    i,
+	//    things[i].time,
+	//    things[i].lat,
+	//    things[i].lon,
+	//    things[i].alt,
+	//    things[i].name);
+	//}
+
+/*
+ * GPX file header.
+ */
+	printf ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
+	printf ("<gpx version=\"1.1\" creator=\"Dire Wolf\">\n");
+
+/*
+ * Group together all records for the same entity.
+ */
+	last = first = 0;
+	while (first < num_things) {
+
+	  while (last < num_things-1 && strcmp(things[first].name, things[last+1].name) == 0) {
+	    last++;
+	  }
+	  process_things (first, last);
+	  first = last + 1;
+	}
+
+/*
+ *  GPX file tail.
+ */
+	printf ("</gpx>\n");
+
+	exit (0);
+}
+
+
+/*
+ * Read from given file, already open, into things array. 
+ */
+
+static void read_csv(FILE *fp)
+{
+	char raw[500];
+	char csv[500];
+	int n;
+
+	while (fgets(raw, sizeof(raw), fp) != NULL) {
+
+	  char *next;
+
+	  char *pchan;
+	  char *putime;
+	  char *pisotime;
+	  char *psource;
+	  char *pheard;
+	  char *plevel;
+	  char *perror;
+	  char *pdti;
+	  char *pname;
+	  char *psymbol;
+	  char *platitude;
+	  char *plongitude;
+	  char *pspeed;
+	  char *pcourse;
+	  char *paltitude;
+	  char *pfreq;
+	  char *poffset;
+	  char *ptone;
+	  char *psystem;
+	  char *pstatus;
+	  char *ptelemetry;
+	  char *pcomment;
+
+
+	  n = strlen(raw) - 1;
+	  while (n >= 0 && (raw[n] == '\r' || raw[n] == '\n')) {
+	    raw[n] = '\0';
+	    n--;
+	  }
+
+	  unquote (raw, csv);
+	 
+	  //printf ("%s\n", csv);
+
+/*
+ * Separate out the fields.
+ */	
+    	  next = csv;
+	  pchan = strsep(&next,"\t");
+	  putime = strsep(&next,"\t");
+	  pisotime = strsep(&next,"\t");
+	  psource = strsep(&next,"\t");
+	  pheard = strsep(&next,"\t");
+	  plevel = strsep(&next,"\t");
+	  perror = strsep(&next,"\t");
+	  pdti = strsep(&next,"\t");
+	  pname = strsep(&next,"\t");
+	  psymbol = strsep(&next,"\t");
+	  platitude = strsep(&next,"\t");
+	  plongitude = strsep(&next,"\t");
+	  pspeed = strsep(&next,"\t");		/* Knots, must convert. */
+	  pcourse = strsep(&next,"\t");
+	  paltitude = strsep(&next,"\t");	/* Meters, already correct units. */
+	  pfreq = strsep(&next,"\t");
+	  poffset = strsep(&next,"\t");
+	  ptone = strsep(&next,"\t");
+	  psystem = strsep(&next,"\t");
+	  pstatus = strsep(&next,"\t");
+	  ptelemetry = strsep(&next,"\t");	/* Currently unused.  Add to description? */
+	  pcomment = strsep(&next,"\t");
+
+/*
+ * Skip header line with names of fields.
+ */
+	  if (strcmp(pchan, "chan") == 0) {
+	    continue;
+	  }
+
+/* 
+ * Save only if we have valid data.
+ * (Some packets don't contain a position.)
+ */
+	  if (pisotime != NULL && strlen(pisotime) > 0 &&
+	      pname != NULL && strlen(pname) > 0 &&
+	      platitude != NULL && strlen(platitude) > 0 &&
+	      plongitude != NULL && strlen(plongitude) > 0) {
+	
+	    float speed = UNKNOWN_VALUE;
+	    float course = UNKNOWN_VALUE;
+	    float alt = UNKNOWN_VALUE;
+	    double freq = UNKNOWN_VALUE;
+	    int offset = UNKNOWN_VALUE;
+	    char stemp[16], desc[32], comment[256];
+
+	    if (pspeed != NULL && strlen(pspeed) > 0) {
+	      speed = KNOTS_TO_METERS_PER_SEC(atof(pspeed));
+	    }
+	    if (pcourse != NULL && strlen(pcourse) > 0) {
+	      course = atof(pcourse);
+	    }
+	    if (paltitude != NULL && strlen(paltitude) > 0) {
+	      alt = atof(platitude);
+	    }
+
+/* combine freq/offset/tone into one description string. */
+
+	    if (pfreq != NULL && strlen(pfreq) > 0) {
+	      freq = atof(pfreq);
+	      sprintf (desc, "%.3f MHz", freq);
+	    }
+	    else {
+	      strcpy (desc, "");
+	    }
+
+	    if (poffset != NULL && strlen(poffset) > 0) {
+	      offset = atoi(poffset);
+	      if (offset != 0 && offset % 1000 == 0) {
+	        sprintf (stemp, "%+dM", offset / 1000);
+	      }
+	      else {
+	        sprintf (stemp, "%+dk", offset);
+	      }
+	      if (strlen(desc) > 0) strcat (desc, " ");
+	      strcat (desc, stemp);
+	    }
+
+	    if (ptone != NULL && strlen(ptone) > 0) {
+	      if (*ptone == 'D') {
+	        sprintf (stemp, "DCS %s", ptone+1);
+	      }
+	      else {
+	        sprintf (stemp, "PL %s", ptone);
+	      }
+	      if (strlen(desc) > 0) strcat (desc, " ");
+	      strcat (desc, stemp);
+	    }
+
+	    strcpy (comment, "");
+	    if (pstatus != NULL && strlen(pstatus) > 0) {
+	      strcpy (comment, pstatus);
+	    }
+	    if (pcomment != NULL && strlen(pcomment) > 0) {
+	      if (strlen(comment) > 0) strcat (comment, ", ");
+	      strcat (comment, pcomment);
+	    }
+	    
+	    if (num_things == max_things) {
+	      /* It's full.  Grow the array by 50%. */
+  	      max_things += max_things / 2;
+	      things = realloc (things, max_things*sizeof(thing_t));	
+	    }
+
+	    things[num_things].lat = atof(platitude);
+	    things[num_things].lon = atof(plongitude);
+	    things[num_things].speed = speed;
+	    things[num_things].course = course;
+	    things[num_things].alt = alt;
+	    strncpy (things[num_things].time, pisotime, sizeof(things[num_things].time));
+	    strncpy (things[num_things].name, pname, sizeof(things[num_things].name));
+	    strncpy (things[num_things].desc, desc, sizeof(things[num_things].desc));
+	    strncpy (things[num_things].comment, comment, sizeof(things[num_things].comment));
+
+	    num_things++;
+	  }
+	}
+}
+
+
+/*
+ * Compare function for use with qsort.
+ * Order by name then date/time.
+ */
+
+static int compar(const void *a, const void *b)
+{
+	thing_t *ta = (thing_t *)a;
+	thing_t *tb = (thing_t *)b;
+	int n;
+
+	n = strcmp(ta->name, tb->name);
+	if (n != 0) 
+	  return (n);
+	return (strcmp(ta->time, tb->time)); 
+}
+
+
+/*
+ * Take quoting out of CSV data.
+ * Replace field separator commas with tabs while retaining 
+ * commas that were part of the original data before quoting.
+ */
+
+static void unquote (char *in, char *out)
+{
+	char *p;
+	char *q = out;		/* Mind your p's and q's */
+	int quoted = 0;
+
+	for (p=in; *p!='\0'; p++) {
+	  if (*p == '"') {
+	    if (p == in || ( !quoted && *(p-1) == ',')) {
+	      /* " found at beginning of field */
+	      quoted = 1;
+	    }
+	    else if (*(p+1) == '\0' || (quoted && *(p+1) == ',')) {
+	      /* " found at end of field */
+	      quoted = 0;
+	    }
+	    else {
+	      /* " found somewhere in middle of field. */
+	      /* We expect to be in quoted state and we should have a pair. */
+	      if (quoted && *(p+1) == '"') {
+	        /* Keep one and drop the other. */
+	        *q++ = *p;
+		p++;
+	      }
+	      else {
+	        /* This shouldn't happen. */
+	        fprintf (stderr, "CSV data quoting is messed up.\n");
+	        *q++ = *p;
+	      }
+	    }
+	  } 
+	  else if (*p == ',') {
+	    if (quoted) {
+	      /* Comma in original data.  Keep it. */
+	      *q++ = *p;
+	    }
+	    else {
+	      /* Comma is field separator.  Replace with tab. */
+	      *q++ = '\t';
+	    }
+	  }
+	  else {
+	    /* copy ordinary character. */
+	    *q++ = *p;
+	  }
+	}
+	*q = '\0';
+}
+
+/*
+ * Prepare text values for XML.
+ * Replace significant characters with "predefined entities."
+ */
+
+static void xml_text (char *in, char *out)
+{
+	char *p, *q;
+
+	q = out;
+	for (p = in; *p != '\0'; p++) {
+	  if (*p == '"') {
+	    *q++ = '&';
+	    *q++ = 'q';
+	    *q++ = 'u';
+	    *q++ = 'o';
+	    *q++ = 't';
+	    *q++ = ';';
+	  }
+	  else if (*p == '&') {
+	    *q++ = '&';
+	    *q++ = 'a';
+	    *q++ = 'm';
+	    *q++ = 'p';
+	    *q++ = ';';
+	  }
+	  else if (*p == '\'') {
+	    *q++ = '&';
+	    *q++ = 'a';
+	    *q++ = 'p';
+	    *q++ = 'o';
+	    *q++ = 's';
+	    *q++ = ';';
+	  }
+	  else if (*p == '<') {
+	    *q++ = '&';
+	    *q++ = 'l';
+	    *q++ = 't';
+	    *q++ = ';';
+	  }
+	  else if (*p == '>') {
+	    *q++ = '&';
+	    *q++ = 'g';
+	    *q++ = 't';
+	    *q++ = ';';
+	  }
+	  else {
+	    *q++ = *p;
+	  }
+	}
+	*q = '\0';
+}
+
+
+/*
+ * Process all things with the same name.
+ * They should be sorted by time.
+ * For stationary entities, generate just one GPX waypoint.
+ * For moving entities, generate a GPX track.
+ */
+
+static void process_things (int first, int last)
+{
+	//printf ("process %d to %d\n", first, last);
+	int i;
+	int moved = 0;
+	char safe_name[30];
+	char safe_comment[120];
+
+	for (i=first+1; i<=last; i++) {
+	  if (things[i].lat != things[first].lat) moved = 1;
+	  if (things[i].lon != things[first].lon) moved = 1;
+	}
+
+	if (moved) {
+
+/*
+ * Generate track for moving thing.
+ */
+	  xml_text (things[first].name, safe_name);
+	  xml_text (things[first].comment, safe_comment);
+
+	  printf ("  <trk>\n");
+	  printf ("    <name>%s</name>\n", safe_name);
+	  printf ("    <trkseg>\n");
+
+	  for (i=first; i<=last; i++) {
+	    printf ("      <trkpt lat=\"%.6f\" lon=\"%.6f\">\n", things[i].lat, things[i].lon);
+	    if (things[i].speed != UNKNOWN_VALUE) {
+	      printf ("        <speed>%.1f</speed>\n", things[i].speed);
+	    }
+	    if (things[i].course != UNKNOWN_VALUE) {
+	      printf ("        <course>%.1f</course>\n", things[i].course);
+	    }
+	    if (things[i].alt != UNKNOWN_VALUE) {
+	      printf ("        <ele>%.1f</ele>\n", things[i].alt);
+	    }
+	    if (strlen(things[i].desc) > 0) {
+	      printf ("        <desc>%s</desc>\n", things[i].desc);
+	    }
+	    if (strlen(safe_comment) > 0) {
+	      printf ("        <cmt>%s</cmt>\n", safe_comment);
+	    }
+	    printf ("        <time>%s</time>\n", things[i].time);
+	    printf ("      </trkpt>\n");
+	  }
+
+	  printf ("    </trkseg>\n");
+	  printf ("  </trk>\n");
+
+	  /* Also generate waypoint for last location. */
+	}
+
+	// Future possibility?
+	// <sym>Symbol Name</sym>	-- not standardized.
+
+/*
+ * Generate waypoint for stationary thing or last known position for moving thing.
+ */
+	xml_text (things[last].name, safe_name);
+	xml_text (things[last].comment, safe_comment);
+
+	printf ("  <wpt lat=\"%.6f\" lon=\"%.6f\">\n", things[last].lat, things[last].lon);
+	if (things[last].alt != UNKNOWN_VALUE) {
+	  printf ("    <ele>%.1f</ele>\n", things[last].alt);
+	}
+	if (strlen(things[i].desc) > 0) {
+	  printf ("    <desc>%s</desc>\n", things[i].desc);
+	}
+	if (strlen(safe_comment) > 0) {
+	  printf ("    <cmt>%s</cmt>\n", safe_comment);
+	}
+	printf ("    <name>%s</name>\n", safe_name);
+	printf ("  </wpt>\n");
+}
\ No newline at end of file
diff --git a/mgn_icon.h b/mgn_icon.h
new file mode 100644
index 0000000..e97005c
--- /dev/null
+++ b/mgn_icon.h
@@ -0,0 +1,267 @@
+
+
+/* 
+ * MGN_icon.h 
+ *
+ * Waypoint icon codes for use in the $PMGNWPL sentence.
+ *
+ * Derived from Data Transmission Protocol For Magellan Products � version 2.11
+ */
+
+#define MGN_crossed_square "a"
+#define MGN_box "b"
+#define MGN_house "c"
+#define MGN_aerial "d"
+#define MGN_airport "e"
+#define MGN_amusement_park "f"
+#define MGN_ATM "g"
+#define MGN_auto_repair "h"
+#define MGN_boating "I"
+#define MGN_camping "j"
+#define MGN_exit_ramp "k"
+#define MGN_first_aid "l"
+#define MGN_nav_aid "m"
+#define MGN_buoy "n"
+#define MGN_fuel "o"
+#define MGN_garden "p"
+#define MGN_golf "q"
+#define MGN_hotel "r"
+#define MGN_hunting_fishing "s"
+#define MGN_large_city "t"
+#define MGN_lighthouse "u"
+#define MGN_major_city "v"
+#define MGN_marina "w"
+#define MGN_medium_city "x"
+#define MGN_museum "y"
+#define MGN_obstruction "z"
+#define MGN_park "aa"
+#define MGN_resort "ab"
+#define MGN_restaurant "ac"
+#define MGN_rock "ad"
+#define MGN_scuba "ae"
+#define MGN_RV_service "af"
+#define MGN_shooting "ag"
+#define MGN_sight_seeing "ah"
+#define MGN_small_city "ai"
+#define MGN_sounding "aj"
+#define MGN_sports_arena "ak"
+#define MGN_tourist_info "al"
+#define MGN_truck_service "am"
+#define MGN_winery "an"
+#define MGN_wreck "ao"
+#define MGN_zoo "ap"
+
+
+/*
+ * Mapping from APRS symbols to Magellan.
+ *
+ * This is a bit of a challenge because there 
+ * are no icons for moving objects.
+ * We can use airport for flying things but 
+ * what about wheeled transportation devices?
+ */
+
+// TODO:  NEEDS MORE WORK!!!
+
+
+#define MGN_default MGN_crossed_square
+
+#define SYMTAB_SIZE 95
+
+static const char mgn_primary_symtab[SYMTAB_SIZE][3] =  {
+
+	MGN_default,		//     00  	 --no-symbol--
+	MGN_default,		//  !  01  	 Police, Sheriff
+	MGN_default,		//  "  02  	 reserved  (was rain)
+	MGN_aerial,		//  #  03  	 DIGI (white center)
+	MGN_default,		//  $  04  	 PHONE
+	MGN_aerial,		//  %  05  	 DX CLUSTER
+	MGN_aerial,		//  &  06  	 HF GATEway
+	MGN_airport,		//  '  07  	 Small AIRCRAFT
+	MGN_aerial,		//  (  08  	 Mobile Satellite Station
+	MGN_default,		//  )  09  	 Wheelchair (handicapped)
+	MGN_default,		//  *  10  	 SnowMobile
+	MGN_default,		//  +  11  	 Red Cross
+	MGN_default,		//  ,  12  	 Boy Scouts
+	MGN_house,		//  -  13  	 House QTH (VHF)
+	MGN_default,		//  .  14  	 X
+	MGN_default,		//  /  15  	 Red Dot
+	MGN_default,		//  0  16  	 # circle (obsolete)
+	MGN_default,		//  1  17  	 TBD
+	MGN_default,		//  2  18  	 TBD
+	MGN_default,		//  3  19  	 TBD
+	MGN_default,		//  4  20  	 TBD
+	MGN_default,		//  5  21  	 TBD
+	MGN_default,		//  6  22  	 TBD
+	MGN_default,		//  7  23  	 TBD
+	MGN_default,		//  8  24  	 TBD
+	MGN_default,		//  9  25  	 TBD
+	MGN_default,		//  :  26  	 FIRE
+	MGN_camping,		//  ;  27  	 Campground (Portable ops)
+	MGN_default,		//  <  28  	 Motorcycle
+	MGN_default,		//  =  29  	 RAILROAD ENGINE
+	MGN_default,		//  >  30  	 CAR
+	MGN_default,		//  ?  31  	 SERVER for Files
+	MGN_default,		//  @  32  	 HC FUTURE predict (dot)
+	MGN_first_aid,		//  A  33  	 Aid Station
+	MGN_aerial,		//  B  34  	 BBS or PBBS
+	MGN_boating,		//  C  35  	 Canoe
+	MGN_default,		//  D  36  	 
+	MGN_default,		//  E  37  	 EYEBALL (Eye catcher!)
+	MGN_default,		//  F  38  	 Farm Vehicle (tractor)
+	MGN_default,		//  G  39  	 Grid Square (6 digit)
+	MGN_default,		//  H  40  	 HOTEL (blue bed symbol)
+	MGN_aerial,		//  I  41  	 TcpIp on air network stn
+	MGN_default,		//  J  42  	 
+	MGN_default,		//  K  43  	 School
+	MGN_default,		//  L  44  	 PC user
+	MGN_default,		//  M  45  	 MacAPRS
+	MGN_aerial,		//  N  46  	 NTS Station
+	MGN_airport,		//  O  47  	 BALLOON
+	MGN_default,		//  P  48  	 Police
+	MGN_default,		//  Q  49  	 TBD
+	MGN_RV_service,		//  R  50  	 REC. VEHICLE
+	MGN_airport,		//  S  51  	 SHUTTLE
+	MGN_default,		//  T  52  	 SSTV
+	MGN_default,		//  U  53  	 BUS
+	MGN_default,		//  V  54  	 ATV
+	MGN_default,		//  W  55  	 National WX Service Site
+	MGN_default,		//  X  56  	 HELO
+	MGN_boating,		//  Y  57  	 YACHT (sail)
+	MGN_default,		//  Z  58  	 WinAPRS
+	MGN_default,		//  [  59  	 Human/Person (HT)
+	MGN_default,		//  \  60  	 TRIANGLE(DF station)
+	MGN_default,		//  ]  61  	 MAIL/PostOffice(was PBBS)
+	MGN_airport,		//  ^  62  	 LARGE AIRCRAFT
+	MGN_default,		//  _  63  	 WEATHER Station (blue)
+	MGN_aerial,		//  `  64  	 Dish Antenna
+	MGN_default,		//  a  65  	 AMBULANCE
+	MGN_default,		//  b  66  	 BIKE
+	MGN_default,		//  c  67  	 Incident Command Post
+	MGN_default,		//  d  68  	 Fire dept
+	MGN_zoo,		//  e  69  	 HORSE (equestrian)
+	MGN_default,		//  f  70  	 FIRE TRUCK
+	MGN_airport,		//  g  71  	 Glider
+	MGN_default,		//  h  72  	 HOSPITAL
+	MGN_default,		//  i  73  	 IOTA (islands on the air)
+	MGN_default,		//  j  74  	 JEEP
+	MGN_default,		//  k  75  	 TRUCK
+	MGN_default,		//  l  76  	 Laptop
+	MGN_aerial,		//  m  77  	 Mic-E Repeater
+	MGN_default,		//  n  78  	 Node (black bulls-eye)
+	MGN_default,		//  o  79  	 EOC
+	MGN_default,		//  p  80  	 ROVER (puppy, or dog)
+	MGN_default,		//  q  81  	 GRID SQ shown above 128 m
+	MGN_aerial,		//  r  82  	 Repeater
+	MGN_default,		//  s  83  	 SHIP (pwr boat)
+	MGN_default,		//  t  84  	 TRUCK STOP
+	MGN_default,		//  u  85  	 TRUCK (18 wheeler)
+	MGN_default,		//  v  86  	 VAN
+	MGN_default,		//  w  87  	 WATER station
+	MGN_aerial,		//  x  88  	 xAPRS (Unix)
+	MGN_aerial,		//  y  89  	 YAGI @ QTH
+	MGN_default,		//  z  90  	 TBD
+	MGN_default,		//  {  91  	 
+	MGN_default,		//  |  92  	 TNC Stream Switch
+	MGN_default,		//  }  93  	 
+	MGN_default };		//  ~  94  	 TNC Stream Switch
+
+
+static const char mgn_alternate_symtab[SYMTAB_SIZE][3] =  {
+
+	MGN_default,		//     00  	 --no-symbol--
+	MGN_default,		//  !  01  	 EMERGENCY (!)
+	MGN_default,		//  "  02  	 reserved
+	MGN_aerial,		//  #  03  	 OVERLAY DIGI (green star)
+	MGN_ATM,		//  $  04  	 Bank or ATM  (green box)
+	MGN_default,		//  %  05  	 Power Plant with overlay
+	MGN_aerial,		//  &  06  	 I=Igte IGate R=RX T=1hopTX 2=2hopTX
+	MGN_default,		//  '  07  	 Crash (& now Incident sites)
+	MGN_default,		//  (  08  	 CLOUDY (other clouds w ovrly)
+	MGN_aerial,		//  )  09  	 Firenet MEO, MODIS Earth Obs.
+	MGN_default,		//  *  10  	 SNOW (& future ovrly codes)
+	MGN_default,		//  +  11  	 Church
+	MGN_default,		//  ,  12  	 Girl Scouts
+	MGN_house,		//  -  13  	 House (H=HF) (O = Op Present)
+	MGN_default,		//  .  14  	 Ambiguous (Big Question mark)
+	MGN_default,		//  /  15  	 Waypoint Destination
+	MGN_default,		//  0  16  	 CIRCLE (E/I/W=IRLP/Echolink/WIRES)
+	MGN_default,		//  1  17  	 
+	MGN_default,		//  2  18  	 
+	MGN_default,		//  3  19  	
+	MGN_default,		//  4  20  
+	MGN_default,		//  5  21 
+	MGN_default,		//  6  22
+	MGN_default,		//  7  23
+	MGN_aerial,		//  8  24  	 802.11 or other network node
+	MGN_fuel,		//  9  25  	 Gas Station (blue pump)
+	MGN_default,		//  :  26  	 Hail (& future ovrly codes)
+	MGN_park,		//  ;  27  	 Park/Picnic area
+	MGN_default,		//  <  28  	 ADVISORY (one WX flag)
+	MGN_default,		//  =  29  	 APRStt Touchtone (DTMF users)
+	MGN_default,		//  >  30  	 OVERLAYED CAR
+	MGN_tourist_info,	//  ?  31  	 INFO Kiosk  (Blue box with ?)
+	MGN_default,		//  @  32  	 HURICANE/Trop-Storm
+	MGN_default,		//  A  33  	 overlayBOX DTMF & RFID & XO
+	MGN_default,		//  B  34  	 Blwng Snow (& future codes)
+	MGN_boating,		//  C  35  	 Coast Guard
+	MGN_default,		//  D  36  	 Drizzle (proposed APRStt)
+	MGN_default,		//  E  37  	 Smoke (& other vis codes)
+	MGN_default,		//  F  38  	 Freezng rain (&future codes)
+	MGN_default,		//  G  39  	 Snow Shwr (& future ovrlys)
+	MGN_default,		//  H  40  	 Haze (& Overlay Hazards)
+	MGN_default,		//  I  41  	 Rain Shower
+	MGN_default,		//  J  42  	 Lightening (& future ovrlys)
+	MGN_default,		//  K  43  	 Kenwood HT (W)
+	MGN_lighthouse,		//  L  44  	 Lighthouse
+	MGN_default,		//  M  45  	 MARS (A=Army,N=Navy,F=AF)
+	MGN_nav_aid,		//  N  46  	 Navigation Buoy
+	MGN_airport,		//  O  47  	 Rocket
+	MGN_default,		//  P  48  	 Parking
+	MGN_default,		//  Q  49  	 QUAKE
+	MGN_restaurant,		//  R  50  	 Restaurant
+	MGN_aerial,		//  S  51  	 Satellite/Pacsat
+	MGN_default,		//  T  52  	 Thunderstorm
+	MGN_default,		//  U  53  	 SUNNY
+	MGN_default,		//  V  54  	 VORTAC Nav Aid
+	MGN_default,		//  W  55  	 # NWS site (NWS options)
+	MGN_default,		//  X  56  	 Pharmacy Rx (Apothicary)
+	MGN_aerial,		//  Y  57  	 Radios and devices
+	MGN_default,		//  Z  58  	 
+	MGN_default,		//  [  59  	 W.Cloud (& humans w Ovrly)
+	MGN_default,		//  \  60  	 New overlayable GPS symbol
+	MGN_default,		//  ]  61  	 
+	MGN_airport,		//  ^  62  	 # Aircraft (shows heading)
+	MGN_default,		//  _  63  	 # WX site (green digi)
+	MGN_default,		//  `  64  	 Rain (all types w ovrly)
+	MGN_aerial,		//  a  65  	 ARRL, ARES, WinLINK
+	MGN_default,		//  b  66  	 Blwng Dst/Snd (& others)
+	MGN_default,		//  c  67  	 CD triangle RACES/SATERN/etc
+	MGN_default,		//  d  68  	 DX spot by callsign
+	MGN_default,		//  e  69  	 Sleet (& future ovrly codes)
+	MGN_default,		//  f  70  	 Funnel Cloud
+	MGN_default,		//  g  71  	 Gale Flags
+	MGN_default,		//  h  72  	 Store. or HAMFST Hh=HAM store
+	MGN_default,		//  i  73  	 BOX or points of Interest
+	MGN_default,		//  j  74  	 WorkZone (Steam Shovel)
+	MGN_default,		//  k  75  	 Special Vehicle SUV,ATV,4x4
+	MGN_default,		//  l  76  	 Areas      (box,circles,etc)
+	MGN_default,		//  m  77  	 Value Sign (3 digit display)
+	MGN_default,		//  n  78  	 OVERLAY TRIANGLE
+	MGN_default,		//  o  79  	 small circle
+	MGN_default,		//  p  80  	 Prtly Cldy (& future ovrlys)
+	MGN_default,		//  q  81  	 
+	MGN_default,		//  r  82  	 Restrooms
+	MGN_default,		//  s  83  	 OVERLAY SHIP/boat (top view)
+	MGN_default,		//  t  84  	 Tornado
+	MGN_default,		//  u  85  	 OVERLAYED TRUCK
+	MGN_default,		//  v  86  	 OVERLAYED Van
+	MGN_default,		//  w  87  	 Flooding
+	MGN_wreck,		//  x  88  	 Wreck or Obstruction ->X<-
+	MGN_default,		//  y  89  	 Skywarn
+	MGN_default,		//  z  90  	 OVERLAYED Shelter
+	MGN_default,		//  {  91  	 Fog (& future ovrly codes)
+	MGN_default,		//  |  92  	 TNC Stream Switch
+	MGN_default,		//  }  93  	 
+	MGN_default };		//  ~  94  	 TNC Stream Switch
+
diff --git a/misc/README-dire-wolf.txt b/misc/README-dire-wolf.txt
index a6373bc..f824e25 100755
--- a/misc/README-dire-wolf.txt
+++ b/misc/README-dire-wolf.txt
@@ -1,3 +1,4 @@
+This is NOT used for the Linux version.
 These are part of the standard C library for Linux and Cygwin.
 For the Windows version we need to include our own copy.
 
diff --git a/multi_modem.c b/multi_modem.c
index aae42f0..0c1e4a6 100755
--- a/multi_modem.c
+++ b/multi_modem.c
@@ -59,9 +59,19 @@
  *		multiple modems & HDLC decoders per channel.  The tricky
  *		part is picking the best one when there is more than one
  *		success and discarding the rest.
- *		
+ *
+ * New in version 1.1:
+ *
+ *		Several enhancements provided by Fabrice FAURE:
+ *
+ *		Additional types of attempts to fix a bad CRC.
+ *		Optimized code to reduce execution time.
+ *		Improved detection of duplicate packets from
+ *		different fixup attempts.
+ *		Set limit on number of packets in fix up later queue.
+ *
  *------------------------------------------------------------------*/
-
+//#define DEBUG 1
 #define DIGIPEATER_C
 
 
@@ -97,8 +107,14 @@ static struct {
 	int score;
 
 } candidate[MAX_CHANS][MAX_SUBCHANS];
+#define MAX_STORED_CRC 256
+
+typedef struct crc_s {
+	struct crc_s* nextp;	/* Next pointer to maintain a queue. */
+	unsigned int crc;
+} *crc_t;
 
-static unsigned int crc_of_last_to_app[MAX_CHANS];
+static crc_t crc_queue_of_last_to_app[MAX_CHANS];
 
 #define PROCESS_AFTER_BITS 2
 
@@ -139,12 +155,99 @@ void multi_modem_init (struct audio_s *pmodem)
 
 	for (chan=0; chan<modem.num_channels; chan++) {
 	  process_age[chan] = PROCESS_AFTER_BITS * modem.samples_per_sec / modem.baud[chan];
-	  crc_of_last_to_app[chan] = 0x12345678;
+	  crc_queue_of_last_to_app[chan] = NULL;
+	}
+
+}
+
+//Add a crc to the end of the queue and returns the numbers of CRC stored in the queue
+int crc_queue_append (unsigned int crc, unsigned int chan) {
+	crc_t plast;
+	crc_t plast1;
+	crc_t pnext;
+	crc_t new_crc;
+	
+	unsigned int nb_crc = 1;
+	if (chan>=MAX_CHANS) {
+	  return -1;
+	}
+	new_crc = (crc_t) malloc (10*sizeof(struct crc_s));
+	if (!new_crc)
+	  return -1;
+	new_crc->crc = crc;
+	new_crc->nextp = NULL;
+	if (crc_queue_of_last_to_app[chan] == NULL) {
+	  crc_queue_of_last_to_app[chan] = new_crc;
+	  nb_crc = 1;
+	}
+	else {
+	  nb_crc = 2;
+	  plast = crc_queue_of_last_to_app[chan];
+	  pnext = plast->nextp;
+	  while (pnext != NULL) {
+	    nb_crc++;
+	    plast = pnext;
+	    pnext = pnext->nextp;
+	  }
+	  plast->nextp = new_crc;
+	}
+#if DEBUG 
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("Out crc_queue_append nb_crc = %d\n", nb_crc);
+#endif
+	return nb_crc;
+
+
+}
+
+//Remove the crc from the top of the queue
+unsigned int crc_queue_remove (unsigned int chan) {
+
+	unsigned int res;
+	crc_t plast;
+	crc_t pnext;
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("In crc_queue_remove\n");
+#endif
+	crc_t removed_crc;
+	if (chan>=MAX_CHANS) {
+	  return 0;
+	}
+	removed_crc = crc_queue_of_last_to_app[chan];
+	if (removed_crc == NULL) {
+	  return 0;
 	}
+	else {
+
+	  crc_queue_of_last_to_app[chan] = removed_crc->nextp;
+	  res = removed_crc->crc;
+	  free(removed_crc);
+	  
+	}
+	return res;
 
 }
 
+unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { 
+	crc_t plast;
+	crc_t pnext;
 
+	if (crc_queue_of_last_to_app[chan] == NULL) {
+	  return 0;
+	}
+	else {
+	  plast = crc_queue_of_last_to_app[chan];
+	  do {
+	    pnext = plast->nextp;
+	    if (plast->crc == crc) {
+	      return 1;
+	    }
+	    plast = pnext;
+	  } while (pnext != NULL);
+	}
+	return 0;
+}
 
 /*------------------------------------------------------------------------------
  *
@@ -310,22 +413,28 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf,
  * Either pass it along or drop if duplicate.
  */
 
-	if (retries == RETRY_TWO_SEP) {
+	if (retries >= RETRY_SWAP_TWO_SEP) {
 	  int mycrc;
 	  char spectrum[MAX_SUBCHANS+1];
+	  int dropped = 0;
 
 	  memset (spectrum, 0, sizeof(spectrum));
 	  memset (spectrum, '_', (size_t)modem.num_subchan[chan]);
 	  spectrum[subchan] = '.';
 
 	  mycrc = ax25_m_m_crc(pp);
+/* Smetimes recovered packet is not the latest one send to the app:
+ * It can be a packet sent to the app before the latest one because of the processing time ...
+ * So we check if the crc of current packet has already been received in the queue of others crc
+ */
+	  dropped = is_crc_in_queue(chan, mycrc);
 
 #if DEBUG
 	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("\n%s\n%d.%d: ptr=%p, retry=%d, age=, crc=%04x, score=  \n", 
-		spectrum, chan, subchan, pp, (int)retries, mycrc);
+	  dw_printf ("\n%s\n%d.%d: ptr=%p, retry=%d, age=, crc=%04x, score=  , dropped =%d\n", 
+		spectrum, chan, subchan, pp, (int)retries, mycrc,dropped);
 #endif	   
-	  if (mycrc == crc_of_last_to_app[chan]) {
+	  if (dropped) {
 	     /* Same as last one.  Drop it. */
 	     ax25_delete (pp);
 #if DEBUG
@@ -338,7 +447,8 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf,
 	  dw_printf ("Send the best one along.\n");
 #endif
 	  app_process_rec_packet (chan, subchan, pp, alevel, retries, spectrum);
-	  crc_of_last_to_app[chan] = mycrc;
+	  if (crc_queue_append(mycrc, chan) > MAX_STORED_CRC)
+	    crc_queue_remove(chan);
 	  return;
 	}
 
@@ -397,7 +507,7 @@ static void pick_best_candidate (int chan)
 	  else if (candidate[chan][subchan].retries == RETRY_NONE) {
 	    spectrum[subchan] = '|';
 	  }
-	  else if (candidate[chan][subchan].retries == RETRY_SINGLE) {
+	  else if (candidate[chan][subchan].retries == RETRY_SWAP_SINGLE) {
 	    spectrum[subchan] = ':';
 	  }
 	  else  {
@@ -406,7 +516,7 @@ static void pick_best_candidate (int chan)
 
 	  /* Begining score depends on effort to get a valid frame CRC. */
 
-	  candidate[chan][subchan].score = 5000 - ((int)candidate[chan][subchan].retries * 1000);
+	  candidate[chan][subchan].score = RETRY_MAX * 1000 - ((int)candidate[chan][subchan].retries * 1000);
 
 	  /* Bump it up slightly if others nearby have the same CRC. */
 	  
@@ -461,8 +571,8 @@ static void pick_best_candidate (int chan)
 		candidate[chan][best_subchan].alevel, 
 		(int)(candidate[chan][best_subchan].retries), 
 		spectrum);
-        crc_of_last_to_app[chan] = candidate[chan][best_subchan].crc;
-
+	if (crc_queue_append(candidate[chan][best_subchan].crc, chan) > MAX_STORED_CRC)
+	    crc_queue_remove(chan);
 	/* Someone else will delete so don't do it below. */
 	candidate[chan][best_subchan].packet_p = NULL;
 
diff --git a/nmea.c b/nmea.c
new file mode 100755
index 0000000..bcd58e6
--- /dev/null
+++ b/nmea.c
@@ -0,0 +1,1037 @@
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014  John Langner, WB2OSZ
+//
+//    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.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+//#define DEBUG 1
+
+/*------------------------------------------------------------------
+ *
+ * Module:      nmea.c
+ *
+ * Purpose:   	Receive NMEA sentences from a GPS receiver.
+ *		Send NMEA waypoint sentences to GPS display or mapping application.
+ *		
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+
+#if __WIN32__
+#include <stdlib.h>
+#include <windows.h>
+#else
+#define __USE_XOPEN2KXSI 1
+#define __USE_XOPEN 1
+//#define __USE_POSIX 1
+#include <stdlib.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+#endif
+
+#include "direwolf.h"
+#include "config.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+//#include "xmit.h"
+#include "latlong.h"
+#include "nmea.h"
+#include "grm_sym.h"		/* Garmin symbols */
+#include "mgn_icon.h"		/* Magellan icons */
+
+
+#if __WIN32__
+typedef HANDLE MYFDTYPE;
+#define MYFDERROR INVALID_HANDLE_VALUE
+#else
+typedef int MYFDTYPE;
+#define MYFDERROR (-1)
+#endif
+
+
+// TODO: receive buffer... static kiss_frame_t kf;	/* Accumulated KISS frame and state of decoder. */
+
+static MYFDTYPE nmea_port_fd = MYFDERROR;
+
+static void nmea_send_sentence (char *sent);
+
+#if __WIN32__
+static unsigned __stdcall nmea_listen_thread (void *arg);
+#else
+static void * nmea_listen_thread (void *arg);
+#endif
+
+static void nmea_parse_gps (char *sentence);
+
+
+static int nmea_debug = 0;		/* Print information flowing from and to attached device. */
+
+void nmea_set_debug (int n) 
+{	
+	nmea_debug = n;
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	nmea_init
+ *
+ * Purpose:	Initialization for NMEA communication port.
+ &
+ * Inputs:	mc->nmea_port	- name of serial port.
+ *
+ * Global output: nmea_port_fd
+ *	
+ *
+ * Description:	(1) Open serial port device.
+ *		(2) Start a new thread to listen for GPS receiver.
+ *
+ * Reference:	http://www.robbayer.com/files/serial-win.pdf
+ *
+ *---------------------------------------------------------------*/
+
+
+
+
+static MYFDTYPE nmea_open_port (char *device);
+
+void nmea_init (struct misc_config_s *mc)
+{
+	
+#if __WIN32__
+	HANDLE nmea_listen_th;
+#else
+	pthread_t nmea_listen_tid;
+#endif
+
+/*
+ * Open serial port connection.
+ */
+	if (strlen(mc->nmea_port) > 0) {
+
+#if ! __WIN32__
+
+	  /* Translate Windows device name into Linux name. */
+	  /* COM1 -> /dev/ttyS0, etc. */
+
+	  if (strncasecmp(mc->nmea_port, "COM", 3) == 0) {
+	    int n = atoi (mc->nmea_port + 3);
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf ("Converted NMEA device '%s'", mc->nmea_port);
+	    if (n < 1) n = 1;
+	    sprintf (mc->nmea_port, "/dev/ttyS%d", n-1);
+	    dw_printf (" to Linux equivalent '%s'\n", mc->nmea_port);
+	  }
+#endif
+	  nmea_port_fd = nmea_open_port (mc->nmea_port);
+
+	  if (nmea_port_fd != MYFDERROR) {
+#if __WIN32__
+	    nmea_listen_th = (HANDLE)_beginthreadex (NULL, 0, nmea_listen_thread, (void*)(long)nmea_port_fd, 0, NULL);
+	    if (nmea_listen_th == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Could not create NMEA listening thread.\n");
+	      return;
+	    }
+#else
+	    int e;
+	    e = pthread_create (&nmea_listen_tid, NULL, nmea_listen_thread, (void*)(long)nmea_port_fd);
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror("Could not create NMEA listening thread.");
+	    
+	    }
+#endif
+	  }
+	}
+
+
+#if DEBUG
+	text_color_set (DW_COLOR_DEBUG);
+
+	dw_printf ("end of nmea_init: nmea_port_fd = %d\n", nmea_port_fd);
+#endif
+}
+
+
+/*
+ * Returns fd for serial port or MYFDERROR for error.
+ */
+
+
+static MYFDTYPE nmea_open_port (char *devicename)
+{
+
+#if __WIN32__
+
+	MYFDTYPE fd;
+	DCB dcb;
+	int ok;
+	char bettername[50];
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("nmea_open_port ( '%s' )\n", devicename);
+#endif
+	
+
+// Need to use FILE_FLAG_OVERLAPPED for full duplex operation.
+// Without it, write blocks when waiting on read.
+
+// Read http://support.microsoft.com/kb/156932 
+
+// Bug fix in release 1.1 - Need to munge name for COM10 and up.
+// http://support.microsoft.com/kb/115831
+
+	strcpy (bettername, devicename);
+	if (strncasecmp(devicename, "COM", 3) == 0) {
+	  int n;
+	  n = atoi(devicename+3);
+	  if (n >= 10) {
+	    strcpy (bettername, "\\\\.\\");
+	    strcat (bettername, devicename);
+	  }
+	}
+
+	fd = CreateFile(bettername, GENERIC_READ | GENERIC_WRITE, 
+			0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+
+	if (fd == MYFDERROR) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Could not open NMEA port %s.\n", devicename);
+	  return (MYFDERROR);
+	}
+
+	/* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */
+
+	memset (&dcb, 0, sizeof(dcb));
+	dcb.DCBlength = sizeof(DCB);
+
+	ok = GetCommState (fd, &dcb);
+	if (! ok) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("nmea_open_port: GetCommState failed.\n");
+	}
+
+	/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
+
+	dcb.DCBlength = sizeof(DCB);
+	dcb.BaudRate = CBR_4800;
+	dcb.fBinary = 1;
+	dcb.fParity = 0;
+	dcb.fOutxCtsFlow = 0;
+	dcb.fOutxDsrFlow = 0;
+	dcb.fDtrControl = DTR_CONTROL_DISABLE;
+	dcb.fDsrSensitivity = 0;
+	dcb.fOutX = 0;
+	dcb.fInX = 0;
+	dcb.fErrorChar = 0;
+	dcb.fNull = 0;		/* Don't drop nul characters! */
+	dcb.fRtsControl = 0;
+	dcb.ByteSize = 8;
+	dcb.Parity = NOPARITY;
+	dcb.StopBits = ONESTOPBIT;
+
+	ok = SetCommState (fd, &dcb);
+	if (! ok) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("nmea_open_port: SetCommState failed.\n");
+	}
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf("NMEA communication started on %s.\n", devicename);
+
+#else
+
+/* Linux version. */
+
+	int fd;
+	struct termios ts;
+	int e;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("nmea_open_port ( '%s' )\n", devicename);
+#endif
+
+	fd = open (devicename, O_RDWR);
+
+	if (fd == MYFDERROR) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Could not open NMEA port %s.\n", devicename);
+	  return (MYFDERROR);
+	}
+
+	e = tcgetattr (fd, &ts);
+	if (e != 0) { perror ("nm tcgetattr"); }
+
+	cfmakeraw (&ts);
+	
+	ts.c_cc[VMIN] = 1;	/* wait for at least one character */
+	ts.c_cc[VTIME] = 0;	/* no fancy timing. */
+
+	cfsetispeed (&ts, B4800);
+	cfsetospeed (&ts, B4800);
+
+	e = tcsetattr (fd, TCSANOW, &ts);
+	if (e != 0) { perror ("nmea tcsetattr"); }
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf("NMEA communication started on %s.\n", devicename);
+
+#endif
+
+	return (fd);
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        append_checksum
+ *
+ * Purpose:     Append checksum to the sentence.
+ *		
+ * In/out:	sentence	- NMEA sentence beginning with '$'.
+ *
+ * Description:	Checksum is exclusive of characters except leading '$'.
+ *		We append '*' and an upper case two hexadecimal value.
+ *
+ *--------------------------------------------------------------------*/
+
+static void append_checksum (char *sentence)
+{
+	char *p;
+	int cs;
+
+	assert (sentence[0] == '$');
+
+	cs = 0;
+	for (p = sentence+1; *p != '\0'; p++) {
+	  cs ^= *p;
+	}
+
+	sprintf (p, "*%02X", cs & 0xff);
+
+// Add crlf too?
+
+} /* end append_checksum */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        nema_send_waypoint
+ *
+ * Purpose:     Convert APRS position or object into NMEA waypoint sentence
+ *		for use by a GPS display or other mapping application.
+ *		
+ * Inputs:	wname_in	- Name of waypoint.
+ *		dlat		- Latitude.
+ *		dlong		- Longitude.
+ *		symtab		- Symbol table or overlay character.
+ *		symbol		- Symbol code.
+ *		alt		- Altitude in meters or G_UNKOWN.
+ *		course		- Course in degrees or ??? for unknown.
+ *		speed		- Speed in knots ?? or ??
+ *		comment		- Description or message.
+ *		
+ *
+ * Description:	Currently we send multiple styles.  Maybe someday there might
+ * 		be an option to send a selected subset.
+ *
+ *			$GPWPL		- Generic with only location and name.
+ *			$PGRMW		- Garmin, adds altitude, symbol, and comment
+ *					  to previously named waypoint.
+ *			$PMGNWPL	- Magellan, more complete for stationary objects.
+ *			$PKWDWPL	- Kenwood with APRS style symbol but missing comment.
+ * 
+ *--------------------------------------------------------------------*/
+
+
+void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol, 
+			float alt, float course, float speed, char *comment)
+{
+	char wname[12];		/* Waypoint name.  Any , or * removed. */
+	char slat[12];		/* DDMM.mmmm */
+	char slat_ns[2];	/* N or S */
+	char slong[12];		/* DDDMM.mmmm */
+	char slong_ew[2];	/* E or W */	
+	char sentence[500];
+	char salt[12];		/* altitude as string, empty if unknown */
+	char sspeed[12];	/* speed as string, empty if unknown */
+	char scourse[12];	/* course as string, empty if unknown */
+	int grm_sym;		/* Garmin symbol code. */
+	char sicon[4];		/* Magellan icon string */
+	char stime[8];
+	char sdate[8];
+	char *p;
+
+
+
+// Remove any comma from name.
+
+// TODO: remove any , or * from comment.
+
+	strcpy (wname, wname_in);
+	for (p=wname; *p != '\0'; p++) {
+	  if (*p == ',') *p = ' ';
+	  if (*p == '*') *p = ' ';
+	}
+
+// Convert position to character form.
+
+	latitude_to_nmea (dlat, slat, slat_ns);
+	longitude_to_nmea (dlong, slong, slong_ew);
+
+/*
+ *	Generic.
+ *
+ *	$GPWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,wname*99
+ *
+ *	Where,
+ *	 	ddmm.mmmm,ns	is latitude
+ *		dddmm.mmmm,ew	is longitude
+ *		wname		is the waypoint name
+ *		*99		is checksum
+ */
+
+	sprintf (sentence, "$GPWPL,%s,%s,%s,%s,%s", slat, slat_ns, slong, slong_ew, wname);
+	append_checksum (sentence);
+	nmea_send_sentence (sentence);
+
+
+
+/*
+ *	Garmin - https://www8.garmin.com/support/pdf/NMEA_0183.pdf
+ *		 http://gpsinformation.net/mag-proto.htm
+ *
+ *	$PGRMW,wname,alt,symbol,comment*99
+ *
+ *	Where,
+ *
+ *		wname		is waypoint name.  Must match existing waypoint.
+ *		alt		is altitude in meters.
+ *		symbol		is symbol code.  Hexadecimal up to FFFF.
+ *				    See Garmin Device Interface Specification 001-0063-00.
+ *		comment		is comment for the waypoint.  
+ *		*99		is checksum
+ */
+
+	if (alt == G_UNKNOWN) {
+	  strcpy (salt, "");
+	} 
+	else {
+	  sprintf (salt, "%.1f", alt);
+	}
+	grm_sym = 0x1234; 	// TODO
+
+	sprintf (sentence, "$PGRMW,%s,%s,%04X,%s", wname, salt, grm_sym, comment);
+	append_checksum (sentence);
+	nmea_send_sentence (sentence);
+
+/*
+ *	Magellan - http://www.gpsinformation.org/mag-proto-2-11.pdf
+ *
+ *	$PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99
+ *
+ *	Where,
+ *	 	ddmm.mmmm,ns	is latitude
+ *		dddmm.mmmm,ew	is longitude
+ *		alt		is altitude
+ *		unit		is M for meters or F for feet
+ *		wname		is the waypoint name
+ *		comment		is message or comment
+ *		icon		is one or two letters for icon code
+ *		xx		is waypoint type which is optional, not well 
+ *					defined, and not used in their example.					
+ *		*99		is checksum
+ */
+
+// TODO: icon
+
+	sprintf (sicon, "??");
+	sprintf (sentence, "$PMGNWPL,%s,%s,%s,%s,%s,M,%s,%s,%s",
+			slat, slat_ns, slong, slong_ew, salt, wname, comment, sicon);
+	append_checksum (sentence);
+	nmea_send_sentence (sentence);
+
+/*
+ *	Kenwood - Speculation due to no official spec found so far.
+ *
+ *	$PKWDWPL,hhmmss,v,ddmm.mmmm,ns,dddmm.mmmm,ew,speed,course,ddmmyy,alt,wname,ts*99
+ *
+ *	Where,
+ *		hhmmss		is time in UTC.  Should we supply current
+ *					time or only pass along time from
+ *					received signal?
+ *		v		indicates valid data ?????????????????
+ *					Why would we send if not valid?
+ *	 	ddmm.mmmm,ns	is latitude
+ *		dddmm.mmmm,ew	is longitude
+ *		speed		is speed in UNITS ???  knots ?????
+ *		course		is course in degrees
+ *		ddmmyy		is date.  Same question as time.
+ *		alt		is altitude.  in UNITS ??? meters ???
+ *		wname		is the waypoint name
+ *		ts		are the table and symbol.
+ *					Non-standard parsing would be required
+ *					to deal with these for symbol:
+ *						, Boy Scouts / Girl Scouts
+ *						* SnowMobile / Snow				
+ *		*99		is checksum
+ *
+ *	Oddly, there is not place for comment.
+ */
+ 
+	if (speed == G_UNKNOWN) {
+	  strcpy (sspeed, "");
+	} 
+	else {
+	  sprintf (sspeed, "%.1f", speed);
+	}
+	if (course == G_UNKNOWN) {
+	  strcpy (scourse, "");
+	} 
+	else {
+	  sprintf (scourse, "%.1f", course);
+	}
+
+// TODO:  how to handle time & date ???
+
+	strcpy (stime, "123456");
+	strcpy (sdate, "123456");
+
+	sprintf (sentence, "$PKWDWPL,%s,V,%s,%s,%s,%s,%s,%s,%s,%s,%s,%c%c",
+			stime, slat, slat_ns, slong, slong_ew, 
+			sspeed, scourse, sdate, salt, wname, symtab, symbol);
+	append_checksum (sentence);
+	nmea_send_sentence (sentence);
+
+/*
+ *	One application recognizes these.  Not implemented at this time.
+ *
+ *	$GPTLL,01,ddmm.mmmm,ns,dddmm.mmmm,ew,tname,000000.00,T,R*99
+ * 
+ *	Where,
+ *		ddmm.mmmm,ns	is latitude
+ *		dddmm.mmmm,ew	is longitude
+ *		tname		is the target name
+ *		000000.00	is timestamp ???
+ *		T		is target status (S for need help)
+ *		R		is reference target ???
+ *		*99		is checksum
+ * 
+ * 
+ *	$GPTXT,01,01,tname,message*99
+ *
+ *	Where,
+ *
+ *		01		is total number of messages in transmission
+ *		01		is message number in this transmission
+ *		tname		is target name.  Should match name in WPL or TTL.
+ *		message		is the message.  
+ *		*99		is checksum
+ * 
+ */
+
+} /* end nmea_send_waypoint */
+
+
+#if 0
+
+*  d710a menu 603   $GPWPL  $PMGNWPL  $PKWDWPL
+
+symbol mapping
+
+https://freepository.com:444/50lItuLQ7fW6s-web/browser/Tracker2/trunk/sources/waypoint.c?rev=108
+
+
+
+Data Transmission Protocol For Magellan Products � version 2.11
+
+
+
+
+
+
+$PMGNWPL,4651.529,N,07111.425,W,0000000,M,GC5A5F, GC. The straight line,a*13
+ $PMGNWPL,3549.499,N,08650.827,W,0000257,M,HOME,HOME,c*4D
+
+http://gpsbabel.sourcearchive.com/documentation/1.3.7~cvs1/magproto_8c-source.html
+
+      sscanf(trkmsg,"$PMGNWPL,%lf,%c,%lf,%c,%d,%c,%[^,],%[^,]", 
+            &latdeg,&latdir,
+            &lngdeg,&lngdir,
+            &alt,&altunits,shortname,descr);  then icon
+
+   sprintf(obuf, "PMGNWPL,%4.3f,%c,%09.3f,%c,%07.0f,M,%-.*s,%-.46s,%s",
+            lat, ilat < 0 ? 'S' : 'N',
+            lon, ilon < 0 ? 'W' : 'E',
+            waypointp->altitude == unknown_alt ?
+                  0 : waypointp->altitude,
+            wpt_len,
+            owpt,
+            odesc,
+            icon_token);
+
+https://freepository.com:444/50lItuLQ7fW6s-web/changeset/108/Tracker2/trunk/sources/waypoint.c
+
+$PMGNWPL,4106.003,S,14640.214,E,0000069,M,KISSING SPOT,,a*3C
+
+
+
+
+
+ *
+https://freepository.com:444/50lItuLQ7fW6s-web/changeset/325
+
+
+#endif
+
+
+static void nmea_send_sentence (char *sent)
+{
+
+	int err;
+	int len = strlen(sent);
+
+
+	if (nmea_port_fd == MYFDERROR) {
+	  return;
+	}
+
+	text_color_set(DW_COLOR_XMIT);
+	dw_printf ("%s\n", sent);
+
+	if (nmea_debug) {
+	  // TODO: debugg out... nmea_debug_print (TO_CLIENT, NULL, nmea_buff+1, nmea_len-2);
+	}
+
+// TODO:  need to append CR LF.
+
+
+#if __WIN32__
+
+	DWORD nwritten; 
+
+	/* Without this, write blocks while we are waiting on a read. */
+	static OVERLAPPED ov_wr;
+	memset (&ov_wr, 0, sizeof(ov_wr));
+
+        if ( ! WriteFile (nmea_port_fd, sent, len, &nwritten, &ov_wr))
+	{
+	  err = GetLastError();
+	  if (err != ERROR_IO_PENDING) 
+	  {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("\nError sending NMEA sentence.  Error %d.\n\n", (int)GetLastError());
+	    //CloseHandle (nmea_port_fd);
+	    //nmea_port_fd = MYFDERROR;
+	  }
+	}
+	else if (nwritten != len) 
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError sending NMEA sentence.  Only %d of %d written.\n\n", (int)nwritten, len);
+	  //CloseHandle (nmea_port_fd);
+	  //nmea_port_fd = MYFDERROR;
+	}
+
+#else
+        err = write (nmea_port_fd, sent, (size_t)len);
+	if (err != len)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError sending NMEA sentence. err=%d\n\n", err);
+	  //close (nmea_port_fd);
+	  //nmea_port_fd = MYFDERROR;
+	}
+#endif
+
+
+} /* nmea_send_sentence */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        nmea_listen_thread
+ *
+ * Purpose:     Wait for messages from GPS receiver.
+ *
+ * Inputs:	arg		- File descriptor for reading.
+ *
+ * Outputs:	pt_slave_fd	- File descriptor for communicating with client app.
+ *
+ * Description:	Process messages from the client application.
+ *
+ *--------------------------------------------------------------------*/
+
+//TODO: should pass fd by reference so it can be zapped.
+//BUG: If we close it here, that fact doesn't get back 
+// to the main receiving thread.
+
+/* Return one byte (value 0 - 255) or terminate thread on error. */
+
+
+static int nmea_get (MYFDTYPE fd)
+{
+	unsigned char ch;
+
+#if __WIN32__		/* Native Windows version. */
+
+	DWORD n;	
+	static OVERLAPPED ov_rd;
+
+	memset (&ov_rd, 0, sizeof(ov_rd));
+	ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
+
+	
+	/* Overlapped I/O makes reading rather complicated. */
+	/* See:  http://msdn.microsoft.com/en-us/library/ms810467.aspx */
+
+	/* It seems that the read completes OK with a count */
+	/* of 0 every time we send a message to the serial port. */
+
+	n = 0;	/* Number of characters read. */
+
+  	while (n == 0) {
+
+	  if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd)) 
+	  {
+	    int err1 = GetLastError();
+
+	    if (err1 == ERROR_IO_PENDING) 
+	    {
+	      /* Wait for completion. */
+
+	      if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0) 
+	      {
+	        if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1))
+	        {
+	          int err3 = GetLastError();
+
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("\nKISS GetOverlappedResult error %d.\n\n", err3);
+	        }
+	        else 
+	        {
+		  /* Success!  n should be 1 */
+	        }
+	      }
+	    }
+	    else
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1);
+	      //CloseHandle (fd);
+	      //fd = MYFDERROR;
+	      //pthread_exit (NULL);
+	    }
+	  }
+
+	}	/* end while n==0 */
+
+	CloseHandle(ov_rd.hEvent); 
+
+	if (n != 1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nNMEA failed to get one byte. n=%d.\n\n", (int)n);
+
+#if DEBUG9
+	  fprintf (log_fp, "n=%d\n", n);
+#endif
+	}
+
+
+#else		/* Linux version */
+
+	int n;
+
+	n = read(fd, &ch, (size_t)1);
+
+	if (n != 1) {
+	  //text_color_set(DW_COLOR_ERROR);
+	  //dw_printf ("\nError receiving kiss message from client application.  Closing connection %d.\n\n", fd);
+
+	  close (fd);
+
+	  fd = MYFDERROR;
+	  pthread_exit (NULL);
+	}
+
+#endif
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("nmea_get(%d) returns 0x%02x\n", fd, ch);
+#endif
+
+	return (ch);
+}
+
+
+// Maximum length of message from GPS receiver.
+// 82 according to some people.  Larger to be safe.
+
+#define NMEA_MAX_LEN 120	
+
+static char gps_msg[NMEA_MAX_LEN];
+int gps_msg_len = 0;
+
+
+#if __WIN32__
+static unsigned __stdcall nmea_listen_thread (void *arg)
+#else
+static void * nmea_listen_thread (void *arg)
+#endif
+{
+	MYFDTYPE fd = (MYFDTYPE)(long)arg;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("nmea_listen_thread ( %d )\n", fd);
+#endif
+
+
+	while (1) {
+	  unsigned char ch;
+
+	  ch = nmea_get(fd);
+
+	  if (ch == '$') {
+	    // Start of new sentence.
+	    gps_msg_len = 0;
+	    gps_msg[gps_msg_len++] = ch;
+	    gps_msg[gps_msg_len] = '\0';
+	  }
+	  else if (ch == '\r' || ch == '\n') {
+	    if (gps_msg_len >= 6 && gps_msg[0] == '$') {
+	      nmea_parse_gps (gps_msg);
+	      text_color_set(DW_COLOR_REC);
+	      dw_printf ("%s\n", gps_msg);
+	    }
+	    gps_msg_len = 0;
+	    gps_msg[gps_msg_len] = '\0';
+	  }
+	  else {	
+	    if (gps_msg_len < NMEA_MAX_LEN-1) {
+	      gps_msg[gps_msg_len++] = ch;
+	      gps_msg[gps_msg_len] = '\0';
+	    }
+	  }
+	}	/* while (1) */
+
+	return (NULL);	/* Unreachable but avoids compiler warning. */
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ...
+ *
+ * Purpose:     ...
+ *
+ * Inputs:	...
+ *
+ * Outputs:	...
+ *
+ * Description:	...
+ *
+ *--------------------------------------------------------------------*/
+
+
+static void remove_checksum (char *sent)
+{
+        char *p;
+        char *next;
+        unsigned char cs;
+
+
+// Do we have valid checksum?
+
+        cs = 0;
+        for (p = sent+1; *p != '*' && *p != '\0'; p++) {
+          cs ^= *p;
+        }
+
+        p = strchr (sent, '*');
+        if (p == NULL) {
+	  text_color_set (DW_COLOR_INFO);
+          dw_printf("Missing GPS checksum.\n");
+          return;
+        }
+        if (cs != strtoul(p+1, NULL, 16)) {
+	  text_color_set (DW_COLOR_ERROR);
+          dw_printf("GPS checksum error. Expected %02x but found %s.\n", cs, p+1);
+          return;
+        }
+        *p = '\0';      // Remove the checksum.
+}
+
+static void nmea_parse_gps (char *sentence)
+{
+
+	char stemp[NMEA_MAX_LEN];
+	char *ptype;
+	char *next;
+	double g_lat, g_lon;
+	float g_speed, g_course;
+	static float g_alt = G_UNKNOWN;
+	int fix;		/* 0=none, 2=2D, 3=3D */
+
+	strcpy (stemp, sentence);
+
+	// TODO: process only if good.
+	remove_checksum (stemp);
+
+	next = stemp;
+	ptype = strsep(&next, ",");
+
+
+// $GPRMC has everything we care about except altitude.
+//
+// Examples: $GPRMC,212404.000,V,4237.1505,N,07120.8602,W,,,150614,,*0B
+//	     $GPRMC,000029.020,V,,,,,,,080810,,,N*45
+//	     $GPRMC,003413.710,A,4237.1240,N,07120.8333,W,5.07,291.42,160614,,,A*7F
+
+	if (strcmp(ptype, "$GPRMC") == 0)
+	{
+
+	  char *ptime;			/* Time, hhmmss[.sss] */
+	  char *pstatus;		/* Status, A=Active (valid position), V=Void */
+	  char *plat;			/* Latitude */
+	  char *pns;			/* North/South */
+	  char *plon;			/* Longitude */
+	  char *pew;			/* East/West */
+	  char *pknots;			/* Speed over ground, knots. */
+	  char *pcourse;		/* True course, degrees. */
+	  char *pdate;			/* Date, ddmmyy */
+					/* Magnetic variation */
+					/* In version 3.00, mode is added: A D E N (see below) */
+					/* Checksum */
+
+	  ptime = strsep(&next, ",");
+	  pstatus = strsep(&next, ",");	
+	  plat = strsep(&next, ",");
+	  pns = strsep(&next, ",");
+	  plon = strsep(&next, ",");
+	  pew = strsep(&next, ",");
+	  pknots = strsep(&next, ",");
+	  pcourse = strsep(&next, ",");
+	  pdate = strsep(&next, ",");	
+
+
+	  g_lat = G_UNKNOWN;
+	  g_lon = G_UNKNOWN;
+	  g_speed = G_UNKNOWN;
+	  g_course = G_UNKNOWN;
+
+	  if (plat != NULL && strlen(plat) > 0) {
+	    g_lat = latitude_from_nmea(plat, pns);
+	  }
+	  if (plon != NULL && strlen(plon) > 0) {
+	    g_lon = longitude_from_nmea(plon, pew);
+	  }
+	  if (pknots != NULL && strlen(pknots) > 0) {
+	    g_speed = atof(pknots);
+	  }
+	  if (pcourse != NULL && strlen(pcourse) > 0) {
+	    g_course = atof(pcourse);
+	  }
+
+	  if (*pstatus == 'A') {
+	    if (g_alt != G_UNKNOWN) {
+	      fix = 3;
+	    }
+	    else {
+	      fix = 2;
+	    }
+	  }
+	  else {
+	    fix = 0;
+	  }
+
+	  text_color_set (DW_COLOR_INFO);
+          dw_printf("%d %.6f %.6f %.1f %.0f %.1f\n", fix, g_lat, g_lon, g_speed, g_course, g_alt);
+	  
+	}
+
+// $GPGGA has altitude.
+//
+// Examples: $GPGGA,212407.000,4237.1505,N,07120.8602,W,0,00,,,M,,M,,*58
+//	     $GPGGA,000409.392,,,,,0,00,,,M,0.0,M,,0000*53
+//	     $GPGGA,003518.710,4237.1250,N,07120.8327,W,1,03,5.9,33.5,M,-33.5,M,,0000*5B
+
+	else if (strcmp(ptype, "$GPGGA") == 0)
+	{
+
+	  char *ptime;			/* Time, hhmmss[.sss] */
+	  char *plat;			/* Latitude */
+	  char *pns;			/* North/South */
+	  char *plon;			/* Longitude */
+	  char *pew;			/* East/West */
+	  char *pfix;			/* 0=invalid, 1=GPS fix, 2=DGPS fix */
+	  char *pnum_sat;		/* Number of satellites */
+	  char *phdop;			/* Horiz. Dilution fo Precision */
+	  char *paltitude;		/* Altitude, above mean sea level */
+	  char *palt_u;			/* Units for Altitude, typically M for meters. */
+	  char *pheight;		/* Height above ellipsoid */
+	  char *pheight_u;		/* Units for height, typically M for meters. */
+	  char *psince;			/* Time since last DGPS update. */
+	  char *pdsta;			/* DGPS reference station id. */
+
+	  ptime = strsep(&next, ",");
+	  plat = strsep(&next, ",");
+	  pns = strsep(&next, ",");
+	  plon = strsep(&next, ",");
+	  pew = strsep(&next, ",");
+	  pfix = strsep(&next, ",");	
+	  pnum_sat = strsep(&next, ",");
+	  phdop = strsep(&next, ",");
+	  paltitude = strsep(&next, ",");
+	  palt_u = strsep(&next, ",");
+	  pheight = strsep(&next, ",");
+	  pheight_u = strsep(&next, ",");
+	  psince = strsep(&next, ",");
+	  pdsta = strsep(&next, ",");
+
+	  g_alt = G_UNKNOWN;
+
+	  if (paltitude != NULL && strlen(paltitude) > 0) {
+	    g_alt = atof(paltitude);
+	  }
+	}
+
+
+} /* end  nmea_parse_gps */
+
+/* end nmea.c */
diff --git a/nmea.h b/nmea.h
new file mode 100755
index 0000000..470353d
--- /dev/null
+++ b/nmea.h
@@ -0,0 +1,19 @@
+
+/* 
+ * Name:	nmea.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"		/* for struct misc_config_s */
+
+
+void nmea_init (struct misc_config_s *misc_config);
+
+void nmea_set_debug (int n);
+
+void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol, 
+			float alt, float course, float speed, char *comment);
+
+/* end nmea.h */
diff --git a/ptt.c b/ptt.c
index 01c77f3..54011b2 100755
--- a/ptt.c
+++ b/ptt.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011,2013  John Langner, WB2OSZ
+//    Copyright (C) 2011,2013,2014  John Langner, WB2OSZ
 //
 //    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
@@ -34,6 +34,8 @@
  *
  * Version 0.9:	Add ability to use GPIO pins on Linux.
  *
+ * Version 1.1: Add parallel printer port for x86 Linux only.
+ *
  * References:	http://www.robbayer.com/files/serial-win.pdf
  *
  *		https://www.kernel.org/doc/Documentation/gpio.txt
@@ -86,6 +88,14 @@ typedef int HANDLE;
 
 #endif
 
+#define LPT_IO_ADDR 0x378
+
+
+#if TEST
+#define dw_printf printf
+#endif
+
+
 
 /*-------------------------------------------------------------------
  *
@@ -108,6 +118,7 @@ static ptt_method_t ptt_method[MAX_CHANS];	/* Method for PTT signal. */
 					/* PTT_METHOD_NONE - not configured.  Could be using VOX. */
 					/* PTT_METHOD_SERIAL - serial (com) port. */
 					/* PTT_METHOD_GPIO - general purpose I/O. */
+					/* PTT_METHOD_LPT - Parallel printer port. */
 
 static char ptt_device[MAX_CHANS][20];	/* Name of serial port device.  */
 					/* e.g. COM1 or /dev/ttyS0. */
@@ -117,6 +128,10 @@ static ptt_line_t ptt_line[MAX_CHANS];	/* RTS or DTR when using serial port. */
 static int ptt_gpio[MAX_CHANS];		/* GPIO number.  Only used for Linux. */
 					/* Valid only when ptt_method is PTT_METHOD_GPIO. */
 		
+int ptt_lpt_bit[MAX_CHANS];		/* Bit number for parallel printer port.  */
+					/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
+					/* Valid only when ptt_method is PTT_METHOD_LPT. */
+
 static int ptt_invert[MAX_CHANS];	/* Invert the signal.  */
 					/* Normally higher voltage means transmit. */
 
@@ -157,16 +172,18 @@ void ptt_init (struct audio_s *p_modem)
 	  strcpy (ptt_device[ch], p_modem->ptt_device[ch]);
 	  ptt_line[ch] = p_modem->ptt_line[ch];
 	  ptt_gpio[ch] = p_modem->ptt_gpio[ch];
+	  ptt_lpt_bit[ch] = p_modem->ptt_lpt_bit[ch];
 	  ptt_invert[ch] = p_modem->ptt_invert[ch];
 	  ptt_fd[ch] = INVALID_HANDLE_VALUE;
 #if DEBUG
 	  text_color_set(DW_COLOR_DEBUG);
-          dw_printf ("ch=%d, method=%d, device=%s, line=%d, gpio=%d, invert=%d\n",
+          dw_printf ("ch=%d, method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n",
 		ch,
 		ptt_method[ch], 
 		ptt_device[ch],
 		ptt_line[ch],
 		ptt_gpio[ch],
+		ptt_lpt_bit[ch],
 		ptt_invert[ch]);
 #endif
 	}
@@ -205,8 +222,20 @@ void ptt_init (struct audio_s *p_modem)
 	    }
 	    else {
 #if __WIN32__
-
-	      fd = CreateFile(ptt_device[ch],
+	      char bettername[50];
+	      // Bug fix in release 1.1 - Need to munge name for COM10 and up.
+	      // http://support.microsoft.com/kb/115831
+
+	      strcpy (bettername, ptt_device[ch]);
+	      if (strncasecmp(bettername, "COM", 3) == 0) {
+	        int n;
+	        n = atoi(bettername+3);
+	        if (n >= 10) {
+	          strcpy (bettername, "\\\\.\\");
+	          strcat (bettername, ptt_device[ch]);
+	        }
+	      }
+	      fd = CreateFile(bettername,
 			GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
 #else
 
@@ -295,9 +324,10 @@ void ptt_init (struct audio_s *p_modem)
  */
 	  if (geteuid() != 0) {
 	    if ( ! (finfo.st_mode & S_IWOTH)) {
+	      int err;
 
 	      /* Try to change protection. */
-	      system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport");
+	      err = system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport");
 	      
 	      if (stat("/sys/class/gpio/export", &finfo) < 0) {
 	        /* Unexpected because we could do it before. */
@@ -327,6 +357,7 @@ void ptt_init (struct audio_s *p_modem)
 	  if (ptt_method[ch] == PTT_METHOD_GPIO) {
 	    char stemp[80];
 	    struct stat finfo;
+	    int err;
 
 	    fd = open("/sys/class/gpio/export", O_WRONLY);
 	    if (fd < 0) {
@@ -355,9 +386,9 @@ void ptt_init (struct audio_s *p_modem)
  * We only care about "direction" and "value".
  */
 	    sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
-	    system (stemp);
+	    err = system (stemp);
 	    sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
-	    system (stemp);
+	    err = system (stemp);
 
 	    sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
 
@@ -414,12 +445,75 @@ void ptt_init (struct audio_s *p_modem)
 #endif
 
 
+
+/*
+ * Set up parallel printer port.
+ * Hardcoded for single port.
+ * For x86 Linux only.
+ */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+	for (ch=0; ch<ptt_num_channels; ch++) {
+
+	  if (ptt_method[ch] == PTT_METHOD_LPT) {
+
+
+	    /* Can't open the same device more than once so we */
+	    /* need more logic to look for the case of both radio */
+	    /* channels using different pins of the same LPT port. */
+
+	    /* TODO: Needs to be rewritten in a more general manner */
+	    /* if we ever have more than 2 channels. */
+
+	    if (ch == 1 && strcmp(ptt_device[0],ptt_device[1]) == 0) {
+	      fd = ptt_fd[0];
+	    }
+	    else {
+	      fd = open ("/dev/port", O_RDWR | O_NDELAY);
+            }
+
+	    if (fd != INVALID_HANDLE_VALUE) {
+	      ptt_fd[ch] = fd;
+	    }
+	    else {
+
+	      int e = errno;
+
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("ERROR - Can't open /dev/port for parallel printer port PTT control.\n");
+	      dw_printf ("%s\n", strerror(errno));
+	      dw_printf ("You probably don't have adequate permissions to access I/O ports.\n");
+	      dw_printf ("Either run direwolf as root or change these permissions:\n");
+	      dw_printf ("  sudo chmod go+rw /dev/port\n");
+	      dw_printf ("  sudo setcap cap_sys_rawio=ep `which direwolf`\n");
+
+	      /* Don't try using it later if device open failed. */
+
+	      ptt_method[ch] = PTT_METHOD_NONE;
+	    }
+
+/*
+ * Set initial state of PTT off.
+ * ptt_set will invert output signal if appropriate.
+ */	  
+	    ptt_set (ch, 0);
+
+	  } 	/* if parallel printer port method. */
+
+	}	/* For each channel. */
+
+
+
+#endif /* x86 Linux */
+
+
 /* Why doesn't it transmit?  Probably forgot to specify PTT option. */
 
 	for (ch=0; ch<ptt_num_channels; ch++) {
 	  if(ptt_method[ch] == PTT_METHOD_NONE) {
 	      text_color_set(DW_COLOR_INFO);
-	      dw_printf ("Note: PTT not configured for channel %d.\n", ch);
+	      dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch);
 	  }
 	}
 } /* end ptt_init */
@@ -523,6 +617,44 @@ void ptt_set (int chan, int ptt)
 	}
 #endif
 	
+/*
+ * Using parallel printer port?
+ */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+	if (ptt_method[chan] == PTT_METHOD_LPT && 
+		ptt_fd[chan] != INVALID_HANDLE_VALUE) {
+
+	  char lpt_data;
+	  ssize_t n;		
+
+	  lseek (ptt_fd[chan], (off_t)LPT_IO_ADDR, SEEK_SET);
+	  if (read (ptt_fd[chan], &lpt_data, (size_t)1) != 1) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error reading current state of LPT for channel %d PTT\n", chan);
+	    dw_printf ("%s\n", strerror(e));
+	  }
+
+	  if (ptt) {
+	    lpt_data |= ( 1 << ptt_lpt_bit[chan] );
+	  }
+	  else {
+	    lpt_data &= ~ ( 1 << ptt_lpt_bit[chan] );
+	  }
+
+	  lseek (ptt_fd[chan], (off_t)LPT_IO_ADDR, SEEK_SET);
+	  if (write (ptt_fd[chan], &lpt_data, (size_t)1) != 1) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error writing to LPT for channel %d PTT\n", chan);
+	    dw_printf ("%s\n", strerror(e));
+	  }
+	}
+
+#endif /* x86 Linux */
+
 
 } /* end ptt_set */
 
@@ -546,6 +678,9 @@ void ptt_term (void)
 
 	for (n = 0; n < ptt_num_channels; n++) {
 	  ptt_set (n, 0);
+	}
+
+	for (n = 0; n < ptt_num_channels; n++) {
 	  if (ptt_fd[n] != INVALID_HANDLE_VALUE) {
 #if __WIN32__
 	    CloseHandle (ptt_fd[n]);
@@ -572,6 +707,8 @@ void ptt_term (void)
 
 void text_color_set (dw_color_t c)  {  }
 
+#define dw_printf printf
+
 main ()
 {
 	struct audio_s modem;
@@ -656,8 +793,7 @@ main ()
 
 /* Test GPIO */
 
-#if __WIN32__
-#else
+#if __arm__
 
 	memset (&modem, 0, sizeof(modem));
 	modem.num_channels = 1;
@@ -680,6 +816,35 @@ main ()
 	ptt_term ();
 #endif
 
+
+	memset (&modem, 0, sizeof(modem));
+	modem.num_channels = 2;
+	modem.ptt_method[0] = PTT_METHOD_LPT;
+	modem.ptt_lpt_bit[0] = 0;
+	modem.ptt_method[1] = PTT_METHOD_LPT;
+	modem.ptt_lpt_bit[1] = 1;
+
+	dw_printf ("Try LPT bits 0 & 1 a few times...\n");
+
+	ptt_init (&modem);
+
+	for (n=0; n<8; n++) {
+	  ptt_set (0, n & 1);
+	  ptt_set (1, (n>>1) & 1);
+	  SLEEP_SEC(1);
+	}
+
+	ptt_term ();	
+
+/* Parallel printer port. */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+
+
+#endif
+
+
 }
 
 #endif
diff --git a/rdq.c b/rdq.c
index 57f18f8..9265989 100755
--- a/rdq.c
+++ b/rdq.c
@@ -44,8 +44,9 @@
 
 
 
-static rrbb_t queue_head;			/* Head of linked list for queue. */
-
+static rrbb_t queue_head = NULL;			/* Head of linked list for queue. */
+static int rdq_len = 0;
+#define RDQ_UNDERRUN_THRESHOLD 30 /* A warning will be emitted if there are still this number of packets to decode in the queue and we try to add another one */
 #if __WIN32__
 
 static CRITICAL_SECTION rdq_cs;			/* Critical section for updating queues. */
@@ -204,7 +205,11 @@ void rdq_append (rrbb_t rrbb)
 	  }
 	  rrbb_set_nextp (plast, rrbb);
 	}
-
+        rdq_len++;
+	if (rdq_len > RDQ_UNDERRUN_THRESHOLD) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Too many packets to decode (%d) in the queue, decrease the FIX_BITS value\n", rdq_len);
+	}
 
 #if __WIN32__ 
 	LeaveCriticalSection (&rdq_cs);
@@ -374,7 +379,7 @@ void rdq_wait_while_empty (void)
 
 #if DEBUG
 	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_wait_while_empty () returns\n");
+	dw_printf ("rdq_wait_while_empty () returns (%d buffers remaining)\n", rdq_len);
 #endif
 
 }
@@ -418,7 +423,10 @@ rrbb_t rdq_remove (void)
 	  exit (1);
 	}
 #endif
-
+        rdq_len--;
+#if DEBUG
+	dw_printf ("-rdq_len: %d\n", rdq_len);
+#endif
 	if (queue_head == NULL) {
 	  result_p = NULL;
 	}
diff --git a/regex/README-dire-wolf.txt b/regex/README-dire-wolf.txt
index 58ca0b9..dea8f72 100755
--- a/regex/README-dire-wolf.txt
+++ b/regex/README-dire-wolf.txt
@@ -1,3 +1,4 @@
+This is NOT used for the Linux version.
 For Linux and Cygwin, we use the built-in regular expression library.
 For the Windows version, we need to include our own version.
 
diff --git a/rpack.h b/rpack.h
new file mode 100755
index 0000000..c830eb8
--- /dev/null
+++ b/rpack.h
@@ -0,0 +1,94 @@
+
+/*------------------------------------------------------------------
+ *
+ * File:        rpack.h
+ *
+ * Purpose:   	Definition of Garmin Rino message format.
+ *		
+ * References:	http://www.radio-active.net.au/web3/APRS/Resources/RINO
+ *
+ *		http://www.radio-active.net.au/web3/APRS/Resources/RINO/OnAir
+ *
+ *---------------------------------------------------------------*/
+
+
+#ifndef RPACK_H
+#define RPACK_H 1
+
+
+#define RPACK_FRAME_LEN 168
+
+
+#ifdef RPACK_C		/* Expose private details */
+
+
+ 
+// Transmission order is LSB first. 
+
+struct __attribute__((__packed__)) rpack_s {
+
+	int lat;		// Latitude.
+				// Signed integer.  Scaled by 2**30/90.
+
+	int lon;		// Longitude.  Same encoding.
+
+	char unknown1;		// Unproven theory: altitude.	
+	char unknown2;		
+
+	unsigned name0:6;	// 10 character name.
+	unsigned name1:6;	// Bit packing is implementation dependent.
+	unsigned name2:6;	// Should rewrite to be more portable.
+	unsigned name3:6;
+	unsigned name4:6;
+	unsigned name5:6;
+	unsigned name6:6;
+	unsigned name7:6;
+	unsigned name8:6;
+	unsigned name9:6;
+
+	unsigned symbol:5;	
+
+	unsigned unknown3:7;		
+				
+	
+//	unsigned crc:16;	// Safe bet this is CRC for error checking.
+
+	unsigned char crc1;
+	unsigned char crc2;
+
+	char dummy[3];		// Total size should be 24 bytes if no gaps.
+
+};
+
+#else			/* Show only public interface.  */
+
+
+struct rpack_s {
+	char stuff[24];
+};
+
+
+#endif
+
+
+
+void rpack_set_bit (struct rpack_s *rp, int position, int value);
+
+int rpack_is_valid (struct rpack_s *rp);
+
+int rpack_get_bit (struct rpack_s *rp, int position);
+
+double rpack_get_lat (struct rpack_s *rp);
+
+double rpack_get_lon (struct rpack_s *rp);
+
+int rpack_get_symbol (struct rpack_s *rp);
+
+void rpack_get_name (struct rpack_s *rp, char *str);
+
+
+
+#endif
+
+/* end rpack.h */
+	
diff --git a/rrbb.c b/rrbb.c
index 36a780a..51d6b43 100755
--- a/rrbb.c
+++ b/rrbb.c
@@ -374,18 +374,13 @@ int rrbb_get_bit (rrbb_t b, unsigned int ind)
 #else
 int rrbb_get_bit (rrbb_t b, unsigned int ind)
 {
-	unsigned int di, mi;
-
 	assert (b != NULL);
 	assert (b->magic1 == MAGIC1);
 	assert (b->magic2 == MAGIC2);
 
 	assert (ind < b->len);
 
-	di = ind / SOI;
-	mi = ind % SOI;
-
-	if (b->data[di] & masks[mi]) {
+	if (b->data[ind / SOI] & masks[ind % SOI]) {
 	  return 1;
 	}
 	else {
@@ -393,6 +388,30 @@ int rrbb_get_bit (rrbb_t b, unsigned int ind)
 	}
 }
 #endif
+unsigned int rrbb_get_computed_bit (rrbb_t b, unsigned int ind)
+{
+	return b->computed_data[ind];
+}
+
+int rrbb_compute_bits (rrbb_t b)
+{
+	unsigned int i,val;
+
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	for (i=0;i<b->len;i++) {
+	  if (b->data[i / SOI] & masks[i % SOI]) {
+	    val = 1;
+	  }
+	  else {
+	    val = 0;
+	  }
+	  b->computed_data[i] = val;
+	}
+	return 0;
+}
 
 
 /***********************************************************************************
@@ -574,6 +593,47 @@ int rrbb_get_audio_level (rrbb_t b)
 
 /***********************************************************************************
  *
+ * Name:	rrbb_set_fix_bits	
+ *
+ * Purpose:	Set fix bits at time the frame was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		a	fix_bits.
+ *		
+ ***********************************************************************************/
+
+void rrbb_set_fix_bits (rrbb_t b, int a)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	b->fix_bits = a;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_fix_bits	
+ *
+ * Purpose:	Get fix bits at time the frame was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_fix_bits (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->fix_bits);
+}
+
+
+/***********************************************************************************
+ *
  * Name:	rrbb_get_is_scrambled	
  *
  * Purpose:	Find out if using scrambled data.
diff --git a/rrbb.h b/rrbb.h
index ae78bb1..0d8477c 100755
--- a/rrbb.h
+++ b/rrbb.h
@@ -37,6 +37,9 @@ typedef struct rrbb_s {
 	int subchan;		/* Which modem when more than one per channel. */
 	int audio_level;	/* Received audio level at time of frame capture. */
 	unsigned int len;	/* Current number of samples in array. */
+	int fix_bits;		/* Level of effort to recover from */
+				/* a bad FCS on the frame. */
+
 
 	int is_scrambled;	/* Is data scrambled G3RUH / K9NG style? */
 	int descram_state;	/* Descrambler state before first data bit of frame. */
@@ -46,6 +49,7 @@ typedef struct rrbb_s {
 	slice_t data[MAX_NUM_BITS];
 #else
 	unsigned int data[(MAX_NUM_BITS+SOI-1)/SOI];
+	unsigned int computed_data[MAX_NUM_BITS];
 #endif
 	int magic2;
 } *rrbb_t;
@@ -79,6 +83,8 @@ void rrbb_set_slice_val (rrbb_t b, slice_t slice_val);
 #endif
 
 int rrbb_get_bit (rrbb_t b, unsigned int ind);
+unsigned int rrbb_get_computed_bit (rrbb_t b, unsigned int ind);
+int rrbb_compute_bits (rrbb_t b);
 
 //void rrbb_flip_bit (rrbb_t b, unsigned int ind);
 
@@ -99,4 +105,7 @@ int rrbb_get_is_scrambled (rrbb_t b);
 
 int rrbb_get_descram_state (rrbb_t b);
 
-#endif
\ No newline at end of file
+int rrbb_get_fix_bits(rrbb_t b);
+void rrbb_set_fix_bits(rrbb_t b, int fix_bits);
+
+#endif
diff --git a/server.c b/server.c
index 5462197..5f04a52 100755
--- a/server.c
+++ b/server.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011,2012,2013  John Langner, WB2OSZ
+//    Copyright (C) 2011, 2012, 2013, 2014  John Langner, WB2OSZ
 //
 //    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
@@ -85,6 +85,12 @@
  * 		Getting Started with Winsock
  *		http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
  *
+ *
+ * Major change in 1.1:
+ *
+ *		Formerly a single client was allowed.
+ *		Now we can have multiple concurrent clients.
+ *
  *---------------------------------------------------------------*/
 
 
@@ -122,21 +128,42 @@
 #include "server.h"
 
 
+/*
+ * Previously, we allowed only one network connection at a time to each port.
+ * In version 1.1, we allow multiple concurrent client apps to connect.
+ */
+
+#define MAX_NET_CLIENTS 3
+
+static int client_sock[MAX_NET_CLIENTS];	
+					/* File descriptor for socket for */
+					/* communication with client application. */
+					/* Set to -1 if not connected. */
+					/* (Don't use SOCKET type because it is unsigned.) */
 
-static int client_sock;		/* File descriptor for socket for */
-				/* communication with client application. */
-				/* Set to -1 if not connected. */
-				/* (Don't use SOCKET type because it is unsigned.) */
+static int enable_send_raw_to_client[MAX_NET_CLIENTS];
+					/* Should we send received packets to client app in raw form? */
+					/* Note that it starts as false for a new connection. */
+					/* the client app must send a command to enable this. */
 
-static int enable_send_raw_to_client;	/* Should we send received packets to client app? */
-static int enable_send_monitor_to_client;
+static int enable_send_monitor_to_client[MAX_NET_CLIENTS];
+					/* Should we send received packets to client app in monitor form? */
+					/* Note that it starts as false for a new connection. */
+					/* the client app must send a command to enable this. */
 
 
 static int num_channels;	/* Number of radio ports. */
 
 
-static void * connect_listen_thread (void *arg);
-static void * cmd_listen_thread (void *arg);
+// TODO:  define in one place, use everywhere.
+#if __WIN32__
+#define THREAD_F unsigned __stdcall
+#else 
+#define THREAD_F void *
+#endif
+
+static THREAD_F connect_listen_thread (void *arg);
+static THREAD_F cmd_listen_thread (void *arg);
 
 /*
  * Message header for AGW protocol.
@@ -169,12 +196,13 @@ struct agwpe_s {
  * Purpose:     Print message to/from client for debugging.
  *
  * Inputs:	fromto		- Direction of message.
+ *		client		- client number, 0 .. MAX_NET_CLIENTS-1
  *		pmsg		- Address of the message block.
  *		msg_len		- Length of the message.
  *
  *--------------------------------------------------------------------*/
 
-static int debug_client = 0;		/* Print information flowing from and to client. */
+static int debug_client = 0;		/* Debug option: Print information flowing from and to client. */
 
 void server_set_debug (int n) 
 {	
@@ -208,7 +236,7 @@ void hex_dump (unsigned char *p, int len)
 
 typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
 
-static void debug_print (fromto_t fromto, struct agwpe_s *pmsg, int msg_len)
+static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int msg_len)
 {
 	char direction [10];
 	char datakind[80];
@@ -269,15 +297,15 @@ static void debug_print (fromto_t fromto, struct agwpe_s *pmsg, int msg_len)
 	text_color_set(DW_COLOR_DEBUG);
 	dw_printf ("\n");
 
-	dw_printf ("%s %s %s AGWPE client application, total length = %d\n",
-			prefix[(int)fromto], datakind, direction, msg_len);
+	dw_printf ("%s %s %s AGWPE client application %d, total length = %d\n",
+			prefix[(int)fromto], datakind, direction, client, msg_len);
 
 	dw_printf ("\tportx = %d, port_hi_reserved = %d\n", pmsg->portx, pmsg->port_hi_reserved);
 	dw_printf ("\tkind_lo = %d = '%c', kind_hi = %d\n", pmsg->kind_lo, pmsg->kind_lo, pmsg->kind_hi);
 	dw_printf ("\tcall_from = \"%s\", call_to = \"%s\"\n", pmsg->call_from, pmsg->call_to);
 	dw_printf ("\tdata_len = %d, user_reserved = %d, data =\n", pmsg->data_len, pmsg->user_reserved);
 
-	hex_dump ((char*)pmsg + sizeof(struct agwpe_s), pmsg->data_len);
+	hex_dump ((unsigned char*)pmsg + sizeof(struct agwpe_s), pmsg->data_len);
 
 	if (msg_len < 36) {
 	  text_color_set (DW_COLOR_ERROR);
@@ -303,9 +331,9 @@ static void debug_print (fromto_t fromto, struct agwpe_s *pmsg, int msg_len)
  *
  * Outputs:	
  *
- * Description:	This starts two threads:
- *		  *  to listen for a connection from client app.
- *		  *  to listen for commands from client app.
+ * Description:	This starts at least two threads:
+ *		  *  one to listen for a connection from client app.
+ *		  *  one or more to listen for commands from client app.
  *		so the main application doesn't block while we wait for these.
  *
  *--------------------------------------------------------------------*/
@@ -313,15 +341,17 @@ static void debug_print (fromto_t fromto, struct agwpe_s *pmsg, int msg_len)
 
 void server_init (struct misc_config_s *mc)
 {
+	int client;
+
 #if __WIN32__
 	HANDLE connect_listen_th;
-	HANDLE cmd_listen_th;
+	HANDLE cmd_listen_th[MAX_NET_CLIENTS];
 #else
 	pthread_t connect_listen_tid;
-	pthread_t cmd_listen_tid;
+	pthread_t cmd_listen_tid[MAX_NET_CLIENTS];
 #endif
 	int e;
-	int server_port = mc->agwpe_port;
+	int server_port = mc->agwpe_port;		/* Usually 8000 but can be changed. */
 
 
 #if DEBUG
@@ -329,16 +359,18 @@ void server_init (struct misc_config_s *mc)
 	dw_printf ("server_init ( %d )\n", server_port);
 	debug_a = 1;
 #endif
-	client_sock = -1;
-	enable_send_raw_to_client = 0;
-	enable_send_monitor_to_client = 0;
+	for (client=0; client<MAX_NET_CLIENTS; client++) {
+	  client_sock[client] = -1;
+	  enable_send_raw_to_client[client] = 0;
+	  enable_send_monitor_to_client[client] = 0;
+	}
 	num_channels = mc->num_channels;
 
 /*
- * This waits for a client to connect and sets client_sock.
+ * This waits for a client to connect and sets an available client_sock[n].
  */
 #if __WIN32__
-	connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)server_port, 0, NULL);
+	connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)(unsigned int)server_port, 0, NULL);
 	if (connect_listen_th == NULL) {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf ("Could not create AGW connect listening thread\n");
@@ -354,23 +386,28 @@ void server_init (struct misc_config_s *mc)
 #endif
 
 /*
- * This reads messages from client when client_sock is valid.
+ * These read messages from client when client_sock[n] is valid.
+ * Currently we start up a separate thread for each potential connection.
+ * Possible later refinement.  Start one now, others only as needed.
  */
+	for (client = 0; client < MAX_NET_CLIENTS; client++) {
+
 #if __WIN32__
-	cmd_listen_th = _beginthreadex (NULL, 0, cmd_listen_thread, NULL, 0, NULL);
-	if (cmd_listen_th == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not create AGW command listening thread\n");
-	  return;
-	}
+	  cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, cmd_listen_thread, (void*)client, 0, NULL);
+	  if (cmd_listen_th[client] == NULL) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Could not create AGW command listening thread\n");
+	    return;
+	  }
 #else
-	e = pthread_create (&cmd_listen_tid, NULL, cmd_listen_thread, NULL);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Could not create AGW command listening thread");
-	  return;
-	}
+	  e = pthread_create (&cmd_listen_tid[client], NULL, cmd_listen_thread, (void *)(long)client);
+	  if (e != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    perror("Could not create AGW command listening thread");
+	    return;
+	  }
 #endif
+	}
 }
 
 
@@ -393,7 +430,7 @@ void server_init (struct misc_config_s *mc)
  *
  *--------------------------------------------------------------------*/
 
-static void * connect_listen_thread (void *arg)
+static THREAD_F connect_listen_thread (void *arg)
 {
 #if __WIN32__
 
@@ -414,7 +451,7 @@ static void * connect_listen_thread (void *arg)
 	if (err != 0) {
 	    text_color_set(DW_COLOR_ERROR);
 	    dw_printf("WSAStartup failed: %d\n", err);
-	    return (NULL);
+	    return (NULL);		// TODO: what should this be for Windows?
 	}
 
 	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
@@ -471,46 +508,59 @@ static void * connect_listen_thread (void *arg)
 #endif
 
  	while (1) {
-  	 
-	  while (client_sock > 0) {
-	    SLEEP_SEC(1);			/* Already connected.  Try again later. */
+
+	  int client;
+	  int c;
+	  
+	  client = -1;
+	  for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
+	    if (client_sock[c] <= 0) {
+	      client = c;
+	    }
 	  }
 
-#define QUEUE_SIZE 5
+/*
+ * Listen for connection if we have not reached maximum.
+ */
+	  if (client >= 0) {
 
-	  if(listen(listen_sock,QUEUE_SIZE) == SOCKET_ERROR)
-	  {
-	    text_color_set(DW_COLOR_ERROR);
-            dw_printf("Listen failed with error: %d\n", WSAGetLastError());
-	    return (NULL);
-	  }
+	    if(listen(listen_sock, MAX_NET_CLIENTS) == SOCKET_ERROR)
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("Listen failed with error: %d\n", WSAGetLastError());
+	      return (NULL);
+	    }
 	
-	  text_color_set(DW_COLOR_INFO);
-          dw_printf("Ready to accept AGW client application on port %s ...\n", server_port_str);
+	    text_color_set(DW_COLOR_INFO);
+            dw_printf("Ready to accept AGW client application %d on port %s ...\n", client, server_port_str);
          
-          client_sock = accept(listen_sock, NULL, NULL);
+            client_sock[client] = accept(listen_sock, NULL, NULL);
 
-	  if (client_sock == -1) {
-	    text_color_set(DW_COLOR_ERROR);
-            dw_printf("Accept failed with error: %d\n", WSAGetLastError());
-            closesocket(listen_sock);
-            WSACleanup();
-            return (NULL);
-          }
+	    if (client_sock[client] == -1) {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("Accept failed with error: %d\n", WSAGetLastError());
+              closesocket(listen_sock);
+              WSACleanup();
+              return (NULL);
+            }
 
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf("\nConnected to AGW client application ...\n\n");
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf("\nConnected to AGW client application %d ...\n\n", client);
 
 /*
  * The command to change this is actually a toggle, not explicit on or off.
  * Make sure it has proper state when we get a new connection.
  */ 
-	  enable_send_raw_to_client = 0;
-	  enable_send_monitor_to_client = 0;
-
+	    enable_send_raw_to_client[client] = 0;
+	    enable_send_monitor_to_client[client] = 0;
+	  }
+	  else {
+	    SLEEP_SEC(1);	/* wait then check again if more clients allowed. */
+	  }
  	}
 
-#else
+#else		/* End of Windows case, now Linux */
+
 
     	struct sockaddr_in sockaddr; /* Internet socket address stuct */
     	socklen_t sockaddr_size = sizeof(struct sockaddr_in);
@@ -547,35 +597,44 @@ static void * connect_listen_thread (void *arg)
 #endif
 
  	while (1) {
-  	 
-	  while (client_sock > 0) {
-	    SLEEP_SEC(1);			/* Already connected.  Try again later. */
+
+	  int client;
+	  int c;
+	  
+	  client = -1;
+	  for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
+	    if (client_sock[c] <= 0) {
+	      client = c;
+	    }
 	  }
 
-#define QUEUE_SIZE 5
+	  if (client >= 0) {
 
-	  if(listen(listen_sock,QUEUE_SIZE) == -1)
-	  {
-	    text_color_set(DW_COLOR_ERROR);
-	    perror ("connect_listen_thread: Listen failed");
-	    return (NULL);
-	  }
+	    if(listen(listen_sock,MAX_NET_CLIENTS) == -1)
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror ("connect_listen_thread: Listen failed");
+	      return (NULL);
+	    }
 	
-	  text_color_set(DW_COLOR_INFO);
-          dw_printf("Ready to accept AGW client application on port %d ...\n", server_port);
+	    text_color_set(DW_COLOR_INFO);
+            dw_printf("Ready to accept AGW client application %d on port %d ...\n", client, server_port);
          
-          client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
+            client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
 
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf("\nConnected to AGW client application ...\n\n");
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf("\nConnected to AGW client application %d...\n\n", client);
 
 /*
  * The command to change this is actually a toggle, not explicit on or off.
  * Make sure it has proper state when we get a new connection.
  */ 
-	  enable_send_raw_to_client = 0;
-	  enable_send_monitor_to_client = 0;
-
+	    enable_send_raw_to_client[client] = 0;
+	    enable_send_monitor_to_client[client] = 0;
+	  }
+	  else {
+	    SLEEP_SEC(1);	/* wait then check again if more clients allowed. */
+	  }
  	}
 #endif
 }
@@ -616,13 +675,15 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int fl
 	int err;
 	int info_len;
 	unsigned char *pinfo;
+	int client;
+
 
 /*
  * RAW format
  */
-	
-	if (enable_send_raw_to_client 
-			&& client_sock > 0){
+	for (client=0; client<MAX_NET_CLIENTS; client++) {
+
+	  if (enable_send_raw_to_client[client] && client_sock[client] > 0){
 
 	    memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr));
 
@@ -642,37 +703,38 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int fl
 	    memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen);
 
 	    if (debug_client) {
-	      debug_print (TO_CLIENT, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
+	      debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
 	    }
 
 #if __WIN32__	
-            err = send (client_sock, (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0);
+            err = send (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0);
 	    if (err == SOCKET_ERROR)
 	    {
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf ("\nError %d sending message to AGW client application.  Closing connection.\n\n", WSAGetLastError());
-	      closesocket (client_sock);
-	      client_sock = -1;
+	      closesocket (client_sock[client]);
+	      client_sock[client] = -1;
 	      WSACleanup();
 	    }
 #else
-            err = write (client_sock, &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
+            err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
 	    if (err <= 0)
 	    {
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf ("\nError sending message to AGW client application.  Closing connection.\n\n");
-	      close (client_sock);
-	      client_sock = -1;    
+	      close (client_sock[client]);
+	      client_sock[client] = -1;    
 	    }
 #endif
+	  }
 	}
 
 
 /* MONITOR format - only for UI frames. */
 
+	for (client=0; client<MAX_NET_CLIENTS; client++) {
 	
-	if (enable_send_monitor_to_client 
-			&& client_sock > 0 
+	  if (enable_send_monitor_to_client[client] && client_sock[client] > 0 
 			&& ax25_get_control(pp) == AX25_UI_FRAME){
 
 	    time_t clock;
@@ -710,29 +772,30 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int fl
 	    agwpe_msg.hdr.data_len = strlen(agwpe_msg.data) + 1 /* include null */ ;
 
 	    if (debug_client) {
-	      debug_print (TO_CLIENT, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
+	      debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
 	    }
 
 #if __WIN32__	
-            err = send (client_sock, (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0);
+            err = send (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0);
 	    if (err == SOCKET_ERROR)
 	    {
 	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("\nError %d sending message to AGW client application.  Closing connection.\n\n", WSAGetLastError());
-	      closesocket (client_sock);
-	      client_sock = -1;
+	      dw_printf ("\nError %d sending message to AGW client application %d.  Closing connection.\n\n", WSAGetLastError(), client);
+	      closesocket (client_sock[client]);
+	      client_sock[client] = -1;
 	      WSACleanup();
 	    }
 #else
-            err = write (client_sock, &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
+            err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
 	    if (err <= 0)
 	    {
 	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("\nError sending message to AGW client application.  Closing connection.\n\n");
-	      close (client_sock);
-	      client_sock = -1;    
+	      dw_printf ("\nError sending message to AGW client application %d.  Closing connection.\n\n", client);
+	      close (client_sock[client]);
+	      client_sock[client] = -1;    
 	    }
 #endif
+	  }
 	}
 
 } /* server_send_rec_packet */
@@ -800,9 +863,9 @@ static int read_from_socket (int fd, char *ptr, int len)
  *
  * Purpose:     Wait for command messages from an application.
  *
- * Inputs:	arg		- Not used.
+ * Inputs:	arg		- client number, 0 .. MAX_NET_CLIENTS-1
  *
- * Outputs:	client_sock	- File descriptor for communicating with client app.
+ * Outputs:	client_sock[n]	- File descriptor for communicating with client app.
  *
  * Description:	Process messages from the client application.
  *		Note that the client can go away and come back again and
@@ -810,11 +873,10 @@ static int read_from_socket (int fd, char *ptr, int len)
  *
  *--------------------------------------------------------------------*/
 
-static void * cmd_listen_thread (void *arg)
+static THREAD_F cmd_listen_thread (void *arg)
 {
 	int n;
 
-
 	struct {
 	  struct agwpe_s hdr;		/* Command header. */
 	
@@ -822,44 +884,85 @@ static void * cmd_listen_thread (void *arg)
 					/* Maximum for 'V': 1 + 8*10 + 256 */
 	} cmd;
 
+	int client = (int) arg;
+
+	assert (client >= 0 && client < MAX_NET_CLIENTS);
+
 	while (1) {
 
-	  while (client_sock <= 0) {
+	  while (client_sock[client] <= 0) {
 	    SLEEP_SEC(1);			/* Not connected.  Try again later. */
 	  }
 
-	  n = read_from_socket (client_sock, (char *)(&cmd.hdr), sizeof(cmd.hdr));
+	  n = read_from_socket (client_sock[client], (char *)(&cmd.hdr), sizeof(cmd.hdr));
 	  if (n != sizeof(cmd.hdr)) {
 	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("\nError getting message header from client application.\n");
+	    dw_printf ("\nError getting message header from AGW client application %d.\n", client);
 	    dw_printf ("Tried to read %d bytes but got only %d.\n", (int)sizeof(cmd.hdr), n);
 	    dw_printf ("Closing connection.\n\n");
 #if __WIN32__
-	    closesocket (client_sock);
+	    closesocket (client_sock[client]);
 #else
-	    close (client_sock);
+	    close (client_sock[client]);
 #endif
-	    client_sock = -1;
+	    client_sock[client] = -1;
 	    continue;
 	  }
 
-	  assert (cmd.hdr.data_len >= 0 && cmd.hdr.data_len < sizeof(cmd.data));
+/*
+ * Take some precautions to guard against bad data
+ * which could cause problems later.
+ */
+
+/*
+ * Call to/from must not exceeed 9 characters.
+ * It's not guaranteed that unused bytes will contain 0 so we
+ * don't issue error message in this case. 
+ */
+
+	  cmd.hdr.call_from[sizeof(cmd.hdr.call_from)-1] = '\0';
+	  cmd.hdr.call_to[sizeof(cmd.hdr.call_to)-1] = '\0';
+
+/*
+ * Following data must fit in available buffer.
+ * Leave room for an extra nul byte terminator at end later.
+ */
+
+	  if (cmd.hdr.data_len < 0 || cmd.hdr.data_len > sizeof(cmd.data) - 1) {
+
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("\nInvalid message from AGW client application %d.\n", client);
+	    dw_printf ("Data Length of %d is out of range.\n", cmd.hdr.data_len);
+	
+	    /* This is a bad situation. */
+	    /* If we tried to read again, the header probably won't be there. */
+	    /* No point in trying to continue reading.  */
+
+	    dw_printf ("Closing connection.\n\n");
+#if __WIN32__
+	    closesocket (client_sock[client]);
+#else
+	    close (client_sock[client]);
+#endif
+	    client_sock[client] = -1;
+	    return NULL;
+	  }
 
 	  cmd.data[0] = '\0';
 
 	  if (cmd.hdr.data_len > 0) {
-	    n = read_from_socket (client_sock, cmd.data, cmd.hdr.data_len);
+	    n = read_from_socket (client_sock[client], cmd.data, cmd.hdr.data_len);
 	    if (n != cmd.hdr.data_len) {
 	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("\nError getting message data from client application.\n");
+	      dw_printf ("\nError getting message data from AGW client application %d.\n", client);
 	      dw_printf ("Tried to read %d bytes but got only %d.\n", cmd.hdr.data_len, n);
 	      dw_printf ("Closing connection.\n\n");
 #if __WIN32__
-	      closesocket (client_sock);
+	      closesocket (client_sock[client]);
 #else
-	      close (client_sock);
+	      close (client_sock[client]);
 #endif
-	      client_sock = -1;
+	      client_sock[client] = -1;
 	      return NULL;
 	    }
 	    if (n > 0) {
@@ -872,7 +975,7 @@ static void * cmd_listen_thread (void *arg)
  */
 
 	  if (debug_client) {
-	    debug_print (FROM_CLIENT, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
+	    debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
 	  }
 
 	  switch (cmd.hdr.kind_lo) {
@@ -901,15 +1004,15 @@ static void * cmd_listen_thread (void *arg)
 		assert (sizeof(reply) == 44);
 
 	        if (debug_client) {
-	          debug_print (TO_CLIENT, &reply.hdr, sizeof(reply));
+	          debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply));
 	        }
 
 // TODO:  Should have unified function instead of multiple versions everywhere.
 
 #if __WIN32__	      
-	        send (client_sock, (char*)(&reply), sizeof(reply), 0);
+	        send (client_sock[client], (char*)(&reply), sizeof(reply), 0);
 #else
-	        write (client_sock, &reply, sizeof(reply));
+	        n = write (client_sock[client], &reply, sizeof(reply));
 #endif
 	      }
 	      break;
@@ -940,13 +1043,13 @@ static void * cmd_listen_thread (void *arg)
 		assert (reply.hdr.data_len == 100);
 
 	        if (debug_client) {
-	          debug_print (TO_CLIENT, &reply.hdr, sizeof(reply));
+	          debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply));
 	        }
 
 #if __WIN32__     
-	        send (client_sock, (char*)(&reply), sizeof(reply), 0);
+	        send (client_sock[client], (char*)(&reply), sizeof(reply), 0);
 #else
-	        write (client_sock, &reply, sizeof(reply));
+	        n = write (client_sock[client], &reply, sizeof(reply));
 #endif
 	      }
 	      break;
@@ -991,13 +1094,13 @@ static void * cmd_listen_thread (void *arg)
 		assert (sizeof(reply) == 48);
 
 	        if (debug_client) {
-	          debug_print (TO_CLIENT, &reply.hdr, sizeof(reply));
+	          debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply));
 	        }
 
 #if __WIN32__     
-	        send (client_sock, (char*)(&reply), sizeof(reply), 0);
+	        send (client_sock[client], (char*)(&reply), sizeof(reply), 0);
 #else
-	        write (client_sock, &reply, sizeof(reply));
+	        n = write (client_sock[client], &reply, sizeof(reply));
 #endif
 	      }
 	      break;
@@ -1027,13 +1130,13 @@ static void * cmd_listen_thread (void *arg)
 	        reply.hdr.data_len = strlen(reply.info);
 
 	        if (debug_client) {
-	          debug_print (TO_CLIENT, &reply.hdr, sizeof(reply.hdr) + reply.hdr.data_len);
+	          debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply.hdr) + reply.hdr.data_len);
 	        }
 
 #if __WIN32__     
-	        send (client_sock, &reply, sizeof(reply.hdr) + reply.hdr.data_len, 0);
+	        send (client_sock[client], &reply, sizeof(reply.hdr) + reply.hdr.data_len, 0);
 #else
-	        write (client_sock, &reply, sizeof(reply.hdr) + reply.hdr.data_len);
+	        write (client_sock[client], &reply, sizeof(reply.hdr) + reply.hdr.data_len);
 #endif	      
 
 #endif
@@ -1047,14 +1150,14 @@ static void * cmd_listen_thread (void *arg)
 
 	      // Actually it is a toggle so we must be sure to clear it for a new connection.
 
-	      enable_send_raw_to_client = ! enable_send_raw_to_client;
+	      enable_send_raw_to_client[client] = ! enable_send_raw_to_client[client];
 	      break;
 
 	    case 'm':				/* Ask to start receiving Monitor frames */
 
 	      // Actually it is a toggle so we must be sure to clear it for a new connection.
 
-	      enable_send_monitor_to_client = ! enable_send_monitor_to_client;
+	      enable_send_monitor_to_client[client] = ! enable_send_monitor_to_client[client];
 	      break;
 
 
@@ -1074,6 +1177,8 @@ static void * cmd_listen_thread (void *arg)
     		//unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
     		//int flen;
 
+		// We have already assured these do not exceed 9 characters.
+
 	      	strcpy (stemp, cmd.hdr.call_from);
 	      	strcat (stemp, ">");
 	      	strcat (stemp, cmd.hdr.call_to);
@@ -1125,11 +1230,23 @@ static void * cmd_listen_thread (void *arg)
 	      	//	data length
 	      	//	data which is raw ax.25 frame.
 		//		
-
 	      
 		packet_t pp;
 
-		pp = ax25_from_frame ((unsigned char *)cmd.data+1, cmd.hdr.data_len, -1);
+		// Bug fix in version 1.1:
+		//
+		// The first byte of data is described as:
+		//
+		// 		the �TNC� to use
+		//		00=Port 1
+		//		16=Port 2
+		//
+		// I don't know what that means; we already a port number in the header.
+		// Anyhow, the original code here added one to cmd.data to get the 
+		// first byte of the frame.  Unfortunately, it did not subtract one from
+		// cmd.hdr.data_len so we ended up sending an extra byte.
+
+		pp = ax25_from_frame ((unsigned char *)cmd.data+1, cmd.hdr.data_len - 1, -1);
 
 		if (pp == NULL) {
 	          text_color_set(DW_COLOR_ERROR);
@@ -1177,13 +1294,13 @@ static void * cmd_listen_thread (void *arg)
 		// That's why more cumbersome size expression is used.
 
 	        if (debug_client) {
-	          debug_print (TO_CLIENT, &reply.hdr, sizeof(reply.hdr) + sizeof(reply.data));
+	          debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply.hdr) + sizeof(reply.data));
 	        }
 
 #if __WIN32__     
-	        send (client_sock, (char*)(&reply), sizeof(reply.hdr) + sizeof(reply.data), 0);
+	        send (client_sock[client], (char*)(&reply), sizeof(reply.hdr) + sizeof(reply.data), 0);
 #else
-	        write (client_sock, &reply, sizeof(reply.hdr) + sizeof(reply.data));
+	        n = write (client_sock[client], &reply, sizeof(reply.hdr) + sizeof(reply.data));
 #endif
 	      }
 	      break;
@@ -1201,7 +1318,7 @@ static void * cmd_listen_thread (void *arg)
 
 	      text_color_set(DW_COLOR_ERROR);
 	      dw_printf ("\n");
-	      dw_printf ("Can't process command from AGW client app.\n");
+	      dw_printf ("Can't process command from AGW client app %d.\n", client);
 	      dw_printf ("Connected packet mode is not implemented.\n");
 
 	      break;
@@ -1211,9 +1328,10 @@ static void * cmd_listen_thread (void *arg)
 
 		Not sure what we might want to do here.  
 		AGWterminal sends this for beacon or ask QRA.
+		None of the other tested applications use it.
 
 
-		<<< Send UNPROTO Information from AGWPE client application, total length = 253
+		<<< Send UNPROTO Information from AGWPE client application 0, total length = 253
 		        portx = 0, port_hi_reserved = 0
 		        kind_lo = 77 = 'M', kind_hi = 0
 		        call_from = "SV2AGW-1", call_to = "BEACON"
@@ -1222,21 +1340,43 @@ static void * cmd_listen_thread (void *arg)
 		  010:  73 20 74 68 65 20 6e 65 77 20 41 47 57 20 50 61  s the new AGW Pa
 		  020:  63 6b 65 74 20 45 6e 67 69 6e 65 20 77 69 6e 73  cket Engine wins
 
-		<<< Send UNPROTO Information from AGWPE client application, total length = 37
+		<<< Send UNPROTO Information from AGWPE client application 0, total length = 37
 		        portx = 0, port_hi_reserved = 0
 		        kind_lo = 77 = 'M', kind_hi = 0
 		        call_from = "SV2AGW-1", call_to = "QRA"
 		        data_len = 1, user_reserved = 32218432, data =
 		  000:  0d                                               .
 
+	      {
+	      
+		packet_t pp;
+		int pid = cmd.datakind_hi & 0xff;
+			/* "AX.25 PID 0x00 or 0xF0 for AX.25 0xCF NETROM and others" */
+
+
+		This is not right.
+		It needs to be more like "V" Transmit UI data frame
+		except there are no digipeaters involved.
+
+		pp = ax25_from_frame ((unsigned char *)cmd.data, cmd.hdr.data_len, -1);
+
+		if (pp != NULL) {
+		  tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
+		  ax25_set_pid (pp, pid);
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Failed to create frame from AGW 'M' message.\n");
+		}
+	      }
 	      break;
 
 #endif
 	    default:
 
 	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("--- Unexpected Command from application using AGW protocol:\n");
-	      debug_print (FROM_CLIENT, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
+	      dw_printf ("--- Unexpected Command from application %d using AGW protocol:\n", client);
+	      debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
 
 	      break;
 	  }
diff --git a/symbols-new.txt b/symbols-new.txt
index 6b81aee..e8856f6 100755
--- a/symbols-new.txt
+++ b/symbols-new.txt
@@ -1,4 +1,4 @@
-APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2      20 May 2014
+APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2      28 Aug 2014
 ---------------------------------------------------------------------
 
 BACKGROUND:  This file addresses new additions proposals (OVERLAYS) 
@@ -13,6 +13,7 @@ CORRECT one.
 
 UPDATES/REVISIONS/CORRECTIONS:
 
+28 Aug 14  Added numerous OpenAPRS symbols see "(new Aug 2014)"
 20 May 14  Changed Da to DSTAR (2700 of them) from Dutch Ares
 19 May 14  Added Submarine&torpedo to ships and lots of Aircraft
            search for "(new may 2014)"
@@ -113,6 +114,11 @@ letting that define a new graphic just for that combination.
 The following tables will attempt to keep track of these and
 any other useful generic applications of overlay characters.
 
+change Flooding #W to include Avalanche, Mudslide/Landslide
+change \' to crash & incident sites
+change \D to DEPOT family
+change overlayed car to generic with (1-9 overlays)
+
 AIRCRAFT
 /^ = LARGE Aircraft
 \^ = top-view originally intended to point in direction of flight
@@ -121,7 +127,9 @@ E^ = Enemy aircraft (too bad I cant use the original Hostile)
 H^ = Hovercraft    (new may 2014)
 J^ = JET     (new may 2014)
 M^ = Missle   (new may 2014)
+P^ = Prop (new Aug 2014)
 V^ = Vertical takeoff   (new may 2014)
+X^ = Experimental (new Aug 2014)
 
 ATM Machine or CURRENCY:  #$ 
 /$ = original primary Phone
@@ -130,12 +138,31 @@ U$ = US dollars
 L$ = Brittish Pound
 Y$ = Japanese Yen
 
+DEPOT
+/D = was originally undefined
+\D = was drizzle (moved to ' ovlyD)
+AD = Airport  (new Aug 2014)
+FD = Ferry Landing (new Aug 2014)
+HD = Heloport (new Aug 2014)
+RD = Rail Depot  (new Aug 2014)
+BD = Bus Depot (new Aug 2014)
+LD = LIght Rail or Subway (new Aug 2014)
+SD = Seaport Depot (new Aug 2014)
+
+EMERGENCY: #!
+/! = Police/Sheriff, etc
+\! = Emergency!
+E! = ELT or EPIRB  (new Aug 2014)
+V! = Volcanic Eruption or Lava  (new Aug 2014)
+
 POWER PLANT: #%  
 /% = DX cluster  <= the original primary table definition
 C% = Coal
+E% = Emergency  (new Aug 2014)
 G% = Geothermal
 H% = Hydroelectric
 N% = Nuclear
+P% = Portable (new Aug 2014)
 S% = Solar
 T% = Turbine
 W% = Wind
@@ -148,7 +175,8 @@ T& = TX igate with path set to 1 hop only)
 2& = TX igate with path set to 2 hops (not generally good idea)
 
 INCIDENT SITES: #'
-\' = Airplane Crash Site  <= the original primary deifinition
+/' = Small Aircraft (original primary symbol)
+\' = Airplane Crash Site  <= the original alternate deifinition
 A' = Automobile crash site
 H' = Hazardous incident
 M' = Multi-Vehicle crash site
@@ -199,9 +227,10 @@ ADVISORIES: #<  (new expansion possibilities)
 /< = motorcycle
 \< = Advisory (single gale flag)
 
-CARS: #>
+CARS: #> (Vehicles)
 /> = normal car (side view)
 \> = Top view and symbol POINTS in direction of travel
+#> = Reserve overlays 1-9 for numbered cars (new Aug 2014)
 E> = Electric 
 H> = Hybrid
 S> = Solar powered
@@ -270,13 +299,18 @@ Wa = WinLink
 CIVIL DEFENSE or TRIANGLE: #c
 /c = Incident Command Post
 \c = Civil Defense
+Dc = Decontamination (new Aug 2014)
 Rc = RACES
 Sc = SATERN mobile canteen
 
 BUILDINGS: #h
 /h = Hospital
 \h = Ham Store       ** <= now used for HAMFESTS
+Fh = HamFest (new Aug 2014)
 Hh = Home Dept etc..
+Mh = Morgue
+Ch = Clinic
+Th = Triage
 
 SPECIAL VEHICLES: #k
 /k = truck
@@ -284,20 +318,36 @@ SPECIAL VEHICLES: #k
 4k = 4x4
 Ak = ATV (all terrain vehicle)
 
-SHIPS: #s
+SHIPS: #s 
 /s = Power boat (ship) side view
 \s = Overlay Boat (Top view)
-Cs = receive as Canoe but still transmit canoe as /C
+6s = Shipwreck ("deep6") (new Aug 2014)
+Bs = Pleasure Boat
+Cs = Cargo
+Ds = Diving
+Es = Emergency or Medical transport
+Fs = Fishing
+Hs = High-speed Craft 
 Js = Jet Ski
-Ks = Kayak
-Hs = Hovercraft (new may 2014)
-Ts = Torpedo   (new may 2014)
-Us = sUbmarine U-boat   (new may 2014)
+Ls = Law enforcement
+Ms = Miltary
+Os = Oil Rig
+Ps = Pilot Boat (new Aug 2014)
+Qs = Torpedo
+Ss = Search and Rescue
+Ts = Tug (new Aug 2014)
+Us = Underwater ops or submarine
+Ws = Wing-in-Ground effect (or Hovercraft)
+Xs = Passenger (paX)(ferry)
+Ys = Sailing (large ship)
+
 
 TRUCKS: #u
 /u = Truck (18 wheeler)
 \u = truck with overlay
+Bu = Buldozer/construction  (new Aug 2014)
 Gu = Gas
+Pu = Plow or SnowPlow (new Aug 2014)
 Tu = Tanker
 Cu = Chlorine Tanker
 Hu = Hazardous
diff --git a/symbols.c b/symbols.c
index 1ba1773..c833ec0 100755
--- a/symbols.c
+++ b/symbols.c
@@ -307,20 +307,32 @@ static int new_sym_len = 0;			/* Number of elements used. */
 void symbols_init (void)
 {
 	FILE *fp;
-	struct {
-	  char overlay;
-	  char symbol;
-	  char sp1;
-	  char equal;
-	  char sp2;
-	  char description[150];
-	} stuff;
-	int j;
 
-#define GOOD_LINE(x) ((x.overlay == '/' || x.overlay == '\\' || isupper(x.overlay) || isdigit(x.overlay)) \
-			&& x.symbol >= '!' && x.symbol <= '~' \
-			&& x.sp1 == ' ' && x.equal == '=' && x.sp2 == ' ')
+/*
+ * We only care about lines with this format:
+ *
+ *  Column 1 - overlay character of / \ upper case or digit
+ *  Column 2 - symbol in range of ! thru ~
+ *  Column 3 - space
+ *  Column 4 - equal sign
+ *  Column 5 - space
+ *  Column 6 - Start of description.
+ */
+
+#define COL1_OVERLAY 0
+#define COL2_SYMBOL 1
+#define COL3_SP 2
+#define COL4_EQUAL 3
+#define COL5_SP 4
+#define COL6_DESC 5
+
+	char stuff[200];
+	int j;
 
+#define GOOD_LINE(x) (strlen(x) > 6 && \
+			(x[COL1_OVERLAY] == '/' || x[COL1_OVERLAY] == '\\' || isupper(x[COL1_OVERLAY]) || isdigit(x[COL1_OVERLAY])) \
+			&& x[COL2_SYMBOL] >= '!' && x[COL2_SYMBOL] <= '~' \
+			&& x[COL3_SP] == ' ' && x[COL4_EQUAL] == '=' && x[COL5_SP] == ' ' && x[COL6_DESC] != ' ')
 
 	if (new_sym_ptr != NULL) {
 	  return;			/* was called already. */
@@ -350,7 +362,7 @@ void symbols_init (void)
 /*
  * Count number of interesting lines and allocate storage.
  */
-	while (fgets((char*)(&stuff), sizeof(stuff), fp) != NULL) {
+	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
 	  if (GOOD_LINE(stuff)) {
 	    new_sym_size++;
 	  }
@@ -363,15 +375,15 @@ void symbols_init (void)
  */
 	rewind (fp);
 
-	while (fgets((char*)(&stuff), sizeof(stuff), fp) != NULL) {
+	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
 
 	  if (GOOD_LINE(stuff)) {
-	    for (j = strlen(stuff.description) - 1; j>=0 && stuff.description[j] <= ' '; j--) {
-	      stuff.description[j] = '\0';
+	    for (j = strlen(stuff+COL6_DESC) - 1; j>=0 && stuff[COL6_DESC+j] <= ' '; j--) {
+	      stuff[COL6_DESC+j] = '\0';
 	    }
-	    new_sym_ptr[new_sym_len].overlay = stuff.overlay;
-	    new_sym_ptr[new_sym_len].symbol = stuff.symbol;
-	    strncpy(new_sym_ptr[new_sym_len].description, stuff.description, NEW_SYM_DESC_LEN);
+	    new_sym_ptr[new_sym_len].overlay = stuff[COL1_OVERLAY];
+	    new_sym_ptr[new_sym_len].symbol = stuff[COL2_SYMBOL];
+	    strncpy(new_sym_ptr[new_sym_len].description, stuff+COL6_DESC, NEW_SYM_DESC_LEN);
 	    new_sym_len++;
 	  }
 	}
diff --git a/symbolsX.txt b/symbolsX.txt
index 7e91724..6f086fa 100755
--- a/symbolsX.txt
+++ b/symbolsX.txt
@@ -1,20 +1,21 @@
-APRS SYMBOLS (Icons)                                        07 Oct 2013
+APRS SYMBOLS (Icons)                                        28 Aug 2014
 -----------------------------------------------------------------------
                                                                  WB4APR
 
 This original APRS symbol specification is updated periodically with 
 new symbols as they are defined.  This is THE master list for APRS. But
-almost all new symbols will be formed as Overlays to the basic set. So
-be sure to check the symbols-new.txt file noted below!
+almost all new symbols since 2007 will be formed as Overlays to the 
+basic set. So be sure to check the symbols-new.txt file noted below!
+
+http://aprs.org/symbols/symbols-new.txt
 
 NEW OVERLAYS ON ALL ALTERNATE SYMBOLS: As of 1 October 2007, the use of 
 overlay characters on all alternate symbols was allowed as needed in 
 order to further expand the APRS symbol set.  These overlay expansions 
 of up to 36 different usages for each of the 94 alternate symbols adds 
-hundreds of potential new symbols.  Since this overlay info will no 
-longer fit in this original document it is found in a new document:
-
-http://aprs.org/symbols/symbols-new.txt
+hundreds of potential new symbols.  Since each of the original 94 
+symbols can now have up to 36 other definitions, this new overlay info
+is now found in the above file.
 
 If an alternate symbol from the table below, contains significant
 definitions of its overlay characters, then the entry will have an "O"
@@ -23,10 +24,17 @@ document.  The # symbol indicates the original Overlay subset.
 
 For details on Upgrading your symbol set, please see the background
 information on Symbols prepared by Stephen Smith, WA8LMF:
-http://www.ew.usna.edu/~bruninga/aprs/symbols-background.txt
+http://aprs.org/symbols/symbols-background.txt
 
 UPDATE CHRONOLOGY:
 
+28 Aug 14: Added notation on newly availble BASE codes (begun in 2007)
+           Old WX versions of these: Bb{*:DFegJp were moved to ovlays
+           Expanded #w Flooding to include Avalanches, Mud/Landslides
+           Changed #D from "avail" to new family called Depots
+           Changed name of overlay Car to generic Vehicles w overlays
+           Edited name of /E Eyeball to include "Events"
+19 May 14: Added many new Aircraft and Ship symbols see symbols-new.txt
 07 Oct 13: Added JetSki [Js] & Ham Club [C-] ovrlys to symbols-new.txt
 19 Sep 11: Added T & 2 overlay for 1 & 2 hop Message TX Igates
            Updated overlay "portable" (;) overlays for events
@@ -100,7 +108,9 @@ Alt:  >KOSY[^ksuv\                               <==[removed /0An]
 
 SYMBOLS.TXT      APRS DISPLAY SYMBOLS             APRSdos    ORIGINAL
 ======================================================================
-Document dated:  28 Apr 99  FInal APRSdos symbol spec (still updated!)
+Document dated:  28 Apr 99  FInal APRSdos symbol spec 
+**************:  This file Remains CUrrent and is Updated Frequently
+**************:  See date and updates at the top of this file.
 Author(s):       Bob Bruninga, WB4APR <bruninga at usna.edu>
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -113,8 +123,8 @@ tables or may be used as an alphanumeric overlay over some symbols:
          &       RESERVED for possible AUXILLIARY tables (Aug 09)
          /       Primary   symbol Table  (Mostly stations)
          \       Alternate symbol table  (Mostly Objects)
-         0-9     Alternate OVERLAY symbol with 0-9 overlayed
-         A-Z     Alternate OVERLAY symbol with A-Z overlayed
+         0-9     Alternate OVERLAY symbols with 0-9 overlayed
+         A-Z     Alternate OVERLAY symbols with A-Z overlayed
       
 For ease of reference we refer to these as the SYMBOL CHARACTERS and
 often abbreviate them as "/$" which refers to the Table character "/"
@@ -128,8 +138,8 @@ symbols through Oct 2007 were:
   CIRCLE, SQUARE, CAR, TRUCK, VAN, DIGIS, GATES
   Civil-Defense(RACES), NWS sites, WX stations, Triangle
 
-After that, provisions should be made in all software to allow for
-overlays on any alternate symbol as they may be used in the future.
+After 2007, provisions should be made in all software to allow for
+overlays on *any/all* alternate symbols for use in the future.
 
 SYMBOLS WITH STAND-ALONE GPS TRACKERS:  Stand-alone devices that
 transmit raw GPS have no method to convey their symbol.  For this
@@ -162,16 +172,16 @@ for the stand-alone trackers described above.
 
 /$ XYZ BASIC SYMBOL TABLE        \$  XYZ OTHER SYMBOL TABLE (\)
 -- --- ------------------------  --  --- ----------------------
-/! BB  Police, Sheriff           \!  OB  EMERGENCY (!)            
+/! BB  Police, Sheriff           \!  OBO EMERGENCY (and overlays)            
 /" BC  reserved  (was rain)      \"  OC  reserved
 /# BD  DIGI (white center)       \#  OD# OVERLAY DIGI (green star)
 /$ BE  PHONE                     \$  OEO Bank or ATM  (green box) 
 /% BF  DX CLUSTER                \%  OFO Power Plant with overlay
 /& BG  HF GATEway                \&  OG# I=Igte R=RX T=1hopTX 2=2hopTX
 /' BH  Small AIRCRAFT (SSID = 7) \'  OHO Crash (& now Incident sites)
-/( BI  Mobile Satellite Station  \(  OI  CLOUDY (other clouds w ovrly)
+/( BI  Mobile Satellite Station  \(  OIO CLOUDY (other clouds w ovrly)
 /) BJ  Wheelchair (handicapped)  \)  OJO Firenet MEO, MODIS Earth Obs.
-/* BK  SnowMobile                \*  OK  SNOW (& future ovrly codes)
+/* BK  SnowMobile                \*  OK  AVAIL (SNOW moved to ` ovly S)
 /+ BL  Red Cross                 \+  OL  Church
 /, BM  Boy Scouts                \,  OM  Girl Scouts
 /- BN  House QTH (VHF)           \-  ONO House (H=HF) (O = Op Present)
@@ -181,38 +191,38 @@ for the stand-alone trackers described above.
 
 /$ XYZ PRIMARY SYMBOL TABLE      \$  XYZ ALTERNATE SYMBOL TABLE (\)
 -- --- ------------------------  --  --- --------------------------
-/0 P0  # circle (obsolete)       \0  A0# CIRCLE (E/I/W=IRLP/Echolink/WIRES)
-/1 P1  TBD (these were numbered) \1  A1
-/2 P2  TBD (circles like pool)   \2  A2
-/3 P3  TBD (balls.  But with)    \3  A3
-/4 P4  TBD (overlays, we can)    \4  A4
-/5 P5  TBD (put all #'s on one)  \5  A5
-/6 P6  TBD (So 1-9 are available)\6  A6
-/7 P7  TBD (for new uses?)       \7  A7
+/0 P0  # circle (obsolete)       \0  A0# CIRCLE (IRLP/Echolink/WIRES)
+/1 P1  TBD (these were numbered) \1  A1  AVAIL
+/2 P2  TBD (circles like pool)   \2  A2  AVAIL
+/3 P3  TBD (balls.  But with)    \3  A3  AVAIL
+/4 P4  TBD (overlays, we can)    \4  A4  AVAIL
+/5 P5  TBD (put all #'s on one)  \5  A5  AVAIL
+/6 P6  TBD (So 1-9 are available)\6  A6  AVAIL
+/7 P7  TBD (for new uses?)       \7  A7  AVAIL
 /8 P8  TBD (They are often used) \8  A8O 802.11 or other network node
 /9 P9  TBD (as mobiles at events)\9  A9  Gas Station (blue pump)  
-/: MR  FIRE                      \:  NR  Hail (& future ovrly codes)                    
-/; MS  Campground (Portable ops) \;  NSO Park/Picnic + overlay events        
+/: MR  FIRE                      \:  NR  AVAIL (Hail ==> ` ovly H)                    
+/; MS  Campground (Portable ops) \;  NSO Park/Picnic + overlay events 
 /< MT  Motorcycle     (SSID =10) \<  NTO ADVISORY (one WX flag)
 /= MU  RAILROAD ENGINE           \=  NUO APRStt Touchtone (DTMF users)
-/> MV  CAR            (SSID = 9) \>  NV# OVERLAYED CAR
+/> MV  CAR            (SSID = 9) \>  NV# OVERLAYED CARs & Vehicles
 /? MW  SERVER for Files          \?  NW  INFO Kiosk  (Blue box with ?)
 /@ MX  HC FUTURE predict (dot)   \@  NX  HURICANE/Trop-Storm
 /A PA  Aid Station               \A  AA# overlayBOX DTMF & RFID & XO
-/B PB  BBS or PBBS               \B  AB  Blwng Snow (& future codes)
+/B PB  BBS or PBBS               \B  AB  AVAIL (BlwngSnow ==> E ovly B
 /C PC  Canoe                     \C  AC  Coast Guard          
-/D PD                            \D  AD  Drizzle (proposed APRStt)
-/E PE  EYEBALL (Eye catcher!)    \E  AE  Smoke (& other vis codes)
-/F PF  Farm Vehicle (tractor)    \F  AF  Freezng rain (&future codes)
-/G PG  Grid Square (6 digit)     \G  AG  Snow Shwr (& future ovrlys)
+/D PD                            \D ADO  DEPOTS (Drizzle ==> ' ovly D)
+/E PE  EYEBALL (Events, etc!)    \E  AE  Smoke (& other vis codes)
+/F PF  Farm Vehicle (tractor)    \F  AF  AVAIL (FrzngRain ==> `F)
+/G PG  Grid Square (6 digit)     \G  AG  AVAIL (Snow Shwr ==> I ovly S)
 /H PH  HOTEL (blue bed symbol)   \H  AHO \Haze (& Overlay Hazards)
 /I PI  TcpIp on air network stn  \I  AI  Rain Shower  
-/J PJ                            \J  AJ  Lightening (& future ovrlys)
+/J PJ                            \J  AJ  AVAIL (Lightening ==> I ovly L)
 /K PK  School                    \K  AK  Kenwood HT (W)              
 /L PL  PC user (Jan 03)          \L  AL  Lighthouse                     
 /M PM  MacAPRS                   \M  AMO MARS (A=Army,N=Navy,F=AF) 
 /N PN  NTS Station               \N  AN  Navigation Buoy          
-/O PO  BALLOON        (SSID =11) \O  AO  Rocket (new June 2004)
+/O PO  BALLOON        (SSID =11) \O  AO  Overlay Balloon (Rocket = \O)
 /P PP  Police                    \P  AP  Parking                    
 /Q PQ  TBD                       \Q  AQ  QUAKE                       
 /R PR  REC. VEHICLE   (SSID =13) \R  ARO Restaurant                   
@@ -223,18 +233,18 @@ for the stand-alone trackers described above.
 /W PW  National WX Service Site  \W  AW# # NWS site (NWS options)
 /X PX  HELO           (SSID = 6) \X  AX  Pharmacy Rx (Apothicary)
 /Y PY  YACHT (sail)   (SSID = 5) \Y  AYO Radios and devices
-/Z PZ  WinAPRS                   \Z  AZ
-/[ HS  Human/Person (HT)         \[  DSO W.Cloud (& humans w Ovrly)                 
+/Z PZ  WinAPRS                   \Z  AZ  AVAIL
+/[ HS  Human/Person (HT)         \[  DSO W.Cloud (& humans w Ovrly)
 /\ HT  TRIANGLE(DF station)      \\  DTO New overlayable GPS symbol
-/] HU  MAIL/PostOffice(was PBBS) \]  DU
-/^ HV  LARGE AIRCRAFT            \^  DV# # Aircraft (shows heading)
+/] HU  MAIL/PostOffice(was PBBS) \]  DU  AVAIL
+/^ HV  LARGE AIRCRAFT            \^  DV# other Aircraft ovrlys (2014)
 /_ HW  WEATHER Station (blue)    \_  DW# # WX site (green digi)
 /` HX  Dish Antenna              \`  DX  Rain (all types w ovrly)    
 
 /$ XYZ LOWER CASE SYMBOL TABLE   \$  XYZ SECONDARY SYMBOL TABLE (\)
 -- --- ------------------------  --  --- --------------------------
-/a LA  AMBULANCE     (SSID = 1)  \a  SA#O ARRL, ARES, WinLINK
-/b LB  BIKE          (SSID = 4)  \b  SB  Blwng Dst/Snd (& others)
+/a LA  AMBULANCE     (SSID = 1)  \a  SA#O ARRL,ARES,WinLINK,Dstar, etc
+/b LB  BIKE          (SSID = 4)  \b  SB  AVAIL(Blwng Dst/Snd => E ovly)
 /c LC  Incident Command Post     \c  SC#O CD triangle RACES/SATERN/etc
 /d LD  Fire dept                 \d  SD  DX spot by callsign
 /e LE  HORSE (equestrian)        \e  SE  Sleet (& future ovrly codes)
@@ -248,27 +258,28 @@ for the stand-alone trackers described above.
 /m LM  Mic-E Repeater            \m  SM  Value Sign (3 digit display)   
 /n LN  Node (black bulls-eye)    \n  SN# OVERLAY TRIANGLE         
 /o LO  EOC                       \o  SO  small circle                    
-/p LP  ROVER (puppy, or dog)     \p  SP  Prtly Cldy (& future ovrlys)            
-/q LQ  GRID SQ shown above 128 m \q  SQ
+/p LP  ROVER (puppy, or dog)     \p  SP  AVAIL (PrtlyCldy => ( ovly P
+/q LQ  GRID SQ shown above 128 m \q  SQ  AVAIL
 /r LR  Repeater         (Feb 07) \r  SR  Restrooms                
-/s LS  SHIP (pwr boat)  (SSID-8) \s  SS# OVERLAY SHIP/boat (top view)
+/s LS  SHIP (pwr boat)  (SSID-8) \s  SS# OVERLAY SHIP/boats
 /t LT  TRUCK STOP                \t  ST  Tornado                  
 /u LU  TRUCK (18 wheeler)        \u  SU# OVERLAYED TRUCK
 /v LV  VAN           (SSID = 15) \v  SV# OVERLAYED Van
-/w LW  WATER station             \w  SW  Flooding                 
-/x LX  xAPRS (Unix)              \x  SX  Wreck or Obstruction ->X<-                                
+/w LW  WATER station             \w  SWO Flooding (Avalanches/Slides)   
+/x LX  xAPRS (Unix)              \x  SX  Wreck or Obstruction ->X<-
 /y LY  YAGI @ QTH                \y  SY  Skywarn
 /z LZ  TBD                       \z  SZ# OVERLAYED Shelter 
-/{ J1                            \{  Q1  Fog (& future ovrly codes)
+/{ J1                            \{  Q1  AVAIL? (Fog ==> E ovly F)
 /| J2  TNC Stream Switch         \|  Q2  TNC Stream Switch
-/} J3                            \}  Q3
+/} J3                            \}  Q3  AVAIL? (maybe)
 /~ J4  TNC Stream Switch         \~  Q4  TNC Stream Switch
 
 HEADING SYMBOLS:  Although all symbols are supposed to have a heading
 line showing the direction of movement with a length proportional to
 the log of speed, some symbols were desiged as top-down views so that 
 they could be displayed actually always POINTING in the direction of 
-movement.  These special symbols are:
+movement.  Now All symbols should be oriented (if practical).  These 
+original special symbols were:
  
 \> OVERLAYED CAR
 \s Overlayed Ship
@@ -277,10 +288,14 @@ movement.  These special symbols are:
 /g Glider
 \n Overlayed Triangle
 
-AREA SYMBOLS!  You can define BOX/CIRCLE/LINE or TRIANGLE areas in all
-colors, either open or filled in, any size from 60 feet to 100 miles. 
-Simply move the cursor to the location, press HOME, move the cursor to 
-the lower right corner of the AREA and hit INPUT-ADD-OBJECTS-AREA.  
+AREA SYMBOLS!  The special symbol \l (lower case L) was special.  It
+indicates an area definition.  You can define these as a BOX, CIRCLE,
+LINE or TRIANGLE area in all colors, either open or filled in, any 
+size from 60 feet to 100 miles.  In APRSdos they were generated auto-
+matically by simply moving the cursor to the location, press HOME, 
+move the cursor to the lower right corner of the AREA and hit INPUT-
+ADD-OBJECTS-AREA.  
+
 Enter the type of area, and color.  NOTE that AREA shapes can only be 
 defined by selecting the upper left corner first, then the lower right 
 second.  The line is an exception.  It is still top to bottom, but the 
@@ -294,10 +309,12 @@ cautious in using the color fill option, since all other objects in
 that area that occur earlier in your PLIST will be obscured.  AND you 
 do NOT know the order of other stations P-lists. 
 
-AREAS FORMAT: The new format for specifying special areas uses the 
-CSE/SPD field to provide the additional information as follows:
+AREAS FORMAT: Use of the special AREAS symbol (/l) triggers special
+processing of the next 7 bytes normally used for CSE and SPD.  In 
+this special case the processing is as follows:
 
 $CSE/SPD...  Normal Field description
+
 lTyy/Cxx...  Where:  l (lower case L) is  symbol for "LOCATION SHAPES"
                      T is Type of shape:  0=circle, 1=line, 2=elipse
                                           3=triangle 4=box
@@ -314,7 +331,7 @@ Type of 6 and are drawn down and to the left.
 HURRICANES, TROPICAL STORMS and DEPRESSIONS:  These symbols will be
 differentiated by colors red, yellos, and blue.  Additionally a radius
 of Huricane and also Tropical storm winds will also be shown if the
-format detailed in WX.txt is used.
+special format detailed in WX.txt is used.
 
 SYMBOLS ON MAPS!  APRS can also be permanently embedded in maps.  To 
 embed a symbol in a map, simply make the first four characters of the 
@@ -325,9 +342,12 @@ same location.  An example are the VORTAC nav-aids in Alaska.  The
 Anchorage VORTAC appears as ANC on all maps below 128 miles. The label 
 entry is #\VFANC,LAT,LONG,128.
 
-VALUE SIGNPOSTS:  Signposts display as a yellow box with a 1-3 letter 
-overlay on them.  You specify the 1-3 letter overlay by enclosing them 
-in braces in the comment field.  Thus a VALUE Signpost with {55} would 
+VALUE SIGNPOSTS:  This is another special handling Symbol.  Signposts
+trigger a display as a yellow box with a 1-3 letter overlay on them. 
+The use of this symbol (\m) triggers a search for a 1-3 letter 
+string of characters encolsed in braces in the comment field.  
+
+Thus a VALUE Signpost with {55} in the comment field would 
 appear as a sign with 55 on it, designed for posting the speed 
 of traffic past speed measuring devices. APRSdos has a version named
 APRStfc.EXE that monitors traffic speed and posts these speed signs
@@ -336,21 +356,21 @@ map, they ONLY appear at 8 miles and below AND they do not show any
 callsign or name.  Only the yellow box and the 3 letters or numbers.  
 Select them from the OBJECT menu under VALUE...
 
-APRS 1.2 OVERLAY TYPE SYMBOLS [April 2007]:
--------------------------------------------
+APRS 1.2 OVERLAY TYPE SYMBOLS EXPANSION!  [April 2007]:
+-------------------------------------------------------
 
 All alternate symbols have the potential to be overlayed.  This was 
 the original intent and was only limited to a few due to limitations 
 in Mac and WinAPRS.  Those original "numbered" symbols are marked 
 with a # in the table above.  But by 2007, it was time to move on.  
-In APRS 1.2 it is proposed that any ALTENATE symbol can have overlays.
+In APRS 1.2 it was proposed that any ALTENATE symbol can have overlays.
 Kenwood has already responded with the new D710 that can now display 
 these overlays on all symbols.  
 
 To help define these hundreds of new symbol combinations, we have
 added a new file called:
 
-http://www.ew.usna.edu/~bruninga/aprs/symbols-new.txt
+http:aprs.org/symbols/symbols-new.txt
 
 The overlay symbols may be used in two ways.  First, simply as an 
 overlay on a basic symbol type.  Most uses of these symbols will be
diff --git a/telemetry.c b/telemetry.c
new file mode 100755
index 0000000..05297ab
--- /dev/null
+++ b/telemetry.c
@@ -0,0 +1,1038 @@
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014  John Langner, WB2OSZ
+//
+//    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.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+
+//#define DEBUG1 1		/* Parsing of original human readable format. */
+//#define DEBUG2 1		/* Parsing of base 91 compressed format. */
+//#define DEBUG3 1		/* Parsing of special messages. */
+//#define DEBUG4 1		/* Resulting display form. */
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      telemetry.c
+ *
+ * Purpose:   	Decode telemetry information.
+ *		Point out where it violates the protocol spec and 
+ *		other applications might not interpret it properly.
+ *		
+ * References:	APRS Protocol, chapter 13.
+ *		http://www.aprs.org/doc/APRS101.PDF
+ *
+ *		Base 91 compressed format
+ *		http://he.fi/doc/aprs-base91-comment-telemetry.txt
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+#endif
+
+#include "direwolf.h"
+#include "ax25_pad.h"			// for packet_t, AX25_MAX_ADDR_LEN
+#include "decode_aprs.h"		// for decode_aprs_t, G_UNKNOWN  
+#include "textcolor.h"
+#include "telemetry.h"
+
+
+#define MAX(x,y) ((x)>(y) ? (x) : (y))
+
+
+#define T_NUM_ANALOG 5				/* Number of analog channels. */
+#define T_NUM_DIGITAL 8				/* Number of digital channnels. */
+
+#define T_STR_LEN 16				/* Max len for labels and units. */
+
+
+#define MAGIC1  0xa51111a5			/* For checking storage allocation problems. */
+#define MAGIC2  0xa52222a5
+
+#define C_A 0					/* Scaling coefficient positions. */
+#define C_B 1
+#define C_C 2
+
+
+/*
+ * Metadata for telemetry data.
+ */
+
+struct t_metadata_s {
+	int magic1;
+
+	struct t_metadata_s * pnext;		/* Next in linked list. */
+
+	char station[AX25_MAX_ADDR_LEN];	/* Station name with optional SSID. */
+
+	char project[40];			/* Description for data. */
+						/* "Project Name" or "project title" in the spec. */
+
+	char name[T_NUM_ANALOG+T_NUM_DIGITAL][T_STR_LEN];
+						/* Names for channels.  e.g. Battery, Temperature */
+
+	char unit[T_NUM_ANALOG+T_NUM_DIGITAL][T_STR_LEN];
+						/* Units for channels.  e.g. Volts, Deg.C */
+
+	float coeff[T_NUM_ANALOG][3];		/* a, b, c coefficients for scaling. */
+
+	int coeff_ndp[T_NUM_ANALOG][3];		/* Number of decimal places for above. */
+
+	int sense[T_NUM_DIGITAL];		/* Polarity for digital channels. */
+
+	int magic2;
+};
+
+
+static 	struct t_metadata_s * md_list_head = NULL;
+
+static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_ANALOG], int ndp[T_NUM_ANALOG], int draw[T_NUM_DIGITAL], char *output); 
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        t_get_metadata
+ *
+ * Purpose:     Obtain pointer to metadata for specified station.
+ *		If not found, allocate a fresh one and initialize with defaults.
+ *
+ * Inputs:	station		- Station name with optional SSID.
+ *
+ * REturns:	Pointer to metadata.
+ *
+ *--------------------------------------------------------------------*/
+
+static struct t_metadata_s * t_get_metadata (char *station)
+{
+	struct t_metadata_s *p;
+	int n, j;
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("t_get_metadata (station=%s)\n", station);
+#endif
+
+	for (p = md_list_head; p != NULL; p = p->pnext) {
+	  if (strcmp(station, p->station) == 0) {
+	    return (p);
+	  }
+	}
+
+	p = malloc (sizeof (struct t_metadata_s));
+	memset (p, 0, sizeof (struct t_metadata_s));
+
+	p->magic1 = MAGIC1;
+	strncpy (p->station, station, sizeof(p->station)-1);
+
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  sprintf (p->name[n], "A%d", n+1);
+	}
+	for (n = 0; n < T_NUM_DIGITAL; n++) {
+	  sprintf (p->name[T_NUM_ANALOG+n], "D%d", n+1);
+	}
+
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  p->coeff[n][C_A] = 0.;
+	  p->coeff[n][C_B] = 1.;
+	  p->coeff[n][C_C] = 0.;
+	  p->coeff_ndp[n][C_A] = 0;
+	  p->coeff_ndp[n][C_B] = 0;
+	  p->coeff_ndp[n][C_C] = 0;
+	}
+	
+	for (n = 0; n < T_NUM_DIGITAL; n++) {
+	  p->sense[n] = 1;
+	}
+
+	p->magic2 = MAGIC2;
+
+	p->pnext = md_list_head;
+	md_list_head = p;
+
+	assert (p->magic1 == MAGIC1);
+	assert (p->magic2 == MAGIC2);
+
+	return (p);
+
+} /* end t_get_metadata */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        t_ndp
+ *
+ * Purpose:     Count number of digits after any decimal point.
+ *
+ * Inputs:	str	- Number in text format.
+ *
+ * Returns:	Number digits after decimal point.  Examples, in --> out.
+ *
+ *			1	--> 0
+ *			1.	--> 0
+ *			1.2	--> 1
+ *			1.23	--> 2
+ *			etc.
+ *
+ *--------------------------------------------------------------------*/
+
+static int t_ndp (char *str)
+{
+	char *p;
+
+	p = strchr(str,'.');
+	if (p == NULL) {
+	  return (0);
+	}
+	else {
+	  return (strlen(p+1));
+	}
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        telemetry_data_original
+ *
+ * Purpose:     Interpret telemetry data in the original format.
+ *
+ * Inputs:	station	- Name of station reporting telemetry.
+ *		info 	- Pointer to packet Information field.
+ *
+ * Outputs:	output	- Decoded telemetry in human readable format.
+ *		comment	- Any comment after the data.
+ *
+ * Description:	The first character, after the "T" data type indicator, must be "#" 
+ *		followed by a sequence number.  Up to 5 analog and 8 digital channel 
+ *		values are specified as in this example from the protocol spec.
+ *
+ *			T#005,199,000,255,073,123,01101001
+ *
+ *		The analog values are supposed to be 3 digit integers in the 
+ *		range of 000 to 255 in fixed columns.  After reading the discussion 
+ *		groups it seems that few adhere to those restrictions.  When I 
+ *		started to look for some local signals, this was the first one
+ *		to appear:
+ *
+ *			KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000
+ *
+ *		Not integers.  Not fixed width fields.
+ *		We will accept these but issue a warning that others might not.		

+ *		
+ *--------------------------------------------------------------------*/
+
+void telemetry_data_original (char *station, char *info, char *output, char *comment) 
+{
+	int n;
+	int seq;
+	char stemp[256];
+	char *next;
+	char *p;
+
+	float araw[T_NUM_ANALOG];
+	int ndp[T_NUM_ANALOG];
+	int draw[T_NUM_DIGITAL];
+
+	struct t_metadata_s *pm;
+
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("\n%s\n\n", info);
+#endif
+
+	pm = t_get_metadata(station);
+	seq = 0;
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  araw[n] = G_UNKNOWN;
+	  ndp[n] = 0;
+	}
+	for (n = 0; n < T_NUM_DIGITAL; n++) {
+	  draw[n] = G_UNKNOWN;
+	}
+
+	if (strncmp(info, "T#", 2) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Error: Information part of telemetry packet must begin with \"#\"\n");
+	  return;	
+	}
+
+/*
+ * Make a copy of the input string because this will alter it.
+ */
+
+	strncpy (stemp, info+2, sizeof(stemp));
+	stemp[sizeof(stemp)-1] = '\0';
+
+	next = stemp;
+	p = strsep(&next,",");
+	seq = atoi(p);
+	n = 0;
+	while ((p = strsep(&next,",")) != NULL) {
+	  if (n < T_NUM_ANALOG) {
+	    if (strlen(p) > 0) {
+	      araw[n] = atof(p);
+	      ndp[n] = t_ndp(p);
+	    }
+	    if (strlen(p) != 3 || araw[n] < 0 || araw[n] > 255 || araw[n] != (int)(araw[n])) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Telemetry analog values should be 3 digit integer values in range of 000 to 255.\n");	      
+	      dw_printf("Some applications might not interpret \"%s\" properly.\n", p);	      
+	    }
+	    n++;
+	  }
+
+	  if (n == T_NUM_ANALOG && next != NULL) {
+	    /* We expect to have 8 digits of 0 and 1. */
+	    /* Anything left over is a comment. */
+
+	    int k;
+
+	    if (strlen(next) < 8) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Expected to find 8 binary digits after \"%s\" for the digital values.\n", p);	      
+	    }
+	    if (strlen(next) > 8) {
+	      strcpy (comment, next+8);
+	      next[8] = '\0';
+	    }
+	    for (k = 0; k < strlen(next); k++) {
+	      if (next[k] == '0') {
+	        draw[k] = 0;
+	      }
+	      else if (next[k] == '1') {
+	        draw[k] = 1;
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf("Found \"%c\" when expecting 0 or 1 for digital value %d.\n", next[k], k+1);	      
+	      }
+ 	    }
+	    n++;
+	  }
+	}
+	if (n < T_NUM_ANALOG+1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Found fewer than expected number of telemetry data values.\n");	
+	}      
+
+/*
+ * Now process the raw data with any metadata available.
+ */
+	
+#if DEBUG1
+	text_color_set(DW_COLOR_DECODED);
+
+	dw_printf ("%d: %.3f %.3f %.3f %.3f %.3f \n", 
+		seq, araw[0], araw[1], araw[2], araw[3], araw[4]);
+
+	dw_printf ("%d %d %d %d %d %d %d %d \"%s\"\n",
+		draw[0], draw[1], draw[2], draw[3], draw[4], draw[5], draw[6], draw[7], comment);
+
+#endif
+
+	t_data_process (pm, seq, araw, ndp, draw, output);
+
+} /* end telemtry_data_original */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        telemetry_data_base91
+ *
+ * Purpose:     Interpret telemetry data in the base 91 compressed format.
+ *
+ * Inputs:	station	- Name of station reporting telemetry.
+ *		cdata 	- Compressed data as character string.
+ *
+ * Outputs:	output	- Telemetry in human readable form.
+ *
+ * Description:	We are expecting from 2 to 7 pairs of base 91 digits.
+ *		The first pair is the sequence number.
+ *		Next we have 1 to 5 analog values.
+ *		If digital values are present, all 5 analog values must be present.	

+ *		
+ *--------------------------------------------------------------------*/
+
+/* Range of digits for Base 91 representation. */
+
+#define B91_MIN '!'
+#define B91_MAX '{'
+#define isdigit91(c) ((c) >= B91_MIN && (c) <= B91_MAX)
+
+
+static int two_base91_to_i (char *c)
+{
+	int result = 0;
+
+	assert (B91_MAX - B91_MIN == 90);
+
+	if (isdigit91(c[0])) {
+	  result = (c[0] - B91_MIN) * 91;
+	}
+	else {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("\"%c\" is not a valid character for base 91 telemetry data.\n", c[0]);
+	}
+
+	if (isdigit91(c[1])) {
+	  result += (c[1] - B91_MIN);
+	}
+	else {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("\"%c\" is not a valid character for base 91 telemetry data.\n", c[1]);
+	}
+	return (result);
+}
+
+void telemetry_data_base91 (char *station, char *cdata, char *output)
+{
+	int n;
+	int seq;
+	char *p;
+
+	float araw[T_NUM_ANALOG];
+	int ndp[T_NUM_ANALOG];
+	int draw[T_NUM_DIGITAL];
+	struct t_metadata_s *pm;
+
+#if DEBUG2
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("\n%s\n\n", cdata);
+#endif
+
+	pm = t_get_metadata(station);
+
+	seq = 0;
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  araw[n] = G_UNKNOWN;
+	  ndp[n] = 0;
+	}
+	for (n = 0; n < T_NUM_DIGITAL; n++) {
+	  draw[n] = G_UNKNOWN;
+	}
+
+	if (strlen(cdata) < 4 || strlen(cdata) > 14 || (strlen(cdata) & 1)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Internal error: Expected even number of 2 to 14 characters but got \"%s\"\n", cdata);
+	  return;	
+	}
+
+	seq = two_base91_to_i (cdata);
+
+	for (n=0, p=cdata+2; n<T_NUM_ANALOG+1 && *p!='\0'; n++,p+=2) {
+	  if (n < T_NUM_ANALOG) {
+	    araw[n] = two_base91_to_i (p);
+	  }
+	  else {
+	    int k;
+	    int b = two_base91_to_i (p);
+	    for (k=0; k<T_NUM_DIGITAL; k++) {
+	      draw[k] = b & 1;
+	      b >>= 1;
+	    }
+	  }
+	}
+
+/*
+ * Now process the raw data with any metadata available.
+ */
+	
+#if DEBUG2
+	text_color_set(DW_COLOR_DECODED);
+
+	dw_printf ("%d: %.3f %.3f %.3f %.3f %.3f \n", 
+		seq, araw[0], araw[1], araw[2], araw[3], araw[4]);
+
+	dw_printf ("%d %d %d %d %d %d %d %d \n",
+		draw[0], draw[1], draw[2], draw[3], draw[4], draw[5], draw[6], draw[7]);
+
+#endif
+
+	t_data_process (pm, seq, araw, ndp, draw, output);
+
+} /* end telemtry_data_base91 */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        telemetry_name_message
+ *
+ * Purpose:     Interpret message with names for analog and digital channels.
+ *
+ * Inputs:	station	- Name of station reporting telemetry.
+ *			  In this case it is the destination for the message,
+ *			  not the sender.
+ *		msg 	- Rest of message after "PARM."
+ *
+ * Outputs:	Stored for future use when data values are received.
+ *
+ * Description:	The first 5 characters of the message are "PARM." and the
+ *		rest is a variable length list of comma separated names.

+ *	
+ *		The original spec has different maximum lengths for different
+ *		fields which we will ignore.
+ *
+ * TBD:		What should we do if some, but not all, names are specified?
+ *		Clear the others or keep the defaults?
+ *	
+ *--------------------------------------------------------------------*/
+
+void telemetry_name_message (char *station, char *msg) 
+{
+	int n;
+	char stemp[256];
+	char *next;
+	char *p;
+	struct t_metadata_s *pm;
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("\n%s\n\n", msg);
+#endif
+
+
+/*
+ * Make a copy of the input string because this will alter it.
+ */
+
+	strncpy (stemp, msg, sizeof(stemp));
+	stemp[sizeof(stemp)-1] = '\0';
+
+	pm = t_get_metadata(station);
+
+	next = stemp;
+
+	n = 0;
+	while ((p = strsep(&next,",")) != NULL) {
+	  if (n < T_NUM_ANALOG + T_NUM_DIGITAL) {
+	    if (strlen(p) > 0 && strcmp(p,"-") != 0) {
+	      strncpy (pm->name[n], p, T_STR_LEN-1);
+	    }
+	    n++;
+	  }
+	}
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("names:\n");
+	for (n = 0; n < T_NUM_ANALOG + T_NUM_DIGITAL; n++) {
+	  dw_printf ("%d=\"%s\"\n", n, pm->name[n]);
+	}
+#endif
+
+} /* end telemetry_name_message */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        telemetry_unit_label_message
+ *
+ * Purpose:     Interpret message with units/labels for analog and digital channels.
+ *
+ * Inputs:	station	- Name of station reporting telemetry.
+ *			  In this case it is the destination for the message,
+ *			  not the sender.
+ *		msg 	- Rest of message after "UNIT."
+ *
+ * Outputs:	Stored for future use when data values are received.
+ *
+ * Description:	The first 5 characters of the message are "UNIT." and the
+ *		rest is a variable length list of comma separated units/labels.

+ *	
+ *		The original spec has different maximum lengths for different
+ *		fields which we will ignore.
+ *
+ *--------------------------------------------------------------------*/
+
+void telemetry_unit_label_message (char *station, char *msg) 
+{
+	int n;
+	char stemp[256];
+	char *next;
+	char *p;
+	struct t_metadata_s *pm;
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("\n%s\n\n", msg);
+#endif
+
+
+/*
+ * Make a copy of the input string because this will alter it.
+ */
+
+	strncpy (stemp, msg, sizeof(stemp));
+	stemp[sizeof(stemp)-1] = '\0';
+
+	pm = t_get_metadata(station);
+
+	next = stemp;
+
+	n = 0;
+	while ((p = strsep(&next,",")) != NULL) {
+	  if (n < T_NUM_ANALOG + T_NUM_DIGITAL) {
+	    if (strlen(p) > 0) {
+	      strncpy (pm->unit[n], p, T_STR_LEN-1);
+	    }
+	    n++;
+	  }
+	}
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("units/labels:\n");
+	for (n = 0; n < T_NUM_ANALOG + T_NUM_DIGITAL; n++) {
+	  dw_printf ("%d=\"%s\"\n", n, pm->unit[n]);
+	}
+#endif
+
+} /* end telemetry_unit_label_message */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        telemetry_coefficents_message
+ *
+ * Purpose:     Interpret message with scaling coefficents for analog channels.
+ *
+ * Inputs:	station	- Name of station reporting telemetry.
+ *			  In this case it is the destination for the message,
+ *			  not the sender.
+ *		msg 	- Rest of message after "EQNS."
+ *
+ * Outputs:	Stored for future use when data values are received.
+ *
+ * Description:	The first 5 characters of the message are "EQNS." and the
+ *		rest is a comma separated list of 15 floating point values.

+ *	
+ *		The spec appears to require all 15 so we will issue an
+ *		error if fewer found.
+ *
+ *--------------------------------------------------------------------*/
+
+void telemetry_coefficents_message (char *station, char *msg) 
+{
+	int n;
+	char stemp[256];
+	char *next;
+	char *p;
+	struct t_metadata_s *pm;
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("\n%s\n\n", msg);
+#endif
+
+
+/*
+ * Make a copy of the input string because this will alter it.
+ */
+
+	strncpy (stemp, msg, sizeof(stemp));
+	stemp[sizeof(stemp)-1] = '\0';
+
+	pm = t_get_metadata(station);
+
+	next = stemp;
+
+	n = 0;
+	while ((p = strsep(&next,",")) != NULL) {
+	  if (n < T_NUM_ANALOG * 3) {
+	    // Keep default (or earlier value) for an empty field.
+	    if (strlen(p) > 0) {
+	      pm->coeff[n/3][n%3] = atof (p);
+	      pm->coeff_ndp[n/3][n%3] = t_ndp (p);
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Equation coefficent position A%d%c is empty.\n", n/3+1, n%3+'a');
+	      dw_printf ("Some applications might not handle this correctly.\n");
+	    }
+	  }
+	  n++;
+	}
+
+	if (n != T_NUM_ANALOG * 3) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Found %d equation coefficents when 15 were expected.\n", n);
+	  dw_printf ("Some applications might not handle this correctly.\n");
+	}
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("coeff:\n");
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  dw_printf ("A%d  a=%.*f  b=%.*f  c=%.*f\n", n+1, 
+			pm->coeff_ndp[n][C_A], pm->coeff[n][C_A], 
+			pm->coeff_ndp[n][C_B], pm->coeff[n][C_B], 
+			pm->coeff_ndp[n][C_C], pm->coeff[n][C_C]);
+	}
+#endif
+
+} /* end telemetry_coefficents_message */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        telemetry_bit_sense_message
+ *
+ * Purpose:     Interpret message with scaling coefficents for analog channels.
+ *
+ * Inputs:	station	- Name of station reporting telemetry.
+ *			  In this case it is the destination for the message,
+ *			  not the sender.
+ *		msg 	- Rest of message after "BITS."
+ *
+ * Outputs:	Stored for future use when data values are received.
+ *
+ * Description:	The first 5 characters of the message are "BITS."
+ *		It should contain eight binary digits for the digital active states.
+ *		Anything left over is the project name or title.
+ *
+ *--------------------------------------------------------------------*/
+
+void telemetry_bit_sense_message (char *station, char *msg) 
+{
+	int n;
+	struct t_metadata_s *pm;
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("\n%s\n\n", msg);
+#endif
+
+	pm = t_get_metadata(station);
+
+	if (strlen(msg) < 8) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("The telemetry bit sense message should have at least 8 characters.\n");
+	}
+
+	for (n = 0; n < T_NUM_DIGITAL && n < strlen(msg); n++) {
+
+	  if (msg[n] == '1') {
+	    pm->sense[n] = 1;
+	  }
+	  else if (msg[n] == '0') {
+	    pm->sense[n] = 0;
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Bit position %d sense value was \"%c\" when 0 or 1 was expected.\n", n+1, msg[n]);
+	  }
+	}
+
+/* Skip comma if first character of comment field. */
+
+	if (msg[n] == ',') n++;
+
+	strncpy (pm->project, msg+n, sizeof(pm->project)-1);
+ 
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("bit sense, project:\n");
+	dw_printf ("%d %d %d %d %d %d %d %d \"%s\"\n", 
+		pm->sense[0],
+		pm->sense[1],
+		pm->sense[2],
+		pm->sense[3],
+		pm->sense[4],
+		pm->sense[5],
+		pm->sense[6],
+		pm->sense[7],
+		pm->project);
+
+
+#endif
+
+} /* end telemetry_bit_sense_message */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        t_data_process
+ *
+ * Purpose:     Interpret telemetry data in the original format.
+ *
+ * Inputs:	pm	- Pointer to metadata.
+ *		seq	- Sequence number.
+ *		araw	- 5 analog raw values.
+ *		ndp	- Number of decimal points for each.
+ *		draw	- 8 digial raw vales.
+ *
+ * Outputs:	output	- Decoded telemetry in human readable format.
+ *
+ * Description:	Process raw data according to any metadata available
+ *		and put into human readable form.	

+ *		
+ *--------------------------------------------------------------------*/
+
+static void fval_to_str (float x, int ndp, char *str)
+{
+	if (x == G_UNKNOWN) {
+	  strcpy (str, "?");
+	}
+	else {
+	  sprintf (str, "%.*f", ndp, x);
+	}
+}
+
+static void ival_to_str (int x, char *str)
+{
+	if (x == G_UNKNOWN) {
+	  strcpy (str, "?");
+	}
+	else {
+	  sprintf (str, "%d", x);
+	}
+}
+
+static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_ANALOG], int ndp[T_NUM_ANALOG], int draw[T_NUM_DIGITAL], char *output) 
+{
+	int n;
+	char stemp[50];
+
+
+	assert (pm != NULL);
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	strcpy (output, "");
+
+	if (strlen(pm->project) > 0) {
+	  strcpy (output, pm->project);
+	  strcat (output, ": ");
+	}
+
+	ival_to_str (seq, stemp);
+	strcat (output, "Seq=");
+	strcat (output, stemp);
+	
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  
+	  // Display all or only defined values?  Only defined for now.
+
+	  if (araw[n] != G_UNKNOWN) {
+	    float fval;
+	    int fndp;
+
+	    strcat (output, ", ");	
+
+	    strcat (output, pm->name[n]);
+	    strcat (output, "=");
+	    
+	    // Scaling and suitable number of decimal places for display.
+
+	    if (araw[n] == G_UNKNOWN) {
+	      fval = G_UNKNOWN;
+	      fndp = 0;
+	    }
+	    else {
+	      int z;
+
+	      fval = pm->coeff[n][C_A] * araw[n] * araw[n] +
+		     pm->coeff[n][C_B] * araw[n] +
+		     pm->coeff[n][C_C];
+	      
+	      z = pm->coeff_ndp[n][C_A] == 0 ? 0 : pm->coeff_ndp[n][C_A] + ndp[n] + ndp[n];
+	      fndp = MAX (z, MAX(pm->coeff_ndp[n][C_B] + ndp[n], pm->coeff_ndp[n][C_C]));
+	    }
+	    fval_to_str (fval, fndp, stemp);
+	    strcat (output, stemp);
+	    if (strlen(pm->unit[n]) > 0) {
+	      strcat (output, " ");
+	      strcat (output, pm->unit[n]);
+	    }
+	    
+	  }
+	}
+
+	for (n = 0; n < T_NUM_DIGITAL; n++) {
+	  
+	  // Display all or only defined values?  Only defined for now.
+
+	  if (draw[n] != G_UNKNOWN) {
+	    int dval;
+
+	    strcat (output, ", ");	
+
+	    strcat (output, pm->name[T_NUM_ANALOG+n]);
+	    strcat (output, "=");
+	    
+	    // Possible inverting for bit sense.
+
+	    if (draw[n] == G_UNKNOWN) {
+	      dval = G_UNKNOWN;
+	    }
+	    else {
+	      dval = draw[n] ^ ! pm->sense[n];
+	    }
+
+	    ival_to_str (dval , stemp);
+
+	    if (strlen(pm->unit[T_NUM_ANALOG+n]) > 0) {
+	      strcat (output, " ");
+	      strcat (output, pm->unit[T_NUM_ANALOG+n]);
+	    }
+	    strcat (output, stemp);
+	    
+	  }
+	}
+
+
+#if DEBUG4
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("%s\n", output);	
+#endif
+
+} /* end t_data_process */
+
+
+#if TEST
+
+int main ( )
+{
+	char result[256];
+	char comment[256];
+
+	strcpy (result, "");
+	strcpy (comment, "");
+
+
+#if DEBUG1
+
+	// From protocol spec.
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001", result, comment);
+
+	// Try adding a comment.
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001Comment,with,commas", result, comment);
+	strcpy (comment, "");
+
+	// Try shortening or omitting parts.
+
+	telemetry_data_original ("WB2OSZ", "T005,199,000,255,073,123,0110", result, comment);
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,0110", result, comment);
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123", result, comment);
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,,123,01101001", result, comment);
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101009", result, comment);
+
+	// Local observation.
+
+	telemetry_data_original ("WB2OSZ", "T#491,4.9,0.3,25.0,0.0,1.0,00000000", result, comment); 
+
+#endif
+
+#if DEBUG2
+
+	// From protocol spec.
+
+	telemetry_data_base91 ("WB2OSZ", "ss11", result);
+	dw_printf ("expect 7544: 1472 above.\n");
+
+	telemetry_data_base91 ("WB2OSZ", "ss11223344{{!\"", result);
+	dw_printf ("expect 7544: 1472, 1564, 1656, 1748, 8280, 10000000 above.\n");
+
+	// Error cases.  Should not happen in practice because function
+	// should be called only with valid data that matches the pattern.
+
+	telemetry_data_base91 ("WB2OSZ", "ss11223344{{!\"x", result);
+	telemetry_data_base91 ("WB2OSZ", "ss1", result);
+	telemetry_data_base91 ("WB2OSZ", "ss11223344{{!", result);
+	telemetry_data_base91 ("WB2OSZ", "s |1", result);
+
+#endif
+
+#if DEBUG3
+
+	telemetry_name_message ("N0QBF-11", "Battery,Btemp,ATemp,Pres,Alt,Camra,Chut,Sun,10m,ATV");
+
+	telemetry_unit_label_message ("N0QBF-11", "v/100,deg.F,deg.F,Mbar,Kft,Click,OPEN,on,on,hi");
+
+	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,2,3");
+
+	// Error if less than 15 or empty field.
+	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,2");
+	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,,3");
+
+	telemetry_bit_sense_message ("N0QBF-11", "10110000,N0QBF's Big Balloon");
+
+	// Too few and invalid digits.
+	telemetry_bit_sense_message ("N0QBF-11", "1011000");
+	telemetry_bit_sense_message ("N0QBF-11", "10110008");
+
+#endif
+
+	telemetry_coefficents_message ("M0XER-3", "0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0");
+	telemetry_bit_sense_message ("M0XER-3", "11111111,10mW research balloon");
+	telemetry_name_message ("M0XER-3", "Vbat,Vsolar,Temp,Sat");
+	telemetry_unit_label_message ("M0XER-3", "V,V,C,,m");
+
+	telemetry_data_base91 ("M0XER-3", "DyR.&^<A!.", result);
+	telemetry_data_base91 ("M0XER-3", "cNOv'C?=!-", result);
+	telemetry_data_base91 ("M0XER-3", "n0RS(:>b!+", result);
+	telemetry_data_base91 ("M0XER-3", "x&G=!(8s!,", result);
+
+
+	exit (0);
+}
+
+/*
+	A more complete test can be performed by placing the following
+	in a text file and feeding it into the "decode_aprs" utility.
+
+2E0TOY>APRS::M0XER-3  :BITS.11111111,10mW research balloon
+2E0TOY>APRS::M0XER-3  :PARM.Vbat,Vsolar,Temp,Sat
+2E0TOY>APRS::M0XER-3  :EQNS.0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0
+2E0TOY>APRS::M0XER-3  :UNIT.V,V,C,,m
+M0XER-3>APRS63,WIDE2-1:!//Bap'.ZGO JHAE/A=042496|E at Q0%i;5!-|
+M0XER-3>APRS63,WIDE2-1:!/4\;u/)K$O J]YD/A=041216|h`RY(1>q!(|
+M0XER-3>APRS63,WIDE2-1:!/23*f/R$UO Jf'x/A=041600|rxR_'J>+!(|
+
+	The interpretation should look something like this:
+	10mW research balloon: Seq=3307, Vbat=4.383 V, Vsolar=0.436 V, Temp=-34.6 C, Sat=12
+*/
+
+#endif
+
+/* end telemetry.c */
diff --git a/telemetry.h b/telemetry.h
new file mode 100755
index 0000000..9ec0aa9
--- /dev/null
+++ b/telemetry.h
@@ -0,0 +1,15 @@
+
+
+/* telemetry.h */
+
+void telemetry_data_original (char *station, char *info, char *output, char *comment);
+ 
+void telemetry_data_base91 (char *station, char *cdata, char *output);
+ 
+void telemetry_name_message (char *station, char *msg);
+ 
+void telemetry_unit_label_message (char *station, char *msg);
+
+void telemetry_coefficents_message (char *station, char *msg);
+
+void telemetry_bit_sense_message (char *station, char *msg);
diff --git a/textcolor.c b/textcolor.c
index 8d3cf52..6fe418f 100755
--- a/textcolor.c
+++ b/textcolor.c
@@ -118,7 +118,33 @@ static const char dark_green[]	= "\e[0;32m" "\e[5;47m";
 static const char clear_eos[]	= "\e[0J";
 
 
-#else 	/* Linux */
+#elif __arm__ 	/* Linux on Raspberry Pi or similar */
+
+
+/* We need "blink" (5) rather than the */
+/* expected bright/bold (1) to get bright white background. */
+/* Makes no sense but I stumbled across that somewhere. */
+
+static const char background_white[] = "\e[5;47m";
+
+/* Whenever a dark color is used, the */
+/* background is reset and needs to be set again. */
+
+static const char black[]	= "\e[0;30m" "\e[5;47m";
+static const char red[] 	= "\e[1;31m";
+static const char green[] 	= "\e[1;32m";
+static const char yellow[] 	= "\e[1;33m";
+static const char blue[] 	= "\e[1;34m";
+static const char magenta[] 	= "\e[1;35m";
+static const char cyan[] 	= "\e[1;36m";
+static const char dark_green[]	= "\e[0;32m" "\e[5;47m";
+
+/* Clear from cursor to end of screen. */
+
+static const char clear_eos[]	= "\e[0J";
+
+
+#else 	/* Other Linux */
 
 static const char background_white[] = "\e[47;1m";
 
diff --git a/tocalls.txt b/tocalls.txt
index a44bf2b..083df31 100755
--- a/tocalls.txt
+++ b/tocalls.txt
@@ -1,6 +1,10 @@
-APRS TO-CALL VERSION NUMBERS                            18 Dec 2013
+APRS TO-CALL VERSION NUMBERS                            02 Sep 2014
 -------------------------------------------------------------------
                                                              WB4APR
+
+02 Sep 14 added APSTMx for W7QO's Balloon trackers
+21 Aug 14 added APSMSx for Paul Defrusne's SMS gateway
+11 Aug 14 added APCWP8 for John GM7HHB, WinphoneAPRS
 18 Dec 13 added APZWKR for GM1WKR NetSked application
 22 Oct 13 added APFIxx for APRS.FI OH7LZB, Hessu
 23 Aug 13 added APOxxx for OSCAR satellites for AMSAT-LU by LU9DO
@@ -56,6 +60,7 @@ a TOCALL number series:
       APCLEY  EYTraker GPRS/GSM tracker by ZS6EY
       APCLWX  EYWeather GPRS/GSM WX station by ZS6EY
       APCLEZ  Telit EZ10 GSM application ZS6CEY
+      APCWP8  John GM7HHB, WinphoneAPRS
       APCYxx  Cybiko applications
  APD  APD4xx  UP4DAR platform
       APDDxx  DV-RPTR Modem and Control Center Software 
@@ -133,11 +138,13 @@ a TOCALL number series:
       APRTLM  used in MIM's and Mic-lites, etc
       APRtfc  APRStraffic
       APRSTx  APRStt (Touch tone)
- APS  APRS+SA, etc
+ APS  APSxxx  APRS+SA, etc
       APSARx  ZL4FOX's SARTRACK
       APSCxx  aprsc APRS-IS core server (OH7LZB, OH2MQK)
       APSK63  APRS Messenger -over-PSK63
       APSK25  APRS Messenger GMSK-250
+      APSMSx  Paul Defrusne's SMS gateway
+      APSTMx  for W7QO's Balloon trackers
  APT  APTIGR  TigerTrack
       APTTxx  Tiny Track
       APT2xx  Tiny Track II
diff --git a/utm/README.txt b/utm/README.txt
index 0f7a041..2036cd5 100755
--- a/utm/README.txt
+++ b/utm/README.txt
@@ -1,8 +1,12 @@
 
-Most of the files in this directory copied from
+The other files in this directory were copied from
 
 http://www.gpsy.com/gpsinfo/geotoutm/
 
+See attachments to message from Chuck Gantz.
+There is no obvious copyright on the source code.
+The links in the message are no longer valid.
+
 
 A few minor modifications were made:
 
diff --git a/version.h b/version.h
index f9cc894..d79cb3a 100755
--- a/version.h
+++ b/version.h
@@ -1,7 +1,7 @@
 
-/* Dire Wolf version 1.0 */
+/* Dire Wolf version 1.1 */
 
 #define APP_TOCALL "APDW"
 
 #define MAJOR_VERSION 1
-#define MINOR_VERSION 0
+#define MINOR_VERSION 1
diff --git a/xmit.c b/xmit.c
index d4222e0..3a4da1d 100755
--- a/xmit.c
+++ b/xmit.c
@@ -1,7 +1,7 @@
 //
 //    This file is part of Dire Wolf, an amateur radio packet TNC.
 //
-//    Copyright (C) 2011,2013  John Langner, WB2OSZ
+//    Copyright (C) 2011,2013,2014  John Langner, WB2OSZ
 //
 //    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
@@ -110,14 +110,21 @@ static int xmit_bits_per_sec[MAX_CHANS];	/* Data transmission rate. */
 					/* this case but could be different with other */
 					/* modulation techniques. */
 
+static int g_debug_xmit_packet;		/* print packet in hexadecimal form for debugging. */
+
+
 
 #define BITS_TO_MS(b,ch) (((b)*1000)/xmit_bits_per_sec[(ch)])
 
 #define MS_TO_BITS(ms,ch) (((ms)*xmit_bits_per_sec[(ch)])/1000)
 
 
-
+#if __WIN32__
+static unsigned __stdcall xmit_thread (void *arg);
+#else
 static void * xmit_thread (void *arg);
+#endif
+
 static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist);
 
 
@@ -142,7 +149,7 @@ static int wait_for_clear_channel (int channel, int nowait, int slotttime, int p
 
 
 
-void xmit_init (struct audio_s *p_modem)
+void xmit_init (struct audio_s *p_modem, int debug_xmit_packet)
 {
 	int j;
 #if __WIN32__
@@ -159,6 +166,8 @@ void xmit_init (struct audio_s *p_modem)
 	dw_printf ("xmit_init ( ... )\n");
 #endif
 
+	g_debug_xmit_packet = debug_xmit_packet;
+
 /*
  * Push to Talk (PTT) control.
  */
@@ -202,7 +211,7 @@ void xmit_init (struct audio_s *p_modem)
 // underrun on the audio output device.
 
 #if __WIN32__
-	xmit_th = _beginthreadex (NULL, 0, xmit_thread, NULL, 0, NULL);
+	xmit_th = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, NULL, 0, NULL);
 	if (xmit_th == NULL) {
 	  text_color_set(DW_COLOR_ERROR);
 	  dw_printf ("Could not create xmit thread\n");
@@ -377,7 +386,11 @@ void xmit_set_txtail (int channel, int value)
  *
  *--------------------------------------------------------------------*/
 
+#if __WIN32__
+static unsigned __stdcall xmit_thread (void *arg)
+#else
 static void * xmit_thread (void *arg)
+#endif
 {
 	packet_t pp;
     	unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
@@ -455,14 +468,25 @@ static void * xmit_thread (void *arg)
 	          text_color_set(DW_COLOR_XMIT);
 	          dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
 	          dw_printf ("%s", stemp);			/* stations followed by : */
-	          ax25_safe_print ((char *)pinfo, info_len, 0);
+	          ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
 	          dw_printf ("\n");
 
-		  flen = ax25_pack (pp, fbuf);
-		  assert (flen <= sizeof(fbuf));
+/* Optional hex dump of packet. */
+
+		  if (g_debug_xmit_packet) {
+
+	            text_color_set(DW_COLOR_DEBUG);
+	            dw_printf ("------\n");
+		    ax25_hex_dump (pp);
+    	            dw_printf ("------\n");
+		  }
+
 /*
  * Transmit the frame.
- */		
+ * 1.1J fixed order.
+ */	
+		  flen = ax25_pack (pp, fbuf);
+		  assert (flen >= 1 && flen <= sizeof(fbuf));
 		  num_bits += hdlc_send_frame (c, fbuf, flen);
 		  numframe = 1;
 		  ax25_delete (pp);
@@ -483,14 +507,21 @@ static void * xmit_thread (void *arg)
 	            text_color_set(DW_COLOR_XMIT);
 	            dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
 	            dw_printf ("%s", stemp);			/* stations followed by : */
-	            ax25_safe_print ((char *)pinfo, info_len, 0);
+	            ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
 	            dw_printf ("\n");
 
-		    flen = ax25_pack (pp, fbuf);
-		    assert (flen <= sizeof(fbuf));
+		    if (g_debug_xmit_packet) {
+	              text_color_set(DW_COLOR_DEBUG);
+	              dw_printf ("------\n");
+		      ax25_hex_dump (pp);
+    	              dw_printf ("------\n");
+		    }
+
 /*
  * Transmit the frame.
  */		
+		    flen = ax25_pack (pp, fbuf);
+		    assert (flen >= 1 && flen <= sizeof(fbuf));
 		    num_bits += hdlc_send_frame (c, fbuf, flen);
 		    numframe++;
 		    ax25_delete (pp);
@@ -559,7 +590,7 @@ static void * xmit_thread (void *arg)
 	          dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
 
 	          dw_printf ("%s", stemp);			/* stations followed by : */
-	          ax25_safe_print ((char *)pinfo, info_len, 0);
+	          ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
 	          dw_printf ("\n");
 		  ax25_delete (pp);
 
diff --git a/xmit.h b/xmit.h
index 7b98ddb..7042128 100755
--- a/xmit.h
+++ b/xmit.h
@@ -6,7 +6,7 @@
 #include "audio.h"	/* for struct audio_s */
 
 
-extern void xmit_init (struct audio_s *p_modem);
+extern void xmit_init (struct audio_s *p_modem, int debug_xmit_packet);
 
 extern void xmit_set_txdelay (int channel, int value);
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/direwolf.git



More information about the pkg-hamradio-commits mailing list