[hamradio-commits] [direwolf] 01/02: Imported Upstream version 1.3

Iain R. Learmonth irl at moszumanska.debian.org
Wed Sep 7 12:03:08 UTC 2016


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

irl pushed a commit to branch master
in repository direwolf.

commit 6e91e572576a5393f2956e4366417950ccda442d
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Wed Sep 7 13:01:40 2016 +0100

    Imported Upstream version 1.3
---
 .gitattributes                                     |   41 +
 .gitignore                                         |  107 +
 APRStt-Implementation-Notes.pdf                    |  Bin 1007433 -> 0 bytes
 CHANGES.md                                         |  329 +
 CHANGES.txt                                        |  314 -
 LICENSE-dire-wolf.txt                              |  562 +-
 LICENSE-other.txt                                  |    8 +-
 Makefile                                           |   11 +-
 Makefile.linux                                     |  541 +-
 Makefile.macosx                                    |  620 ++
 Makefile.win                                       |  999 ++-
 README.md                                          |   96 +
 Raspberry-Pi-APRS-Tracker.pdf                      |  Bin 717451 -> 0 bytes
 Raspberry-Pi-APRS.pdf                              |  Bin 1558740 -> 0 bytes
 User-Guide.pdf                                     |  Bin 3139411 -> 0 bytes
 aclients.c                                         | 1694 ++--
 aprs_tt.c                                          | 3581 ++++----
 aprs_tt.h                                          |  309 +-
 atest.c                                            | 1482 ++--
 audio.c                                            | 2823 +++---
 audio.h                                            |  664 +-
 audio_portaudio.c                                  | 1326 +++
 audio_stats.c                                      |  178 +
 audio_stats.h                                      |    7 +
 audio_win.c                                        | 2311 +++--
 ax25_pad.c                                         | 4635 +++++-----
 ax25_pad.h                                         |  759 +-
 beacon.c                                           | 1731 ++--
 beacon.h                                           |   12 +-
 config.c                                           | 7885 +++++++++--------
 config.h                                           |  322 +-
 decode_aprs.c                                      | 9047 ++++++++++----------
 decode_aprs.h                                      |  236 +-
 dedupe.c                                           |  497 +-
 dedupe.h                                           |   20 +-
 demod.c                                            | 1753 ++--
 demod.h                                            |   32 +-
 demod_9600.c                                       | 1044 ++-
 demod_9600.h                                       |   50 +-
 demod_afsk.c                                       | 2301 ++---
 demod_afsk.h                                       |   16 +-
 digipeater.c                                       | 1860 ++--
 digipeater.h                                       |  142 +-
 direwolf.c                                         | 2149 ++---
 direwolf.conf                                      |  458 -
 direwolf.h                                         |  390 +-
 direwolf.spec                                      |    0
 direwolf.txt                                       | 1064 +--
 dlq.c                                              | 1276 +--
 dlq.h                                              |   59 +-
 ...er-APRS-Packet-Demodulator-Part-1-1200-baud.pdf |  Bin
 ...er-APRS-Packet-Demodulator-Part-2-9600-baud.pdf |  Bin
 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf    |  Bin 0 -> 461593 bytes
 doc/APRS-Telemetry-Toolkit.pdf                     |  Bin 0 -> 1253277 bytes
 doc/APRStt-Implementation-Notes.pdf                |  Bin 0 -> 1502085 bytes
 doc/APRStt-Listening-Example.pdf                   |  Bin 0 -> 789455 bytes
 doc/APRStt-interface-for-SARTrack.pdf              |  Bin 0 -> 1123525 bytes
 doc/README.md                                      |   81 +
 doc/Raspberry-Pi-APRS-Tracker.pdf                  |  Bin 0 -> 732449 bytes
 doc/Raspberry-Pi-APRS.pdf                          |  Bin 0 -> 1873515 bytes
 doc/Raspberry-Pi-SDR-IGate.pdf                     |  Bin 0 -> 543002 bytes
 doc/User-Guide.pdf                                 |  Bin 0 -> 3506061 bytes
 doc/WA8LMF-TNC-Test-CD-Results.pdf                 |  Bin 0 -> 488843 bytes
 dsp.c                                              |  504 +-
 dsp.h                                              |   18 +-
 dtime_now.c                                        |  113 +-
 dtime_now.h                                        |    4 +-
 dtmf.c                                             |  922 +-
 dtmf.h                                             |   24 +-
 dw-icon.ico                                        |  Bin
 dw-icon.png                                        |  Bin
 dw-icon.rc                                         |    0
 dw-start.sh                                        |   39 +-
 dwespeak.bat                                       |   14 +-
 dwespeak.sh                                        |    0
 dwgps.c                                            |  687 +-
 dwgps.h                                            |   76 +-
 dwgpsd.c                                           |  440 +
 dwgpsd.h                                           |   22 +
 dwgpsnmea.c                                        |  800 ++
 dwgpsnmea.h                                        |   28 +
 encode_aprs.c                                      | 1713 ++--
 encode_aprs.h                                      |   32 +-
 fcs_calc.c                                         |  216 +-
 fcs_calc.h                                         |   22 +-
 fsk_demod_agc.h                                    |    4 +-
 fsk_demod_state.h                                  |  527 +-
 fsk_filters.h                                      |   14 +-
 fsk_gen_filter.h                                   |   28 +-
 gen_packets.c                                      | 1565 ++--
 gen_tone.c                                         |  971 ++-
 gen_tone.h                                         |   33 +-
 direwolf.txt => generic.conf                       | 1105 +--
 geotranz/README-FIRST.txt                          |   10 +-
 geotranz/error_string.c                            |  262 +-
 geotranz/error_string.h                            |   14 +-
 geotranz/mgrs.c                                    | 2694 +++---
 geotranz/mgrs.h                                    |  506 +-
 geotranz/polarst.c                                 | 1046 +--
 geotranz/polarst.h                                 |  404 +-
 geotranz/readme.txt                                |   82 +-
 geotranz/releasenotes.txt                          |  930 +-
 geotranz/tranmerc.c                                | 1236 +--
 geotranz/tranmerc.h                                |  418 +-
 geotranz/ups.c                                     |  604 +-
 geotranz/ups.h                                     |  350 +-
 geotranz/usng.c                                    | 2512 +++---
 geotranz/usng.h                                    |  504 +-
 geotranz/utm.c                                     |  708 +-
 geotranz/utm.h                                     |  356 +-
 grm_sym.h                                          | 1002 +--
 hdlc_rec.c                                         | 1297 +--
 hdlc_rec.h                                         |   55 +-
 hdlc_rec2.c                                        | 2360 +++--
 hdlc_rec2.h                                        |  145 +-
 hdlc_send.c                                        |  432 +-
 hdlc_send.h                                        |   20 +-
 igate.c                                            | 3608 ++++----
 igate.h                                            |  154 +-
 kiss.c                                             | 1924 ++---
 kiss.h                                             |   42 +-
 kiss_frame.c                                       | 1332 +--
 kiss_frame.h                                       |  106 +-
 kissnet.c                                          | 1375 +--
 kissnet.h                                          |   42 +-
 latlong.c                                          | 1583 ++--
 latlong.h                                          |   46 +-
 ll2utm.c                                           |  226 +-
 log.c                                              |  738 +-
 log.h                                              |   32 +-
 log2gpx.c                                          | 1084 +--
 man1/aclients.1                                    |    7 +-
 man1/direwolf.1                                    |   16 +-
 man1/gen_packets.1                                 |    8 +-
 mgn_icon.h                                         |  534 +-
 misc/README-dire-wolf.txt                          |   43 +-
 misc/strcasestr.c                                  |  128 +-
 misc/strlcat.c                                     |  127 +
 misc/strlcpy.c                                     |  119 +
 misc/strsep.c                                      |   44 +-
 misc/strtok_r.c                                    |  204 +-
 morse.c                                            |  924 +-
 morse.h                                            |    8 +
 multi_modem.c                                      | 1333 ++-
 multi_modem.h                                      |   39 +-
 nmea.c                                             | 1502 +---
 nmea.h                                             |   38 +-
 pfilter.c                                          | 2210 ++---
 pfilter.h                                          |    6 +-
 ptt.c                                              | 2233 ++---
 ptt.h                                              |   51 +-
 rdq.c                                              |  748 +-
 rdq.h                                              |   56 +-
 recv.c                                             |  655 +-
 recv.h                                             |   10 +-
 redecode.c                                         |  507 +-
 redecode.h                                         |   30 +-
 regex/COPYING                                      |    0
 regex/COPYING.LIB                                  |  510 --
 regex/INSTALL                                      |    0
 regex/LICENSES                                     |    0
 regex/NEWS                                         |    0
 regex/README                                       |    0
 regex/README-dire-wolf.txt                         |   12 +-
 regex/re_comp.h                                    |    0
 regex/regcomp.c                                    |    0
 regex/regex.c                                      |    0
 regex/regex.h                                      |    0
 regex/regex_internal.c                             |    0
 regex/regex_internal.h                             |    0
 regex/regexec.c                                    |    0
 rpack.h                                            |  188 +-
 rrbb.c                                             | 1055 ++-
 rrbb.h                                             |  182 +-
 sdr.conf                                           |   30 +
 search_sdks.sh                                     |  109 +
 serial_port.c                                      |  451 +
 serial_port.h                                      |   27 +
 server.c                                           | 3262 +++----
 server.h                                           |   41 +-
 symbols-new.txt                                    |  946 +-
 symbols.c                                          | 1964 +++--
 symbols.h                                          |   33 +-
 symbolsX.txt                                       |  771 +-
 telemetry-toolkit/telem-balloon.conf               |   55 +
 telemetry-toolkit/telem-balloon.pl                 |   43 +
 telemetry-toolkit/telem-bits.pl                    |   33 +
 telemetry-toolkit/telem-data.pl                    |   31 +
 telemetry-toolkit/telem-data91.pl                  |   65 +
 telemetry-toolkit/telem-eqns.pl                    |   28 +
 telemetry-toolkit/telem-m0xer-3.txt                |    7 +
 telemetry-toolkit/telem-parm.pl                    |   27 +
 telemetry-toolkit/telem-seq.sh                     |    7 +
 telemetry-toolkit/telem-unit.pl                    |   27 +
 telemetry-toolkit/telem-volts.conf                 |   28 +
 telemetry-toolkit/telem-volts.py                   |   36 +
 telemetry.c                                        | 2460 +++---
 telemetry.h                                        |   30 +-
 textcolor.c                                        |  775 +-
 textcolor.h                                        |  116 +-
 tocalls.txt                                        |  442 +-
 tq.c                                               | 1118 +--
 tq.h                                               |   70 +-
 tt_text.c                                          | 2958 ++++---
 tt_text.h                                          |   63 +-
 tt_user.c                                          | 2016 +++--
 tt_user.h                                          |   27 +-
 ttcalc.c                                           | 1089 +--
 utm2ll.c                                           |  288 +-
 version.h                                          |   16 +-
 walk96.c                                           |  205 +
 xid.c                                              | 1018 +--
 xmit.c                                             | 2054 ++---
 xmit.h                                             |   50 +-
 214 files changed, 70799 insertions(+), 61526 deletions(-)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9320eae
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,41 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs     diff=csharp
+
+# Standard to msysgit
+*.doc	 diff=astextplain
+*.DOC	 diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot  diff=astextplain
+*.DOT  diff=astextplain
+*.pdf  diff=astextplain
+*.PDF	 diff=astextplain
+*.rtf	 diff=astextplain
+*.RTF	 diff=astextplain
+
+# My rules to remove any doubt
+
+*.c 		text
+*.cpp		text
+*.h		text
+*.pl		text
+*.py		text
+*.sh		text
+*.txt		text
+*.desktop	text
+*.conf		text
+*.rc		text
+*.spec		text
+*.bat		text
+*.1		text
+*.md		text
+COPYING		text
+Makefile*	text
+README*		text
+
+*.ico		binary
+*.png		binary
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9f3c376
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,107 @@
+# Custom
+*.docx
+z*
+*.log
+*bak*
+*~
+*.xlsx
+*.stackdump
+*.wav
+
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Binaries, other build results
+
+aclients
+atest
+decode_aprs
+direwolf
+gen_fff
+gen_packets
+ll2utm
+log2gpx
+text2tt
+tt2text
+ttcalc
+utm2ll
+
+direwolf.conf
+fsk_fast_filter.h
+direwolf.desktop
+
+
+# =========================
+# Operating System Files
+# =========================
+
+# OSX
+# =========================
+
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Windows
+# =========================
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
diff --git a/APRStt-Implementation-Notes.pdf b/APRStt-Implementation-Notes.pdf
deleted file mode 100755
index 6477a52..0000000
Binary files a/APRStt-Implementation-Notes.pdf and /dev/null differ
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000..9b31ec3
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,329 @@
+
+# Revision History #
+
+----------
+
+## Version 1.3  -- May 2016 ##
+
+This is the same as the 1.3 beta test version with a few minor documentation updates.  If you are already using 1.3 beta test, there is no need to install this.
+
+### New Features: ###
+
+- Support for Mac OS X. 
+
+- Many APRStt enhancements including: Morse code and speech responses to to APRStt tone sequences, new 5 digit callsign suffix abbreviation, 
+position ambiguity for latitude and longitude in object reports
+
+- APRS Telemetry Toolkit.
+ 
+- GPS Tracker beacons are now available for the Windows version.  Previously this was only in the Linux version.
+
+- SATgate mode for IGate.  Packets heard directly are delayed before being sent
+to the Internet Server.   This favors digipeated packets because the original
+arrives later and gets dropped if there are duplicates.
+
+- Added support for hamlib. This provides more flexible options for PTT control.
+
+- Implemented AGW network protocol 'M' message for sending UNPROTO information without digipeater path.
+
+
+- A list of all symbols available can be obtained with the -S
+command line option.
+
+- Command line option "-a n" to print audio device statistics each n seconds.  Previously this was always each 100 seconds on Linux and not available on Windows.
+
+### Bugs Fixed: ###
+
+
+
+- Fixed several cases where crashes were caused by unexpected packet contents:
+
+ - When receiving packet with unexpected form of GPS NMEA sentence.
+
+ - When receiving packet with comment of a few hundred characters.
+ 
+ - Address in path, from Internet server, more than 9 characters.
+
+- "INTERNAL ERROR: dlq_append NULL packet pointer." when using PASSALL.
+
+- In Mac OSX version:  Assertion failed: (adev[a].inbuf_size_in_bytes >= 100 &&   adev[a].inbuf_size_in_bytes <= 32768), function audio_get, file audio_portaudio.c, line 917.
+
+- Tracker beacons were not always updating the location properly.
+
+- AGW network protocol now works properly for big-endian processors
+such as PowerPC or MIPS.
+
+- Packet filtering treated telemetry metadata as messages rather than telemetry.
+
+----------
+
+## Version 1.2  -- June 2015 ##
+
+### New Features ###
+
+- Improved decoder performance.  
+Over 1000 error-free frames decoded from WA8LMF TNC Test CD.  
+See "A-Better-APRS-Packet-Demodulator.pdf" for details.
+
+- Up to 3 soundcards and 6 radio channels can be handled at the same time.
+
+- New framework for applications which listen for Touch Tone commands
+and respond with voice.  A sample calculator application is included
+as a starting point for building more interesting applications.  
+For example, if it hears the DTMF sequence "2*3*4#" it will respond 
+with the spoken words "Twenty Four."  
+
+- Reduced latency for transfers to/from soundcards.
+
+- More accurate transmit PTT timing.
+
+- Packet filtering for digipeater and IGate.
+
+- New command line -q (quiet) option to suppress some types of output.
+
+- Attempted fixing of corrupted bits now works for 9600 baud.
+
+- Implemented AGW network protocol 'y' message so applications can
+throttle generation of packets when sending a large file.
+
+- When using serial port RTS/DTR to activate transmitter, the two
+control lines can now be driven with opposite polarity as required
+by some interfaces.
+
+- Data Carrier Detect (DCD) can be sent to an output line (just 
+like PTT) to activate a carrier detect light.
+
+- Linux "man" pages for on-line documentation.
+
+- AGWPORT and KISSPORT can be set to 0 to disable the interfaces.
+
+- APRStt gateway enhancements:  MGRS/USNG coordinates, new APRStt3
+format call, satellite grid squares.
+
+
+### Bugs fixed ###
+
+- Fixed "gen_packets" so it now handles user-specified messages correctly.
+
+- Under some circumstances PTT would be held on long after the transmit
+audio was finished.
+
+
+
+### Known problems ###
+
+- Sometimes writes to a pseudo terminal will block causing the received
+frame processing thread to hang.   The first thing you will notice is that
+received frames are not being printed.  After a while this message will appear:
+
+  Received frame queue is out of control. Length=... Reader thread is probably 
+  frozen.  This can be caused by using a pseudo terminal (direwolf -p) where 
+  another application is not reading the frames from the other side.
+
+-----------
+
+## Version 1.1  -- December 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.
+
+- It is still highly recommended, but no longer mandatory, that
+beaconing be enabled for digipeating to work.
+
+* 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.
+
+### 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.0a	-- May 2014 ##
+
+### Bug fixed ###
+
+- Beacons sent directly to IGate server had incorrect source address.
+
+-----------
+
+## Version 1.0 -- May 2014 ##
+
+### New Features ###
+
+- Received audio can be obtained with a UDP socket or stdin.
+This can be used to take audio from software defined radios
+such as rtl_fm or gqrx.
+
+- 9600 baud data rate.
+
+- New PBEACON and OBEACON configuration options. Previously
+it was necessary to handcraft beacons. 
+
+- Less CPU power required for 300 baud.  This is important
+if you want to run a bunch of decoders at the same time
+to tolerate off-frequency HF SSB signals.
+
+- 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.
+
+
+-----------
+
+## Version 0.9 --November 2013 ##
+
+### New Features ###
+
+- Selection of non-default audio device for Linux ALSA.
+
+- Simplified audio device set up for Raspberry Pi.
+
+- GPIO lines can be used for PTT on suitable Linux systems.
+
+- Improved 1200 baud decoder.
+
+- Multiple decoders per channel to tolerate HF SSB signals off frequency.
+
+- Command line option "-t 0" to disable text colors.
+
+- APRStt macros which allow short numeric only touch tone
+sequences to be processed as much longer predefined sequences.
+
+
+### Bugs Fixed ###
+
+- Now works on 64 bit target.
+
+### New Restriction for Windows version ###
+
+- Minimum processor is now Pentium 3 or equivalent or later.
+It's possible to run on something older but you will need
+to rebuild it from source.
+
+
+-----------
+
+## Version 0.8	-- August 2013 ##
+
+### New Features ###
+
+- Internet Gateway (IGate) including IPv6 support.
+
+- Compatibility with YAAC.
+
+- Preemptive digipeating option.
+
+- KISS TNC should now work with connected AX.25 protocols
+(e.g. AX25 for Linux), not just APRS.
+
+
+----------
+
+## Version 0.7	-- March 2013 ##
+
+### New Features: ###
+
+- Added APRStt gateway capability.  For details, see:  
+**APRStt-Implementation-Notes.pdf**
+
+
+-----------
+
+## Version 0.6 --	February 2013 ##
+
+### New Features ###
+
+- Improved performance of AFSK demodulator.
+Now decodes 965 frames from Track 2 of WA8LMF's TNC Test CD.
+
+- KISS protocol now available thru a TCP socket.
+Default port is 8001.
+Change it with KISSPORT option in configuration file.
+
+- Ability to salvage frames with bad FCS.
+See section mentioning "bad apple" in the user guide.
+Default of fixing 1 bit works well.  
+Fixing more bits not recommended because there is a high
+probability of occasional corrupted data getting thru.
+
+- Added AGW "monitor" format messages.
+Now compatible with APRS-TW for telemetry.
+
+
+### Known Problem ###
+
+- The Linux (but not Cygwin) version eventually hangs if nothing is
+reading from the KISS pseudo terminal.  Some operating system
+queue fills up, the application write blocks, and decoding stops.
+
+
+### Workaround ###
+
+- If another application is not using the serial KISS interface,
+run this in another window:
+
+	tail -f /tmp/kisstnc
+
+-----------
+
+## Version 0.5 -- March 2012 ##
+
+- More error checking and messages for invalid APRS data.
+
+-----------
+
+## Version 0.4 -- September 2011 ##
+
+- First general availability.
+
diff --git a/CHANGES.txt b/CHANGES.txt
deleted file mode 100644
index 8a9f0d9..0000000
--- a/CHANGES.txt
+++ /dev/null
@@ -1,314 +0,0 @@
-----------------
-Revision history
-----------------
-
-
-
------------
-Version 1.2  -- June 2015
------------
-
-* New Features:
-
-Improved decoder performance.  
-Over 1000 error-free frames decoded from WA8LMF TNC Test CD.  
-See "A-Better-APRS-Packet-Demodulator.pdf" for details.
-
-Up to 3 soundcards and 6 radio channels can be handled at the same time.
-
-New framework for applications which listen for Touch Tone commands
-and respond with voice.  A sample calculator application is included
-as a starting point for building more interesting applications.  
-For example, if it hears the DTMF sequence "2*3*4#" it will respond 
-with the spoken words "Twenty Four."  
-
-Reduced latency for transfers to/from soundcards.
-
-More accurate transmit PTT timing.
-
-Packet filtering for digipeater and IGate.
-
-New command line -q (quiet) option to suppress some types of output.
-
-Attempted fixing of corrupted bits now works for 9600 baud.
-
-Implemented AGW network protocol 'y' message so applications can
-throttle generation of packets when sending a large file.
-
-When using serial port RTS/DTR to activate transmitter, the two
-control lines can now be driven with opposite polarity as required
-by some interfaces.
-
-Data Carrier Detect (DCD) can be sent to an output line (just 
-like PTT) to activate a carrier detect light.
-
-Linux "man" pages for on-line documentation.
-
-AGWPORT and KISSPORT can be set to 0 to disable the interfaces.
-
-APRStt gateway enhancements:  MGRS/USNG coordinates, new APRStt3
-format call, satellite grid squares.
-
-
-
-* Bugs fixed:
-
-Fixed "gen_packets" so it now handles user-specified messages correctly.
-
-Under some circumstances PTT would be held on long after the transmit
-audio was finished.
-
-
-
-* Known problems:
-
-Sometimes writes to a pseudo terminal will block causing the received
-frame processing thread to hang.   The first thing you will notice is that
-received frames are not being printed.  After a while this message will appear:
-Received frame queue is out of control. Length=... Reader thread is probably 
-frozen.  This can be caused by using a pseudo terminal (direwolf -p) where 
-another application is not reading the frames from the other side.
-
-
-
------------
-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
------------
-
-* Bug fix:
-
-Beacons sent directly to IGate server had incorrect source address.
-
-
-
------------
-Version 1.0	May 2014
------------
-
-* New Features:
-
-Received audio can be obtained with a UDP socket or stdin.
-This can be used to take audio from software defined radios
-such as rtl_fm or gqrx.
-
-9600 baud data rate.
-
-New PBEACON and OBEACON configuration options. Previously
-it was necessary to handcraft beacons. 
-
-Less CPU power required for 300 baud.  This is important
-if you want to run a bunch of decoders at the same time
-to tolerate off-frequency HF SSB signals.
-
-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.
-
-
-
-
------------
-Version 0.9	November 2013
------------
-
-* New Features:
-
-Selection of non-default audio device for Linux ALSA.
-
-Simplified audio device set up for Raspberry Pi.
-
-GPIO lines can be used for PTT on suitable Linux systems.
-
-Improved 1200 baud decoder.
-
-Multiple decoders per channel to tolerate HF SSB signals off frequency.
-
-Command line option "-t 0" to disable text colors.
-
-APRStt macros which allow short numeric only touch tone
-sequences to be processed as much longer predefined sequences.
-
-
-
-* Bugs Fixed:
-
-Now works on 64 bit target.
-
-
-
-* New Restriction for Windows version:
-
-Minimum processor is now Pentium 3 or equivalent or later.
-It's possible to run on something older but you will need
-to rebuild it from source.
-
-
-
-
------------
-Version 0.8	August 2013
------------
-
-* New Features:
-
-Internet Gateway (IGate) including IPv6 support.
-
-Compatibility with YAAC.
-
-Preemptive digipeating option.
-
-KISS TNC should now work with connected AX.25 protocols
-(e.g. AX25 for Linux), not just APRS.
-
-
-
------------
-Version 0.7	March 2013
------------
-
-* New Features:
-
-Added APRStt gateway capability.  For details, see:
-
-APRStt-Implementation-Notes.pdf
-
-
-
-
------------
-Version 0.6	February 2013
------------
-
-
-* New Features:
-
-Improved performance of AFSK demodulator.
-Now decodes 965 frames from Track 2 of WA8LMF's TNC Test CD.
-
-KISS protocol now available thru a TCP socket.
-Default port is 8001.
-Change it with KISSPORT option in configuration file.
-
-Ability to salvage frames with bad FCS.
-See section mentioning "bad apple" in the user guide.
-Default of fixing 1 bit works well.  
-Fixing more bits not recommended because there is a high
-probability of occasional corrupted data getting thru.
-
-Added AGW "monitor" format messages.
-Now compatible with APRS-TW for telemetry.
-
-
-* Bugs Fixed:
-
-None.
-
-
-
-* Known Problem:
-
-The Linux (but not Cygwin) version eventually hangs if nothing is
-reading from the KISS pseudo terminal.  Some operating system
-queue fills up, the application write blocks, and decoding stops.
-
-
-* Workaround:
-
-If another application is not using the serial KISS interface,
-run this in another window:
-
-	tail -f /tmp/kisstnc
-
-
------------
-Version 0.5	March 2012
------------
-
-
-More error checking and messages for invalid APRS data.
-
-
------------
-Version 0.4	September 2011
------------
-
-First general availability.
-
diff --git a/LICENSE-dire-wolf.txt b/LICENSE-dire-wolf.txt
index 3a51aab..b8b347f 100644
--- a/LICENSE-dire-wolf.txt
+++ b/LICENSE-dire-wolf.txt
@@ -1,281 +1,281 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
diff --git a/LICENSE-other.txt b/LICENSE-other.txt
index 1027d22..c4ec456 100644
--- a/LICENSE-other.txt
+++ b/LICENSE-other.txt
@@ -1,5 +1,5 @@
-The Windows version of Dire Wolf contains additional
-open source covered by BSD, GPL, and other licenses.
-
-See "regex" and "misc" subdirectories in the source
+The Windows version of Dire Wolf contains additional
+open source covered by BSD, GPL, and other licenses.
+
+See "regex" and "misc" subdirectories in the source
 distribution for more details.
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 6800756..0ae5394 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,16 @@
 # Select proper Makefile for operating system.
 # The Windows version is built with the help of Cygwin. 
 
+# In my case, I see CYGWIN_NT-6.1-WOW so we don't check for 
+# equal to some value.   Your mileage my vary.
+
 win := $(shell uname | grep CYGWIN)
+dar := $(shell uname | grep Darwin)
+
 ifneq ($(win),)
-include Makefile.win
+   include Makefile.win
+else ifeq ($(dar),Darwin)
+   include Makefile.macosx
 else
-include Makefile.linux
+   include Makefile.linux
 endif
diff --git a/Makefile.linux b/Makefile.linux
index 0a3537e..be9bd59 100644
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -2,7 +2,9 @@
 # Makefile for Linux version of Dire Wolf.
 #
 
-all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.desktop 
+APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
+
+all :  $(APPS) direwolf.desktop direwolf.conf
 	@echo " "
 	@echo "Next step - install with:"
 	@echo " "
@@ -12,6 +14,9 @@ all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx
 CC := gcc
 CFLAGS := -O3 -pthread -Igeotranz
 
+LDFLAGS := -lm -lpthread -lrt
+
+
 
 #
 # The DSP filters spend a lot of time spinning around in little
@@ -184,24 +189,32 @@ endif
 #
 # If you are planning to distribute the binary version to other 
 # people (in some ham radio software collection, RPM, or DEB package), 
-# avoid # fine tuning it for your particular computer.  It could
+# avoid fine tuning it for your particular computer.  It could
 # cause compatibility issues for those with older computers.
 #
 
 
-#CFLAGS += -D_FORTIFY_SOURCE
-
 # If you want to use OSS (for FreeBSD, OpenBSD) instead of
 # ALSA (for Linux), comment out (or remove) the two lines below.
 
 CFLAGS += -DUSE_ALSA
-LDLIBS += -lasound
+LDFLAGS += -lasound
 
 
-# Uncomment following lines to enable GPS interface & tracker function.
+# Enable GPS if header file is present.
+# Finding libgps.so* is more difficult because it
+# is in different places on different operating systems.
 
-#CFLAGS += -DENABLE_GPS
-#LDLIBS += -lgps
+enable_gpsd := $(wildcard /usr/include/gps.h)
+ifneq ($(enable_gpsd),)
+CFLAGS += -DENABLE_GPSD
+LDFLAGS += -lgps
+endif
+
+
+# Uncomment following lines to enable hamlib support.
+#CFLAGS += -DUSE_HAMLIB
+#LDFLAGS += -lhamlib
 
 
 # Name of current directory.
@@ -210,18 +223,28 @@ LDLIBS += -lasound
 z := $(notdir ${CURDIR})
 
 
-# Main application.
+
+# --------------------------------  Main application  -----------------------------------------
+
+
 
 direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \
 		hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \
 		fcs_calc.o ax25_pad.o \
 		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 pfilter.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 nmea.o log.o telemetry.o dtime_now.o \
-		geotranz.a
-	$(CC) $(CFLAGS) -o $@ $^ -lpthread -lrt $(LDLIBS) -lm
-
+		gen_tone.o audio.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
+		ptt.o beacon.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 nmea.o serial_port.o log.o telemetry.o \
+		dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o \
+		misc.a geotranz.a
+	$(CC) -o $@ $^ $(LDFLAGS)
+ifneq ($(enable_gpsd),)
+	@echo " "
+	@echo "This includes support for gpsd."
+else
+	@echo " "
+	@echo "This does NOT include support for gpsd."
+endif
 
 # Optimization for slow processors.
 
@@ -231,10 +254,100 @@ demod_afsk.o : fsk_fast_filter.h
 
 
 fsk_fast_filter.h : demod_afsk.c
-	$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm
+	$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c $(LDFLAGS)
 	./gen_fff > fsk_fast_filter.h
 
 
+#
+# The destination field is often used to identify the manufacturer/model.
+# These are not hardcoded into Dire Wolf.  Instead they are read from
+# a file called tocalls.txt at application start up time.
+#
+# The original permanent symbols are built in but the "new" symbols,
+# using overlays, are often updated.  These are also read from files.
+#
+# You can obtain an updated copy by typing "make tocalls-symbols".
+# This is not part of the normal build process.  You have to do this explicitly.
+#
+# The locations below appear to be the most recent.
+# The copy at http://www.aprs.org/tocalls.txt is out of date.
+#
+
+.PHONY: tocalls-symbols
+tocalls-symbols :
+	cp tocalls.txt tocalls.txt~
+	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
+	-diff -Z tocalls.txt~ tocalls.txt
+	cp symbols-new.txt symbols-new.txt~
+	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
+	-diff -Z symbols-new.txt~ symbols-new.txt
+	cp symbolsX.txt symbolsX.txt~
+	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
+	-diff -Z symbolsX.txt~ symbolsX.txt
+
+
+# ---------------------------------------- Other utilities included ------------------------------
+
+
+# Separate application to decode raw data.
+
+decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a
+	$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS)
+
+
+
+# Convert between text and touch tone representation.
+
+text2tt : tt_text.c misc.a
+	$(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ $(LDFLAGS)
+
+tt2text : tt_text.c misc.a
+	$(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ $(LDFLAGS)
+
+
+# Convert between Latitude/Longitude and UTM coordinates.
+
+ll2utm : ll2utm.c geotranz.a textcolor.o misc.a
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+utm2ll : utm2ll.c geotranz.a textcolor.o misc.a
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+
+# Convert from log file to GPX.
+
+log2gpx : log2gpx.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+
+# Test application to generate sound.
+
+gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c misc.a
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+# Unit test for AFSK demodulator
+
+atest : atest.c demod.o demod_afsk.o demod_9600.o \
+		dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
+		fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \
+		dwgps.o dwgpsd.o serial_port.o telemetry.o latlong.o symbols.o tt_text.o textcolor.o \
+		misc.a
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+
+# Multiple AGWPE network or serial port clients to test TNCs side by side.
+
+aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -g -o $@ $^ 
+
+
+# Touch Tone to Speech sample application.
+
+ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a
+	$(CC) $(CFLAGS) -g -o $@ $^ 
+
+
+# -----------------------------------------  Libraries  --------------------------------------------
 
 # UTM, USNG, MGRS conversions.
 
@@ -263,6 +376,37 @@ utm.o : geotranz/utm.c
 	$(CC) $(CFLAGS) -c -o $@ $^
 
 
+# Provide our own copy of strlcpy and strlcat because they are not included with Linux.
+# We don't need the others in that same directory.
+
+misc.a : strlcpy.o strlcat.o
+	ar -cr $@ $^	
+ 
+strlcpy.o : misc/strlcpy.c
+	$(CC) $(CFLAGS) -I. -c -o $@ $^
+
+strlcat.o : misc/strlcat.c
+	$(CC) $(CFLAGS) -I. -c -o $@ $^
+
+
+
+# -------------------------------------  Installation  ----------------------------------
+
+
+
+# Generate apprpriate sample configuration file for this platform.
+# Originally, there was one sample for all platforms.  It got too cluttered
+# and confusing saying, this is for windows, and this is for Linux, and this ...
+# Trying to maintain 3 different versions in parallel is error prone.
+# We now have a single generic version which can be used to generate
+# the various platform specific versions.
+
+# generic.conf should be checked into source control.
+# direwolf.conf should NOT.  It is generated when compiling on the target platform.
+
+direwolf.conf : generic.conf
+	egrep '^C|^L' generic.conf | cut -c2-999 > direwolf.conf
+
 
 # Where should we install it?
 
@@ -270,19 +414,23 @@ utm.o : geotranz/utm.c
 # from source, that is not a standard part of the operating system,
 # should go in /usr/local/bin.
 
-# However, if you are preparing a "binary" RPM or DEB package, the 
+# However, if you are preparing a "binary" DEB or RPM package, the
 # installation location should be /usr/bin.
 
 # This is a step in the right direction but not sufficient to use /usr instead.
+# Eventually I'd like to have targets here to build the .DEB and .RPM packages.
 
 INSTALLDIR := /usr/local
 
+# Command to "install" to system directories.  Use "ginstall" for Mac.
+
+INSTALL=install
 
 # direwolf.desktop was previously handcrafted for the Raspberry Pi.
 # It was hardcoded with lxterminal, /home/pi, and so on.
 # In version 1.2, try to customize this to match other situations better.
 
-# TODO1.2:  Test this better.
+# TODO:  Test this better.
 
 
 direwolf.desktop :
@@ -305,62 +453,102 @@ endif
 	@echo 'Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25' >> $@
 
 
-# Optional installation into /usr/local/...
+# Installation into /usr/local/...
 # Needs to be run as root or with sudo.
-# TODO: Review file locations.
-
-install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \
-		tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
-	install direwolf $(INSTALLDIR)/bin
-	install decode_aprs $(INSTALLDIR)/bin
-	install text2tt $(INSTALLDIR)/bin
-	install tt2text $(INSTALLDIR)/bin
-	install ll2utm $(INSTALLDIR)/bin
-	install utm2ll $(INSTALLDIR)/bin
-	install aclients $(INSTALLDIR)/bin
-	install log2gpx $(INSTALLDIR)/bin
-	install gen_packets $(INSTALLDIR)/bin
-	install atest $(INSTALLDIR)/bin
-	install ttcalc $(INSTALLDIR)/bin
-	install dwespeak.sh $(INSTALLDIR)/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 $(INSTALLDIR)/share/doc/direwolf/CHANGES.txt
-	install -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
-	install -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt
-	install -D --mode=644 User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf
-	install -D --mode=644 Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf
-	install -D --mode=644 Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
-	install -D --mode=644 APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
-	install -D --mode=644 A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
-	install -D --mode=644 A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
-	install -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1
-	install -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1
-	install -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1
-	install -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1
-	install -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1
-	install -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1
-	install -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1
-	install -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1
-	install -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1
-	install -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1
+
+
+.PHONY: install
+install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
+#
+# Applications, not installed with package manager, normally go in /usr/local/bin.
+# /usr/bin is used instead when installing from .DEB or .RPM package.
+#
+	$(INSTALL) direwolf $(INSTALLDIR)/bin
+	$(INSTALL) decode_aprs $(INSTALLDIR)/bin
+	$(INSTALL) text2tt $(INSTALLDIR)/bin
+	$(INSTALL) tt2text $(INSTALLDIR)/bin
+	$(INSTALL) ll2utm $(INSTALLDIR)/bin
+	$(INSTALL) utm2ll $(INSTALLDIR)/bin
+	$(INSTALL) aclients $(INSTALLDIR)/bin
+	$(INSTALL) log2gpx $(INSTALLDIR)/bin
+	$(INSTALL) gen_packets $(INSTALLDIR)/bin
+	$(INSTALL) atest $(INSTALLDIR)/bin
+	$(INSTALL) ttcalc $(INSTALLDIR)/bin
+	$(INSTALL) dwespeak.sh $(INSTALLDIR)/bin
+#
+# Telemetry Toolkit executables.   Other .conf and .txt files will go into doc directory.
+#
+	$(INSTALL) telemetry-toolkit/telem-balloon.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-bits.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-data.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-data91.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-eqns.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-parm.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-seq.sh $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-unit.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-volts.py $(INSTALLDIR)/bin
+#
+# Misc. data such as "tocall" to system mapping.
+#
+	$(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
+#
+# Documentation.  Various plain text files and PDF.
+#
+	$(INSTALL) -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md
+	$(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
+	$(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt
+#
+# ./README.md is an overview for the project main page.
+# doc/README.md contains an overview of the PDF file contents and is more useful here.
+#
+	$(INSTALL) -D --mode=644 doc/README.md $(INSTALLDIR)/share/doc/direwolf/README.md
+	$(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf
+	$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf
+	$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
+	$(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf
+	$(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
+	$(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf
+	$(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf
+	$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
+	$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
+#
+# Various sample config and other files go into examples under the doc directory.
+# When building from source, these can be put in home directory with "make install-conf".
+# When installed from .DEB or .RPM package, the user will need to copy these to
+# the home directory or other desired location.
+#
+	$(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/examples/direwolf.conf
+	$(INSTALL) -D --mode=644 dw-start.sh $(INSTALLDIR)/share/doc/direwolf/examples/dw-start.sh
+	$(INSTALL) -D --mode=644 sdr.conf $(INSTALLDIR)/share/doc/direwolf/examples/sdr.conf
+	$(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt
+	$(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-balloon.conf
+	$(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-volts.conf
+#
+# "man" pages
+#
+	$(INSTALL) -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1
+	$(INSTALL) -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1
+	$(INSTALL) -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1
+	$(INSTALL) -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1
+	$(INSTALL) -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1
+	$(INSTALL) -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1
+	$(INSTALL) -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1
+	$(INSTALL) -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1
+	$(INSTALL) -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1
+	$(INSTALL) -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1
+#
 	@echo " "
-	@echo "If this is your first install, not an upgrade, type this"
-	@echo "to put a copy of the sample configuration file in your home directory:"
+	@echo "If this is your first install, not an upgrade, type this to put a copy"
+	@echo "of the sample configuration file (direwolf.conf) in your home directory:"
 	@echo " "
 	@echo "        make install-conf"
 	@echo " "
 
 
-# TODO:  Should we put the sample direwolf.conf file somewhere like
-# /usr/share/doc/direwolf/examples or /etc/ax25 and add that to the 
-# end of the search path list?
-# That would make it easy to see user customizations compared to the
-# latest sample.
-
 # These would be done as ordinary user.
 
 # The Raspberry Pi has ~/Desktop but Ubuntu does not.
@@ -371,6 +559,9 @@ install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx ge
 .PHONY: install-conf
 install-conf : direwolf.conf
 	cp direwolf.conf ~
+	cp sdr.conf ~
+	cp telemetry-toolkit/telem-m0xer-3.txt ~
+	cp telemetry-toolkit/telem-*.conf ~
 ifneq ($(wildcard $(HOME)/Desktop),)
 	@echo " "
 	@echo "This will add a desktop icon on some systems:"
@@ -387,181 +578,191 @@ install-rpi : dw-start.sh
 
 
 
-# Separate application to decode raw data.
+# ----------------------------------  Automated Smoke Test  --------------------------------
 
-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
 
 
+# Combine some unit tests into a single regression sanity check.
 
-# Convert between text and touch tone representation.
 
-text2tt : tt_text.c
-	$(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c
+check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600
 
-tt2text : tt_text.c
-	$(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c
+# Can we encode and decode at popular data rates?
 
+check-modem1200 : gen_packets atest
+	./gen_packets -n 100 -o /tmp/test1.wav
+	./atest -F0 -PE -L70 -G71 /tmp/test1.wav
+	./atest -F1 -PE -L73 -G75 /tmp/test1.wav
+	#rm /tmp/test1.wav
 
-# Convert between Latitude/Longitude and UTM coordinates.
+check-modem300 : gen_packets atest
+	./gen_packets -B300 -n 100 -o /tmp/test3.wav
+	./atest -B300 -F0 -L68 -G69 /tmp/test3.wav
+	./atest -B300 -F1 -L73 -G75 /tmp/test3.wav
+	rm /tmp/test3.wav
 
-ll2utm : ll2utm.c geotranz.a
-	$(CC) $(CFLAGS) -o $@ $^ -lm
+check-modem9600 : gen_packets atest
+	./gen_packets -B9600 -n 100 -o /tmp/test9.wav
+	./atest -B9600 -F0 -L57 -G59 /tmp/test9.wav
+	./atest -B9600 -F1 -L66 -G67 /tmp/test9.wav
+	rm /tmp/test9.wav
 
-utm2ll : utm2ll.c geotranz.a
-	$(CC) $(CFLAGS) -o $@ $^ -lm
 
 
-# Convert from log file to GPX.
+# Unit test for inner digipeater algorithm
 
-log2gpx : log2gpx.c 
-	$(CC) $(CFLAGS) -o $@ $^ -lm
+.PHONY : dtest
+dtest : digipeater.c dedupe.c \
+		pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \
+		decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a
+	$(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS)
+	./dtest
+	rm dtest
 
 
-# Test application to generate sound.
+# Unit test for APRStt tone sequence parsing.
 
-gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c textcolor.c dsp.c
-	$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm
+.PHONY : ttest
+ttest : aprs_tt.c tt_text.c latlong.o textcolor.o misc.a geotranz.a misc.a
+	$(CC) $(CFLAGS) -DTT_MAIN  -o $@ $^ $(LDFLAGS)
+	./ttest
+	rm ttest
 
-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 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
+# Unit test for APRStt tone sequence / text conversions.
 
+.PHONY: tttexttest
+tttexttest : tt_text.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -DTTT_TEST -o $@ $^ $(LDFLAGS)
+	./tttexttest
+	rm tttexttest
 
-# Unit test for AFSK demodulator
 
+# Unit test for Packet Filtering.
 
-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
+.PHONY: pftest
+pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a 
+	$(CC) $(CFLAGS) -DPFTEST -o $@ $^ $(LDFLAGS)
+	./pftest
+	rm pftest
 
-# Unit test for inner digipeater algorithm
+# Unit test for telemetry decoding.
 
+.PHONY: tlmtest
+tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a
+	$(CC) $(CFLAGS) -DTEST -o $@ $^ $(LDFLAGS)
+	./tlmtest
+	rm tlmtest
 
-dtest : digipeater.c pfilter.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c
-	$(CC) $(CFLAGS) -DTEST -o $@ $^
-	./dtest
+# Unit test for location coordinate conversion.
 
+.PHONY: lltest
+lltest : latlong.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -DLLTEST -o $@ $^ $(LDFLAGS)
+	./lltest
+	rm lltest
 
-# Unit test for APRStt.
+# Unit test for encoding position & object report.
 
-ttest : aprs_tt.c tt_text.c  latlong.c misc.a  geotranz.a
-	$(CC) $(CFLAGS) -DTT_MAIN  -o $@ $^
+.PHONY: enctest
+enctest : encode_aprs.c latlong.c textcolor.c misc.a
+	$(CC) $(CFLAGS) -DEN_MAIN -o $@ $^ $(LDFLAGS)
+	./enctest
+	rm enctest
 
 
-# Unit test for IGate
+# Unit test for KISS encapsulation.
 
+.PHONY: kisstest
+kisstest : kiss_frame.c
+	$(CC) $(CFLAGS) -DKISSTEST -o $@ $^ $(LDFLAGS)
+	./kisstest
+	rm kisstest
 
-itest : igate.c textcolor.c ax25_pad.c fcs_calc.c 
-	$(CC) $(CFLAGS) -DITEST -o $@ $^
-	./itest
 
 
-# Unit test for UDP reception with AFSK demodulator
+#  -----------------------------  Manual tests and experiments  ---------------------------
 
-udptest : udp_test.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
-	$(CC) $(CFLAGS) -o $@ $^ -lm -lrt
-	./udptest
+# These are not included in a normal build.  Might be broken.
 
+# Unit test for IGate
 
-# Unit test for telemetry decoding.
+itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -DITEST -o $@ $^
+	./itest
 
+# Unit test for UDP reception with AFSK demodulator.
+# Temporary during development.  Might not be useful anymore.
 
-etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
-	$(CC) $(CFLAGS) -o $@ $^ -lm -lrt
-	./etest
-	
+udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
+		fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+	./udptest
 
-# Multiple AGWPE network or serial port clients to test TNCs side by side.
+# For demodulator tweaking experiments.
+# Dependencies of demod*.c, rather than .o, are intentional.
 
-aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c
-	$(CC) $(CFLAGS) -g -o $@ $^ 
+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.o hdlc_rec2.o multi_modem.o rrbb.o \
+		fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o latlong.o symbols.o tune.h textcolor.o misc.a
+	$(CC) $(CFLAGS) -o atest $^ $(LDFLAGS)
+	./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
 
 
-# Touch Tone to Speech sample application.
 
-ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o 
-	$(CC) $(CFLAGS) -g -o $@ $^ 
 
 
-depend : $(wildcard *.c)
-	makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
 
+# -------------------------------   Source distribution  ---------------------------------
 
-.PHONY: clean 
-clean :
-	rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc \
-		fsk_fast_filter.h *.o *.a direwolf.desktop
-	echo " " > tune.h
+# probably obsolete and can be removed after move to github.
 
 
-# Package it up for distribution.
 
 .PHONY: dist-src
-dist-src : CHANGES.txt User-Guide.pdf Raspberry-Pi-APRS.pdf \
-		Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf \
-		A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
+dist-src : README.md CHANGES.md 
+		doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \
+		doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \
 		dw-start.sh dwespeak.bat dwespeak.sh \
 		tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
 	rm -f fsk_fast_filter.h
 	echo " " > tune.h
 	rm -f ../$z-src.zip
-	egrep '^C|^L' direwolf.txt | cut -c2-999 > direwolf.conf
 	(cd .. ; zip $z-src.zip \
-		$z/CHANGES.txt \
+		$z/README.md \
+		$z/CHANGES.md \
 		$z/LICENSE* \
-		$z/User-Guide.pdf \
-		$z/Raspberry-Pi-APRS.pdf \
-		$z/Raspberry-Pi-APRS-Tracker.pdf \
-		$z/APRStt-Implementation-Notes.pdf \
-		$z/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
-		$z/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
+		$z/doc/User-Guide.pdf \
+		$z/doc/Raspberry-Pi-APRS.pdf \
+		$z/doc/Raspberry-Pi-APRS-Tracker.pdf \
+		$z/doc/APRStt-Implementation-Notes.pdf \
+		$z/doc/APRS-Telemetry-Toolkit.pdf \
 		$z/Makefile* \
 		$z/*.c $z/*.h \
 		$z/regex/* $z/misc/* $z/geotranz/* \
 		$z/man1/* \
-		$z/direwolf.conf $z/direwolf.txt \
+		$z/generic.conf \
 		$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
 		$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
 		$z/dw-start.sh $z/direwolf.spec \
-		$z/dwespeak.bat $z/dwespeak.sh )
+		$z/dwespeak.bat $z/dwespeak.sh \
+		$z/telemetry-toolkit/* )
 		
 
+# -----------------------------------------------------------------------------------------
 
-#User-Guide.pdf : User-Guide.docx
-#	echo "***** User-Guide.pdf is out of date *****"
-
-#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 *****" 
 
-#A-Better-APRS-Packet-Demodulator.pdf : A-Better-APRS-Packet-Demodulator.docx
-#	echo "***** A-Better-APRS-Packet-Demodulator.pdf is out of date *****"
+.PHONY: clean
+clean :
+	rm -f $(APPS) fsk_fast_filter.h *.o *.a direwolf.desktop
+	echo " " > tune.h
 
 
-#
-# The locations below appear to be the most recent.
-# The copy at http://www.aprs.org/tocalls.txt is out of date.
-#
-
-.PHONY: tocalls-symbols
-tocalls-symbols :
-	cp tocalls.txt tocalls.txt~
-	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
-	diff tocalls.txt~ tocalls.txt
-	cp symbols-new.txt symbols-new.txt~
-	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
-	diff symbols-new.txt~ symbols-new.txt
-	cp symbolsX.txt symbolsX.txt~
-	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
-	diff symbolsX.txt~ symbolsX.txt
+depend : $(wildcard *.c)
+	makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
 
 
 #
diff --git a/Makefile.macosx b/Makefile.macosx
new file mode 100644
index 0000000..c0fc4e7
--- /dev/null
+++ b/Makefile.macosx
@@ -0,0 +1,620 @@
+#
+# Makefile for Macintosh 10.6+ version of Dire Wolf.
+#
+
+# TODO: This is a modified version of Makefile.linux and it
+# has fallen a little behind.  For example, it is missing the check target.
+# It would be more maintainable if we could use a single file for both.
+# The differences are not that great.
+# Maybe the most of the differences could go in to platform specific include
+# files rather than cluttering it up with too many if blocks.
+
+# Changes:
+#
+# 16 Dec 2015
+# 1. Added condition check for gps/gpsd code. Commented out due to 32/64 bit
+#    library issues. Macports gpsd build problem.
+# 2. SDK version checks are now performed by a bash script 'search_sdks.sh'.
+#    This should resolve the varied locations Apple stored the SDKs on the different
+#    Xcode/OS versions. Executing 'make' on the first pass asks the operator
+#    which SDK he/she wishes to use. Executing 'make clean' resets the SDK
+#    selection and operator intervention is once again required. Selected SDK
+#    information resides in a file named './use_this_sdk' in the current working
+#    directory.
+# 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having
+#    a hissy fit. Not check with GCC.
+
+all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.conf
+	@echo " "
+	@echo "Next step install with: "
+	@echo " "
+	@echo "      sudo make install"
+	@echo " "
+	@echo " "
+
+SYS_LIBS :=
+SYS_MIN :=
+#SDK := $(shell find /Developer -maxdepth 1 -type d -name "SDKs")
+#$(info $$SDK = ${SDK})
+#ifeq (${SDK},/Developer/SDKs)
+#	SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.8.sdk")
+#	ifeq (${SDK},/Developer/SDKs/MacOSX10.8.sdk)
+#		SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.8.sdk
+#		SYS_MIN := -mmacosx-version-min=10.8
+#	else
+#		SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.9.sdk")
+#		ifeq (${SDK},/Developer/SDKs/MacOSX10.9.sdk)
+#			SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.9.sdk
+#			SYS_MIN := -mmacosx-version-min=10.9
+#		else
+#			SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.10.sdk")
+#			ifeq (${SDK},/Developer/SDKs/MacOSX10.10.sdk)
+#				SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.10.sdk
+#				SYS_MIN := -mmacosx-version-min=10.10
+#			endif
+#		endif
+#	endif
+#endif
+
+SYS_LIBS := $(shell ./search_sdks.sh)
+EXTRA_CFLAGS :=
+DARWIN_CC := $(shell which clang)
+ifeq (${DARWIN_CC},)
+DARWIN_CC := $(shell which gcc)
+EXTRA_CFLAGS :=
+else
+EXTRA_CFLAGS := -fvectorize -fslp-vectorize -fslp-vectorize-aggressive -pthread
+endif
+
+# Change as required in support of the available libraries
+
+#CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN)
+CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN)
+CFLAGS := -Os -pthread -Igeotranz $(EXTRA_CFLAGS)
+# $(info $$CC is [${CC}])
+
+#
+# The DSP filters spend a lot of time spinning around in little
+# loops multiplying and adding arrays of numbers.  The Intel "SSE"
+# instructions, introduced in 1999 with the Pentium III series,
+# can speed this up considerably.
+#
+# SSE2 instructions, added in 2000, don't seem to offer any advantage.
+#
+#
+# Let's take a look at the effect of the compile options.
+#
+#
+# Times are elapsed time to process Track 2 of the TNC test CD.
+#
+# i.e.   "./atest 02_Track_2.wav"
+# Default demodulator type is new "E" added for version 1.2.
+#
+
+#
+# ---------- x86 (32 bit) ----------
+#
+
+#
+# gcc 4.6.3 running on Ubuntu 12.04.05.
+# Intel(R) Celeron(R) CPU 2.53GHz.  Appears to have only 32 bit instructions.
+# Probably from around 2004 or 2005.
+#
+# When gcc is generating code for a 32 bit x86 target, it assumes the ancient
+# i386 processor.  This is good for portability but bad for performance.
+#
+# The code can run considerably faster by taking advantage of the SSE instructions
+# available in the Pentium 3 or later.
+#
+#       seconds options		comments
+#       ------  -------		--------
+#         524
+#         183	-O2
+#         182	-O3
+#         183	-O3 -ffast-math  	(should be same as -Ofast)
+#         184	-Ofast
+#         189	-O3 -ffast-math -march=pentium
+#         122	-O3 -ffast-math -msse
+#         122	-O3 -ffast-math -march=pentium -msse
+#         121	-O3 -ffast-math -march=pentium3   (this implies -msse)
+#         120	-O3 -ffast-math -march=native
+#
+# Note that "-march=native" is essentially the same as "-march=pentium3."
+#
+
+# If the compiler is generating code for the i386 target, we can
+# get much better results by telling it we have at least a Pentium 3.
+
+CFLAGS += -march=core2 -msse4.1 -std=gnu99
+#CFLAGS += -march=pentium3 -sse
+
+#
+# gcc 4.8.2 running on Ubuntu 14.04.1.
+# Intel Core 2 Duo from around 2007 or 2008.
+#
+# 64 bit target implies that we have SSE and probably even better vector instructions.
+#
+#       seconds options		comments
+#       ------  -------		--------
+#         245
+#          75	-01
+#          72 	-02
+#          71  	-03
+#          73  	-O3 -march=native
+#          42	-O3 -ffast-math
+#          42  	-Ofast			(note below)
+#          40	-O3 -ffast-math -march=native
+#
+#
+# Note that "-Ofast" is a newer option roughly equivalent to "-O3 -ffast-math".
+# I use the longer form because it is compatible with older compilers.
+#
+# Why don't I don't have "-march=native?"
+# Older compilers don't recognize "native" as one of the valid options.
+# One article said it was added with gcc 4.2 but I haven't verified that.
+#
+
+# Add -ffastmath in only if compiler version recognizes it.
+
+useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math)
+ifneq ($(useffast),)
+CFLAGS += -ffast-math
+endif
+
+#
+# You would expect "-march=native" to produce the fastest code.
+# Why don't I use it here?
+#
+#	1. In my benchmarks, above, it has a negligible impact if any at all.
+#	2. Some older versions of gcc don't recognize "native" as a valid choice.
+#	3. Results are less portable.  Not a consideration if you are
+#		building only for your own use but very important for anyone
+#		redistributing a "binary" version.
+#
+# If you are planning to distribute the binary version to other
+# people (in some ham radio software collection, RPM, or DEB package),
+# avoid # fine tuning it for your particular computer.  It could
+# cause compatibility issues for those with older computers.
+#
+
+#CFLAGS += -D_FORTIFY_SOURCE
+
+# Use PortAudio Library
+
+# Force static linking of portaudio if the static library is available.
+PA_LIB_STATIC := $(shell find /opt/local/lib -maxdepth 1 -type f -name "libportaudio.a")
+#$(info $$PA_LIB_STATIC is [${PA_LIB_STATIC}])
+ifeq (${PA_LIB_STATIC},)
+LDLIBS += -L/opt/local/lib -lportaudio
+else
+LDLIBS += /opt/local/lib/libportaudio.a
+endif
+
+# Include libraries portaudio requires.
+LDLIBS += -framework CoreAudio -framework AudioUnit -framework AudioToolbox
+LDLIBS += -framework Foundation -framework CoreServices
+
+CFLAGS += -DUSE_PORTAUDIO -I/opt/local/include
+
+# Uncomment following lines to enable GPS interface & tracker function.
+# Not available for MacOSX (as far as I know).
+# Although MacPorts has gpsd, wonder if it's the same thing. Add the check
+# just in case it works.
+# Well never mind, issue with Macports with 64bit libs ;-( leave the check in
+# until (if ever) Macports fixes the issue.
+
+#GPS_HEADER := $(shell find /opt/local/include -maxdepth 1 -type f -name "gps.h")
+#ifeq (${GPS_HEADER},)
+#GPS_OBJS :=
+#else
+#CFLAGS += -DENABLE_GPSD
+#LDLIBS += -L/opt/local/lib -lgps -lgpsd
+#GPS_OBJS := dwgps.o dwgpsnmea.o dwgpsd.o
+#endif
+
+# Name of current directory.
+# Used to generate zip file name for distribution.
+
+z := $(notdir ${CURDIR})
+
+
+# Main application.
+
+direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \
+		config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o \
+		demod.o digipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
+		encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \
+		geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \
+		kiss.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \
+		nmea.o serial_port.o pfilter.o ptt.o rdq.o recv.o redecode.o rrbb.o server.o \
+		symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xmit.o \
+		dwgps.o dwgpsnmea.o
+	$(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm
+
+
+# Optimization for slow processors.
+
+demod.o : fsk_fast_filter.h
+
+demod_afsk.o : fsk_fast_filter.h
+
+
+fsk_fast_filter.h : demod_afsk.c
+	$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm
+	./gen_fff > fsk_fast_filter.h
+
+# UTM, USNG, MGRS conversions.
+
+geotranz.a : error_string.o  mgrs.o  polarst.o  tranmerc.o  ups.o  usng.o  utm.o
+	ar -cr $@ $^
+
+error_string.o : geotranz/error_string.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+mgrs.o : geotranz/mgrs.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+polarst.o : geotranz/polarst.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+tranmerc.o : geotranz/tranmerc.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+ups.o : geotranz/ups.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+usng.o : geotranz/usng.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+utm.o : geotranz/utm.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+
+
+# Generate apprpriate sample configuration file for this platform.
+
+direwolf.conf : generic.conf
+	egrep '^C|^M' generic.conf | cut -c2-999 > direwolf.conf
+
+
+# Where should we install it?
+
+# My understanding, of the convention, is that something you compile
+# from source, that is not a standard part of the operating system,
+# should go in /usr/local/bin.
+
+# This is a step in the right direction but not sufficient to use /usr instead.
+
+INSTALLDIR := /usr/local
+
+# TODO:  Test this better.
+
+# Optional installation into /usr/local/...
+# Needs to be run as root or with sudo.
+# TODO: Review file locations.
+
+# Command to "install" to system directories.  "install" for Linux.  "ginstall" for Mac.
+
+INSTALL=ginstall
+
+.PHONY: install
+install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
+#
+# Applications, not installed with package manager, normally go in /usr/local/bin.
+# /usr/bin is used instead when installing from .DEB or .RPM package.
+#
+	$(INSTALL) direwolf $(INSTALLDIR)/bin
+	$(INSTALL) decode_aprs $(INSTALLDIR)/bin
+	$(INSTALL) text2tt $(INSTALLDIR)/bin
+	$(INSTALL) tt2text $(INSTALLDIR)/bin
+	$(INSTALL) ll2utm $(INSTALLDIR)/bin
+	$(INSTALL) utm2ll $(INSTALLDIR)/bin
+	$(INSTALL) aclients $(INSTALLDIR)/bin
+	$(INSTALL) log2gpx $(INSTALLDIR)/bin
+	$(INSTALL) gen_packets $(INSTALLDIR)/bin
+	$(INSTALL) atest $(INSTALLDIR)/bin
+	$(INSTALL) ttcalc $(INSTALLDIR)/bin
+	$(INSTALL) dwespeak.sh $(INSTALLDIR)/bin
+#
+# Telemetry Toolkit executables.   Other .conf and .txt files will go into doc directory.
+#
+	$(INSTALL) telemetry-toolkit/telem-balloon.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-bits.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-data.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-data91.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-eqns.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-parm.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-unit.pl $(INSTALLDIR)/bin
+	$(INSTALL) telemetry-toolkit/telem-volts.py $(INSTALLDIR)/bin
+#
+# Misc. data such as "tocall" to system mapping.
+#
+	$(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
+#
+# Documentation.  Various plain text files and PDF.
+#
+	$(INSTALL) -D --mode=644 README.md $(INSTALLDIR)/share/doc/direwolf/README.md
+	$(INSTALL) -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md
+	$(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
+	$(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt
+#
+	$(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf
+	$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf
+	$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
+	$(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf
+	$(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
+	$(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf
+	$(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf
+	$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
+	$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
+#
+# Sample config files also go into the doc directory.
+# When building from source, these can be put in home directory with "make install-conf".
+# When installed from .DEB or .RPM package, the user will need to copy these to
+# the home directory or other desired location.
+# Someone suggested that these could go into an "examples" subdirectory under doc.
+#
+	$(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/direwolf.conf
+	$(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/telem-m0xer-3.txt
+	$(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/telem-balloon.conf
+	$(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(INSTALLDIR)/share/doc/direwolf/telem-volts.conf
+#
+# "man" pages
+#
+	$(INSTALL) -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1
+	$(INSTALL) -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1
+	$(INSTALL) -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1
+	$(INSTALL) -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1
+	$(INSTALL) -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1
+	$(INSTALL) -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1
+	$(INSTALL) -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1
+	$(INSTALL) -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1
+	$(INSTALL) -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1
+	$(INSTALL) -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1
+#
+	@echo " "
+	@echo "If this is your first install, not an upgrade, type this to put a copy"
+	@echo "of the sample configuration file (direwolf.conf) in your home directory:"
+	@echo " "
+	@echo "        make install-conf"
+	@echo " "
+
+
+# TODO:  Should we put the sample direwolf.conf file somewhere like
+# /usr/share/doc/direwolf/examples and add that to the
+# end of the search path list?
+# That would make it easy to see user customizations compared to the
+# latest sample.
+
+# These would be done as ordinary user.
+
+# The Raspberry Pi has ~/Desktop but Ubuntu does not.
+
+# TODO: Handle Linux variations correctly.
+
+
+.PHONY: install-conf
+install-conf : direwolf.conf
+	cp direwolf.conf ~
+	cp telemetry-toolkit/telem-m0xer-3.txt ~
+	cp telemetry-toolkit/telem-*.conf ~
+
+
+# Separate application to decode raw data.
+
+decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o
+	$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm
+
+# Convert between text and touch tone representation.
+
+text2tt : tt_text.c
+	$(CC) $(CFLAGS) -DENC_MAIN -o $@ $^
+
+tt2text : tt_text.c
+	$(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^
+
+
+# Convert between Latitude/Longitude and UTM coordinates.
+
+ll2utm : ll2utm.c geotranz.a
+	$(CC) $(CFLAGS) -o $@ $^ -lm
+
+utm2ll : utm2ll.c geotranz.a
+	$(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 morse.c textcolor.c dsp.c
+	$(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 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
+
+
+# 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 dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c
+	$(CC) $(CFLAGS) -o $@ $^ -lm
+#atest : atest.c fsk_fast_filter.h 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 dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c
+#	$(CC) $(CFLAGS) -o $@ $^ -lm
+
+# Unit test for inner digipeater algorithm
+
+
+dtest : digipeater.c pfilter.o ax25_pad.o dedupe.o fcs_calc.o tq.o textcolor.o \
+		decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o
+	$(CC) $(CFLAGS) -DTEST -o $@ $^
+	./dtest
+
+
+# Unit test for APRStt.
+
+ttest : aprs_tt.c tt_text.c  latlong.c geotranz.a
+	$(CC) $(CFLAGS) -DTT_MAIN  -o $@ $^
+
+
+# Unit test for IGate
+
+
+itest : igate.c textcolor.c ax25_pad.c fcs_calc.c
+	$(CC) $(CFLAGS) -DITEST -o $@ $^
+	./itest
+
+
+# Unit test for UDP reception with AFSK demodulator
+
+udptest : udp_test.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
+	$(CC) $(CFLAGS) -o $@ $^ -lm
+	./udptest
+
+
+# Unit test for telemetry decoding.
+
+
+tlmtest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c
+	$(CC) $(CFLAGS) -o $@ $^ -lm
+	./tlmtest
+
+
+# Multiple AGWPE network or serial port clients to test TNCs side by side.
+
+aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c
+	$(CC) $(CFLAGS) -g -o $@ $^
+
+
+# Touch Tone to Speech sample application.
+
+ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o
+	$(CC) $(CFLAGS) -g -o $@ $^
+
+
+depend : $(wildcard *.c)
+	makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
+
+
+.PHONY: clean
+clean :
+	rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc \
+		fsk_fast_filter.h *.o *.a use_this_sdk
+	echo " " > tune.h
+
+
+.PHONY: dist-mac
+dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \
+		tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png
+	rm -f ../direwolf_dist_bin.zip
+	(cd .. ; zip direwolf_dist_bin.zip \
+	$(INSTALLDIR)/bin/direwolf \
+	$(INSTALLDIR)/bin/decode_aprs \
+	$(INSTALLDIR)/bin/text2tt \
+	$(INSTALLDIR)/bin/tt2text \
+	$(INSTALLDIR)/bin/ll2utm \
+	$(INSTALLDIR)/bin/utm2ll \
+	$(INSTALLDIR)/bin/aclients \
+	$(INSTALLDIR)/bin/log2gpx \
+	$(INSTALLDIR)/bin/gen_packets \
+	$(INSTALLDIR)/bin/atest \
+	$(INSTALLDIR)/bin/ttcalc \
+	$(INSTALLDIR)/bin/dwespeak.sh \
+	$(INSTALLDIR)/share/direwolf/tocalls.txt \
+	$(INSTALLDIR)/share/direwolf/config/direwolf.conf \
+	$(INSTALLDIR)/share/direwolf/symbols-new.txt \
+	$(INSTALLDIR)/share/direwolf/symbolsX.txt \
+	$(INSTALLDIR)/share/direwolf/dw-icon.png \
+	$(INSTALLDIR)/share/doc/direwolf/README.md \
+	$(INSTALLDIR)/share/doc/direwolf/CHANGES.md \
+	$(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt \
+	$(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt \
+	$(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf \
+	$(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf \
+	$(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf \
+	$(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf \
+	$(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf \
+	$(INSTALLDIR)/man/man1/aclients.1 \
+	$(INSTALLDIR)/man/man1/atest.1 \
+	$(INSTALLDIR)/man/man1/decode_aprs.1 \
+	$(INSTALLDIR)/man/man1/direwolf.1 \
+	$(INSTALLDIR)/man/man1/gen_packets.1 \
+	$(INSTALLDIR)/man/man1/ll2utm.1 \
+	$(INSTALLDIR)/man/man1/log2gpx.1 \
+	$(INSTALLDIR)/man/man1/text2tt.1 \
+	$(INSTALLDIR)/man/man1/tt2text.1 \
+	$(INSTALLDIR)/man/man1/utm2ll.1 \
+	)
+
+# Package it up for distribution.
+
+.PHONY: dist-src
+dist-src : README.md CHANGES.md \
+		doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \
+		doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \
+		dw-start.sh dwespeak.bat dwespeak.sh \
+		tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
+	rm -f fsk_fast_filter.h
+	echo " " > tune.h
+	rm -f ../$z-src.zip
+	(cd .. ; zip $z-src.zip \
+		$z/README.md \
+		$z/CHANGES.md \
+		$z/LICENSE* \
+		$z/doc/User-Guide.pdf \
+		$z/doc/Raspberry-Pi-APRS.pdf \
+		$z/doc/Raspberry-Pi-APRS-Tracker.pdf \
+		$z/doc/APRStt-Implementation-Notes.pdf \
+		$z/Makefile* \
+		$z/*.c $z/*.h \
+		$z/regex/* $z/misc/* $z/geotranz/* \
+		$z/man1/* \
+		$z/generic.conf \
+		$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
+		$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
+		$z/dw-start.sh $z/direwolf.spec \
+		$z/dwespeak.bat $z/dwespeak.sh \
+		$z/telemetry-toolkit/* )
+
+
+#
+# The destination field is often used to identify the manufacturer/model.
+# These are not hardcoded into Dire Wolf.  Instead they are read from
+# a file called tocalls.txt at application start up time.
+#
+# The original permanent symbols are built in but the "new" symbols,
+# using overlays, are often updated.  These are also read from files.
+#
+# You can obtain an updated copy by typing "make tocalls-symbols".
+# This is not part of the normal build process.  You have to do this explicitly.
+#
+# The locations below appear to be the most recent.
+# The copy at http://www.aprs.org/tocalls.txt is out of date.
+#
+
+.PHONY: tocalls-symbols
+tocalls-symbols :
+	cp tocalls.txt tocalls.txt~
+	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
+	-diff -Z tocalls.txt~ tocalls.txt
+	cp symbols-new.txt symbols-new.txt~
+	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
+	-diff -Z symbols-new.txt~ symbols-new.txt
+	cp symbolsX.txt symbolsX.txt~
+	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
+	-diff -Z symbolsX.txt~ symbolsX.txt
diff --git a/Makefile.win b/Makefile.win
index 8fc38f3..3a9d9f6 100644
--- a/Makefile.win
+++ b/Makefile.win
@@ -1,427 +1,574 @@
-#
-# Makefile for native Windows version of Dire Wolf.
-#
-#
-# This is built in the Cygwin environment but with the 
-# compiler from http://www.mingw.org/ so there is no
-# dependency on extra DLLs.
-#
-# The MinGW/bin directory must be in the PATH for the 
-# compiler.  e.g.   export PATH=/cygdrive/c/MinGW/bin:$PATH
-#
-# Failure to have the path set correctly will result in the
-# obscure message:     Makefile.win:... recipe for target ... failed. 
-#
-# Type "which gcc" to make sure you are getting the right one!
-#
-
-
-all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc
-
-
-# People say we need -mthreads option for threads to work properly.
-# They also say it creates a dependency on mingwm10.dll but I'm not seeing that.
-
-CC := gcc
-CFLAGS :=    -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC
-AR := ar
-
-#CFLAGS += -g
-
-#
-# Let's see impact of various optimization levels.
-# Benchmark results with MinGW gcc version 4.6.2.
-#
-#	seconds	options, comments
-#	------	-----------------
-#	119.8	-O2	Used for version 0.8
-#	 92.1	-O3
-#	 88.7	-Ofast  (should be same as -O3 -ffastmath)
-#	 87.5	-Ofast -march=pentium
-#	 74.1	-Ofast -msse
-#	 72.2	-Ofast -march=pentium -msse
-#	 62.0	-Ofast -march=pentium3   (this implies -msse)
-#	 61.9	-Ofast -march=pentium3 -msse
-#
-# A minimum of Windows XP is required due to some of the system
-# features being used.  XP requires a Pentium processor or later.
-# The DSP filters can be sped up considerably with the SSE instructions.
-# The SSE instructions were introduced in 1999 with the 
-# Pentium III series.  
-# SSE2 instructions, added in 2000, don't seem to offer any advantage.
-#
-# For version 0.9, a Pentium 3 or equivalent is now the minimum required
-# for the prebuilt Windows distribution.
-# If you insist on using a computer from the previous century,
-# you can compile this yourself with different options.
-#
-
-# Name of zip file for distribution.
-
-z := $(notdir ${CURDIR})
-
-
-
-# Main application.
-
-demod.o : fsk_demod_state.h
-demod_9600.o : fsk_demod_state.h
-demod_afsk.o : fsk_demod_state.h
-
-
-direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \
-		hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \
-		fcs_calc.o ax25_pad.o \
-		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 pfilter.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 nmea.o log.o telemetry.o dtime_now.o \
-		dw-icon.o regex.a misc.a geotranz.a
-	$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
-
-dw-icon.o : dw-icon.rc dw-icon.ico
-	windres dw-icon.rc -o $@
-
-
-# Optimization for slow processors.
-
-demod.o : fsk_fast_filter.h
-
-demod_afsk.o : fsk_fast_filter.h
-
-
-fsk_fast_filter.h : demod_afsk.c
-	$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c
-	./gen_fff > fsk_fast_filter.h
-
-
-# UTM, USNG, MGRS conversions.
-
-geotranz.a : error_string.o  mgrs.o  polarst.o  tranmerc.o  ups.o  usng.o  utm.o
-	ar -cr $@ $^
-
-error_string.o : geotranz/error_string.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-mgrs.o : geotranz/mgrs.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-polarst.o : geotranz/polarst.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-tranmerc.o : geotranz/tranmerc.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-ups.o : geotranz/ups.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-usng.o : geotranz/usng.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-utm.o : geotranz/utm.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-
-#
-# When building for Linux, we use regular expression
-# functions supplied by the gnu C library.
-# For the native WIN32 version, we need to use our own copy.
-# These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm
-#
-
-regex.a : regex.o
-	ar -cr $@ $^	
- 
-regex.o : regex/regex.c
-	$(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^
-
-
-# There are also a couple other functions in the misc
-# subdirectory that are missing on Windows.
-
-misc.a : strsep.o strtok_r.o strcasestr.o
-	ar -cr $@ $^	
- 
-strsep.o : misc/strsep.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-strtok_r.o : misc/strtok_r.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-strcasestr.o : misc/strcasestr.c
-	$(CC) $(CFLAGS) -c -o $@ $^
-
-
-
-# Separate application to decode raw data.
-
-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 geotranz.a
-	$(CC) $(CFLAGS) -o decode_aprs -DTEST $^
-
-
-# Convert between text and touch tone representation.
-
-text2tt : tt_text.c
-	$(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c
-
-tt2text : tt_text.c
-	$(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c
-
-
-# Convert between Latitude/Longitude and UTM coordinates.
-
-ll2utm : ll2utm.c geotranz.a
-	$(CC) $(CFLAGS) -o $@ $^
-
-utm2ll : utm2ll.c geotranz.a
-	$(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.
-
-gen_packets : gen_packets.o  ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o textcolor.o dsp.o misc.a regex.a
-	$(CC) $(CFLAGS) -o $@ $^
-
-# For tweaking the demodulator.
-
-demod.o : tune.h
-demod_9600.o : tune.h
-demod_afsk.o : tune.h
-
-
-testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c fsk_demod_agc.h \
-		hdlc_rec.o hdlc_rec2.o multi_modem.o \
-		rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \
-		regex.a misc.a \
-		
-	rm -f atest.exe
-	$(CC) $(CFLAGS) -o atest $^
-	./atest -P E ../02_Track_2.wav | grep "packets decoded in" >atest.out
-	echo " " > tune.h
-
-
-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 latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \
-		tune.h 
-	rm -f atest.exe
-	$(CC) $(CFLAGS) -o atest $^
-	./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out
-	echo " " > tune.h
-
-
-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 latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \
-		tune.h 
-	rm -f atest.exe
-	$(CC) $(CFLAGS) -o atest $^
-	./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
-	#./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
-	echo " " > tune.h
-
-
-# Unit test for AFSK demodulator
-
-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 latlong.c symbols.c textcolor.c telemetry.c misc.a regex.a \
-		fsk_fast_filter.h
-	echo " " > tune.h
-	$(CC) $(CFLAGS) -o $@ $^
-	#./atest ..\\direwolf-0.2\\02_Track_2.wav 
-	#atest -B 9600 z9.wav
-	#atest za100.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 latlong.c symbols.c textcolor.c telemetry.c misc.a regex.a \
-		fsk_fast_filter.h
-	echo " " > tune.h
-	$(CC) $(CFLAGS) -o $@ $^
-	./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
-	#./atest9 -B 9600 noise96.wav 
-
-
-# Unit test for inner digipeater algorithm
-
-
-dtest : digipeater.c pfilter.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c misc.a regex.a
-	$(CC) $(CFLAGS) -DTEST -o $@ $^
-	./dtest
-	rm dtest.exe
-
-# Unit test for APRStt.
-
-ttest : aprs_tt.c tt_text.c  latlong.c misc.a  geotranz.a
-	$(CC) $(CFLAGS) -DTT_MAIN  -o $@ $^
-
-
-# Unit test for IGate
-
-itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a
-	$(CC) $(CFLAGS) -DITEST -o $@ $^ -lwinmm -lws2_32
-
-
-# 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
-	$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
-
-
-# Touch Tone to Speech sample application.
-
-ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
-	$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
-
-
-
-.PHONY: depend
-depend : $(wildcard *.c)
-	makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
-
-.PHONY: clean
-clean :
-	rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav
-	echo " " > tune.h
-
-
-# Package it up for distribution:  Prebuilt Windows & source versions.
-
-
-# Left out RPi Tracker due to Comcast upload size limit.
-
-.PHONY: dist-win
-dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe \
-			aclients.exe log2gpx.exe gen_packets.exe atest.exe ttcalc.exe \
-		 	direwolf.txt dwespeak.bat \
-			CHANGES.txt User-Guide.pdf \
-			Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf \
-			A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
-			A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
-	rm -f ../$z-win.zip
-	egrep '^C|^W' direwolf.txt | cut -c2-999 > direwolf.conf
-	unix2dos direwolf.conf
-	zip ../$z-win.zip \
-		CHANGES.txt \
-		User-Guide.pdf \
-		Raspberry-Pi-APRS.pdf \
-		APRStt-Implementation-Notes.pdf \
-		A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
-		A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
-		LICENSE* \
-		direwolf.conf \
-		direwolf.exe \
-		decode_aprs.exe \
-		tocalls.txt symbols-new.txt symbolsX.txt \
-		text2tt.exe tt2text.exe \
-		ll2utm.exe utm2ll.exe \
-		aclients.exe \
-		log2gpx.exe \
-		gen_packets.exe \
-		atest.exe \
-		ttcalc.exe \
-		dwespeak.bat
-
-.PHONY: dist-src
-dist-src : CHANGES.txt \
-		User-Guide.pdf \
-		Raspberry-Pi-APRS.pdf \
-		Raspberry-Pi-APRS-Tracker.pdf \
-		APRStt-Implementation-Notes.pdf \
-		A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
-		A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
-		dw-start.sh dwespeak.bat dwespeak.sh \
-		tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
-	rm -f fsk_fast_filter.h
-	echo " " > tune.h
-	rm -f ../$z-src.zip
-	egrep '^C|^L' direwolf.txt | cut -c2-999 > direwolf.conf
-	dos2unix direwolf.conf
-	dos2unix Makefile
-	dos2unix Makefile.linux
-	(cd .. ; zip $z-src.zip \
-		$z/CHANGES.txt \
-		$z/LICENSE* \
-		$z/User-Guide.pdf \
-		$z/Raspberry-Pi-APRS.pdf \
-		$z/Raspberry-Pi-APRS-Tracker.pdf \
-		$z/APRStt-Implementation-Notes.pdf \
-		$z/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
-		$z/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
-		$z/Makefile.win $z/Makefile.linux $z/Makefile \
-		$z/*.c $z/*.h \
-		$z/regex/* $z/misc/* $z/geotranz/* \
-		$z/man1/* \
-		$z/direwolf.conf $z/direwolf.txt \
-		$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
-		$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
-		$z/dw-start.sh $z/direwolf.spec \
-		$z/dwespeak.bat $z/dwespeak.sh )
-	unix2dos Makefile
-	unix2dos Makefile.linux
-	rm direwolf.conf
-
-
-
-User-Guide.pdf : User-Guide.docx
-	echo "***** User-Guide.pdf is out of date *****"
-
-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 *****"
-
-A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf : A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.docx
-	echo "***** A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf is out of date *****"
-
-A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf : A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.docx
-	echo "***** A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf is out of date *****"
-
-.PHONY: backup
-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.
-#
-
-.PHONY: tocalls-symbols
-tocalls-symbols :
-	cp tocalls.txt tocalls.txt~
-	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
-	diff tocalls.txt~ tocalls.txt
-	cp symbols-new.txt symbols-new.txt~
-	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
-	diff symbols-new.txt~ symbols-new.txt
-	cp symbolsX.txt symbolsX.txt~
-	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
-	diff symbolsX.txt~ symbolsX.txt
-
-#
-# The following is updated by "make depend"
-#
-# DO NOT DELETE
+#
+# Makefile for native Windows version of Dire Wolf.
+#
+#
+# This is built in the Cygwin environment but with the 
+# compiler from http://www.mingw.org/ so there is no
+# dependency on extra DLLs.
+#
+# The MinGW/bin directory must be in the PATH for the 
+# compiler.  e.g.   export PATH=/cygdrive/c/MinGW/bin:$PATH
+#
+# Failure to have the path set correctly will result in the
+# obscure message:     Makefile.win:... recipe for target ... failed. 
+#
+# Type "which gcc" to make sure you are getting the right one!
+#
+
+
+all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc
+
+
+# People say we need -mthreads option for threads to work properly.
+# They also say it creates a dependency on mingwm10.dll but I'm not seeing that.
+
+CC := gcc
+CFLAGS :=    -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC
+AR := ar
+
+CFLAGS += -g
+
+#
+# Let's see impact of various optimization levels.
+# Benchmark results with MinGW gcc version 4.6.2.
+#
+#	seconds	options, comments
+#	------	-----------------
+#	119.8	-O2	Used for version 0.8
+#	 92.1	-O3
+#	 88.7	-Ofast  (should be same as -O3 -ffastmath)
+#	 87.5	-Ofast -march=pentium
+#	 74.1	-Ofast -msse
+#	 72.2	-Ofast -march=pentium -msse
+#	 62.0	-Ofast -march=pentium3   (this implies -msse)
+#	 61.9	-Ofast -march=pentium3 -msse
+#
+# A minimum of Windows XP is required due to some of the system
+# features being used.  XP requires a Pentium processor or later.
+# The DSP filters can be sped up considerably with the SSE instructions.
+# The SSE instructions were introduced in 1999 with the 
+# Pentium III series.  
+# SSE2 instructions, added in 2000, don't seem to offer any advantage.
+#
+# For version 0.9, a Pentium 3 or equivalent is now the minimum required
+# for the prebuilt Windows distribution.
+# If you insist on using a computer from the previous century,
+# you can compile this yourself with different options.
+#
+
+
+
+
+# --------------------------------------  Main application   --------------------------------
+
+demod.o : fsk_demod_state.h
+demod_9600.o : fsk_demod_state.h
+demod_afsk.o : fsk_demod_state.h
+
+
+direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \
+		hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \
+		fcs_calc.o ax25_pad.o \
+		decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
+		gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o pfilter.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 nmea.o serial_port.o log.o telemetry.o \
+		dwgps.o dwgpsnmea.o dtime_now.o \
+		dw-icon.o regex.a misc.a geotranz.a
+	$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
+
+dw-icon.o : dw-icon.rc dw-icon.ico
+	windres dw-icon.rc -o $@
+
+
+# Optimization for slow processors.
+
+demod.o : fsk_fast_filter.h
+
+demod_afsk.o : fsk_fast_filter.h
+
+
+fsk_fast_filter.h : demod_afsk.c
+	$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c
+	./gen_fff > fsk_fast_filter.h
+
+
+#
+# The destination field is often used to identify the manufacturer/model.
+# These are not hardcoded into Dire Wolf.  Instead they are read from
+# a file called tocalls.txt at application start up time.
+#
+# The original permanent symbols are built in but the "new" symbols,
+# using overlays, are often updated.  These are also read from files.
+#
+# You can obtain an updated copy by typing "make tocalls-symbols".
+# This is not part of the normal build process.  You have to do this explicitly.
+#
+# The locations below appear to be the most recent.
+# The copy at http://www.aprs.org/tocalls.txt is out of date.
+#
+
+.PHONY: tocalls-symbols
+tocalls-symbols :
+	cp tocalls.txt tocalls.txt~
+	wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
+	-diff tocalls.txt~ tocalls.txt
+	cp symbols-new.txt symbols-new.txt~
+	wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
+	-diff symbols-new.txt~ symbols-new.txt
+	cp symbolsX.txt symbolsX.txt~
+	wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
+	-diff symbolsX.txt~ symbolsX.txt
+
+
+
+# ----------------------------  Other utilities included with distribution  -------------------------
+
+
+# Separate application to decode raw data.
+
+decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.c regex.a misc.a geotranz.a
+	$(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^
+
+
+# Convert between text and touch tone representation.
+
+text2tt : tt_text.c misc.a
+	$(CC) $(CFLAGS) -DENC_MAIN -o $@ $^
+
+tt2text : tt_text.c misc.a
+	$(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^
+
+
+# Convert between Latitude/Longitude and UTM coordinates.
+
+ll2utm : ll2utm.c textcolor.c geotranz.a misc.a
+	$(CC) $(CFLAGS) -o $@ $^
+
+utm2ll : utm2ll.c textcolor.c geotranz.a misc.a
+	$(CC) $(CFLAGS) -o $@ $^
+
+
+# Convert from log file to GPX.
+
+log2gpx : log2gpx.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -o $@ $^
+
+
+# Test application to generate sound.
+
+gen_packets : gen_packets.o  ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o textcolor.o dsp.o misc.a regex.a
+	$(CC) $(CFLAGS) -o $@ $^
+
+
+
+# -------------------------------------------  Libraries  --------------------------------------------
+
+
+
+# UTM, USNG, MGRS conversions.
+
+geotranz.a : error_string.o  mgrs.o  polarst.o  tranmerc.o  ups.o  usng.o  utm.o
+	ar -cr $@ $^
+
+error_string.o : geotranz/error_string.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+mgrs.o : geotranz/mgrs.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+polarst.o : geotranz/polarst.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+tranmerc.o : geotranz/tranmerc.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+ups.o : geotranz/ups.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+usng.o : geotranz/usng.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+utm.o : geotranz/utm.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+
+#
+# When building for Linux, we use regular expression
+# functions supplied by the gnu C library.
+# For the native WIN32 version, we need to use our own copy.
+# These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm
+#
+
+regex.a : regex.o
+	ar -cr $@ $^	
+ 
+regex.o : regex/regex.c
+	$(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^
+
+
+# There are several string functios found in Linux
+# but not on Windows.  Need to provide our own copy.
+
+misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o
+	ar -cr $@ $^	
+ 
+strsep.o : misc/strsep.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+strtok_r.o : misc/strtok_r.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+strcasestr.o : misc/strcasestr.c
+	$(CC) $(CFLAGS) -c -o $@ $^
+
+strlcpy.o : misc/strlcpy.c
+	$(CC) $(CFLAGS) -I. -c -o $@ $^
+
+strlcat.o : misc/strlcat.c
+	$(CC) $(CFLAGS) -I. -c -o $@ $^
+
+
+# ---------------------------------  Automated Smoke Test  --------------------------------
+
+
+# Combine some unit tests into a single regression sanity check.
+
+
+check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600 
+
+# Can we encode and decode at popular data rates?
+# Verify that single bit fixup increases the count.
+
+check-modem1200 : gen_packets atest
+	gen_packets -n 100 -o test1.wav
+	atest -F0 -PE -L70 -G71 test1.wav
+	atest -F1 -PE -L73 -G75 test1.wav
+	#rm test1.wav
+
+check-modem300 : gen_packets atest
+	gen_packets -B300 -n 100 -o test3.wav
+	atest -B300 -F0 -L68 -G69 test3.wav
+	atest -B300 -F1 -L73 -G75 test3.wav
+	rm test3.wav
+
+check-modem9600 : gen_packets atest
+	gen_packets -B9600 -n 100 -o test9.wav
+	atest -B9600 -F0 -L57 -G59 test9.wav
+	atest -B9600 -F1 -L66 -G67 test9.wav
+	rm test9.wav
+
+# Unit test for AFSK demodulator
+
+atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_9600.c \
+		dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
+		rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
+		dwgpsnmea.o dwgps.o serial_port.o latlong.c \
+		symbols.c tt_text.c textcolor.c telemetry.c \
+		misc.a regex.a
+	echo " " > tune.h
+	$(CC) $(CFLAGS) -o $@ $^
+	#./atest ..\\direwolf-0.2\\02_Track_2.wav
+	#atest -B 9600 z9.wav
+	#atest za100.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 latlong.c symbols.c textcolor.c telemetry.c misc.a regex.a \
+		fsk_fast_filter.h
+	echo " " > tune.h
+	$(CC) $(CFLAGS) -o $@ $^
+	./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
+	#./atest9 -B 9600 noise96.wav
+
+
+# Unit test for inner digipeater algorithm
+
+.PHONY: dtest
+dtest : digipeater.c dedupe.c \
+		pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \
+		decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a
+	$(CC) $(CFLAGS) -DDIGITEST -o $@ $^
+	./dtest
+	rm dtest.exe
+
+# Unit test for APRStt tone seqence parsing.
+
+.PHONTY: ttest
+ttest : aprs_tt.c tt_text.c latlong.o textcolor.o geotranz.a misc.a
+	$(CC) $(CFLAGS) -Igeotranz -DTT_MAIN  -o $@ $^
+	./ttest
+	rm ttest.exe
+
+# Unit test for APRStt tone sequence / text conversions.
+
+.PHONY: tttexttest
+tttexttest : tt_text.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -DTTT_TEST -o $@ $^
+	./tttexttest
+	rm tttexttest.exe
+
+# Unit test for Packet Filtering.
+
+.PHONY: pftest
+pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a regex.a
+	$(CC) $(CFLAGS) -DPFTEST -o $@ $^
+	./pftest
+	rm pftest.exe
+
+
+
+# Unit test for telemetry decoding.
+
+.PHONY: tlmtest
+tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
+	$(CC) $(CFLAGS) -DTEST -o $@ $^
+	./tlmtest
+	rm tlmtest.exe
+
+
+# Unit test for location coordinate conversion.
+
+.PHONY: lltest
+lltest : latlong.c textcolor.o misc.a
+	$(CC) $(CFLAGS) -DLLTEST -o $@ $^
+	./lltest
+	rm lltest.exe
+
+# Unit test for encoding position & object report.
+
+.PHONY: enctest
+enctest : encode_aprs.c latlong.c textcolor.c misc.a
+	$(CC) $(CFLAGS) -DEN_MAIN -o $@ $^
+	./enctest
+	rm enctest.exe
+
+
+# Unit test for KISS encapsulation.
+
+.PHONY: kisstest
+kisstest : kiss_frame.c
+	$(CC) $(CFLAGS) -DKISSTEST -o $@ $^
+	./kisstest
+	rm kisstest.exe
+
+
+# ------------------------------ Other manual testing & experimenting  -------------------------------
+
+
+# For tweaking the demodulator.
+
+demod.o : tune.h
+demod_9600.o : tune.h
+demod_afsk.o : tune.h
+
+
+testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \
+		hdlc_rec.o hdlc_rec2.o multi_modem.o \
+		rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \
+		dwgpsnmea.o dwgps.o serial_port.o tt_text.o regex.a misc.a
+	rm -f atest.exe
+	$(CC) $(CFLAGS) -o atest $^
+	./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out
+	echo " " > tune.h
+
+
+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 latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \
+		tune.h 
+	rm -f atest.exe
+	$(CC) $(CFLAGS) -o atest $^
+	./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out
+	echo " " > tune.h
+
+
+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 latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \
+		tune.h 
+	rm -f atest.exe
+	$(CC) $(CFLAGS) -o atest $^
+	./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
+	#./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
+	echo " " > tune.h
+
+# Unit test for IGate
+
+itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a
+	$(CC) $(CFLAGS) -DITEST -o $@ $^ -lwinmm -lws2_32
+
+
+
+
+
+# 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
+	$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
+
+
+# Touch Tone to Speech sample application.
+
+ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
+	$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
+
+
+# Send GPS location to KISS TNC each second.
+
+walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \
+		latlong.o encode_aprs.o serial_port.o textcolor.o \
+		ax25_pad.o fcs_calc.o \
+		xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \
+		hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \
+		multi_modem.o demod.o demod_afsk.o demod_9600.o rdq.o \
+		server.o morse.o audio_stats.o dtime_now.o dlq.o \
+		regex.a misc.a 
+	$(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32
+
+
+
+#--------------------------------------------------------------
+
+
+.PHONY: depend
+depend : $(wildcard *.c)
+	makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
+
+.PHONY: clean
+clean :
+	rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav
+	echo " " > tune.h
+
+
+
+# -------------------------------  Packaging for distribution  ----------------------
+
+# Name of zip file for distribution.
+
+z := $(notdir ${CURDIR})
+
+
+.PHONY: dist-win
+dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe \
+			aclients.exe log2gpx.exe gen_packets.exe atest.exe ttcalc.exe \
+		 	generic.conf dwespeak.bat \
+			README.md CHANGES.md \
+			doc/User-Guide.pdf \
+			doc/Raspberry-Pi-APRS.pdf \
+			doc/APRStt-Implementation-Notes.pdf 
+	rm -f ../$z-win.zip
+	egrep '^C|^W' generic.conf | cut -c2-999 > direwolf.conf
+	unix2dos direwolf.conf
+	zip --junk-paths ../$z-win.zip \
+		README.md \
+		CHANGES.md \
+		doc/User-Guide.pdf \
+		doc/Raspberry-Pi-APRS.pdf \
+		doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
+		doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
+		doc/APRS-Telemetry-Toolkit.pdf \
+		doc/APRStt-Implementation-Notes.pdf \
+		doc/APRStt-interface-for-SARTrack.pdf \
+		doc/APRStt-Listening-Example.pdf \
+		doc/Raspberry-Pi-APRS.pdf \
+		doc/Raspberry-Pi-APRS-Tracker.pdf \
+		doc/Raspberry-Pi-SDR-IGate.pdf \
+		doc/User-Guide.pdf \
+		doc/WA8LMF-TNC-Test-CD-Results.pdf \
+		LICENSE* \
+		direwolf.conf \
+		direwolf.exe \
+		decode_aprs.exe \
+		tocalls.txt symbols-new.txt symbolsX.txt \
+		text2tt.exe tt2text.exe \
+		ll2utm.exe utm2ll.exe \
+		aclients.exe \
+		log2gpx.exe \
+		gen_packets.exe \
+		atest.exe \
+		ttcalc.exe \
+		dwespeak.bat \
+		telemetry-toolkit/*
+
+.PHONY: dist-src
+dist-src : README.md CHANGES.md \
+		doc/User-Guide.pdf \
+		doc/Raspberry-Pi-APRS.pdf \
+		doc/Raspberry-Pi-APRS-Tracker.pdf \
+		doc/APRStt-Implementation-Notes.pdf \
+		doc/APRS-Telemetry-Toolkit.pdf \
+		dw-start.sh dwespeak.bat dwespeak.sh \
+		tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
+	rm -f fsk_fast_filter.h
+	echo " " > tune.h
+	rm -f ../$z-src.zip
+	dos2unix generic.conf
+	dos2unix Makefile
+	dos2unix Makefile.linux
+	dos2unix Makefile.macosx
+	dos2unix telemetry-toolkit/telem-balloon.conf
+	dos2unix telemetry-toolkit/telem-balloon.pl
+	dos2unix telemetry-toolkit/telem-bits.pl
+	dos2unix telemetry-toolkit/telem-data.pl
+	dos2unix telemetry-toolkit/telem-data91.pl
+	dos2unix telemetry-toolkit/telem-eqns.pl
+	dos2unix telemetry-toolkit/telem-m0xer-3.txt
+	dos2unix telemetry-toolkit/telem-parm.pl
+	dos2unix telemetry-toolkit/telem-unit.pl
+	dos2unix telemetry-toolkit/telem-volts.py
+	dos2unix telemetry-toolkit/telem-volts.conf
+	(cd .. ; zip $z-src.zip \
+		$z/README.md \
+		$z/CHANGES.md \
+		$z/LICENSE* \
+		$z/doc/User-Guide.pdf \
+		$z/doc/Raspberry-Pi-APRS.pdf \
+		$z/doc/Raspberry-Pi-APRS-Tracker.pdf \
+		$z/doc/APRStt-Implementation-Notes.pdf \
+		$z/doc/APRS-Telemetry-Toolkit.pdf \
+		$z/Makefile.win $z/Makefile.linux $z/Makefile.macosx $z/Makefile \
+		$z/*.c $z/*.h \
+		$z/regex/* $z/misc/* $z/geotranz/* \
+		$z/man1/* \
+		$z/generic.conf \
+		$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
+		$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
+		$z/dw-start.sh $z/direwolf.spec \
+		$z/dwespeak.bat $z/dwespeak.sh \
+		$z/telemetry-toolkit/* )
+	unix2dos Makefile
+	unix2dos Makefile.linux
+	unix2dos Makefile.macosx
+	unix2dos telemetry-toolkit/telem-balloon.conf
+	unix2dos telemetry-toolkit/telem-balloon.pl
+	dos2unix telemetry-toolkit/telem-bits.pl
+	unix2dos telemetry-toolkit/telem-data.pl
+	unix2dos telemetry-toolkit/telem-data91.pl
+	unix2dos telemetry-toolkit/telem-eqns.pl
+	unix2dos telemetry-toolkit/telem-m0xer-3.txt
+	unix2dos telemetry-toolkit/telem-parm.pl
+	unix2dos telemetry-toolkit/telem-unit.pl
+	unix2dos telemetry-toolkit/telem-volts.py
+	unix2dos telemetry-toolkit/telem-volts.conf
+
+
+# Reminders if pdf files are not up to date.
+
+
+
+
+.PHONY: backup
+backup :
+	mkdir /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
+	cp -r . /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
+
+
+#
+# The following is updated by "make depend"
+#
+# DO NOT DELETE
 
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8a457b3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+
+# Dire Wolf #
+
+### Decoded Information from Radio Emissions for Windows Or Linux Fans ###
+
+In the early days of Amateur Packet Radio, it was necessary to use a “Terminal Node Controller” (TNC) with specialized hardware.  Those days are gone.  You can now get better results at lower cost by connecting your radio to the “soundcard” interface of a computer and using software to decode the signals.
+ 
+Dire Wolf is a software "soundcard" modem/TNC and [APRS](http://www.aprs.org/) encoder/decoder.   It can be used stand-alone to observe APRS traffic, as a digipeater, [APRStt](http://www.aprs.org/aprstt.html) gateway, or Internet Gateway (IGate).    It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YA [...]
+ 
+ 
+## Features ##
+
+- Lower cost, higher performance alternative to hardware TNC.
+Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf.net/TNCtest/).  
+
+- Ideal for building a Raspberry Pi digipeater & IGate.
+
+- 300, 1200, and 9600 baud operation.
+
+- Interface with applications by
+      - [AGW](http://uz7.ho.ua/includes/agwpeapi.htm) network protocol
+      - [KISS](http://www.ax25.net/kiss.aspx) serial port
+      - [KISS](http://www.ax25.net/kiss.aspx) network protocol
+      
+- Decoding of received information for troubleshooting.
+
+- Logging and conversion to GPX file format.
+
+- Beaconing for yourself or other nearby entities.
+
+- Very flexible Digipeating with routing and filtering between up to 6 ports.
+
+- APRStt gateway - converts touch tone sequences to APRS objects and voice responses.
+
+- APRS Internet Gateway (IGate) with IPv6 support and special SATGate mode.
+
+- APRS Telemetry Toolkit.
+
+- Compatible with software defined radios (SDR) such as [gqrx](http://gqrx.dk/),  [rtl_fm](http://sdr.osmocom.org/trac/wiki/rtl-sdr), and SDR#.
+
+- Includes separate raw packet decoder, decode_aprs.
+
+- Open source so you can see how it works and make your own modifications.
+
+- Runs in 3 different environments:
+      - Microsoft Windows XP or later
+      - Linux, regular PC or single board computer such as Raspberry Pi, BeagleBone Black, or cubieboard 2
+      - Mac OS X
+ 
+## Installation ##
+
+### Windows ###
+
+Go to the [releases page](https://github.com/wb2osz/direwolf/releases).   Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window.
+
+For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc).  
+
+
+### Linux - Download with web browser ###
+
+Go to the [releases page](https://github.com/wb2osz/direwolf/releases).  Chose desired release and download the source as zip or compressed tar file.  Unpack the files, with "unzip" or "tar xfz," and then:
+
+	cd direwolf-*
+	make
+	sudo make install
+	make install-conf
+
+For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc).  Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
+
+### Linux - Using git clone ###
+
+	cd ~
+	git clone https://www.github.com/wb2osz/direwolf
+	cd direwolf
+	make
+	sudo make install
+	make install-conf
+
+This should give you the most recent stable release.  If you want the latest (unstable) development version, use "git checkout dev" instead before the first "make" command.
+
+For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc).  Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
+
+## Join the conversation  ##
+ 
+Here are some good places to share information:
+
+- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) 
+
+- [Raspberry Pi 4 Ham Radio](https://groups.yahoo.com/neo/groups/Raspberry_Pi_4-Ham_RADIO/info)
+
+- [linuxham](https://groups.yahoo.com/neo/groups/linuxham/info)
+
+- [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/)
+ 
+
+
diff --git a/Raspberry-Pi-APRS-Tracker.pdf b/Raspberry-Pi-APRS-Tracker.pdf
deleted file mode 100755
index eefd30f..0000000
Binary files a/Raspberry-Pi-APRS-Tracker.pdf and /dev/null differ
diff --git a/Raspberry-Pi-APRS.pdf b/Raspberry-Pi-APRS.pdf
deleted file mode 100755
index 49182a0..0000000
Binary files a/Raspberry-Pi-APRS.pdf and /dev/null differ
diff --git a/User-Guide.pdf b/User-Guide.pdf
deleted file mode 100755
index ba4f7fd..0000000
Binary files a/User-Guide.pdf and /dev/null differ
diff --git a/aclients.c b/aclients.c
index aeb8220..fe5e93c 100644
--- a/aclients.c
+++ b/aclients.c
@@ -1,830 +1,864 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      aclients.c
- *
- * Purpose:   	Multiple concurrent APRS clients for comparing 
- *		TNC demodulator performance.
- *		
- * Description:	Establish connection with multiple servers and 
- *		compare results side by side.
- *
- * Usage:	aclients  8000=AGWPE  8002=DireWolf  COM1=D710A
- *
- *		This will connect to multiple physical or virtual
- *		TNCs, read packets from them, and display results.
- *
- *---------------------------------------------------------------*/
-
-
-
-/*
- * Native Windows:	Use the Winsock interface.
- * Linux:		Use the BSD socket interface.
- * Cygwin:		Can use either one.
- */
-
-
-#if __WIN32__
-
-#include <winsock2.h>
-// default is 0x0400
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0501	/* Minimum OS version is XP. */
-#include <ws2tcpip.h>
-#else 
-//#define __USE_XOPEN2KXSI 1
-//#define __USE_XOPEN 1
-#include <stdlib.h>
-#include <netdb.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <sys/errno.h>
-#endif
-
-#include <unistd.h>
-#include <stdio.h>
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "version.h"
-
-
-struct agwpe_s {	
-  short portx;			/* 0 for first, 1 for second, etc. */
-  short port_hi_reserved;	
-  short kind_lo;		/* message type */
-  short kind_hi;
-  char call_from[10];
-  char call_to[10];
-  int data_len;			/* Number of data bytes following. */
-  int user_reserved;
-};
-
-
-#if __WIN32__
-static unsigned __stdcall client_thread_net (void *arg);
-static unsigned __stdcall client_thread_serial (void *arg);
-#else
-static void * client_thread_net (void *arg);
-static void * client_thread_serial (void *arg);
-#endif
-
-
-
-/*
- * Convert Internet address to text.
- * Can't use InetNtop because it is supported only on Windows Vista and later. 
- */
-
-static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
-{
-	struct sockaddr_in *sa4;
-	struct sockaddr_in6 *sa6;
-
-	switch (Family) {
-	  case AF_INET:
-	    sa4 = (struct sockaddr_in *)pAddr;
-#if __WIN32__
-	    sprintf (pStringBuf, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
-						sa4->sin_addr.S_un.S_un_b.s_b2,
-						sa4->sin_addr.S_un.S_un_b.s_b3,
-						sa4->sin_addr.S_un.S_un_b.s_b4);
-#else
-	    inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
-#endif
-	    break;
-	  case AF_INET6:
-	    sa6 = (struct sockaddr_in6 *)pAddr;
-#if __WIN32__
-	    sprintf (pStringBuf, "%x:%x:%x:%x:%x:%x:%x:%x",  
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
-#else
-	    inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
-#endif
-	    break;
-	  default:
-	    sprintf (pStringBuf, "Invalid address family!");
-	}
-	assert (strlen(pStringBuf) < StringBufSize);
-	return pStringBuf;
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name: 	main
- *
- * Purpose:   	Start up multiple client threads listening to different
- *		TNCs.   Print packets.  Tally up statistics.
- *
- * Usage:	aclients  8000=AGWPE  8002=DireWolf  COM1=D710A
- *
- *		Each command line argument is TCP port number or a 
- *		serial port name.  Follow by = and a text description
- *		of what is connected.
- *
- *		For now, everything is assumed to be on localhost.
- *		Maybe someday we might recognize host:port=description.
- *
- *---------------------------------------------------------------*/
-
-#define MAX_CLIENTS 6
-
-/* Obtained from the command line. */
-
-static int num_clients;
-
-static char hostname[MAX_CLIENTS][50];
-static char port[MAX_CLIENTS][30];
-static char description[MAX_CLIENTS][50];
-
-#if __WIN32__
-	static HANDLE client_th[MAX_CLIENTS];
-#else
-	static pthread_t client_tid[MAX_CLIENTS];
-#endif
-
-#define LINE_WIDTH 120
-static int column_width;
-static char packets[LINE_WIDTH+4];
-static int packet_count[MAX_CLIENTS];
-
-
-//#define PRINT_MINUTES 2
-
-#define PRINT_MINUTES 30
-
-
-
-int main (int argc, char *argv[])
-{
-	int j;
-	time_t start_time, now, next_print_time;
-
-#if __WIN32__
-#else
-	int e;
-
- 	setlinebuf (stdout);
-#endif
-
-/*
- * Extract command line args.
- */
-	num_clients = argc - 1;
-
-	if (num_clients < 1 || num_clients > MAX_CLIENTS) {
-	  printf ("Specify up to %d TNCs on the command line.\n", MAX_CLIENTS);
-	  exit (1);
-	}
-
-	column_width = LINE_WIDTH / num_clients;
-
-	for (j=0; j<num_clients; j++) {
-	  char stemp[100];
-	  char *p;
-	
-	  strcpy (stemp, argv[j+1]);
-	  p = strtok (stemp, "=");
-	  if (p == NULL) {
-	    printf ("Internal error 1\n");
-	    exit (1);
-	  }
-	  strcpy (hostname[j], "localhost");
-	  strcpy (port[j], p);
-	  p = strtok (NULL, "=");
-	  if (p == NULL) {
-	    printf ("Missing description after %s\n", port[j]);
-	    exit (1);
-	  }
-	  strcpy (description[j], p);
-	}
-	
-	//printf ("_WIN32_WINNT = %04x\n", _WIN32_WINNT);
-	//for (j=0; j<num_clients; j++) {
-	//  printf ("%s,%s,%s\n", hostname[j], port[j], description[j]);
-	//}
-
-	memset (packets, ' ', (size_t)LINE_WIDTH);
-	packets[LINE_WIDTH] = '\0';
-
-	for (j=0; j<num_clients; j++) {
-	  packet_count[j] = 0;
-	}
-
-
-	for (j=0; j<num_clients; j++) {
-#if __WIN32__
-	  if (isdigit(port[j][0])) {
-	    client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_net, (void *)j, 0, NULL);
-	  }
-	  else {
-	    client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_serial, (void *)j, 0, NULL);
-	  }
-	  if (client_th[j] == NULL) {
-	    printf ("Internal error: Could not create client thread %d.\n", j);
-	    exit (1);
-	  }
-#else
-	  if (isdigit(port[j][0])) {
-	    e = pthread_create (&client_tid[j], NULL, client_thread_net, (void *)(long)j);
-	  }
-	  else {
-	    e = pthread_create (&client_tid[j], NULL, client_thread_serial, (void *)(long)j);
-	  }
-	  if (e != 0) {
-	    perror("Internal error: Could not create client thread.");
-	    exit (1);
-	  }
-#endif
-	}
-
-	start_time = time(NULL);
-	next_print_time = start_time + (PRINT_MINUTES) * 60;
-
-/*
- * Print results from clients. 
- */
-	while (1) {
-	  int k;
-	  int something;	
-
-	  SLEEP_MS(100);
-	  
-	  something = 0;
-	  for (k=0; k<LINE_WIDTH && ! something; k++) {
-	    if (packets[k] != ' ') {
-	      something = 1;
-	    }
-	  }
-	  if (something) {
-	    /* time for others to catch up. */
-	    SLEEP_MS(200);
-
-	    printf ("%s\n", packets);
-	    memset (packets, ' ', (size_t)LINE_WIDTH);	
-	  }
-
-	  now = time(NULL);
-	  if (now >= next_print_time) {
-	    next_print_time = now + (PRINT_MINUTES) * 60;
-	
-	    printf ("\nTotals after %d minutes", (int)((now - start_time) / 60));
-
-	    for (j=0; j<num_clients; j++) {
-	      printf (", %s %d", description[j], packet_count[j]);
-	    }
-	    printf ("\n\n");
-	  }
-	}
-
-
-	return 0;  // unreachable
-}
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        client_thread_net
- *
- * Purpose:     Establish connection with a TNC via network.
- *
- * Inputs:	arg		- My instance index, 0 thru MAX_CLIENTS-1.
- *
- * Outputs:	packets		- Received packets are put in the corresponding column.
- *
- *--------------------------------------------------------------------*/
-
-#define MAX_HOSTS 30
-
-#if __WIN32__
-static unsigned __stdcall client_thread_net (void *arg)
-#else
-static void * client_thread_net (void *arg)	
-#endif	
-{
-	int my_index;
-	struct addrinfo hints;
-	struct addrinfo *ai_head = NULL;
-	struct addrinfo *ai;
-	struct addrinfo *hosts[MAX_HOSTS];
-	int num_hosts, n;
-	int err;
-	char ipaddr_str[46];		/* text form of IP address */
-#if __WIN32__
-	WSADATA wsadata;
-#endif
-/* 
- * File descriptor for socket to server. 
- * Set to -1 if not connected. 
- * (Don't use SOCKET type because it is unsigned.) 
-*/
-	int server_sock = -1;	
-	struct agwpe_s mon_cmd;
-	char data[1024];
-	int use_chan = -1;
-
-
-	my_index = (int)(long)arg;
-
-#if DEBUGx
-        printf ("DEBUG: client_thread_net %d start, port = '%s'\n", my_index, port[my_index]);
-#endif
-
-#if __WIN32__
-	err = WSAStartup (MAKEWORD(2,2), &wsadata);
-	if (err != 0) {
-	    printf("WSAStartup failed: %d\n", err);
-	    return (0);
-	}
-
-	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
-          printf("Could not find a usable version of Winsock.dll\n");
-          WSACleanup();
-	  //sleep (1);
-          return (0);
-	}
-#endif
-
-	memset (&hints, 0, sizeof(hints));
-
-	hints.ai_family = AF_UNSPEC;	/* Allow either IPv4 or IPv6. */
-	// hints.ai_family = AF_INET;	/* IPv4 only. */
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_protocol = IPPROTO_TCP;
-
-/*
- * Connect to TNC server.
- */
-
-	ai_head = NULL;
-	err = getaddrinfo(hostname[my_index], port[my_index], &hints, &ai_head);
-	if (err != 0) {
-#if __WIN32__
-	  printf ("Can't get address for server %s, err=%d\n", 
-					hostname[my_index], WSAGetLastError());
-#else 
-	  printf ("Can't get address for server %s, %s\n", 
-					hostname[my_index], gai_strerror(err));
-#endif
-	  freeaddrinfo(ai_head);
-      	  exit (1);
-	}
-
-#if DEBUG_DNS
-	printf ("getaddrinfo returns:\n");
-#endif
-	num_hosts = 0;
-	for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
-#if DEBUG_DNS
-	  ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	  printf ("    %s\n", ipaddr_str);
-#endif
-	  hosts[num_hosts] = ai;
-	  if (num_hosts < MAX_HOSTS) num_hosts++;
-	}
-
-#if DEBUG_DNS
-	printf ("addresses for hostname:\n");
-	for (n=0; n<num_hosts; n++) {
-	  ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	  printf ("    %s\n", ipaddr_str);
-	}
-#endif
-
-	// Try each address until we find one that is successful.
-
-	for (n=0; n<num_hosts; n++) {
-	  int is;
-
-	  ai = hosts[n];
-
-	  ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	  is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-#if __WIN32__
-	  if (is == INVALID_SOCKET) {
-	    printf ("Socket creation failed, err=%d", WSAGetLastError());
-	    WSACleanup();
-	    is = -1;
-	    continue;
-	  }
-#else
-	  if (err != 0) {
-	    printf ("Socket creation failed, err=%s", gai_strerror(err));
-	    (void) close (is);
-	    is = -1;
-	    continue;
-	  }
-#endif
-
-#ifndef DEBUG_DNS 
-	  err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
-#if __WIN32__
-	  if (err == SOCKET_ERROR) {
-#if DEBUGx
-	    printf("Connect to %s on %s (%s), port %s failed.\n",
-					description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
-#endif
-	    closesocket (is);
-	    is = -1;
-	    continue;
-	  }
-#else
-	  if (err != 0) {
-#if DEBUGx
-	    printf("Connect to %s on %s (%s), port %s failed.\n",
-					description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
-#endif
-	    (void) close (is);
-	    is = -1;
-	    continue;
-	  }
-	  int flag = 1;
-	  err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
-	  if (err < 0) {
-	    printf("setsockopt TCP_NODELAY failed.\n");
-	  }
-#endif
-
-/* Success. */
-
- 	  printf("Client %d now connected to %s on %s (%s), port %s\n", 
-			my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
-
-	  server_sock = is;
-#endif	  
-	  break;
-	}
-
-	freeaddrinfo(ai_head);
-
-	if (server_sock == -1) {
-
- 	  printf("Client %d unable to connect to %s on %s (%s), port %s\n", 
-			my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
-	  exit (1);
-	}
-
-/*
- * Send command to toggle reception of frames in raw format.
- *
- * Note: Monitor format is only for UI frames.
- * It also discards the via path.
- */
-
-	memset (&mon_cmd, 0, sizeof(mon_cmd));
-
-	mon_cmd.kind_lo = 'k';
-
-#if __WIN32__	      
-	send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
-#else
-	err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
-#endif
-
-
-/*
- * Print all of the monitored packets.
- */
-
-	while (1) {
-	  int n;
-
-#if __WIN32__
-	  n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
-#else
-	  n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
-#endif
-
-	  if (n != sizeof(mon_cmd)) {
-	    printf ("Read error, client %d received %d command bytes.\n", my_index, n);
-	    exit (1);
-	  }
-
-#if DEBUGx
-	  printf ("client %d received '%c' data, data_len = %d\n", 
-			my_index, mon_cmd.kind_lo, mon_cmd.data_len);
-#endif
-	  assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data));
-
-	  if (mon_cmd.data_len > 0) {
-#if __WIN32__
-	    n = recv (server_sock, data, mon_cmd.data_len, 0);
-#else
-	    n = read (server_sock, data, mon_cmd.data_len);
-#endif
-
-	    if (n != mon_cmd.data_len) {
-	      printf ("Read error, client %d received %d data bytes.\n", my_index, n);
-	      exit (1);
-	    }
-	  }
-
-/* 
- * Print it and add to counter.
- * The AGWPE score was coming out double the proper value because 
- * we were getting the same thing from ports 2 and 3.
- * 'use_chan' is the first channel we hear from.
- * Listen only to that one.
- */
-
-	  if (mon_cmd.kind_lo == 'K' && (use_chan == -1 || use_chan == mon_cmd.portx)) {
-	    packet_t pp;
-	    char *pinfo;
-	    int info_len;
-	    char result[400];
-	    char *p;
-	    int col, len;
-	    alevel_t alevel;
-
-	    //printf ("server %d, portx = %d\n", my_index, mon_cmd.portx);
-
-	    use_chan == mon_cmd.portx;
-	    memset (&alevel, 0xff, sizeof(alevel));
-	    pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
-	    ax25_format_addrs (pp, result);
-	    info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
-	    pinfo[info_len] = '\0';
-	    strcat (result, pinfo);
-	    for (p=result; *p!='\0'; p++) {
-	      if (! isprint(*p)) *p = ' ';
-	    }
-#if DEBUGx
-	    printf ("[%d] %s\n", my_index, result);
-#endif
-	    col = column_width * my_index;
-	    len = strlen(result);
-#define MARGIN 3
-	    if (len > column_width - 3) {
-	      len = column_width - 3;
-	    }
-	    if (packets[col] == ' ') {
-	      memcpy (packets+col, result, (size_t)len);
-	    }
-	    else {
-	      memcpy (packets+col, "OVERRUN!    ", (size_t)10);
-	    }
-	    	    
-	    ax25_delete (pp);
-	    packet_count[my_index]++;
-	  }
-	}
-
-} /* end client_thread_net */
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        client_thread_serial
- *
- * Purpose:     Establish connection with a TNC via serial port.
- *
- * Inputs:	arg		- My instance index, 0 thru MAX_CLIENTS-1.
- *
- * Outputs:	packets		- Received packets are put in the corresponding column.
- *
- *--------------------------------------------------------------------*/
-
-#if __WIN32__
-typedef HANDLE MYFDTYPE;
-#define MYFDERROR INVALID_HANDLE_VALUE
-#else
-typedef int MYFDTYPE;
-#define MYFDERROR (-1)
-#endif
-
-
-#if __WIN32__
-static unsigned __stdcall client_thread_serial (void *arg)
-#else
-static void * client_thread_serial (void *arg)	
-#endif	
-{
-	int my_index = (int)(long)arg;
-
-#if __WIN32__
-
-	MYFDTYPE fd;
-	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);
-
-	if (fd == MYFDERROR) {
- 	  printf("Client %d unable to connect to %s on %s.\n", 
-			my_index, description[my_index], port[my_index] );
-	  exit (1);
-	}
-
-	/* 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) {
-	  printf ("GetCommState failed.\n");
-	}
-
-	/* 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;
-	dcb.fOutxCtsFlow = 0;
-	dcb.fOutxDsrFlow = 0;
-	dcb.fDtrControl = 0;
-	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) {
-	  printf ("SetCommState failed.\n");
-	}
-
-#else
-
-/* Linux version. */
-
-	int fd;
-	struct termios ts;
-	int e;
-
-	fd = open (port[my_index], O_RDWR);
-
-	if (fd == MYFDERROR) {
- 	  printf("Client %d unable to connect to %s on %s.\n", 
-			my_index, description[my_index], port[my_index] );
-	  exit (1);
-	}
-
-	e = tcgetattr (fd, &ts);
-	if (e != 0) { perror ("nm tcgetattr"); }
-
-	cfmakeraw (&ts);
-	
-	// TODO: speed?
-	ts.c_cc[VMIN] = 1;	/* wait for at least one character */
-	ts.c_cc[VTIME] = 0;	/* no fancy timing. */
-
-	e = tcsetattr (fd, TCSANOW, &ts);
-	if (e != 0) { perror ("nm tcsetattr"); }
-#endif
-
-
-/* Success. */
-
- 	printf("Client %d now connected to %s on %s\n", 
-			my_index, description[my_index], port[my_index] );
-
-/*
- * Assume we are already in monitor mode.
- */
-
-
-/*
- * Print all of the monitored packets.
- */
-
-	while (1) {
-	  unsigned char ch;
-	  char result[500];
-	  int col, len;
-	  int done;
-	  char *p;
-
-	  len = 0;
-	  done = 0;
-
-	  while ( ! done) {
-
-#if __WIN32__
-	    DWORD n;
-
-	    if (! ReadFile (fd, &ch, 1, &n, NULL)) {
-	      printf ("Read error on %s.\n", description[my_index]);
-	      CloseHandle (fd);
-	      exit (1);
-	    }
-	  
-#else
-	    int n;
-
-	    if ( ( n = read(fd, & ch, 1)) < 0) {
-	      printf ("Read error on %s.\n", description[my_index]);
-	      close (fd);
-	      exit (1);
-	    }
-#endif
-	    if (n == 1) {
-
-/*
- * Try to build one line for each packet.
- * The KPC3+ breaks a packet into two lines like this:
- *
- *	KB1ZXL-1>T2QY5P,W1MHL*,WIDE2-1: <<UI>>:
- *	`c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1=
- *
- *	N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: <UI>:
- * 	!4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100
- *
- * Don't know why some are <<UI>> and some <UI>.
- *
- * Anyhow, ignore the return character if preceded by >:
- */
-	      if (ch == '\r') { 
-	        if (len >= 10 && result[len-2] == '>' && result[len-1] == ':') {
-	          continue;
-	        }
-	        done = 1; 
-	        continue; 
-	      }
-	      if (ch == '\n') continue;
-	      result[len++] = ch;
-	    }
-	  }
-	  result[len] = '\0';
-
-/* 
- * Print it and add to counter.
- */
-	 if (len > 0) {
-	  /* Blank any unprintable characters. */
-	  for (p=result; *p!='\0'; p++) {
-	    if (! isprint(*p)) *p = ' ';
-	  }
-#if DEBUGx
-	  printf ("[%d] %s\n", my_index, result);
-#endif
-	  col = column_width * my_index;
-	  if (len > column_width - 3) {
-	    len = column_width - 3;
-	  }
-	  if (packets[col] == ' ') {
-	    memcpy (packets+col, result, (size_t)len);
-	  }
-	  else {
-	    memcpy (packets+col, "OVERRUN!    ", (size_t)10);
-	  }
-	  packet_count[my_index]++;
-         }
-	}
-
-} /* end client_thread_serial */
-
-/* end aclients.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      aclients.c
+ *
+ * Purpose:   	Multiple concurrent APRS clients for comparing 
+ *		TNC demodulator performance.
+ *		
+ * Description:	Establish connection with multiple servers and 
+ *		compare results side by side.
+ *
+ * Usage:	aclients port1=name1 port2=name2 ...
+ *
+ * Example:	aclients  8000=AGWPE  192.168.1.64:8002=DireWolf  COM1=D710A
+ *
+ *		This will connect to multiple physical or virtual
+ *		TNCs, read packets from them, and display results.
+ *
+ *		Each port can have the following forms:
+ *
+ *		* host-name:tcp-port
+ *		* ip-addr:tcp-port
+ *		* tcp-port
+ *		* serial port name (e.g.  COM1, /dev/ttyS0)
+ *
+ *---------------------------------------------------------------*/
+
+
+
+/*
+ * Native Windows:	Use the Winsock interface.
+ * Linux:		Use the BSD socket interface.
+ */
+
+
+#if __WIN32__
+
+#include <winsock2.h>
+// default is 0x0400
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501	/* Minimum OS version is XP. */
+#include <ws2tcpip.h>
+#else 
+//#define __USE_XOPEN2KXSI 1
+//#define __USE_XOPEN 1
+#include <stdlib.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/errno.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "version.h"
+
+
+struct agwpe_s {	
+  short portx;			/* 0 for first, 1 for second, etc. */
+  short port_hi_reserved;	
+  short kind_lo;		/* message type */
+  short kind_hi;
+  char call_from[10];
+  char call_to[10];
+  int data_len;			/* Number of data bytes following. */
+  int user_reserved;
+};
+
+
+#if __WIN32__
+static unsigned __stdcall client_thread_net (void *arg);
+static unsigned __stdcall client_thread_serial (void *arg);
+#else
+static void * client_thread_net (void *arg);
+static void * client_thread_serial (void *arg);
+#endif
+
+
+
+/*
+ * Convert Internet address to text.
+ * Can't use InetNtop because it is supported only on Windows Vista and later. 
+ */
+
+static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
+{
+	struct sockaddr_in *sa4;
+	struct sockaddr_in6 *sa6;
+
+	switch (Family) {
+	  case AF_INET:
+	    sa4 = (struct sockaddr_in *)pAddr;
+#if __WIN32__
+	    snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
+						sa4->sin_addr.S_un.S_un_b.s_b2,
+						sa4->sin_addr.S_un.S_un_b.s_b3,
+						sa4->sin_addr.S_un.S_un_b.s_b4);
+#else
+	    inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
+#endif
+	    break;
+	  case AF_INET6:
+	    sa6 = (struct sockaddr_in6 *)pAddr;
+#if __WIN32__
+	    snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
+#else
+	    inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
+#endif
+	    break;
+	  default:
+	    snprintf (pStringBuf, StringBufSize, "Invalid address family!");
+	}
+	assert (strlen(pStringBuf) < StringBufSize);
+	return pStringBuf;
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: 	main
+ *
+ * Purpose:   	Start up multiple client threads listening to different
+ *		TNCs.   Print packets.  Tally up statistics.
+ *
+ * Usage:	Described above.
+ *
+ *---------------------------------------------------------------*/
+
+#define MAX_CLIENTS 6
+
+/* Obtained from the command line. */
+
+static int num_clients;
+
+static char hostname[MAX_CLIENTS][50];		/* DNS host name or IPv4 address. */
+						/* Some of the code is there for IPv6 but */
+						/* needs more work. */
+						/* Defaults to "localhost" if not specified. */
+
+static char port[MAX_CLIENTS][30];		/* If it begins with a digit, it is considered */
+						/* a TCP port number at the hostname.  */
+						/* Otherwise, we treat it as a serial port name. */
+
+static char description[MAX_CLIENTS][50];	/* Name used in the output. */
+
+
+#if __WIN32__
+	static HANDLE client_th[MAX_CLIENTS];
+#else
+	static pthread_t client_tid[MAX_CLIENTS];
+#endif
+
+#define LINE_WIDTH 120
+static int column_width;
+static char packets[LINE_WIDTH+4];
+static int packet_count[MAX_CLIENTS];
+
+
+//#define PRINT_MINUTES 2
+
+#define PRINT_MINUTES 30
+
+
+
+int main (int argc, char *argv[])
+{
+	int j;
+	time_t start_time, now, next_print_time;
+
+#if __WIN32__
+#else
+	int e;
+
+ 	setlinebuf (stdout);
+#endif
+
+/*
+ * Extract command line args.
+ */
+	num_clients = argc - 1;
+
+	if (num_clients < 1 || num_clients > MAX_CLIENTS) {
+	  printf ("Specify up to %d TNCs on the command line.\n", MAX_CLIENTS);
+	  exit (1);
+	}
+
+	column_width = LINE_WIDTH / num_clients;
+
+	for (j=0; j<num_clients; j++) {
+	  char stemp[100];
+	  char *p;
+
+/* Each command line argument should be of the form "port=description." */
+
+	  strlcpy (stemp, argv[j+1], sizeof(stemp));
+	  p = strtok (stemp, "=");
+	  if (p == NULL) {
+	    printf ("Internal error 1\n");
+	    exit (1);
+	  }
+	  strlcpy (hostname[j], "localhost", sizeof(hostname[j]));
+	  strlcpy (port[j], p, sizeof(port[j]));
+	  p = strtok (NULL, "=");
+	  if (p == NULL) {
+	    printf ("Missing description after %s\n", port[j]);
+	    exit (1);
+	  }
+	  strlcpy (description[j], p, sizeof(description[j]));
+
+/* If the port contains ":" split it into hostname (or addr) and port number. */
+/* Haven't thought about IPv6 yet. */
+
+	  strlcpy (stemp, port[j], sizeof(stemp));
+
+	  char *h;
+
+	  h = strtok (stemp, ":");
+	  if (h != NULL) {
+	    p = strtok (NULL, ":");
+	    if (p != NULL) {
+	      strlcpy (hostname[j], h, sizeof(hostname[j]));
+	      strlcpy (port[j], p, sizeof(port[j]));
+	    }
+	  }
+	}
+
+	//printf ("_WIN32_WINNT = %04x\n", _WIN32_WINNT);
+	//for (j=0; j<num_clients; j++) {
+	//  printf ("%s,%s,%s\n", hostname[j], port[j], description[j]);
+	//}
+
+	memset (packets, ' ', (size_t)LINE_WIDTH);
+	packets[LINE_WIDTH] = '\0';
+
+	for (j=0; j<num_clients; j++) {
+	  packet_count[j] = 0;
+	}
+
+
+	for (j=0; j<num_clients; j++) {
+
+/* If port begins with digit, consider it to be TCP. */
+/* Otherwise, treat as serial port name. */
+
+#if __WIN32__
+	  if (isdigit(port[j][0])) {
+	    client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_net, (void *)j, 0, NULL);
+	  }
+	  else {
+	    client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_serial, (void *)j, 0, NULL);
+	  }
+	  if (client_th[j] == NULL) {
+	    printf ("Internal error: Could not create client thread %d.\n", j);
+	    exit (1);
+	  }
+#else
+	  if (isdigit(port[j][0])) {
+	    e = pthread_create (&client_tid[j], NULL, client_thread_net, (void *)(long)j);
+	  }
+	  else {
+	    e = pthread_create (&client_tid[j], NULL, client_thread_serial, (void *)(long)j);
+	  }
+	  if (e != 0) {
+	    perror("Internal error: Could not create client thread.");
+	    exit (1);
+	  }
+#endif
+	}
+
+	start_time = time(NULL);
+	next_print_time = start_time + (PRINT_MINUTES) * 60;
+
+/*
+ * Print results from clients. 
+ */
+	while (1) {
+	  int k;
+	  int something;	
+
+	  SLEEP_MS(100);
+	  
+	  something = 0;
+	  for (k=0; k<LINE_WIDTH && ! something; k++) {
+	    if (packets[k] != ' ') {
+	      something = 1;
+	    }
+	  }
+	  if (something) {
+	    /* time for others to catch up. */
+	    SLEEP_MS(200);
+
+	    printf ("%s\n", packets);
+	    memset (packets, ' ', (size_t)LINE_WIDTH);	
+	  }
+
+	  now = time(NULL);
+	  if (now >= next_print_time) {
+	    next_print_time = now + (PRINT_MINUTES) * 60;
+	
+	    printf ("\nTotals after %d minutes", (int)((now - start_time) / 60));
+
+	    for (j=0; j<num_clients; j++) {
+	      printf (", %s %d", description[j], packet_count[j]);
+	    }
+	    printf ("\n\n");
+	  }
+	}
+
+
+	return 0;  // unreachable
+}
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        client_thread_net
+ *
+ * Purpose:     Establish connection with a TNC via network.
+ *
+ * Inputs:	arg		- My instance index, 0 thru MAX_CLIENTS-1.
+ *
+ * Outputs:	packets		- Received packets are put in the corresponding column.
+ *
+ *--------------------------------------------------------------------*/
+
+#define MAX_HOSTS 30
+
+#if __WIN32__
+static unsigned __stdcall client_thread_net (void *arg)
+#else
+static void * client_thread_net (void *arg)	
+#endif	
+{
+	int my_index;
+	struct addrinfo hints;
+	struct addrinfo *ai_head = NULL;
+	struct addrinfo *ai;
+	struct addrinfo *hosts[MAX_HOSTS];
+	int num_hosts, n;
+	int err;
+	char ipaddr_str[46];		/* text form of IP address */
+#if __WIN32__
+	WSADATA wsadata;
+#endif
+/* 
+ * File descriptor for socket to server. 
+ * Set to -1 if not connected. 
+ * (Don't use SOCKET type because it is unsigned.) 
+*/
+	int server_sock = -1;	
+	struct agwpe_s mon_cmd;
+	char data[1024];
+	int use_chan = -1;
+
+
+	my_index = (int)(long)arg;
+
+#if DEBUGx
+        printf ("DEBUG: client_thread_net %d start, port = '%s'\n", my_index, port[my_index]);
+#endif
+
+#if __WIN32__
+	err = WSAStartup (MAKEWORD(2,2), &wsadata);
+	if (err != 0) {
+	    printf("WSAStartup failed: %d\n", err);
+	    return (0);
+	}
+
+	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+          printf("Could not find a usable version of Winsock.dll\n");
+          WSACleanup();
+	  //sleep (1);
+          return (0);
+	}
+#endif
+
+	memset (&hints, 0, sizeof(hints));
+
+	hints.ai_family = AF_UNSPEC;	/* Allow either IPv4 or IPv6. */
+	// hints.ai_family = AF_INET;	/* IPv4 only. */
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+/*
+ * Connect to TNC server.
+ */
+
+	ai_head = NULL;
+	err = getaddrinfo(hostname[my_index], port[my_index], &hints, &ai_head);
+	if (err != 0) {
+#if __WIN32__
+	  printf ("Can't get address for server %s, err=%d\n", 
+					hostname[my_index], WSAGetLastError());
+#else 
+	  printf ("Can't get address for server %s, %s\n", 
+					hostname[my_index], gai_strerror(err));
+#endif
+	  freeaddrinfo(ai_head);
+      	  exit (1);
+	}
+
+#if DEBUG_DNS
+	printf ("getaddrinfo returns:\n");
+#endif
+	num_hosts = 0;
+	for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
+#if DEBUG_DNS
+	  ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	  printf ("    %s\n", ipaddr_str);
+#endif
+	  hosts[num_hosts] = ai;
+	  if (num_hosts < MAX_HOSTS) num_hosts++;
+	}
+
+#if DEBUG_DNS
+	printf ("addresses for hostname:\n");
+	for (n=0; n<num_hosts; n++) {
+	  ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	  printf ("    %s\n", ipaddr_str);
+	}
+#endif
+
+	// Try each address until we find one that is successful.
+
+	for (n=0; n<num_hosts; n++) {
+	  int is;
+
+	  ai = hosts[n];
+
+	  ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	  is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+#if __WIN32__
+	  if (is == INVALID_SOCKET) {
+	    printf ("Socket creation failed, err=%d", WSAGetLastError());
+	    WSACleanup();
+	    is = -1;
+	    continue;
+	  }
+#else
+	  if (err != 0) {
+	    printf ("Socket creation failed, err=%s", gai_strerror(err));
+	    (void) close (is);
+	    is = -1;
+	    continue;
+	  }
+#endif
+
+#ifndef DEBUG_DNS 
+	  err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
+#if __WIN32__
+	  if (err == SOCKET_ERROR) {
+#if DEBUGx
+	    printf("Connect to %s on %s (%s), port %s failed.\n",
+					description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
+#endif
+	    closesocket (is);
+	    is = -1;
+	    continue;
+	  }
+#else
+	  if (err != 0) {
+#if DEBUGx
+	    printf("Connect to %s on %s (%s), port %s failed.\n",
+					description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
+#endif
+	    (void) close (is);
+	    is = -1;
+	    continue;
+	  }
+	  int flag = 1;
+	  err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
+	  if (err < 0) {
+	    printf("setsockopt TCP_NODELAY failed.\n");
+	  }
+#endif
+
+/* Success. */
+
+ 	  printf("Client %d now connected to %s on %s (%s), port %s\n", 
+			my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
+
+	  server_sock = is;
+#endif	  
+	  break;
+	}
+
+	freeaddrinfo(ai_head);
+
+	if (server_sock == -1) {
+
+ 	  printf("Client %d unable to connect to %s on %s (%s), port %s\n", 
+			my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
+	  exit (1);
+	}
+
+/*
+ * Send command to toggle reception of frames in raw format.
+ *
+ * Note: Monitor format is only for UI frames.
+ * It also discards the via path.
+ */
+
+	memset (&mon_cmd, 0, sizeof(mon_cmd));
+
+	mon_cmd.kind_lo = 'k';
+
+#if __WIN32__	      
+	send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
+#else
+	err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
+#endif
+
+
+/*
+ * Print all of the monitored packets.
+ */
+
+	while (1) {
+	  int n;
+
+#if __WIN32__
+	  n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
+#else
+	  n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
+#endif
+
+	  if (n != sizeof(mon_cmd)) {
+	    printf ("Read error, client %d received %d command bytes.\n", my_index, n);
+	    exit (1);
+	  }
+
+#if DEBUGx
+	  printf ("client %d received '%c' data, data_len = %d\n", 
+			my_index, mon_cmd.kind_lo, mon_cmd.data_len);
+#endif
+	  assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data));
+
+	  if (mon_cmd.data_len > 0) {
+#if __WIN32__
+	    n = recv (server_sock, data, mon_cmd.data_len, 0);
+#else
+	    n = read (server_sock, data, mon_cmd.data_len);
+#endif
+
+	    if (n != mon_cmd.data_len) {
+	      printf ("Read error, client %d received %d data bytes.\n", my_index, n);
+	      exit (1);
+	    }
+	  }
+
+/* 
+ * Print it and add to counter.
+ * The AGWPE score was coming out double the proper value because 
+ * we were getting the same thing from ports 2 and 3.
+ * 'use_chan' is the first channel we hear from.
+ * Listen only to that one.
+ */
+
+	  if (mon_cmd.kind_lo == 'K' && (use_chan == -1 || use_chan == mon_cmd.portx)) {
+	    packet_t pp;
+	    char *pinfo;
+	    int info_len;
+	    char result[400];
+	    char *p;
+	    int col, len;
+	    alevel_t alevel;
+
+	    //printf ("server %d, portx = %d\n", my_index, mon_cmd.portx);
+
+	    use_chan = mon_cmd.portx;
+	    memset (&alevel, 0xff, sizeof(alevel));
+	    pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
+	    assert (pp != NULL);
+	    ax25_format_addrs (pp, result);
+	    info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
+	    pinfo[info_len] = '\0';
+	    strlcat (result, pinfo, sizeof(result));
+	    for (p=result; *p!='\0'; p++) {
+	      if (! isprint(*p)) *p = ' ';
+	    }
+#if DEBUGx
+	    printf ("[%d] %s\n", my_index, result);
+#endif
+	    col = column_width * my_index;
+	    len = strlen(result);
+#define MARGIN 3
+	    if (len > column_width - 3) {
+	      len = column_width - 3;
+	    }
+	    if (packets[col] == ' ') {
+	      memcpy (packets+col, result, (size_t)len);
+	    }
+	    else {
+	      memcpy (packets+col, "OVERRUN!    ", (size_t)10);
+	    }
+	    	    
+	    ax25_delete (pp);
+	    packet_count[my_index]++;
+	  }
+	}
+
+} /* end client_thread_net */
+
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        client_thread_serial
+ *
+ * Purpose:     Establish connection with a TNC via serial port.
+ *
+ * Inputs:	arg		- My instance index, 0 thru MAX_CLIENTS-1.
+ *
+ * Outputs:	packets		- Received packets are put in the corresponding column.
+ *
+ *--------------------------------------------------------------------*/
+
+#if __WIN32__
+typedef HANDLE MYFDTYPE;
+#define MYFDERROR INVALID_HANDLE_VALUE
+#else
+typedef int MYFDTYPE;
+#define MYFDERROR (-1)
+#endif
+
+
+#if __WIN32__
+static unsigned __stdcall client_thread_serial (void *arg)
+#else
+static void * client_thread_serial (void *arg)	
+#endif	
+{
+	int my_index = (int)(long)arg;
+
+#if __WIN32__
+
+	MYFDTYPE fd;
+	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);
+
+	if (fd == MYFDERROR) {
+ 	  printf("Client %d unable to connect to %s on %s.\n", 
+			my_index, description[my_index], port[my_index] );
+	  exit (1);
+	}
+
+	/* 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) {
+	  printf ("GetCommState failed.\n");
+	}
+
+	/* 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;
+	dcb.fOutxCtsFlow = 0;
+	dcb.fOutxDsrFlow = 0;
+	dcb.fDtrControl = 0;
+	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) {
+	  printf ("SetCommState failed.\n");
+	}
+
+#else
+
+/* Linux version. */
+
+	int fd;
+	struct termios ts;
+	int e;
+
+	fd = open (port[my_index], O_RDWR);
+
+	if (fd == MYFDERROR) {
+ 	  printf("Client %d unable to connect to %s on %s.\n", 
+			my_index, description[my_index], port[my_index] );
+	  exit (1);
+	}
+
+	e = tcgetattr (fd, &ts);
+	if (e != 0) { perror ("nm tcgetattr"); }
+
+	cfmakeraw (&ts);
+	
+	// TODO: speed?
+	ts.c_cc[VMIN] = 1;	/* wait for at least one character */
+	ts.c_cc[VTIME] = 0;	/* no fancy timing. */
+
+	e = tcsetattr (fd, TCSANOW, &ts);
+	if (e != 0) { perror ("nm tcsetattr"); }
+#endif
+
+
+/* Success. */
+
+ 	printf("Client %d now connected to %s on %s\n", 
+			my_index, description[my_index], port[my_index] );
+
+/*
+ * Assume we are already in monitor mode.
+ */
+
+
+/*
+ * Print all of the monitored packets.
+ */
+
+	while (1) {
+	  unsigned char ch;
+	  char result[500];
+	  int col, len;
+	  int done;
+	  char *p;
+
+	  len = 0;
+	  done = 0;
+
+	  while ( ! done) {
+
+#if __WIN32__
+	    DWORD n;
+
+	    if (! ReadFile (fd, &ch, 1, &n, NULL)) {
+	      printf ("Read error on %s.\n", description[my_index]);
+	      CloseHandle (fd);
+	      exit (1);
+	    }
+	  
+#else
+	    int n;
+
+	    if ( ( n = read(fd, & ch, 1)) < 0) {
+	      printf ("Read error on %s.\n", description[my_index]);
+	      close (fd);
+	      exit (1);
+	    }
+#endif
+	    if (n == 1) {
+
+/*
+ * Try to build one line for each packet.
+ * The KPC3+ breaks a packet into two lines like this:
+ *
+ *	KB1ZXL-1>T2QY5P,W1MHL*,WIDE2-1: <<UI>>:
+ *	`c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1=
+ *
+ *	N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: <UI>:
+ * 	!4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100
+ *
+ * Don't know why some are <<UI>> and some <UI>.
+ *
+ * Anyhow, ignore the return character if preceded by >:
+ */
+	      if (ch == '\r') { 
+	        if (len >= 10 && result[len-2] == '>' && result[len-1] == ':') {
+	          continue;
+	        }
+	        done = 1; 
+	        continue; 
+	      }
+	      if (ch == '\n') continue;
+	      result[len++] = ch;
+	    }
+	  }
+	  result[len] = '\0';
+
+/* 
+ * Print it and add to counter.
+ */
+	 if (len > 0) {
+	  /* Blank any unprintable characters. */
+	  for (p=result; *p!='\0'; p++) {
+	    if (! isprint(*p)) *p = ' ';
+	  }
+#if DEBUGx
+	  printf ("[%d] %s\n", my_index, result);
+#endif
+	  col = column_width * my_index;
+	  if (len > column_width - 3) {
+	    len = column_width - 3;
+	  }
+	  if (packets[col] == ' ') {
+	    memcpy (packets+col, result, (size_t)len);
+	  }
+	  else {
+	    memcpy (packets+col, "OVERRUN!    ", (size_t)10);
+	  }
+	  packet_count[my_index]++;
+         }
+	}
+
+} /* end client_thread_serial */
+
+/* end aclients.c */
diff --git a/aprs_tt.c b/aprs_tt.c
index a9fd1e0..cf4e1c7 100644
--- a/aprs_tt.c
+++ b/aprs_tt.c
@@ -1,1568 +1,2013 @@
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013, 2014, 2015  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/>.
-//
-
-/*------------------------------------------------------------------
- *
- * Module:      aprs_tt.c
- *
- * Purpose:   	APRStt gateway.
- *		
- * Description: Transfer touch tone messages into the APRS network.
- *
- * References:	This is based upon APRStt (TM) documents with some
- *		artistic freedom.
- *
- *		http://www.aprs.org/aprstt.html
- *
- *---------------------------------------------------------------*/
-
-#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.
-// What do we call the parts separated by * key?   Entry?  Field?
-
-
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-
-
-#include <ctype.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "version.h"
-#include "ax25_pad.h"
-#include "hdlc_rec2.h"		/* for process_rec_frame */
-#include "textcolor.h"
-#include "aprs_tt.h"
-#include "tt_text.h"
-#include "tt_user.h"
-#include "symbols.h"
-#include "latlong.h"
-#include "dlq.h"
-#include "demod.h"          /* for alevel_t & demod_get_audio_level() */
-
-#if __WIN32__
-char *strtok_r(char *str, const char *delim, char **saveptr);
-#endif
-
-// geotranz
-
-#include "utm.h"
-#include "mgrs.h"
-#include "usng.h"
-#include "error_string.h"
-
-/* Convert between degrees and radians. */
-
-#define D2R(d) ((d) * M_PI / 180.)
-#define R2D(r) ((r) * 180. / M_PI)
-
-
-/*
- * Touch Tone sequences are accumulated here until # terminator found.
- * Kept separate for each audio channel so the gateway CAN be listening
- * on multiple channels at the same time.
- */
-
-#define MAX_MSG_LEN 100
-
-static char msg_str[MAX_CHANS][MAX_MSG_LEN+1];
-static int msg_len[MAX_CHANS];
-
-static int parse_fields (char *msg);
-static int parse_callsign (char *e);
-static int parse_object_name (char *e);
-static int parse_symbol (char *e);
-static int parse_location (char *e);
-static int parse_comment (char *e);
-static int expand_macro (char *e);
-static void raw_tt_data_to_app (int chan, char *msg);
-static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr);
-
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        aprs_tt_init
- *
- * Purpose:     Initialize the APRStt gateway at system startup time.
- *
- * Inputs:      Configuration options gathered by config.c.
- *
- * Global out:	Make our own local copy of the structure here.
- *
- * Returns:     None
- *
- * Description:	The main program needs to call this at application
- *		start up time after reading the configuration file.
- *
- *		TT_MAIN is defined for unit testing.
- *
- *----------------------------------------------------------------*/
-
-static struct tt_config_s tt_config;
-
-#if TT_MAIN
-#define NUM_TEST_CONFIG (sizeof(test_config) / sizeof (struct ttloc_s))
-static struct ttloc_s test_config[] = {
-
-	{ TTLOC_POINT, "B01", .point.lat = 12.25, .point.lon = 56.25 },
-	{ TTLOC_POINT, "B988", .point.lat = 12.50, .point.lon = 56.50 },
-
-	{ TTLOC_VECTOR, "B5bbbdddd", .vector.lat = 53., .vector.lon = -1., .vector.scale = 1000. },  /* km units */
-
-	/* Hilltop Tower http://www.aprs.org/aprs-jamboree-2013.html */
-	{ TTLOC_VECTOR, "B5bbbddd", .vector.lat = 37+55.37/60., .vector.lon = -(81+7.86/60.), .vector.scale = 16.09344 },   /* .01 mile units */
-
-	{ TTLOC_GRID, "B2xxyy", .grid.lat0 = 12.00, .grid.lon0 = 56.00, 
-				.grid.lat9 = 12.99, .grid.lon9 = 56.99 },
-	{ TTLOC_GRID, "Byyyxxx", .grid.lat0 = 37 + 50./60.0, .grid.lon0 = 81, 
-				.grid.lat9 = 37 + 59.99/60.0, .grid.lon9 = 81 + 9.99/60.0 },
-
-	{ TTLOC_SATSQ, "BAxxxx" },
-
-	{ TTLOC_MACRO, "xxyyy", .macro.definition = "B9xx*AB166*AA2B4C5B3B0Ayyy" },
-	{ TTLOC_MACRO, "xxxxzzzzzzzzzz", .macro.definition = "BAxxxx*ACzzzzzzzzzz" },
-}; 
-#endif
-
-
-void aprs_tt_init (struct tt_config_s *p)
-{
-	int c;
-
-#if TT_MAIN
-	/* For unit testing. */
-
-	memset (&tt_config, 0, sizeof(struct tt_config_s));	
-	tt_config.ttloc_size = NUM_TEST_CONFIG;
-	tt_config.ttloc_ptr = test_config;
-	tt_config.ttloc_len = NUM_TEST_CONFIG;
-
-	/* Don't care about xmit timing or corral here. */
-#else
-	// TODO: Keep ptr instead of making a copy.
-	memcpy (&tt_config, p, sizeof(struct tt_config_s));
-#endif
-	for (c=0; c<MAX_CHANS; c++) {	
-	  msg_len[c] = 0;
-	  msg_str[c][0] = '\0';
-	}
-
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        aprs_tt_button
- *
- * Purpose:     Process one received button press.
- *
- * Inputs:      chan		- Audio channel it came from.
- *
- *		button		0123456789ABCD*#	- Received button press.
- *				$			- No activity timeout.
- *				space			- Quiet time filler.
- *
- * Returns:     None
- *
- * Description:	Individual key presses are accumulated here until
- *		the # message terminator is found.
- *		The complete message is then processed.
- *		The touch tone decoder sends $ if no activity
- *		for some amount of time, perhaps 5 seconds.
- *		A partially accumulated messge is discarded if
- *		there is a long gap.
- *
- *		'.' means no activity during processing period.
- *		space, between blocks, shouldn't get here.
- *
- *----------------------------------------------------------------*/
-
-#ifndef TT_MAIN
-
-void aprs_tt_button (int chan, char button)
-{
-	static int poll_period = 0;
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-
-
-	//if (button != '.') {
-	//  dw_printf ("aprs_tt_button (%d, '%c')\n", chan, button);
-	//}
-
-
-// TODO:  Might make more sense to put timeout here rather in the dtmf decoder.
-
-	if (button == '$') {
-
-/* Timeout reset. */
-
-	  msg_len[chan] = 0;
-	  msg_str[chan][0] = '\0';
-	}
-	else if (button != '.' && button != ' ') {
-	  if (msg_len[chan] < MAX_MSG_LEN) {
-	    msg_str[chan][msg_len[chan]++] = button;
-	    msg_str[chan][msg_len[chan]] = '\0';
-	  }
-	  if (button == '#') {
-
-/* 
- * Put into the receive queue like any other packet.
- * This way they are all processed by the common receive thread
- * rather than the thread associated with the particular audio device.
- */
-	    raw_tt_data_to_app (chan, msg_str[chan]);
-
-	    msg_len[chan] = 0;
-	    msg_str[chan][0] = '\0';
-	  }
-	}
-	else {
-
-/* 
- * Idle time. Poll occasionally for processing. 
- * Timing would be off we we are listening to more than
- * one channel so do this only for the one specified
- * in the TTOBJ command. 
- */
-
-	  if (chan == tt_config.obj_recv_chan) {	  
-	    poll_period++;
-	    if (poll_period >= 39) {
-	      poll_period = 0;
-	      tt_user_background ();
-	    }
-	  }
-	}	
-  
-} /* end aprs_tt_button */
-
-#endif
-
-/*------------------------------------------------------------------
- *
- * Name:        aprs_tt_sequence
- *
- * Purpose:     Process complete received touch tone sequence
- *		terminated by #.
- *
- * Inputs:      chan		- Audio channel it came from.
- *
- *		msg		- String of DTMF buttons.
- *				  # should be the final character.
- *
- * Returns:     None
- *
- * Description:	Process a complete message.
- *		It should have one or more fields separatedy by *
- *		and terminated by a final # like these:
- *
- *		callsign #
- *		entry1 * callsign #
- *		entry1 * entry * callsign #
- *
- * Limitation:	Has one set of static data for communication among
- *		group of functions.  This shouldn't be a problem
- *		when receiving on multiple channels at once 
- *		because they get serialized thru the receive packet queue.
- *
- *----------------------------------------------------------------*/
-
-static char m_callsign[20];	/* really object name */
-
-/*
- * Standard APRStt has symbol code 'A' (box) with overlay of 0-9, A-Z. 
- *
- * Dire Wolf extension allows:
- *	Symbol table '/' (primary), any symbol code.
- *	Symbol table '\' (alternate), any symbol code.
- *	Alternate table symbol code, overlay of 0-9, A-Z.
- */
-
-static char m_symtab_or_overlay;
-static char m_symbol_code;
-
-static double m_longitude;
-static double m_latitude;
-static char m_comment[200];
-static char m_freq[12];
-static char m_mic_e;
-static char m_dao[6];
-static int m_ssid;
-
-//#define G_UNKNOWN -999999
-
-
-
-void aprs_tt_sequence (int chan, char *msg)
-{
-	int err;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("\n\"%s\"\n", msg);
-#endif
-
-/* 
- * Discard empty message. 
- * In case # is there as optional start. 
- */
-
-	if (msg[0] == '#') return;
-
-/*
- * The parse functions will fill these in. 
- */
-	strcpy (m_callsign, "");
-	m_symtab_or_overlay = '\\';
-	m_symbol_code = 'A';
-	m_longitude = G_UNKNOWN; 
-	m_latitude = G_UNKNOWN; 
-	strcpy (m_comment, "");
-	strcpy (m_freq, "");
-	m_mic_e = ' ';
-	strcpy (m_dao, "!T  !");	/* start out unknown */
-	m_ssid = 12;
-
-/*
- * Parse the touch tone sequence.
- */
-	err = parse_fields (msg);
-
-#if defined(DEBUG) || defined(TT_MAIN)
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n", 
-		m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_comment, m_latitude, m_longitude, m_dao);
-#endif
-
-
-	if (err == 0) {
-
-/*
- * Digested successfully.  Add to our list of users and schedule transmissions.
- */
-
-#ifndef TT_MAIN
-	  err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_latitude, m_longitude,
-		m_freq, m_comment, m_mic_e, m_dao);
-#endif
-	}
-
-	// TODO send response to user.
-	// err == 0 OK, others, suitable error response.
-
-
-} /* end aprs_tt_sequence */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_fields
- *
- * Purpose:     Separate the complete string of touch tone characters
- *		into fields, delimited by *, and process each.
- *
- * Inputs:      msg		- String of DTMF buttons.
- *
- * Returns:     None
- *
- * Description:	It should have one or more fields separated by *.
- *
- *		callsign #
- *		entry1 * callsign #
- *		entry1 * entry * callsign #
- *
- *		Note that this will be used recursively when macros
- *		are expanded.
- *
- *		"To iterate is human, to recurse divine."
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- *----------------------------------------------------------------*/
-
-static int parse_fields (char *msg)
-{
-	char stemp[MAX_MSG_LEN+1];
-	char *e;
-	char *save;
-
-	strcpy (stemp, msg);
-	e = strtok_r (stemp, "*#", &save);
-	while (e != NULL) {
-
-	  switch (*e) {
-
-	    case 'A': 
-	      
-	      switch (e[1]) {
-	        case 'A':
-	          parse_object_name (e);
-	          break;
-	        case 'B':
-	          parse_symbol (e);
-	          break;
-	        case 'C':
-	          /*
-	           * New in 1.2: test for 10 digit callsign.
-	           */
-	          if (tt_call10_to_text(e+2,1,stemp) == 0) {
-	             strcpy(m_callsign, stemp);
-	          }
-	          break;
-	        default:
-	          parse_callsign (e);
-	          break;
-	      }
-	      break;
-
-	    case 'B': 
-	      parse_location (e);
-	      break;
-
-	    case 'C': 
-	      parse_comment (e);
-	      break;
-
-	    case '0': 
-	    case '1': 
-	    case '2': 
-	    case '3': 
-	    case '4': 
-	    case '5': 
-	    case '6': 
-	    case '7': 
-	    case '8': 
-	    case '9': 
-	      expand_macro (e);
-	      break;
-
-	    case '\0':
-	      /* Empty field.  Just ignore it. */
-	      /* This would happen if someone uses a leading *. */
-	      break;
-
-	    default:
-
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Entry does not start with A, B, C, or digit: \"%s\"\n", msg);
-	      return (TT_ERROR_D_MSG);
-
-	  }
-	
-	  e = strtok_r (NULL, "*#", &save);
-	}
-
-	return (0);
-
-} /* end parse_fields */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        expand_macro
- *
- * Purpose:     Expand compact form "macro" to full format then process.
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should contain only digits.
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- * Description:	Separate out the fields, perform substitution, 
- *		call parse_fields for processing.
- *
- *
- * Future:	Generalize this to allow any lower case letter for substitution?
- *
- *----------------------------------------------------------------*/
-
-static int expand_macro (char *e) 
-{
-	int len;
-	int ipat;
-	char xstr[20], ystr[20], zstr[20], bstr[20], dstr[20];
-	char stemp[MAX_MSG_LEN+1];
-	char *d, *s;
-
-
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("Macro tone sequence: '%s'\n", e);
-
-	len = strlen(e);
-
-	ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr);
-
-	if (ipat >= 0) {
-
-	  // Why did we print b & d here?
-	  // Documentation says only x, y, z can be used with macros.
-	  // Only those 3 are processed below.
-
-	  //dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s, b=%s, d=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr, bstr, dstr);
-	  dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr);
-
-	  dw_printf ("Replace with:        '%s'\n", tt_config.ttloc_ptr[ipat].macro.definition);
-
-	  if (tt_config.ttloc_ptr[ipat].type != TTLOC_MACRO) {
-
-	     /* Found match to a different type.  Really shouldn't be here. */
-	     /* Print internal error message... */
-	     dw_printf ("expand_macro: type != TTLOC_MACRO\n");
-	     return (TT_ERROR_INTERNAL);
-	  }
-
-/*
- * We found a match for the length and any fixed digits.
- * Substitute values in to the definition.
- */		
-	  
-	  strcpy (stemp, "");
-
-	  for (d = tt_config.ttloc_ptr[ipat].macro.definition; *d != '\0'; d++) {
-
-	    while (( *d == 'x' || *d == 'y' || *d == 'z') && *d == d[1]) {
-	      /* Collapse adjacent matching substitution characters. */
-	      d++;
-	    }
-
-	    switch (*d) {
-	      case 'x':
-		strcat (stemp, xstr);
-	        break;
-	      case 'y':
-		strcat (stemp, ystr);
-	        break;
-	      case 'z':
-		strcat (stemp, zstr);
-	        break;
-	      default:
-		{
-	        char c1[2];
-	        c1[0] = *d;
-	        c1[1] = '\0';
-	        strcat (stemp, c1); 
-		}
-		break;
-	    }
-	  }
-/*
- * Process as if we heard this over the air.
- */
-
-	  dw_printf ("After substitution:  '%s'\n", stemp);
-	  return (parse_fields (stemp));
-	}
-
-	else {
-
-
-	  /* Send reject sound. */
-	  /* Does not match any macro definitions. */
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Tone sequence did not match any pattern\n");
-	  return (TT_ERROR_MACRO_NOMATCH);
-	}
-	
-	/* should be unreachable */
-	return (0);
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_callsign
- *
- * Purpose:     Extract callsign or object name from touch tone message. 
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should start with "A".
- *
- * Outputs:	m_callsign
- *
- *		m_symtab_or_overlay - Set to 0-9 or A-Z if specified.
- *
- *		m_symbol_code	- Always set to 'A'.
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- * Description:	We recognize 3 different formats:
- *
- *		Annn		- 3 digits are a tactical callsign.  No overlay.
- *
- *		Annnvk		- Abbreviation with 3 digits, numeric overlay, checksum.
- *		Annnvvk		- Abbreviation with 3 digits, letter overlay, checksum.
- *
- *		Att...ttvk	- Full callsign in two key method, numeric overlay, checksum.
- *		Att...ttvvk	- Full callsign in two key method, letter overlay, checksum.
- *		
- *
- *----------------------------------------------------------------*/
-
-static int checksum_not_ok (char *str, int len, char found)
-{
-	int i;
-	int sum;
-	char expected;
-
-	sum = 0;
-	for (i=0; i<len; i++) {
-	  if (isdigit(str[i])) {
-	    sum += str[i] - '0';
-	  }
-	  else if (str[i] >= 'A' && str[i] <= 'D') {
-	    sum += str[i] - 'A' + 10;
-	  }
-	  else {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("aprs_tt: checksum: bad character \"%c\" in checksum calculation!\n", str[i]);
-	  }
-	}
-	expected =  '0' + (sum % 10);
-
-	if (expected != found) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Bad checksum for \"%.*s\".  Expected %c but received %c.\n", len, str, expected, found);
-	    return (TT_ERROR_BAD_CHECKSUM);
-	}
-	return (0);
-}
-
-
-static int parse_callsign (char *e)
-{
-	int len;
-	int c_length;
-	char tttemp[40], stemp[30];
-
-	assert (*e == 'A');
-
-	len = strlen(e);
-
-/*
- * special case: 3 digit tactical call.
- */
-
-	if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) {
-	  strcpy (m_callsign, e+1);
-	  return (0);
-	}
-
-/* 
- * 3 digit abbreviation:  We only do the parsing here.
- * Another part of application will try to find corresponding full call.
- */
-
-	if ((len == 6 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5])) ||
-	    (len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isupper(e[5]) && isdigit(e[6]))) {
-
-	  int cs_err = checksum_not_ok (e+1, len-2, e[len-1]);
-
-	  if (cs_err != 0) {
-	    return (cs_err);
-	  }
-
-	  strncpy (m_callsign, e+1, 3);
-	  m_callsign[3] = '\0';
-	
-	  if (len == 7) {
-	    tttemp[0] = e[len-3];
-	    tttemp[1] = e[len-2];
-	    tttemp[2] = '\0';
-	    tt_two_key_to_text (tttemp, 0, stemp);
-	    m_symbol_code = 'A';
-	    m_symtab_or_overlay = stemp[0];
-	  }
-	  else {
-	    m_symbol_code = 'A';
-	    m_symtab_or_overlay = e[len-2];
-	  }
-	  return (0);
-	}
-
-/* 
- * Callsign in two key format.
- */
-
-	if (len >= 7 && len <= 24) {
-
-	  int cs_err = checksum_not_ok (e+1, len-2, e[len-1]);
-
-	  if (cs_err != 0) {
-	    return (cs_err);
-	  }
-
-	
-	  if (isupper(e[len-2])) {
-	    strncpy (tttemp, e+1, len-4);
-	    tttemp[len-4] = '\0';
-	    tt_two_key_to_text (tttemp, 0, m_callsign);
-
-	    tttemp[0] = e[len-3];
-	    tttemp[1] = e[len-2];
-	    tttemp[2] = '\0';
-	    tt_two_key_to_text (tttemp, 0, stemp);
-	    m_symbol_code = 'A';
-	    m_symtab_or_overlay = stemp[0];
-	  }
-	  else {
-	    strncpy (tttemp, e+1, len-3);
-	    tttemp[len-3] = '\0';
-	    tt_two_key_to_text (tttemp, 0, m_callsign);
-
-	    m_symbol_code = 'A';
-	    m_symtab_or_overlay = e[len-2];
-	  }
-	  return (0);
-	}
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Touch tone callsign not valid: \"%s\"\n", e);
-	return (TT_ERROR_INVALID_CALL);
-}
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_object_name 
- *
- * Purpose:     Extract object name from touch tone message. 
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should start with "AA".
- *
- * Outputs:	m_callsign
- *
- *		m_ssid		- Cleared to remove the default of 12.
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- * Description:	Data format
- *
- *		AAtt...tt	- Symbol name, two key method, up to 9 characters.
- *
- *----------------------------------------------------------------*/
-
-
-static int parse_object_name (char *e)
-{
-	int len;
-	int c_length;
-	char tttemp[40], stemp[30];
-
-	assert (e[0] == 'A');
-	assert (e[1] == 'A');
-
-	len = strlen(e);
-
-/* 
- * Object name in two key format.
- */
-
-	if (len >= 2 + 1 && len <= 30) {
-
-	  if (tt_two_key_to_text (e+2, 0, m_callsign) == 0) {
-	    m_callsign[9] = '\0';  /* truncate to 9 */
-	    m_ssid = 0;		/* No ssid for object name */
-	    return (0);
-	  }
-	}
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Touch tone object name not valid: \"%s\"\n", e);
-
-	return (TT_ERROR_INVALID_OBJNAME);
-
-}  /* end parse_oject_name */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_symbol 
- *
- * Purpose:     Extract symbol from touch tone message. 
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should start with "AB".
- *
- * Outputs:	m_symtab_or_overlay
- *
- * 		m_symbol_code
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- * Description:	Data format
- *
- *		AB1nn		- Symbol from primary symbol table.  
- *				  Two digits nn are the same as in the GPSCnn 
- *				  generic address used as a destination. 
- *
- *		AB2nn		- Symbol from alternate symbol table.  
- *				  Two digits nn are the same as in the GPSEnn 
- *				  generic address used as a destination. 
- *
- *		AB0nnvv		- Symbol from alternate symbol table.  
- *				  Two digits nn are the same as in the GPSEnn 
- *				  generic address used as a destination.
- *	 			  vv is an overlay digit or letter in two key method.
- *
- *----------------------------------------------------------------*/
-
-
-static int parse_symbol (char *e)
-{
-	int len;
-	char nstr[4];
-	int nn;
-	char stemp[10];
-
-	assert (e[0] == 'A');
-	assert (e[1] == 'B');
-
-	len = strlen(e);
-
-	if (len >= 4 && len <= 10) {
-
-	  nstr[0] = e[3];
-	  nstr[1] = e[4];
-	  nstr[2] = '\0';
-
-	  nn = atoi (nstr);
-	  if (nn < 1) {
-	    nn = 1;
-	  }
-	  else if (nn > 94) {
-	    nn = 94;
-	  }
-
-	  switch (e[2]) {
-
-	    case '1':
-	      m_symtab_or_overlay = '/';
-	      m_symbol_code = 32 + nn;
-	      return (0);
-	      break;
-
-	    case '2':
-	      m_symtab_or_overlay = '\\';
-	      m_symbol_code = 32 + nn;
-	      return (0);
-	      break;
-
-	    case '0':
-	      if (len >= 6) {
-	        if (tt_two_key_to_text (e+5, 0, stemp) == 0) {
-	          m_symbol_code = 32 + nn;
-	          m_symtab_or_overlay = stemp[0];
-	          return (0);
-	        }
-	      }
-	      break;
-	  }
-	}
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Touch tone symbol not valid: \"%s\"\n", e);
-
-	return (TT_ERROR_INVALID_SYMBOL);
-
-}  /* end parse_oject_name */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_location
- *
- * Purpose:     Extract location from touch tone message. 
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should start with "B".
- *
- * Outputs:	m_latitude
- *		m_longitude
- *		m_dao
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- * Description:	There are many different formats recognizable
- *		by total number of digits and sometimes the first digit.
- *
- *		We handle most of them in a general way, processing
- *		them in 5 groups:
- *
- *		* points
- *		* vector
- *		* grid
- *		* utm
- *		* usng / mgrs
- *
- *----------------------------------------------------------------*/
-
-
-
-/* Average radius of earth in meters. */
-#define R 6371000.
-
-
-
-
-static int parse_location (char *e)
-{
-	int len;
-	int ipat;
-	char xstr[20], ystr[20], zstr[20], bstr[20], dstr[20];
-	double x, y, dist, bearing;
-	double lat0, lon0;
-	double lat9, lon9;
-	long lerr;	
-	double easting, northing;
-	char mh[20];	
-
-	assert (*e == 'B');
-
-	m_dao[2] = e[0];
-	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);
-	if (ipat >= 0) {
-
-	  //dw_printf ("ipat=%d, x=%s, y=%s, b=%s, d=%s\n", ipat, xstr, ystr, bstr, dstr);
-
-	  switch (tt_config.ttloc_ptr[ipat].type) {
-	    case TTLOC_POINT:
-		
-	      m_latitude = tt_config.ttloc_ptr[ipat].point.lat;
-	      m_longitude = tt_config.ttloc_ptr[ipat].point.lon;
-
-	      /* Is it one of ten or a hundred positions? */
-	      /* It's not hardwired to always be B0n or B9nn.  */
-	      /* This is a pretty good approximation. */
-
-	      if (strlen(e) == 3) {	/* probably B0n -->  !Tn ! */
-		m_dao[2] = e[2];
-	        m_dao[3] = ' ';
-	      }
-	      if (strlen(e) == 4) {	/* probably B9nn -->  !Tnn! */
-		m_dao[2] = e[2];
-	        m_dao[3] = e[3];
-	      }
-	      break;
-
-	    case TTLOC_VECTOR:
-
-	      if (strlen(bstr) != 3) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Bearing \"%s\" should be 3 digits.\n", bstr);
-	        // return error code?
-	      }
-	      if (strlen(dstr) < 1) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Distance \"%s\" should 1 or more digits.\n", dstr);
-	        // return error code?
-	      }
-
-	      lat0 = D2R(tt_config.ttloc_ptr[ipat].vector.lat);
-	      lon0 = D2R(tt_config.ttloc_ptr[ipat].vector.lon);
-	      dist = atof(dstr) * tt_config.ttloc_ptr[ipat].vector.scale;
-	      bearing = D2R(atof(bstr));
-
-	      /* Equations and caluculators found here: */
-	      /* http://movable-type.co.uk/scripts/latlong.html */
-	      /* This should probably be a function in latlong.c in case we have another use for it someday. */
-
-	      m_latitude = R2D(asin(sin(lat0) * cos(dist/R) + cos(lat0) * sin(dist/R) * cos(bearing)));
-
-	      m_longitude = R2D(lon0 + atan2(sin(bearing) * sin(dist/R) * cos(lat0),
-				  cos(dist/R) - sin(lat0) * sin(D2R(m_latitude))));
-	      break;
-
-	    case TTLOC_GRID:
-
-	      if (strlen(xstr) == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Missing X coordinate.\n");
-		strcpy (xstr, "0");
-	      }
-	      if (strlen(ystr) == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Missing Y coordinate.\n");
-		strcpy (ystr, "0");
-	      }
-
-	      lat0 = tt_config.ttloc_ptr[ipat].grid.lat0;
-	      lat9 = tt_config.ttloc_ptr[ipat].grid.lat9;
-	      y = atof(ystr);
-	      m_latitude = lat0 + y * (lat9-lat0) / (pow(10., strlen(ystr)) - 1.);
-
-	      lon0 = tt_config.ttloc_ptr[ipat].grid.lon0;
-	      lon9 = tt_config.ttloc_ptr[ipat].grid.lon9;
-	      x = atof(xstr);
-	      m_longitude = lon0 + x * (lon9-lon0) / (pow(10., strlen(xstr)) - 1.);
-
-	      break;
-
-	    case TTLOC_UTM:
-
-	      if (strlen(xstr) == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Missing X coordinate.\n");
-	        /* Avoid divide by zero later.  Put in middle of range. */
-		strcpy (xstr, "5");
-	      }
-	      if (strlen(ystr) == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Missing Y coordinate.\n");
-	        /* Avoid divide by zero later.  Put in middle of range. */
-		strcpy (ystr, "5");
-	      }
-
-	      x = atof(xstr);
-	      easting = x * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.x_offset;
-
-	      y = atof(ystr);
-	      northing = y * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.y_offset;
-		
-              lerr = Convert_UTM_To_Geodetic(tt_config.ttloc_ptr[ipat].utm.lzone, 
-			tt_config.ttloc_ptr[ipat].utm.hemi, easting, northing, &lat0, &lon0);
-
-              if (lerr == 0) {
-                m_latitude = R2D(lat0);
-                m_longitude = R2D(lon0);
-
-                //printf ("DEBUG: from UTM, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude);
-              }
-              else {
-	        char message[300];
-
-		text_color_set(DW_COLOR_ERROR);
-	        utm_error_string (lerr, message);
-                dw_printf ("Conversion from UTM failed:\n%s\n\n", message);
-              }
-
-	      break;
-
-
-	    case TTLOC_MGRS:
-	    case TTLOC_USNG:
-
-	      if (strlen(xstr) == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("MGRS/USNG: Missing X (easting) coordinate.\n");
-	        /* Should not be possible to get here. Fake it and carry on. */
-		strcpy (xstr, "5");
-	      }
-	      if (strlen(ystr) == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("MGRS/USNG: Missing Y (northing) coordinate.\n");
-	        /* Should not be possible to get here. Fake it and carry on. */
-		strcpy (ystr, "5");
-	      }
-
-	      char loc[40];
-	
-	      strcpy (loc, tt_config.ttloc_ptr[ipat].mgrs.zone);
-	      strcat (loc, xstr);
-	      strcat (loc, ystr);
-
-	      //text_color_set(DW_COLOR_DEBUG);
-	      //dw_printf ("MGRS/USNG location debug:  %s\n", loc);
-
-	      if (tt_config.ttloc_ptr[ipat].type == TTLOC_MGRS)
-                lerr = Convert_MGRS_To_Geodetic(loc, &lat0, &lon0);
-	      else
-                lerr = Convert_USNG_To_Geodetic(loc, &lat0, &lon0);
-
-
-              if (lerr == 0) {
-                m_latitude = R2D(lat0);
-                m_longitude = R2D(lon0);
-
-                //printf ("DEBUG: from MGRS/USNG, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude);
-              }
-              else {
-	        char message[300];
-
-		text_color_set(DW_COLOR_ERROR);
-	        mgrs_error_string (lerr, message);
-                dw_printf ("Conversion from MGRS/USNG failed:\n%s\n\n", message);
-              }
-	      break;
-
-	    case TTLOC_SATSQ:
-
-	      if (strlen(xstr) != 4) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Expected 4 digits for the Satellite Square.\n");
-	        return (TT_ERROR_SATSQ);
-	      }
-
-	      /* Convert 4 digits to usual AA99 form, then to location. */
-
-	      if (tt_gridsquare_to_text (xstr, 0, mh)  == 0) {
-		ll_from_grid_square (mh, &m_latitude, &m_longitude);
-	      }
-	      break;
-
-
-	    default:
-	      assert (0);
-	  }
-	  return (0);
-	}
-
-	/* Send reject sound. */
-	/* Does not match any location specification. */
-
-	return (TT_ERROR_INVALID_LOC);
-
-} /* end parse_location */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        find_ttloc_match
- *
- * Purpose:     Try to match the received position report to a pattern
- *		defined in the configuration file.
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should start with "B".
- *
- * Outputs:	xstr		- All digits matching x positions in configuration.
- *		ystr		-                     y 
- *		zstr		-                     z 
- *		bstr		-                     b
- * 		dstr		-                     d
- *
- * Returns:     >= 0 for index into table if found.
- *		-1 if not found.
- *
- * Description:	
- *
- *----------------------------------------------------------------*/
-
-static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr)
-{
-	int ipat;	/* Index into patterns from configuration file */
-	int len;	/* Length of pattern we are trying to match. */
-	int match;
-	char mc;
-	int k;
-
-	// debug dw_printf ("find_ttloc_match: e=%s\n", e);
-
-	for (ipat=0; ipat<tt_config.ttloc_len; ipat++) {
-	  
-	  len = strlen(tt_config.ttloc_ptr[ipat].pattern);
-
-	  if (strlen(e) == len) {
-
-	    match = 1;
-	    strcpy (xstr, "");
-	    strcpy (ystr, "");
-	    strcpy (zstr, "");
-	    strcpy (bstr, "");
-	    strcpy (dstr, "");
-
-	    for (k=0; k<len; k++) {
-	      mc = tt_config.ttloc_ptr[ipat].pattern[k];
-	      switch (mc) {
-
-	        case 'B':
-	        case '0':
-	        case '1':
-	        case '2':
-	        case '3':
-	        case '4':
-	        case '5':
-	        case '6':
-	        case '7':
-	        case '8':
-	        case '9':
-	        case 'A':	/* Allow A,C,D after the B? */
-	        case 'C':
-	        case 'D':
-
-		  if (e[k] != mc) {
-		    match = 0;
-		  }
-		  break;
-
-		case 'x':
-		   if (isdigit(e[k])) {
-	             char stemp[2];
-		     stemp[0] = e[k];
-		     stemp[1] = '\0';
-		     strcat (xstr, stemp);
-		   }
-		   else {
-		     match = 0;
-	           }
-		  break;
-
-		case 'y':
-		   if (isdigit(e[k])) {
-	             char stemp[2];
-		     stemp[0] = e[k];
-		     stemp[1] = '\0';
-		     strcat (ystr, stemp);
-		   }
-		   else {
-		     match = 0;
-	           }
-		  break;
-
-		case 'z':
-		   if (isdigit(e[k])) {
-	             char stemp[2];
-		     stemp[0] = e[k];
-		     stemp[1] = '\0';
-		     strcat (zstr, stemp);
-		   }
-		   else {
-		     match = 0;
-	           }
-		  break;
-
-		case 'b':
-		   if (isdigit(e[k])) {
-	             char stemp[2];
-		     stemp[0] = e[k];
-		     stemp[1] = '\0';
-		     strcat (bstr, stemp);
-		   }
-		   else {
-		     match = 0;
-	           }
-		  break;
-
-		case 'd':
-		   if (isdigit(e[k])) {
-	             char stemp[2];
-		     stemp[0] = e[k];
-		     stemp[1] = '\0';
-		     strcat (dstr, stemp);
-		   }
-		   else {
-		     match = 0;
-	           }
-		  break;
-
-		default:
-		  dw_printf ("find_ttloc_match: shouldn't be here.\n");
-		  /* Shouldn't be here. */
-		  match = 0;
-		  break;
-
-	      } /* switch */
-	    } /* for k */
-	  
-	    if (match) {
-	      return (ipat);
-	    }
-	  } /* if strlen */
-	}
-	return (-1);
-
-} /* end find_ttloc_match */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_comment
- *
- * Purpose:     Extract comment / status or other special information from touch tone message. 
- *
- * Inputs:      e		- An "entry" extracted from a complete
- *				  APRStt messsage.
- *				  In this case, it should start with "C".
- *
- * Outputs:	m_comment
- *		m_mic_e
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- * Description:	We recognize these different formats:
- *
- *		Cn		- One digit for MIC-E position comment.
- *				  Always / plus exactly 10 characters.
- *	
- *		Cnnnnnn		- Six digit frequency reformatted as nnn.nnnMHz
- *
- *		Cttt...tttt	- General comment in Multi-press encoding.
- *
- *----------------------------------------------------------------*/
-
-static int parse_comment (char *e)
-{
-	int len;
-	int n;
-
-	assert (*e == 'C');
-
-	len = strlen(e);
-
-	if (len == 2 && isdigit(e[1])) {
-	  m_mic_e = e[1];
-	  return (0);
-	}
-
-	if (len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5]) && isdigit(e[6])) {
-	  m_freq[0] = e[1];
-	  m_freq[1] = e[2];
-	  m_freq[2] = e[3];
-	  m_freq[3] = '.';
-	  m_freq[4] = e[4];
-	  m_freq[5] = e[5];
-	  m_freq[6] = e[6];
-	  m_freq[7] = 'M';
-	  m_freq[8] = 'H';
-	  m_freq[9] = 'z';
-	  m_freq[10] = '\0';
-	  return (0);
-	}
-
-	tt_multipress_to_text (e+1, 0, m_comment);
-	return (0);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        raw_tt_data_to_app
- *
- * Purpose:     Send raw touch tone data to application. 
- *
- * Inputs:      chan		- Channel where touch tone data heard.
- *		msg		- String of button pushes.
- *				  Normally ends with #.
- *
- * Global In:	m_callsign
- *		m_symtab_or_overlay
- *		m_symbol_code
- *
- * Returns:     None
- *
- * Description:	
- * 		Put raw touch tone message in a packet and send to application.
- * 		The APRS protocol does not have provision for this.
- * 		For now, use the unused "t" message type.
- * 		TODO:  Get an officially sanctioned method.
- *
- * 		Use callsign for source if available.
- * 		Encode the overlay in the destination. 
- *
- *----------------------------------------------------------------*/
-
-
-static void raw_tt_data_to_app (int chan, char *msg)
-{
-
-#if TT_MAIN
-	return ;
-#else
-	char src[10], dest[10];
-	char raw_tt_msg[200];
-	packet_t pp;
-	char *c, *s;
-	int i;
-	int err;
-	alevel_t alevel;
-
-
-
-// Set source and dest to something valid to keep rest of processing happy.
-// For lack of a better idea, make source "DTMF" to indicate where it came from.
-// Application version might be useful in case we end up using different
-// message formats in later versions.
-
-	strcpy (src, "DTMF");
-	sprintf (dest, "%s%d%d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
-
-	sprintf (raw_tt_msg, "%s>%s:t%s", src, dest, msg);
-
-	pp = ax25_from_text (raw_tt_msg, 1);
-
-/*
- * Process like a normal received frame.
- * NOTE:  This goes directly to application rather than
- * thru the multi modem duplicate processing.
- *
- * Should we use a different type so it can be easily
- * distinguished later?
- *
- * We try to capture an overall audio level here.
- * Mark and space do not apply in this case.
- * This currently doesn't get displayed but we might want it someday.
- */
-
-	if (pp != NULL) {
-
-	  alevel = demod_get_audio_level (chan, 0);
-	  alevel.mark = -2;
-	  alevel.space = -2;
-
-	  dlq_append (DLQ_REC_FRAME, chan, -1, pp, alevel, RETRY_NONE, "tt");
-	}
-	else {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not convert \"%s\" into APRS packet.\n", raw_tt_msg);
-	}
-
-#endif
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Unit test for this file.
- *
- * Description:	Run unit test like this:
- *
- *		rm a.exe ; gcc tt_text.c -DTT_MAIN -DDEBUG aprs_tt.c latlong.c strtok_r.o utm/LatLong-UTMconversion.c ; ./a.exe 
- *
- * Bugs:	No automatic checking.
- *		Just eyeball it to see if things look right.
- *
- *----------------------------------------------------------------*/
-
-
-#if TT_MAIN
-
-
-void text_color_set (dw_color_t c) { return; }
-
-int dw_printf (const char *fmt, ...) 
-{
-	va_list args;
-	int len;
-	
-	va_start (args, fmt);
-	len = vprintf (fmt, args);
-	va_end (args);
-	return (len);
-}
-
-
-
-int main (int argc, char *argv[])
-{
-	char text[256], buttons[256];
-	int n;
-
-	dw_printf ("Hello, world!\n");
-
-	aprs_tt_init (NULL);
-
-	//if (argc < 2) {
-	  //dw_printf ("Supply text string on command line.\n");
-	  //exit (1);
-	//}
-
-/* Callsigns & abbreviations. */
-
-	aprs_tt_sequence (0, "A9A2B42A7A7C71#");		/* WB4APR/7 */
-	aprs_tt_sequence (0, "A27773#");			/* abbreviated form */
-	/* Example in http://www.aprs.org/aprstt/aprstt-coding24.txt has a bad checksum! */
-	aprs_tt_sequence (0, "A27776#");			/* Expect error message. */
-
-	aprs_tt_sequence (0, "A2A7A7C71#");		/* Spelled suffix, overlay, checksum */
-	aprs_tt_sequence (0, "A27773#");			/* Suffix digits, overlay, checksum */
-
-	aprs_tt_sequence (0, "A9A2B26C7D9D71#");		/* WB2OSZ/7 numeric overlay */
-	aprs_tt_sequence (0, "A67979#");			/* abbreviated form */
-
-	aprs_tt_sequence (0, "A9A2B26C7D9D5A9#");	/* WB2OSZ/J letter overlay */
-	aprs_tt_sequence (0, "A6795A7#");		/* abbreviated form */
-
-	aprs_tt_sequence (0, "A277#");			/* Tactical call "277" no overlay and no checksum */
-
-/* Locations */
-
-	aprs_tt_sequence (0, "B01*A67979#"); 
-	aprs_tt_sequence (0, "B988*A67979#"); 
-
-	/* expect about 52.79  +0.83 */
-	aprs_tt_sequence (0, "B51000125*A67979#"); 
-
-	/* Try to get from Hilltop Tower to Archery & Target Range. */
-	/* Latitude comes out ok, 37.9137 -> 55.82 min. */
-	/* Longitude -81.1254 -> 8.20 min */
-
-	aprs_tt_sequence (0, "B5206070*A67979#"); 
-
-	aprs_tt_sequence (0, "B21234*A67979#"); 
-	aprs_tt_sequence (0, "B533686*A67979#"); 
-
-
-/* Comments */
-
-	aprs_tt_sequence (0, "C1");
-	aprs_tt_sequence (0, "C2");
-	aprs_tt_sequence (0, "C146520");
-	aprs_tt_sequence (0, "C7788444222550227776669660333666990122223333");
-
-/* Macros */
-
-	aprs_tt_sequence (0, "88345");
-
-/* 10 digit representation for callsign & satellite grid. WB4APR near 39.5, -77   */
-
-	aprs_tt_sequence (0, "AC9242771558*BA1819");
-	aprs_tt_sequence (0, "18199242771558");
-
-
-	return(0);
-
-}  /* end main */
-
-
-
-
-#endif		
-
-/* end aprs_tt.c */
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * Module:      aprs_tt.c
+ *
+ * Purpose:   	First half of APRStt gateway.
+ *
+ * Description: This file contains functions to parse the tone sequences
+ *		and extract meaning from them.
+ *
+ *		tt_user.c maintains information about users and
+ *		generates the APRS Object Reports.
+ *		
+ *
+ * References:	This is based upon APRStt (TM) documents with some
+ *		artistic freedom.
+ *
+ *		http://www.aprs.org/aprstt.html
+ *
+ *---------------------------------------------------------------*/
+
+#define APRS_TT_C 1
+
+
+// TODO:  clean up terminolgy.  
+// "Message" has a specific meaning in APRS and this is not it.  
+// Touch Tone sequence should be appropriate.
+// What do we call the parts separated by * key?  Field.
+
+
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <ctype.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "version.h"
+#include "ax25_pad.h"
+#include "hdlc_rec2.h"		/* for process_rec_frame */
+#include "textcolor.h"
+#include "aprs_tt.h"
+#include "tt_text.h"
+#include "tt_user.h"
+#include "symbols.h"
+#include "latlong.h"
+#include "dlq.h"
+#include "demod.h"          /* for alevel_t & demod_get_audio_level() */
+#include "tq.h"
+
+
+
+
+// geotranz
+
+#include "utm.h"
+#include "mgrs.h"
+#include "usng.h"
+#include "error_string.h"
+
+/* Convert between degrees and radians. */
+
+#define D2R(d) ((d) * M_PI / 180.)
+#define R2D(r) ((r) * 180. / M_PI)
+
+
+/*
+ * Touch Tone sequences are accumulated here until # terminator found.
+ * Kept separate for each audio channel so the gateway CAN be listening
+ * on multiple channels at the same time.
+ */
+
+#define MAX_MSG_LEN 100
+
+static char msg_str[MAX_CHANS][MAX_MSG_LEN+1];
+static int msg_len[MAX_CHANS];
+
+static int parse_fields (char *msg);
+static int parse_callsign (char *e);
+static int parse_object_name (char *e);
+static int parse_symbol (char *e);
+static int parse_aprstt3_call (char *e);
+static int parse_location (char *e);
+static int parse_comment (char *e);
+static int expand_macro (char *e);
+static void raw_tt_data_to_app (int chan, char *msg);
+static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize);
+
+#if TT_MAIN
+static void check_result (void);
+#endif
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        aprs_tt_init
+ *
+ * Purpose:     Initialize the APRStt gateway at system startup time.
+ *
+ * Inputs:      Configuration options gathered by config.c.
+ *
+ * Global out:	Make our own local copy of the structure here.
+ *
+ * Returns:     None
+ *
+ * Description:	The main program needs to call this at application
+ *		start up time after reading the configuration file.
+ *
+ *		TT_MAIN is defined for unit testing.
+ *
+ *----------------------------------------------------------------*/
+
+static struct tt_config_s tt_config;
+
+#if TT_MAIN
+#define NUM_TEST_CONFIG (sizeof(test_config) / sizeof (struct ttloc_s))
+static struct ttloc_s test_config[] = {
+
+	{ TTLOC_POINT, "B01", .point.lat = 12.25, .point.lon = 56.25 },
+	{ TTLOC_POINT, "B988", .point.lat = 12.50, .point.lon = 56.50 },
+
+	{ TTLOC_VECTOR, "B5bbbdddd", .vector.lat = 53., .vector.lon = -1., .vector.scale = 1000. },  /* km units */
+
+	/* Hilltop Tower http://www.aprs.org/aprs-jamboree-2013.html */
+	{ TTLOC_VECTOR, "B5bbbddd", .vector.lat = 37+55.37/60., .vector.lon = -(81+7.86/60.), .vector.scale = 16.09344 },   /* .01 mile units */
+
+	{ TTLOC_GRID, "B2xxyy", .grid.lat0 = 12.00, .grid.lon0 = 56.00, 
+				.grid.lat9 = 12.99, .grid.lon9 = 56.99 },
+	{ TTLOC_GRID, "Byyyxxx", .grid.lat0 = 37 + 50./60.0, .grid.lon0 = 81, 
+				.grid.lat9 = 37 + 59.99/60.0, .grid.lon9 = 81 + 9.99/60.0 },
+
+	{ TTLOC_MHEAD, "BAxxxxxx", .mhead.prefix = "326129" },
+
+	{ TTLOC_SATSQ, "BAxxxx" },
+
+	{ TTLOC_MACRO, "xxyyy", .macro.definition = "B9xx*AB166*AA2B4C5B3B0Ayyy" },
+	{ TTLOC_MACRO, "xxxxzzzzzzzzzz", .macro.definition = "BAxxxx*ACzzzzzzzzzz" },
+}; 
+#endif
+
+
+void aprs_tt_init (struct tt_config_s *p)
+{
+	int c;
+
+#if TT_MAIN
+	/* For unit testing. */
+
+	memset (&tt_config, 0, sizeof(struct tt_config_s));	
+	tt_config.ttloc_size = NUM_TEST_CONFIG;
+	tt_config.ttloc_ptr = test_config;
+	tt_config.ttloc_len = NUM_TEST_CONFIG;
+
+	/* Don't care about xmit timing or corral here. */
+#else
+	// TODO: Keep ptr instead of making a copy.
+	memcpy (&tt_config, p, sizeof(struct tt_config_s));
+#endif
+	for (c=0; c<MAX_CHANS; c++) {	
+	  msg_len[c] = 0;
+	  msg_str[c][0] = '\0';
+	}
+
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        aprs_tt_button
+ *
+ * Purpose:     Process one received button press.
+ *
+ * Inputs:      chan		- Audio channel it came from.
+ *
+ *		button		0123456789ABCD*#	- Received button press.
+ *				$			- No activity timeout.
+ *				space			- Quiet time filler.
+ *
+ * Returns:     None
+ *
+ * Description:	Individual key presses are accumulated here until
+ *		the # message terminator is found.
+ *		The complete message is then processed.
+ *		The touch tone decoder sends $ if no activity
+ *		for some amount of time, perhaps 5 seconds.
+ *		A partially accumulated messge is discarded if
+ *		there is a long gap.
+ *
+ *		'.' means no activity during processing period.
+ *		space, between blocks, shouldn't get here.
+ *
+ *----------------------------------------------------------------*/
+
+#ifndef TT_MAIN
+
+void aprs_tt_button (int chan, char button)
+{
+	static int poll_period = 0;
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+
+	//if (button != '.') {
+	//  dw_printf ("aprs_tt_button (%d, '%c')\n", chan, button);
+	//}
+
+
+// TODO:  Might make more sense to put timeout here rather in the dtmf decoder.
+
+	if (button == '$') {
+
+/* Timeout reset. */
+
+	  msg_len[chan] = 0;
+	  msg_str[chan][0] = '\0';
+	}
+	else if (button != '.' && button != ' ') {
+	  if (msg_len[chan] < MAX_MSG_LEN) {
+	    msg_str[chan][msg_len[chan]++] = button;
+	    msg_str[chan][msg_len[chan]] = '\0';
+	  }
+	  if (button == '#') {
+
+/* 
+ * Put into the receive queue like any other packet.
+ * This way they are all processed by the common receive thread
+ * rather than the thread associated with the particular audio device.
+ */
+	    raw_tt_data_to_app (chan, msg_str[chan]);
+
+	    msg_len[chan] = 0;
+	    msg_str[chan][0] = '\0';
+	  }
+	}
+	else {
+
+/* 
+ * Idle time. Poll occasionally for processing. 
+ * Timing would be off we we are listening to more than
+ * one channel so do this only for the one specified
+ * in the TTOBJ command. 
+ */
+
+	  if (chan == tt_config.obj_recv_chan) {	  
+	    poll_period++;
+	    if (poll_period >= 39) {
+	      poll_period = 0;
+	      tt_user_background ();
+	    }
+	  }
+	}	
+  
+} /* end aprs_tt_button */
+
+#endif
+
+/*------------------------------------------------------------------
+ *
+ * Name:        aprs_tt_sequence
+ *
+ * Purpose:     Process complete received touch tone sequence
+ *		terminated by #.
+ *
+ * Inputs:      chan		- Audio channel it came from.
+ *
+ *		msg		- String of DTMF buttons.
+ *				  # should be the final character.
+ *
+ * Returns:     None
+ *
+ * Description:	Process a complete tone sequence.
+ *		It should have one or more fields separated by *
+ *		and terminated by a final # like these:
+ *
+ *		callsign #
+ *		entry1 * callsign #
+ *		entry1 * entry * callsign #
+ *
+ * Limitation:	Has one set of static data for communication among
+ *		group of functions.  This shouldn't be a problem
+ *		when receiving on multiple channels at once 
+ *		because they get serialized thru the receive packet queue.
+ *
+ *----------------------------------------------------------------*/
+
+static char m_callsign[20];	/* really object name */
+
+/*
+ * Standard APRStt has symbol code 'A' (box) with overlay of 0-9, A-Z. 
+ *
+ * Dire Wolf extension allows:
+ *	Symbol table '/' (primary), any symbol code.
+ *	Symbol table '\' (alternate), any symbol code.
+ *	Alternate table symbol code, overlay of 0-9, A-Z.
+ */
+
+static char m_symtab_or_overlay;
+static char m_symbol_code;		// Default 'A'
+
+static char m_loc_text[24];
+static double m_longitude;		// Set to G_UNKNOWN if not defined.
+static double m_latitude;		// Set to G_UNKNOWN if not defined.
+static int m_ambiguity;
+static char m_comment[200];
+static char m_freq[12];
+static char m_ctcss[8];
+static char m_mic_e;
+static char m_dao[6];
+static int m_ssid;			// Default 12 for APRStt user.
+
+
+
+void aprs_tt_sequence (int chan, char *msg)
+{
+	int err;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("\n\"%s\"\n", msg);
+#endif
+
+/* 
+ * Discard empty message. 
+ * In case # is there as optional start. 
+ */
+
+	if (msg[0] == '#') return;
+
+/*
+ * The parse functions will fill these in. 
+ */
+	strlcpy (m_callsign, "", sizeof(m_callsign));
+	m_symtab_or_overlay = APRSTT_DEFAULT_SYMTAB;
+	m_symbol_code = APRSTT_DEFAULT_SYMBOL;
+	strlcpy (m_loc_text, "", sizeof(m_loc_text));
+	m_longitude = G_UNKNOWN;
+	m_latitude = G_UNKNOWN;
+	m_ambiguity = 0;
+	strlcpy (m_comment, "", sizeof(m_comment));
+	strlcpy (m_freq, "", sizeof(m_freq));
+	strlcpy (m_ctcss, "", sizeof(m_ctcss));
+	m_mic_e = ' ';
+	strlcpy (m_dao, "!T  !", sizeof(m_dao));	/* start out unknown */
+	m_ssid = 12;
+
+/*
+ * Parse the touch tone sequence.
+ */
+	err = parse_fields (msg);
+
+#if defined(DEBUG)
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", ctcss=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n",
+		m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_ctcss, m_comment, m_latitude, m_longitude, m_dao);
+#endif
+
+#if TT_MAIN
+	check_result ();	// for unit testing.
+#else
+
+/*
+ * If digested successfully.  Add to our list of users and schedule transmissions.
+ */
+
+	if (err == 0) {
+
+	  err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, 
+		m_loc_text, m_latitude, m_longitude, m_ambiguity,
+		m_freq, m_ctcss, m_comment, m_mic_e, m_dao);
+	}
+
+
+/*
+ * If a command / script was supplied, run it now.
+ * This can do additional processing and provide a custom audible response.
+ * This is done only for the success case.
+ * It might be useful to run it for error cases as well but we currently
+ * don't pass in the success / failure code to know the difference.
+ */
+	char script_response[1000];
+
+	strlcpy (script_response, "", sizeof(script_response));
+
+	if (err == 0 && strlen(tt_config.ttcmd) > 0) {
+
+	  dw_run_cmd (tt_config.ttcmd, 1, script_response, sizeof(script_response));
+
+	}
+
+/*
+ * Send response to user by constructing packet with SPEECH or MORSE as destination.
+ * Source shouldn't matter because it doesn't get transmitted as AX.25 frame.
+ * Use high priority queue for consistent timing.
+ *
+ * Anything from script, above, will override other predefined responses.
+ */
+
+	char audible_response[1000];
+
+	snprintf (audible_response, sizeof(audible_response), 
+					"APRSTT>%s:%s", 
+					tt_config.response[err].method,
+					(strlen(script_response) > 0) ? script_response : tt_config.response[err].mtext);
+
+	packet_t pp;
+
+	pp = ax25_from_text (audible_response, 0);
+
+	if (pp == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error. Couldn't make frame from \"%s\"\n", audible_response);
+	  return;
+	}
+
+	tq_append (chan, TQ_PRIO_0_HI, pp);
+
+#endif  /* ifndef TT_MAIN */
+
+} /* end aprs_tt_sequence */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_fields
+ *
+ * Purpose:     Separate the complete string of touch tone characters
+ *		into fields, delimited by *, and process each.
+ *
+ * Inputs:      msg		- String of DTMF buttons.
+ *
+ * Returns:     None
+ *
+ * Description:	It should have one or more fields separated by *.
+ *
+ *		callsign #
+ *		entry1 * callsign #
+ *		entry1 * entry * callsign #
+ *
+ *		Note that this will be used recursively when macros
+ *		are expanded.
+ *
+ *		"To iterate is human, to recurse divine."
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ *----------------------------------------------------------------*/
+
+static int parse_fields (char *msg)
+{
+	char stemp[MAX_MSG_LEN+1];
+	char *e;
+	char *save;
+	int err;
+
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("parse_fields (%s).\n", msg);
+
+	strlcpy (stemp, msg, sizeof(stemp));
+	e = strtok_r (stemp, "*#", &save);
+	while (e != NULL) {
+
+	  //text_color_set(DW_COLOR_DEBUG);
+	  //dw_printf ("parse_fields () field = %s\n", e);
+
+	  switch (*e) {
+
+	    case 'A': 
+	      
+	      switch (e[1]) {
+
+	        case 'A':			/* AA object-name */
+	          err = parse_object_name (e);
+	          if (err != 0) return (err);
+	          break;
+
+	        case 'B':			/* AB symbol */
+	          err = parse_symbol (e);
+	          if (err != 0) return (err);
+	          break;
+
+	        case 'C':			/* AC new-style-callsign */
+
+	          err = parse_aprstt3_call (e);
+	          if (err != 0) return (err);
+	          break;
+
+	        default:			/* Traditional style call or suffix */
+	          err = parse_callsign (e);
+	          if (err != 0) return (err);
+	          break;
+	      }
+	      break;
+
+	    case 'B': 
+	      err = parse_location (e);
+	      if (err != 0) return (err);
+	      break;
+
+	    case 'C': 
+	      err = parse_comment (e);
+	      if (err != 0) return (err);
+	      break;
+
+	    case '0': 
+	    case '1': 
+	    case '2': 
+	    case '3': 
+	    case '4': 
+	    case '5': 
+	    case '6': 
+	    case '7': 
+	    case '8': 
+	    case '9': 
+	      err = expand_macro (e);
+	      if (err != 0) return (err);
+	      break;
+
+	    case '\0':
+	      /* Empty field.  Just ignore it. */
+	      /* This would happen if someone uses a leading *. */
+	      break;
+
+	    default:
+
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Field does not start with A, B, C, or digit: \"%s\"\n", msg);
+	      return (TT_ERROR_D_MSG);
+
+	  }
+	
+	  e = strtok_r (NULL, "*#", &save);
+	}
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("parse_fields () normal return\n");
+
+	return (0);
+
+} /* end parse_fields */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        expand_macro
+ *
+ * Purpose:     Expand compact form "macro" to full format then process.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should contain only digits.
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	Separate out the fields, perform substitution, 
+ *		call parse_fields for processing.
+ *
+ *
+ * Future:	Generalize this to allow any lower case letter for substitution?
+ *
+ *----------------------------------------------------------------*/
+
+#define VALSTRSIZE 20
+
+static int expand_macro (char *e) 
+{
+	//int len;
+	int ipat;
+	char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE];
+	char stemp[MAX_MSG_LEN+1];
+	char *d;
+
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("Macro tone sequence: '%s'\n", e);
+
+	//len = strlen(e);
+
+	ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE);
+
+	if (ipat >= 0) {
+
+	  // Why did we print b & d here?
+	  // Documentation says only x, y, z can be used with macros.
+	  // Only those 3 are processed below.
+
+	  //dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s, b=%s, d=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr, bstr, dstr);
+	  dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr);
+
+	  dw_printf ("Replace with:        '%s'\n", tt_config.ttloc_ptr[ipat].macro.definition);
+
+	  if (tt_config.ttloc_ptr[ipat].type != TTLOC_MACRO) {
+
+	     /* Found match to a different type.  Really shouldn't be here. */
+	     /* Print internal error message... */
+	     dw_printf ("expand_macro: type != TTLOC_MACRO\n");
+	     return (TT_ERROR_INTERNAL);
+	  }
+
+/*
+ * We found a match for the length and any fixed digits.
+ * Substitute values in to the definition.
+ */		
+	  
+	  strlcpy (stemp, "", sizeof(stemp));
+
+	  for (d = tt_config.ttloc_ptr[ipat].macro.definition; *d != '\0'; d++) {
+
+	    while (( *d == 'x' || *d == 'y' || *d == 'z') && *d == d[1]) {
+	      /* Collapse adjacent matching substitution characters. */
+	      d++;
+	    }
+
+	    switch (*d) {
+	      case 'x':
+		strlcat (stemp, xstr, sizeof(stemp));
+	        break;
+	      case 'y':
+		strlcat (stemp, ystr, sizeof(stemp));
+	        break;
+	      case 'z':
+		strlcat (stemp, zstr, sizeof(stemp));
+	        break;
+	      default:
+		{
+	        char c1[2];
+	        c1[0] = *d;
+	        c1[1] = '\0';
+	        strlcat (stemp, c1, sizeof(stemp)); 
+		}
+		break;
+	    }
+	  }
+/*
+ * Process as if we heard this over the air.
+ */
+
+	  dw_printf ("After substitution:  '%s'\n", stemp);
+	  return (parse_fields (stemp));
+	}
+
+	else {
+
+
+	  /* Send reject sound. */
+	  /* Does not match any macro definitions. */
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Tone sequence did not match any pattern\n");
+	  return (TT_ERROR_MACRO_NOMATCH);
+	}
+	
+	/* should be unreachable */
+	return (0);
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_callsign
+ *
+ * Purpose:     Extract traditional format callsign or object name from touch tone sequence.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "A".
+ *
+ * Outputs:	m_callsign
+ *
+ *		m_symtab_or_overlay - Set to 0-9 or A-Z if specified.
+ *
+ *		m_symbol_code	- Always set to 'A'.
+ *					NO!  This should be applied only if we
+ *					have the default value at this point.
+ *					The symbol might have been explicitly
+ *					set already and we don't want to overwrite that.
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	We recognize 3 different formats:
+ *
+ *		Annn		- 3 digits are a tactical callsign.  No overlay.
+ *
+ *		Annnvk		- Abbreviation with 3 digits, numeric overlay, checksum.
+ *		Annnvvk		- Abbreviation with 3 digits, letter overlay, checksum.
+ *
+ *		Att...ttvk	- Full callsign in two key method, numeric overlay, checksum.
+ *		Att...ttvvk	- Full callsign in two key method, letter overlay, checksum.
+ *		
+ *
+ *----------------------------------------------------------------*/
+
+static int checksum_not_ok (char *str, int len, char found)
+{
+	int i;
+	int sum;
+	char expected;
+
+	sum = 0;
+	for (i=0; i<len; i++) {
+	  if (isdigit(str[i])) {
+	    sum += str[i] - '0';
+	  }
+	  else if (str[i] >= 'A' && str[i] <= 'D') {
+	    sum += str[i] - 'A' + 10;
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("aprs_tt: checksum: bad character \"%c\" in checksum calculation!\n", str[i]);
+	  }
+	}
+	expected =  '0' + (sum % 10);
+
+	if (expected != found) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Bad checksum for \"%.*s\".  Expected %c but received %c.\n", len, str, expected, found);
+	    return (TT_ERROR_BAD_CHECKSUM);
+	}
+	return (0);
+}
+
+
+static int parse_callsign (char *e)
+{
+	int len;
+	char tttemp[40], stemp[30];
+
+	assert (*e == 'A');
+
+	len = strlen(e);
+
+/*
+ * special case: 3 digit tactical call.
+ */
+
+	if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) {
+	  strlcpy (m_callsign, e+1, sizeof(m_callsign));
+	  return (0);
+	}
+
+/* 
+ * 3 digit abbreviation:  We only do the parsing here.
+ * Another part of application will try to find corresponding full call.
+ */
+
+	if ((len == 6 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5])) ||
+	    (len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isupper(e[5]) && isdigit(e[6]))) {
+
+	  int cs_err = checksum_not_ok (e+1, len-2, e[len-1]);
+
+	  if (cs_err != 0) {
+	    return (cs_err);
+	  }
+
+	  strncpy (m_callsign, e+1, 3);
+	  m_callsign[3] = '\0';
+	
+	  if (len == 7) {
+	    tttemp[0] = e[len-3];
+	    tttemp[1] = e[len-2];
+	    tttemp[2] = '\0';
+	    tt_two_key_to_text (tttemp, 0, stemp);
+	    m_symbol_code = APRSTT_DEFAULT_SYMBOL;
+	    m_symtab_or_overlay = stemp[0];
+	  }
+	  else {
+	    m_symbol_code = APRSTT_DEFAULT_SYMBOL;
+	    m_symtab_or_overlay = e[len-2];
+	  }
+	  return (0);
+	}
+
+/* 
+ * Callsign in two key format.
+ */
+
+	if (len >= 7 && len <= 24) {
+
+	  int cs_err = checksum_not_ok (e+1, len-2, e[len-1]);
+
+	  if (cs_err != 0) {
+	    return (cs_err);
+	  }
+	
+	  if (isupper(e[len-2])) {
+	    strncpy (tttemp, e+1, len-4);
+	    tttemp[len-4] = '\0';
+	    tt_two_key_to_text (tttemp, 0, m_callsign);
+
+	    tttemp[0] = e[len-3];
+	    tttemp[1] = e[len-2];
+	    tttemp[2] = '\0';
+	    tt_two_key_to_text (tttemp, 0, stemp);
+	    m_symbol_code = APRSTT_DEFAULT_SYMBOL;
+	    m_symtab_or_overlay = stemp[0];
+	  }
+	  else {
+	    strncpy (tttemp, e+1, len-3);
+	    tttemp[len-3] = '\0';
+	    tt_two_key_to_text (tttemp, 0, m_callsign);
+
+	    m_symbol_code = APRSTT_DEFAULT_SYMBOL;
+	    m_symtab_or_overlay = e[len-2];
+	  }
+	  return (0);
+	}
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Touch tone callsign not valid: \"%s\"\n", e);
+	return (TT_ERROR_INVALID_CALL);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_object_name 
+ *
+ * Purpose:     Extract object name from touch tone sequence.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "AA".
+ *
+ * Outputs:	m_callsign
+ *
+ *		m_ssid		- Cleared to remove the default of 12.
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	Data format
+ *
+ *		AAtt...tt	- Symbol name, two key method, up to 9 characters.
+ *
+ *----------------------------------------------------------------*/
+
+
+static int parse_object_name (char *e)
+{
+	int len;
+	//int c_length;
+	//char tttemp[40];
+	//char stemp[30];
+
+	assert (e[0] == 'A');
+	assert (e[1] == 'A');
+
+	len = strlen(e);
+
+/* 
+ * Object name in two key format.
+ */
+
+	if (len >= 2 + 1 && len <= 30) {
+
+	  if (tt_two_key_to_text (e+2, 0, m_callsign) == 0) {
+	    m_callsign[9] = '\0';  /* truncate to 9 */
+	    m_ssid = 0;		/* No ssid for object name */
+	    return (0);
+	  }
+	}
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Touch tone object name not valid: \"%s\"\n", e);
+
+	return (TT_ERROR_INVALID_OBJNAME);
+
+}  /* end parse_oject_name */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_symbol 
+ *
+ * Purpose:     Extract symbol from touch tone sequence.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "AB".
+ *
+ * Outputs:	m_symtab_or_overlay
+ *
+ * 		m_symbol_code
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	Data format
+ *
+ *		AB1nn		- Symbol from primary symbol table.  
+ *				  Two digits nn are the same as in the GPSCnn 
+ *				  generic address used as a destination. 
+ *
+ *		AB2nn		- Symbol from alternate symbol table.  
+ *				  Two digits nn are the same as in the GPSEnn 
+ *				  generic address used as a destination. 
+ *
+ *		AB0nnvv		- Symbol from alternate symbol table.  
+ *				  Two digits nn are the same as in the GPSEnn 
+ *				  generic address used as a destination.
+ *	 			  vv is an overlay digit or letter in two key method.
+ *
+ *----------------------------------------------------------------*/
+
+
+static int parse_symbol (char *e)
+{
+	int len;
+	char nstr[3];
+	int nn;
+	char stemp[10];
+
+	assert (e[0] == 'A');
+	assert (e[1] == 'B');
+
+	len = strlen(e);
+
+	if (len >= 4 && len <= 10) {
+
+	  nstr[0] = e[3];
+	  nstr[1] = e[4];
+	  nstr[2] = '\0';
+
+	  nn = atoi (nstr);
+	  if (nn < 1) {
+	    nn = 1;
+	  }
+	  else if (nn > 94) {
+	    nn = 94;
+	  }
+
+	  switch (e[2]) {
+
+	    case '1':
+	      m_symtab_or_overlay = '/';
+	      m_symbol_code = 32 + nn;
+	      return (0);
+	      break;
+
+	    case '2':
+	      m_symtab_or_overlay = '\\';
+	      m_symbol_code = 32 + nn;
+	      return (0);
+	      break;
+
+	    case '0':
+	      if (len >= 6) {
+	        if (tt_two_key_to_text (e+5, 0, stemp) == 0) {
+	          m_symbol_code = 32 + nn;
+	          m_symtab_or_overlay = stemp[0];
+	          return (0);
+	        }
+	      }
+	      break;
+	  }
+	}
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Touch tone symbol not valid: \"%s\"\n", e);
+
+	return (TT_ERROR_INVALID_SYMBOL);
+
+}  /* end parse_oject_name */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_aprstt3_call
+ *
+ * Purpose:     Extract QIKcom-2 / APRStt 3 ten digit call or five digit suffix.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "AC".
+ *
+ * Outputs:	m_callsign
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	We recognize 3 different formats:
+ *
+ *		ACxxxxxxxxxx	- 10 digit full callsign.
+ *
+ *		ACxxxxx		- 5 digit suffix.   If we can find a corresponding full
+ *				  callsign, that will be substituted.
+ *				  Error condition is returned if we can't find one.
+ *
+ *----------------------------------------------------------------*/
+
+static int parse_aprstt3_call (char *e)
+{
+
+	assert (e[0] == 'A');
+	assert (e[1] == 'C');
+
+	if (strlen(e) == 2+10) {
+	  char call[12];
+
+	  if (tt_call10_to_text(e+2,1,call) == 0) {
+	    strlcpy(m_callsign, call, sizeof(m_callsign));
+	  }
+	  else {
+	    return (TT_ERROR_INVALID_CALL);		/* Could not convert to text */
+	  }
+	}
+	else if (strlen(e) == 2+5) {
+	  char suffix[8];
+          if (tt_call5_suffix_to_text(e+2,1,suffix) == 0) {
+
+#if TT_MAIN
+	    /* For unit test, use suffix rather than trying lookup. */
+	    strlcpy (m_callsign, suffix, sizeof(m_callsign));
+#else
+	    char call[12];
+
+	    /* In normal operation, try to find full callsign for the suffix received. */
+
+	    if (tt_3char_suffix_search (suffix, call) >= 0) {
+	      text_color_set(DW_COLOR_INFO);
+	      dw_printf ("Suffix \"%s\" was converted to full callsign \"%s\"\n", suffix, call);
+
+	      strlcpy(m_callsign, call, sizeof(m_callsign));
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Couldn't find full callsign for suffix \"%s\"\n", suffix);
+	      return (TT_ERROR_SUFFIX_NO_CALL);	/* Don't know this user. */
+	    }
+#endif
+	  }
+	  else {
+	    return (TT_ERROR_INVALID_CALL);	/* Could not convert to text */
+	  }
+	}
+	else {
+	  return (TT_ERROR_INVALID_CALL);	/* Invalid length, not 2+ (10 ir 5) */
+	}
+
+	return (0);
+
+}  /* end parse_aprstt3_call */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_location
+ *
+ * Purpose:     Extract location from touch tone sequence.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "B".
+ *
+ * Outputs:	m_latitude
+ *		m_longitude
+ *
+ *		m_dao		It should previously be "!T  !" to mean unknown or none.
+ *				We generally take the first two tones of the field.
+ *				For example, "!TB5!" for the standard bearing & range.
+ *				The point type is an exception where we use "!Tn !" for
+ *				one of ten positions or "!Tnn" for one of a hundred.
+ *				If this ever changes, be sure to update corresponding
+ *				section in process_comment() in decode_aprs.c
+ *
+ *		m_ambiguity
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	There are many different formats recognizable
+ *		by total number of digits and sometimes the first digit.
+ *
+ *		We handle most of them in a general way, processing
+ *		them in 5 groups:
+ *
+ *		* points
+ *		* vector
+ *		* grid
+ *		* utm
+ *		* usng / mgrs
+ *
+ *		Position ambiguity is also handled here.
+ *			Latitude, Longitude, and DAO should not be touched in this case.
+ *		 	We only record a position ambiguity value.
+ *
+ *----------------------------------------------------------------*/
+
+/* Average radius of earth in meters. */
+#define R 6371000.
+
+
+static int parse_location (char *e)
+{
+	int ipat;
+	char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE];
+	double x, y, dist, bearing;
+	double lat0, lon0;
+	double lat9, lon9;
+	long lerr;	
+	double easting, northing;
+	char mh[20];	
+	char stemp[32];
+
+
+	assert (*e == 'B');
+
+
+	ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE);
+	if (ipat >= 0) {
+
+	  //dw_printf ("ipat=%d, x=%s, y=%s, b=%s, d=%s\n", ipat, xstr, ystr, bstr, dstr);
+
+	  switch (tt_config.ttloc_ptr[ipat].type) {
+	    case TTLOC_POINT:
+		
+	      m_latitude = tt_config.ttloc_ptr[ipat].point.lat;
+	      m_longitude = tt_config.ttloc_ptr[ipat].point.lon;
+
+	      /* Is it one of ten or a hundred positions? */
+	      /* It's not hardwired to always be B0n or B9nn.  */
+	      /* This is a pretty good approximation. */
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      if (strlen(e) == 3) {	/* probably B0n -->  !Tn ! */
+		m_dao[2] = e[2];
+	        m_dao[3] = ' ';
+	      }
+	      if (strlen(e) == 4) {	/* probably B9nn -->  !Tnn! */
+		m_dao[2] = e[2];
+	        m_dao[3] = e[3];
+	      }
+	      break;
+
+	    case TTLOC_VECTOR:
+
+	      if (strlen(bstr) != 3) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Bearing \"%s\" should be 3 digits.\n", bstr);
+	        // return error code?
+	      }
+	      if (strlen(dstr) < 1) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Distance \"%s\" should 1 or more digits.\n", dstr);
+	        // return error code?
+	      }
+
+	      lat0 = D2R(tt_config.ttloc_ptr[ipat].vector.lat);
+	      lon0 = D2R(tt_config.ttloc_ptr[ipat].vector.lon);
+	      dist = atof(dstr) * tt_config.ttloc_ptr[ipat].vector.scale;
+	      bearing = D2R(atof(bstr));
+
+	      /* Equations and caluculators found here: */
+	      /* http://movable-type.co.uk/scripts/latlong.html */
+	      /* This should probably be a function in latlong.c in case we have another use for it someday. */
+
+	      m_latitude = R2D(asin(sin(lat0) * cos(dist/R) + cos(lat0) * sin(dist/R) * cos(bearing)));
+
+	      m_longitude = R2D(lon0 + atan2(sin(bearing) * sin(dist/R) * cos(lat0),
+				  cos(dist/R) - sin(lat0) * sin(D2R(m_latitude))));
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      break;
+
+	    case TTLOC_GRID:
+
+	      if (strlen(xstr) == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Missing X coordinate.\n");
+		strlcpy (xstr, "0", sizeof(xstr));
+	      }
+	      if (strlen(ystr) == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Missing Y coordinate.\n");
+		strlcpy (ystr, "0", sizeof(ystr));
+	      }
+
+	      lat0 = tt_config.ttloc_ptr[ipat].grid.lat0;
+	      lat9 = tt_config.ttloc_ptr[ipat].grid.lat9;
+	      y = atof(ystr);
+	      m_latitude = lat0 + y * (lat9-lat0) / (pow(10., strlen(ystr)) - 1.);
+
+	      lon0 = tt_config.ttloc_ptr[ipat].grid.lon0;
+	      lon9 = tt_config.ttloc_ptr[ipat].grid.lon9;
+	      x = atof(xstr);
+	      m_longitude = lon0 + x * (lon9-lon0) / (pow(10., strlen(xstr)) - 1.);
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      break;
+
+	    case TTLOC_UTM:
+
+	      if (strlen(xstr) == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Missing X coordinate.\n");
+	        /* Avoid divide by zero later.  Put in middle of range. */
+		strlcpy (xstr, "5", sizeof(xstr));
+	      }
+	      if (strlen(ystr) == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Missing Y coordinate.\n");
+	        /* Avoid divide by zero later.  Put in middle of range. */
+		strlcpy (ystr, "5", sizeof(ystr));
+	      }
+
+	      x = atof(xstr);
+	      easting = x * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.x_offset;
+
+	      y = atof(ystr);
+	      northing = y * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.y_offset;
+	
+	      if (isalpha(tt_config.ttloc_ptr[ipat].utm.latband)) {
+	        snprintf (m_loc_text, sizeof(m_loc_text), "%d%c %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), tt_config.ttloc_ptr[ipat].utm.latband, easting, northing);
+	      }
+	      else if (tt_config.ttloc_ptr[ipat].utm.latband == '-') {
+	        snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(- tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing);
+	      }
+	      else {
+	        snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing);
+	      }
+
+              lerr = Convert_UTM_To_Geodetic(tt_config.ttloc_ptr[ipat].utm.lzone, 
+			tt_config.ttloc_ptr[ipat].utm.hemi, easting, northing, &lat0, &lon0);
+
+              if (lerr == 0) {
+                m_latitude = R2D(lat0);
+                m_longitude = R2D(lon0);
+
+                //dw_printf ("DEBUG: from UTM, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude);
+              }
+              else {
+	        char message[300];
+
+		text_color_set(DW_COLOR_ERROR);
+	        utm_error_string (lerr, message);
+                dw_printf ("Conversion from UTM failed:\n%s\n\n", message);
+              }
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      break;
+
+
+	    case TTLOC_MGRS:
+	    case TTLOC_USNG:
+
+	      if (strlen(xstr) == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("MGRS/USNG: Missing X (easting) coordinate.\n");
+	        /* Should not be possible to get here. Fake it and carry on. */
+		strlcpy (xstr, "5", sizeof(xstr));
+	      }
+	      if (strlen(ystr) == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("MGRS/USNG: Missing Y (northing) coordinate.\n");
+	        /* Should not be possible to get here. Fake it and carry on. */
+		strlcpy (ystr, "5", sizeof(ystr));
+	      }
+
+	      char loc[40];
+	
+	      strlcpy (loc, tt_config.ttloc_ptr[ipat].mgrs.zone, sizeof(loc));
+	      strlcat (loc, xstr, sizeof(loc));
+	      strlcat (loc, ystr, sizeof(loc));
+
+	      //text_color_set(DW_COLOR_DEBUG);
+	      //dw_printf ("MGRS/USNG location debug:  %s\n", loc);
+
+	      strlcpy (m_loc_text, loc, sizeof(m_loc_text));
+
+	      if (tt_config.ttloc_ptr[ipat].type == TTLOC_MGRS)
+                lerr = Convert_MGRS_To_Geodetic(loc, &lat0, &lon0);
+	      else
+                lerr = Convert_USNG_To_Geodetic(loc, &lat0, &lon0);
+
+
+              if (lerr == 0) {
+                m_latitude = R2D(lat0);
+                m_longitude = R2D(lon0);
+
+                //dw_printf ("DEBUG: from MGRS/USNG, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude);
+              }
+              else {
+	        char message[300];
+
+		text_color_set(DW_COLOR_ERROR);
+	        mgrs_error_string (lerr, message);
+                dw_printf ("Conversion from MGRS/USNG failed:\n%s\n\n", message);
+              }
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      break;
+
+	    case TTLOC_MHEAD:
+
+
+	      /* Combine prefix from configuration and digits from user. */
+	
+  	      strlcpy (stemp, tt_config.ttloc_ptr[ipat].mhead.prefix, sizeof(stemp));
+	      strlcat (stemp, xstr, sizeof(stemp));
+
+	      if (strlen(stemp) != 4 && strlen(stemp) != 6 && strlen(stemp) != 10 && strlen(stemp) != 12) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Expected total of 4, 6, 10, or 12 digits for the Maidenhead Locator \"%s\" + \"%s\"\n", 
+							tt_config.ttloc_ptr[ipat].mhead.prefix, xstr);
+	        return (TT_ERROR_INVALID_MHEAD);
+	      }
+
+	      //text_color_set(DW_COLOR_DEBUG);
+	      //dw_printf ("Case MHEAD: Convert to text \"%s\".\n", stemp);
+
+	      if (tt_mhead_to_text (stemp, 0, mh, sizeof(mh))  == 0) {
+	        //text_color_set(DW_COLOR_DEBUG);
+	        //dw_printf ("Case MHEAD: Resulting text \"%s\".\n", mh);
+
+		strlcpy (m_loc_text, mh, sizeof(m_loc_text));
+
+		ll_from_grid_square (mh, &m_latitude, &m_longitude);
+	      }
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      break;
+
+	    case TTLOC_SATSQ:
+
+	      if (strlen(xstr) != 4) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Expected 4 digits for the Satellite Square.\n");
+	        return (TT_ERROR_INVALID_SATSQ);
+	      }
+
+	      /* Convert 4 digits to usual AA99 form, then to location. */
+
+	      if (tt_satsq_to_text (xstr, 0, mh)  == 0) {
+
+	        strlcpy (m_loc_text, mh, sizeof(m_loc_text));
+
+		ll_from_grid_square (mh, &m_latitude, &m_longitude);
+	      }
+
+	      m_dao[2] = e[0];
+	      m_dao[3] = e[1];
+
+	      break;
+
+	    case TTLOC_AMBIG:
+
+	      if (strlen(xstr) != 1) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Expected 1 digits for the position ambiguity.\n");
+	        return (TT_ERROR_INVALID_LOC);
+	      }
+
+	      m_ambiguity = atoi(xstr);
+
+	      break;
+
+	    default:
+	      assert (0);
+	  }
+	  return (0);
+	}
+
+	/* Does not match any location specification. */
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Received location \"%s\" does not match any definitions.\n", e);
+
+	/* Send reject sound. */
+
+	return (TT_ERROR_INVALID_LOC);
+
+} /* end parse_location */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        find_ttloc_match
+ *
+ * Purpose:     Try to match the received position report to a pattern
+ *		defined in the configuration file.
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "B".
+ *
+ *		valstrsize	- size of the outputs so we can check for buffer overflow.
+ *
+ * Outputs:	xstr		- All digits matching x positions in configuration.
+ *		ystr		-                     y 
+ *		zstr		-                     z 
+ *		bstr		-                     b
+ * 		dstr		-                     d
+ *
+ * Returns:     >= 0 for index into table if found.
+ *		-1 if not found.
+ *
+ * Description:	
+ *
+ *----------------------------------------------------------------*/
+
+static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize)
+{
+	int ipat;	/* Index into patterns from configuration file */
+	int len;	/* Length of pattern we are trying to match. */
+	int match;
+	char mc;
+	int k;
+
+	// debug dw_printf ("find_ttloc_match: e=%s\n", e);
+
+	for (ipat=0; ipat<tt_config.ttloc_len; ipat++) {
+	  
+	  len = strlen(tt_config.ttloc_ptr[ipat].pattern);
+
+	  if (strlen(e) == len) {
+
+	    match = 1;
+	    strlcpy (xstr, "", valstrsize);
+	    strlcpy (ystr, "", valstrsize);
+	    strlcpy (zstr, "", valstrsize);
+	    strlcpy (bstr, "", valstrsize);
+	    strlcpy (dstr, "", valstrsize);
+
+	    for (k=0; k<len; k++) {
+	      mc = tt_config.ttloc_ptr[ipat].pattern[k];
+	      switch (mc) {
+
+	        case 'B':
+	        case '0':
+	        case '1':
+	        case '2':
+	        case '3':
+	        case '4':
+	        case '5':
+	        case '6':
+	        case '7':
+	        case '8':
+	        case '9':
+	        case 'A':	/* Allow A,C,D after the B? */
+	        case 'C':
+	        case 'D':
+
+		  if (e[k] != mc) {
+		    match = 0;
+		  }
+		  break;
+
+		case 'x':
+		   if (isdigit(e[k])) {
+	             char stemp[2];
+		     stemp[0] = e[k];
+		     stemp[1] = '\0';
+		     strlcat (xstr, stemp, valstrsize);
+		   }
+		   else {
+		     match = 0;
+	           }
+		  break;
+
+		case 'y':
+		   if (isdigit(e[k])) {
+	             char stemp[2];
+		     stemp[0] = e[k];
+		     stemp[1] = '\0';
+		     strlcat (ystr, stemp, valstrsize);
+		   }
+		   else {
+		     match = 0;
+	           }
+		  break;
+
+		case 'z':
+		   if (isdigit(e[k])) {
+	             char stemp[2];
+		     stemp[0] = e[k];
+		     stemp[1] = '\0';
+		     strlcat (zstr, stemp, valstrsize);
+		   }
+		   else {
+		     match = 0;
+	           }
+		  break;
+
+		case 'b':
+		   if (isdigit(e[k])) {
+	             char stemp[2];
+		     stemp[0] = e[k];
+		     stemp[1] = '\0';
+		     strlcat (bstr, stemp, valstrsize);
+		   }
+		   else {
+		     match = 0;
+	           }
+		  break;
+
+		case 'd':
+		   if (isdigit(e[k])) {
+	             char stemp[2];
+		     stemp[0] = e[k];
+		     stemp[1] = '\0';
+		     strlcat (dstr, stemp, valstrsize);
+		   }
+		   else {
+		     match = 0;
+	           }
+		  break;
+
+		default:
+		  dw_printf ("find_ttloc_match: shouldn't be here.\n");
+		  /* Shouldn't be here. */
+		  match = 0;
+		  break;
+
+	      } /* switch */
+	    } /* for k */
+	  
+	    if (match) {
+	      return (ipat);
+	    }
+	  } /* if strlen */
+	}
+	return (-1);
+
+} /* end find_ttloc_match */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_comment
+ *
+ * Purpose:     Extract comment / status or other special information from touch tone message. 
+ *
+ * Inputs:      e		- An "entry" extracted from a complete
+ *				  APRStt messsage.
+ *				  In this case, it should start with "C".
+ *
+ * Outputs:	m_comment
+ *		m_mic_e
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description:	We recognize these different formats:
+ *
+ *		Cn		- One digit (1-9) predefined status.  0 is reserved for none.
+ *				  The defaults are derived from the MIC-E position comments
+ *				  which were always "/" plus exactly 10 characters.
+ *				  Users can override the defaults with configuration options.
+ *	
+ *		Cnnnnnn		- Six digit frequency reformatted as nnn.nnnMHz
+ *
+ *		Cnnn		- Three digit are for CTCSS tone.  Use only integer part
+ *				  and leading 0 if necessary to make exactly 3 digits.
+ *
+ *		Cttt...tttt	- General comment in Multi-press encoding.
+ *
+ *		CAttt...tttt	- New enhanced comment format that can handle all ASCII characters.
+ *
+ *----------------------------------------------------------------*/
+
+static int parse_comment (char *e)
+{
+	int len;
+
+	assert (*e == 'C');
+
+	len = strlen(e);
+
+	if (e[1] == 'A') {
+	  tt_ascii2d_to_text (e+2, 0, m_comment);
+	  return (0);
+	}
+
+	if (len == 2 && isdigit(e[1])) {
+	  m_mic_e = e[1];
+	  return (0);
+	}
+
+	if (len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5]) && isdigit(e[6])) {
+	  m_freq[0] = e[1];
+	  m_freq[1] = e[2];
+	  m_freq[2] = e[3];
+	  m_freq[3] = '.';
+	  m_freq[4] = e[4];
+	  m_freq[5] = e[5];
+	  m_freq[6] = e[6];
+	  m_freq[7] = 'M';
+	  m_freq[8] = 'H';
+	  m_freq[9] = 'z';
+	  m_freq[10] = '\0';
+	  return (0);
+	}
+
+	if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) {
+	  strlcpy (m_ctcss, e+1, sizeof(m_ctcss));
+	  return (0);
+	}
+
+	tt_multipress_to_text (e+1, 0, m_comment);
+	return (0);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        raw_tt_data_to_app
+ *
+ * Purpose:     Send raw touch tone data to application. 
+ *
+ * Inputs:      chan		- Channel where touch tone data heard.
+ *		msg		- String of button pushes.
+ *				  Normally ends with #.
+ *
+ * Global In:	m_callsign
+ *		m_symtab_or_overlay
+ *		m_symbol_code
+ *
+ * Returns:     None
+ *
+ * Description:	
+ * 		Put raw touch tone message in a packet and send to application.
+ * 		The APRS protocol does not have provision for this.
+ * 		For now, use the unused "t" message type.
+ * 		TODO:  Get an officially sanctioned method.
+ *
+ * 		Use callsign for source if available.
+ * 		Encode the overlay in the destination. 
+ *
+ *----------------------------------------------------------------*/
+
+
+static void raw_tt_data_to_app (int chan, char *msg)
+{
+
+#if TT_MAIN
+	return ;
+#else
+	char src[10], dest[10];
+	char raw_tt_msg[256];
+	packet_t pp;
+	char *c, *s;
+	int i;
+	int err;
+	alevel_t alevel;
+
+
+
+// Set source and dest to something valid to keep rest of processing happy.
+// For lack of a better idea, make source "DTMF" to indicate where it came from.
+// Application version might be useful in case we end up using different
+// message formats in later versions.
+
+	strlcpy (src, "DTMF", sizeof(src));
+	snprintf (dest, sizeof(dest), "%s%d%d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
+
+	snprintf (raw_tt_msg, sizeof(raw_tt_msg), "%s>%s:t%s", src, dest, msg);
+
+	pp = ax25_from_text (raw_tt_msg, 1);
+
+/*
+ * Process like a normal received frame.
+ * NOTE:  This goes directly to application rather than
+ * thru the multi modem duplicate processing.
+ *
+ * Should we use a different type so it can be easily
+ * distinguished later?
+ *
+ * We try to capture an overall audio level here.
+ * Mark and space do not apply in this case.
+ * This currently doesn't get displayed but we might want it someday.
+ */
+
+	if (pp != NULL) {
+
+	  alevel = demod_get_audio_level (chan, 0);
+	  alevel.mark = -2;
+	  alevel.space = -2;
+
+	  dlq_append (DLQ_REC_FRAME, chan, -1, 0, pp, alevel, RETRY_NONE, "tt");
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not convert \"%s\" into APRS packet.\n", raw_tt_msg);
+	}
+
+#endif
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        dw_run_cmd
+ *
+ * Purpose:     Run a command and capture the output. 
+ *
+ * Inputs:      cmd		- The command.
+ *
+ *		oneline		- 0 = Keep original line separators. Caller
+ *					must deal with operating system differences.
+ *				  1 = Change CR, LF, TAB to space so result
+ *					is one line of text.
+ *				  2 = Also remove any trailing whitespace.
+ *
+ *		resultsiz	- Amount of space available for result.
+ *
+ * Outputs:	result		- Output captured from running command.
+ *
+ * Returns:     -1 for any sort of error.
+ *		>0 for number of characters returned (= strlen(result))
+ *
+ * Description:	This is currently used for running a user-specified
+ *		script to generate a custom speech response.
+ *
+ * Future:	There are potential other uses so it should probably
+ *		be relocated to a file of other misc. utilities.
+ *
+ *----------------------------------------------------------------*/
+
+int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz) 
+{
+	FILE *fp;
+
+	strlcpy (result, "", resultsiz);
+
+	fp = popen (cmd, "r");
+	if (fp != NULL) {
+	  int remaining = (int)resultsiz;
+	  char *pr = result;
+	  int err;
+
+	  while (remaining > 2 && fgets(pr, remaining, fp) != NULL) {
+	    pr = result + strlen(result);
+	    remaining = (int)resultsiz - strlen(result);
+	  }
+
+	  if ((err = pclose(fp)) != 0) {	 
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("ERROR: Unable to run \"%s\"\n", cmd);
+	    // On Windows, non-existent file produces "Operation not permitted"
+	    // Maybe we should put in a test for whether file exists.
+	    dw_printf ("%s\n", strerror(err));
+
+	    return (-1);
+	  }
+
+	  // take out any newline characters.
+	
+	  if (oneline) {
+	    for (pr = result; *pr != '\0'; pr++) {
+	      if (*pr == '\r' || *pr == '\n' || *pr == '\t') {
+	        *pr = ' ';
+	      }
+	    }
+
+	    if (oneline > 1) {
+	      pr = result + strlen(result) - 1;
+	      while (pr >= result && *pr == ' ') {
+	        *pr = '\0';
+	        pr--;
+	      }
+	    }
+	  }
+
+	  //text_color_set(DW_COLOR_DEBUG);
+	  //dw_printf ("%s returns \"%s\"\n", cmd, result);
+	  
+	  return (strlen(result));
+	}
+
+	else {
+	  // explain_popen() would be nice but doesn't seem to be commonly available.
+	  
+	  // We get here only if fork or pipe fails.
+	  // The command not existing must be caught above.
+
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Unable to run \"%s\"\n", cmd);
+	  dw_printf ("%s\n", strerror(errno));
+	  
+	  return (-1);
+	}
+
+
+} /* end dw_run_cmd */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Unit test for this file.
+ *
+ * Description:	Run unit test like this:
+ *
+ *			rm a.exe ; gcc tt_text.c -DTT_MAIN -Igeotranz aprs_tt.c latlong.o textcolor.o geotranz.a misc.a  ; ./a.exe
+ *		or
+ *			make ttest
+ *
+ *----------------------------------------------------------------*/
+
+
+#if TT_MAIN
+
+/*
+ * Regression test for the parsing.
+ * It does not maintain any history so abbreviation will not invoke previous full call.
+ */
+
+/* Some examples are derived from http://www.aprs.org/aprstt/aprstt-coding24.txt */
+
+
+static const struct {
+	char *toneseq;		/* Tone sequence in. */
+	
+	char *callsign;		/* Expected results... */
+	char *ssid;
+	char *symbol;
+	char *freq;
+	char *comment;
+	char *lat;
+	char *lon;
+	char *dao;
+} testcases[] = {
+
+  /* Callsigns & abbreviations, traditional */
+
+	{ "A9A2B42A7A7C71#",	"WB4APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T  !" }, 	/* WB4APR/7 */
+	{ "A27773#",		"277",    "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T  !" }, 	/* abbreviated form */
+
+	/* Intentionally wrong - Has 6 for checksum when it should be 3. */
+	{ "A27776#",		"",       "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* Expect error message. */
+	
+	/* Example in spec is wrong.  checksum should be 5 in this case. */
+	{ "A2A7A7C71#",		"",       "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* Spelled suffix, overlay, checksum */
+	{ "A2A7A7C75#",		"APR",    "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* Spelled suffix, overlay, checksum */
+	{ "A27773#",		"277",    "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* Suffix digits, overlay, checksum */
+
+	{ "A9A2B26C7D9D71#",	"WB2OSZ", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* WB2OSZ/7 numeric overlay */
+	{ "A67979#",		"679",    "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* abbreviated form */
+
+	{ "A9A2B26C7D9D5A9#",	"WB2OSZ", "12", "JA", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* WB2OSZ/J letter overlay */
+	{ "A6795A7#",		"679",    "12", "JA", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* abbreviated form */
+
+	{ "A277#",		"277",    "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },	/* Tactical call "277" no overlay and no checksum */
+
+  /* QIKcom-2 style 10 digit call & 5 digit suffix */
+
+	{ "AC9242771558#", 	"WB4APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },
+	{ "AC27722#",		"APR",    "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },
+
+  /* Locations */
+
+	{ "B01*A67979#",	"679",    "12", "7A", "", "", "12.2500", "56.2500", "!T1 !" },
+	{ "B988*A67979#",	"679",    "12", "7A", "", "", "12.5000", "56.5000", "!T88!" },
+
+	{ "B51000125*A67979#",	"679",    "12", "7A", "", "", "52.7907", "0.8309", "!TB5!" },		/* expect about 52.79  +0.83 */
+
+	{ "B5206070*A67979#",	"679",    "12", "7A", "", "", "37.9137", "-81.1366", "!TB5!" },		/* Try to get from Hilltop Tower to Archery & Target Range. */
+													/* Latitude comes out ok, 37.9137 -> 55.82 min. */
+													/* Longitude -81.1254 -> 8.20 min */
+	{ "B21234*A67979#",	"679",    "12", "7A", "", "", "12.3400", "56.1200", "!TB2!" },
+	{ "B533686*A67979#",	"679",    "12", "7A", "", "", "37.9222", "81.1143", "!TB5!" },
+
+// TODO: should test other coordinate systems.
+
+  /* Comments */
+
+	{ "C1",			"",       "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },
+	{ "C2",			"",       "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T  !" },
+	{ "C146520",		"",       "12", "\\A", "146.520MHz", "", "-999999.0000", "-999999.0000", "!T  !" },
+	{ "C7788444222550227776669660333666990122223333",
+	                        "",       "12", "\\A", "", "QUICK BROWN FOX 123", "-999999.0000", "-999999.0000", "!T  !" },
+  /* Macros */
+
+	{ "88345",		"BIKE 345", "0", "/b", "", "", "12.5000", "56.5000", "!T88!" },
+
+  /* 10 digit representation for callsign & satellite grid. WB4APR near 39.5, -77   */
+
+	{ "AC9242771558*BA1819", "WB4APR", "12", "\\A", "", "", "39.5000", "-77.0000", "!TBA!" },
+	{ "18199242771558",	 "WB4APR", "12", "\\A", "", "", "39.5000", "-77.0000", "!TBA!" },
+};
+
+
+static int test_num;
+static int error_count;
+
+static void check_result (void)
+{
+	char stemp[32];
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n", 
+		m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_comment, m_latitude, m_longitude, m_dao);
+
+
+	if (strcmp(m_callsign, testcases[test_num].callsign) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for callsign.\n", testcases[test_num].callsign);
+	  error_count++;
+	}
+
+	snprintf (stemp, sizeof(stemp), "%d", m_ssid);
+	if (strcmp(stemp, testcases[test_num].ssid) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for SSID.\n", testcases[test_num].ssid);
+	  error_count++;
+	}
+
+	stemp[0] = m_symtab_or_overlay;
+	stemp[1] = m_symbol_code;
+	stemp[2] = '\0';
+	if (strcmp(stemp, testcases[test_num].symbol) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for Symbol.\n", testcases[test_num].symbol);
+	  error_count++;
+	}
+
+	if (strcmp(m_freq, testcases[test_num].freq) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for Freq.\n", testcases[test_num].freq);
+	  error_count++;
+	}
+
+	if (strcmp(m_comment, testcases[test_num].comment) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for Comment.\n", testcases[test_num].comment);
+	  error_count++;
+	}
+
+	snprintf (stemp, sizeof(stemp), "%.4f", m_latitude);
+	if (strcmp(stemp, testcases[test_num].lat) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for Latitude.\n", testcases[test_num].lat);
+	  error_count++;
+	}
+
+	snprintf (stemp, sizeof(stemp), "%.4f", m_longitude);
+	if (strcmp(stemp, testcases[test_num].lon) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for Longitude.\n", testcases[test_num].lon);
+	  error_count++;
+	}
+
+	if (strcmp(m_dao, testcases[test_num].dao) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR: Expected \"%s\" for DAO.\n", testcases[test_num].dao);
+	  error_count++;
+	}
+}
+
+
+int main (int argc, char *argv[])
+{
+	aprs_tt_init (NULL);
+
+	error_count = 0;
+
+	for (test_num = 0; test_num < sizeof(testcases) / sizeof(testcases[0]); test_num++) {
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("\nTest case %d: %s\n", test_num, testcases[test_num].toneseq);
+
+	  aprs_tt_sequence (0, testcases[test_num].toneseq);
+	}
+
+	if (error_count != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\n\nTEST FAILED, Total of %d errors.\n", error_count);
+	  return (EXIT_FAILURE);
+	}
+
+	text_color_set(DW_COLOR_REC);
+	dw_printf ("\n\nAll tests passed.\n");
+	return (EXIT_SUCCESS);
+
+}  /* end main */
+
+
+#endif		
+
+/* end aprs_tt.c */
+
diff --git a/aprs_tt.h b/aprs_tt.h
index 28a757b..8cc845d 100644
--- a/aprs_tt.h
+++ b/aprs_tt.h
@@ -1,120 +1,191 @@
-
-/* aprs_tt.h */
-
-#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.
- * We have exactly the same situation of looking for a pattern
- * match and extracting fixed size groups of digits.
- */
-
-struct ttloc_s {
-	enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_SATSQ } type;
-
-	char pattern[20];	/* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx, BAxxxx */
-				/* For macros, it should be all fixed digits, */
-				/* and the letters x, y, z.  e.g.  911, xxyyyz */
-
-	union {
-
-	  struct {	
-	    double lat;		/* Specific locations. */
-	    double lon;
-	  } point;
-
-	  struct {
-	    double lat;		/* For bearing/direction. */
-	    double lon;
-	    double scale;	/* conversion to meters */
-	  } vector;
-
-	  struct {
-	    double lat0;	/* yyy all zeros. */
-	    double lon0;	/* xxx */
-	    double lat9;	/* yyy all nines. */
-	    double lon9;	/* xxx */
-	  } grid;
-
-	  struct {
-	    double scale;
-	    double x_offset;
-	    double y_offset;
-	    long lzone;		/* UTM zone, should be 1-60 */
-	    char hemi;		/* UTM Hemisphere, should be 'N' or 'S'. */
-	  } utm;
-
-	  struct {
-	    char zone[8];	/* Zone and square for USNG/MGRS */
-	  } mgrs;
-
-	  struct {
-	    char *definition;
-	  } macro;
-	};
-};
-
-/* 
- * Configuration options for APRStt.
- */
-
-#define TT_MAX_XMITS 10
-
-struct tt_config_s {
-
-	int gateway_enabled;		/* Send DTMF sequences to APRStt gateway. */
-
-	int obj_recv_chan;		/* Channel to listen for tones. */
-
-	int obj_xmit_chan;		/* Channel to transmit object report. */
-
-	char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)];	
-					/* e.g.  empty or "WIDE2-1,WIDE1-1" */
-	
-	int retain_time;		/* Seconds to keep information about a user. */
-	int num_xmits;			/* Number of times to transmit object report. */
-	int xmit_delay[TT_MAX_XMITS];	/* Delay between them. */
-
-	struct ttloc_s *ttloc_ptr;	/* Pointer to variable length array of above. */
-	int ttloc_size;			/* Number of elements allocated. */
-	int ttloc_len;			/* Number of elements actually used. */
-
-	double corral_lat;		/* The "corral" for unknown locations. */
-	double corral_lon;
-	double corral_offset;
-	int corral_ambiguity;
-};
-
-
-
-	
-void aprs_tt_init (struct tt_config_s *p_config);
-
-void aprs_tt_button (int chan, char button);
-
-/* Error codes for sending responses to user. */
-
-#define TT_ERROR_D_MSG		1	/* D was first char of field.  Not implemented yet. */
-#define TT_ERROR_INTERNAL	2	/* Internal error.  Shouldn't be here. */
-#define TT_ERROR_MACRO_NOMATCH	3	/* No definition for digit sequence. */
-#define TT_ERROR_BAD_CHECKSUM	4	/* Bad checksum on call. */
-#define TT_ERROR_INVALID_CALL	5	/* Invalid callsign. */
-#define TT_ERROR_INVALID_OBJNAME 6	/* Invalid object name. */
-#define TT_ERROR_INVALID_SYMBOL	7	/* Invalid symbol specification. */
-#define TT_ERROR_INVALID_LOC	8	/* Invalid location. */
-#define TT_ERROR_NO_CALL	9	/* No call or object name included. */
-#define TT_ERROR_SATSQ		10	/* Satellite square must be 4 digits. */
-
-
-#define APRSTT_LOC_DESC_LEN 32		/* Need at least 26 */
-
-void aprs_tt_dao_to_desc (char *dao, char *str);
-
-void aprs_tt_sequence (int chan, char *msg);
-
-#endif
-
+
+/* aprs_tt.h */
+
+#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.
+ * We have exactly the same situation of looking for a pattern
+ * match and extracting fixed size groups of digits.
+ */
+
+struct ttloc_s {
+	enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_MHEAD, TTLOC_SATSQ, TTLOC_AMBIG } type;
+
+	char pattern[20];	/* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx, BAxxxx */
+				/* For macros, it should be all fixed digits, */
+				/* and the letters x, y, z.  e.g.  911, xxyyyz */
+
+	union {
+
+	  struct {	
+	    double lat;		/* Specific locations. */
+	    double lon;
+	  } point;
+
+	  struct {
+	    double lat;		/* For bearing/direction. */
+	    double lon;
+	    double scale;	/* conversion to meters */
+	  } vector;
+
+	  struct {
+	    double lat0;	/* yyy all zeros. */
+	    double lon0;	/* xxx */
+	    double lat9;	/* yyy all nines. */
+	    double lon9;	/* xxx */
+	  } grid;
+
+	  struct {
+	    double scale;
+	    double x_offset;
+	    double y_offset;
+	    long lzone;		/* UTM zone, should be 1-60 */
+	    char latband;	/* Latitude band if specified, otherwise space or - */
+	    char hemi;		/* UTM Hemisphere, should be 'N' or 'S'. */
+	  } utm;
+
+	  struct {
+	    char zone[8];	/* Zone and square for USNG/MGRS */
+	  } mgrs;
+
+	  struct {
+	    char prefix[24];	/* should be 10, 6, or 4 digits to be */
+				/* prepended to the received sequence. */
+	  } mhead;
+
+	  struct {
+	    char *definition;
+	  } macro;
+
+	};
+};
+
+
+/* Error codes for sending responses to user. */
+
+#define TT_ERROR_OK		0	/* Success. */
+#define TT_ERROR_D_MSG		1	/* D was first char of field.  Not implemented yet. */
+#define TT_ERROR_INTERNAL	2	/* Internal error.  Shouldn't be here. */
+#define TT_ERROR_MACRO_NOMATCH	3	/* No definition for digit sequence. */
+#define TT_ERROR_BAD_CHECKSUM	4	/* Bad checksum on call. */
+#define TT_ERROR_INVALID_CALL	5	/* Invalid callsign. */
+#define TT_ERROR_INVALID_OBJNAME 6	/* Invalid object name. */
+#define TT_ERROR_INVALID_SYMBOL	7	/* Invalid symbol specification. */
+#define TT_ERROR_INVALID_LOC	8	/* Invalid location. */
+#define TT_ERROR_NO_CALL	9	/* No call or object name included. */
+#define TT_ERROR_INVALID_MHEAD	10	/* Invalid Maidenhead Locator. */
+#define TT_ERROR_INVALID_SATSQ	11	/* Satellite square must be 4 digits. */
+#define TT_ERROR_SUFFIX_NO_CALL 12	/* No known callsign for suffix. */
+
+#define TT_ERROR_MAXP1		13	/* Number of items above.  i.e. Last number plus 1. */
+
+
+#if CONFIG_C		/* Is this being included from config.c? */
+
+/* Must keep in sync with above !!! */
+
+static const char *tt_msg_id[TT_ERROR_MAXP1] = {
+	"OK",
+	"D_MSG",
+	"INTERNAL",
+	"MACRO_NOMATCH",
+	"BAD_CHECKSUM",
+	"INVALID_CALL",
+	"INVALID_OBJNAME",
+	"INVALID_SYMBOL",
+	"INVALID_LOC",
+	"NO_CALL",
+	"INVALID_MHEAD",
+	"INVALID_SATSQ",
+	"SUFFIX_NO_CALL"
+};
+
+#endif
+
+/* 
+ * Configuration options for APRStt.
+ */
+
+#define TT_MAX_XMITS 10
+
+#define TT_MTEXT_LEN 64
+
+
+struct tt_config_s {
+
+	int gateway_enabled;		/* Send DTMF sequences to APRStt gateway. */
+
+	int obj_recv_chan;		/* Channel to listen for tones. */
+
+	int obj_xmit_chan;		/* Channel to transmit object report. */
+					/* -1 for none.  This could happpen if we */
+					/* are only sending to application */
+					/* and/or IGate. */
+
+	int obj_send_to_app;		/* send to attached application(s). */
+
+	int obj_send_to_ig;		/* send to IGate. */
+
+	char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)];	
+					/* e.g.  empty or "WIDE2-1,WIDE1-1" */
+	
+	int retain_time;		/* Seconds to keep information about a user. */
+
+	int num_xmits;			/* Number of times to transmit object report. */
+				
+	int xmit_delay[TT_MAX_XMITS];	/* Delay between them. */
+					/* e.g.  3 seconds before first transmission then */
+					/* delays of 16, 32, seconds etc. in between repeats. */
+
+	struct ttloc_s *ttloc_ptr;	/* Pointer to variable length array of above. */
+	int ttloc_size;			/* Number of elements allocated. */
+	int ttloc_len;			/* Number of elements actually used. */
+
+	double corral_lat;		/* The "corral" for unknown locations. */
+	double corral_lon;
+	double corral_offset;
+	int corral_ambiguity;
+
+	char status[10][TT_MTEXT_LEN];		/* Up to 9 status messages. e.g.  "/enroute" */
+						/* Position 0 means none and can't be changed. */
+
+	struct {
+	  char method[AX25_MAX_ADDR_LEN];	/* SPEECH or MORSE[-n] */
+	  char mtext[TT_MTEXT_LEN];		/* Message text. */
+	} response[TT_ERROR_MAXP1];
+
+	char ttcmd[80];			/* Command to generate custom audible response. */
+};
+
+
+
+	
+void aprs_tt_init (struct tt_config_s *p_config);
+
+void aprs_tt_button (int chan, char button);
+
+
+
+
+
+#define APRSTT_LOC_DESC_LEN 32		/* Need at least 26 */
+
+#define APRSTT_DEFAULT_SYMTAB '\\'
+#define APRSTT_DEFAULT_SYMBOL 'A'
+
+
+void aprs_tt_dao_to_desc (char *dao, char *str);
+
+void aprs_tt_sequence (int chan, char *msg);
+
+int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz);
+
+
+#endif
+
 /* end aprs_tt.h */
\ No newline at end of file
diff --git a/atest.c b/atest.c
index beb0bc6..7ab60a8 100644
--- a/atest.c
+++ b/atest.c
@@ -1,708 +1,774 @@
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
-//
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        atest.c
- *
- * Purpose:     Test fixture for the AFSK demodulator.
- *
- * Inputs:	Takes audio from a .WAV file insted of the audio device.
- *
- * Description:	This can be used to test the AFSK demodulator under
- *		controlled and reproducable conditions for tweaking.
- *	
- *		For example
- *
- *		(1) Download WA8LMF's TNC Test CD image file from
- *			http://wa8lmf.net/TNCtest/index.htm
- *
- *		(2) Burn a physical CD.
- *
- *		(3) "Rip" the desired tracks with Windows Media Player.
- *			Select .WAV file format.
- *		
- *		"Track 2" is used for most tests because that is more
- *		realistic for most people using the speaker output.
- *
- *
- * 	Without ONE_CHAN defined:
- *
- *	  Notice that the number of packets decoded, as reported by
- *	  this test program, will be twice the number expected because
- *	  we are decoding the left and right audio channels separately.
- *
- *
- * 	With ONE_CHAN defined:
- *
- *	  Only process one channel.  
- *
- *--------------------------------------------------------------------*/
-
-// #define X 1
-
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <time.h>
-#include <getopt.h>
-
-
-#define ATEST_C 1
-
-#include "audio.h"
-#include "demod.h"
-#include "multi_modem.h"
-#include "textcolor.h"
-#include "ax25_pad.h"
-#include "hdlc_rec2.h"
-#include "dlq.h"
-#include "ptt.h"
-
-
-
-#if 0	/* Typical but not flexible enough. */
-
-struct wav_header {             /* .WAV file header. */
-        char riff[4];           /* "RIFF" */
-        int filesize;          /* file length - 8 */
-        char wave[4];           /* "WAVE" */
-        char fmt[4];            /* "fmt " */
-        int fmtsize;           /* 16. */
-        short wformattag;       /* 1 for PCM. */
-        short nchannels;        /* 1 for mono, 2 for stereo. */
-        int nsamplespersec;    /* sampling freq, Hz. */
-        int navgbytespersec;   /* = nblockalign*nsamplespersec. */
-        short nblockalign;      /* = wbitspersample/8 * nchannels. */
-        short wbitspersample;   /* 16 or 8. */
-        char data[4];           /* "data" */
-        int datasize;          /* number of bytes following. */
-} ;
-#endif
-					/* 8 bit samples are unsigned bytes */
-					/* in range of 0 .. 255. */
- 
- 					/* 16 bit samples are signed short */
-					/* in range of -32768 .. +32767. */
-
-static struct {
-        char riff[4];          /* "RIFF" */
-        int filesize;          /* file length - 8 */
-        char wave[4];          /* "WAVE" */
-} header;
-
-static struct {
-	char id[4];		/* "LIST" or "fmt " */
-	int datasize;
-} chunk;
-
-static struct {
-        short wformattag;       /* 1 for PCM. */
-        short nchannels;        /* 1 for mono, 2 for stereo. */
-        int nsamplespersec;    /* sampling freq, Hz. */
-        int navgbytespersec;   /* = nblockalign*nsamplespersec. */
-        short nblockalign;      /* = wbitspersample/8 * nchannels. */
-        short wbitspersample;   /* 16 or 8. */
-	char extras[4];
-} format;
-
-static struct {
-	char data[4];		/* "data" */
-	int datasize;
-} wav_data;
-
-
-static FILE *fp;
-static int e_o_f;
-static int packets_decoded = 0;
-static int decimate = 0;		/* Reduce that sampling rate if set. */
-					/* 1 = normal, 2 = half, etc. */
-
-static struct audio_s my_audio_config;
-
-static int error_if_less_than = 0;	/* Exit with error status if this minimum not reached. */
-
-
-
-//#define EXPERIMENT_G 1
-//#define EXPERIMENT_H 1
-
-#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
-
-static 	int count[MAX_SUBCHANS];
-
-#if EXPERIMENT_H
-extern float space_gain[MAX_SUBCHANS];
-#endif
-
-#endif
-
-static void usage (void);
-
-
-
-int main (int argc, char *argv[])
-{
-
-	int err;
-	int c;
-	int channel;
-	time_t start_time;
-
-
-
-#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
-	int j;
-
-	for (j=0; j<MAX_SUBCHANS; j++) {
-	  count[j] = 0;
-	}
-#endif
-
-	text_color_init(1);
-	text_color_set(DW_COLOR_INFO);
-
-/* 
- * First apply defaults.
- */
-	
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-
-	my_audio_config.adev[0].num_channels = DEFAULT_NUM_CHANNELS;		
-	my_audio_config.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;	
-	my_audio_config.adev[0].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, my_audio_config.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, my_audio_config.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.
-
-
-	for (channel=0; channel<MAX_CHANS; channel++) {
-
-	  my_audio_config.achan[channel].modem_type = MODEM_AFSK;
-
-	  my_audio_config.achan[channel].mark_freq = DEFAULT_MARK_FREQ;		
-	  my_audio_config.achan[channel].space_freq = DEFAULT_SPACE_FREQ;		
-	  my_audio_config.achan[channel].baud = DEFAULT_BAUD;	
-
-	  strcpy (my_audio_config.achan[channel].profiles, "E");	
- 		
-	  my_audio_config.achan[channel].num_freq = 1;				
-	  my_audio_config.achan[channel].offset = 0;	
-
-	  my_audio_config.achan[channel].fix_bits = RETRY_NONE;	
-
-	  my_audio_config.achan[channel].sanity_test = SANITY_APRS;	
-	  //my_audio_config.achan[channel].sanity_test = SANITY_AX25;	
-	  //my_audio_config.achan[channel].sanity_test = SANITY_NONE;	
-
-	  my_audio_config.achan[channel].passall = 0;				
-	  //my_audio_config.achan[channel].passall = 1;				
-	}
-
-	while (1) {
-          int this_option_optind = optind ? optind : 1;
-          int option_index = 0;
-          static struct option long_options[] = {
-            {"future1", 1, 0, 0},
-            {"future2", 0, 0, 0},
-            {"future3", 1, 0, 'c'},
-            {0, 0, 0, 0}
-          };
-
-	  /* ':' following option character means arg is required. */
-
-          c = getopt_long(argc, argv, "B:P:D:F:e:",
-                        long_options, &option_index);
-          if (c == -1)
-            break;
-
-          switch (c) {
-
-            case 'B':				/* -B for data Bit rate */
-						/*    300 implies 1600/1800 AFSK. */
-						/*    1200 implies 1200/2200 AFSK. */
-						/*    9600 implies scrambled. */
-
-              my_audio_config.achan[0].baud = atoi(optarg);
-
-              dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud);
-
-              if (my_audio_config.achan[0].baud < 100 || my_audio_config.achan[0].baud > 10000) {
-		text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
-                exit (EXIT_FAILURE);
-              }
-	      if (my_audio_config.achan[0].baud < 600) {
-                my_audio_config.achan[0].modem_type = MODEM_AFSK;
-                my_audio_config.achan[0].mark_freq = 1600;
-                my_audio_config.achan[0].space_freq = 1800;
-	        strcpy (my_audio_config.achan[0].profiles, "D");	      }
-	      else if (my_audio_config.achan[0].baud > 2400) {
-                my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
-                my_audio_config.achan[0].mark_freq = 0;
-                my_audio_config.achan[0].space_freq = 0;
-	        strcpy (my_audio_config.achan[0].profiles, " ");	// avoid getting default later.
-                dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
-	      }
-	      else {
-                my_audio_config.achan[0].modem_type = MODEM_AFSK;
-                my_audio_config.achan[0].mark_freq = 1200;
-                my_audio_config.achan[0].space_freq = 2200;
-	      }
-              break;
-
-	    case 'P':				/* -P for modem profile. */
-
-	      dw_printf ("Demodulator profile set to \"%s\"\n", optarg);
-	      strcpy (my_audio_config.achan[0].profiles, optarg); 
-	      break;	
-
-	    case 'D':				/* -D reduce sampling rate for lower CPU usage. */
-
-	      decimate = atoi(optarg);
-
-	      dw_printf ("Divide audio sample rate by %d\n", decimate);
-	      if (decimate < 1 || decimate > 8) {
-		text_color_set(DW_COLOR_ERROR);
-		dw_printf ("Unreasonable value for -D.\n");
-		exit (1);
-	      }
-	      dw_printf ("Divide audio sample rate by %d\n", decimate);
-	      my_audio_config.achan[0].decimate = decimate;
-	      break;	
-
-	    case 'F':				/* -D set "fix bits" level. */
-
-	      my_audio_config.achan[0].fix_bits = atoi(optarg);
-
-	      if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) {
-		text_color_set(DW_COLOR_ERROR);
-		dw_printf ("Invalid Fix Bits level.\n");
-		exit (1);
-	      }
-	      break;	
-
-	    case 'e':				/* -e error if less than this number decoded. */
-
-	      error_if_less_than = atoi(optarg);
-	      break;	
-
-             case '?':
-
-              /* Unknown option message was already printed. */
-              usage ();
-              break;
-
-            default:
-
-              /* Should not be here. */
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf("?? getopt returned character code 0%o ??\n", c);
-              usage ();
-	    }
-        }
-    
-	if (optind >= argc) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Specify .WAV file name on command line.\n");
-	  usage ();
-	}
-
-	fp = fopen(argv[optind], "rb");
-        if (fp == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf ("Couldn't open file for read: %s\n", argv[optind]);
-	  //perror ("more info?");
-          exit (1);
-        }
-
-	start_time = time(NULL);
-
-
-/*
- * Read the file header.  
- * Doesn't handle all possible cases but good enough for our purposes.
- */
-
-        err= fread (&header, (size_t)12, (size_t)1, fp);
-
-	if (strncmp(header.riff, "RIFF", 4) != 0 || strncmp(header.wave, "WAVE", 4) != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf ("This is not a .WAV format file.\n");
-          exit (EXIT_FAILURE);
-	}
-
-	err = fread (&chunk, (size_t)8, (size_t)1, fp);
-
-	if (strncmp(chunk.id, "LIST", 4) == 0) {
-	  err = fseek (fp, (long)chunk.datasize, SEEK_CUR);
-	  err = fread (&chunk, (size_t)8, (size_t)1, fp);
-	}
-
-	if (strncmp(chunk.id, "fmt ", 4) != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id);
-	  exit(1);
-	}
-	if (chunk.datasize != 16 && chunk.datasize != 18) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18.  Found %d.\n", chunk.datasize);
-	  exit(1);
-	}
-
-        err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp);	
-
-	err = fread (&wav_data, (size_t)8, (size_t)1, fp);
-
-	if (strncmp(wav_data.data, "data", 4) != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data);
-	  exit(1);
-	}
-
-	assert (format.nchannels == 1 || format.nchannels == 2);
-	assert (format.wbitspersample == 8 || format.wbitspersample == 16);
-
-        my_audio_config.adev[0].samples_per_sec = format.nsamplespersec;
-	my_audio_config.adev[0].bits_per_sample = format.wbitspersample;
- 	my_audio_config.adev[0].num_channels = format.nchannels;
-
-	my_audio_config.achan[0].valid = 1;
-	if (format.nchannels == 2) my_audio_config.achan[1].valid = 1;
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("%d samples per second\n", my_audio_config.adev[0].samples_per_sec);
-	dw_printf ("%d bits per sample\n", my_audio_config.adev[0].bits_per_sample);
-	dw_printf ("%d audio channels\n", my_audio_config.adev[0].num_channels);
-	dw_printf ("%d audio bytes in file\n", (int)(wav_data.datasize));
-	dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits);
-
-		
-/*
- * Initialize the AFSK demodulator and HDLC decoder.
- */
-	multi_modem_init (&my_audio_config);
-
-
-	e_o_f = 0;
-	while ( ! e_o_f) 
-	{
-
-
-          int audio_sample;
-          int c;
-
-          for (c=0; c<my_audio_config.adev[0].num_channels; c++)
-          {
-
-            /* This reads either 1 or 2 bytes depending on */
-            /* bits per sample.  */
-
-            audio_sample = demod_get_sample (ACHAN2ADEV(c));
-
-            if (audio_sample >= 256 * 256)
-              e_o_f = 1;
-
-#define ONE_CHAN 1              /* only use one audio channel. */
-
-#if ONE_CHAN
-            if (c != 0) continue;
-#endif
-
-            multi_modem_process_sample(c,audio_sample);
-          }
-
-                /* When a complete frame is accumulated, */
-                /* process_rec_frame, below, is called. */
-
-	}
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("\n\n");
-
-#if EXPERIMENT_G
-
-	for (j=0; j<MAX_SUBCHANS; j++) {
-	  float db = 20.0 * log10f(space_gain[j]);
-	  dw_printf ("%+.1f dB, %d\n", db, count[j]);
-	}
-#endif
-#if EXPERIMENT_H
-
-	for (j=0; j<MAX_SUBCHANS; j++) {
-	  dw_printf ("%d\n", count[j]);
-	}
-#endif
-	dw_printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time));
-
-	if (packets_decoded < error_if_less_than) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\n * * * TEST FAILED to achieve minimum of %d * * * \n", error_if_less_than);
-	  exit (1);
-	}
-
-	exit (0);
-}
-
-
-/*
- * Simulate sample from the audio device.
- */
-
-int audio_get (int a)
-{
-	int ch;
-
-	if (wav_data.datasize <= 0) {
-	  e_o_f = 1;
-	  return (-1);
-	}
-
-	ch = getc(fp);
-	wav_data.datasize--;
-
-	if (ch < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Unexpected end of file.\n");
-	  e_o_f = 1;
-	}
-
-	return (ch);
-}
-
-
-
-/*
- * Rather than queuing up frames with bad FCS, 
- * try to fix them immediately.
- */
-
-void rdq_append (rrbb_t rrbb)
-{
-	int chan;
-	alevel_t alevel;
-	int subchan;
-
-
-	chan = rrbb_get_chan(rrbb);
-	subchan = rrbb_get_subchan(rrbb);
-	alevel = rrbb_get_audio_level(rrbb);
-
-	hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, alevel);
-	rrbb_delete (rrbb);
-}
-
-
-/*
- * This is called when we have a good frame.
- */
-
-void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)  
-{	
-	
-	char stemp[500];
-	unsigned char *pinfo;
-	int info_len;
-	int h;
-	char heard[20];
-	char alevel_text[32];
-
-	packets_decoded++;
-
-
-	ax25_format_addrs (pp, stemp);
-
-	info_len = ax25_get_info (pp, &pinfo);
-
-	/* Print so we can see what is going on. */
-
-//TODO: quiet option - suppress packet printing, only the count at the end.
-
-#if 1
-	/* Display audio input level. */
-        /* Who are we hearing?   Original station or digipeater? */
-
-	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);
-	}
-
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("\n");
-	dw_printf("DECODED[%d] ", packets_decoded );
-	if (h != AX25_SOURCE) {
-	  dw_printf ("Digipeater ");
-	}
-	ax25_alevel_to_text (alevel, alevel_text);
-
-	if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) {
-	  dw_printf ("%s audio level = %s     %s\n", heard, alevel_text, spectrum);
-	}
-	else {
-	  dw_printf ("%s audio level = %s   [%s]   %s\n", heard, alevel_text, retry_text[(int)retries], spectrum);
-	}
-
-#endif
-
-#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
-	int j;
-
-	for (j=0; j<MAX_SUBCHANS; j++) {
-	  if (spectrum[j] == '|') {
-	    count[j]++;
-	  }
-	}
-#endif
-
-
-// Display non-APRS packets in a different color.
-
-// TODO: display subchannel if appropriate.
-
-	if (ax25_is_aprs(pp)) {
-	  text_color_set(DW_COLOR_REC);
-	  dw_printf ("[%d] ", chan);
-	}
-	else {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("[%d] ", chan);
-	}
-
-	dw_printf ("%s", stemp);			/* stations followed by : */
-	ax25_safe_print ((char *)pinfo, info_len, 0);
-	dw_printf ("\n");
-
-	ax25_delete (pp);
-
-} /* end app_process_rec_packet */
-
-
-void ptt_set (int ot, int chan, int ptt_signal)
-{
-	return;
-}
-
-
-static void usage (void) {
-
-	text_color_set(DW_COLOR_ERROR);
-
-	dw_printf ("\n");
-	dw_printf ("atest is a test application which decodes AX.25 frames from an audio\n");
-	dw_printf ("recording.  This provides an easy way to test Dire Wolf decoding\n");
-	dw_printf ("performance much quicker than normal real-time.   \n"); 
-	dw_printf ("\n");
-	dw_printf ("usage:\n");
-	dw_printf ("\n");
-	dw_printf ("        atest [ options ] wav-file-in\n");
-	dw_printf ("\n");
-	dw_printf ("        -B n   Bits/second  for data.  Proper modem automatically selected for speed.\n");
-	dw_printf ("               300 baud uses 1600/1800 Hz AFSK.\n");
-	dw_printf ("               1200 (default) baud uses 1200/2200 Hz AFSK.\n");
-	dw_printf ("               9600 baud uses K9NG/G2RUH standard.\n");
-	dw_printf ("\n");
-	dw_printf ("        -D n   Divide audio sample rate by n.\n");
-	dw_printf ("\n");
-	dw_printf ("        -F n   Amount of effort to try fixing frames with an invalid CRC.  \n");
-	dw_printf ("               0 (default) = consider only correct frames.  \n");
-	dw_printf ("               1 = Try to fix only a single bit.  \n");
-	dw_printf ("               more = Try modifying more bits to get a good CRC.\n");
-	dw_printf ("\n");
-	dw_printf ("        -P m   Select  the  demodulator  type such as A, B, C, D (default for 300 baud),\n");
-	dw_printf ("               E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+.\n");
-	dw_printf ("\n");
-	dw_printf ("        wav-file-in is a WAV format audio file.\n");
-	dw_printf ("\n");
-	dw_printf ("Examples:\n");
-	dw_printf ("\n");
-	dw_printf ("        gen_packets -o test1.wav\n");
-	dw_printf ("        atest test1.wav\n");
-	dw_printf ("\n");
-	dw_printf ("        gen_packets -B 300 -o test3.wav\n");
-	dw_printf ("        atest -B 300 test3.wav\n");
-	dw_printf ("\n");
-	dw_printf ("        gen_packets -B 9600 -o test9.wav\n");
-	dw_printf ("        atest -B 9600 test9.wav\n");
-	dw_printf ("\n");
-	dw_printf ("              This generates and decodes 3 test files with 1200, 300, and 9600\n");
-	dw_printf ("              bits per second.\n");
-	dw_printf ("\n");
-	dw_printf ("        atest 02_Track_2.wav\n");
-	dw_printf ("        atest -P C+ 02_Track_2.wav\n");
-	dw_printf ("        atest -F 1 02_Track_2.wav\n");
-	dw_printf ("        atest -P C+ -F 1 02_Track_2.wav\n");
-	dw_printf ("\n");
-	dw_printf ("              Try  different combinations of options to find the best decoding\n");
-	dw_printf ("              performance.\n");
-
-	exit (1);
-}
-
-
-
-/* end atest.c */
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
+//
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        atest.c
+ *
+ * Purpose:     Test fixture for the AFSK demodulator.
+ *
+ * Inputs:	Takes audio from a .WAV file insted of the audio device.
+ *
+ * Description:	This can be used to test the AFSK demodulator under
+ *		controlled and reproducable conditions for tweaking.
+ *	
+ *		For example
+ *
+ *		(1) Download WA8LMF's TNC Test CD image file from
+ *			http://wa8lmf.net/TNCtest/index.htm
+ *
+ *		(2) Burn a physical CD.
+ *
+ *		(3) "Rip" the desired tracks with Windows Media Player.
+ *			Select .WAV file format.
+ *		
+ *		"Track 2" is used for most tests because that is more
+ *		realistic for most people using the speaker output.
+ *
+ *
+ * 	Without ONE_CHAN defined:
+ *
+ *	  Notice that the number of packets decoded, as reported by
+ *	  this test program, will be twice the number expected because
+ *	  we are decoding the left and right audio channels separately.
+ *
+ *
+ * 	With ONE_CHAN defined:
+ *
+ *	  Only process one channel.  
+ *
+ *--------------------------------------------------------------------*/
+
+// #define X 1
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+
+
+#define ATEST_C 1
+
+#include "audio.h"
+#include "demod.h"
+#include "multi_modem.h"
+#include "textcolor.h"
+#include "ax25_pad.h"
+#include "hdlc_rec2.h"
+#include "dlq.h"
+#include "ptt.h"
+
+
+
+#if 0	/* Typical but not flexible enough. */
+
+struct wav_header {             /* .WAV file header. */
+        char riff[4];           /* "RIFF" */
+        int filesize;          /* file length - 8 */
+        char wave[4];           /* "WAVE" */
+        char fmt[4];            /* "fmt " */
+        int fmtsize;           /* 16. */
+        short wformattag;       /* 1 for PCM. */
+        short nchannels;        /* 1 for mono, 2 for stereo. */
+        int nsamplespersec;    /* sampling freq, Hz. */
+        int navgbytespersec;   /* = nblockalign*nsamplespersec. */
+        short nblockalign;      /* = wbitspersample/8 * nchannels. */
+        short wbitspersample;   /* 16 or 8. */
+        char data[4];           /* "data" */
+        int datasize;          /* number of bytes following. */
+} ;
+#endif
+					/* 8 bit samples are unsigned bytes */
+					/* in range of 0 .. 255. */
+ 
+ 					/* 16 bit samples are signed short */
+					/* in range of -32768 .. +32767. */
+
+static struct {
+        char riff[4];          /* "RIFF" */
+        int filesize;          /* file length - 8 */
+        char wave[4];          /* "WAVE" */
+} header;
+
+static struct {
+	char id[4];		/* "LIST" or "fmt " */
+	int datasize;
+} chunk;
+
+static struct {
+        short wformattag;       /* 1 for PCM. */
+        short nchannels;        /* 1 for mono, 2 for stereo. */
+        int nsamplespersec;    /* sampling freq, Hz. */
+        int navgbytespersec;   /* = nblockalign*nsamplespersec. */
+        short nblockalign;      /* = wbitspersample/8 * nchannels. */
+        short wbitspersample;   /* 16 or 8. */
+	char extras[4];
+} format;
+
+static struct {
+	char data[4];		/* "data" */
+	int datasize;
+} wav_data;
+
+
+static FILE *fp;
+static int e_o_f;
+static int packets_decoded = 0;
+static int decimate = 0;		/* Reduce that sampling rate if set. */
+					/* 1 = normal, 2 = half, etc. */
+
+static struct audio_s my_audio_config;
+
+static int error_if_less_than = -1;	/* Exit with error status if this minimum not reached. */
+					/* Can be used to check that performance has not decreased. */
+
+static int error_if_greater_than = -1;	/* Exit with error status if this maximum exceeded. */
+					/* Can be used to check that duplicate removal is not broken. */
+
+
+
+//#define EXPERIMENT_G 1
+//#define EXPERIMENT_H 1
+
+#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
+
+static 	int count[MAX_SUBCHANS];
+
+#if EXPERIMENT_H
+extern float space_gain[MAX_SUBCHANS];
+#endif
+
+#endif
+
+static void usage (void);
+
+
+static int decode_only = 0;		/* Set to 0 or 1 to decode only one channel.  2 for both.  */
+
+static int sample_number = -1;		/* Sample number from the file. */
+					/* Incremented only for channel 0. */
+					/* Use to print timestamp, relative to beginning */
+					/* of file, when frame was decoded. */
+
+int main (int argc, char *argv[])
+{
+
+	int err;
+	int c;
+	int channel;
+	time_t start_time;
+
+
+#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
+	int j;
+
+	for (j=0; j<MAX_SUBCHANS; j++) {
+	  count[j] = 0;
+	}
+#endif
+
+	text_color_init(1);
+	text_color_set(DW_COLOR_INFO);
+
+/* 
+ * First apply defaults.
+ */
+	
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+
+	my_audio_config.adev[0].num_channels = DEFAULT_NUM_CHANNELS;		
+	my_audio_config.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;	
+	my_audio_config.adev[0].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, my_audio_config.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, my_audio_config.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.
+
+
+	for (channel=0; channel<MAX_CHANS; channel++) {
+
+	  my_audio_config.achan[channel].modem_type = MODEM_AFSK;
+
+	  my_audio_config.achan[channel].mark_freq = DEFAULT_MARK_FREQ;		
+	  my_audio_config.achan[channel].space_freq = DEFAULT_SPACE_FREQ;		
+	  my_audio_config.achan[channel].baud = DEFAULT_BAUD;	
+
+	  strlcpy (my_audio_config.achan[channel].profiles, "E", sizeof(my_audio_config.achan[channel].profiles));
+ 		
+	  my_audio_config.achan[channel].num_freq = 1;				
+	  my_audio_config.achan[channel].offset = 0;	
+
+	  my_audio_config.achan[channel].fix_bits = RETRY_NONE;	
+
+	  my_audio_config.achan[channel].sanity_test = SANITY_APRS;	
+	  //my_audio_config.achan[channel].sanity_test = SANITY_AX25;	
+	  //my_audio_config.achan[channel].sanity_test = SANITY_NONE;	
+
+	  my_audio_config.achan[channel].passall = 0;				
+	  //my_audio_config.achan[channel].passall = 1;				
+	}
+
+	while (1) {
+          //int this_option_optind = optind ? optind : 1;
+          int option_index = 0;
+          static struct option long_options[] = {
+            {"future1", 1, 0, 0},
+            {"future2", 0, 0, 0},
+            {"future3", 1, 0, 'c'},
+            {0, 0, 0, 0}
+          };
+
+	  /* ':' following option character means arg is required. */
+
+          c = getopt_long(argc, argv, "B:P:D:F:L:G:012",
+                        long_options, &option_index);
+          if (c == -1)
+            break;
+
+          switch (c) {
+
+            case 'B':				/* -B for data Bit rate */
+						/*    300 implies 1600/1800 AFSK. */
+						/*    1200 implies 1200/2200 AFSK. */
+						/*    9600 implies scrambled. */
+
+              my_audio_config.achan[0].baud = atoi(optarg);
+
+              dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud);
+
+              if (my_audio_config.achan[0].baud < 100 || my_audio_config.achan[0].baud > 10000) {
+		text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
+                exit (EXIT_FAILURE);
+              }
+	      if (my_audio_config.achan[0].baud < 600) {
+                my_audio_config.achan[0].modem_type = MODEM_AFSK;
+                my_audio_config.achan[0].mark_freq = 1600;
+                my_audio_config.achan[0].space_freq = 1800;
+	        strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
+	      }
+	      else if (my_audio_config.achan[0].baud > 2400) {
+                my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
+                my_audio_config.achan[0].mark_freq = 0;
+                my_audio_config.achan[0].space_freq = 0;
+	        strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles));	// avoid getting default later.
+                dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
+	      }
+	      else {
+                my_audio_config.achan[0].modem_type = MODEM_AFSK;
+                my_audio_config.achan[0].mark_freq = 1200;
+                my_audio_config.achan[0].space_freq = 2200;
+	      }
+              break;
+
+	    case 'P':				/* -P for modem profile. */
+
+	      dw_printf ("Demodulator profile set to \"%s\"\n", optarg);
+	      strlcpy (my_audio_config.achan[0].profiles, optarg, sizeof(my_audio_config.achan[0].profiles)); 
+	      break;	
+
+	    case 'D':				/* -D reduce sampling rate for lower CPU usage. */
+
+	      decimate = atoi(optarg);
+
+	      dw_printf ("Divide audio sample rate by %d\n", decimate);
+	      if (decimate < 1 || decimate > 8) {
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf ("Unreasonable value for -D.\n");
+		exit (1);
+	      }
+	      dw_printf ("Divide audio sample rate by %d\n", decimate);
+	      my_audio_config.achan[0].decimate = decimate;
+	      break;	
+
+	    case 'F':				/* -D set "fix bits" level. */
+
+	      my_audio_config.achan[0].fix_bits = atoi(optarg);
+
+	      if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) {
+		text_color_set(DW_COLOR_ERROR);
+		dw_printf ("Invalid Fix Bits level.\n");
+		exit (1);
+	      }
+	      break;	
+
+	    case 'L':				/* -L error if less than this number decoded. */
+
+	      error_if_less_than = atoi(optarg);
+	      break;	
+
+	    case 'G':				/* -G error if greater than this number decoded. */
+
+	      error_if_greater_than = atoi(optarg);
+	      break;
+
+	     case '0':				/* channel 0, left from stereo */
+
+	       decode_only = 0;
+	       break;
+
+	     case '1':				/* channel 1, right from stereo */
+
+	       decode_only = 1;
+	       break;
+
+	     case '2':				/* decode both from stereo */
+
+	       decode_only = 2;
+	       break;
+
+             case '?':
+
+              /* Unknown option message was already printed. */
+              usage ();
+              break;
+
+            default:
+
+              /* Should not be here. */
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("?? getopt returned character code 0%o ??\n", c);
+              usage ();
+	    }
+        }
+    
+	memcpy (&my_audio_config.achan[1], &my_audio_config.achan[0], sizeof(my_audio_config.achan[0]));
+
+
+	if (optind >= argc) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Specify .WAV file name on command line.\n");
+	  usage ();
+	}
+
+	fp = fopen(argv[optind], "rb");
+        if (fp == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf ("Couldn't open file for read: %s\n", argv[optind]);
+	  //perror ("more info?");
+          exit (1);
+        }
+
+	start_time = time(NULL);
+
+
+/*
+ * Read the file header.  
+ * Doesn't handle all possible cases but good enough for our purposes.
+ */
+
+        err= fread (&header, (size_t)12, (size_t)1, fp);
+	(void)(err);
+
+	if (strncmp(header.riff, "RIFF", 4) != 0 || strncmp(header.wave, "WAVE", 4) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf ("This is not a .WAV format file.\n");
+          exit (EXIT_FAILURE);
+	}
+
+	err = fread (&chunk, (size_t)8, (size_t)1, fp);
+
+	if (strncmp(chunk.id, "LIST", 4) == 0) {
+	  err = fseek (fp, (long)chunk.datasize, SEEK_CUR);
+	  err = fread (&chunk, (size_t)8, (size_t)1, fp);
+	}
+
+	if (strncmp(chunk.id, "fmt ", 4) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id);
+	  exit(1);
+	}
+	if (chunk.datasize != 16 && chunk.datasize != 18) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18.  Found %d.\n", chunk.datasize);
+	  exit(1);
+	}
+
+        err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp);	
+
+	err = fread (&wav_data, (size_t)8, (size_t)1, fp);
+
+	if (strncmp(wav_data.data, "data", 4) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data);
+	  exit(1);
+	}
+
+	// TODO: Should have proper message, not abort.
+	assert (format.nchannels == 1 || format.nchannels == 2);
+	assert (format.wbitspersample == 8 || format.wbitspersample == 16);
+
+        my_audio_config.adev[0].samples_per_sec = format.nsamplespersec;
+	my_audio_config.adev[0].bits_per_sample = format.wbitspersample;
+ 	my_audio_config.adev[0].num_channels = format.nchannels;
+
+	my_audio_config.achan[0].valid = 1;
+	if (format.nchannels == 2) my_audio_config.achan[1].valid = 1;
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("%d samples per second\n", my_audio_config.adev[0].samples_per_sec);
+	dw_printf ("%d bits per sample\n", my_audio_config.adev[0].bits_per_sample);
+	dw_printf ("%d audio channels\n", my_audio_config.adev[0].num_channels);
+	dw_printf ("%d audio bytes in file\n", (int)(wav_data.datasize));
+	dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits);
+
+		
+/*
+ * Initialize the AFSK demodulator and HDLC decoder.
+ */
+	multi_modem_init (&my_audio_config);
+
+
+	e_o_f = 0;
+	while ( ! e_o_f) 
+	{
+
+
+          int audio_sample;
+          int c;
+
+          for (c=0; c<my_audio_config.adev[0].num_channels; c++)
+          {
+
+            /* This reads either 1 or 2 bytes depending on */
+            /* bits per sample.  */
+
+            audio_sample = demod_get_sample (ACHAN2ADEV(c));
+
+            if (audio_sample >= 256 * 256)
+               e_o_f = 1;
+
+	    if (c == 0) sample_number++;
+
+            if (decode_only == 0 && c != 0) continue;
+            if (decode_only == 1 && c != 1) continue;
+
+            multi_modem_process_sample(c,audio_sample);
+          }
+
+                /* When a complete frame is accumulated, */
+                /* process_rec_frame, below, is called. */
+
+	}
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("\n\n");
+
+#if EXPERIMENT_G
+
+	for (j=0; j<MAX_SUBCHANS; j++) {
+	  float db = 20.0 * log10f(space_gain[j]);
+	  dw_printf ("%+.1f dB, %d\n", db, count[j]);
+	}
+#endif
+#if EXPERIMENT_H
+
+	for (j=0; j<MAX_SUBCHANS; j++) {
+	  dw_printf ("%d\n", count[j]);
+	}
+#endif
+	dw_printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time));
+
+	if (error_if_less_than != -1 && packets_decoded < error_if_less_than) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\n * * * TEST FAILED: number decoded is less than %d * * * \n", error_if_less_than);
+	  exit (1);
+	}
+	if (error_if_greater_than != -1 && packets_decoded > error_if_greater_than) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than);
+	  exit (1);
+	}
+
+	exit (0);
+}
+
+
+/*
+ * Simulate sample from the audio device.
+ */
+
+int audio_get (int a)
+{
+	int ch;
+
+	if (wav_data.datasize <= 0) {
+	  e_o_f = 1;
+	  return (-1);
+	}
+
+	ch = getc(fp);
+	wav_data.datasize--;
+
+	if (ch < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Unexpected end of file.\n");
+	  e_o_f = 1;
+	}
+
+	return (ch);
+}
+
+
+
+/*
+ * Rather than queuing up frames with bad FCS, 
+ * try to fix them immediately.
+ */
+
+void rdq_append (rrbb_t rrbb)
+{
+	int chan, subchan, slice;
+	alevel_t alevel;
+
+
+	chan = rrbb_get_chan(rrbb);
+	subchan = rrbb_get_subchan(rrbb);
+	slice = rrbb_get_slice(rrbb);
+	alevel = rrbb_get_audio_level(rrbb);
+
+	hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, slice, alevel);
+	rrbb_delete (rrbb);
+}
+
+
+/*
+ * This is called when we have a good frame.
+ */
+
+void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
+{	
+	
+	char stemp[500];
+	unsigned char *pinfo;
+	int info_len;
+	int h;
+	char heard[AX25_MAX_ADDR_LEN];
+	char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE];
+
+	packets_decoded++;
+
+	ax25_format_addrs (pp, stemp);
+
+	info_len = ax25_get_info (pp, &pinfo);
+
+	/* Print so we can see what is going on. */
+
+//TODO: quiet option - suppress packet printing, only the count at the end.
+
+#if 1
+	/* Display audio input level. */
+        /* Who are we hearing?   Original station or digipeater? */
+
+	if (ax25_get_num_addr(pp) == 0) {
+	  /* Not AX.25. No station to display below. */
+	  h = -1;
+	  strlcpy (heard, "", sizeof(heard));
+	}
+	else {
+	  h = ax25_get_heard(pp);
+          ax25_get_addr_with_ssid(pp, h, heard);
+	}
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("\n");
+	dw_printf("DECODED[%d] ", packets_decoded );
+
+	/* Insert time stamp relative to start of file. */
+
+	double sec = (double)sample_number / my_audio_config.adev[0].samples_per_sec;
+	int min = (int)(sec / 60.);
+	sec -= min * 60;
+
+	dw_printf ("%d:%07.4f ", min, sec);
+
+	if (h != AX25_SOURCE) {
+	  dw_printf ("Digipeater ");
+	}
+	ax25_alevel_to_text (alevel, alevel_text);
+
+	if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) {
+	  dw_printf ("%s audio level = %s     %s\n", heard, alevel_text, spectrum);
+	}
+	else {
+	  dw_printf ("%s audio level = %s   [%s]   %s\n", heard, alevel_text, retry_text[(int)retries], spectrum);
+	}
+
+#endif
+
+//#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
+//	int j;
+//
+//	for (j=0; j<MAX_SUBCHANS; j++) {
+//	  if (spectrum[j] == '|') {
+//	    count[j]++;
+//	  }
+//	}
+//#endif
+
+
+// Display non-APRS packets in a different color.
+
+// Display channel with subchannel/slice if applicable.
+
+	if (ax25_is_aprs(pp)) {
+	  text_color_set(DW_COLOR_REC);
+	}
+	else {
+	  text_color_set(DW_COLOR_DEBUG);
+	}
+
+	if (my_audio_config.achan[chan].num_subchan > 1 && my_audio_config.achan[chan].num_slicers == 1) {
+	  dw_printf ("[%d.%d] ", chan, subchan);
+	}
+	else if (my_audio_config.achan[chan].num_subchan == 1 && my_audio_config.achan[chan].num_slicers > 1) {
+	  dw_printf ("[%d.%d] ", chan, slice);
+	}
+	else if (my_audio_config.achan[chan].num_subchan > 1 && my_audio_config.achan[chan].num_slicers > 1) {
+	  dw_printf ("[%d.%d.%d] ", chan, subchan, slice);
+	}
+	else {
+	  dw_printf ("[%d] ", chan);
+	}
+
+	dw_printf ("%s", stemp);			/* stations followed by : */
+	ax25_safe_print ((char *)pinfo, info_len, 0);
+	dw_printf ("\n");
+
+	ax25_delete (pp);
+
+} /* end fake dlq_append */
+
+
+void ptt_set (int ot, int chan, int ptt_signal)
+{
+	return;
+}
+
+int get_input (int it, int chan)
+{
+	return -1;
+}
+
+static void usage (void) {
+
+	text_color_set(DW_COLOR_ERROR);
+
+	dw_printf ("\n");
+	dw_printf ("atest is a test application which decodes AX.25 frames from an audio\n");
+	dw_printf ("recording.  This provides an easy way to test Dire Wolf decoding\n");
+	dw_printf ("performance much quicker than normal real-time.   \n"); 
+	dw_printf ("\n");
+	dw_printf ("usage:\n");
+	dw_printf ("\n");
+	dw_printf ("        atest [ options ] wav-file-in\n");
+	dw_printf ("\n");
+	dw_printf ("        -B n   Bits/second  for data.  Proper modem automatically selected for speed.\n");
+	dw_printf ("               300 baud uses 1600/1800 Hz AFSK.\n");
+	dw_printf ("               1200 (default) baud uses 1200/2200 Hz AFSK.\n");
+	dw_printf ("               9600 baud uses K9NG/G2RUH standard.\n");
+	dw_printf ("\n");
+	dw_printf ("        -D n   Divide audio sample rate by n.\n");
+	dw_printf ("\n");
+	dw_printf ("        -F n   Amount of effort to try fixing frames with an invalid CRC.  \n");
+	dw_printf ("               0 (default) = consider only correct frames.  \n");
+	dw_printf ("               1 = Try to fix only a single bit.  \n");
+	dw_printf ("               more = Try modifying more bits to get a good CRC.\n");
+	dw_printf ("\n");
+	dw_printf ("        -P m   Select  the  demodulator  type such as A, B, C, D (default for 300 baud),\n");
+	dw_printf ("               E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+.\n");
+	dw_printf ("\n");
+	dw_printf ("        -0     Use channel 0 (left) of stereo audio (default).\n");
+	dw_printf ("        -1     use channel 1 (right) of stereo audio.\n");
+	dw_printf ("        -1     decode both channels of stereo audio.\n");
+	dw_printf ("\n");
+	dw_printf ("        wav-file-in is a WAV format audio file.\n");
+	dw_printf ("\n");
+	dw_printf ("Examples:\n");
+	dw_printf ("\n");
+	dw_printf ("        gen_packets -o test1.wav\n");
+	dw_printf ("        atest test1.wav\n");
+	dw_printf ("\n");
+	dw_printf ("        gen_packets -B 300 -o test3.wav\n");
+	dw_printf ("        atest -B 300 test3.wav\n");
+	dw_printf ("\n");
+	dw_printf ("        gen_packets -B 9600 -o test9.wav\n");
+	dw_printf ("        atest -B 9600 test9.wav\n");
+	dw_printf ("\n");
+	dw_printf ("              This generates and decodes 3 test files with 1200, 300, and 9600\n");
+	dw_printf ("              bits per second.\n");
+	dw_printf ("\n");
+	dw_printf ("        atest 02_Track_2.wav\n");
+	dw_printf ("        atest -P C+ 02_Track_2.wav\n");
+	dw_printf ("        atest -F 1 02_Track_2.wav\n");
+	dw_printf ("        atest -P C+ -F 1 02_Track_2.wav\n");
+	dw_printf ("\n");
+	dw_printf ("              Try  different combinations of options to find the best decoding\n");
+	dw_printf ("              performance.\n");
+
+	exit (1);
+}
+
+
+
+/* end atest.c */
diff --git a/audio.c b/audio.c
index 866a055..84aa854 100644
--- a/audio.c
+++ b/audio.c
@@ -1,1418 +1,1405 @@
-
-// Remove next line to eliminate annoying/useful (depending on who you ask) debug messages every 100 seconds.
-#define STATISTICS 1
-
-
-// 
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      audio.c
- *
- * Purpose:   	Interface to audio device commonly called a "sound card" for
- *		historical reasons.	
- *
- *		This version is for Linux and Cygwin.	
- *
- *		Two different types of sound interfaces are supported:
- *
- *		* OSS - For Cygwin or Linux versions with /dev/dsp.
- *
- *		* ALSA - For Linux versions without /dev/dsp.
- *			In this case, define preprocessor symbol USE_ALSA.
- *
- * References:	Some tips on on using Linux sound devices.
- *
- *		http://www.oreilly.de/catalog/multilinux/excerpt/ch14-05.htm
- *		http://cygwin.com/ml/cygwin-patches/2004-q1/msg00116/devdsp.c
- *		http://manuals.opensound.com/developer/fulldup.c.html
- *
- *		"Introduction to Sound Programming with ALSA"
- *		http://www.linuxjournal.com/article/6735?page=0,1
- *
- *		http://www.alsa-project.org/main/index.php/Asoundrc
- *
- * Credits:	Release 1.0: Fabrice FAURE contributed code for the SDR UDP interface.
- *
- *		Discussion here:  http://gqrx.dk/doc/streaming-audio-over-udp
- *
- *		Release 1.1:  Gabor Berczi provided fixes for the OSS code
- *		which had fallen into decay.
- *		
- * Major Revisions: 	
- *
- *		1.2 - Add ability to use more than one audio device. 
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <assert.h>
-
-#include <sys/socket.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-
-#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"
-#include "textcolor.h"
-#include "dtime_now.h"
-#include "demod.h"		/* for alevel_t & demod_get_audio_level() */
-
-
-/* Audio configuration. */
-
-static struct audio_s          *save_audio_config_p;
-
-
-/* Current state for each of the audio devices. */
-
-static struct adev_s {
-
-#if USE_ALSA
-	snd_pcm_t *audio_in_handle;
-	snd_pcm_t *audio_out_handle;
-
-	int bytes_per_frame;		/* number of bytes for a sample from all channels. */
-					/* e.g. 4 for stereo 16 bit. */
-
-#else
-	oss_audio_device_fd;		/* Single device, both directions. */
-
-#endif
-
-	int inbuf_size_in_bytes;	/* number of bytes allocated */
-	unsigned char *inbuf_ptr;
-	int inbuf_len;			/* number byte of actual data available. */
-	int inbuf_next;			/* index of next to remove. */
-
-	int outbuf_size_in_bytes;
-	unsigned char *outbuf_ptr;
-	int outbuf_len;
-
-	enum audio_in_type_e g_audio_in_type;
-
-	int udp_sock;			/* UDP socket for receiving data */
-
-} adev[MAX_ADEVS];
-
-
-// Originally 40.  Version 1.2, try 10 for lower latency.
-
-#define ONE_BUF_TIME 10
-
-
-#if USE_ALSA
-static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *name, char *dir);
-//static void alsa_select_device (char *pick_dev, int direction, char *result);
-#else
-static int set_oss_params (int fd, struct audio_s *pa);
-#endif
-
-
-#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
-
-static int calcbufsize(int rate, int chans, int bits)
-{
-	int size1 = (rate * chans * bits  / 8 * ONE_BUF_TIME) / 1000;
-	int size2 = roundup1k(size1);
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
-		rate, chans, bits, size1, size2);
-#endif
-	return (size2);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_open
- *
- * Purpose:     Open the digital audio device.
- *		For "OSS", the device name is typically "/dev/dsp".
- *		For "ALSA", it's a lot more complicated.  See User Guide.
- *
- *		New in version 1.0, we recognize "udp:" optionally
- *		followed by a port number.
- *
- * Inputs:      pa		- Address of structure of type audio_s.
- *				
- *				Using a structure, rather than separate arguments
- *				seemed to make sense because we often pass around
- *				the same set of parameters various places.
- *
- *				The fields that we care about are:
- *					num_channels
- *					samples_per_sec
- *					bits_per_sample
- *				If zero, reasonable defaults will be provided.
- *
- *				The device names are in adevice_in and adevice_out.
- *				 - For "OSS", the device name is typically "/dev/dsp".
- *				 - For "ALSA", the device names are hw:c,d
- *				   where c is the "card" (for historical purposes)
- *				   and d is the "device" within the "card."
- *
- *
- * Outputs:	pa		- The ACTUAL values are returned here.
- *
- *				These might not be exactly the same as what was requested.
- *					
- *				Example: ask for stereo, 16 bits, 22050 per second.
- *				An ordinary desktop/laptop PC should be able to handle this.
- *				However, some other sort of smaller device might be
- *				more restrictive in its capabilities.
- *				It might say, the best I can do is mono, 8 bit, 8000/sec.
- *
- *				The sofware modem must use this ACTUAL information
- *				that the device is supplying, that could be different
- *				than what the user specified.
- *              
- * Returns:     0 for success, -1 for failure.
- *	
- *
- *----------------------------------------------------------------*/
-
-int audio_open (struct audio_s *pa)
-{
-	int err;
-	int chan;
-	int a;
-	char audio_in_name[30];
-	char audio_out_name[30];
-
-
-	save_audio_config_p = pa;
-
-	memset (adev, 0, sizeof(adev));
-
-	for (a=0; a<MAX_ADEVS; a++) {
-#ifndef USE_ALSA
-	  adev[a].oss_audio_device_fd = -1;
-#endif
-	  adev[a].udp_sock = -1;
-	}
-
-
-/*
- * Fill in defaults for any missing values.
- */
-
-	for (a=0; a<MAX_ADEVS; a++) {
-
-	  if (pa->adev[a].num_channels == 0)
-	    pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS;
-
-	  if (pa->adev[a].samples_per_sec == 0)
-	    pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
-
-	  if (pa->adev[a].bits_per_sample == 0)
-	    pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
-
-	  for (chan=0; chan<MAX_CHANS; chan++) {
-	    if (pa->achan[chan].mark_freq == 0)
-	      pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ;
-
-	    if (pa->achan[chan].space_freq == 0)
-	      pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ;
-
-	    if (pa->achan[chan].baud == 0)
-	      pa->achan[chan].baud = DEFAULT_BAUD;
-
-	    if (pa->achan[chan].num_subchan == 0)
-	      pa->achan[chan].num_subchan = 1;
-	  }
-	}
-
-/*
- * Open audio device(s).
- */
-
-    	for (a=0; a<MAX_ADEVS; a++) {
-         if (pa->adev[a].defined) {
-
-	    adev[a].inbuf_size_in_bytes = 0;
-	    adev[a].inbuf_ptr = NULL;
-	    adev[a].inbuf_len = 0;
-	    adev[a].inbuf_next = 0;
-
-	    adev[a].outbuf_size_in_bytes = 0;
-	    adev[a].outbuf_ptr = NULL;
-	    adev[a].outbuf_len = 0;
-
-/*
- * Determine the type of audio input.
- */
-
-	    adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; 	
-
-	    if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
-	      adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN;
-	      /* Change "-" to stdin for readability. */
-	      strcpy (pa->adev[a].adevice_in, "stdin");
-	    }
-	    if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
-	      adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
-	      /* Supply default port if none specified. */
-	      if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
-	        strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
-	        sprintf (pa->adev[a].adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT);
-	      }
-	    } 
-
-/* Let user know what is going on. */
-
-	    /* If not specified, the device names should be "default". */
-
-	    strcpy (audio_in_name, pa->adev[a].adevice_in);
-	    strcpy (audio_out_name, pa->adev[a].adevice_out);
-
-	    char ctemp[40];
-
-	    if (pa->adev[a].num_channels == 2) {
-	      sprintf (ctemp, " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
-	    }
-	    else {
-	      sprintf (ctemp, " (channel %d)", ADEVFIRSTCHAN(a));
-	    }
-
-            text_color_set(DW_COLOR_INFO);
-
-	    if (strcmp(audio_in_name,audio_out_name) == 0) {
-              dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp);
-	    }
-	    else {
-              dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp);
-              dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp);
-	    }
-
-/*
- * Now attempt actual opens.
- */
-
-/*
- * Input device.
- */
-
-	    switch (adev[a].g_audio_in_type) {
-
-/*
- * Soundcard - ALSA.
- */
-	      case AUDIO_IN_TYPE_SOUNDCARD:
-#if USE_ALSA
-	        err = snd_pcm_open (&(adev[a].audio_in_handle), audio_in_name, SND_PCM_STREAM_CAPTURE, 0);
-	        if (err < 0) {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Could not open audio device %s for input\n%s\n", 
-	  		audio_in_name, snd_strerror(err));
-	          return (-1);
-	        }
-
-	        adev[a].inbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_in_handle, pa, audio_in_name, "input");
-	    
-#else // OSS
-	        oss_audio_device_fd = open (pa->adev[a].adevice_in, O_RDWR);
-
-	        if (oss_audio_device_fd < 0) {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("%s:\n", pa->adev[a].adevice_in);
-//	          sprintf (message, "Could not open audio device %s", pa->adev[a].adevice_in);
-//	          perror (message);
-	          return (-1);
-	        }
-
-	        adev[a].outbuf_size_in_bytes = adev[a].inbuf_size_in_bytes = set_oss_params (oss_audio_device_fd, pa);
-
-	        if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
-	          return (-1);
-	        }
-#endif
-	        break;
-/*
- * UDP.
- */
-	      case AUDIO_IN_TYPE_SDR_UDP:
-
-	        //Create socket and bind socket
-	    
-	        {
-	          struct sockaddr_in si_me;
-	          int slen=sizeof(si_me);
-	          int data_size = 0;
-
-	          //Create UDP Socket
-	          if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Couldn't create socket, errno %d\n", errno);
-	            return -1;
-	          }
-
-	          memset((char *) &si_me, 0, sizeof(si_me));
-	          si_me.sin_family = AF_INET;   
-	          si_me.sin_port = htons((short)atoi(audio_in_name+4));
-	          si_me.sin_addr.s_addr = htonl(INADDR_ANY);
-
-	          //Bind to the socket
-	          if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Couldn't bind socket, errno %d\n", errno);
-	            return -1;
-	          }
-	        }
-	        adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; 
-	
-	        break;
-
-/* 
- * stdin.
- */
-   	      case AUDIO_IN_TYPE_STDIN:
-
-	        /* Do we need to adjust any properties of stdin? */
-
-	        adev[a].inbuf_size_in_bytes = 1024; 
-	    
-	        break;
-
-	      default:
-
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Internal error, invalid audio_in_type\n");
-	        return (-1);
-  	    }
-
-/*
- * Output device.  Only "soundcard" is supported at this time. 
- */
-
-#if USE_ALSA
-	    err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0);
-
-	    if (err < 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Could not open audio device %s for output\n%s\n", 
-			audio_out_name, snd_strerror(err));
-	      return (-1);
-	    }
-
-	    adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output");
-
-	    if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
-	      return (-1);
-	    }
-
-#endif
-
-/*
- * Finally allocate buffer for each direction.
- */
-	    adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes);
-	    assert (adev[a].inbuf_ptr  != NULL);
-	    adev[a].inbuf_len = 0;
-	    adev[a].inbuf_next = 0;
-
-	    adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes);
-	    assert (adev[a].outbuf_ptr  != NULL);
-	    adev[a].outbuf_len = 0;
-
-          } /* end of audio device defined */
-
-        } /* end of for each audio device */
-	
-	return (0);
-
-} /* end audio_open */
-
-
-
-
-#if USE_ALSA
-
-/*
- * Set parameters for sound card.
- *
- * See  ??  for details. 
- */
-/* 
- * Terminology:
- *   Sample	- for one channel.		e.g. 2 bytes for 16 bit.
- *   Frame	- one sample for all channels.  e.g. 4 bytes for 16 bit stereo
- *   Period	- size of one transfer.
- */
-
-static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *devname, char *inout)
-{
-	
-	snd_pcm_hw_params_t *hw_params;
-	snd_pcm_uframes_t fpp; 			/* Frames per period. */
-
-	unsigned int val;
-
-	int dir;
-	int err;
-
-	int buf_size_in_bytes;			/* result, number of bytes per transfer. */
-
-
-	err = snd_pcm_hw_params_malloc (&hw_params);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not alloc hw param structure.\n%s\n", 
-			snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	err = snd_pcm_hw_params_any (handle, hw_params);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not init hw param structure.\n%s\n", 
-			snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	/* Interleaved data: L, R, L, R, ... */
-
-	err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
-
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not set interleaved mode.\n%s\n", 
-			snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	/* Signed 16 bit little endian or unsigned 8 bit. */
-
-
-	err = snd_pcm_hw_params_set_format (handle, hw_params,
- 		pa->adev[a].bits_per_sample == 8 ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16_LE);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not set bits per sample.\n%s\n", 
-			snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	/* Number of audio channels. */
-
-
-	err = snd_pcm_hw_params_set_channels (handle, hw_params, pa->adev[a].num_channels);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not set number of audio channels.\n%s\n", 
-			snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	/* Audio sample rate. */
-
-
-	val = pa->adev[a].samples_per_sec;
-
-	dir = 0;
-
-
-	err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &val, &dir);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not set audio sample rate.\n%s\n", 
-			snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	if (val != pa->adev[a].samples_per_sec) {
-
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Asked for %d samples/sec but got %d.\n",
-
-				pa->adev[a].samples_per_sec, val);
-	  dw_printf ("for %s %s.\n", devname, inout);
-
-	  pa->adev[a].samples_per_sec = val;
-
-	}
-	
-	/* Original: */
-	/* Guessed around 20 reads/sec might be good. */
-	/* Period too long = too much latency. */
-	/* Period too short = more overhead of many small transfers. */
-
-	/* fpp = pa->adev[a].samples_per_sec / 20; */
-
-	/* The suggested period size was 2205 frames.  */
-	/* I thought the later "...set_period_size_near" might adjust it to be */
-	/* some more optimal nearby value based hardware buffer sizes but */
-	/* that didn't happen.   We ended up with a buffer size of 4410 bytes. */
-
-	/* In version 1.2, let's take a different approach. */
-	/* Reduce the latency and round up to a multiple of 1 Kbyte. */
-	
-	/* For the typical case of 44100 sample rate, 1 channel, 16 bits, we calculate */
-	/* a buffer size of 882 and round it up to 1k.  This results in 512 frames per period. */
-	/* A period comes out to be about 80 periods per second or about 12.5 mSec each. */
-
-	buf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
-
-#if __arm__
-	/* Ugly hack for RPi. */
-	/* Reducing buffer size is fine for input but not so good for output. */
-	
-	if (*inout == 'o') {
-	  buf_size_in_bytes = buf_size_in_bytes * 4;
-	}
-#endif
-
-	fpp = buf_size_in_bytes / (pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-
-	dw_printf ("suggest period size of %d frames\n", (int)fpp);
-#endif
-	dir = 0;
-	err = snd_pcm_hw_params_set_period_size_near (handle, hw_params, &fpp, &dir);
-
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not set period size\n%s\n", snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	err = snd_pcm_hw_params (handle, hw_params);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not set hw params\n%s\n", snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	/* Driver might not like our suggested period size */
-	/* and might have another idea. */
-
-	err = snd_pcm_hw_params_get_period_size (hw_params, &fpp, NULL);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not get audio period size.\n%s\n", snd_strerror(err));
-	  dw_printf ("for %s %s.\n", devname, inout);
-	  return (-1);
-	}
-
-	snd_pcm_hw_params_free (hw_params);
-	
-	/* A "frame" is one sample for all channels. */
-
-	/* The read and write use units of frames, not bytes. */
-
-	adev[a].bytes_per_frame = snd_pcm_frames_to_bytes (handle, 1);
-
-	assert (adev[a].bytes_per_frame == pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8);
-
-	buf_size_in_bytes = fpp * adev[a].bytes_per_frame;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio buffer size = %d (bytes per frame) x %d (frames per period) = %d \n", adev[a].bytes_per_frame, (int)fpp, buf_size_in_bytes);
-#endif
-
-	return (buf_size_in_bytes);
-
-
-} /* end alsa_set_params */
-
-
-#else
-
-
-/*
- * Set parameters for sound card.  (OSS only)
- *
- * See  /usr/include/sys/soundcard.h  for details. 
- */
-
-static int set_oss_params (int fd, struct audio_s *pa) 
-{
-	int err;
-	int devcaps;
-	int asked_for;
-	char message[100];
-	int ossbuf_size_in_bytes;
-
-
-	err = ioctl (fd, SNDCTL_DSP_CHANNELS, &(pa->adev[a].num_channels));
-   	if (err == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-    	  perror("Not able to set audio device number of channels");
- 	  return (-1);
-	}
-
-        asked_for = pa->adev[a].samples_per_sec;
-
-	err = ioctl (fd, SNDCTL_DSP_SPEED, &(pa->adev[a].samples_per_sec));
-   	if (err == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-    	  perror("Not able to set audio device sample rate");
- 	  return (-1);
-	}
-
-	if (pa->adev[a].samples_per_sec != asked_for) {
-	  text_color_set(DW_COLOR_INFO);
-          dw_printf ("Asked for %d samples/sec but actually using %d.\n",
-		asked_for, pa->adev[a].samples_per_sec);
-	}
-
-	/* This is actually a bit mask but it happens that */
-	/* 0x8 is unsigned 8 bit samples and */
-	/* 0x10 is signed 16 bit little endian. */
-
-	err = ioctl (fd, SNDCTL_DSP_SETFMT, &(pa->adev[a].bits_per_sample));
-   	if (err == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-    	  perror("Not able to set audio device sample size");
- 	  return (-1);
-	}
-
-/*
- * Determine capabilities.
- */
-	err = ioctl (fd, SNDCTL_DSP_GETCAPS, &devcaps);
-   	if (err == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-    	  perror("Not able to get audio device capabilities");
- 	  // Is this fatal? //	return (-1);
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_open(): devcaps = %08x\n", devcaps);	
-	if (devcaps & DSP_CAP_DUPLEX) dw_printf ("Full duplex record/playback.\n");
-	if (devcaps & DSP_CAP_BATCH) dw_printf ("Device has some kind of internal buffers which may cause delays.\n");
-	if (devcaps & ~ (DSP_CAP_DUPLEX | DSP_CAP_BATCH)) dw_printf ("Others...\n");
-#endif
-
-	if (!(devcaps & DSP_CAP_DUPLEX)) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Audio device does not support full duplex\n");
-    	  // Do we care? //	return (-1);
-	}
-
-	err = ioctl (fd, SNDCTL_DSP_SETDUPLEX, NULL);
-   	if (err == -1) {
-	  // text_color_set(DW_COLOR_ERROR);
-    	  // perror("Not able to set audio full duplex mode");
- 	  // Unfortunate but not a disaster.
-	}
-
-/*
- * Get preferred block size.
- * Presumably this will provide the most efficient transfer.
- *
- * In my particular situation, this turned out to be
- *  	2816 for 11025 Hz 16 bit mono
- *	5568 for 11025 Hz 16 bit stereo
- *     11072 for 44100 Hz 16 bit mono
- *
- * This was long ago under different conditions.
- * Should study this again some day.
- */
- * Your milage may vary.
- */
-	err = ioctl (fd, SNDCTL_DSP_GETBLKSIZE, &ossbuf_size_in_bytes);
-   	if (err == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-    	  perror("Not able to get audio block size");
-	  ossbuf_size_in_bytes = 2048;	/* pick something reasonable */
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_open(): suggestd block size is %d\n", ossbuf_size_in_bytes);	
-#endif
-
-/*
- * That's 1/8 of a second which seems rather long if we want to
- * respond quickly.
- */
-
-	ossbuf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_open(): using block size of %d\n", ossbuf_size_in_bytes);	
-#endif
-
-	assert (ossbuf_size_in_bytes >= 256 && ossbuf_size_in_bytes <= 32768);
-
-
-	return (ossbuf_size_in_bytes);
-
-} /* end set_oss_params */
-
-
-#endif
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_get
- *
- * Purpose:     Get one byte from the audio device.
- *
- * Inputs:	a	- Our number for audio device.
- *
- * Returns:     0 - 255 for a valid sample.
- *              -1 for any type of error.
- *
- * Description:	The caller must deal with the details of mono/stereo
- *		and number of bytes per sample.
- *
- *		This will wait if no data is currently available.
- *
- *----------------------------------------------------------------*/
-
-// Use hot attribute for all functions called for every audio sample.
-
-__attribute__((hot))
-int audio_get (int a)
-{
-	int n;
-	int retries = 0;
-
-#if STATISTICS
-	/* Gather numbers for read from audio device. */
-
-#define duration 100			/* report every 100 seconds. */
-	static time_t last_time[MAX_ADEVS];
-	time_t this_time[MAX_ADEVS];
-	static int sample_count[MAX_ADEVS];
-	static int error_count[MAX_ADEVS];
-#endif
-
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-
-	dw_printf ("audio_get():\n");
-
-#endif
-
-	assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768);
-
-	  
-
-	switch (adev[a].g_audio_in_type) {
-
-/*
- * Soundcard - ALSA 
- */
-	  case AUDIO_IN_TYPE_SOUNDCARD:
-
-
-#if USE_ALSA
-
-
-	    while (adev[a].inbuf_next >= adev[a].inbuf_len) {
-
-	      assert (adev[a].audio_in_handle != NULL);
-#if DEBUGx
-	      text_color_set(DW_COLOR_DEBUG);
-	      dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);	
-#endif
-	      n = snd_pcm_readi (adev[a].audio_in_handle, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
-
-#if DEBUGx	  
-	      text_color_set(DW_COLOR_DEBUG);
-	      dw_printf ("audio_get(): readi asked for %d and got %d frames\n",
-		adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n);	
-#endif
-
-#if STATISTICS
-
-// TODO1.2: add audio level information to windows version.  Common function?
-// TODO1.2: add quiet option to suppress this.
-
-/*
- * Print information about the sample rate as a debugging aid.
- * I've never seen an issue with Windows or x86 Linux but the Raspberry Pi
- * has a very troublesome audio input system where many samples got lost.
- * Occasional lines like this would immediately identify the issue.
- *
- *	Past 100 seconds, 4409856 audio samples processed, 0 errors.
- *
- * That's a little hard to read.  Maybe we'd be better off with an average
- * and fewer digits like this:    44.1 k
- *
- * While we are at it we can also print the current audio level(s) providing 
- * more clues if nothing is being decoded.
- */
-
-	      if (last_time[a] == 0) {
-	        last_time[a] = time(NULL);
-	        sample_count[a] = 0;
-	        error_count[a] = 0;
-	      }
-	      else {
-	        if (n > 0) {
-	           sample_count[a] += n;
-	        }
-	        else {
-	           error_count[a]++;
-	        }
-	        this_time[a] = time(NULL);
-	        if (this_time[a] >= last_time[a] + duration) {
-
-#if 1	/* Try this for version 1.2 and see how people react. */
-
-		  float ave_rate = (sample_count[a] / 1000.0) / duration;
-
-	          text_color_set(DW_COLOR_DEBUG);
-
-	          if (save_audio_config_p->adev[a].num_channels > 1) {
-		    int ch0 = ADEVFIRSTCHAN(a);
-		    alevel_t alevel0 = demod_get_audio_level(a,ch0);
-		    int ch1 = ADEVFIRSTCHAN(a) + 1;
-		    alevel_t alevel1 = demod_get_audio_level(a,ch1);
-
-	            dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio levels CH%d %d, CH%d %d\n\n", 
-			a, ave_rate, error_count[a], ch0, alevel0.rec, ch1, alevel1.rec);
-	          }
-	          else {
-		    int ch0 = ADEVFIRSTCHAN(a);
-		    alevel_t alevel0 = demod_get_audio_level(a,ch0);
-
-	            dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio level CH%d %d\n\n", 
-			a, ave_rate, error_count[a], ch0, alevel0.rec);
-	          }
-
-#else
-	          text_color_set(DW_COLOR_DEBUG);
-	          dw_printf ("\nADEVICE%d: Past %d seconds, %d audio samples processed, %d errors.\n\n", 
-			a, duration, sample_count[a], error_count[a]);
-#endif 
-	          last_time[a] = this_time[a];
-	          sample_count[a] = 0;
-	          error_count[a] = 0;
-	        }      
-	      }
-#endif
- 
-	      if (n > 0) {
-
-	        /* Success */
-
-	        adev[a].inbuf_len = n * adev[a].bytes_per_frame;		/* convert to number of bytes */
-	        adev[a].inbuf_next = 0;
-	      }
-	      else if (n == 0) {
-
-	        /* Didn't expect this, but it's not a problem. */
-	        /* Wait a little while and try again. */
-
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Audio input got zero bytes: %s\n", snd_strerror(n));
-	        SLEEP_MS(10);
-
-	        adev[a].inbuf_len = 0;
-	        adev[a].inbuf_next = 0;
-	      }
-	      else {
-	        /* Error */
-	        // TODO: Needs more study and testing. 
-
-		// TODO: print n.  should snd_strerror use n or errno?
-		// Audio input device error: Unknown error
-
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Audio input device %d error: %s\n", a, snd_strerror(n));
-
-	        /* Try to recover a few times and eventually give up. */
-	        if (++retries > 10) {
-	          adev[a].inbuf_len = 0;
-	          adev[a].inbuf_next = 0;
-	          return (-1);
-	        }
-
-	        if (n == -EPIPE) {
-
-	          /* EPIPE means overrun */
-
-	          snd_pcm_recover (adev[a].audio_in_handle, n, 1);
-
-	        } 
-	        else {
-	          /* Could be some temporary condition. */
-	          /* Wait a little then try again. */
-	          /* Sometimes I get "Resource temporarily available" */
-	          /* when the Update Manager decides to run. */
-
-	          SLEEP_MS (250);
-	          snd_pcm_recover (adev[a].audio_in_handle, n, 1);
-	        }
-	      }
-	    }
-
-
-#else	/* end ALSA, begin OSS */
-
-	    /* Fixed in 1.2.  This was formerly outside of the switch */
-	    /* so the OSS version did not process stdin or UDP. */
-
-	    while (adev[a]..g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD && adev[a].inbuf_next >= adev[a].inbuf_len) {
-	      assert (oss_audio_device_fd > 0);
-	      n = read (oss_audio_device_fd, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes);
-	      //text_color_set(DW_COLOR_DEBUG);
-	      // dw_printf ("audio_get(): read %d returns %d\n", adev[a].inbuf_size_in_bytes, n);	
-	      if (n < 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        perror("Can't read from audio device");
-	        adev[a].inbuf_len = 0;
-	        adev[a].inbuf_next = 0;
-	        return (-1);
-	      }
-	      adev[a].inbuf_len = n;
-	      adev[a].inbuf_next = 0;
-	    }
-
-#endif	/* USE_ALSA */
-
-
-	    break;
-
-/* 
- * UDP.
- */
-
-	  case AUDIO_IN_TYPE_SDR_UDP:
-
-	    while (adev[a].inbuf_next >= adev[a].inbuf_len) {
-	      int ch, res,i;
-
-              assert (adev[a].udp_sock > 0);
-	      res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
-	      if (res < 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Can't read from udp socket, res=%d", res);
-	        adev[a].inbuf_len = 0;
-	        adev[a].inbuf_next = 0;
-	        return (-1);
-	      }
-	    
-	      adev[a].inbuf_len = res;
-	      adev[a].inbuf_next = 0;
-	    }
-	    break;
-
-/*
- * stdin.
- */
-	  case AUDIO_IN_TYPE_STDIN:
-
-	    while (adev[a].inbuf_next >= adev[a].inbuf_len) {
-	      int ch, res,i;
-
-	      res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
-	      if (res <= 0) {
-	        text_color_set(DW_COLOR_INFO);
-	        dw_printf ("\nEnd of file on stdin.  Exiting.\n");
-	        exit (0);
-	      }
-	    
-	      adev[a].inbuf_len = res;
-	      adev[a].inbuf_next = 0;
-	    }
-
-	    break;
-	}
-
-
-	if (adev[a].inbuf_next < adev[a].inbuf_len)
-	  n = adev[a].inbuf_ptr[adev[a].inbuf_next++];
-	//No data to read, avoid reading outside buffer
-	else
-	  n = 0;
-
-#if DEBUGx
-
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_get(): returns %d\n", n);
-
-#endif
- 
-
-	return (n);
-
-} /* end audio_get */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_put
- *
- * Purpose:     Send one byte to the audio device.
- *
- * Inputs:	a
- *
- *		c	- One byte in range of 0 - 255.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- * Description:	The caller must deal with the details of mono/stereo
- *		and number of bytes per sample.
- *
- * See Also:	audio_flush
- *		audio_wait
- *
- *----------------------------------------------------------------*/
-
-int audio_put (int a, int c)
-{
-	/* Should never be full at this point. */
-	assert (adev[a].outbuf_len < adev[a].outbuf_size_in_bytes);
-
-	adev[a].outbuf_ptr[adev[a].outbuf_len++] = c;
-
-	if (adev[a].outbuf_len == adev[a].outbuf_size_in_bytes) {
-	  return (audio_flush(a));
-	}
-
-	return (0);
-
-} /* end audio_put */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_flush
- *
- * Purpose:     Push out any partially filled output buffer.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- * See Also:	audio_flush
- *		audio_wait
- *
- *----------------------------------------------------------------*/
-
-int audio_flush (int a)
-{
-#if USE_ALSA
-	int k;
-	char *psound;
-	int retries = 10;
-	snd_pcm_status_t *status;
-
-	assert (adev[a].audio_out_handle != NULL);
-
-
-/*
- * Trying to set the automatic start threshold didn't have the desired
- * effect.  After the first transmitted packet, they are saved up
- * for a few minutes and then all come out together.
- *
- * "Prepare" it if not already in the running state.
- * We stop it at the end of each transmitted packet.
- */
-
-
-	snd_pcm_status_alloca(&status);
-
-	k = snd_pcm_status (adev[a].audio_out_handle, status);
-	if (k != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k));
-	}
-
-	if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) {
-
-	  //text_color_set(DW_COLOR_DEBUG);
-	  //dw_printf ("Audio output state = %d.  Try to start.\n", k);
-
-	  k = snd_pcm_prepare (adev[a].audio_out_handle);
-
-	  if (k != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Audio output start error.\n%s\n", snd_strerror(k));
-	  }
-	}
-
-
-	psound = adev[a].outbuf_ptr;
-
-	while (retries-- > 0) {
-
-	  k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame);	
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n",
-				adev[a].outbuf_len / adev[a].bytes_per_frame, k);
-	  fflush (stdout);	
-#endif
-	  if (k == -EPIPE) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Audio output data underrun.\n");
-
-	    /* No problemo.  Recover and go around again. */
-
-	    snd_pcm_recover (adev[a].audio_out_handle, k, 1);
-	  }
- 	  else if (k < 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Audio write error: %s\n", snd_strerror(k));
-
-	    /* Some other error condition. */
-	    /* Try again. What do we have to lose? */
-
-	    snd_pcm_recover (adev[a].audio_out_handle, k, 1);
-	  }
- 	  else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Audio write took %d frames rather than %d.\n",
- 			k, adev[a].outbuf_len / adev[a].bytes_per_frame);
-	
-	    /* Go around again with the rest of it. */
-
-	    psound += k * adev[a].bytes_per_frame;
-	    adev[a].outbuf_len -= k * adev[a].bytes_per_frame;
-	  }
-	  else {
-	    /* Success! */
-	    adev[a].outbuf_len = 0;
-	    return (0);
-	  }
-	}
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Audio write error retry count exceeded.\n");
-
-	adev[a].outbuf_len = 0;
-	return (-1);
-
-#else		/* OSS */
-
-	int k;
-	unsigned char *ptr;	
-	int len;
-
-	ptr = adev[a].outbuf_ptr;
-	len = adev[a].outbuf_len;
-
-	while (len > 0) {
-	  assert (oss_audio_device_fd > 0);
-	  k = write (oss_audio_device_fd, ptr, len);	
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("audio_flush(): write %d returns %d\n", len, k);
-	  fflush (stdout);	
-#endif
-	  if (k < 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    perror("Can't write to audio device");
-	    adev[a].outbuf_len = 0;
-	    return (-1);
-	  }
-	  if (k < len) {
-	    /* presumably full but didn't block. */
-	    usleep (10000);
-	  }
-	  ptr += k;
-	  len -= k;
-	}
-
-	adev[a].outbuf_len = 0;
-	return (0);
-#endif
-
-} /* end audio_flush */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_wait
- *
- * Purpose:	Finish up audio output before turning PTT off.
- *
- * Inputs:	a		- Index for audio device (not channel!)
- *
- * Returns:     None.
- *
- * Description:	Flush out any partially filled audio output buffer.
- *		Wait until all the queued up audio out has been played.
- *		Take any other necessary actions to stop audio output.
- *
- * In an ideal world:
- * 
- *		We would like to ask the hardware when all the queued
- *		up sound has actually come out the speaker.
- *
- * In reality:
- *
- * 		This has been found to be less than reliable in practice.
- *
- *		Caller does the following:
- *
- *		(1) Make note of when PTT is turned on.
- *		(2) Calculate how long it will take to transmit the 
- *			frame including TXDELAY, frame (including 
- *			"flags", data, FCS and bit stuffing), and TXTAIL.
- *		(3) Call this function, which might or might not wait long enough.
- *		(4) Add (1) and (2) resulting in when PTT should be turned off.
- *		(5) Take difference between current time and desired PPT off time
- *			and wait for additoinal time if required.
- *
- *----------------------------------------------------------------*/
-
-void audio_wait (int a)
-{	
-
-	audio_flush (a);
-
-#if USE_ALSA
-
-	/* For playback, this should wait for all pending frames */
-	/* to be played and then stop. */
-
-	snd_pcm_drain (adev[a].audio_out_handle);
-
-	/*
-	 * When this was first implemented, I observed:
- 	 *
- 	 * 	"Experimentation reveals that snd_pcm_drain doesn't
-	 * 	 actually wait.  It returns immediately. 
-	 * 	 However it does serve a useful purpose of stopping
-	 * 	 the playback after all the queued up data is used."
- 	 *
-	 *
-	 * Now that I take a closer look at the transmit timing, for
- 	 * version 1.2, it seems that snd_pcm_drain DOES wait until all
-	 * all pending frames have been played.  
-	 * Either way, the caller will now compensate for it.
- 	 */
-
-#else
-
-	assert (oss_audio_device_fd > 0);
-
-	// This caused a crash later on Cygwin.
-	// Haven't tried it on other (non-Linux) Unix yet.
-
-	// err = ioctl (oss_audio_device_fd, SNDCTL_DSP_SYNC, NULL);
-
-#endif
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_wait(): after sync, status=%d\n", err);	
-#endif
-
-} /* end audio_wait */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_close
- *
- * Purpose:     Close the audio device(s).
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- *
- *----------------------------------------------------------------*/
-
-int audio_close (void)
-{
-	int err = 0;
-	int a;
-
-	for (a = 0; a < MAX_ADEVS; a++) {
-
-#if USE_ALSA
-	  if (adev[a].audio_in_handle != NULL && adev[a].audio_out_handle != NULL) {
-
-	    audio_wait (a);
-
-	    snd_pcm_close (adev[a].audio_in_handle);
-	    snd_pcm_close (adev[a].audio_out_handle);
-	
-#else
-
-	  if  (oss_audio_device_fd > 0) {
-  
-	    audio_wait (a);
-
-	    close (oss_audio_device_fd);
-
-	    oss_audio_device_fd = -1;
-#endif
-
-	    free (adev[a].inbuf_ptr);
-	    free (adev[a].outbuf_ptr);
-
-	    adev[a].inbuf_size_in_bytes = 0;
-	    adev[a].inbuf_ptr = NULL;
-	    adev[a].inbuf_len = 0;
-	    adev[a].inbuf_next = 0;
-
-	    adev[a].outbuf_size_in_bytes = 0;
-	    adev[a].outbuf_ptr = NULL;
-	    adev[a].outbuf_len = 0;
-	  }
-	}
-
-	return (err);
-
-} /* end audio_close */
-
-
-/* end audio.c */
-
+
+
+// 
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      audio.c
+ *
+ * Purpose:   	Interface to audio device commonly called a "sound card" for
+ *		historical reasons.	
+ *
+ *		This version is for Linux and Cygwin.	
+ *
+ *		Two different types of sound interfaces are supported:
+ *
+ *		* OSS - For Cygwin or Linux versions with /dev/dsp.
+ *
+ *		* ALSA - For Linux versions without /dev/dsp.
+ *			In this case, define preprocessor symbol USE_ALSA.
+ *
+ * References:	Some tips on on using Linux sound devices.
+ *
+ *		http://www.oreilly.de/catalog/multilinux/excerpt/ch14-05.htm
+ *		http://cygwin.com/ml/cygwin-patches/2004-q1/msg00116/devdsp.c
+ *		http://manuals.opensound.com/developer/fulldup.c.html
+ *
+ *		"Introduction to Sound Programming with ALSA"
+ *		http://www.linuxjournal.com/article/6735?page=0,1
+ *
+ *		http://www.alsa-project.org/main/index.php/Asoundrc
+ *
+ * Credits:	Release 1.0: Fabrice FAURE contributed code for the SDR UDP interface.
+ *
+ *		Discussion here:  http://gqrx.dk/doc/streaming-audio-over-udp
+ *
+ *		Release 1.1:  Gabor Berczi provided fixes for the OSS code
+ *		which had fallen into decay.
+ *		
+ * Major Revisions: 	
+ *
+ *		1.2 - Add ability to use more than one audio device. 
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+
+#if USE_ALSA
+#include <alsa/asoundlib.h>
+#else
+#include <errno.h>
+#ifdef __OpenBSD__
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#endif
+
+
+#include "direwolf.h"
+#include "audio.h"
+#include "audio_stats.h"
+#include "textcolor.h"
+#include "dtime_now.h"
+#include "demod.h"		/* for alevel_t & demod_get_audio_level() */
+
+
+/* Audio configuration. */
+
+static struct audio_s          *save_audio_config_p;
+
+
+/* Current state for each of the audio devices. */
+
+static struct adev_s {
+
+#if USE_ALSA
+	snd_pcm_t *audio_in_handle;
+	snd_pcm_t *audio_out_handle;
+
+	int bytes_per_frame;		/* number of bytes for a sample from all channels. */
+					/* e.g. 4 for stereo 16 bit. */
+
+#else
+	int oss_audio_device_fd;	/* Single device, both directions. */
+
+#endif
+
+	int inbuf_size_in_bytes;	/* number of bytes allocated */
+	unsigned char *inbuf_ptr;
+	int inbuf_len;			/* number byte of actual data available. */
+	int inbuf_next;			/* index of next to remove. */
+
+	int outbuf_size_in_bytes;
+	unsigned char *outbuf_ptr;
+	int outbuf_len;
+
+	enum audio_in_type_e g_audio_in_type;
+
+	int udp_sock;			/* UDP socket for receiving data */
+
+} adev[MAX_ADEVS];
+
+
+// Originally 40.  Version 1.2, try 10 for lower latency.
+
+#define ONE_BUF_TIME 10
+
+
+#if USE_ALSA
+static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *name, char *dir);
+//static void alsa_select_device (char *pick_dev, int direction, char *result);
+#else
+static int set_oss_params (int a, int fd, struct audio_s *pa);
+#endif
+
+
+#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
+
+static int calcbufsize(int rate, int chans, int bits)
+{
+	int size1 = (rate * chans * bits  / 8 * ONE_BUF_TIME) / 1000;
+	int size2 = roundup1k(size1);
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
+		rate, chans, bits, size1, size2);
+#endif
+	return (size2);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_open
+ *
+ * Purpose:     Open the digital audio device.
+ *		For "OSS", the device name is typically "/dev/dsp".
+ *		For "ALSA", it's a lot more complicated.  See User Guide.
+ *
+ *		New in version 1.0, we recognize "udp:" optionally
+ *		followed by a port number.
+ *
+ * Inputs:      pa		- Address of structure of type audio_s.
+ *				
+ *				Using a structure, rather than separate arguments
+ *				seemed to make sense because we often pass around
+ *				the same set of parameters various places.
+ *
+ *				The fields that we care about are:
+ *					num_channels
+ *					samples_per_sec
+ *					bits_per_sample
+ *				If zero, reasonable defaults will be provided.
+ *
+ *				The device names are in adevice_in and adevice_out.
+ *				 - For "OSS", the device name is typically "/dev/dsp".
+ *				 - For "ALSA", the device names are hw:c,d
+ *				   where c is the "card" (for historical purposes)
+ *				   and d is the "device" within the "card."
+ *
+ *
+ * Outputs:	pa		- The ACTUAL values are returned here.
+ *
+ *				These might not be exactly the same as what was requested.
+ *					
+ *				Example: ask for stereo, 16 bits, 22050 per second.
+ *				An ordinary desktop/laptop PC should be able to handle this.
+ *				However, some other sort of smaller device might be
+ *				more restrictive in its capabilities.
+ *				It might say, the best I can do is mono, 8 bit, 8000/sec.
+ *
+ *				The sofware modem must use this ACTUAL information
+ *				that the device is supplying, that could be different
+ *				than what the user specified.
+ *              
+ * Returns:     0 for success, -1 for failure.
+ *	
+ *
+ *----------------------------------------------------------------*/
+
+int audio_open (struct audio_s *pa)
+{
+	int err;
+	int chan;
+	int a;
+	char audio_in_name[30];
+	char audio_out_name[30];
+
+
+	save_audio_config_p = pa;
+
+	memset (adev, 0, sizeof(adev));
+
+	for (a=0; a<MAX_ADEVS; a++) {
+#ifndef USE_ALSA
+	  adev[a].oss_audio_device_fd = -1;
+#endif
+	  adev[a].udp_sock = -1;
+	}
+
+
+/*
+ * Fill in defaults for any missing values.
+ */
+
+	for (a=0; a<MAX_ADEVS; a++) {
+
+	  if (pa->adev[a].num_channels == 0)
+	    pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS;
+
+	  if (pa->adev[a].samples_per_sec == 0)
+	    pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
+
+	  if (pa->adev[a].bits_per_sample == 0)
+	    pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
+
+	  for (chan=0; chan<MAX_CHANS; chan++) {
+	    if (pa->achan[chan].mark_freq == 0)
+	      pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ;
+
+	    if (pa->achan[chan].space_freq == 0)
+	      pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ;
+
+	    if (pa->achan[chan].baud == 0)
+	      pa->achan[chan].baud = DEFAULT_BAUD;
+
+	    if (pa->achan[chan].num_subchan == 0)
+	      pa->achan[chan].num_subchan = 1;
+	  }
+	}
+
+/*
+ * Open audio device(s).
+ */
+
+    	for (a=0; a<MAX_ADEVS; a++) {
+         if (pa->adev[a].defined) {
+
+	    adev[a].inbuf_size_in_bytes = 0;
+	    adev[a].inbuf_ptr = NULL;
+	    adev[a].inbuf_len = 0;
+	    adev[a].inbuf_next = 0;
+
+	    adev[a].outbuf_size_in_bytes = 0;
+	    adev[a].outbuf_ptr = NULL;
+	    adev[a].outbuf_len = 0;
+
+/*
+ * Determine the type of audio input.
+ */
+
+	    adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; 	
+
+	    if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
+	      adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN;
+	      /* Change "-" to stdin for readability. */
+	      strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
+	    }
+	    if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
+	      adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
+	      /* Supply default port if none specified. */
+	      if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
+	        strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
+	        snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
+	      }
+	    } 
+
+/* Let user know what is going on. */
+
+	    /* If not specified, the device names should be "default". */
+
+	    strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name));
+	    strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name));
+
+	    char ctemp[40];
+
+	    if (pa->adev[a].num_channels == 2) {
+	      snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
+	    }
+	    else {
+	      snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a));
+	    }
+
+            text_color_set(DW_COLOR_INFO);
+
+	    if (strcmp(audio_in_name,audio_out_name) == 0) {
+              dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp);
+	    }
+	    else {
+              dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp);
+              dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp);
+	    }
+
+/*
+ * Now attempt actual opens.
+ */
+
+/*
+ * Input device.
+ */
+
+	    switch (adev[a].g_audio_in_type) {
+
+/*
+ * Soundcard - ALSA.
+ */
+	      case AUDIO_IN_TYPE_SOUNDCARD:
+#if USE_ALSA
+	        err = snd_pcm_open (&(adev[a].audio_in_handle), audio_in_name, SND_PCM_STREAM_CAPTURE, 0);
+	        if (err < 0) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Could not open audio device %s for input\n%s\n", 
+	  		audio_in_name, snd_strerror(err));
+	          return (-1);
+	        }
+
+	        adev[a].inbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_in_handle, pa, audio_in_name, "input");
+	    
+#else // OSS
+	        adev[a].oss_audio_device_fd = open (pa->adev[a].adevice_in, O_RDWR);
+
+	        if (adev[a].oss_audio_device_fd < 0) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("%s:\n", pa->adev[a].adevice_in);
+//	          snprintf (message, sizeof(message), "Could not open audio device %s", pa->adev[a].adevice_in);
+//	          perror (message);
+	          return (-1);
+	        }
+
+	        adev[a].outbuf_size_in_bytes = adev[a].inbuf_size_in_bytes = set_oss_params (a, adev[a].oss_audio_device_fd, pa);
+
+	        if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
+	          return (-1);
+	        }
+#endif
+	        break;
+/*
+ * UDP.
+ */
+	      case AUDIO_IN_TYPE_SDR_UDP:
+
+	        //Create socket and bind socket
+	    
+	        {
+	          struct sockaddr_in si_me;
+	          int slen=sizeof(si_me);
+	          int data_size = 0;
+
+	          //Create UDP Socket
+	          if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Couldn't create socket, errno %d\n", errno);
+	            return -1;
+	          }
+
+	          memset((char *) &si_me, 0, sizeof(si_me));
+	          si_me.sin_family = AF_INET;   
+	          si_me.sin_port = htons((short)atoi(audio_in_name+4));
+	          si_me.sin_addr.s_addr = htonl(INADDR_ANY);
+
+	          //Bind to the socket
+	          if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Couldn't bind socket, errno %d\n", errno);
+	            return -1;
+	          }
+	        }
+	        adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; 
+	
+	        break;
+
+/* 
+ * stdin.
+ */
+   	      case AUDIO_IN_TYPE_STDIN:
+
+	        /* Do we need to adjust any properties of stdin? */
+
+	        adev[a].inbuf_size_in_bytes = 1024; 
+	    
+	        break;
+
+	      default:
+
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Internal error, invalid audio_in_type\n");
+	        return (-1);
+  	    }
+
+/*
+ * Output device.  Only "soundcard" is supported at this time. 
+ */
+
+#if USE_ALSA
+	    err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0);
+
+	    if (err < 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Could not open audio device %s for output\n%s\n", 
+			audio_out_name, snd_strerror(err));
+	      return (-1);
+	    }
+
+	    adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output");
+
+	    if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
+	      return (-1);
+	    }
+
+#endif
+
+/*
+ * Finally allocate buffer for each direction.
+ */
+	    adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes);
+	    assert (adev[a].inbuf_ptr  != NULL);
+	    adev[a].inbuf_len = 0;
+	    adev[a].inbuf_next = 0;
+
+	    adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes);
+	    assert (adev[a].outbuf_ptr  != NULL);
+	    adev[a].outbuf_len = 0;
+
+          } /* end of audio device defined */
+
+        } /* end of for each audio device */
+	
+	return (0);
+
+} /* end audio_open */
+
+
+
+
+#if USE_ALSA
+
+/*
+ * Set parameters for sound card.
+ *
+ * See  ??  for details. 
+ */
+/* 
+ * Terminology:
+ *   Sample	- for one channel.		e.g. 2 bytes for 16 bit.
+ *   Frame	- one sample for all channels.  e.g. 4 bytes for 16 bit stereo
+ *   Period	- size of one transfer.
+ */
+
+static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *devname, char *inout)
+{
+	
+	snd_pcm_hw_params_t *hw_params;
+	snd_pcm_uframes_t fpp; 			/* Frames per period. */
+
+	unsigned int val;
+
+	int dir;
+	int err;
+
+	int buf_size_in_bytes;			/* result, number of bytes per transfer. */
+
+
+	err = snd_pcm_hw_params_malloc (&hw_params);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not alloc hw param structure.\n%s\n", 
+			snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	err = snd_pcm_hw_params_any (handle, hw_params);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not init hw param structure.\n%s\n", 
+			snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	/* Interleaved data: L, R, L, R, ... */
+
+	err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not set interleaved mode.\n%s\n", 
+			snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	/* Signed 16 bit little endian or unsigned 8 bit. */
+
+
+	err = snd_pcm_hw_params_set_format (handle, hw_params,
+ 		pa->adev[a].bits_per_sample == 8 ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16_LE);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not set bits per sample.\n%s\n", 
+			snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	/* Number of audio channels. */
+
+
+	err = snd_pcm_hw_params_set_channels (handle, hw_params, pa->adev[a].num_channels);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not set number of audio channels.\n%s\n", 
+			snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	/* Audio sample rate. */
+
+
+	val = pa->adev[a].samples_per_sec;
+
+	dir = 0;
+
+
+	err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &val, &dir);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not set audio sample rate.\n%s\n", 
+			snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	if (val != pa->adev[a].samples_per_sec) {
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Asked for %d samples/sec but got %d.\n",
+
+				pa->adev[a].samples_per_sec, val);
+	  dw_printf ("for %s %s.\n", devname, inout);
+
+	  pa->adev[a].samples_per_sec = val;
+
+	}
+	
+	/* Original: */
+	/* Guessed around 20 reads/sec might be good. */
+	/* Period too long = too much latency. */
+	/* Period too short = more overhead of many small transfers. */
+
+	/* fpp = pa->adev[a].samples_per_sec / 20; */
+
+	/* The suggested period size was 2205 frames.  */
+	/* I thought the later "...set_period_size_near" might adjust it to be */
+	/* some more optimal nearby value based hardware buffer sizes but */
+	/* that didn't happen.   We ended up with a buffer size of 4410 bytes. */
+
+	/* In version 1.2, let's take a different approach. */
+	/* Reduce the latency and round up to a multiple of 1 Kbyte. */
+	
+	/* For the typical case of 44100 sample rate, 1 channel, 16 bits, we calculate */
+	/* a buffer size of 882 and round it up to 1k.  This results in 512 frames per period. */
+	/* A period comes out to be about 80 periods per second or about 12.5 mSec each. */
+
+	buf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
+
+#if __arm__
+	/* Ugly hack for RPi. */
+	/* Reducing buffer size is fine for input but not so good for output. */
+	
+	if (*inout == 'o') {
+	  buf_size_in_bytes = buf_size_in_bytes * 4;
+	}
+#endif
+
+	fpp = buf_size_in_bytes / (pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("suggest period size of %d frames\n", (int)fpp);
+#endif
+	dir = 0;
+	err = snd_pcm_hw_params_set_period_size_near (handle, hw_params, &fpp, &dir);
+
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not set period size\n%s\n", snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	err = snd_pcm_hw_params (handle, hw_params);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not set hw params\n%s\n", snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	/* Driver might not like our suggested period size */
+	/* and might have another idea. */
+
+	err = snd_pcm_hw_params_get_period_size (hw_params, &fpp, NULL);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not get audio period size.\n%s\n", snd_strerror(err));
+	  dw_printf ("for %s %s.\n", devname, inout);
+	  return (-1);
+	}
+
+	snd_pcm_hw_params_free (hw_params);
+	
+	/* A "frame" is one sample for all channels. */
+
+	/* The read and write use units of frames, not bytes. */
+
+	adev[a].bytes_per_frame = snd_pcm_frames_to_bytes (handle, 1);
+
+	assert (adev[a].bytes_per_frame == pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8);
+
+	buf_size_in_bytes = fpp * adev[a].bytes_per_frame;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio buffer size = %d (bytes per frame) x %d (frames per period) = %d \n", adev[a].bytes_per_frame, (int)fpp, buf_size_in_bytes);
+#endif
+
+	/* Version 1.3 - after a report of this situation for Mac OSX version. */
+	if (buf_size_in_bytes < 256 || buf_size_in_bytes > 32768) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", buf_size_in_bytes);
+	  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("This might be caused by unusual audio device configuration values.\n"); 
+	  buf_size_in_bytes = 2048;
+	  dw_printf ("Using %d to attempt recovery.\n", buf_size_in_bytes);
+	}
+
+	return (buf_size_in_bytes);
+
+
+} /* end alsa_set_params */
+
+
+#else
+
+
+/*
+ * Set parameters for sound card.  (OSS only)
+ *
+ * See  /usr/include/sys/soundcard.h  for details. 
+ */
+
+static int set_oss_params (int a, int fd, struct audio_s *pa)
+{
+	int err;
+	int devcaps;
+	int asked_for;
+	char message[100];
+	int ossbuf_size_in_bytes;
+
+
+	err = ioctl (fd, SNDCTL_DSP_CHANNELS, &(pa->adev[a].num_channels));
+   	if (err == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+    	  perror("Not able to set audio device number of channels");
+ 	  return (-1);
+	}
+
+        asked_for = pa->adev[a].samples_per_sec;
+
+	err = ioctl (fd, SNDCTL_DSP_SPEED, &(pa->adev[a].samples_per_sec));
+   	if (err == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+    	  perror("Not able to set audio device sample rate");
+ 	  return (-1);
+	}
+
+	if (pa->adev[a].samples_per_sec != asked_for) {
+	  text_color_set(DW_COLOR_INFO);
+          dw_printf ("Asked for %d samples/sec but actually using %d.\n",
+		asked_for, pa->adev[a].samples_per_sec);
+	}
+
+	/* This is actually a bit mask but it happens that */
+	/* 0x8 is unsigned 8 bit samples and */
+	/* 0x10 is signed 16 bit little endian. */
+
+	err = ioctl (fd, SNDCTL_DSP_SETFMT, &(pa->adev[a].bits_per_sample));
+   	if (err == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+    	  perror("Not able to set audio device sample size");
+ 	  return (-1);
+	}
+
+/*
+ * Determine capabilities.
+ */
+	err = ioctl (fd, SNDCTL_DSP_GETCAPS, &devcaps);
+   	if (err == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+    	  perror("Not able to get audio device capabilities");
+ 	  // Is this fatal? //	return (-1);
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_open(): devcaps = %08x\n", devcaps);	
+	if (devcaps & DSP_CAP_DUPLEX) dw_printf ("Full duplex record/playback.\n");
+	if (devcaps & DSP_CAP_BATCH) dw_printf ("Device has some kind of internal buffers which may cause delays.\n");
+	if (devcaps & ~ (DSP_CAP_DUPLEX | DSP_CAP_BATCH)) dw_printf ("Others...\n");
+#endif
+
+	if (!(devcaps & DSP_CAP_DUPLEX)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Audio device does not support full duplex\n");
+    	  // Do we care? //	return (-1);
+	}
+
+	err = ioctl (fd, SNDCTL_DSP_SETDUPLEX, NULL);
+   	if (err == -1) {
+	  // text_color_set(DW_COLOR_ERROR);
+    	  // perror("Not able to set audio full duplex mode");
+ 	  // Unfortunate but not a disaster.
+	}
+
+/*
+ * Get preferred block size.
+ * Presumably this will provide the most efficient transfer.
+ *
+ * In my particular situation, this turned out to be
+ *  	2816 for 11025 Hz 16 bit mono
+ *	5568 for 11025 Hz 16 bit stereo
+ *     11072 for 44100 Hz 16 bit mono
+ *
+ * This was long ago under different conditions.
+ * Should study this again some day.
+ *
+ * Your milage may vary.
+ */
+	err = ioctl (fd, SNDCTL_DSP_GETBLKSIZE, &ossbuf_size_in_bytes);
+   	if (err == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+    	  perror("Not able to get audio block size");
+	  ossbuf_size_in_bytes = 2048;	/* pick something reasonable */
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_open(): suggestd block size is %d\n", ossbuf_size_in_bytes);	
+#endif
+
+/*
+ * That's 1/8 of a second which seems rather long if we want to
+ * respond quickly.
+ */
+
+	ossbuf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_open(): using block size of %d\n", ossbuf_size_in_bytes);	
+#endif
+
+#if 0
+	/* Original - dies without good explanation. */
+	assert (ossbuf_size_in_bytes >= 256 && ossbuf_size_in_bytes <= 32768);
+#else
+	/* Version 1.3 - after a report of this situation for Mac OSX version. */
+	if (ossbuf_size_in_bytes < 256 || ossbuf_size_in_bytes > 32768) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", ossbuf_size_in_bytes);
+	  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("This might be caused by unusual audio device configuration values.\n");
+	  ossbuf_size_in_bytes = 2048;
+	  dw_printf ("Using %d to attempt recovery.\n", ossbuf_size_in_bytes);
+	}
+#endif
+	return (ossbuf_size_in_bytes);
+
+} /* end set_oss_params */
+
+
+#endif
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_get
+ *
+ * Purpose:     Get one byte from the audio device.
+ *
+ * Inputs:	a	- Our number for audio device.
+ *
+ * Returns:     0 - 255 for a valid sample.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ *		This will wait if no data is currently available.
+ *
+ *----------------------------------------------------------------*/
+
+// Use hot attribute for all functions called for every audio sample.
+
+__attribute__((hot))
+int audio_get (int a)
+{
+	int n;
+	int retries = 0;
+
+#if STATISTICS
+	/* Gather numbers for read from audio device. */
+
+#define duration 100			/* report every 100 seconds. */
+	static time_t last_time[MAX_ADEVS];
+	time_t this_time[MAX_ADEVS];
+	static int sample_count[MAX_ADEVS];
+	static int error_count[MAX_ADEVS];
+#endif
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("audio_get():\n");
+
+#endif
+
+	assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768);
+
+	  
+
+	switch (adev[a].g_audio_in_type) {
+
+/*
+ * Soundcard - ALSA 
+ */
+	  case AUDIO_IN_TYPE_SOUNDCARD:
+
+
+#if USE_ALSA
+
+
+	    while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+
+	      assert (adev[a].audio_in_handle != NULL);
+#if DEBUGx
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);	
+#endif
+	      n = snd_pcm_readi (adev[a].audio_in_handle, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
+
+#if DEBUGx	  
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("audio_get(): readi asked for %d and got %d frames\n",
+		adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n);	
+#endif
+
+ 
+	      if (n > 0) {
+
+	        /* Success */
+
+	        adev[a].inbuf_len = n * adev[a].bytes_per_frame;		/* convert to number of bytes */
+	        adev[a].inbuf_next = 0;
+
+	        audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			n, 
+			save_audio_config_p->statistics_interval);
+
+	      }
+	      else if (n == 0) {
+
+	        /* Didn't expect this, but it's not a problem. */
+	        /* Wait a little while and try again. */
+
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Audio input got zero bytes: %s\n", snd_strerror(n));
+	        SLEEP_MS(10);
+
+	        adev[a].inbuf_len = 0;
+	        adev[a].inbuf_next = 0;
+	      }
+	      else {
+	        /* Error */
+	        // TODO: Needs more study and testing. 
+
+		// TODO: print n.  should snd_strerror use n or errno?
+		// Audio input device error: Unknown error
+
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Audio input device %d error: %s\n", a, snd_strerror(n));
+
+	        audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			0, 
+			save_audio_config_p->statistics_interval);
+
+	        /* Try to recover a few times and eventually give up. */
+	        if (++retries > 10) {
+	          adev[a].inbuf_len = 0;
+	          adev[a].inbuf_next = 0;
+	          return (-1);
+	        }
+
+	        if (n == -EPIPE) {
+
+	          /* EPIPE means overrun */
+
+	          snd_pcm_recover (adev[a].audio_in_handle, n, 1);
+
+	        } 
+	        else {
+	          /* Could be some temporary condition. */
+	          /* Wait a little then try again. */
+	          /* Sometimes I get "Resource temporarily available" */
+	          /* when the Update Manager decides to run. */
+
+	          SLEEP_MS (250);
+	          snd_pcm_recover (adev[a].audio_in_handle, n, 1);
+	        }
+	      }
+	    }
+
+
+#else	/* end ALSA, begin OSS */
+
+	    /* Fixed in 1.2.  This was formerly outside of the switch */
+	    /* so the OSS version did not process stdin or UDP. */
+
+	    while (adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD && adev[a].inbuf_next >= adev[a].inbuf_len) {
+	      assert (adev[a].oss_audio_device_fd > 0);
+	      n = read (adev[a].oss_audio_device_fd, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes);
+	      //text_color_set(DW_COLOR_DEBUG);
+	      // dw_printf ("audio_get(): read %d returns %d\n", adev[a].inbuf_size_in_bytes, n);	
+	      if (n < 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        perror("Can't read from audio device");
+	        adev[a].inbuf_len = 0;
+	        adev[a].inbuf_next = 0;
+
+	        audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			0, 
+			save_audio_config_p->statistics_interval);
+
+	        return (-1);
+	      }
+	      adev[a].inbuf_len = n;
+	      adev[a].inbuf_next = 0;
+
+	      audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			n / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+			save_audio_config_p->statistics_interval);
+	    }
+
+#endif	/* USE_ALSA */
+
+
+	    break;
+
+/* 
+ * UDP.
+ */
+
+	  case AUDIO_IN_TYPE_SDR_UDP:
+
+	    while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+	      int ch, res,i;
+
+              assert (adev[a].udp_sock > 0);
+	      res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
+	      if (res < 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Can't read from udp socket, res=%d", res);
+	        adev[a].inbuf_len = 0;
+	        adev[a].inbuf_next = 0;
+
+	        audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			0, 
+			save_audio_config_p->statistics_interval);
+
+	        return (-1);
+	      }
+	    
+	      adev[a].inbuf_len = res;
+	      adev[a].inbuf_next = 0;
+
+	      audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+			save_audio_config_p->statistics_interval);
+
+	    }
+	    break;
+
+/*
+ * stdin.
+ */
+	  case AUDIO_IN_TYPE_STDIN:
+
+	    while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+	      int ch, res,i;
+
+	      res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
+	      if (res <= 0) {
+	        text_color_set(DW_COLOR_INFO);
+	        dw_printf ("\nEnd of file on stdin.  Exiting.\n");
+	        exit (0);
+	      }
+	    
+	      audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+			save_audio_config_p->statistics_interval);
+	    
+	      adev[a].inbuf_len = res;
+	      adev[a].inbuf_next = 0;
+	    }
+
+	    break;
+	}
+
+
+	if (adev[a].inbuf_next < adev[a].inbuf_len)
+	  n = adev[a].inbuf_ptr[adev[a].inbuf_next++];
+	//No data to read, avoid reading outside buffer
+	else
+	  n = 0;
+
+#if DEBUGx
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_get(): returns %d\n", n);
+
+#endif
+ 
+
+	return (n);
+
+} /* end audio_get */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_put
+ *
+ * Purpose:     Send one byte to the audio device.
+ *
+ * Inputs:	a
+ *
+ *		c	- One byte in range of 0 - 255.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ * See Also:	audio_flush
+ *		audio_wait
+ *
+ *----------------------------------------------------------------*/
+
+int audio_put (int a, int c)
+{
+	/* Should never be full at this point. */
+	assert (adev[a].outbuf_len < adev[a].outbuf_size_in_bytes);
+
+	adev[a].outbuf_ptr[adev[a].outbuf_len++] = c;
+
+	if (adev[a].outbuf_len == adev[a].outbuf_size_in_bytes) {
+	  return (audio_flush(a));
+	}
+
+	return (0);
+
+} /* end audio_put */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_flush
+ *
+ * Purpose:     Push out any partially filled output buffer.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * See Also:	audio_flush
+ *		audio_wait
+ *
+ *----------------------------------------------------------------*/
+
+int audio_flush (int a)
+{
+#if USE_ALSA
+	int k;
+	unsigned char *psound;
+	int retries = 10;
+	snd_pcm_status_t *status;
+
+	assert (adev[a].audio_out_handle != NULL);
+
+
+/*
+ * Trying to set the automatic start threshold didn't have the desired
+ * effect.  After the first transmitted packet, they are saved up
+ * for a few minutes and then all come out together.
+ *
+ * "Prepare" it if not already in the running state.
+ * We stop it at the end of each transmitted packet.
+ */
+
+
+	snd_pcm_status_alloca(&status);
+
+	k = snd_pcm_status (adev[a].audio_out_handle, status);
+	if (k != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k));
+	}
+
+	if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) {
+
+	  //text_color_set(DW_COLOR_DEBUG);
+	  //dw_printf ("Audio output state = %d.  Try to start.\n", k);
+
+	  k = snd_pcm_prepare (adev[a].audio_out_handle);
+
+	  if (k != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Audio output start error.\n%s\n", snd_strerror(k));
+	  }
+	}
+
+
+	psound = adev[a].outbuf_ptr;
+
+	while (retries-- > 0) {
+
+	  k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame);	
+#if DEBUGx
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n",
+				adev[a].outbuf_len / adev[a].bytes_per_frame, k);
+	  fflush (stdout);	
+#endif
+	  if (k == -EPIPE) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Audio output data underrun.\n");
+
+	    /* No problemo.  Recover and go around again. */
+
+	    snd_pcm_recover (adev[a].audio_out_handle, k, 1);
+	  }
+ 	  else if (k < 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Audio write error: %s\n", snd_strerror(k));
+
+	    /* Some other error condition. */
+	    /* Try again. What do we have to lose? */
+
+	    snd_pcm_recover (adev[a].audio_out_handle, k, 1);
+	  }
+ 	  else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Audio write took %d frames rather than %d.\n",
+ 			k, adev[a].outbuf_len / adev[a].bytes_per_frame);
+	
+	    /* Go around again with the rest of it. */
+
+	    psound += k * adev[a].bytes_per_frame;
+	    adev[a].outbuf_len -= k * adev[a].bytes_per_frame;
+	  }
+	  else {
+	    /* Success! */
+	    adev[a].outbuf_len = 0;
+	    return (0);
+	  }
+	}
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Audio write error retry count exceeded.\n");
+
+	adev[a].outbuf_len = 0;
+	return (-1);
+
+#else		/* OSS */
+
+	int k;
+	unsigned char *ptr;	
+	int len;
+
+	ptr = adev[a].outbuf_ptr;
+	len = adev[a].outbuf_len;
+
+	while (len > 0) {
+	  assert (adev[a].oss_audio_device_fd > 0);
+	  k = write (adev[a].oss_audio_device_fd, ptr, len);
+#if DEBUGx
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("audio_flush(): write %d returns %d\n", len, k);
+	  fflush (stdout);	
+#endif
+	  if (k < 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    perror("Can't write to audio device");
+	    adev[a].outbuf_len = 0;
+	    return (-1);
+	  }
+	  if (k < len) {
+	    /* presumably full but didn't block. */
+	    usleep (10000);
+	  }
+	  ptr += k;
+	  len -= k;
+	}
+
+	adev[a].outbuf_len = 0;
+	return (0);
+#endif
+
+} /* end audio_flush */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_wait
+ *
+ * Purpose:	Finish up audio output before turning PTT off.
+ *
+ * Inputs:	a		- Index for audio device (not channel!)
+ *
+ * Returns:     None.
+ *
+ * Description:	Flush out any partially filled audio output buffer.
+ *		Wait until all the queued up audio out has been played.
+ *		Take any other necessary actions to stop audio output.
+ *
+ * In an ideal world:
+ * 
+ *		We would like to ask the hardware when all the queued
+ *		up sound has actually come out the speaker.
+ *
+ * In reality:
+ *
+ * 		This has been found to be less than reliable in practice.
+ *
+ *		Caller does the following:
+ *
+ *		(1) Make note of when PTT is turned on.
+ *		(2) Calculate how long it will take to transmit the 
+ *			frame including TXDELAY, frame (including 
+ *			"flags", data, FCS and bit stuffing), and TXTAIL.
+ *		(3) Call this function, which might or might not wait long enough.
+ *		(4) Add (1) and (2) resulting in when PTT should be turned off.
+ *		(5) Take difference between current time and desired PPT off time
+ *			and wait for additoinal time if required.
+ *
+ *----------------------------------------------------------------*/
+
+void audio_wait (int a)
+{	
+
+	audio_flush (a);
+
+#if USE_ALSA
+
+	/* For playback, this should wait for all pending frames */
+	/* to be played and then stop. */
+
+	snd_pcm_drain (adev[a].audio_out_handle);
+
+	/*
+	 * When this was first implemented, I observed:
+ 	 *
+ 	 * 	"Experimentation reveals that snd_pcm_drain doesn't
+	 * 	 actually wait.  It returns immediately. 
+	 * 	 However it does serve a useful purpose of stopping
+	 * 	 the playback after all the queued up data is used."
+ 	 *
+	 *
+	 * Now that I take a closer look at the transmit timing, for
+ 	 * version 1.2, it seems that snd_pcm_drain DOES wait until all
+	 * all pending frames have been played.  
+	 * Either way, the caller will now compensate for it.
+ 	 */
+
+#else
+
+	assert (adev[a].oss_audio_device_fd > 0);
+
+	// This caused a crash later on Cygwin.
+	// Haven't tried it on other (non-Linux) Unix yet.
+
+	// err = ioctl (adev[a].oss_audio_device_fd, SNDCTL_DSP_SYNC, NULL);
+
+#endif
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_wait(): after sync, status=%d\n", err);	
+#endif
+
+} /* end audio_wait */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_close
+ *
+ * Purpose:     Close the audio device(s).
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ *
+ *----------------------------------------------------------------*/
+
+int audio_close (void)
+{
+	int err = 0;
+	int a;
+
+	for (a = 0; a < MAX_ADEVS; a++) {
+
+#if USE_ALSA
+	  if (adev[a].audio_in_handle != NULL && adev[a].audio_out_handle != NULL) {
+
+	    audio_wait (a);
+
+	    snd_pcm_close (adev[a].audio_in_handle);
+	    snd_pcm_close (adev[a].audio_out_handle);
+	
+#else
+
+	  if  (adev[a].oss_audio_device_fd > 0) {
+  
+	    audio_wait (a);
+
+	    close (adev[a].oss_audio_device_fd);
+
+	    adev[a].oss_audio_device_fd = -1;
+#endif
+
+	    free (adev[a].inbuf_ptr);
+	    free (adev[a].outbuf_ptr);
+
+	    adev[a].inbuf_size_in_bytes = 0;
+	    adev[a].inbuf_ptr = NULL;
+	    adev[a].inbuf_len = 0;
+	    adev[a].inbuf_next = 0;
+
+	    adev[a].outbuf_size_in_bytes = 0;
+	    adev[a].outbuf_ptr = NULL;
+	    adev[a].outbuf_len = 0;
+	  }
+	}
+
+	return (err);
+
+} /* end audio_close */
+
+
+/* end audio.c */
+
diff --git a/audio.h b/audio.h
index ebcaed9..674c27e 100644
--- a/audio.h
+++ b/audio.h
@@ -1,320 +1,344 @@
-
-/*------------------------------------------------------------------
- *
- * Module:      audio.h
- *
- * Purpose:   	Interface to audio device commonly called a "sound card"
- *		for historical reasons.
- *		
- *---------------------------------------------------------------*/
-
-
-#ifndef AUDIO_H
-#define AUDIO_H 1
-
-#include "direwolf.h"		/* for MAX_CHANS used throughout the application. */
-#include "ax25_pad.h"		/* for AX25_MAX_ADDR_LEN */
-
-				
-
-/*
- * PTT control. 
- */
-
-enum ptt_method_e { 
-	PTT_METHOD_NONE,	/* VOX or no transmit. */
-	PTT_METHOD_SERIAL,	/* Serial port RTS or DTR. */
-	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;
-
-enum ptt_line_e { PTT_LINE_NONE = 0, PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 };	  //  Important: 0 for neither.	
-typedef enum ptt_line_e ptt_line_t;
-
-enum audio_in_type_e {
-	AUDIO_IN_TYPE_SOUNDCARD,
-	AUDIO_IN_TYPE_SDR_UDP,
-	AUDIO_IN_TYPE_STDIN };
-
-/* For option to try fixing frames with bad CRC. */
-
-typedef enum retry_e {
-		RETRY_NONE=0,
-		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 sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t;
-			 
-
-struct audio_s {
-
-	/* Previously we could handle only a single audio device. */
-	/* In version 1.2, we generalize this to handle multiple devices. */
-	/* This means we can now have more than 2 radio channels. */
-
-	struct adev_param_s {
-
-	    /* Properites of the sound device. */
-
-	    int defined;		/* Was device defined? */
-					/* First one defaults to yes. */
-
-	    char adevice_in[80];	/* Name of the audio input device (or file?). */
-					/* TODO: Can be "-" to read from stdin. */
-
-	    char adevice_out[80];	/* Name of the audio output device (or file?). */
-
-	    int num_channels;		/* Should be 1 for mono or 2 for stereo. */
-	    int samples_per_sec;	/* Audio sampling rate.  Typically 11025, 22050, or 44100. */
-	    int bits_per_sample;	/* 8 (unsigned char) or 16 (signed short). */
-
-	} adev[MAX_ADEVS];
-
-
-	/* Common to all channels. */
-
-	char tts_script[80];		/* Script for text to speech. */
-
-
-	/* Properties for each audio channel, common to receive and transmit. */
-	/* Can be different for each radio channel. */
-
-
-	struct achan_param_s {
-
-	    int valid;			/* Is this channel valid?  */
-
-	    char mycall[AX25_MAX_ADDR_LEN];      /* Call associated with this radio channel. */
-                                	/* Could all be the same or different. */
-
-
-	    enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_OFF } modem_type;
-
-					/* Usual AFSK. */
-					/* Baseband signal. Not used yet. */
-					/* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */
-					/* No modem.  Might want this for DTMF only channel. */
-
-
-	    enum dtmf_decode_t { DTMF_DECODE_OFF, DTMF_DECODE_ON } dtmf_decode; 
-
-					/* Originally the DTMF ("Touch Tone") decoder was always */
-					/* enabled because it took a negligible amount of CPU. */
-					/* There were complaints about the false positives when */
-					/* hearing other modulation schemes on HF SSB so now it */
-					/* is enabled only when needed. */
-
-					/* "On" will send special "t" packet to attached applications */
-					/* and process as APRStt.  Someday we might want to separate */
-					/* these but for now, we have a single off/on. */
-
-	    int decimate;		/* Reduce AFSK sample rate by this factor to */
-					/* decrease computational requirements. */
-
-            int mark_freq;		/* Two tones for AFSK modulation, in Hz. */
-	    int space_freq;		/* Standard tones are 1200 and 2200 for 1200 baud. */
-
-	    int baud;			/* Data bits (more accurately, symbols) per second. */
-					/* Standard rates are 1200 for VHF and 300 for HF. */
-
-	/* Next 3 come from config file or command line. */
-
-	    char profiles[16];		/* zero or more of ABC etc, optional + */
-
-	    int num_freq;		/* Number of different frequency pairs for decoders. */
-
-	    int offset;			/* Spacing between filter frequencies. */
-
-	/* Next two are derived from 3 above by demod_init. */
-
-	    int num_demod;		/* Number of different demodulators (filters). */
-					/* Previously this was same as num_subchan but we add */
-					/* a new variation in version 1.2 where a single modem */
-					/* can feed multiple slicers and HDLC decoders. */
-
-					/* num_slicers could be added to be more general but */
-					/* for the intial experiment, we can just examine profiles. */
-
-	    int num_subchan;		/* Total number of modems / hdlc decoders for each channel. */
-					/* Potentially it could be product of strlen(profiles) * num_freq. */
-					/* Currently can't use both at once. */
-
-	/* These are for dealing with imperfect frames. */
-
-	    enum retry_e fix_bits;	/* Level of effort to recover from */
-					/* a bad FCS on the frame. */
-					/* 0 = no effort */
-					/* 1 = try fixing a single bit */
-					/* 2... = more techniques... */
-
-	    enum sanity_e sanity_test;	/* Sanity test to apply when finding a good */
-					/* CRC after making a change. */
-					/* Must look like APRS, AX.25, or anything. */
-
-	    int passall;		/* Allow thru even with bad CRC. */
-
-
-	/* Additional properties for transmit. */
-	
-	/* Originally we had control outputs only for PTT. */
-	/* In version 1.2, we generalize this to allow others such as DCD. */
-	/* Index following structure by one of these: */
-
-
-#define OCTYPE_PTT 0
-#define OCTYPE_DCD 1
-#define OCTYPE_FUTURE 2	
-
-#define NUM_OCTYPES 3		/* number of values above */
-	
-	    struct {  		
-
-	        ptt_method_t ptt_method; /* none, serial port, GPIO, LPT. */
-
-	        char ptt_device[20];	/* Serial device name for PTT.  e.g. COM1 or /dev/ttyS0 */
-			
-	        ptt_line_t ptt_line;	/* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */
-	        ptt_line_t ptt_line2;	/* Optional second one:  PTT_LINE_NONE when not used. */
-
-	        int ptt_gpio;		/* GPIO number. */
-	
-	        int ptt_lpt_bit;	/* Bit number for parallel printer port.  */
-					/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
-
-	        int ptt_invert;		/* Invert the output. */
-	        int ptt_invert2;	/* Invert the secondary output. */
-
-	    } octrl[NUM_OCTYPES];
-
-	/* Transmit timing. */
-
-	    int dwait;			/* First wait extra time for receiver squelch. */
-					/* Default 0 units of 10 mS each . */
-
-	    int slottime;		/* Slot time in 10 mS units for persistance algorithm. */
-					/* Typical value is 10 meaning 100 milliseconds. */
-
-	    int persist;		/* Sets probability for transmitting after each */
-					/* slot time delay.  Transmit if a random number */
-					/* in range of 0 - 255 <= persist value.  */
-					/* Otherwise wait another slot time and try again. */
-					/* Default value is 63 for 25% probability. */
-
-	    int txdelay;		/* After turning on the transmitter, */
-					/* send "flags" for txdelay * 10 mS. */
-					/* Default value is 30 meaning 300 milliseconds. */
-
-	    int txtail;			/* Amount of time to keep transmitting after we */
-					/* are done sending the data.  This is to avoid */
-					/* dropping PTT too soon and chopping off the end */
-					/* of the frame.  Again 10 mS units. */
-					/* At this point, I'm thinking of 10 as the default. */
-
-	} achan[MAX_CHANS];
-
-};
-
-
-#if __WIN32__
-#define DEFAULT_ADEVICE	""		/* Windows: Empty string = default audio device. */
-#else
-#if USE_ALSA
-#define DEFAULT_ADEVICE	"default"	/* Use default device for ALSA. */
-#else
-#define DEFAULT_ADEVICE	"/dev/dsp"	/* First audio device for OSS. */
-#endif					
-#endif
-
-
-/*
- * UDP audio receiving port.  Couldn't find any standard or usage precedent.
- * Got the number from this example:   http://gqrx.dk/doc/streaming-audio-over-udp
- * Any better suggestions?
- */
-
-#define DEFAULT_UDP_AUDIO_PORT 7355
-
-
-// Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes)
-
-#define SDR_UDP_BUF_MAXLEN 2000
-
-
-
-#define DEFAULT_NUM_CHANNELS 	1
-#define DEFAULT_SAMPLES_PER_SEC	44100	/* Very early observations.  Might no longer be valid. */
-					/* 22050 works a lot better than 11025. */
-					/* 44100 works a little better than 22050. */
-					/* If you have a reasonable machine, use the highest rate. */
-#define MIN_SAMPLES_PER_SEC	8000
-#define MAX_SAMPLES_PER_SEC	48000	/* Formerly 44100. */
-					/* Software defined radio often uses 48000. */
-
-#define DEFAULT_BITS_PER_SAMPLE	16
-
-#define DEFAULT_FIX_BITS RETRY_SWAP_SINGLE
-
-/* 
- * Standard for AFSK on VHF FM. 
- * Reversing mark and space makes no difference because
- * NRZI encoding only cares about change or lack of change
- * between the two tones.
- *
- * HF SSB uses 300 baud and 200 Hz shift.
- * 1600 & 1800 Hz is a popular tone pair, sometimes 
- * called the KAM tones.
- */
-
-#define DEFAULT_MARK_FREQ	1200	
-#define DEFAULT_SPACE_FREQ	2200
-#define DEFAULT_BAUD		1200
-
-
-
-/*
- * Typical transmit timings for VHF.
- */
-
-#define DEFAULT_DWAIT		0
-#define DEFAULT_SLOTTIME	10
-#define DEFAULT_PERSIST		63
-#define DEFAULT_TXDELAY		30
-#define DEFAULT_TXTAIL		10	
-
-
-/* 
- * Note that we have two versions of these in audio.c and audio_win.c.
- * Use one or the other depending on the platform.
- */
-
-
-int audio_open (struct audio_s *pa);
-
-int audio_get (int a);		/* a = audio device, 0 for first */
-
-int audio_put (int a, int c);
-
-int audio_flush (int a);
-
-void audio_wait (int a);
-
-int audio_close (void);
-
-
-#endif  /* ifdef AUDIO_H */
-
-
-/* end audio.h */
-
+
+/*------------------------------------------------------------------
+ *
+ * Module:      audio.h
+ *
+ * Purpose:   	Interface to audio device commonly called a "sound card"
+ *		for historical reasons.
+ *		
+ *---------------------------------------------------------------*/
+
+
+#ifndef AUDIO_H
+#define AUDIO_H 1
+
+#ifdef USE_HAMLIB
+#include <hamlib/rig.h>
+#endif
+
+#include "direwolf.h"		/* for MAX_CHANS used throughout the application. */
+#include "ax25_pad.h"		/* for AX25_MAX_ADDR_LEN */
+
+				
+
+/*
+ * PTT control. 
+ */
+
+enum ptt_method_e { 
+	PTT_METHOD_NONE,	/* VOX or no transmit. */
+	PTT_METHOD_SERIAL,	/* Serial port RTS or DTR. */
+	PTT_METHOD_GPIO,	/* General purpose I/O, Linux only. */
+	PTT_METHOD_LPT,	    /* Parallel printer port, Linux only. */
+    PTT_METHOD_HAMLIB }; /* HAMLib, Linux only. */
+
+typedef enum ptt_method_e ptt_method_t;
+
+enum ptt_line_e { PTT_LINE_NONE = 0, PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 };	  //  Important: 0 for neither.	
+typedef enum ptt_line_e ptt_line_t;
+
+enum audio_in_type_e {
+	AUDIO_IN_TYPE_SOUNDCARD,
+	AUDIO_IN_TYPE_SDR_UDP,
+	AUDIO_IN_TYPE_STDIN };
+
+/* For option to try fixing frames with bad CRC. */
+
+typedef enum retry_e {
+		RETRY_NONE=0,
+		RETRY_INVERT_SINGLE=1,
+		RETRY_INVERT_DOUBLE=2,
+		RETRY_INVERT_TRIPLE=3,
+		RETRY_INVERT_TWO_SEP=4,
+		RETRY_MAX = 5}  retry_t;
+
+
+typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t;
+			 
+
+struct audio_s {
+
+	/* Previously we could handle only a single audio device. */
+	/* In version 1.2, we generalize this to handle multiple devices. */
+	/* This means we can now have more than 2 radio channels. */
+
+	struct adev_param_s {
+
+	    /* Properites of the sound device. */
+
+	    int defined;		/* Was device defined? */
+					/* First one defaults to yes. */
+
+	    char adevice_in[80];	/* Name of the audio input device (or file?). */
+					/* TODO: Can be "-" to read from stdin. */
+
+	    char adevice_out[80];	/* Name of the audio output device (or file?). */
+
+	    int num_channels;		/* Should be 1 for mono or 2 for stereo. */
+	    int samples_per_sec;	/* Audio sampling rate.  Typically 11025, 22050, or 44100. */
+	    int bits_per_sample;	/* 8 (unsigned char) or 16 (signed short). */
+
+	} adev[MAX_ADEVS];
+
+
+	/* Common to all channels. */
+
+	char tts_script[80];		/* Script for text to speech. */
+
+	int statistics_interval;	/* Number of seconds between the audio */
+					/* statistics reports.  This is set by */
+					/* the "-a" option.  0 to disable feature. */
+
+	/* Properties for each audio channel, common to receive and transmit. */
+	/* Can be different for each radio channel. */
+
+
+	struct achan_param_s {
+
+	    int valid;			/* Is this channel valid?  */
+
+	    char mycall[AX25_MAX_ADDR_LEN];      /* Call associated with this radio channel. */
+                                	/* Could all be the same or different. */
+
+
+	    enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_OFF } modem_type;
+
+					/* Usual AFSK. */
+					/* Baseband signal. Not used yet. */
+					/* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */
+					/* No modem.  Might want this for DTMF only channel. */
+
+
+	    enum dtmf_decode_t { DTMF_DECODE_OFF, DTMF_DECODE_ON } dtmf_decode; 
+
+					/* Originally the DTMF ("Touch Tone") decoder was always */
+					/* enabled because it took a negligible amount of CPU. */
+					/* There were complaints about the false positives when */
+					/* hearing other modulation schemes on HF SSB so now it */
+					/* is enabled only when needed. */
+
+					/* "On" will send special "t" packet to attached applications */
+					/* and process as APRStt.  Someday we might want to separate */
+					/* these but for now, we have a single off/on. */
+
+	    int decimate;		/* Reduce AFSK sample rate by this factor to */
+					/* decrease computational requirements. */
+
+	    int interleave;		/* If > 1, interleave samples among multiple decoders. */
+					/* Quick hack for experiment. */
+
+            int mark_freq;		/* Two tones for AFSK modulation, in Hz. */
+	    int space_freq;		/* Standard tones are 1200 and 2200 for 1200 baud. */
+
+	    int baud;			/* Data bits (more accurately, symbols) per second. */
+					/* Standard rates are 1200 for VHF and 300 for HF. */
+
+	/* Next 3 come from config file or command line. */
+
+	    char profiles[16];		/* zero or more of ABC etc, optional + */
+
+	    int num_freq;		/* Number of different frequency pairs for decoders. */
+
+	    int offset;			/* Spacing between filter frequencies. */
+
+	    int num_slicers;		/* Number of different threshold points to decide */
+					/* between mark or space. */
+
+	/* This is derived from above by demod_init. */
+
+	    int num_subchan;		/* Total number of modems for each channel. */
+
+
+	/* These are for dealing with imperfect frames. */
+
+	    enum retry_e fix_bits;	/* Level of effort to recover from */
+					/* a bad FCS on the frame. */
+					/* 0 = no effort */
+					/* 1 = try fixing a single bit */
+					/* 2... = more techniques... */
+
+	    enum sanity_e sanity_test;	/* Sanity test to apply when finding a good */
+					/* CRC after making a change. */
+					/* Must look like APRS, AX.25, or anything. */
+
+	    int passall;		/* Allow thru even with bad CRC. */
+
+
+	/* Additional properties for transmit. */
+	
+	/* Originally we had control outputs only for PTT. */
+	/* In version 1.2, we generalize this to allow others such as DCD. */
+	/* Index following structure by one of these: */
+
+
+#define OCTYPE_PTT 0
+#define OCTYPE_DCD 1
+#define OCTYPE_FUTURE 2
+
+#define NUM_OCTYPES 4		/* number of values above */
+	
+	    struct {  		
+
+	        ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB. */
+
+	        char ptt_device[20];	/* Serial device name for PTT.  e.g. COM1 or /dev/ttyS0 */
+					/* Also used for HAMLIB.  Could be host:port when model is 1. */
+			
+	        ptt_line_t ptt_line;	/* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */
+	        ptt_line_t ptt_line2;	/* Optional second one:  PTT_LINE_NONE when not used. */
+
+	        int ptt_gpio;		/* GPIO number. */
+	
+	        int ptt_lpt_bit;	/* Bit number for parallel printer port.  */
+					/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
+
+	        int ptt_invert;		/* Invert the output. */
+	        int ptt_invert2;	/* Invert the secondary output. */
+
+#ifdef USE_HAMLIB
+
+	        int ptt_model;		/* HAMLIB model.  -1 for AUTO.  2 for rigctld.  Others are radio model. */
+#endif
+
+	    } octrl[NUM_OCTYPES];
+
+#define ICTYPE_TXINH 0
+
+#define NUM_ICTYPES 1
+
+	    struct {
+		ptt_method_t method;	/* none, serial port, GPIO, LPT. */
+		int gpio;		/* GPIO number */
+		int invert;		/* 1 = active low */
+	    } ictrl[NUM_ICTYPES];
+
+	/* Transmit timing. */
+
+	    int dwait;			/* First wait extra time for receiver squelch. */
+					/* Default 0 units of 10 mS each . */
+
+	    int slottime;		/* Slot time in 10 mS units for persistance algorithm. */
+					/* Typical value is 10 meaning 100 milliseconds. */
+
+	    int persist;		/* Sets probability for transmitting after each */
+					/* slot time delay.  Transmit if a random number */
+					/* in range of 0 - 255 <= persist value.  */
+					/* Otherwise wait another slot time and try again. */
+					/* Default value is 63 for 25% probability. */
+
+	    int txdelay;		/* After turning on the transmitter, */
+					/* send "flags" for txdelay * 10 mS. */
+					/* Default value is 30 meaning 300 milliseconds. */
+
+	    int txtail;			/* Amount of time to keep transmitting after we */
+					/* are done sending the data.  This is to avoid */
+					/* dropping PTT too soon and chopping off the end */
+					/* of the frame.  Again 10 mS units. */
+					/* At this point, I'm thinking of 10 as the default. */
+
+	} achan[MAX_CHANS];
+
+#ifdef USE_HAMLIB
+    int rigs;               /* Total number of configured rigs */
+    RIG *rig[MAX_RIGS];     /* HAMLib rig instances */
+#endif
+
+};
+
+
+#if __WIN32__ || __APPLE__
+#define DEFAULT_ADEVICE	""		/* Windows: Empty string = default audio device. */
+#else
+#if USE_ALSA
+#define DEFAULT_ADEVICE	"default"	/* Use default device for ALSA. */
+#else
+#define DEFAULT_ADEVICE	"/dev/dsp"	/* First audio device for OSS. */
+#endif					
+#endif
+
+
+/*
+ * UDP audio receiving port.  Couldn't find any standard or usage precedent.
+ * Got the number from this example:   http://gqrx.dk/doc/streaming-audio-over-udp
+ * Any better suggestions?
+ */
+
+#define DEFAULT_UDP_AUDIO_PORT 7355
+
+
+// Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes)
+
+#define SDR_UDP_BUF_MAXLEN 2000
+
+
+
+#define DEFAULT_NUM_CHANNELS 	1
+#define DEFAULT_SAMPLES_PER_SEC	44100	/* Very early observations.  Might no longer be valid. */
+					/* 22050 works a lot better than 11025. */
+					/* 44100 works a little better than 22050. */
+					/* If you have a reasonable machine, use the highest rate. */
+#define MIN_SAMPLES_PER_SEC	8000
+#define MAX_SAMPLES_PER_SEC	48000	/* Formerly 44100. */
+					/* Software defined radio often uses 48000. */
+
+#define DEFAULT_BITS_PER_SAMPLE	16
+
+#define DEFAULT_FIX_BITS RETRY_INVERT_SINGLE
+
+/* 
+ * Standard for AFSK on VHF FM. 
+ * Reversing mark and space makes no difference because
+ * NRZI encoding only cares about change or lack of change
+ * between the two tones.
+ *
+ * HF SSB uses 300 baud and 200 Hz shift.
+ * 1600 & 1800 Hz is a popular tone pair, sometimes 
+ * called the KAM tones.
+ */
+
+#define DEFAULT_MARK_FREQ	1200	
+#define DEFAULT_SPACE_FREQ	2200
+#define DEFAULT_BAUD		1200
+
+/* Used for sanity checking in config file and command line options. */
+/* 9600 is known to work.  */
+/* TODO: Is 19200 possible with a soundcard at 44100 samples/sec? */
+
+#define MIN_BAUD		100
+#define MAX_BAUD		10000
+
+
+/*
+ * Typical transmit timings for VHF.
+ */
+
+#define DEFAULT_DWAIT		0
+#define DEFAULT_SLOTTIME	10
+#define DEFAULT_PERSIST		63
+#define DEFAULT_TXDELAY		30
+#define DEFAULT_TXTAIL		10	
+
+
+/* 
+ * Note that we have two versions of these in audio.c and audio_win.c.
+ * Use one or the other depending on the platform.
+ */
+
+int audio_open (struct audio_s *pa);
+
+int audio_get (int a);		/* a = audio device, 0 for first */
+
+int audio_put (int a, int c);
+
+int audio_flush (int a);
+
+void audio_wait (int a);
+
+int audio_close (void);
+
+
+#endif  /* ifdef AUDIO_H */
+
+
+/* end audio.h */
+
diff --git a/audio_portaudio.c b/audio_portaudio.c
new file mode 100644
index 0000000..ead1ffc
--- /dev/null
+++ b/audio_portaudio.c
@@ -0,0 +1,1326 @@
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
+//    Copyright (C) 2015 Robert Stiles, KK5VD
+//
+//    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/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * Module:  audio_portaudio.c
+ *
+ * Purpose: Interface to audio device commonly called a "sound card" for
+ *          historical reasons.
+ *
+ * This version is for Various OS' using Port Audio
+ *
+ * Major Revisions:
+ *
+ *		1.2 - Add ability to use more than one audio device.
+ *		1.3 - New file added for Port Audio for Mac and possibly others.
+ *
+ *---------------------------------------------------------------*/
+
+#if	defined(USE_PORTAUDIO)
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "direwolf.h"
+#include "audio.h"
+#include "audio_stats.h"
+#include "textcolor.h"
+#include "dtime_now.h"
+#include "demod.h"		/* for alevel_t & demod_get_audio_level() */
+
+#include "portaudio.h"
+
+/* Audio configuration. */
+
+static struct audio_s          *save_audio_config_p;
+
+/* Current state for each of the audio devices. */
+
+static struct adev_s {
+
+	pthread_mutex_t input_mutex;
+	pthread_cond_t  input_cond;
+
+	PaStream *inStream;
+	PaStreamParameters inputParameters;
+	int pa_input_device_number;
+	int no_of_input_channels;
+	int input_finished;
+	int input_pause;
+	int input_flush;
+
+	void *audio_in_handle;
+	int inbuf_size_in_bytes;	  /* number of bytes allocated */
+	unsigned char *inbuf_ptr;
+	int inbuf_len;				  /* number byte of actual data available. */
+	int inbuf_next;				  /* index of next to remove. */
+	int inbuf_bytes_per_frame;	  /* number of bytes for a sample from all channels. */
+	int inbuf_frames_per_buffer;  /* number of frames in a buffer. */
+
+	pthread_mutex_t output_mutex;
+	pthread_cond_t  output_cond;
+
+	PaStream *outStream;
+	PaStreamParameters outputParameters;
+	int pa_output_device_number;
+	int no_of_output_channels;
+	int output_pause;
+	int output_finished;
+	int output_flush;
+	int output_wait_flag;
+
+	void *audio_out_handle;
+	int outbuf_size_in_bytes;
+	unsigned char *outbuf_ptr;
+	int outbuf_len;
+	int outbuf_next;			  /* index of next to remove. */
+	int outbuf_bytes_per_frame;   /* number of bytes for a sample from all channels. */
+	int outbuf_frames_per_buffer; /* number of frames in a buffer. */
+
+	enum audio_in_type_e g_audio_in_type;
+
+	int udp_sock;			      /* UDP socket for receiving data */
+
+} adev[MAX_ADEVS];
+
+// Originally 40.  Version 1.2, try 10 for lower latency.
+
+#define ONE_BUF_TIME 10
+#define SAMPLE_SILENCE 0
+
+#define PA_INPUT  1
+#define PA_OUTPUT 2
+
+#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
+
+#undef FOR_FUTURE_USE
+
+static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *devname, char *inout);
+static void print_pa_devices(void);
+static int check_pa_configure(struct adev_s *dev, int sample_rate);
+static void list_supported_sample_rates(struct adev_s *dev);
+static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo);
+static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag);
+static int calcbufsize(int rate, int chans, int bits);
+
+
+static int calcbufsize(int rate, int chans, int bits)
+{
+	int size1 = (rate * chans * bits  / 8 * ONE_BUF_TIME) / 1000;
+	int size2 = roundup1k(size1);
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
+			   rate, chans, bits, size1, size2);
+#endif
+	return (size2);
+}
+
+/*------------------------------------------------------------------
+ * Search the portaudio device tree looking for the request device.
+ * One of the issues with portaudio has to do with devices returning
+ * the same device name for more then one connected device
+ * (ie two SignaLinks). Appending a Portaudio device index to the
+ * the device name ensure we can find the correct one. And if it's not
+ * available return the first occurence that matches the device name.
+ *----------------------------------------------------------------*/
+static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag)
+{
+	int numDevices = Pa_GetDeviceCount();
+	const PaDeviceInfo * di = (PaDeviceInfo *)0;
+	int i = 0;
+
+	// First check to see if the requested index matches the device name.
+	if(reqDeviceNo < numDevices) {
+		di = Pa_GetDeviceInfo((PaDeviceIndex) reqDeviceNo);
+		if(strncmp(di->name, _devName, 80) == 0) {
+			if((io_flag == PA_INPUT) && di->maxInputChannels)
+				return reqDeviceNo;
+			if((io_flag == PA_OUTPUT) && di->maxOutputChannels)
+				return reqDeviceNo;
+		}
+	}
+
+	// Requested device index doesn't match device name. Search for one.
+	for(i = 0; i < numDevices; i++) {
+		di = Pa_GetDeviceInfo((PaDeviceIndex) i);
+		if(strncmp(di->name, _devName, 80) == 0) {
+			if((io_flag == PA_INPUT) && di->maxInputChannels)
+				return i;
+			if((io_flag == PA_OUTPUT) && di->maxOutputChannels)
+				return i;
+		}
+	}
+
+	// No Matches found
+	return -1;
+}
+
+/*------------------------------------------------------------------
+ * Extract device name and number.
+ *----------------------------------------------------------------*/
+static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo)
+{
+	char *cPtr = (char *)0;
+	char cVal = 0;
+	int count = 0;
+	char numStr[8];
+
+	if(!deviceStr || !_devName || !_devNo) {
+		dw_printf( "Internal Error: Func %s passed null pointer.\n", __func__);
+		return -1;
+	}
+
+	cPtr = deviceStr;
+
+	memset(_devName, 0, length);
+	memset(numStr, 0, sizeof(numStr));
+
+	while(*cPtr) {
+		cVal = *cPtr++;
+		if(cVal == ':')  break;
+		if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) {
+			_devName[count++] = cVal;
+		}
+
+	}
+
+	count = 0;
+
+	while(*cPtr) {
+		cVal = *cPtr++;
+		if(isdigit(cVal) && (count < (sizeof(numStr) - 1))) {
+			numStr[count++] = cVal;
+		}
+	}
+
+	if(numStr[0] == 0) {
+		*_devNo = 0;
+	} else {
+		sscanf(numStr, "%d", _devNo);
+	}
+
+	return 0;
+}
+
+/*------------------------------------------------------------------
+ * List the supported sample rates.
+ *----------------------------------------------------------------*/
+static void list_supported_sample_rates(struct adev_s *dev)
+{
+	static double standardSampleRates[] = {
+		8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0,
+		44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated  list */
+	};
+	int     i, printCount;
+	PaError err;
+
+	printCount = 0;
+	for(i = 0; standardSampleRates[i] > 0; i++ ) {
+		err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, standardSampleRates[i] );
+		if( err == paFormatIsSupported ) {
+			if( printCount == 0 ) {
+				dw_printf( "\t%8.2f", standardSampleRates[i] );
+				printCount = 1;
+			}
+			else if( printCount == 4 ) {
+				dw_printf( ",\n\t%8.2f", standardSampleRates[i] );
+				printCount = 1;
+			}
+			else {
+				dw_printf( ", %8.2f", standardSampleRates[i] );
+				++printCount;
+			}
+		}
+	}
+
+	if( !printCount )
+		dw_printf( "None\n" );
+	else
+		dw_printf( "\n" );
+}
+
+/*------------------------------------------------------------------
+ * Check PA Configure parameters.
+ *----------------------------------------------------------------*/
+static int check_pa_configure(struct adev_s *dev, int sample_rate)
+{
+	if(!dev) {
+		dw_printf( "Internal Error: Func %s struct adev_s *dev null pointer.\n", __func__);
+		return -1;
+	}
+
+	PaError err = 0;
+	err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, sample_rate);
+	if(err == paFormatIsSupported) return 0;
+	dw_printf( "PortAudio Config Error: %s\n", Pa_GetErrorText(err));
+	return err;
+}
+
+/*------------------------------------------------------------------
+ * Print a list of device names and parameters
+ *----------------------------------------------------------------*/
+static void print_pa_devices(void)
+{
+	int     i, numDevices, defaultDisplayed;
+	const   PaDeviceInfo *deviceInfo;
+
+	numDevices = Pa_GetDeviceCount();
+
+	if( numDevices < 0 ) {
+		dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices );
+		return;
+	}
+
+	dw_printf( "Number of devices = %d\n", numDevices );
+
+	for(i = 0; i < numDevices; i++ ) {
+		deviceInfo = Pa_GetDeviceInfo( i );
+		dw_printf( "--------------------------------------- device #%d\n", i );
+
+		/* Mark global and API specific default devices */
+		defaultDisplayed = 0;
+		if( i == Pa_GetDefaultInputDevice() ) {
+			dw_printf( "[ Default Input" );
+			defaultDisplayed = 1;
+		}
+		else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) {
+			const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
+			dw_printf( "[ Default %s Input", hostInfo->name );
+			defaultDisplayed = 1;
+		}
+
+		if( i == Pa_GetDefaultOutputDevice() ) {
+			dw_printf( (defaultDisplayed ? "," : "[") );
+			dw_printf( " Default Output" );
+			defaultDisplayed = 1;
+		}
+		else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) {
+			const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
+			dw_printf( (defaultDisplayed ? "," : "[") );
+			dw_printf( " Default %s Output", hostInfo->name );
+			defaultDisplayed = 1;
+		}
+
+		if( defaultDisplayed )
+			dw_printf( " ]\n" );
+
+		/* print device info fields */
+		dw_printf( "Name        = \"%s\"\n", deviceInfo->name );
+		dw_printf( "Host API    = %s\n",     Pa_GetHostApiInfo( deviceInfo->hostApi )->name );
+		dw_printf( "Max inputs  = %d\n",     deviceInfo->maxInputChannels  );
+		dw_printf( "Max outputs = %d\n",     deviceInfo->maxOutputChannels  );
+	}
+}
+
+/*------------------------------------------------------------------
+ * Port Audio Input Callback
+ *----------------------------------------------------------------*/
+static int paInput16CB( const void *inputBuffer, void *outputBuffer,
+					   unsigned long framesPerBuffer,
+					   const PaStreamCallbackTimeInfo* timeInfo,
+					   PaStreamCallbackFlags statusFlags,
+					   void *userData )
+{
+	struct adev_s *data = (struct adev_s *) userData;
+	const int16_t *rptr = (const int16_t *) inputBuffer;
+	size_t framesToCalc = 0;
+	size_t i = 0;
+	int finished = 0;
+	int word = 0;
+	size_t bytes_left = data->inbuf_size_in_bytes - data->inbuf_len;
+	size_t framesLeft = bytes_left / data->inbuf_bytes_per_frame;
+
+	(void) outputBuffer; /* Prevent unused variable warnings. */
+	(void) timeInfo;
+	(void) statusFlags;
+	(void) userData;
+
+	if( framesLeft < framesPerBuffer ) {
+		framesToCalc = framesLeft;
+		finished = paComplete;
+	} else {
+		framesToCalc = framesPerBuffer;
+		finished = paContinue;
+	}
+
+	if( inputBuffer == NULL || data->input_flush) {
+		for(i = 0; i < framesToCalc; i++) {
+			data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
+			data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
+			if(data->no_of_input_channels == 2) {
+				data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
+				data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
+			}
+		}
+	} else {
+		for(i = 0; i < framesToCalc; i++) {
+			word = *rptr++;  /* left */
+			data->inbuf_ptr[data->inbuf_len++] = word & 0xff;
+			data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff;
+
+			if(data->no_of_input_channels == 2) {
+				word = *rptr++;  /* right */
+				data->inbuf_ptr[data->inbuf_len++] = word & 0xff;
+				data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff;
+			}
+		}
+	}
+
+	if((finished == paComplete) ||
+	   (data->inbuf_len >= data->inbuf_size_in_bytes)) {
+		pthread_cond_signal(&data->input_cond);
+		finished = data->input_finished;
+	}
+
+	return finished;
+}
+
+#if FOR_FUTURE_USE
+/*------------------------------------------------------------------
+ * Port Audio Output Callback
+ *----------------------------------------------------------------*/
+static int paOutput16CB( const void *inputBuffer, void *outputBuffer,
+						unsigned long framesPerBuffer,
+						const PaStreamCallbackTimeInfo* timeInfo,
+						PaStreamCallbackFlags statusFlags,
+						void *userData)
+{
+	struct adev_s *data = (struct adev_s *) userData;
+	int16_t *wptr = (int16_t *) outputBuffer;
+	size_t i = 0;
+	int finished = 0;
+	size_t bytes_left = data->outbuf_size_in_bytes - data->outbuf_len;
+	size_t framesLeft = bytes_left / data->outbuf_bytes_per_frame;
+	int word = 0;
+
+	(void) inputBuffer; /* Prevent unused variable warnings. */
+	(void) timeInfo;
+	(void) statusFlags;
+	(void) userData;
+
+	if(framesLeft && (framesLeft < framesPerBuffer)) {
+		/* final buffer... */
+		for(i = 0; i < framesLeft; i++ ) {
+			word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
+			word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
+			*wptr++ = word;  /* left */
+			if(data->no_of_output_channels == 2 ) {
+				word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
+				word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
+				*wptr++ = word;  /* right */
+			}
+		}
+		for( ; i < framesPerBuffer; i++ ) {
+			*wptr++ = 0;  	/* left */
+			if(data->no_of_output_channels == 2 )
+				*wptr++ = 0;  /* right */
+		}
+		finished = paContinue;
+	} else {
+		for(i = 0; i < framesPerBuffer; i++ ) {
+			word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
+			word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
+			*wptr++ = word;  /* left */
+			if(data->no_of_output_channels == 2) {
+				word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
+				word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
+				*wptr++ = word;  /* right */
+			}
+		}
+		finished = paComplete;
+	}
+
+	if(data->output_flush) {
+		data->output_flush = 0;
+		finished = paComplete;
+	}
+
+	pthread_cond_signal(&data->output_cond);
+	finished = data->output_finished;
+
+	return finished;
+}
+#endif
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_open
+ *
+ * Purpose:     Open the digital audio device.
+ *
+ *		New in version 1.0, we recognize "udp:" optionally
+ *		followed by a port number.
+ *
+ * Inputs:      pa		- Address of structure of type audio_s.
+ *
+ *				Using a structure, rather than separate arguments
+ *				seemed to make sense because we often pass around
+ *				the same set of parameters various places.
+ *
+ *				The fields that we care about are:
+ *					num_channels
+ *					samples_per_sec
+ *					bits_per_sample
+ *				If zero, reasonable defaults will be provided.
+ *
+ *				The device names are in adevice_in and adevice_out.
+ *				   where c is the "card" (for historical purposes)
+ *				   and d is the "device" within the "card."
+ *
+ *
+ * Outputs:	pa  - The ACTUAL values are returned here.
+ *
+ *				These might not be exactly the same as what was requested.
+ *
+ *				Example: ask for stereo, 16 bits, 22050 per second.
+ *				An ordinary desktop/laptop PC should be able to handle this.
+ *				However, some other sort of smaller device might be
+ *				more restrictive in its capabilities.
+ *				It might say, the best I can do is mono, 8 bit, 8000/sec.
+ *
+ *				The sofware modem must use this ACTUAL information
+ *				that the device is supplying, that could be different
+ *				than what the user specified.
+ *
+ * Returns:     0 for success, -1 for failure.
+ *
+ *
+ *----------------------------------------------------------------*/
+
+int audio_open (struct audio_s *pa)
+{
+	int err  = 0;
+	int chan = 0;
+	int a    = 0;
+	int clear_value = 0;
+	char audio_in_name[80];
+	char audio_out_name[80];
+	static int initalize_flag = 0;
+	PaError paerr = paNoError;
+
+	if(!initalize_flag) {
+		paerr = Pa_Initialize();
+		initalize_flag = -1;
+	}
+
+	if(paerr != paNoError ) return -1;
+
+	save_audio_config_p = pa;
+
+	memset (adev,           0, sizeof(adev));
+	memset (audio_in_name,  0, sizeof(audio_in_name));
+	memset (audio_out_name, 0, sizeof(audio_out_name));
+
+	for (a = 0; a < MAX_ADEVS; a++) {
+		adev[a].udp_sock = -1;
+	}
+
+	/*
+	 * Fill in defaults for any missing values.
+	 */
+
+	for (a = 0; a < MAX_ADEVS; a++) {
+		if (pa->adev[a].num_channels == 0)
+			pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS;
+
+		if (pa->adev[a].samples_per_sec == 0)
+			pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
+
+		if (pa->adev[a].bits_per_sample == 0)
+			pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
+
+		for (chan = 0; chan < MAX_CHANS; chan++) {
+			if (pa->achan[chan].mark_freq == 0)
+				pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ;
+
+			if (pa->achan[chan].space_freq == 0)
+				pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ;
+
+			if (pa->achan[chan].baud == 0)
+				pa->achan[chan].baud = DEFAULT_BAUD;
+
+			if (pa->achan[chan].num_subchan == 0)
+				pa->achan[chan].num_subchan = 1;
+		}
+	}
+
+	/*
+	 * Open audio device(s).
+	 */
+
+	for (a = 0; a < MAX_ADEVS; a++) {
+		if (pa->adev[a].defined) {
+
+			adev[a].inbuf_size_in_bytes = 0;
+			adev[a].outbuf_size_in_bytes = 0;
+
+			/*
+			 * Determine the type of audio input.
+			 */
+
+			adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
+
+			if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
+				adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN;
+				/* Change "-" to stdin for readability. */
+				strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
+			}
+
+			if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
+				adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
+				/* Supply default port if none specified. */
+				if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
+					strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
+					snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
+				}
+			}
+
+			/* Let user know what is going on. */
+			/* If not specified, the device names should be "default". */
+
+			strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name));
+			strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name));
+
+			char ctemp[40];
+
+			if (pa->adev[a].num_channels == 2) {
+				snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
+			} else {
+				snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a));
+			}
+
+			text_color_set(DW_COLOR_INFO);
+
+			if (strcmp(audio_in_name,audio_out_name) == 0) {
+				dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp);
+			} else {
+				dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp);
+				dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp);
+			}
+
+			/*
+			 * Now attempt actual opens.
+			 */
+
+			/*
+			 * Input device.
+			 */
+
+			switch (adev[a].g_audio_in_type) {
+
+				case AUDIO_IN_TYPE_SOUNDCARD:
+					print_pa_devices();
+					err = set_portaudio_params (a, &adev[a], pa, audio_in_name, audio_out_name);
+					if(err < 0) return -1;
+
+					pthread_mutex_init(&adev[a].input_mutex, NULL);
+					pthread_cond_init(&adev[a].input_cond, NULL);
+
+					pthread_mutex_init(&adev[a].output_mutex, NULL);
+					pthread_cond_init(&adev[a].output_cond, NULL);
+
+					if(pa->adev[a].bits_per_sample == 8)
+						clear_value = 128;
+					else
+						clear_value = 0;
+
+					break;
+
+					/*
+					 * UDP.
+					 */
+				case AUDIO_IN_TYPE_SDR_UDP:
+
+					// Create socket and bind socket
+
+				{
+					struct sockaddr_in si_me;
+					//Create UDP Socket
+					if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
+						text_color_set(DW_COLOR_ERROR);
+						dw_printf ("Couldn't create socket, errno %d\n", errno);
+						return -1;
+					}
+
+					memset((char *) &si_me, 0, sizeof(si_me));
+					si_me.sin_family = AF_INET;
+					si_me.sin_port = htons((short)atoi(audio_in_name+4));
+					si_me.sin_addr.s_addr = htonl(INADDR_ANY);
+
+					//Bind to the socket
+					if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
+						text_color_set(DW_COLOR_ERROR);
+						dw_printf ("Couldn't bind socket, errno %d\n", errno);
+						return -1;
+					}
+				}
+					//adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN;
+
+					break;
+
+					/*
+					 * stdin.
+					 */
+				case AUDIO_IN_TYPE_STDIN:
+
+					/* Do we need to adjust any properties of stdin? */
+
+					//adev[a].inbuf_size_in_bytes = 1024;
+
+					break;
+
+				default:
+
+					text_color_set(DW_COLOR_ERROR);
+					dw_printf ("Internal error, invalid audio_in_type\n");
+					return (-1);
+			}
+
+			/*
+			 * Finally allocate buffer for each direction.
+			 */
+
+	                /* Version 1.3 - Add sanity check on buffer size. */
+	                /* There was a reported case of assert failure on buffer size in audio_get(). */
+
+	                if (adev[a].inbuf_size_in_bytes < 256 || adev[a].inbuf_size_in_bytes > 32768) {
+	                  text_color_set(DW_COLOR_ERROR);
+	                  dw_printf ("Audio input buffer has unexpected extreme size of %d bytes.\n", adev[a].inbuf_size_in_bytes);
+	                  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
+	                  dw_printf ("This might be caused by unusual audio device configuration values.\n"); 
+	                  adev[a].inbuf_size_in_bytes = 2048;
+	                  dw_printf ("Using %d to attempt recovery.\n", adev[a].inbuf_size_in_bytes);
+	                }
+
+	                if (adev[a].outbuf_size_in_bytes < 256 || adev[a].outbuf_size_in_bytes > 32768) {
+	                  text_color_set(DW_COLOR_ERROR);
+	                  dw_printf ("Audio output buffer has unexpected extreme size of %d bytes.\n", adev[a].outbuf_size_in_bytes);
+	                  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
+	                  dw_printf ("This might be caused by unusual audio device configuration values.\n"); 
+	                  adev[a].outbuf_size_in_bytes = 2048;
+	                  dw_printf ("Using %d to attempt recovery.\n", adev[a].outbuf_size_in_bytes);
+	                }
+
+			adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes);
+			assert (adev[a].inbuf_ptr != NULL);
+			adev[a].inbuf_len = 0;
+			adev[a].inbuf_next = 0;
+			memset(adev[a].inbuf_ptr, clear_value, adev[a].inbuf_size_in_bytes);
+
+			adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes);
+			assert (adev[a].outbuf_ptr != NULL);
+			adev[a].outbuf_len = 0;
+			adev[a].outbuf_next = 0;
+			memset(adev[a].outbuf_ptr, clear_value, adev[a].outbuf_size_in_bytes);
+
+			if(adev[a].inStream) {
+				err = Pa_StartStream(adev[a].inStream);
+				if(err != paNoError) {
+					dw_printf ("Input stream start Error %s\n", Pa_GetErrorText(err));
+				}
+			}
+
+			if(adev[a].outStream) {
+				err = Pa_StartStream(adev[a].outStream);
+				if(err != paNoError) {
+					dw_printf ("Output stream start Error %s\n", Pa_GetErrorText(err));
+				}
+			}
+		} /* end of audio device defined */
+	} /* end of for each audio device */
+
+	return (0);
+
+} /* end audio_open */
+
+
+/*
+ * Set parameters for sound card.
+ *
+ * See  ??  for details.
+ */
+static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *_audio_in_name, char *_audio_out_name)
+{
+	int numDevices  = 0;
+	int err = 0;
+	int buffer_size = 0;
+	int sampleFormat = 0;
+	int no_of_bytes_per_sample = 0;
+	int reqInDeviceNo  = 0;
+	int reqOutDeviceNo = 0;
+	char input_devName[80];
+	char output_devName[80];
+
+	text_color_set(DW_COLOR_ERROR);
+
+	if(!dev || !pa || !_audio_in_name || !_audio_out_name) {
+		dw_printf ("Internal error, invalid function parameter pointer(s) (null)\n");
+		return -1;
+	}
+
+	if(_audio_in_name[0] == 0) {
+		dw_printf ("Input device name null\n");
+		return -1;
+	}
+
+	if(_audio_out_name[0] == 0) {
+		dw_printf ("Output device name null\n");
+		return -1;
+	}
+
+	numDevices = Pa_GetDeviceCount();
+	if( numDevices < 0 ) {
+		dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices );
+		return -1;
+	}
+
+	err = pa_devNN(_audio_in_name, input_devName, sizeof(input_devName), &reqInDeviceNo);
+	if(err < 0)	return -1;
+
+	reqInDeviceNo = searchPADevice(dev, input_devName, reqInDeviceNo, PA_INPUT);
+	if(reqInDeviceNo < 0) {
+		dw_printf ("Requested Input Audio Device not found %s.\n", input_devName);
+		return -1;
+	}
+
+	err = pa_devNN(_audio_out_name, output_devName, sizeof(output_devName), &reqOutDeviceNo);
+	if(err < 0)	return -1;
+
+	reqOutDeviceNo = searchPADevice(dev, output_devName, reqOutDeviceNo, PA_OUTPUT);
+	if(reqOutDeviceNo < 0) {
+		dw_printf ("Requested Output Audio Device not found %s.\n", output_devName);
+		return -1;
+	}
+
+	dev->pa_input_device_number  = reqInDeviceNo;
+	dev->pa_output_device_number = reqOutDeviceNo;
+
+	switch(pa->adev[a].bits_per_sample) {
+		case 8:
+			sampleFormat = paInt8;
+			no_of_bytes_per_sample = sizeof(int8_t);
+			assert("int8_t size not equal to 1" && sizeof(int8_t) == 1);
+			break;
+
+		case 16:
+			sampleFormat = paInt16;
+			no_of_bytes_per_sample = sizeof(int16_t);
+			assert("int16_t size not equal to 2" && sizeof(int16_t) == 2);
+			break;
+
+		default:
+			dw_printf ("Unsupported Sample Size %s.\n", output_devName);
+			return -1;
+	}
+
+
+	buffer_size = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
+
+	dev->inbuf_size_in_bytes     = buffer_size;
+	dev->inbuf_bytes_per_frame   = no_of_bytes_per_sample * pa->adev[a].num_channels;
+	dev->inbuf_frames_per_buffer = dev->inbuf_size_in_bytes  / dev->inbuf_bytes_per_frame;
+
+	dev->inputParameters.device       = dev->pa_input_device_number;
+	dev->inputParameters.channelCount = pa->adev[a].num_channels;
+	dev->inputParameters.sampleFormat = sampleFormat;
+	dev->inputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->inputParameters.device)->defaultLowInputLatency;
+	dev->inputParameters.hostApiSpecificStreamInfo = NULL;
+
+	dev->outbuf_size_in_bytes     = buffer_size;
+	dev->outbuf_bytes_per_frame   = no_of_bytes_per_sample * pa->adev[a].num_channels;
+	dev->outbuf_frames_per_buffer = dev->outbuf_size_in_bytes / dev->outbuf_bytes_per_frame;
+
+	dev->outputParameters.device       = dev->pa_output_device_number;
+	dev->outputParameters.channelCount = pa->adev[a].num_channels;
+	dev->outputParameters.sampleFormat = sampleFormat;
+	dev->outputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->outputParameters.device)->defaultHighOutputLatency;
+	dev->outputParameters.hostApiSpecificStreamInfo = NULL;
+
+	err = check_pa_configure(dev, pa->adev[a].samples_per_sec);
+	if(err) {
+		if(err == paInvalidSampleRate)
+			list_supported_sample_rates(dev);
+		return -1;
+	}
+
+	err = Pa_OpenStream(&dev->inStream,	&dev->inputParameters, NULL,
+						pa->adev[a].samples_per_sec, dev->inbuf_frames_per_buffer, 0, paInput16CB, dev );
+
+	if( err != paNoError ) {
+		dw_printf( "PortAudio OpenStream (input) Error: %s\n", Pa_GetErrorText(err));
+		return -1;
+	}
+
+	err = Pa_OpenStream(&dev->outStream, NULL, &dev->outputParameters,
+						// pa->adev[a].samples_per_sec, framesPerBuffer, 0, paOutput16CB, dev );
+						pa->adev[a].samples_per_sec, dev->outbuf_frames_per_buffer, 0, NULL, dev );
+
+	if( err != paNoError ) {
+		dw_printf( "PortAudio OpenStream (output) Error: %s\n", Pa_GetErrorText(err));
+		return -1;
+	}
+
+	dev->input_finished  = paContinue;
+	dev->output_finished = paContinue;
+
+	return buffer_size;
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_get
+ *
+ * Purpose:     Get one byte from the audio device.
+ *
+ * Inputs:	a	- Our number for audio device.
+ *
+ * Returns:     0 - 255 for a valid sample.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ *		This will wait if no data is currently available.
+ *
+ *----------------------------------------------------------------*/
+
+// Use hot attribute for all functions called for every audio sample.
+
+__attribute__((hot))
+int audio_get (int a)
+{
+	int n;
+	int retries = 0;
+
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("audio_get():\n");
+
+#endif
+
+	assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768);
+
+	switch (adev[a].g_audio_in_type) {
+
+			/*
+			 * Soundcard - PortAudio
+			 */
+		case AUDIO_IN_TYPE_SOUNDCARD:
+
+			while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+
+				assert (adev[a].inStream != NULL);
+#if DEBUGx
+				text_color_set(DW_COLOR_DEBUG);
+				dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
+#endif
+				if(adev[a].inbuf_len >= adev[a].inbuf_size_in_bytes) {
+					adev[a].inbuf_len = 0;
+					adev[a].inbuf_next = 0;
+				}
+
+				pthread_mutex_lock(&adev[a].input_mutex);
+				pthread_cond_wait(&adev[a].input_cond, &adev[a].input_mutex);
+				pthread_mutex_unlock(&adev[a].input_mutex);
+
+				n = adev[a].inbuf_len / adev[a].inbuf_bytes_per_frame;
+#if DEBUGx
+				text_color_set(DW_COLOR_DEBUG);
+				dw_printf ("audio_get(): readi asked for %d and got %d frames\n",
+						   adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n);
+#endif
+
+
+				if (n > 0) {
+
+					/* Success */
+
+					adev[a].inbuf_len = n * adev[a].inbuf_bytes_per_frame;		/* convert to number of bytes */
+					adev[a].inbuf_next = 0;
+
+	        			audio_stats (a, 
+						save_audio_config_p->adev[a].num_channels, 
+						n, 
+						save_audio_config_p->statistics_interval);
+
+				}
+				else if (n == 0) {
+
+					/* Didn't expect this, but it's not a problem. */
+					/* Wait a little while and try again. */
+
+					text_color_set(DW_COLOR_ERROR);
+					dw_printf ("[%s], Audio input got zero bytes\n", __func__);
+					SLEEP_MS(10);
+
+					adev[a].inbuf_len = 0;
+					adev[a].inbuf_next = 0;
+				}
+				else {
+					/* Error */
+					// TODO: Needs more study and testing.
+
+					// TODO: print n.  should snd_strerror use n or errno?
+					// Audio input device error: Unknown error
+
+					text_color_set(DW_COLOR_ERROR);
+					dw_printf ("Audio input device %d error\n", a);
+
+	        			audio_stats (a, 
+						save_audio_config_p->adev[a].num_channels, 
+						0, 
+						save_audio_config_p->statistics_interval);
+
+					/* Try to recover a few times and eventually give up. */
+					if (++retries > 10) {
+						adev[a].inbuf_len = 0;
+						adev[a].inbuf_next = 0;
+						return (-1);
+					}
+
+					if (n == -EPIPE) {
+
+						/* EPIPE means overrun */
+
+						//snd_pcm_recover (adev[a].audio_in_handle, n, 1);
+
+					}
+					else {
+						/* Could be some temporary condition. */
+						/* Wait a little then try again. */
+						/* Sometimes I get "Resource temporarily available" */
+						/* when the Update Manager decides to run. */
+
+						SLEEP_MS (250);
+						//snd_pcm_recover (adev[a].audio_in_handle, n, 1);
+					}
+				}
+			}
+
+			break;
+
+			/*
+			 * UDP.
+			 */
+
+		case AUDIO_IN_TYPE_SDR_UDP:
+
+			while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+				int res;
+
+				assert (adev[a].udp_sock > 0);
+				res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
+				if (res < 0) {
+					text_color_set(DW_COLOR_ERROR);
+					dw_printf ("Can't read from udp socket, res=%d", res);
+					adev[a].inbuf_len = 0;
+					adev[a].inbuf_next = 0;
+
+	        			audio_stats (a, 
+						save_audio_config_p->adev[a].num_channels, 
+						0, 
+						save_audio_config_p->statistics_interval);
+
+					return (-1);
+				}
+
+				adev[a].inbuf_len = res;
+				adev[a].inbuf_next = 0;
+
+	      			audio_stats (a, 
+					save_audio_config_p->adev[a].num_channels, 
+					res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+					save_audio_config_p->statistics_interval);
+			}
+			break;
+
+			/*
+			 * stdin.
+			 */
+		case AUDIO_IN_TYPE_STDIN:
+
+			while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+				int res;
+
+				res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
+				if (res <= 0) {
+					text_color_set(DW_COLOR_INFO);
+					dw_printf ("\nEnd of file on stdin.  Exiting.\n");
+					exit (0);
+				}
+
+	      			audio_stats (a, 
+					save_audio_config_p->adev[a].num_channels, 
+					res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+					save_audio_config_p->statistics_interval);
+
+				adev[a].inbuf_len = res;
+				adev[a].inbuf_next = 0;
+			}
+
+			break;
+	}
+
+
+	if (adev[a].inbuf_next < adev[a].inbuf_len)
+		n = adev[a].inbuf_ptr[adev[a].inbuf_next++];
+	else
+		n = 0;
+
+#if DEBUGx
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_get(): returns %d\n", n);
+
+#endif
+
+
+	return (n);
+
+} /* end audio_get */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_put
+ *
+ * Purpose:     Send one byte to the audio device.
+ *
+ * Inputs:	a
+ *
+ *		c	- One byte in range of 0 - 255.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ * See Also:	audio_flush
+ *		audio_wait
+ *
+ *----------------------------------------------------------------*/
+int audio_put (int a, int c)
+{
+	int err = 0;
+	size_t frames = 0;
+
+	//#define __TIMED__
+#ifdef __TIMED__
+	static int count = 0;
+	static double start = 0, end = 0, diff = 0;
+
+	if(adev[a].outbuf_len == 0)
+		start = dtime_now();
+#endif
+
+	if(c >= 0) {
+		adev[a].outbuf_ptr[adev[a].outbuf_len++] = c;
+	}
+
+	if ((adev[a].outbuf_len >= adev[a].outbuf_size_in_bytes) || (c < 0)) {
+
+		frames = adev[a].outbuf_len / adev[a].outbuf_bytes_per_frame;
+
+		if(frames > 0) {
+			err =  Pa_WriteStream(adev[a].outStream, adev[a].outbuf_ptr, frames);
+		}
+
+		// Getting underflow error for some reason on the first pass. Upon examination of the
+		// audio data revealed no discontinuity in the signal. Time measurements indicate this routine
+		// on this machine (2.8Ghz/Xeon E5462/2008 vintage) can handle ~6 times the current
+		// sample rate (44100/2 bytes per frame). For now, mask the error.
+		// Transfer Time:0.184750080 No of Frames:56264 Per frame:0.000003284 speed:6.905695
+
+		if ((err != paNoError) && (err != paOutputUnderflowed)) {
+			text_color_set(DW_COLOR_ERROR);
+			dw_printf ("[%s] Audio Output Error: %s\n", __func__, Pa_GetErrorText(err));
+		}
+
+#ifdef __TIMED__
+		count += frames;
+		if(c < 0) { // When the Ax25 frames are flushed.
+			end = dtime_now();
+			diff = end - start;
+			if(count)
+				dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n",
+						   diff, count, diff/(count * 1.0), (1.0/44100.0)/(diff/(count * 1.0)));
+			count = 0;
+		}
+#endif
+		adev[a].outbuf_len  = 0;
+		adev[a].outbuf_next = 0;
+	}
+
+	return (0);
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_flush
+ *
+ * Purpose:     Push out any partially filled output buffer.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * See Also:	audio_flush
+ *		audio_wait
+ *
+ *----------------------------------------------------------------*/
+
+int audio_flush (int a)
+{
+	audio_put(a, -1);
+	return 0;
+} /* end audio_flush */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_wait
+ *
+ * Purpose:	Finish up audio output before turning PTT off.
+ *
+ * Inputs:	a		- Index for audio device (not channel!)
+ *
+ * Returns:     None.
+ *
+ * Description:	Flush out any partially filled audio output buffer.
+ *		Wait until all the queued up audio out has been played.
+ *		Take any other necessary actions to stop audio output.
+ *
+ * In an ideal world:
+ *
+ *		We would like to ask the hardware when all the queued
+ *		up sound has actually come out the speaker.
+ *
+ * In reality:
+ *
+ * 		This has been found to be less than reliable in practice.
+ *
+ *		Caller does the following:
+ *
+ *		(1) Make note of when PTT is turned on.
+ *		(2) Calculate how long it will take to transmit the
+ *			frame including TXDELAY, frame (including
+ *			"flags", data, FCS and bit stuffing), and TXTAIL.
+ *		(3) Call this function, which might or might not wait long enough.
+ *		(4) Add (1) and (2) resulting in when PTT should be turned off.
+ *		(5) Take difference between current time and desired PPT off time
+ *			and wait for additoinal time if required.
+ *
+ *----------------------------------------------------------------*/
+
+void audio_wait (int a)
+{
+	audio_flush(a);
+	
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_wait(): after sync, status=%d\n", err);
+#endif
+} /* end audio_wait */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_close
+ *
+ * Purpose:     Close the audio device(s).
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ *
+ *----------------------------------------------------------------*/
+
+int audio_close (void)
+{
+	int err = 0;
+	int a;
+	
+	for (a = 0; a < MAX_ADEVS; a++) {
+		if(adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD) {
+			
+			audio_wait (a);
+			
+			if (adev[a].inStream != NULL) {
+				pthread_mutex_destroy(&adev[a].input_mutex);
+				pthread_cond_destroy(&adev[a].input_cond);
+				err |= (int) Pa_CloseStream(adev[a].inStream);
+			}
+			
+			if(adev[a].outStream != NULL) {
+				pthread_mutex_destroy(&adev[a].output_mutex);
+				pthread_cond_destroy(&adev[a].output_cond);
+				err |= (int) Pa_CloseStream(adev[a].outStream);
+			}
+			
+			err |= (int) Pa_Terminate();
+		}
+		
+		if(adev[a].inbuf_ptr)
+			free (adev[a].inbuf_ptr);
+		
+		if(adev[a].outbuf_ptr)
+			free (adev[a].outbuf_ptr);
+		
+		adev[a].inbuf_size_in_bytes = 0;
+		adev[a].inbuf_ptr  = NULL;
+		adev[a].inbuf_len  = 0;
+		adev[a].inbuf_next = 0;
+		
+		adev[a].outbuf_size_in_bytes = 0;
+		adev[a].outbuf_ptr  = NULL;
+		adev[a].outbuf_len  = 0;
+		adev[a].outbuf_next = 0;
+	}
+	
+	if(err < 0)
+		err = -1;
+	
+	return (err);
+
+} /* end audio_close */
+
+/* end audio_portaudio.c */
+
+#endif // USE_PORTAUDIO
diff --git a/audio_stats.c b/audio_stats.c
new file mode 100644
index 0000000..9c67769
--- /dev/null
+++ b/audio_stats.c
@@ -0,0 +1,178 @@
+
+// 
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      audio_stats.c
+ *
+ * Purpose:   	Print statistics for audio input stream.
+ *
+ * 		A common complaint is that there is no indication of 
+ *		audio input level until a packet is received correctly.
+ *		That's true for the Windows version but the Linux version
+ *		prints something like this each 100 seconds:
+ *
+ *		ADEVICE0: Sample rate approx. 44.1 k, 0 errors, receive audio level CH0 73
+ *
+ *		Some complain about the clutter but it has been a useful
+ *		troubleshooting tool.  In the earlier RPi days, the sample
+ *		rate was quite low due to a device driver issue.  
+ *		Using a USB hub on the RPi also caused audio problems.
+ *		One adapter, that I tried, produces samples at the 
+ *		right rate but all the samples are 0.
+ *
+ *		Here we pull the code out of the Linux version of audio.c
+ *		so we have a common function for all the platforms.
+ *
+ *		We also add a command line option to adjust the time
+ *		between reports or turn them off entirely.
+ *		
+ * Revisions: 	This is new in version 1.3.
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "audio_stats.h"
+#include "textcolor.h"
+#include "dtime_now.h"
+#include "demod.h"		/* for alevel_t & demod_get_audio_level() */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_stats 
+ *
+ * Purpose:     Add sample count from one buffer to the statistics.
+ *		Print if specified amount of time has passed.
+ *
+ * Inputs:	adev	- Audio device number:  0, 1, ..., MAX_ADEVS-1
+ *
+ 		nchan	- Number of channels for this device, 1 or 2.
+ *
+ *		nsamp	- How many audio samples were read.
+ *
+ *		interval - How many seconds between reports.
+ *				0 to turn off.
+ *
+ * Returns:     none
+ *
+ * Description:	...
+ *
+ *----------------------------------------------------------------*/
+
+
+void audio_stats (int adev, int nchan, int nsamp, int interval)
+{
+
+	/* Gather numbers for read from audio device. */
+
+
+	static time_t last_time[MAX_ADEVS] = { 0, 0, 0 };
+	time_t this_time[MAX_ADEVS];
+	static int sample_count[MAX_ADEVS];
+	static int error_count[MAX_ADEVS];
+	static int suppress_first[MAX_ADEVS];
+
+
+	if (interval <= 0) {
+	  return;
+	}
+
+	assert (adev >= 0 && adev < MAX_ADEVS);
+
+/*
+ * Print information about the sample rate as a troubleshooting aid.
+ * I've never seen an issue with Windows or x86 Linux but the Raspberry Pi
+ * has a very troublesome audio input system where many samples got lost.
+ *
+ * While we are at it we can also print the current audio level(s) providing 
+ * more clues if nothing is being decoded.
+ */
+
+	if (last_time[adev] == 0) {
+	  last_time[adev] = time(NULL);
+	  sample_count[adev] = 0;
+	  error_count[adev] = 0;
+	  suppress_first[adev] = 1;
+	 	/* suppressing the first one could mean a rather */
+		/* long wait for the first message.  We make the */
+		/* first collection interval 3 seconds. */
+	  last_time[adev] -= (interval - 3);
+	}
+	else {
+	  if (nsamp > 0) {
+	     sample_count[adev] += nsamp;
+	  }
+	  else {
+	     error_count[adev]++;
+	  }
+	  this_time[adev] = time(NULL);
+	  if (this_time[adev] >= last_time[adev] + interval) {
+
+	    if (suppress_first[adev]) {
+
+		/* The issue we had is that the first time the rate */
+		/* would be off considerably because we didn't start */
+		/* on a second boundary.  So we will suppress printing */
+		/* of the first one.  */
+
+	      suppress_first[adev] = 0;
+	    }
+	    else {
+	      float ave_rate = (sample_count[adev] / 1000.0) / interval;
+
+	      text_color_set(DW_COLOR_DEBUG);
+
+	      if (nchan > 1) {
+	        int ch0 = ADEVFIRSTCHAN(adev);
+	        alevel_t alevel0 = demod_get_audio_level(ch0,0);
+	        int ch1 = ADEVFIRSTCHAN(adev) + 1;
+	        alevel_t alevel1 = demod_get_audio_level(ch1,0);
+
+	        dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio levels CH%d %d, CH%d %d\n\n", 
+			adev, ave_rate, error_count[adev], ch0, alevel0.rec, ch1, alevel1.rec);
+	      }
+	      else {
+	        int ch0 = ADEVFIRSTCHAN(adev);
+	        alevel_t alevel0 = demod_get_audio_level(ch0,0);
+
+	        dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio level CH%d %d\n\n", 
+			adev, ave_rate, error_count[adev], ch0, alevel0.rec);
+	      }
+	    }
+	    last_time[adev] = this_time[adev];
+	    sample_count[adev] = 0;
+	    error_count[adev] = 0;
+	  }      
+	}
+
+}   /* end audio_stats.c */
+
diff --git a/audio_stats.h b/audio_stats.h
new file mode 100644
index 0000000..4cf8ad0
--- /dev/null
+++ b/audio_stats.h
@@ -0,0 +1,7 @@
+
+
+/* audio_stats.h */
+
+
+extern void audio_stats (int adev, int nchan, int nsamp, int interval);
+
diff --git a/audio_win.c b/audio_win.c
index 2fc2ae8..f431363 100644
--- a/audio_win.c
+++ b/audio_win.c
@@ -1,1159 +1,1154 @@
-
-//#define DEBUGUDP 1
-//#define DEBUG 1
-
-#define STATISTICS 1
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      audio_win.c
- *
- * Purpose:   	Interface to audio device commonly called a "sound card" for
- *		historical reasons.		
- *
- *		This version uses the native Windows sound interface.
- *
- * Credits:	Fabrice FAURE contributed Linux code for the SDR UDP interface.
- *
- *		Discussion here:  http://gqrx.dk/doc/streaming-audio-over-udp
- *
- * Major revisions:
- *
- *		1.2 - Add ability to use more than one audio device.
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <ctype.h>
-#include <io.h>
-#include <fcntl.h>
-
-#include <windows.h>		
-#include <mmsystem.h>
-
-#ifndef WAVE_FORMAT_96M16
-#define WAVE_FORMAT_96M16 0x40000
-#define WAVE_FORMAT_96S16 0x80000
-#endif
-
-#include <winsock2.h>
-#define _WIN32_WINNT 0x0501
-#include <ws2tcpip.h>
-
-
-#include "direwolf.h"
-#include "audio.h"
-#include "textcolor.h"
-#include "ptt.h"
-#include "demod.h"		/* for alevel_t & demod_get_audio_level() */
-
-
-
-/* Audio configuration. */
-
-static struct audio_s          *save_audio_config_p;
-
-
-/* 
- * Allocate enough buffers for 1 second each direction. 
- * Each buffer size is a trade off between being responsive 
- * to activity on the channel vs. overhead of having too
- * many little transfers.
- */
-
-/*
- * Originally, we had an abitrary buf time of 40 mS.
- *
- * For mono, the buffer size was rounded up from 3528 to 4k so
- * it was really about 50 mS per buffer or about 20 per second.
- * For stereo, the buffer size was rounded up from 7056 to 7k so
- * it was really about 43.7 mS per buffer or about 23 per second.
- * 
- * In version 1.2, let's try changing it to 10 to reduce the latency.
- * For mono, the buffer size was rounded up from 882 to 1k so it
- * was really about 12.5 mS per buffer or about 80 per second.
- */
-
-#define TOTAL_BUF_TIME 1000	
-#define ONE_BUF_TIME 10
-		
-#define NUM_IN_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME))
-#define NUM_OUT_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME))
-
-
-#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
-
-static int calcbufsize(int rate, int chans, int bits)
-{
-	int size1 = (rate * chans * bits  / 8 * ONE_BUF_TIME) / 1000;
-	int size2 = roundup1k(size1);
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
-		rate, chans, bits, size1, size2);
-#endif
-	return (size2);
-}
-
-
-/* Information for each audio stream (soundcard, stdin, or UDP) */
-
-static struct adev_s {
-
-	enum audio_in_type_e g_audio_in_type;	
-
-/*
- * UDP socket for receiving audio stream.
- * Buffer, length, and pointer for UDP or stdin.
- */
-
-	
-	SOCKET udp_sock;
-	char stream_data[SDR_UDP_BUF_MAXLEN];
-	int stream_len;
-	int stream_next;
-
-
-/* For sound output. */
-/* out_wavehdr.dwUser is used to keep track of output buffer state. */
-
-#define DWU_FILLING 1		/* Ready to use or in process of being filled. */
-#define DWU_PLAYING 2		/* Was given to sound system for playing. */
-#define DWU_DONE 3		/* Sound system is done with it. */
-
-	HWAVEOUT audio_out_handle;
-
-	volatile WAVEHDR out_wavehdr[NUM_OUT_BUF];
-	int out_current;		/* index to above. */
-	int outbuf_size;
-
-
-/* For sound input. */
-/* In this case dwUser is index of next available byte to remove. */
-
-	HWAVEIN  audio_in_handle;
-	WAVEHDR in_wavehdr[NUM_IN_BUF];
-	volatile WAVEHDR *in_headp;	/* head of queue to process. */
-	CRITICAL_SECTION in_cs;
-
-} adev[MAX_ADEVS];
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_open
- *
- * Purpose:     Open the digital audio device.
- *
- *		New in version 1.0, we recognize "udp:" optionally
- *		followed by a port number.
- *
- * Inputs:      pa		- Address of structure of type audio_s.
- *				
- *				Using a structure, rather than separate arguments
- *				seemed to make sense because we often pass around
- *				the same set of parameters various places.
- *
- *				The fields that we care about are:
- *					num_channels
- *					samples_per_sec
- *					bits_per_sample
- *				If zero, reasonable defaults will be provided.
- *
- * Outputs:	pa		- The ACTUAL values are returned here.
- *
- *				The Linux version adjusts strange values to the 
- *				nearest valid value.  Don't know, yet, if Windows
- *				does the same or just fails.  Or performs some
- *				expensive resampling from a rate supported by
- *				hardware.
- *
- *				These might not be exactly the same as what was requested.
- *					
- *				Example: ask for stereo, 16 bits, 22050 per second.
- *				An ordinary desktop/laptop PC should be able to handle this.
- *				However, some other sort of smaller device might be
- *				more restrictive in its capabilities.
- *				It might say, the best I can do is mono, 8 bit, 8000/sec.
- *
- *				The sofware modem must use this ACTUAL information
- *				that the device is supplying, that could be different
- *				than what the user specified.
- * 
- * Returns:     0 for success, -1 for failure.
- *
- * References:	Multimedia Reference
- *
- *		http://msdn.microsoft.com/en-us/library/windows/desktop/dd743606%28v=vs.85%29.aspx
- *
- *----------------------------------------------------------------*/
-
-
-static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2);
-static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2);
-
-int audio_open (struct audio_s *pa)
-{
-	int a;
-
-	int err;
-	int chan;
-	int n;
-	int in_dev_no[MAX_ADEVS];
-	int out_dev_no[MAX_ADEVS];
-
-
-	int num_devices;
-	WAVEINCAPS wic;
-	WAVEOUTCAPS woc;
-
-	save_audio_config_p = pa;
-
-
-    	for (a=0; a<MAX_ADEVS; a++) {
-      	  if (pa->adev[a].defined) {
-
-            struct adev_s *A = &(adev[a]);
-
-	    assert (A->audio_in_handle == 0);
-	    assert (A->audio_out_handle == 0);
-
-	    //text_color_set(DW_COLOR_DEBUG);
-	    //dw_printf ("pa->adev[a].adevice_in = '%s'\n",  pa->adev[a].adevice_in);
-	    //dw_printf ("pa->adev[a].adevice_out = '%s'\n", pa->adev[a].adevice_out);
-
-
-/*
- * Fill in defaults for any missing values.
- */
-	    if (pa -> adev[a].num_channels == 0)
-	      pa -> adev[a].num_channels = DEFAULT_NUM_CHANNELS;
-
-	    if (pa -> adev[a].samples_per_sec == 0)
-	      pa -> adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
-
-	    if (pa -> adev[a].bits_per_sample == 0)
-	      pa -> adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
-
-	    A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
-
-	    for (chan=0; chan<MAX_CHANS; chan++) {
-	      if (pa -> achan[chan].mark_freq == 0)
-	        pa -> achan[chan].mark_freq = DEFAULT_MARK_FREQ;
-
-	      if (pa -> achan[chan].space_freq == 0)
-	        pa -> achan[chan].space_freq = DEFAULT_SPACE_FREQ;
-
-	      if (pa -> achan[chan].baud == 0)
-	        pa -> achan[chan].baud = DEFAULT_BAUD;
-
-	      if (pa->achan[chan].num_subchan == 0)
-	        pa->achan[chan].num_subchan = 1;
-	    }
-
-
-	    A->udp_sock = INVALID_SOCKET;
-
-	    in_dev_no[a] = WAVE_MAPPER;	/* = -1 */
-	    out_dev_no[a] = WAVE_MAPPER;
-
-/*
- * Determine the type of audio input and select device.
- * This can be soundcard, UDP stream, or stdin.
- */
-	
-	    if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
-	      A->g_audio_in_type = AUDIO_IN_TYPE_STDIN;
-	      /* Change - to stdin for readability. */
-	      strcpy (pa->adev[a].adevice_in, "stdin");
-	    }
-	    else if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
-	      A->g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
-	      /* Supply default port if none specified. */
-	      if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
-	        strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
-	        sprintf (pa->adev[a].adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT);
-	      }
-	    } 
-	    else {
-	      A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; 	
-
-	      /* Does config file have a number?  */
-	      /* If so, it is an index into list of devices. */
-
-	      if (strlen(pa->adev[a].adevice_in) == 1 && isdigit(pa->adev[a].adevice_in[0])) {
-	        in_dev_no[a] = atoi(pa->adev[a].adevice_in);
-	      }
-
-	      /* Otherwise, does it have search string? */
-
-	      if (in_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) {
-	        num_devices = waveInGetNumDevs();
-	        for (n=0 ; n<num_devices && in_dev_no[a] == WAVE_MAPPER ; n++) {
-	          if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
-	            if (strstr(wic.szPname, pa->adev[a].adevice_in) != NULL) {
-	              in_dev_no[a] = n;
-	            }
-	          }
-	        }
-	        if (in_dev_no[a] == WAVE_MAPPER) {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in);
-	        }
-	      }
- 	    }
-
-/*
- * Select output device.
- * Only soundcard at this point.
- * Purhaps we'd like to add UDP for an SDR transmitter.
- */
-	    if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) {
-	      out_dev_no[a] = atoi(pa->adev[a].adevice_out);
-	    }
-
-	    if (out_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
-	      num_devices = waveOutGetNumDevs();
-	      for (n=0 ; n<num_devices && out_dev_no[a] == WAVE_MAPPER ; n++) {
-	        if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
-	          if (strstr(woc.szPname, pa->adev[a].adevice_out) != NULL) {
-	            out_dev_no[a] = n;
-	          }
-	        }
-	      }
-	      if (out_dev_no[a] == WAVE_MAPPER) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out);
-	      }
-	    }
-	  }   /* if defined */
-	}    /* for each device */
-
-
-/*
- * Display the input devices (soundcards) available and what is selected.
- */
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("Available audio input devices for receive (*=selected):\n");
-
-	num_devices = waveInGetNumDevs();
-
-        for (a=0; a<MAX_ADEVS; a++) {
-          if (pa->adev[a].defined) {
-
-	    if (in_dev_no[a] < -1 || in_dev_no[a] >= num_devices) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Invalid input (receive) audio device number %d.\n", in_dev_no[a]);
-	      in_dev_no[a] = WAVE_MAPPER;
-	    }
-	  }
-        }
-
-	text_color_set(DW_COLOR_INFO);
-	for (n=0; n<num_devices; n++) {
-
-	  if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
-	    for (a=0; a<MAX_ADEVS; a++) {
-	      if (pa->adev[a].defined) {
-	        dw_printf (" %c", n==in_dev_no[a] ? '*' : ' ');
-
-	      }
-	    }
-	    dw_printf ("  %d: %s", n, wic.szPname);
-
-	    for (a=0; a<MAX_ADEVS; a++) {
-	      if (pa->adev[a].defined && n==in_dev_no[a]) {
-	        if (pa->adev[a].num_channels == 2) {
-	          dw_printf ("   (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
-	        }
-	        else {
-	          dw_printf ("   (channel %d)", ADEVFIRSTCHAN(a));
-	        }
-	      }
-	    }
-	    dw_printf ("\n");
- 	  }
-    	}
-
-// Add UDP or stdin to end of device list if used.
-
-    	for (a=0; a<MAX_ADEVS; a++) {
-      	  if (pa->adev[a].defined) {
-
-            struct adev_s *A = &(adev[a]);
-
-	    /* Display stdin or udp:port if appropriate. */   
-
-	    if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) {
-
-	      int aaa;
-	      for (aaa=0; aaa<MAX_ADEVS; aaa++) {
-	        if (pa->adev[aaa].defined) {
-	          dw_printf (" %c", a == aaa ? '*' : ' ');
-
-	        }
-	      }
-	      dw_printf ("  %s                             ", pa->adev[a].adevice_in);	/* should be UDP:nnnn or stdin */
-
-	      if (pa->adev[a].num_channels == 2) {
-	        dw_printf ("   (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
-	      }
-	      else {
-	        dw_printf ("   (channel %d)", ADEVFIRSTCHAN(a));
-	      }
-	      dw_printf ("\n");
-	    }
-      	  }
-     	}
-
-
-/*
- * Display the output devices (soundcards) available and what is selected.
- */
-
-	dw_printf ("Available audio output devices for transmit (*=selected):\n");
-
-	/* TODO? */
-	/* No "*" is currently displayed when using the default device. */
-	/* Should we put "*" next to the default device when using it? */
-	/* Which is the default?  The first one? */
-
-	num_devices = waveOutGetNumDevs();
-
-        for (a=0; a<MAX_ADEVS; a++) {
-          if (pa->adev[a].defined) {
-	    if (out_dev_no[a] < -1 || out_dev_no[a] >= num_devices) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Invalid output (transmit) audio device number %d.\n", out_dev_no[a]);
-	      out_dev_no[a] = WAVE_MAPPER;
-	    }
-	  }
-	}
-
-	text_color_set(DW_COLOR_INFO);
-	for (n=0; n<num_devices; n++) {
-
-	  if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
-	    for (a=0; a<MAX_ADEVS; a++) {
-	      if (pa->adev[a].defined) {
-	        dw_printf (" %c", n==out_dev_no[a] ? '*' : ' ');
-
-	      }
-	    }
-	    dw_printf ("  %d: %s", n, woc.szPname);
-
-	    for (a=0; a<MAX_ADEVS; a++) {
-	      if (pa->adev[a].defined && n==out_dev_no[a]) {
-	        if (pa->adev[a].num_channels == 2) {
-	          dw_printf ("   (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
-	        }
-	        else {
-	          dw_printf ("   (channel %d)", ADEVFIRSTCHAN(a));
-	        }
-	      }
-	    }
-	    dw_printf ("\n");
-	  }
-	}
-
-
-/*
- * Open for each audio device input/output pair.
- */
-
-     	for (a=0; a<MAX_ADEVS; a++) {
-      	  if (pa->adev[a].defined) {
-
-            struct adev_s *A = &(adev[a]);
-
-	     WAVEFORMATEX wf;
-
-	     wf.wFormatTag = WAVE_FORMAT_PCM;
-	     wf.nChannels = pa -> adev[a].num_channels; 
-	     wf.nSamplesPerSec = pa -> adev[a].samples_per_sec;
-	     wf.wBitsPerSample = pa -> adev[a].bits_per_sample;
-	     wf.nBlockAlign = (wf.wBitsPerSample / 8) * wf.nChannels;
-	     wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec;
-	     wf.cbSize = 0;
-
-	     A->outbuf_size = calcbufsize(wf.nSamplesPerSec,wf.nChannels,wf.wBitsPerSample);
-
-
-/*
- * Open the audio output device.
- * Soundcard is only possibility at this time.
- */
-
-	     err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION);
-	     if (err != MMSYSERR_NOERROR) {
-	       text_color_set(DW_COLOR_ERROR);
-	       dw_printf ("Could not open audio device for output.\n");
-	       return (-1);
-	     }
-	  
-
-/*
- * Set up the output buffers.
- * We use dwUser to indicate it is available for filling.
- */
-
-	     memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr));
-
-	     for (n = 0; n < NUM_OUT_BUF; n++) {
-	       A->out_wavehdr[n].lpData = malloc(A->outbuf_size);
-	       A->out_wavehdr[n].dwUser = DWU_FILLING;	
-	       A->out_wavehdr[n].dwBufferLength = 0;
-	     }
-	     A->out_current = 0;			
-
-	
-/*
- * Open audio input device.
- * More possibilities here:  soundcard, UDP port, stdin.
- */
-
-	     switch (A->g_audio_in_type) {
-
-/*
- * Soundcard.
- */
-	       case AUDIO_IN_TYPE_SOUNDCARD:
-
-	         InitializeCriticalSection (&(A->in_cs));
-
-	         err = waveInOpen (&(A->audio_in_handle), in_dev_no[a], &wf, (DWORD_PTR)in_callback, a, CALLBACK_FUNCTION);
-	         if (err != MMSYSERR_NOERROR) {
-	           text_color_set(DW_COLOR_ERROR);
-	           dw_printf ("Could not open audio device for input.\n");
-	           return (-1);
-	         }	  
-
-
-	         /*
-	          * Set up the input buffers.
-	          */
-
-	         memset ((void*)(A->in_wavehdr), 0, sizeof(A->in_wavehdr));
-
-	         for (n = 0; n < NUM_OUT_BUF; n++) {
-	           A->in_wavehdr[n].dwBufferLength = A->outbuf_size;  /* all the same size */
-	           A->in_wavehdr[n].lpData = malloc(A->outbuf_size);
-	         }
-	         A->in_headp = NULL;			
-
-	         /*
-	          * Give them to the sound input system.
-	          */
-	
-	         for (n = 0; n < NUM_OUT_BUF; n++) {
-	           waveInPrepareHeader(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR));
-	           waveInAddBuffer(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR));
-	         }
-
-	         /*
-	          * Start it up.
-	          * The callback function is called when one is filled.
-	          */
-
-	         waveInStart (A->audio_in_handle);
-	         break;
-
-/*
- * UDP.
- */
-	       case AUDIO_IN_TYPE_SDR_UDP:
-
-	         {
-	           WSADATA wsadata;
-	           struct sockaddr_in si_me;
-	           //int slen=sizeof(si_me);
-	           //int data_size = 0;
-	           int err;
-
-	           err = WSAStartup (MAKEWORD(2,2), &wsadata);
-	           if (err != 0) {
-	               text_color_set(DW_COLOR_ERROR);
-	               dw_printf("WSAStartup failed: %d\n", err);
-	               return (-1);
-	           }
-
-	           if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
-	             text_color_set(DW_COLOR_ERROR);
-                     dw_printf("Could not find a usable version of Winsock.dll\n");
-                     WSACleanup();
-                     return (-1);
-	           }
-
-	           // Create UDP Socket
-
-	           A->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-	           if (A->udp_sock == INVALID_SOCKET) {
-	             text_color_set(DW_COLOR_ERROR);
-	             dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError());
-	             return -1;
-	           }
-
-	           memset((char *) &si_me, 0, sizeof(si_me));
-	           si_me.sin_family = AF_INET;   
-	           si_me.sin_port = htons((short)atoi(pa->adev[a].adevice_in + 4));
-	           si_me.sin_addr.s_addr = htonl(INADDR_ANY);
-
-	           // Bind to the socket
-
-	           if (bind(A->udp_sock, (SOCKADDR *) &si_me, sizeof(si_me)) != 0) {
-	             text_color_set(DW_COLOR_ERROR);
-	             dw_printf ("Couldn't bind socket, errno %d\n", WSAGetLastError());
-	             return -1;
-	           }
-	           A->stream_next= 0;
-	           A->stream_len = 0;
-	         }
-
-	         break;
-
-/* 
- * stdin.
- */
-   	       case AUDIO_IN_TYPE_STDIN:
-
-  	         setmode (STDIN_FILENO, _O_BINARY);
-	         A->stream_next= 0;
-	         A->stream_len = 0;
-
-	         break;
-
-	       default:
-
-	         text_color_set(DW_COLOR_ERROR);
-	         dw_printf ("Internal error, invalid audio_in_type\n");
-	         return (-1);
-  	     }
-
-	  }
-    	}
-
-	return (0);
-
-} /* end audio_open */
-
-
-
-/*
- * Called when input audio block is ready.
- */
-
-static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2)
-{
-
-	int a = instance;
-
-//dw_printf ("in_callback, handle = %d, a = %d\n", (int)handle, a);
-
-	assert (a >= 0 && a < MAX_ADEVS);
-	struct adev_s *A = &(adev[a]);
-
-
-	if (msg == WIM_DATA) {
-	
-	  WAVEHDR *p = (WAVEHDR*)param1;
-	  
-	  p->dwUser = -1;		/* needs to be unprepared. */
-	  p->lpNext = NULL;
-
-	  EnterCriticalSection (&(A->in_cs));
-
-	  if (A->in_headp == NULL) {
-	    A->in_headp = p;		/* first one in list */
-	  }
-	  else {
-	    WAVEHDR *last = (WAVEHDR*)(A->in_headp);
-
-	    while (last->lpNext != NULL) {
-	      last = last->lpNext;
-	    }
-	    last->lpNext = p;		/* append to last one */
-	  }
-
-	  LeaveCriticalSection (&(A->in_cs));
-	}
-}
-
-/*
- * Called when output system is done with a block and it
- * is again available for us to fill.
- */
-
-
-static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2)
-{
-	if (msg == WOM_DONE) {   
-
-	  WAVEHDR *p = (WAVEHDR*)param1;
-	  
-	  p->dwBufferLength = 0;
-	  p->dwUser = DWU_DONE;
-	}
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_get
- *
- * Purpose:     Get one byte from the audio device.
- *
- *
- * Inputs:	a	- Audio soundcard number.
- *
- * Returns:     0 - 255 for a valid sample.
- *              -1 for any type of error.
- *
- * Description:	The caller must deal with the details of mono/stereo
- *		and number of bytes per sample.
- *
- *		This will wait if no data is currently available.
- *
- *----------------------------------------------------------------*/
-
-// Use hot attribute for all functions called for every audio sample.
-
-__attribute__((hot))
-int audio_get (int a)
-{
-	struct adev_s *A;
- 
-	WAVEHDR *p;
-	int n;
-	int sample;
-
-        A = &(adev[a]); 
-
-#if defined(DEBUGUDP) || defined(STATISTICS)
-	
-	/* Gather numbers for read from UDP stream. */
-	/* Gather numbers for read from audio device. */
-
-#define duration 100			/* report every 100 seconds. */
-	static time_t last_time[MAX_ADEVS];
-	time_t this_time[MAX_ADEVS];
-	static int sample_count[MAX_ADEVS];
-	static int error_count[MAX_ADEVS];
-#endif
-
-	switch (A->g_audio_in_type) {
-
-/*
- * Soundcard.
- */
-	  case AUDIO_IN_TYPE_SOUNDCARD:
-
-	    while (1) {
-
-	      /*
-	       * Wait if nothing available.
-	       * Could use an event to wake up but this is adequate.
-	       */
-	      int timeout = 25;
-
-	      while (A->in_headp == NULL) {
-	        //SLEEP_MS (ONE_BUF_TIME / 5);
-	        SLEEP_MS (ONE_BUF_TIME);
-	        timeout--;
-	        if (timeout <= 0) {
-	          text_color_set(DW_COLOR_ERROR);
-
-// TODO1.2: Need more details.  Can we keep going?
-
-	          dw_printf ("Timeout waiting for input from audio device %d.\n", a);
-	          return (-1);
-	        }
-	      }
-
-	      p = (WAVEHDR*)(A->in_headp);		/* no need to be volatile at this point */
-
-	      if (p->dwUser == -1) {
-	        waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
-	        p->dwUser = 0;	/* Index for next byte. */
-	      }
-
-	      if (p->dwUser < p->dwBytesRecorded) {
-	        n = ((unsigned char*)(p->lpData))[p->dwUser++];
-#if DEBUGx
-
-	        text_color_set(DW_COLOR_DEBUG);
-	        dw_printf ("audio_get(): returns %d\n", n);
-
-#endif
-	        return (n);
-	      }
-	      /*
-	       * Buffer is all used up.  Give it back to sound input system.
-	       */
-
-	      EnterCriticalSection (&(A->in_cs));
-	      A->in_headp = p->lpNext;
-	      LeaveCriticalSection (&(A->in_cs));
-
-	      p->dwFlags = 0;
-	      waveInPrepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
-	      waveInAddBuffer(A->audio_in_handle, p, sizeof(WAVEHDR));	  
-	    }
-	    break;
-/*
- * UDP.
- */
-	  case AUDIO_IN_TYPE_SDR_UDP:
-
-	    while (A->stream_next >= A->stream_len) {
-	      int res;
-
-              assert (A->udp_sock > 0);
-
-	      res = recv (A->udp_sock, A->stream_data, SDR_UDP_BUF_MAXLEN, 0);
-	      if (res <= 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError());
-	        A->stream_len = 0;
-	        A->stream_next = 0;
-	        return (-1);
-	      } 
-
-#if DEBUGUDP
-
-// TODO: We should collect audio statistics like this for all types of input.  Move to common function.
-
-	      if (last_time[a] == 0) {
-	        last_time[a] = time(NULL);
-	        sample_count[a] = 0;
-	        error_count[a] = 0;
-	      }
-	      else {
-	        if (res > 0) {
-	           sample_count[a] += res/2;
-	        }
-	        else {
-	           error_count[a]++;
-	        }
-	        this_time[a] = time(NULL);
-	        if (this_time[a] >= last_time + duration) {
-	          text_color_set(DW_COLOR_DEBUG);
-	          dw_printf ("\ADEVICE%d: Past %d seconds, %d UDP audio samples processed, %d errors.\n\n", 
-			a, duration, sample_count[a], error_count[a]);
-	          last_time[a] = this_time[a];
-	          sample_count[a] = 0;
-	          error_count[a] = 0;
-	        }      
-	      }
-#endif
-	      A->stream_len = res;
-	      A->stream_next = 0;
-	    }
-	    sample = A->stream_data[A->stream_next] & 0xff;
-	    A->stream_next++;
-	    return (sample);
-	    break;
-/* 
- * stdin.
- */
-   	  case AUDIO_IN_TYPE_STDIN:
-
-	    while (A->stream_next >= A->stream_len) {
-	      int res;
-
-	      res = read(STDIN_FILENO, A->stream_data, 1024);
-	      if (res <= 0) {
-	        text_color_set(DW_COLOR_INFO);
-	        dw_printf ("\nEnd of file on stdin.  Exiting.\n");
-	        exit (0);
-	      }
-	    
-	      A->stream_len = res;
-	      A->stream_next = 0;
-	    }
-	    return (A->stream_data[A->stream_next++] & 0xff);
-	    break;
-  	}
-
-	return (-1);
-
-} /* end audio_get */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_put
- *
- * Purpose:     Send one byte to the audio device.
- *
- * Inputs:	a	- Index for audio device.
- *
- *		c	- One byte in range of 0 - 255.
- *
- *
- * Global In:	out_current	- index of output buffer currenly being filled.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- * Description:	The caller must deal with the details of mono/stereo
- *		and number of bytes per sample.
- *
- * See Also:	audio_flush
- *		audio_wait
- *
- *----------------------------------------------------------------*/
-
-int audio_put (int a, int c)
-{
-	WAVEHDR *p;
-
-	struct adev_s *A;
-	A = &(adev[a]); 
-	
-/* 
- * Wait if no buffers are available.
- * Don't use p yet because compiler might might consider dwFlags a loop invariant. 
- */
-
-	int timeout = 10;
-	while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) {
-	  SLEEP_MS (ONE_BUF_TIME);
-	  timeout--;
-	  if (timeout <= 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Audio output failure waiting for buffer.\n");
-	    ptt_term ();
-	    return (-1);
-	  }
-	}
-
-	p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
-
-	if (p->dwUser == DWU_DONE) {
-	  waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR));
-	  p->dwBufferLength = 0;
-	  p->dwUser = DWU_FILLING;
-	}
-
-	/* Should never be full at this point. */
-
-	assert (p->dwBufferLength >= 0);
-	assert (p->dwBufferLength < A->outbuf_size);
-
-	p->lpData[p->dwBufferLength++] = c;
-
-	if (p->dwBufferLength == A->outbuf_size) {
-	  return (audio_flush(a));
-	}
-
-	return (0);
-
-} /* end audio_put */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_flush
- *
- * Purpose:     Send current buffer to the audio output system.
- *
- * Inputs:	a	- Index for audio device.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- * See Also:	audio_flush
- *		audio_wait
- *
- *----------------------------------------------------------------*/
-
-int audio_flush (int a)
-{
-	WAVEHDR *p;
-	MMRESULT e;
-	struct adev_s *A;
-
-	A = &(adev[a]); 
-	
-	p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
-
-	if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) {
-
-	  p->dwUser = DWU_PLAYING;
-
-	  waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR));
-
-	  e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR));
-	  if (e != MMSYSERR_NOERROR) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("audio out write error %d\n", e);
-
-	    /* I don't expect this to ever happen but if it */
-	    /* does, make the buffer available for filling. */
-
-	    p->dwUser = DWU_DONE;
-	    return (-1);
-	  }
-	  A->out_current = (A->out_current + 1) % NUM_OUT_BUF;
-	}
-	return (0);
-
-} /* end audio_flush */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_wait
- *
- * Purpose:	Finish up audio output before turning PTT off.
- *
- * Inputs:	a		- Index for audio device (not channel!)
- *
- * Returns:     None.
- *
- * Description:	Flush out any partially filled audio output buffer.
- *		Wait until all the queued up audio out has been played.
- *		Take any other necessary actions to stop audio output.
- *
- * In an ideal world:
- * 
- *		We would like to ask the hardware when all the queued
- *		up sound has actually come out the speaker.
- *
- * In reality:
- *
- * 		This has been found to be less than reliable in practice.
- *
- *		Caller does the following:
- *
- *		(1) Make note of when PTT is turned on.
- *		(2) Calculate how long it will take to transmit the 
- *			frame including TXDELAY, frame (including 
- *			"flags", data, FCS and bit stuffing), and TXTAIL.
- *		(3) Call this function, which might or might not wait long enough.
- *		(4) Add (1) and (2) resulting in when PTT should be turned off.
- *		(5) Take difference between current time and desired PPT off time
- *			and wait for additoinal time if required.
- *
- *----------------------------------------------------------------*/
-
-void audio_wait (int a)
-{	
-
-	audio_flush (a);
-
-} /* end audio_wait */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_close
- *
- *
- * Purpose:     Close all of the audio devices.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- *
- *----------------------------------------------------------------*/
-
-int audio_close (void)
-{
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      audio_win.c
+ *
+ * Purpose:   	Interface to audio device commonly called a "sound card" for
+ *		historical reasons.		
+ *
+ *		This version uses the native Windows sound interface.
+ *
+ * Credits:	Fabrice FAURE contributed Linux code for the SDR UDP interface.
+ *
+ *		Discussion here:  http://gqrx.dk/doc/streaming-audio-over-udp
+ *
+ * Major revisions:
+ *
+ *		1.2 - Add ability to use more than one audio device.
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+#include <io.h>
+#include <fcntl.h>
+
+#include <windows.h>		
+#include <mmsystem.h>
+
+#ifndef WAVE_FORMAT_96M16
+#define WAVE_FORMAT_96M16 0x40000
+#define WAVE_FORMAT_96S16 0x80000
+#endif
+
+#include <winsock2.h>
+#define _WIN32_WINNT 0x0501
+#include <ws2tcpip.h>
+
+
+#include "direwolf.h"
+#include "audio.h"
+#include "audio_stats.h"
+#include "textcolor.h"
+#include "ptt.h"
+#include "demod.h"		/* for alevel_t & demod_get_audio_level() */
+
+
+
+/* Audio configuration. */
+
+static struct audio_s          *save_audio_config_p;
+
+
+/* 
+ * Allocate enough buffers for 1 second each direction. 
+ * Each buffer size is a trade off between being responsive 
+ * to activity on the channel vs. overhead of having too
+ * many little transfers.
+ */
+
+/*
+ * Originally, we had an abitrary buf time of 40 mS.
+ *
+ * For mono, the buffer size was rounded up from 3528 to 4k so
+ * it was really about 50 mS per buffer or about 20 per second.
+ * For stereo, the buffer size was rounded up from 7056 to 7k so
+ * it was really about 43.7 mS per buffer or about 23 per second.
+ * 
+ * In version 1.2, let's try changing it to 10 to reduce the latency.
+ * For mono, the buffer size was rounded up from 882 to 1k so it
+ * was really about 12.5 mS per buffer or about 80 per second.
+ */
+
+#define TOTAL_BUF_TIME 1000	
+#define ONE_BUF_TIME 10
+		
+#define NUM_IN_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME))
+#define NUM_OUT_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME))
+
+
+#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
+
+static int calcbufsize(int rate, int chans, int bits)
+{
+	int size1 = (rate * chans * bits  / 8 * ONE_BUF_TIME) / 1000;
+	int size2 = roundup1k(size1);
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
+		rate, chans, bits, size1, size2);
+#endif
+
+	/* Version 1.3 - add a sanity check. */
+	if (size2 < 256 || size2 > 32768) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", size2);
+	  dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("This might be caused by unusual audio device configuration values.\n"); 
+	  size2 = 2048;
+	  dw_printf ("Using %d to attempt recovery.\n", size2);
+	}
+
+	return (size2);
+}
+
+
+/* Information for each audio stream (soundcard, stdin, or UDP) */
+
+static struct adev_s {
+
+	enum audio_in_type_e g_audio_in_type;	
+
+/*
+ * UDP socket for receiving audio stream.
+ * Buffer, length, and pointer for UDP or stdin.
+ */
+
+	
+	SOCKET udp_sock;
+	char stream_data[SDR_UDP_BUF_MAXLEN];
+	int stream_len;
+	int stream_next;
+
+
+/* For sound output. */
+/* out_wavehdr.dwUser is used to keep track of output buffer state. */
+
+#define DWU_FILLING 1		/* Ready to use or in process of being filled. */
+#define DWU_PLAYING 2		/* Was given to sound system for playing. */
+#define DWU_DONE 3		/* Sound system is done with it. */
+
+	HWAVEOUT audio_out_handle;
+
+	volatile WAVEHDR out_wavehdr[NUM_OUT_BUF];
+	int out_current;		/* index to above. */
+	int outbuf_size;
+
+
+/* For sound input. */
+/* In this case dwUser is index of next available byte to remove. */
+
+	HWAVEIN  audio_in_handle;
+	WAVEHDR in_wavehdr[NUM_IN_BUF];
+	volatile WAVEHDR *in_headp;	/* head of queue to process. */
+	CRITICAL_SECTION in_cs;
+
+} adev[MAX_ADEVS];
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_open
+ *
+ * Purpose:     Open the digital audio device.
+ *
+ *		New in version 1.0, we recognize "udp:" optionally
+ *		followed by a port number.
+ *
+ * Inputs:      pa		- Address of structure of type audio_s.
+ *				
+ *				Using a structure, rather than separate arguments
+ *				seemed to make sense because we often pass around
+ *				the same set of parameters various places.
+ *
+ *				The fields that we care about are:
+ *					num_channels
+ *					samples_per_sec
+ *					bits_per_sample
+ *				If zero, reasonable defaults will be provided.
+ *
+ * Outputs:	pa		- The ACTUAL values are returned here.
+ *
+ *				The Linux version adjusts strange values to the 
+ *				nearest valid value.  Don't know, yet, if Windows
+ *				does the same or just fails.  Or performs some
+ *				expensive resampling from a rate supported by
+ *				hardware.
+ *
+ *				These might not be exactly the same as what was requested.
+ *					
+ *				Example: ask for stereo, 16 bits, 22050 per second.
+ *				An ordinary desktop/laptop PC should be able to handle this.
+ *				However, some other sort of smaller device might be
+ *				more restrictive in its capabilities.
+ *				It might say, the best I can do is mono, 8 bit, 8000/sec.
+ *
+ *				The sofware modem must use this ACTUAL information
+ *				that the device is supplying, that could be different
+ *				than what the user specified.
+ * 
+ * Returns:     0 for success, -1 for failure.
+ *
+ * References:	Multimedia Reference
+ *
+ *		http://msdn.microsoft.com/en-us/library/windows/desktop/dd743606%28v=vs.85%29.aspx
+ *
+ *----------------------------------------------------------------*/
+
+
+static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2);
+static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2);
+
+int audio_open (struct audio_s *pa)
+{
+	int a;
+
+	int err;
+	int chan;
+	int n;
+	int in_dev_no[MAX_ADEVS];
+	int out_dev_no[MAX_ADEVS];
+
+
+	int num_devices;
+	WAVEINCAPS wic;
+	WAVEOUTCAPS woc;
+
+	save_audio_config_p = pa;
+
+
+    	for (a=0; a<MAX_ADEVS; a++) {
+      	  if (pa->adev[a].defined) {
+
+            struct adev_s *A = &(adev[a]);
+
+	    assert (A->audio_in_handle == 0);
+	    assert (A->audio_out_handle == 0);
+
+	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("pa->adev[a].adevice_in = '%s'\n",  pa->adev[a].adevice_in);
+	    //dw_printf ("pa->adev[a].adevice_out = '%s'\n", pa->adev[a].adevice_out);
+
+
+/*
+ * Fill in defaults for any missing values.
+ */
+	    if (pa -> adev[a].num_channels == 0)
+	      pa -> adev[a].num_channels = DEFAULT_NUM_CHANNELS;
+
+	    if (pa -> adev[a].samples_per_sec == 0)
+	      pa -> adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
+
+	    if (pa -> adev[a].bits_per_sample == 0)
+	      pa -> adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
+
+	    A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
+
+	    for (chan=0; chan<MAX_CHANS; chan++) {
+	      if (pa -> achan[chan].mark_freq == 0)
+	        pa -> achan[chan].mark_freq = DEFAULT_MARK_FREQ;
+
+	      if (pa -> achan[chan].space_freq == 0)
+	        pa -> achan[chan].space_freq = DEFAULT_SPACE_FREQ;
+
+	      if (pa -> achan[chan].baud == 0)
+	        pa -> achan[chan].baud = DEFAULT_BAUD;
+
+	      if (pa->achan[chan].num_subchan == 0)
+	        pa->achan[chan].num_subchan = 1;
+	    }
+
+
+	    A->udp_sock = INVALID_SOCKET;
+
+	    in_dev_no[a] = WAVE_MAPPER;	/* = -1 */
+	    out_dev_no[a] = WAVE_MAPPER;
+
+/*
+ * Determine the type of audio input and select device.
+ * This can be soundcard, UDP stream, or stdin.
+ */
+	
+	    if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
+	      A->g_audio_in_type = AUDIO_IN_TYPE_STDIN;
+	      /* Change - to stdin for readability. */
+	      strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
+	    }
+	    else if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
+	      A->g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
+	      /* Supply default port if none specified. */
+	      if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
+	        strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
+	        snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
+	      }
+	    } 
+	    else {
+	      A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; 	
+
+	      /* Does config file have a number?  */
+	      /* If so, it is an index into list of devices. */
+
+	      if (strlen(pa->adev[a].adevice_in) == 1 && isdigit(pa->adev[a].adevice_in[0])) {
+	        in_dev_no[a] = atoi(pa->adev[a].adevice_in);
+	      }
+
+	      /* Otherwise, does it have search string? */
+
+	      if (in_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) {
+	        num_devices = waveInGetNumDevs();
+	        for (n=0 ; n<num_devices && in_dev_no[a] == WAVE_MAPPER ; n++) {
+	          if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
+	            if (strstr(wic.szPname, pa->adev[a].adevice_in) != NULL) {
+	              in_dev_no[a] = n;
+	            }
+	          }
+	        }
+	        if (in_dev_no[a] == WAVE_MAPPER) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in);
+	        }
+	      }
+ 	    }
+
+/*
+ * Select output device.
+ * Only soundcard at this point.
+ * Purhaps we'd like to add UDP for an SDR transmitter.
+ */
+	    if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) {
+	      out_dev_no[a] = atoi(pa->adev[a].adevice_out);
+	    }
+
+	    if (out_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
+	      num_devices = waveOutGetNumDevs();
+	      for (n=0 ; n<num_devices && out_dev_no[a] == WAVE_MAPPER ; n++) {
+	        if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
+	          if (strstr(woc.szPname, pa->adev[a].adevice_out) != NULL) {
+	            out_dev_no[a] = n;
+	          }
+	        }
+	      }
+	      if (out_dev_no[a] == WAVE_MAPPER) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out);
+	      }
+	    }
+	  }   /* if defined */
+	}    /* for each device */
+
+
+/*
+ * Display the input devices (soundcards) available and what is selected.
+ */
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("Available audio input devices for receive (*=selected):\n");
+
+	num_devices = waveInGetNumDevs();
+
+        for (a=0; a<MAX_ADEVS; a++) {
+          if (pa->adev[a].defined) {
+
+	    if (in_dev_no[a] < -1 || in_dev_no[a] >= num_devices) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Invalid input (receive) audio device number %d.\n", in_dev_no[a]);
+	      in_dev_no[a] = WAVE_MAPPER;
+	    }
+	  }
+        }
+
+	text_color_set(DW_COLOR_INFO);
+	for (n=0; n<num_devices; n++) {
+
+	  if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
+	    for (a=0; a<MAX_ADEVS; a++) {
+	      if (pa->adev[a].defined) {
+	        dw_printf (" %c", n==in_dev_no[a] ? '*' : ' ');
+
+	      }
+	    }
+	    dw_printf ("  %d: %s", n, wic.szPname);
+
+	    for (a=0; a<MAX_ADEVS; a++) {
+	      if (pa->adev[a].defined && n==in_dev_no[a]) {
+	        if (pa->adev[a].num_channels == 2) {
+	          dw_printf ("   (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
+	        }
+	        else {
+	          dw_printf ("   (channel %d)", ADEVFIRSTCHAN(a));
+	        }
+	      }
+	    }
+	    dw_printf ("\n");
+ 	  }
+    	}
+
+// Add UDP or stdin to end of device list if used.
+
+    	for (a=0; a<MAX_ADEVS; a++) {
+      	  if (pa->adev[a].defined) {
+
+            struct adev_s *A = &(adev[a]);
+
+	    /* Display stdin or udp:port if appropriate. */   
+
+	    if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) {
+
+	      int aaa;
+	      for (aaa=0; aaa<MAX_ADEVS; aaa++) {
+	        if (pa->adev[aaa].defined) {
+	          dw_printf (" %c", a == aaa ? '*' : ' ');
+
+	        }
+	      }
+	      dw_printf ("  %s                             ", pa->adev[a].adevice_in);	/* should be UDP:nnnn or stdin */
+
+	      if (pa->adev[a].num_channels == 2) {
+	        dw_printf ("   (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
+	      }
+	      else {
+	        dw_printf ("   (channel %d)", ADEVFIRSTCHAN(a));
+	      }
+	      dw_printf ("\n");
+	    }
+      	  }
+     	}
+
+
+/*
+ * Display the output devices (soundcards) available and what is selected.
+ */
+
+	dw_printf ("Available audio output devices for transmit (*=selected):\n");
+
+	/* TODO? */
+	/* No "*" is currently displayed when using the default device. */
+	/* Should we put "*" next to the default device when using it? */
+	/* Which is the default?  The first one? */
+
+	num_devices = waveOutGetNumDevs();
+
+        for (a=0; a<MAX_ADEVS; a++) {
+          if (pa->adev[a].defined) {
+	    if (out_dev_no[a] < -1 || out_dev_no[a] >= num_devices) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Invalid output (transmit) audio device number %d.\n", out_dev_no[a]);
+	      out_dev_no[a] = WAVE_MAPPER;
+	    }
+	  }
+	}
+
+	text_color_set(DW_COLOR_INFO);
+	for (n=0; n<num_devices; n++) {
+
+	  if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
+	    for (a=0; a<MAX_ADEVS; a++) {
+	      if (pa->adev[a].defined) {
+	        dw_printf (" %c", n==out_dev_no[a] ? '*' : ' ');
+
+	      }
+	    }
+	    dw_printf ("  %d: %s", n, woc.szPname);
+
+	    for (a=0; a<MAX_ADEVS; a++) {
+	      if (pa->adev[a].defined && n==out_dev_no[a]) {
+	        if (pa->adev[a].num_channels == 2) {
+	          dw_printf ("   (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
+	        }
+	        else {
+	          dw_printf ("   (channel %d)", ADEVFIRSTCHAN(a));
+	        }
+	      }
+	    }
+	    dw_printf ("\n");
+	  }
+	}
+
+
+/*
+ * Open for each audio device input/output pair.
+ */
+
+     	for (a=0; a<MAX_ADEVS; a++) {
+      	  if (pa->adev[a].defined) {
+
+            struct adev_s *A = &(adev[a]);
+
+	     WAVEFORMATEX wf;
+
+	     wf.wFormatTag = WAVE_FORMAT_PCM;
+	     wf.nChannels = pa -> adev[a].num_channels; 
+	     wf.nSamplesPerSec = pa -> adev[a].samples_per_sec;
+	     wf.wBitsPerSample = pa -> adev[a].bits_per_sample;
+	     wf.nBlockAlign = (wf.wBitsPerSample / 8) * wf.nChannels;
+	     wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec;
+	     wf.cbSize = 0;
+
+	     A->outbuf_size = calcbufsize(wf.nSamplesPerSec,wf.nChannels,wf.wBitsPerSample);
+
+
+/*
+ * Open the audio output device.
+ * Soundcard is only possibility at this time.
+ */
+
+	     err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION);
+	     if (err != MMSYSERR_NOERROR) {
+	       text_color_set(DW_COLOR_ERROR);
+	       dw_printf ("Could not open audio device for output.\n");
+	       return (-1);
+	     }
+	  
+
+/*
+ * Set up the output buffers.
+ * We use dwUser to indicate it is available for filling.
+ */
+
+	     memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr));
+
+	     for (n = 0; n < NUM_OUT_BUF; n++) {
+	       A->out_wavehdr[n].lpData = malloc(A->outbuf_size);
+	       A->out_wavehdr[n].dwUser = DWU_FILLING;	
+	       A->out_wavehdr[n].dwBufferLength = 0;
+	     }
+	     A->out_current = 0;			
+
+	
+/*
+ * Open audio input device.
+ * More possibilities here:  soundcard, UDP port, stdin.
+ */
+
+	     switch (A->g_audio_in_type) {
+
+/*
+ * Soundcard.
+ */
+	       case AUDIO_IN_TYPE_SOUNDCARD:
+
+	         InitializeCriticalSection (&(A->in_cs));
+
+	         err = waveInOpen (&(A->audio_in_handle), in_dev_no[a], &wf, (DWORD_PTR)in_callback, a, CALLBACK_FUNCTION);
+	         if (err != MMSYSERR_NOERROR) {
+	           text_color_set(DW_COLOR_ERROR);
+	           dw_printf ("Could not open audio device for input.\n");
+	           return (-1);
+	         }	  
+
+
+	         /*
+	          * Set up the input buffers.
+	          */
+
+	         memset ((void*)(A->in_wavehdr), 0, sizeof(A->in_wavehdr));
+
+	         for (n = 0; n < NUM_OUT_BUF; n++) {
+	           A->in_wavehdr[n].dwBufferLength = A->outbuf_size;  /* all the same size */
+	           A->in_wavehdr[n].lpData = malloc(A->outbuf_size);
+	         }
+	         A->in_headp = NULL;			
+
+	         /*
+	          * Give them to the sound input system.
+	          */
+	
+	         for (n = 0; n < NUM_OUT_BUF; n++) {
+	           waveInPrepareHeader(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR));
+	           waveInAddBuffer(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR));
+	         }
+
+	         /*
+	          * Start it up.
+	          * The callback function is called when one is filled.
+	          */
+
+	         waveInStart (A->audio_in_handle);
+	         break;
+
+/*
+ * UDP.
+ */
+	       case AUDIO_IN_TYPE_SDR_UDP:
+
+	         {
+	           WSADATA wsadata;
+	           struct sockaddr_in si_me;
+	           //int slen=sizeof(si_me);
+	           //int data_size = 0;
+	           int err;
+
+	           err = WSAStartup (MAKEWORD(2,2), &wsadata);
+	           if (err != 0) {
+	               text_color_set(DW_COLOR_ERROR);
+	               dw_printf("WSAStartup failed: %d\n", err);
+	               return (-1);
+	           }
+
+	           if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+	             text_color_set(DW_COLOR_ERROR);
+                     dw_printf("Could not find a usable version of Winsock.dll\n");
+                     WSACleanup();
+                     return (-1);
+	           }
+
+	           // Create UDP Socket
+
+	           A->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	           if (A->udp_sock == INVALID_SOCKET) {
+	             text_color_set(DW_COLOR_ERROR);
+	             dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError());
+	             return -1;
+	           }
+
+	           memset((char *) &si_me, 0, sizeof(si_me));
+	           si_me.sin_family = AF_INET;   
+	           si_me.sin_port = htons((short)atoi(pa->adev[a].adevice_in + 4));
+	           si_me.sin_addr.s_addr = htonl(INADDR_ANY);
+
+	           // Bind to the socket
+
+	           if (bind(A->udp_sock, (SOCKADDR *) &si_me, sizeof(si_me)) != 0) {
+	             text_color_set(DW_COLOR_ERROR);
+	             dw_printf ("Couldn't bind socket, errno %d\n", WSAGetLastError());
+	             return -1;
+	           }
+	           A->stream_next= 0;
+	           A->stream_len = 0;
+	         }
+
+	         break;
+
+/* 
+ * stdin.
+ */
+   	       case AUDIO_IN_TYPE_STDIN:
+
+  	         setmode (STDIN_FILENO, _O_BINARY);
+	         A->stream_next= 0;
+	         A->stream_len = 0;
+
+	         break;
+
+	       default:
+
+	         text_color_set(DW_COLOR_ERROR);
+	         dw_printf ("Internal error, invalid audio_in_type\n");
+	         return (-1);
+  	     }
+
+	  }
+    	}
+
+	return (0);
+
+} /* end audio_open */
+
+
+
+/*
+ * Called when input audio block is ready.
+ */
+
+static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2)
+{
+
+	int a = instance;
+
+//dw_printf ("in_callback, handle = %d, a = %d\n", (int)handle, a);
+
+	assert (a >= 0 && a < MAX_ADEVS);
+	struct adev_s *A = &(adev[a]);
+
+
+	if (msg == WIM_DATA) {
+	
+	  WAVEHDR *p = (WAVEHDR*)param1;
+	  
+	  p->dwUser = -1;		/* needs to be unprepared. */
+	  p->lpNext = NULL;
+
+	  EnterCriticalSection (&(A->in_cs));
+
+	  if (A->in_headp == NULL) {
+	    A->in_headp = p;		/* first one in list */
+	  }
+	  else {
+	    WAVEHDR *last = (WAVEHDR*)(A->in_headp);
+
+	    while (last->lpNext != NULL) {
+	      last = last->lpNext;
+	    }
+	    last->lpNext = p;		/* append to last one */
+	  }
+
+	  LeaveCriticalSection (&(A->in_cs));
+	}
+}
+
+/*
+ * Called when output system is done with a block and it
+ * is again available for us to fill.
+ */
+
+
+static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2)
+{
+	if (msg == WOM_DONE) {   
+
+	  WAVEHDR *p = (WAVEHDR*)param1;
+	  
+	  p->dwBufferLength = 0;
+	  p->dwUser = DWU_DONE;
+	}
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_get
+ *
+ * Purpose:     Get one byte from the audio device.
+ *
+ *
+ * Inputs:	a	- Audio soundcard number.
+ *
+ * Returns:     0 - 255 for a valid sample.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ *		This will wait if no data is currently available.
+ *
+ *----------------------------------------------------------------*/
+
+// Use hot attribute for all functions called for every audio sample.
+
+__attribute__((hot))
+int audio_get (int a)
+{
+	struct adev_s *A;
+ 
+	WAVEHDR *p;
+	int n;
+	int sample;
+
+        A = &(adev[a]); 
+
+	switch (A->g_audio_in_type) {
+
+/*
+ * Soundcard.
+ */
+	  case AUDIO_IN_TYPE_SOUNDCARD:
+
+	    while (1) {
+
+	      /*
+	       * Wait if nothing available.
+	       * Could use an event to wake up but this is adequate.
+	       */
+	      int timeout = 25;
+
+	      while (A->in_headp == NULL) {
+	        //SLEEP_MS (ONE_BUF_TIME / 5);
+	        SLEEP_MS (ONE_BUF_TIME);
+	        timeout--;
+	        if (timeout <= 0) {
+	          text_color_set(DW_COLOR_ERROR);
+
+// TODO1.2: Need more details.  Can we keep going?
+
+	          dw_printf ("Timeout waiting for input from audio device %d.\n", a);
+
+	          audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			0, 
+			save_audio_config_p->statistics_interval);
+
+	          return (-1);
+	        }
+	      }
+
+	      p = (WAVEHDR*)(A->in_headp);		/* no need to be volatile at this point */
+
+	      if (p->dwUser == -1) {
+	        waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
+	        p->dwUser = 0;	/* Index for next byte. */
+
+	        audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			p->dwBytesRecorded / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+			save_audio_config_p->statistics_interval);
+	      }
+
+	      if (p->dwUser < p->dwBytesRecorded) {
+	        n = ((unsigned char*)(p->lpData))[p->dwUser++];
+#if DEBUGx
+
+	        text_color_set(DW_COLOR_DEBUG);
+	        dw_printf ("audio_get(): returns %d\n", n);
+
+#endif
+	        return (n);
+	      }
+	      /*
+	       * Buffer is all used up.  Give it back to sound input system.
+	       */
+
+	      EnterCriticalSection (&(A->in_cs));
+	      A->in_headp = p->lpNext;
+	      LeaveCriticalSection (&(A->in_cs));
+
+	      p->dwFlags = 0;
+	      waveInPrepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
+	      waveInAddBuffer(A->audio_in_handle, p, sizeof(WAVEHDR));	  
+	    }
+	    break;
+/*
+ * UDP.
+ */
+	  case AUDIO_IN_TYPE_SDR_UDP:
+
+	    while (A->stream_next >= A->stream_len) {
+	      int res;
+
+              assert (A->udp_sock > 0);
+
+	      res = recv (A->udp_sock, A->stream_data, SDR_UDP_BUF_MAXLEN, 0);
+	      if (res <= 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError());
+	        A->stream_len = 0;
+	        A->stream_next = 0;
+
+	        audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			0, 
+			save_audio_config_p->statistics_interval);
+
+	        return (-1);
+	      } 
+
+	      audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+			save_audio_config_p->statistics_interval);
+
+	      A->stream_len = res;
+	      A->stream_next = 0;
+	    }
+	    sample = A->stream_data[A->stream_next] & 0xff;
+	    A->stream_next++;
+	    return (sample);
+	    break;
+/* 
+ * stdin.
+ */
+   	  case AUDIO_IN_TYPE_STDIN:
+
+	    while (A->stream_next >= A->stream_len) {
+	      int res;
+
+	      res = read(STDIN_FILENO, A->stream_data, 1024);
+	      if (res <= 0) {
+	        text_color_set(DW_COLOR_INFO);
+	        dw_printf ("\nEnd of file on stdin.  Exiting.\n");
+	        exit (0);
+	      }
+
+	      audio_stats (a, 
+			save_audio_config_p->adev[a].num_channels, 
+			res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), 
+			save_audio_config_p->statistics_interval);
+	    
+	      A->stream_len = res;
+	      A->stream_next = 0;
+	    }
+	    return (A->stream_data[A->stream_next++] & 0xff);
+	    break;
+  	}
+
+	return (-1);
+
+} /* end audio_get */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_put
+ *
+ * Purpose:     Send one byte to the audio device.
+ *
+ * Inputs:	a	- Index for audio device.
+ *
+ *		c	- One byte in range of 0 - 255.
+ *
+ *
+ * Global In:	out_current	- index of output buffer currenly being filled.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ * See Also:	audio_flush
+ *		audio_wait
+ *
+ *----------------------------------------------------------------*/
+
+int audio_put (int a, int c)
+{
+	WAVEHDR *p;
+
+	struct adev_s *A;
+	A = &(adev[a]); 
+	
+/* 
+ * Wait if no buffers are available.
+ * Don't use p yet because compiler might might consider dwFlags a loop invariant. 
+ */
+
+	int timeout = 10;
+	while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) {
+	  SLEEP_MS (ONE_BUF_TIME);
+	  timeout--;
+	  if (timeout <= 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Audio output failure waiting for buffer.\n");
+	    ptt_term ();
+	    return (-1);
+	  }
+	}
+
+	p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
+
+	if (p->dwUser == DWU_DONE) {
+	  waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR));
+	  p->dwBufferLength = 0;
+	  p->dwUser = DWU_FILLING;
+	}
+
+	/* Should never be full at this point. */
+
+	assert (p->dwBufferLength >= 0);
+	assert (p->dwBufferLength < A->outbuf_size);
+
+	p->lpData[p->dwBufferLength++] = c;
+
+	if (p->dwBufferLength == A->outbuf_size) {
+	  return (audio_flush(a));
+	}
+
+	return (0);
+
+} /* end audio_put */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_flush
+ *
+ * Purpose:     Send current buffer to the audio output system.
+ *
+ * Inputs:	a	- Index for audio device.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * See Also:	audio_flush
+ *		audio_wait
+ *
+ *----------------------------------------------------------------*/
+
+int audio_flush (int a)
+{
+	WAVEHDR *p;
+	MMRESULT e;
+	struct adev_s *A;
+
+	A = &(adev[a]); 
+	
+	p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
+
+	if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) {
+
+	  p->dwUser = DWU_PLAYING;
+
+	  waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR));
+
+	  e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR));
+	  if (e != MMSYSERR_NOERROR) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("audio out write error %d\n", e);
+
+	    /* I don't expect this to ever happen but if it */
+	    /* does, make the buffer available for filling. */
+
+	    p->dwUser = DWU_DONE;
+	    return (-1);
+	  }
+	  A->out_current = (A->out_current + 1) % NUM_OUT_BUF;
+	}
+	return (0);
+
+} /* end audio_flush */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_wait
+ *
+ * Purpose:	Finish up audio output before turning PTT off.
+ *
+ * Inputs:	a		- Index for audio device (not channel!)
+ *
+ * Returns:     None.
+ *
+ * Description:	Flush out any partially filled audio output buffer.
+ *		Wait until all the queued up audio out has been played.
+ *		Take any other necessary actions to stop audio output.
+ *
+ * In an ideal world:
+ * 
+ *		We would like to ask the hardware when all the queued
+ *		up sound has actually come out the speaker.
+ *
+ * In reality:
+ *
+ * 		This has been found to be less than reliable in practice.
+ *
+ *		Caller does the following:
+ *
+ *		(1) Make note of when PTT is turned on.
+ *		(2) Calculate how long it will take to transmit the 
+ *			frame including TXDELAY, frame (including 
+ *			"flags", data, FCS and bit stuffing), and TXTAIL.
+ *		(3) Call this function, which might or might not wait long enough.
+ *		(4) Add (1) and (2) resulting in when PTT should be turned off.
+ *		(5) Take difference between current time and desired PPT off time
+ *			and wait for additoinal time if required.
+ *
+ *----------------------------------------------------------------*/
+
+void audio_wait (int a)
+{	
+
+	audio_flush (a);
+
+} /* end audio_wait */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_close
+ *
+ *
+ * Purpose:     Close all of the audio devices.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ *
+ *----------------------------------------------------------------*/
+
+int audio_close (void)
+{
 	int err = 0;
-
-	int n;
-
-	int a;
-
-    	for (a=0; a<MAX_ADEVS; a++) {
-      	  if (save_audio_config_p->adev[a].defined) {
-
-            struct adev_s *A = &(adev[a]);
-
-	    assert (A->audio_in_handle != 0);
-	    assert (A->audio_out_handle != 0);
-
-	    audio_wait (a);
-
-/* Shutdown audio input. */
-
-	    waveInReset(A->audio_in_handle); 
-	    waveInStop(A->audio_in_handle);
-	    waveInClose(A->audio_in_handle);
-	    A->audio_in_handle = 0;
-
-	    for (n = 0; n < NUM_IN_BUF; n++) {
-
-	      waveInUnprepareHeader (A->audio_in_handle, (LPWAVEHDR)(&(A->in_wavehdr[n])), sizeof(WAVEHDR));
-	      A->in_wavehdr[n].dwFlags = 0;
-	      free (A->in_wavehdr[n].lpData);
- 	      A->in_wavehdr[n].lpData = NULL;
-	    }
-
-	    DeleteCriticalSection (&(A->in_cs));
-
-
-/* Make sure all output buffers have been played then free them. */
-
-	    for (n = 0; n < NUM_OUT_BUF; n++) {
-	      if (A->out_wavehdr[n].dwUser == DWU_PLAYING) {
-
-	        int timeout = 2 * NUM_OUT_BUF;
-	        while (A->out_wavehdr[n].dwUser == DWU_PLAYING) {
-	          SLEEP_MS (ONE_BUF_TIME);
-	          timeout--;
-	          if (timeout <= 0) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Audio output failure on close.\n");
-	          }
-	        }
-
-	        waveOutUnprepareHeader (A->audio_out_handle, (LPWAVEHDR)(&(A->out_wavehdr[n])), sizeof(WAVEHDR));
-
-	        A->out_wavehdr[n].dwUser = DWU_DONE;
-	      }
-	      free (A->out_wavehdr[n].lpData);
- 	      A->out_wavehdr[n].lpData = NULL;
-	    }
-
-	    waveOutClose (A->audio_out_handle);
-	    A->audio_out_handle = 0;
-
-          }  /* if device configured */
-        }  /* for each device. */
-
-        /* Not right.  always returns 0 but at this point, doesn't matter. */
-
-	return (err);
-
-} /* end audio_close */
-
-/* end audio_win.c */
-
+
+	int n;
+
+	int a;
+
+    	for (a=0; a<MAX_ADEVS; a++) {
+      	  if (save_audio_config_p->adev[a].defined) {
+
+            struct adev_s *A = &(adev[a]);
+
+	    assert (A->audio_in_handle != 0);
+	    assert (A->audio_out_handle != 0);
+
+	    audio_wait (a);
+
+/* Shutdown audio input. */
+
+	    waveInReset(A->audio_in_handle); 
+	    waveInStop(A->audio_in_handle);
+	    waveInClose(A->audio_in_handle);
+	    A->audio_in_handle = 0;
+
+	    for (n = 0; n < NUM_IN_BUF; n++) {
+
+	      waveInUnprepareHeader (A->audio_in_handle, (LPWAVEHDR)(&(A->in_wavehdr[n])), sizeof(WAVEHDR));
+	      A->in_wavehdr[n].dwFlags = 0;
+	      free (A->in_wavehdr[n].lpData);
+ 	      A->in_wavehdr[n].lpData = NULL;
+	    }
+
+	    DeleteCriticalSection (&(A->in_cs));
+
+
+/* Make sure all output buffers have been played then free them. */
+
+	    for (n = 0; n < NUM_OUT_BUF; n++) {
+	      if (A->out_wavehdr[n].dwUser == DWU_PLAYING) {
+
+	        int timeout = 2 * NUM_OUT_BUF;
+	        while (A->out_wavehdr[n].dwUser == DWU_PLAYING) {
+	          SLEEP_MS (ONE_BUF_TIME);
+	          timeout--;
+	          if (timeout <= 0) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Audio output failure on close.\n");
+	          }
+	        }
+
+	        waveOutUnprepareHeader (A->audio_out_handle, (LPWAVEHDR)(&(A->out_wavehdr[n])), sizeof(WAVEHDR));
+
+	        A->out_wavehdr[n].dwUser = DWU_DONE;
+	      }
+	      free (A->out_wavehdr[n].lpData);
+ 	      A->out_wavehdr[n].lpData = NULL;
+	    }
+
+	    waveOutClose (A->audio_out_handle);
+	    A->audio_out_handle = 0;
+
+          }  /* if device configured */
+        }  /* for each device. */
+
+        /* Not right.  always returns 0 but at this point, doesn't matter. */
+
+	return (err);
+
+} /* end audio_close */
+
+/* end audio_win.c */
+
diff --git a/ax25_pad.c b/ax25_pad.c
index 277f138..37a9508 100644
--- a/ax25_pad.c
+++ b/ax25_pad.c
@@ -1,2181 +1,2454 @@
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011 , 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Name:	ax25_pad
- *
- * Purpose:	Packet assembler and disasembler.
- *
- *   		We can obtain AX.25 packets from different sources:
- *		
- *		(a) from an HDLC frame.
- *		(b) from text representation.
- *		(c) built up piece by piece.
- *
- *		We also want to use a packet in different ways:
- *
- *		(a) transmit as an HDLC frame.
- *		(b) print in human-readable text.
- *		(c) take it apart piece by piece.
- *
- *		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.
- *
- *
- * Description:	
- *
- *
- *	A UI frame starts with 2-10 addressses (14-70 octets):
- *
- *	* Destination Address
- *	* Source Address
- *	* 0-8 Digipeater Addresses  (Could there ever be more as a result of 
- *					digipeaters inserting their own call
- *					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 only in text form, not here.)
- *
- *	Each address is composed of:
- *
- *	* 6 upper case letters or digits, blank padded.
- *		These are shifted left one bit, leaving the the LSB always 0.
- *	* a 7th octet containing the SSID and flags.
- *		The LSB is always 0 except for the last octet of the address field.
- *
- *	The final octet of the Destination has the form:
- *
- *		C R R SSID 0, where,
- *
- *			C = command/response = 1
- *			R R = Reserved = 1 1
- *			SSID = substation ID
- *			0 = zero
- *
- *	The final octet of the Source has the form:
- *
- *		C R R SSID 0, where,
- *
- *			C = command/response = 1
- *			R R = Reserved = 1 1
- *			SSID = substation ID
- *			0 = zero (or 1 if no repeaters)
- *
- *	The final octet of each repeater has the form:
- *
- *		H R R SSID 0, where,
- *
- *			H = has-been-repeated = 0 initially.  
- *				Set to 1 after this address has been used.
- *			R R = Reserved = 1 1
- *			SSID = substation ID
- *			0 = zero (or 1 if last repeater in list)
- *
- *		A digipeater would repeat this frame if it finds its address
- *		with the "H" bit set to 0 and all earlier repeater addresses
- *		have the "H" bit set to 1.  
- *		The "H" bit would be set to 1 in the repeated frame.
- *
- *	When monitoring, an asterisk is displayed after the last digipeater with 
- *	the "H" bit set.  No asterisk means the source is being heard directly.
- *
- *	Example, if we can hear all stations involved,
- *
- *		SRC>DST,RPT1,RPT2,RPT3:		-- we heard SRC
- *		SRC>DST,RPT1*,RPT2,RPT3:	-- we heard RPT1
- *		SRC>DST,RPT1,RPT2*,RPT3:	-- we heard RPT2
- *		SRC>DST,RPT1,RPT2,RPT3*:	-- we heard RPT3
- *
- *	
- *	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.
- *
- *	And, of course, the 2 byte CRC.
- *
- *
- * Constructors: ax25_init		- Clear everything.
- *		ax25_from_text		- Tear apart a text string
- *		ax25_from_frame		- Tear apart an AX.25 frame.  
- *					  Must be called before any other function.
- *
- * Get methods:	....			- Extract destination, source, or digipeater
- *					  address from frame.
- *
- * Assumptions:	CRC has already been verified to be correct.
- *
- *------------------------------------------------------------------*/
-
-#define AX25_PAD_C		/* this will affect behavior of ax25_pad.h */
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdio.h>
-#include <ctype.h>
-#ifndef _POSIX_C_SOURCE
-
-#define _POSIX_C_SOURCE 1
-#endif
-
-#include "regex.h"
-
-#if __WIN32__
-char *strtok_r(char *str, const char *delim, char **saveptr);
-#endif
-
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "fcs_calc.h"
-
-/*
- * Accumulate statistics.
- * If new_count gets much larger than delete_count plus the size of 
- * the transmit queue we have a memory leak.
- */
-
-static volatile int new_count = 0;
-static volatile int delete_count = 0;
-static volatile int last_seq_num = 0;
-
-#if AX25MEMDEBUG
-
-int ax25memdebug = 0;
-
-
-void ax25memdebug_set(void) 
-{
-	ax25memdebug = 1;
-}
-
-int ax25memdebug_get (void)
-{
-	return (ax25memdebug);
-}
-
-int ax25memdebug_seq (packet_t this_p)
-{
-	return (this_p->seq);
-}
-
-
-#endif
-
-
-
-#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
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_new
- * 
- * Purpose:	Allocate memory for a new packet object.
- *
- * Returns:	Identifier for a new packet object.
- *		In the current implementation this happens to be a pointer.
- *
- *------------------------------------------------------------------------------*/
-
-
-static packet_t ax25_new (void) 
-{
-	struct packet_s *this_p;
-
-
-#if DEBUG 
-        text_color_set(DW_COLOR_DEBUG);
-        dw_printf ("ax25_new(): before alloc, new=%d, delete=%d\n", new_count, delete_count);
-#endif
-
-	last_seq_num++;
-	new_count++;
-
-/*
- * check for memory leak.
- */
-	if (new_count > delete_count + 100) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Report to WB2OSZ - Memory leak for packet objects.  new=%d, delete=%d\n", new_count, delete_count);
-#if AX25MEMDEBUG
-	  // Force on debug option to gather evidence.
-	  ax25memdebug_set();
-#endif
-	}
-
-	this_p = calloc(sizeof (struct packet_s), (size_t)1);
-	this_p->magic1 = MAGIC;
-	this_p->seq = last_seq_num;
-	this_p->magic2 = MAGIC;
-	this_p->num_addr = (-1);
-	return (this_p);
-}
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_delete
- * 
- * Purpose:	Destroy a packet object, freeing up memory it was using.
- *
- *------------------------------------------------------------------------------*/
-
-#if AX25MEMDEBUG
-void ax25_delete_debug (packet_t this_p, char *src_file, int src_line)
-#else
-void ax25_delete (packet_t this_p)
-#endif
-{
-#if DEBUG
-        text_color_set(DW_COLOR_DEBUG);
-        dw_printf ("ax25_delete(): before free, new=%d, delete=%d\n", new_count, delete_count);
-#endif
-
-	delete_count++;
-
-#if AX25MEMDEBUG	
-	if (ax25memdebug) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("ax25_delete, seq=%d, called from %s %d, new_count=%d, delete_count=%d\n", this_p->seq, src_file, src_line, new_count, delete_count);
-	}
-#endif
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-	
-	this_p->magic1 = 0;
-	this_p->magic1 = 0;
-
-	//memset (this_p, 0, sizeof (struct packet_s));
-	free (this_p);
-}
-
-
-		
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_from_text
- * 
- * Purpose:	Parse a frame in human-readable monitoring format and change
- *		to internal representation.
- *
- * Input:	monitor	- "TNC-2" format of a monitored packet.  i.e.
- *				source>dest[,repeater1,repeater2,...]:information
- *
- *		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.
- *
- *------------------------------------------------------------------------------*/
-
-#if AX25MEMDEBUG
-packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line)
-#else
-packet_t ax25_from_text (char *monitor, int strict)
-#endif
-{
-
-/*
- * Tearing it apart is destructive so make our own copy first.
- */
-	char stuff[512];
-
-	char *pinfo;
-	char *pa;
-	char *saveptr;		/* Used with strtok_r because strtok is not thread safe. */
-
-	static int first_time = 1;
-	static regex_t unhex_re;
-	int e;
-	char emsg[100];
-#define MAXMATCH 1
-	regmatch_t match[MAXMATCH];
-	int keep_going;
-	char temp[512];
-	int ssid_temp, heard_temp;
-	char atemp[AX25_MAX_ADDR_LEN];
-
-
-	packet_t this_p = ax25_new ();
-
-#if AX25MEMDEBUG	
-	if (ax25memdebug) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("ax25_from_text, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
-	}
-#endif
-
-	/* 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);
-
-/* 
- * Translate hexadecimal values like <0xff> to non-printing characters.
- * MIC-E message type uses 5 different non-printing characters.
- */
-
-	if (first_time) 
-	{
-	  e = regcomp (&unhex_re, "<0x[0-9a-fA-F][0-9a-fA-F]>", 0);
-	  if (e) {
-	    regerror (e, &unhex_re, emsg, sizeof(emsg));
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("%s:%d: %s\n", __FILE__, __LINE__, emsg);
-	  }
-
-	  first_time = 0;
-	}
-
-#if 0
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("BEFORE: %s\n", stuff);
-	ax25_safe_print (stuff, -1, 0);
-	dw_printf ("\n");
-#endif
-	keep_going = 1;
-	while (keep_going) {
-	  if (regexec (&unhex_re, stuff, MAXMATCH, match, 0) == 0) {
-	    int n;
-	    char *p;
-  
-	    stuff[match[0].rm_so + 5] = '\0';
-	    n = strtol (stuff + match[0].rm_so + 3, &p, 16);
-	    stuff[match[0].rm_so] = n;
-	    strcpy (temp, stuff + match[0].rm_eo);
-	    strcpy (stuff + match[0].rm_so + 1, temp);
-	  }
-	  else {
-	    keep_going = 0;
-	  }
-	}
-#if 0
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("AFTER:  %s\n", stuff);
-	ax25_safe_print (stuff, -1, 0);
-	dw_printf ("\n");
-#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, ':');
-
-	if (pinfo == NULL) {
-	  ax25_delete (this_p);
-	  return (NULL);
-	}
-
-	*pinfo = '\0';
-	pinfo++;
-
-	if (strlen(pinfo) > AX25_MAX_INFO_LEN) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Warning: Information part truncated to %d characters.\n", AX25_MAX_INFO_LEN);
-	  pinfo[AX25_MAX_INFO_LEN] = '\0';
-	}
-	
-/*
- * Separate the addresses.
- * Note that source and destination order is swappped.
- */
-
-/*
- * Source address.
- * Don't use traditional strtok because it is not thread safe.
- */
-	pa = strtok_r (stuff, ">", &saveptr);
-	if (pa == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Failed to create packet from text.  No source address\n");
-	  ax25_delete (this_p);
-	  return (NULL);
-	}
-
-	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);
-	}
-
-	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);
-
-/*
- * Destination address.
- */
- 
-	pa = strtok_r (NULL, ",", &saveptr);
-	if (pa == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Failed to create packet from text.  No destination address\n");
-	  ax25_delete (this_p);
-	  return (NULL);
-	}
-
-	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);
-	}
-
-	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);
-
-/*
- * VIA path.
- */
-	while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) {
-
-	  //char *last;
-	  int k;
-
-	  k = this_p->num_addr;
-
-	  // JWL 10:38 this_p->num_addr++;
-
-	  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);
-	  }
-
-	  ax25_set_addr (this_p, k, pa);
-	  
-	  ax25_set_ssid (this_p, k, ssid_temp);
-
-	  // Does it have an "*" at the end? 
-	  // TODO: Complain if more than one "*".
-	  // Could also check for all has been repeated bits are adjacent.
-	
-          if (heard_temp) {
-	    for ( ; k >= AX25_REPEATER_1; k--) {
-	      ax25_set_h (this_p, k);
-	    }
-	  }
-        }
-
-/*
- * Append the info part.  
- */
-	strcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo);
-	this_p->frame_len += strlen(pinfo);
-
-
-	return (this_p);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_from_frame
- * 
- * Purpose:	Split apart an HDLC frame to components.
- *
- * Inputs:	fbuf	- Pointer to beginning of frame.
- *
- *		flen	- Length excluding the two FCS bytes.
- *
- *		alevel	- Audio level of received signal.  
- *			  Maximum range 0 - 100.
- *			  -1 might be used when not applicable.
- *
- * Returns:	Pointer to new packet object or NULL if error.
- *
- * Outputs:	Use the "get" functions to retrieve information in different ways.
- *
- *------------------------------------------------------------------------------*/
-
-#if AX25MEMDEBUG
-packet_t ax25_from_frame_debug (unsigned char *fbuf, int flen, alevel_t alevel, char *src_file, int src_line)
-#else
-packet_t ax25_from_frame (unsigned char *fbuf, int flen, alevel_t alevel)
-#endif
-{
-	unsigned char *pf;
-	packet_t this_p;
-
-	int a;
-	int addr_bytes;
-	
-/*
- * First make sure we have an acceptable length:
- *
- *	We are not concerned with the FCS (CRC) because someone else checked it.
- *
- * 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)
-	{
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Frame length %d not in allowable range of %d to %d.\n", flen, AX25_MIN_PACKET_LEN, AX25_MAX_PACKET_LEN);
-	  return (NULL);
-	}
-
-	this_p = ax25_new ();
-
-#if AX25MEMDEBUG	
-	if (ax25memdebug) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("ax25_from_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
-	}
-#endif
-
-/* Copy the whole thing intact. */
-
-	memcpy (this_p->frame_data, fbuf, flen);
-	this_p->frame_data[flen] = 0;
-	this_p->frame_len = flen;
-
-/* Find number of addresses. */
-	
-	this_p->num_addr = (-1);
-	(void) ax25_get_num_addr (this_p);
-
-	return (this_p);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_dup
- * 
- * Purpose:	Make a copy of given packet object.
- *
- * Inputs:	copy_from	- Existing packet object.
- *
- * Returns:	Pointer to new packet object or NULL if error.
- *
- *
- *------------------------------------------------------------------------------*/
-
-
-#if AX25MEMDEBUG
-packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line)
-#else
-packet_t ax25_dup (packet_t copy_from)
-#endif
-{
-	int save_seq;
-	packet_t this_p;
-
-	
-	this_p = ax25_new ();
-	save_seq = this_p->seq;
-
-	memcpy (this_p, copy_from, sizeof (struct packet_s));
-	this_p->seq = save_seq;
-
-#if AX25MEMDEBUG
-	if (ax25memdebug) {	
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("ax25_dup, seq=%d, called from %s %d, clone of seq %d\n", this_p->seq, src_file, src_line, copy_from->seq);
-	}
-#endif
-
-	return (this_p);
-
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_parse_addr
- * 
- * Purpose:	Parse address with optional ssid.
- *
- * Inputs:	in_addr		- Input such as "WB2OSZ-15*"
- *
- * 		strict		- TRUE for strict checking (6 characters, no lower case,
- *				  SSID must be in range of 0 to 15).
- *				  Strict is appropriate for packets sent
- *				  over the radio.  Communication with IGate
- *				  allows lower case (e.g. "qAR") and two 
- *				  alphanumeric characters for the SSID.
- *				  We also get messages like this from a server.
- *					KB1POR>APU25N,TCPIP*,qAC,T2NUENGLD:...
- *
- * Outputs:	out_addr	- Address without any SSID.
- *				  Must be at least AX25_MAX_ADDR_LEN bytes.
- *
- *		out_ssid	- Numeric value of SSID.
- *
- *		out_heard	- True if "*" found.
- *
- * Returns:	True (1) if OK, false (0) if any error.
- *
- *
- *------------------------------------------------------------------------------*/
-
-
-int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard)
-{
-	char *p;
-	char sstr[4];
-	int i, j, k;
-	int maxlen;
-
-	strcpy (out_addr, "");
-	*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;
-	for (p = in_addr; isalnum(*p); p++) {
-	  if (i >= maxlen) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Address is too long. \"%s\" has more than %d characters.\n", in_addr, maxlen);
-	    return 0;
-	  }
-	  out_addr[i++] = *p;
-	  out_addr[i] = '\0';
-	  if (strict && islower(*p)) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Address has lower case letters. \"%s\" must be all upper case.\n", in_addr);
-	    return 0;
-	  }
-	}
-	
-	strcpy (sstr, "");
-	j = 0;
-	if (*p == '-') {
-	  for (p++; isalnum(*p); p++) {
-	    if (j >= 2) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("SSID is too long. SSID part of \"%s\" has more than 2 characters.\n", in_addr);
-	      return 0;
-	    }
-	    sstr[j++] = *p;
-	    sstr[j] = '\0';
-	    if (strict && ! isdigit(*p)) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("SSID must be digits. \"%s\" has letters in SSID.\n", in_addr);
-	      return 0;
-	    }
-	  }
-	  k = atoi(sstr);
-	  if (k < 0 || k > 15) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("SSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", in_addr);
-	    return 0;
-	  }
-	  *out_ssid = k;
-	}
-
-	if (*p == '*') {
-	  *out_heard = 1;
-	  p++;
-	}
-
-	if (*p != '\0') {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Invalid character \"%c\" found in address \"%s\".\n", *p, in_addr);
-	  return 0;
-	}
-
-	//dw_printf ("ax25_parse_addr out: %s %d %d\n", out_addr, *out_ssid, *out_heard);
-
-	return (1);
-
-} /* end ax25_parse_addr */
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_unwrap_third_party
- * 
- * Purpose:	Unwrap a third party messge from the header.
- *
- * Inputs:	copy_from	- Existing packet object.
- *
- * Returns:	Pointer to new packet object or NULL if error.
- *
- * Example:	Input:		A>B,C:}D>E,F:info
- *		Output:		D>E,F:info
- *
- *------------------------------------------------------------------------------*/
-
-packet_t ax25_unwrap_third_party (packet_t from_pp)
-{
-	unsigned char *info_p;
-	packet_t result_pp;
-
-	if (ax25_get_dti(from_pp) != '}') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error: ax25_unwrap_third_party: wrong data type.\n");
-	  return (NULL);
-	}
-
-	(void) ax25_get_info (from_pp, &info_p);
-
-	result_pp = ax25_from_text((char *)info_p + 1, 0);
-
-	return (result_pp);
-}
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_set_addr
- * 
- * Purpose:	Add or change an address.
- *
- * 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.
- *
- * 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);
-
-	//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) {		
-
-	  //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");
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_insert_addr
- * 
- * Purpose:	Insert address at specified position, shifting others up one
- *		position.
- *		This is used when a digipeater wants to insert its own call
- *		for tracing purposes.
- *		For example:
- *			W1ABC>TEST,WIDE3-3
- *		Would become:
- *			W1ABC>TEST,WB2OSZ-1*,WIDE3-2
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
- *
- *		ad	- Address with optional dash and substation id.
- *
- * Bugs:	Little validity or bounds checking is performed.  Be careful.
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	None.
- *		
- *
- *------------------------------------------------------------------------------*/
-
-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. */
-
-	if ( this_p->num_addr >= AX25_MAX_ADDRS) {
-	  return;
-	}
-
-	CLEAR_LAST_ADDR_FLAG;
-
-	this_p->num_addr++;
-
-	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);
-	}
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_remove_addr
- * 
- * Purpose:	Remove address at specified position, shifting others down one position.
- *		This is used when we want to remove something from the digipeater list.
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			  AX25_REPEATER1, AX25_REPEATER2, etc.
- *
- * Bugs:	Little validity or bounds checking is performed.  Be careful.
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	None.
- *		
- *
- *------------------------------------------------------------------------------*/
-
-void ax25_remove_addr (packet_t this_p, int n)
-{
-	int k;
-	int expect; 
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-	assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS);
-
-	/* Shift those beyond to fill this position. */
-
-	CLEAR_LAST_ADDR_FLAG;
-
-	this_p->num_addr--;
-
-	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);
-	}
-
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_num_addr
- * 
- * Purpose:	Return number of addresses in current packet.
- *
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	Number of addresses in the current packet.
- *		Should be in the range of 2 .. AX25_MAX_ADDRS.
- *
- * Version 0.9:	Could be zero for a non AX.25 frame in KISS mode.
- *
- *------------------------------------------------------------------------------*/
-
-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);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_num_repeaters
- * 
- * Purpose:	Return number of repeater addresses in current packet.
- *
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	Number of addresses in the current packet - 2.
- *		Should be in the range of 0 .. AX25_MAX_ADDRS - 2.
- *
- *------------------------------------------------------------------------------*/
-
-int ax25_get_num_repeaters (packet_t this_p)
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	if (this_p->num_addr >= 2) {
-	  return (this_p->num_addr - 2);
-	}
-
-	return (0);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_addr_with_ssid
- * 
- * Purpose:	Return specified address with any SSID in current packet.
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
- *
- * Outputs:	station - String representation of the station, including the SSID.
- *			e.g.  "WB2OSZ-15"
- *
- * Bugs:	No bounds checking is performed.  Be careful.
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	Character string in usual human readable format,
- *		
- *
- *------------------------------------------------------------------------------*/
-
-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);
-
-
-	if (n < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
-	  dw_printf ("Address index, %d, is less than zero.\n", n);
-	  strcpy (station, "??????");
-	  return;
-	}
-
-	if (n >= this_p->num_addr) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
-	  dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr);
-	  strcpy (station, "??????");
-	  return;
-	}
-
-	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);
-	}   
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_ssid
- * 
- * Purpose:	Return SSID of specified address in current packet.
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
- *
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	Substation id, as integer 0 .. 15.
- *
- *------------------------------------------------------------------------------*/
-
-int ax25_get_ssid (packet_t this_p, int n)
-{
-	
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-	
-	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);
-	}
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_set_ssid
- * 
- * Purpose:	Set the SSID of specified address in current packet.
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
- *
- *		ssid	- New SSID.  Must be in range of 0 to 15.
- *
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Bugs:	Rewrite to keep call and SSID separate internally.
- *
- *------------------------------------------------------------------------------*/
-
-void ax25_set_ssid (packet_t this_p, int n, int ssid)
-{
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-
-	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);
-	}
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_h
- * 
- * Purpose:	Return "has been repeated" flag of specified address in current packet.
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
- *
- * Bugs:	No bounds checking is performed.  Be careful.
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	True or false.
- *
- *------------------------------------------------------------------------------*/
-
-int ax25_get_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);
-
-	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);
-	}
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_set_h
- * 
- * Purpose:	Set the "has been repeated" flag of specified address in current packet.
- *
- * Inputs:	n	- Index of address.   Use the symbols 
- *			 Should be in range of AX25_REPEATER_1 .. AX25_REPEATER_8.
- *
- * Bugs:	No bounds checking is performed.  Be careful.
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	None
- *
- *------------------------------------------------------------------------------*/
-
-void ax25_set_h (packet_t this_p, int n)
-{
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	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);
-	}
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_heard
- * 
- * Purpose:	Return index of the station that we heard.
- *		
- * Inputs:	none
- *
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	If any of the digipeaters have the has-been-repeated bit set, 
- *		return the index of the last one.  Otherwise return index for source.
- *
- *------------------------------------------------------------------------------*/
-
-int ax25_get_heard(packet_t this_p)
-{
-	int i;
-	int result;
-
-	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++) {
-	
-	  if (ax25_get_h(this_p,i)) {
-	    result = i;
-	  }
-	}
-	return (result);
-}
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_first_not_repeated
- * 
- * Purpose:	Return index of the first repeater that does NOT have the 
- *		"has been repeated" flag set or -1 if none.
- *
- * Inputs:	none
- *
- *		  
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	In range of X25_REPEATER_1 .. X25_REPEATER_8 or -1 if none.
- *
- *------------------------------------------------------------------------------*/
-
-int ax25_get_first_not_repeated(packet_t this_p)
-{
-	int i;
-
-	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)) {
-	    return (i);
-	  }
-	}
-	return (-1);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_info
- * 
- * Purpose:	Obtain Information part of current packet.
- *
- * Inputs:	None.
- *
- * Outputs:	paddr	- Starting address is returned here.
- *
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	Number of octets in the Information part.
- *		Should be in the range of AX25_MIN_INFO_LEN .. AX25_MAX_INFO_LEN.
- *
- *------------------------------------------------------------------------------*/
-
-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) {
-
-	  /* 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;
-	}
-
-	/* Add nul character in case caller treats as printable string. */
-	
-	assert (info_len >= 0);
-
-	info_ptr[info_len] = '\0';
-
-	*paddr = info_ptr;
-	return (info_len);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_dti
- * 
- * Purpose:	Get Data Type Identifier from Information part.
- *
- * Inputs:	None.
- *
- * Assumption:	ax25_from_text or ax25_from_frame was called first.
- *
- * Returns:	First byte from the information part.
- *
- *------------------------------------------------------------------------------*/
-
-int ax25_get_dti (packet_t this_p)
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	if (this_p->num_addr >= 2) {
-	  return (this_p->frame_data[ax25_get_info_offset(this_p)]);
-	}
-	return (' ');
-}
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_set_nextp
- * 
- * Purpose:	Set next packet object in queue.
- *
- * Inputs:	this_p		- Current packet object.
- *
- *		next_p		- pointer to next one
- *
- * Description:	This is used to build a linked list for a queue.
- *
- *------------------------------------------------------------------------------*/
-
-void ax25_set_nextp (packet_t this_p, packet_t next_p)
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-	
-	this_p->nextp = next_p;
-}
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_get_nextp
- * 
- * Purpose:	Obtain next packet object in queue.
- *
- * Inputs:	Packet object.
- *
- * Returns:	Following object in queue or NULL.
- *
- *------------------------------------------------------------------------------*/
-
-packet_t ax25_get_nextp (packet_t this_p)
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-	
-	return (this_p->nextp);
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_format_addrs
- *
- * Purpose:	Format all the addresses suitable for printing.
- *
- *		The AX.25 spec refers to this as "Source Path Header" - "TNC-2" Format
- *
- * Inputs:	Current packet.
- *		
- * Outputs:	result	- All addresses combined into a single string of the form:
- *
- *				"Source > Destination [ , repeater ... ] :"
- *
- *			An asterisk is displayed after the last digipeater 
- *			with the "H" bit set.  e.g.  If we hear RPT2, 
- *
- *			SRC>DST,RPT1,RPT2*,RPT3:
- *
- *			No asterisk means the source is being heard directly.
- *			Needs to be 101 characters to avoid overflowing.
- *			(Up to 100 characters + \0)
- *
- * Errors:	No error checking so caller needs to be careful.
- *
- *
- *------------------------------------------------------------------*/
-
-void ax25_format_addrs (packet_t this_p, char *result)
-{
-	int i;
-	int heard;
-	char stemp[AX25_MAX_ADDR_LEN];
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-	*result = '\0';
-
-	/* New in 0.9. */
-	/* Don't get upset if no addresses.  */
-	/* This will allow packets that do not comply to AX.25 format. */
-
-	if (this_p->num_addr == 0) {
-	  return;
-	}
-
-	ax25_get_addr_with_ssid (this_p, AX25_SOURCE, stemp);
-	strcat (result, stemp);
-	strcat (result, ">");
-
-	ax25_get_addr_with_ssid (this_p, AX25_DESTINATION, stemp);
-	strcat (result, stemp);
-
-	heard = ax25_get_heard(this_p);
-
-	for (i=(int)AX25_REPEATER_1; i<this_p->num_addr; i++) {
-	  ax25_get_addr_with_ssid (this_p, i, stemp);
-	  strcat (result, ",");
-	  strcat (result, stemp);
-	  if (i == heard) {
-	    strcat (result, "*");
-	  }
-	}
-	
-	strcat (result, ":");
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_pack
- *
- * Purpose:	Put all the pieces into format ready for transmission.
- *
- * Inputs:	this_p	- pointer to packet object.
- *		
- * Outputs:	result		- Frame buffer, AX25_MAX_PACKET_LEN bytes.
- *				Should also have two extra for FCS to be
- *				added later.
- *
- * Returns:	Number of octets in the frame buffer.  
- *		Does NOT include the extra 2 for FCS.
- *
- * Errors:	Returns -1.
- *
- *------------------------------------------------------------------*/
-
-int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) 
-{
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	assert (this_p->frame_len > 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
-
-	memcpy (result, this_p->frame_data, this_p->frame_len);
-
-	return (this_p->frame_len);
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_frame_type
- *
- * Purpose:	Extract the type of frame.
- *		This is derived from the control byte(s) but
- *		is an enumerated type for easier handling.
- *
- * Inputs:	this_p	- pointer to packet object.
- *		
- * 		modulo	- We often need to know this because context is
- *			  required to know if control is 1 or 2 bytes.
- *
- * Outputs:	desc	- Text description such as "I frame" or
- *			  "U frame SABME".   
- *			  Supply 16 bytes to be safe.
- *
- *		pf	- P/F - Poll/Final or -1 if not applicable
- *
- *		nr	- N(R) - receive sequence or -1 if not applicable.
- *
- *		ns	- N(S) - send sequence or -1 if not applicable.
- *	
- * Returns:	Frame type from  enum ax25_frame_type_e.
- *
- *------------------------------------------------------------------*/
-
-
-
-ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns) 
-{
-	int c;
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	strcpy (desc, "????");
-	*pf = -1;
-	*nr = -1;
-	*ns = -1;
-
-	c = ax25_get_control(this_p);
-	if (c < 0) {
-	  strcpy (desc, "Not AX.25");
-	  return (frame_not_AX25);
-	}
-
-	if ((c & 1) == 0) {
-
-// Information
-
-	  if (modulo == modulo_128) {
-	    int c2 = ax25_get_c2 (this_p);	
-	    *ns = (c >> 1) & 0x7f;
-	    *pf = c2 & 1;
-	    *nr = (c2 >> 1) & 0x7f;
-	  }
-	  else {
-	    *ns = (c >> 1) & 7;
-	    *pf = (c >> 4) & 1;
-	    *nr = (c >> 5) & 7;
-	  }
-	  strcpy (desc, "I frame");
-	  return (frame_type_I);
-	}
-	else if ((c & 2) == 0) {
-
-// Supervisory
-
-	  if (modulo == modulo_128) {
-	    int c2 = ax25_get_c2 (this_p);	
-	    *pf = c2 & 1;
-	    *nr = (c2 >> 1) & 0x7f;
-	  }
-	  else {
-	    *pf = (c >> 4) & 1;
-	    *nr = (c >> 5) & 7;
-	  }
-	  
-	  switch ((c >> 2) & 3) {
-	    case 0: strcpy (desc, "S frame RR");   return (frame_type_RR);   break;
-	    case 1: strcpy (desc, "S frame RNR");  return (frame_type_RNR);  break;
-	    case 2: strcpy (desc, "S frame REJ");  return (frame_type_REJ);  break;
-	    case 3: strcpy (desc, "S frame SREJ"); return (frame_type_SREJ); break;
-	 } 
-	}
-	else {
-
-// Unnumbered
-
-	  *pf = (c >> 4) & 1;
-	  
-	  switch (c & 0xef) {
-	
-	    case 0x6f: strcpy (desc, "U frame SABME"); return (frame_type_SABME); break;
-	    case 0x2f: strcpy (desc, "U frame SABM");  return (frame_type_SABM);  break;
-	    case 0x43: strcpy (desc, "U frame DISC");  return (frame_type_DISC);  break;
-	    case 0x0f: strcpy (desc, "U frame DM");    return (frame_type_DM);    break;
-	    case 0x63: strcpy (desc, "U frame UA");    return (frame_type_UA);    break;
-	    case 0x87: strcpy (desc, "U frame FRMR");  return (frame_type_FRMR);  break;
-	    case 0x03: strcpy (desc, "U frame UI");    return (frame_type_UI);    break;
-	    case 0xaf: strcpy (desc, "U frame XID");   return (frame_type_XID);   break;
-	    case 0xe3: strcpy (desc, "U frame TEST");  return (frame_type_TEST);  break;
-	    default:   strcpy (desc, "U frame ???");   return (frame_type_U);     break;
-	  }
-	}
-
-	// Should be unreachable but compiler doesn't realize that.
-	// Suppress "warning: control reaches end of non-void function"
-
-	return (frame_not_AX25);
-
-} /* end ax25_frame_type */
-
-/*------------------------------------------------------------------
- *
- * 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.
- *		
- *------------------------------------------------------------------*/
-
-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]);
-	  }
-	  for (i=n; i<16; i++) {
-	    dw_printf ("   ");
-	  }
-	  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;
-	}
-}
-
-/* Text description of control octet. */
-
-// TODO: use ax25_frame_type() instead.
-
-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. */
-
-static void pid_to_text (int p, char *out)
-{
-
-	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); // TODO: use ax25_frame_type() instead.
-
-	  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
- *
- * Purpose:	Is this packet APRS format?
- *
- * Inputs:	this_p	- pointer to packet object.
- *		
- * Returns:	True if this frame has the proper control
- *		octets for an APRS packet.
- *			control		3 for UI frame
- *			protocol id	0xf0 for no layer 3
- *
- *
- * Description:	Dire Wolf should be able to act as a KISS TNC for
- *		any type of AX.25 activity.  However, there are other
- *		places where we want to process only APRS.
- *		(e.g. digipeating and IGate.)
- *
- *------------------------------------------------------------------*/
-
-
-int ax25_is_aprs (packet_t this_p) 
-{
-	int ctrl, pid, is_aprs;
-
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	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);
-}
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_get_control
- 		ax25_get_c2
- *
- * Purpose:	Get Control field from packet.
- *
- * Inputs:	this_p	- pointer to packet object.
- *		
- * Returns:	APRS uses AX25_UI_FRAME.
- *		This could also be used in other situations.
- *
- *------------------------------------------------------------------*/
-
-
-int ax25_get_control (packet_t this_p) 
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	if (this_p->num_addr >= 2) {
-	  return (this_p->frame_data[ax25_get_control_offset(this_p)]);
-	}
-	return (-1);
-}
-
-int ax25_get_c2 (packet_t this_p) 
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	if (this_p->num_addr >= 2) {
-	  return (this_p->frame_data[ax25_get_control_offset(this_p)+1]);
-	}
-	return (-1);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_get_pid
- *
- * Purpose:	Get protocol ID from packet.
- *
- * Inputs:	this_p	- pointer to packet object.
- *		
- * 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."
- *
- *------------------------------------------------------------------*/
-
-
-int ax25_get_pid (packet_t this_p) 
-{
-	assert (this_p->magic1 == MAGIC);
-	assert (this_p->magic2 == MAGIC);
-
-	// TODO: handle 2 control byte case.
-	// TODO: sanity check: is it I or UI frame?
-
-	if (this_p->num_addr >= 2) {
-	  return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
-	}
-	return (-1);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_dedupe_crc 
- * 
- * Purpose:	Calculate a checksum for the packet source, destination, and
- *		information but NOT the digipeaters.
- *		This is used for duplicate detection in the digipeater 
- *		and IGate algorithms.
- *
- * Input:	pp	- Pointer to packet object.
- *		
- * Returns:	Value which will be the same for a duplicate but very unlikely 
- *		to match a non-duplicate packet.
- *
- * Description:	For detecting duplicates, we need to look
- *			+ source station
- *			+ destination 
- *			+ information field
- *		but NOT the changing list of digipeaters.
- *
- *		Typically, only a checksum is kept to reduce memory 
- *		requirements and amount of compution for comparisons.
- *		There is a very very small probability that two unrelated 
- *		packets will result in the same checksum, and the
- *		undesired dropping of the packet.
- *		
- *------------------------------------------------------------------------------*/
-
-unsigned short ax25_dedupe_crc (packet_t pp)
-{
-	unsigned short crc;
-	char src[AX25_MAX_ADDR_LEN];
-	char dest[AX25_MAX_ADDR_LEN];
-	unsigned char *pinfo;
-	int info_len;
-
-	ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
-	ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
-	info_len = ax25_get_info (pp, &pinfo);
-
-	crc = 0xffff;
-	crc = crc16((unsigned char *)src, strlen(src), crc);
-	crc = crc16((unsigned char *)dest, strlen(dest), crc);
-	crc = crc16(pinfo, info_len, crc);
-
-	return (crc);
-}
-
-/*------------------------------------------------------------------------------
- *
- * Name:	ax25_m_m_crc 
- * 
- * Purpose:	Calculate a checksum for the packet.
- *		This is used for the multimodem duplicate detection.
- *
- * Input:	pp	- Pointer to packet object.
- *		
- * Returns:	Value which will be the same for a duplicate but very unlikely 
- *		to match a non-duplicate packet.
- *
- * Description:	For detecting duplicates, we need to look the entire packet.
- *
- *		Typically, only a checksum is kept to reduce memory 
- *		requirements and amount of compution for comparisons.
- *		There is a very very small probability that two unrelated 
- *		packets will result in the same checksum, and the
- *		undesired dropping of the packet.
-		
- *------------------------------------------------------------------------------*/
-
-unsigned short ax25_m_m_crc (packet_t pp)
-{
-	unsigned short crc;
-	unsigned char fbuf[AX25_MAX_PACKET_LEN];
-	int flen;
-
-	flen = ax25_pack (pp, fbuf); 
-
-	crc = 0xffff;
-	crc = crc16(fbuf, flen, crc);
-
-	return (crc);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_safe_print
- *
- * Purpose:	Print given string, changing non printable characters to 
- *		hexadecimal notation.   Note that character values
- *		<DEL>, 28, 29, 30, and 31 can appear in MIC-E message.
- *
- * Inputs:	pstr	- Pointer to string.
- *
- *		len	- Maximum length if not -1.
- *
- *		ascii_only	- Restrict output to only ASCII.
- *				  Normally we allow UTF-8.
- *		
- *		Stops after non-zero len characters or at nul.
- *
- * Returns:	none
- *
- * Description:	Print a string in a "safe" manner.
- *		Anything that is not a printable character
- *		will be converted to a hexadecimal representation.
- *		For example, a Line Feed character will appear as <0x0a>
- *		rather than dropping down to the next line on the screen.
- *
- *		ax25_from_text can accept this format.
- *
- *
- * Example:	W1MED-1>T2QP0S,N1OHZ,N8VIM*,WIDE1-1:'cQBl <0x1c>-/]<0x0d>
- *		                                          ------   ------
- *
- * Questions:	What should we do about UTF-8?  Should that be displayed
- *		as hexadecimal for troubleshooting? Maybe an option so the
- *		packet raw data is in hexadecimal but an extracted 
- *		comment displays UTF-8?  Or a command line option for only ASCII?
- *			
- *------------------------------------------------------------------*/
-
-#define MAXSAFE 500
-
-void ax25_safe_print (char *pstr, int len, int ascii_only)
-{
-	int ch;
-	char safe_str[MAXSAFE*6+1];
-	int safe_len;
-
-	safe_len = 0;
-	safe_str[safe_len] = '\0';
-
-
-	if (len < 0) 
-	  len = strlen(pstr);
-
-	if (len > MAXSAFE)
-	  len = MAXSAFE;
-
-	while (len > 0 && *pstr != '\0')
-	{
-	  ch = *((unsigned char *)pstr);
-
-	  if (ch < ' ' || ch == 0x7f || ch == 0xfe || ch == 0xff ||
-			(ascii_only && ch >= 0x80) ) {
-
-	      /* Control codes and delete. */
-	      /* UTF-8 does not use fe and ff except in a possible */
-	      /* "Byte Order Mark" (BOM) at the beginning. */
-
-	      sprintf (safe_str + safe_len, "<0x%02x>", ch);
-	      safe_len += 6;	      
-	    }
-	  else {
-	    /* Let everything else thru so we can handle UTF-8 */
-	    /* Maybe we should have an option to display 0x80 */
-	    /* and above as hexadecimal. */
-
-	    safe_str[safe_len++] = ch;
-	    safe_str[safe_len] = '\0';
-	  }
-
-	  pstr++;
-	  len--;
-	}
-
-// TODO1.2: should return string rather printing to remove a race condition.
-
-	dw_printf ("%s", safe_str);
-
-} /* end ax25_safe_print */
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ax25_alevel_to_text
- *
- * Purpose:	Convert audio level to text representation.
- *
- * Inputs:	alevel	- Audio levels collected from demodulator.
- *
- * Outputs:	text	- Text representation for presentation to user.  
- *			  Currently it will look something like this:
- *
- *				r(m/s)
- *
- *			  With n,m,s corresponding to received, mark, and space.
- *			  Comma is to be avoided because one place this 
- *			  ends up is in a CSV format file.
- *
- * Returns:	True if something to print.  (currently if alevel.original >= 0)
- *		False if not.
- *
- * Description:	Audio level used to be simple; it was a single number.
- *		In version 1.2, we start collecting more details.
- *		At the moment, it includes:
- *
- *		- Received level from new method.  
- *		- Levels from mark & space filters to examine the ratio.
- *
- *		We print this in multiple places so put it into a function.
- *			
- *------------------------------------------------------------------*/
-
-
-int ax25_alevel_to_text (alevel_t alevel, char *text)
-{
-	if (alevel.rec < 0) {
-	  strcpy (text, "");
-	  return (0);
-	}
-
-// TODO1.2: haven't thought much about non-AFSK cases yet.
-// What should we do for 9600 baud?
-// Possibility: low/high tone for DTMF???
-
-	if (alevel.mark >= 0 &&  alevel.space < 0) {		/* baseband */
-
-	  sprintf (text, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space);
-	}
-	else {		/* AFSK */
-
-	  //sprintf (text, "%d:%d(%d/%d=%05.3f=)", alevel.original, alevel.rec, alevel.mark, alevel.space, alevel.ms_ratio);
-	  sprintf (text, "%d(%d/%d)", alevel.rec, alevel.mark, alevel.space);
-	}
-	return (1);	
-
-} /* end ax25_alevel_to_text */
-
-
-/* end ax25_pad.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011 , 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:	ax25_pad
+ *
+ * Purpose:	Packet assembler and disasembler.
+ *
+ *   		We can obtain AX.25 packets from different sources:
+ *		
+ *		(a) from an HDLC frame.
+ *		(b) from text representation.
+ *		(c) built up piece by piece.
+ *
+ *		We also want to use a packet in different ways:
+ *
+ *		(a) transmit as an HDLC frame.
+ *		(b) print in human-readable text.
+ *		(c) take it apart piece by piece.
+ *
+ *		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.
+ *
+ *
+ * Description:	
+ *
+ *
+ *	APRS uses only UI frames.
+ *	Each starts with 2-10 addressses (14-70 octets):
+ *
+ *	* Destination Address  (note: opposite order in printed format)
+ *
+ *	* Source Address
+ *
+ *	* 0-8 Digipeater Addresses  (Could there ever be more as a result of
+ *					digipeaters inserting their own call for
+ *					the tracing feature?
+ *					NO.  The limit is 8 when transmitting AX.25 over the
+ *					radio.
+ *					Communication with an IGate server could
+ *					have a longer VIA path but that is only in text form,
+ *					not as an AX.25 frame.)
+ *
+ *	Each address is composed of:
+ *
+ *	* 6 upper case letters or digits, blank padded.
+ *		These are shifted left one bit, leaving the LSB always 0.
+ *
+ *	* a 7th octet containing the SSID and flags.
+ *		The LSB is always 0 except for the last octet of the address field.
+ *
+ *	The final octet of the Destination has the form:
+ *
+ *		C R R SSID 0, where,
+ *
+ *			C = command/response = 1
+ *			R R = Reserved = 1 1
+ *			SSID = substation ID
+ *			0 = zero
+ *
+ *	The final octet of the Source has the form:
+ *
+ *		C R R SSID 0, where,
+ *
+ *			C = command/response = 1
+ *			R R = Reserved = 1 1
+ *			SSID = substation ID
+ *			0 = zero (or 1 if no repeaters)
+ *
+ *	The final octet of each repeater has the form:
+ *
+ *		H R R SSID 0, where,
+ *
+ *			H = has-been-repeated = 0 initially.  
+ *				Set to 1 after this address has been used.
+ *			R R = Reserved = 1 1
+ *			SSID = substation ID
+ *			0 = zero (or 1 if last repeater in list)
+ *
+ *		A digipeater would repeat this frame if it finds its address
+ *		with the "H" bit set to 0 and all earlier repeater addresses
+ *		have the "H" bit set to 1.  
+ *		The "H" bit would be set to 1 in the repeated frame.
+ *
+ *	In standard monitoring format, an asterisk is displayed after the last
+ *	digipeater with the "H" bit set.  That indicates who you are hearing
+ *	over the radio.
+ *	(That is if digipeaters update the via path properly.  Some don't so
+ *	we don't know who we are hearing.  This is discussed in the User Guide.)
+ *	No asterisk means the source is being heard directly.
+ *
+ *	Example, if we can hear all stations involved,
+ *
+ *		SRC>DST,RPT1,RPT2,RPT3:		-- we heard SRC
+ *		SRC>DST,RPT1*,RPT2,RPT3:	-- we heard RPT1
+ *		SRC>DST,RPT1,RPT2*,RPT3:	-- we heard RPT2
+ *		SRC>DST,RPT1,RPT2,RPT3*:	-- we heard RPT3
+ *
+ *	
+ *	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.
+ *
+ *	And, of course, the 2 byte CRC.
+ *
+ * 	The descriptions above, for the C, H, and RR bits, are for APRS usage.
+ *	When operating as a KISS TNC we just pass everything along and don't
+ *	interpret or change them.
+ *
+ *
+ * Constructors: ax25_init		- Clear everything.
+ *		ax25_from_text		- Tear apart a text string
+ *		ax25_from_frame		- Tear apart an AX.25 frame.  
+ *					  Must be called before any other function.
+ *
+ * Get methods:	....			- Extract destination, source, or digipeater
+ *					  address from frame.
+ *
+ * Assumptions:	CRC has already been verified to be correct.
+ *
+ *------------------------------------------------------------------*/
+
+#define AX25_PAD_C		/* this will affect behavior of ax25_pad.h */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <ctype.h>
+#ifndef _POSIX_C_SOURCE
+
+#define _POSIX_C_SOURCE 1
+#endif
+
+#include "regex.h"
+
+#if __WIN32__
+char *strtok_r(char *str, const char *delim, char **saveptr);
+#endif
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "fcs_calc.h"
+
+/*
+ * Accumulate statistics.
+ * If new_count gets much larger than delete_count plus the size of 
+ * the transmit queue we have a memory leak.
+ */
+
+static volatile int new_count = 0;
+static volatile int delete_count = 0;
+static volatile int last_seq_num = 0;
+
+#if AX25MEMDEBUG
+
+int ax25memdebug = 0;
+
+
+void ax25memdebug_set(void) 
+{
+	ax25memdebug = 1;
+}
+
+int ax25memdebug_get (void)
+{
+	return (ax25memdebug);
+}
+
+int ax25memdebug_seq (packet_t this_p)
+{
+	return (this_p->seq);
+}
+
+
+#endif
+
+
+
+#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
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_new
+ * 
+ * Purpose:	Allocate memory for a new packet object.
+ *
+ * Returns:	Identifier for a new packet object.
+ *		In the current implementation this happens to be a pointer.
+ *
+ *------------------------------------------------------------------------------*/
+
+
+packet_t ax25_new (void)
+{
+	struct packet_s *this_p;
+
+
+#if DEBUG 
+        text_color_set(DW_COLOR_DEBUG);
+        dw_printf ("ax25_new(): before alloc, new=%d, delete=%d\n", new_count, delete_count);
+#endif
+
+	last_seq_num++;
+	new_count++;
+
+/*
+ * check for memory leak.
+ */
+	if (new_count > delete_count + 100) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Report to WB2OSZ - Memory leak for packet objects.  new=%d, delete=%d\n", new_count, delete_count);
+#if AX25MEMDEBUG
+	  // Force on debug option to gather evidence.
+	  ax25memdebug_set();
+#endif
+	}
+
+	this_p = calloc(sizeof (struct packet_s), (size_t)1);
+
+	if (this_p == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - can't allocate memory in ax25_new.\n");
+	}
+
+	assert (this_p != NULL);
+
+	this_p->magic1 = MAGIC;
+	this_p->seq = last_seq_num;
+	this_p->magic2 = MAGIC;
+	this_p->num_addr = (-1);
+
+	return (this_p);
+}
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_delete
+ * 
+ * Purpose:	Destroy a packet object, freeing up memory it was using.
+ *
+ *------------------------------------------------------------------------------*/
+
+#if AX25MEMDEBUG
+void ax25_delete_debug (packet_t this_p, char *src_file, int src_line)
+#else
+void ax25_delete (packet_t this_p)
+#endif
+{
+#if DEBUG
+        text_color_set(DW_COLOR_DEBUG);
+        dw_printf ("ax25_delete(): before free, new=%d, delete=%d\n", new_count, delete_count);
+#endif
+
+	if (this_p == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - NULL pointer passed to ax25_delete.\n");
+	  return;
+	}
+
+
+	delete_count++;
+
+#if AX25MEMDEBUG	
+	if (ax25memdebug) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ax25_delete, seq=%d, called from %s %d, new_count=%d, delete_count=%d\n", this_p->seq, src_file, src_line, new_count, delete_count);
+	}
+#endif
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+	
+	this_p->magic1 = 0;
+	this_p->magic1 = 0;
+
+	//memset (this_p, 0, sizeof (struct packet_s));
+	free (this_p);
+}
+
+
+		
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_from_text
+ * 
+ * Purpose:	Parse a frame in human-readable monitoring format and change
+ *		to internal representation.
+ *
+ * Input:	monitor	- "TNC-2" format of a monitored packet.  i.e.
+ *				source>dest[,repeater1,repeater2,...]:information
+ *
+ *		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.
+ *
+ *------------------------------------------------------------------------------*/
+
+#if AX25MEMDEBUG
+packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line)
+#else
+packet_t ax25_from_text (char *monitor, int strict)
+#endif
+{
+
+/*
+ * Tearing it apart is destructive so make our own copy first.
+ */
+	char stuff[512];
+
+	char *pinfo;
+	char *pa;
+	char *saveptr;		/* Used with strtok_r because strtok is not thread safe. */
+
+	static int first_time = 1;
+	static regex_t unhex_re;
+	int e;
+	char emsg[100];
+#define MAXMATCH 1
+	regmatch_t match[MAXMATCH];
+	int keep_going;
+	char temp[512];
+	int ssid_temp, heard_temp;
+	char atemp[AX25_MAX_ADDR_LEN];
+
+
+	packet_t this_p = ax25_new ();
+
+#if AX25MEMDEBUG	
+	if (ax25memdebug) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ax25_from_text, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
+	}
+#endif
+
+	/* 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. */
+
+	strlcpy (stuff, monitor, sizeof(stuff));
+
+/* 
+ * Translate hexadecimal values like <0xff> to non-printing characters.
+ * MIC-E message type uses 5 different non-printing characters.
+ */
+
+	if (first_time) 
+	{
+	  e = regcomp (&unhex_re, "<0x[0-9a-fA-F][0-9a-fA-F]>", 0);
+	  if (e) {
+	    regerror (e, &unhex_re, emsg, sizeof(emsg));
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+	  }
+
+	  first_time = 0;
+	}
+
+#if 0
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("BEFORE: %s\n", stuff);
+	ax25_safe_print (stuff, -1, 0);
+	dw_printf ("\n");
+#endif
+	keep_going = 1;
+	while (keep_going) {
+	  if (regexec (&unhex_re, stuff, MAXMATCH, match, 0) == 0) {
+	    int n;
+	    char *p;
+  
+	    stuff[match[0].rm_so + 5] = '\0';
+	    n = strtol (stuff + match[0].rm_so + 3, &p, 16);
+	    stuff[match[0].rm_so] = n;
+	    strlcpy (temp, stuff + match[0].rm_eo, sizeof(temp));
+	    strlcpy (stuff + match[0].rm_so + 1, temp, sizeof(stuff)-match[0].rm_so-1);
+	  }
+	  else {
+	    keep_going = 0;
+	  }
+	}
+#if 0
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("AFTER:  %s\n", stuff);
+	ax25_safe_print (stuff, -1, 0);
+	dw_printf ("\n");
+#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, ':');
+
+	if (pinfo == NULL) {
+	  ax25_delete (this_p);
+	  return (NULL);
+	}
+
+	*pinfo = '\0';
+	pinfo++;
+
+	if (strlen(pinfo) > AX25_MAX_INFO_LEN) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Warning: Information part truncated to %d characters.\n", AX25_MAX_INFO_LEN);
+	  pinfo[AX25_MAX_INFO_LEN] = '\0';
+	}
+	
+/*
+ * Separate the addresses.
+ * Note that source and destination order is swappped.
+ */
+
+/*
+ * Source address.
+ * Don't use traditional strtok because it is not thread safe.
+ */
+	pa = strtok_r (stuff, ">", &saveptr);
+	if (pa == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Failed to create packet from text.  No source address\n");
+	  ax25_delete (this_p);
+	  return (NULL);
+	}
+
+	if ( ! ax25_parse_addr (AX25_SOURCE, 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);
+	}
+
+	ax25_set_addr (this_p, AX25_SOURCE, atemp);
+	ax25_set_h (this_p, AX25_SOURCE);	// c/r in this position
+	ax25_set_ssid (this_p, AX25_SOURCE, ssid_temp);
+
+/*
+ * Destination address.
+ */
+ 
+	pa = strtok_r (NULL, ",", &saveptr);
+	if (pa == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Failed to create packet from text.  No destination address\n");
+	  ax25_delete (this_p);
+	  return (NULL);
+	}
+
+	if ( ! ax25_parse_addr (AX25_DESTINATION, 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);
+	}
+
+	ax25_set_addr (this_p, AX25_DESTINATION, atemp);
+	ax25_set_h (this_p, AX25_DESTINATION);	// c/r in this position
+	ax25_set_ssid (this_p, AX25_DESTINATION, ssid_temp);
+
+/*
+ * VIA path.
+ */
+	while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) {
+
+	  //char *last;
+	  int k;
+
+	  k = this_p->num_addr;
+
+	  if ( ! ax25_parse_addr (k, 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);
+	  }
+
+	  ax25_set_addr (this_p, k, atemp);
+	  ax25_set_ssid (this_p, k, ssid_temp);
+
+	  // Does it have an "*" at the end? 
+	  // TODO: Complain if more than one "*".
+	  // Could also check for all has been repeated bits are adjacent.
+	
+          if (heard_temp) {
+	    for ( ; k >= AX25_REPEATER_1; k--) {
+	      ax25_set_h (this_p, k);
+	    }
+	  }
+        }
+
+/*
+ * Append the info part.  
+ */
+	strlcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo, sizeof(this_p->frame_data)-this_p->frame_len);
+	this_p->frame_len += strlen(pinfo);
+
+	return (this_p);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_from_frame
+ * 
+ * Purpose:	Split apart an HDLC frame to components.
+ *
+ * Inputs:	fbuf	- Pointer to beginning of frame.
+ *
+ *		flen	- Length excluding the two FCS bytes.
+ *
+ *		alevel	- Audio level of received signal.  
+ *			  Maximum range 0 - 100.
+ *			  -1 might be used when not applicable.
+ *
+ * Returns:	Pointer to new packet object or NULL if error.
+ *
+ * Outputs:	Use the "get" functions to retrieve information in different ways.
+ *
+ *------------------------------------------------------------------------------*/
+
+#if AX25MEMDEBUG
+packet_t ax25_from_frame_debug (unsigned char *fbuf, int flen, alevel_t alevel, char *src_file, int src_line)
+#else
+packet_t ax25_from_frame (unsigned char *fbuf, int flen, alevel_t alevel)
+#endif
+{
+	packet_t this_p;
+
+
+/*
+ * First make sure we have an acceptable length:
+ *
+ *	We are not concerned with the FCS (CRC) because someone else checked it.
+ *
+ * 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)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Frame length %d not in allowable range of %d to %d.\n", flen, AX25_MIN_PACKET_LEN, AX25_MAX_PACKET_LEN);
+	  return (NULL);
+	}
+
+	this_p = ax25_new ();
+
+#if AX25MEMDEBUG	
+	if (ax25memdebug) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ax25_from_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
+	}
+#endif
+
+/* Copy the whole thing intact. */
+
+	memcpy (this_p->frame_data, fbuf, flen);
+	this_p->frame_data[flen] = 0;
+	this_p->frame_len = flen;
+
+/* Find number of addresses. */
+	
+	this_p->num_addr = (-1);
+	(void) ax25_get_num_addr (this_p);
+
+	return (this_p);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_dup
+ * 
+ * Purpose:	Make a copy of given packet object.
+ *
+ * Inputs:	copy_from	- Existing packet object.
+ *
+ * Returns:	Pointer to new packet object or NULL if error.
+ *
+ *
+ *------------------------------------------------------------------------------*/
+
+
+#if AX25MEMDEBUG
+packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line)
+#else
+packet_t ax25_dup (packet_t copy_from)
+#endif
+{
+	int save_seq;
+	packet_t this_p;
+
+	
+	this_p = ax25_new ();
+	assert (this_p != NULL);
+
+	save_seq = this_p->seq;
+
+	memcpy (this_p, copy_from, sizeof (struct packet_s));
+	this_p->seq = save_seq;
+
+#if AX25MEMDEBUG
+	if (ax25memdebug) {	
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ax25_dup, seq=%d, called from %s %d, clone of seq %d\n", this_p->seq, src_file, src_line, copy_from->seq);
+	}
+#endif
+
+	return (this_p);
+
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_parse_addr
+ * 
+ * Purpose:	Parse address with optional ssid.
+ *
+ * Inputs:	position	- AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER_1...
+ *				  Used for more specific error message.  -1 if not used.
+ *
+ *		in_addr		- Input such as "WB2OSZ-15*"
+ *
+ * 		strict		- TRUE for strict checking (6 characters, no lower case,
+ *				  SSID must be in range of 0 to 15).
+ *				  Strict is appropriate for packets sent
+ *				  over the radio.  Communication with IGate
+ *				  allows lower case (e.g. "qAR") and two 
+ *				  alphanumeric characters for the SSID.
+ *				  We also get messages like this from a server.
+ *					KB1POR>APU25N,TCPIP*,qAC,T2NUENGLD:...
+ *
+ * Outputs:	out_addr	- Address without any SSID.
+ *				  Must be at least AX25_MAX_ADDR_LEN bytes.
+ *
+ *		out_ssid	- Numeric value of SSID.
+ *
+ *		out_heard	- True if "*" found.
+ *
+ * Returns:	True (1) if OK, false (0) if any error.
+ *
+ *
+ *------------------------------------------------------------------------------*/
+
+static const char *position_name[1 + AX25_MAX_ADDRS] = {
+	"", "Destination ", "Source ",
+	"Digi1 ", "Digi2 ", "Digi3 ", "Digi4 ",
+	"Digi5 ", "Digi6 ", "Digi7 ", "Digi8 " };
+
+int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard)
+{
+	char *p;
+	char sstr[8];		/* Should be 1 or 2 digits for SSID. */
+	int i, j, k;
+	int maxlen;
+
+	*out_addr = '\0';
+	*out_ssid = 0;
+	*out_heard = 0;
+
+	if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) {
+
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("%sAddress \"%s\" is a \"q-construct\" used for communicating\n", position_name[position], in_addr);
+	  dw_printf ("with APRS Internet Servers.  It was not expected here.\n");
+	}
+
+	//dw_printf ("ax25_parse_addr in: %s\n", in_addr);
+
+	if (position < -1) position = -1;
+	if (position > AX25_REPEATER_8) position = AX25_REPEATER_8;
+	position++;	/* Adjust for position_name above. */
+
+	maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1);
+	p = in_addr;
+	i = 0;
+	for (p = in_addr; isalnum(*p); p++) {
+	  if (i >= maxlen) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("%sAddress is too long. \"%s\" has more than %d characters.\n", position_name[position], in_addr, maxlen);
+	    return 0;
+	  }
+	  out_addr[i++] = *p;
+	  out_addr[i] = '\0';
+	  if (strict && islower(*p)) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr);
+	    return 0;
+	  }
+	}
+	
+	j = 0;
+	sstr[j] = '\0';
+	if (*p == '-') {
+	  for (p++; isalnum(*p); p++) {
+	    if (j >= 2) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("%sSSID is too long. SSID part of \"%s\" has more than 2 characters.\n", position_name[position], in_addr);
+	      return 0;
+	    }
+	    sstr[j++] = *p;
+	    sstr[j] = '\0';
+	    if (strict && ! isdigit(*p)) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("%sSSID must be digits. \"%s\" has letters in SSID.\n", position_name[position], in_addr);
+	      return 0;
+	    }
+	  }
+	  k = atoi(sstr);
+	  if (k < 0 || k > 15) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("%sSSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", position_name[position], in_addr);
+	    return 0;
+	  }
+	  *out_ssid = k;
+	}
+
+	if (*p == '*') {
+	  *out_heard = 1;
+	  p++;
+	}
+
+	if (*p != '\0') {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Invalid character \"%c\" found in %saddress \"%s\".\n", *p, position_name[position], in_addr);
+	  return 0;
+	}
+
+	//dw_printf ("ax25_parse_addr out: %s %d %d\n", out_addr, *out_ssid, *out_heard);
+
+	return (1);
+
+} /* end ax25_parse_addr */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ax25_check_addresses
+ *
+ * Purpose:     Check addresses of given packet and print message if any issues.
+ *		We call this when receiving and transmitting.
+ *
+ * Inputs:	pp	- packet object pointer.
+ *
+ * Errors:	Print error message.
+ *
+ * Returns:	1 for all valid.  0 if not.
+ *
+ * Examples:	I was surprised to get this from an APRS-IS server with
+ *		a lower case source address.
+ *
+ *			n1otx>APRS,TCPIP*,qAC,THIRD:@141335z4227.48N/07111.73W_348/005g014t044r000p000h60b10075.wview_5_20_2
+ *
+ *		I haven't gotten to the bottom of this yet but it sounds
+ *		like "q constructs" are somehow getting on to the air when
+ *		they should only appear in conversations with IGate servers.
+ *
+ *			https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/topics/678
+ *
+ *			WB0VGI-7>APDW12,W0YC-5*,qAR,AE0RF-10:}N0DZQ-10>APWW10,TCPIP,WB0VGI-7*:;145.230MN*080306z4607.62N/09230.58WrKE0ACL/R 145.230- T146.2 (Pine County ARES)	
+ *
+ * Typical result:
+ *
+ *			Digipeater WIDE2 (probably N3LEE-4) audio level = 28(10/6)   [NONE]   __|||||||
+ *			[0.5] VE2DJE-9>P_0_P?,VE2PCQ-3,K1DF-7,N3LEE-4,WIDE2*:'{S+l <0x1c>>/
+ *			Invalid character "_" in MIC-E destination/latitude.
+ *			Invalid character "_" in MIC-E destination/latitude.
+ *			Invalid character "?" in MIC-E destination/latitude.
+ *			Invalid MIC-E N/S encoding in 4th character of destination.
+ *			Invalid MIC-E E/W encoding in 6th character of destination.
+ *			MIC-E, normal car (side view), Unknown manufacturer, Returning
+ *			N 00 00.0000, E 005 55.1500, 0 MPH
+ *			Invalid character "_" found in Destination address "P_0_P?".
+ *
+ *			*** The origin and journey of this packet should receive some scrutiny. ***
+ *
+ *--------------------------------------------------------------------*/
+
+int ax25_check_addresses (packet_t pp)
+{
+	int n;
+	char addr[AX25_MAX_ADDR_LEN];
+	char ignore1[AX25_MAX_ADDR_LEN];
+	int ignore2, ignore3;
+	int all_ok = 1;
+
+	for (n = 0; n < ax25_get_num_addr(pp); n++) {
+	  ax25_get_addr_with_ssid (pp, n, addr);
+	  all_ok &= ax25_parse_addr (n, addr, 1, ignore1, &ignore2, &ignore3);
+	}
+
+	if (! all_ok) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\n");
+	  dw_printf ("*** The origin and journey of this packet should receive some scrutiny. ***\n");
+	  dw_printf ("\n");
+	}
+
+	return (all_ok);
+} /* end ax25_check_addresses */
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_unwrap_third_party
+ * 
+ * Purpose:	Unwrap a third party messge from the header.
+ *
+ * Inputs:	copy_from	- Existing packet object.
+ *
+ * Returns:	Pointer to new packet object or NULL if error.
+ *
+ * Example:	Input:		A>B,C:}D>E,F:info
+ *		Output:		D>E,F:info
+ *
+ *------------------------------------------------------------------------------*/
+
+packet_t ax25_unwrap_third_party (packet_t from_pp)
+{
+	unsigned char *info_p;
+	packet_t result_pp;
+
+	if (ax25_get_dti(from_pp) != '}') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: ax25_unwrap_third_party: wrong data type.\n");
+	  return (NULL);
+	}
+
+	(void) ax25_get_info (from_pp, &info_p);
+
+	result_pp = ax25_from_text((char *)info_p + 1, 0);
+
+	return (result_pp);
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_set_addr
+ * 
+ * Purpose:	Add or change an address.
+ *
+ * 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.
+ *
+ * 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);
+
+	//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. 
+ */
+
+	  // Why aren't we setting 'strict' here?
+	  // Messages from IGate have q-constructs.
+	  // We use this to parse it and later remove unwanted parts.
+
+	  ax25_parse_addr (n, 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) {		
+
+	  //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");
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_insert_addr
+ * 
+ * Purpose:	Insert address at specified position, shifting others up one
+ *		position.
+ *		This is used when a digipeater wants to insert its own call
+ *		for tracing purposes.
+ *		For example:
+ *			W1ABC>TEST,WIDE3-3
+ *		Would become:
+ *			W1ABC>TEST,WB2OSZ-1*,WIDE3-2
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ *		ad	- Address with optional dash and substation id.
+ *
+ * Bugs:	Little validity or bounds checking is performed.  Be careful.
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	None.
+ *		
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_insert_addr (packet_t this_p, int n, char *ad)
+{
+	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);
+
+	//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. */
+
+	if ( this_p->num_addr >= AX25_MAX_ADDRS) {
+	  return;
+	}
+
+	CLEAR_LAST_ADDR_FLAG;
+
+	this_p->num_addr++;
+
+	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;
+
+	// Why aren't we setting 'strict' here?
+	// Messages from IGate have q-constructs.
+	// We use this to parse it and later remove unwanted parts.
+
+	ax25_parse_addr (n, 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);
+	}
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_remove_addr
+ * 
+ * Purpose:	Remove address at specified position, shifting others down one position.
+ *		This is used when we want to remove something from the digipeater list.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_REPEATER1, AX25_REPEATER2, etc.
+ *
+ * Bugs:	Little validity or bounds checking is performed.  Be careful.
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	None.
+ *		
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_remove_addr (packet_t this_p, int n)
+{
+	int expect; 
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+	assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS);
+
+	/* Shift those beyond to fill this position. */
+
+	CLEAR_LAST_ADDR_FLAG;
+
+	this_p->num_addr--;
+
+	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);
+	}
+
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_num_addr
+ * 
+ * Purpose:	Return number of addresses in current packet.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Number of addresses in the current packet.
+ *		Should be in the range of 2 .. AX25_MAX_ADDRS.
+ *
+ * Version 0.9:	Could be zero for a non AX.25 frame in KISS mode.
+ *
+ *------------------------------------------------------------------------------*/
+
+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);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_num_repeaters
+ * 
+ * Purpose:	Return number of repeater addresses in current packet.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Number of addresses in the current packet - 2.
+ *		Should be in the range of 0 .. AX25_MAX_ADDRS - 2.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_num_repeaters (packet_t this_p)
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	if (this_p->num_addr >= 2) {
+	  return (this_p->num_addr - 2);
+	}
+
+	return (0);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_addr_with_ssid
+ * 
+ * Purpose:	Return specified address with any SSID in current packet.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Outputs:	station - String representation of the station, including the SSID.
+ *			e.g.  "WB2OSZ-15"
+ *			  Usually variables will be AX25_MAX_ADDR_LEN bytes
+ *			  but 10 would be adequate.
+ *
+ * Bugs:	No bounds checking is performed.  Be careful.
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Character string in usual human readable format,
+ *		
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station)
+{	
+	int ssid;
+	char sstr[8];		/* Should be 1 or 2 digits for SSID. */
+	int i;
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+
+	if (n < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("Address index, %d, is less than zero.\n", n);
+	  strlcpy (station, "??????", 10);
+	  return;
+	}
+
+	if (n >= this_p->num_addr) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr);
+	  strlcpy (station, "??????", 10);
+	  return;
+	}
+
+	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) {
+	  snprintf (sstr, sizeof(sstr), "-%d", ssid);
+	  strlcat (station, sstr, 10);
+	}
+
+} /* end ax25_get_addr_with_ssid */
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_addr_no_ssid
+ * 
+ * Purpose:	Return specified address WITHOUT any SSID.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Outputs:	station - String representation of the station, WITHOUT the SSID.
+ *			e.g.  "WB2OSZ"
+ *			  Usually variables will be AX25_MAX_ADDR_LEN bytes
+ *			  but 7 would be adequate.
+ *
+ * Bugs:	No bounds checking is performed.  Be careful.
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Character string in usual human readable format,
+ *		
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_get_addr_no_ssid (packet_t this_p, int n, char *station)
+{	
+	int i;
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+
+	if (n < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error detected in ax25_get_addr_no_ssid, %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("Address index, %d, is less than zero.\n", n);
+	  strlcpy (station, "??????", 7);
+	  return;
+	}
+
+	if (n >= this_p->num_addr) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error detected in ax25_get_no_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
+	  dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr);
+	  strlcpy (station, "??????", 7);
+	  return;
+	}
+
+	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;
+	}
+
+} /* end ax25_get_addr_no_ssid */
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_ssid
+ * 
+ * Purpose:	Return SSID of specified address in current packet.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Substation id, as integer 0 .. 15.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_ssid (packet_t this_p, int n)
+{
+	
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+	
+	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);
+	}
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_set_ssid
+ * 
+ * Purpose:	Set the SSID of specified address in current packet.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ *		ssid	- New SSID.  Must be in range of 0 to 15.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Bugs:	Rewrite to keep call and SSID separate internally.
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_set_ssid (packet_t this_p, int n, int ssid)
+{
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+
+	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);
+	}
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_h
+ * 
+ * Purpose:	Return "has been repeated" flag of specified address in current packet.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			  AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
+ *
+ * Bugs:	No bounds checking is performed.  Be careful.
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	True or false.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_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);
+
+	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);
+	}
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_set_h
+ * 
+ * Purpose:	Set the "has been repeated" flag of specified address in current packet.
+ *
+ * Inputs:	n	- Index of address.   Use the symbols 
+ *			 Should be in range of AX25_REPEATER_1 .. AX25_REPEATER_8.
+ *
+ * Bugs:	No bounds checking is performed.  Be careful.
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	None
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_set_h (packet_t this_p, int n)
+{
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	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);
+	}
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_heard
+ * 
+ * Purpose:	Return index of the station that we heard.
+ *		
+ * Inputs:	none
+ *
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	If any of the digipeaters have the has-been-repeated bit set, 
+ *		return the index of the last one.  Otherwise return index for source.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_heard(packet_t this_p)
+{
+	int i;
+	int result;
+
+	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++) {
+	
+	  if (ax25_get_h(this_p,i)) {
+	    result = i;
+	  }
+	}
+	return (result);
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_first_not_repeated
+ * 
+ * Purpose:	Return index of the first repeater that does NOT have the 
+ *		"has been repeated" flag set or -1 if none.
+ *
+ * Inputs:	none
+ *
+ *		  
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	In range of X25_REPEATER_1 .. X25_REPEATER_8 or -1 if none.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_first_not_repeated(packet_t this_p)
+{
+	int i;
+
+	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)) {
+	    return (i);
+	  }
+	}
+	return (-1);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_info
+ * 
+ * Purpose:	Obtain Information part of current packet.
+ *
+ * Inputs:	None.
+ *
+ * Outputs:	paddr	- Starting address is returned here.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	Number of octets in the Information part.
+ *		Should be in the range of AX25_MIN_INFO_LEN .. AX25_MAX_INFO_LEN.
+ *
+ *------------------------------------------------------------------------------*/
+
+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) {
+
+	  /* 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;
+	}
+
+	/* Add nul character in case caller treats as printable string. */
+	
+	assert (info_len >= 0);
+
+	info_ptr[info_len] = '\0';
+
+	*paddr = info_ptr;
+	return (info_len);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_dti
+ * 
+ * Purpose:	Get Data Type Identifier from Information part.
+ *
+ * Inputs:	None.
+ *
+ * Assumption:	ax25_from_text or ax25_from_frame was called first.
+ *
+ * Returns:	First byte from the information part.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_dti (packet_t this_p)
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	if (this_p->num_addr >= 2) {
+	  return (this_p->frame_data[ax25_get_info_offset(this_p)]);
+	}
+	return (' ');
+}
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_set_nextp
+ * 
+ * Purpose:	Set next packet object in queue.
+ *
+ * Inputs:	this_p		- Current packet object.
+ *
+ *		next_p		- pointer to next one
+ *
+ * Description:	This is used to build a linked list for a queue.
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_set_nextp (packet_t this_p, packet_t next_p)
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+	
+	this_p->nextp = next_p;
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_nextp
+ * 
+ * Purpose:	Obtain next packet object in queue.
+ *
+ * Inputs:	Packet object.
+ *
+ * Returns:	Following object in queue or NULL.
+ *
+ *------------------------------------------------------------------------------*/
+
+packet_t ax25_get_nextp (packet_t this_p)
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	return (this_p->nextp);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_set_release_time
+ *
+ * Purpose:	Set release time
+ *
+ * Inputs:	this_p		- Current packet object.
+ *
+ *		release_time	- Time as returned by dtime_now().
+ *
+ *------------------------------------------------------------------------------*/
+
+void ax25_set_release_time (packet_t this_p, double release_time)
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+	
+	this_p->release_time = release_time;
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_get_release_time
+ *
+ * Purpose:	Get release time.
+ *
+ *------------------------------------------------------------------------------*/
+
+double ax25_get_release_time (packet_t this_p)
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	return (this_p->release_time);
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_format_addrs
+ *
+ * Purpose:	Format all the addresses suitable for printing.
+ *
+ *		The AX.25 spec refers to this as "Source Path Header" - "TNC-2" Format
+ *
+ * Inputs:	Current packet.
+ *		
+ * Outputs:	result	- All addresses combined into a single string of the form:
+ *
+ *				"Source > Destination [ , repeater ... ] :"
+ *
+ *			An asterisk is displayed after the last digipeater 
+ *			with the "H" bit set.  e.g.  If we hear RPT2, 
+ *
+ *			SRC>DST,RPT1,RPT2*,RPT3:
+ *
+ *			No asterisk means the source is being heard directly.
+ *			Needs to be 101 characters to avoid overflowing.
+ *			(Up to 100 characters + \0)
+ *
+ * Errors:	No error checking so caller needs to be careful.
+ *
+ *
+ *------------------------------------------------------------------*/
+
+// TODO: max len for result.  buffer overflow?
+
+void ax25_format_addrs (packet_t this_p, char *result)
+{
+	int i;
+	int heard;
+	char stemp[AX25_MAX_ADDR_LEN];
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+	*result = '\0';
+
+	/* New in 0.9. */
+	/* Don't get upset if no addresses.  */
+	/* This will allow packets that do not comply to AX.25 format. */
+
+	if (this_p->num_addr == 0) {
+	  return;
+	}
+
+	ax25_get_addr_with_ssid (this_p, AX25_SOURCE, stemp);
+	strcat (result, stemp);
+	strcat (result, ">");
+
+	ax25_get_addr_with_ssid (this_p, AX25_DESTINATION, stemp);
+	strcat (result, stemp);
+
+	heard = ax25_get_heard(this_p);
+
+	for (i=(int)AX25_REPEATER_1; i<this_p->num_addr; i++) {
+	  ax25_get_addr_with_ssid (this_p, i, stemp);
+	  strcat (result, ",");
+	  strcat (result, stemp);
+	  if (i == heard) {
+	    strcat (result, "*");
+	  }
+	}
+	
+	strcat (result, ":");
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_pack
+ *
+ * Purpose:	Put all the pieces into format ready for transmission.
+ *
+ * Inputs:	this_p	- pointer to packet object.
+ *		
+ * Outputs:	result		- Frame buffer, AX25_MAX_PACKET_LEN bytes.
+ *				Should also have two extra for FCS to be
+ *				added later.
+ *
+ * Returns:	Number of octets in the frame buffer.  
+ *		Does NOT include the extra 2 for FCS.
+ *
+ * Errors:	Returns -1.
+ *
+ *------------------------------------------------------------------*/
+
+int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) 
+{
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	assert (this_p->frame_len > 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
+
+	memcpy (result, this_p->frame_data, this_p->frame_len);
+
+	return (this_p->frame_len);
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_frame_type
+ *
+ * Purpose:	Extract the type of frame.
+ *		This is derived from the control byte(s) but
+ *		is an enumerated type for easier handling.
+ *
+ * Inputs:	this_p	- pointer to packet object.
+ *		
+ * 		modulo	- We often need to know this because context is
+ *			  required to know if control is 1 or 2 bytes.
+ *
+ * Outputs:	desc	- Text description such as "I frame" or
+ *			  "U frame SABME".   
+ *			  Supply 16 bytes to be safe.
+ *
+ *		pf	- P/F - Poll/Final or -1 if not applicable
+ *
+ *		nr	- N(R) - receive sequence or -1 if not applicable.
+ *
+ *		ns	- N(S) - send sequence or -1 if not applicable.
+ *	
+ * Returns:	Frame type from  enum ax25_frame_type_e.
+ *
+ *------------------------------------------------------------------*/
+
+// TODO: need someway to ensure caller allocated enough space.
+#define DESC_SIZ 32
+
+ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns) 
+{
+	int c;		// U frames are always one control byte.
+	int c2;		// I & S frames can have second Control byte.
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	strlcpy (desc, "????", DESC_SIZ);
+	*pf = -1;
+	*nr = -1;
+	*ns = -1;
+
+	c = ax25_get_control(this_p);
+	if (c < 0) {
+	  strlcpy (desc, "Not AX.25", DESC_SIZ);
+	  return (frame_not_AX25);
+	}
+	if (modulo == modulo_128) {
+	  c2 = ax25_get_c2 (this_p);
+	}
+
+
+	if ((c & 1) == 0) {
+
+// Information 			rrr p sss 0		or	sssssss 0  rrrrrrr p
+
+	  if (modulo == modulo_128) {
+	    *ns = (c >> 1) & 0x7f;
+	    *pf = c2 & 1;
+	    *nr = (c2 >> 1) & 0x7f;
+	  }
+	  else {
+	    *ns = (c >> 1) & 7;
+	    *pf = (c >> 4) & 1;
+	    *nr = (c >> 5) & 7;
+	  }
+	  snprintf (desc, DESC_SIZ, "I frame, n(s)= %d, n(r)=%d, p=%d", *ns, *nr, *pf);
+	  return (frame_type_I);
+	}
+	else if ((c & 2) == 0) {
+
+// Supervisory			rrr p/f ss 0 1		or	0000 ss 0 1  rrrrrrr p/f
+
+	  if (modulo == modulo_128) {
+	    *pf = c2 & 1;
+	    *nr = (c2 >> 1) & 0x7f;
+	  }
+	  else {
+	    *pf = (c >> 4) & 1;
+	    *nr = (c >> 5) & 7;
+	  }
+	  
+	  switch ((c >> 2) & 3) {
+	    case 0: snprintf (desc, DESC_SIZ, "S frame RR, n(r)=%d, p/f=%d", *nr, *pf);   return (frame_type_S_RR);   break;
+	    case 1: snprintf (desc, DESC_SIZ, "S frame RNR, n(r)=%d, p/f=%d", *nr, *pf);  return (frame_type_S_RNR);  break;
+	    case 2: snprintf (desc, DESC_SIZ, "S frame REJ, n(r)=%d, p/f=%d", *nr, *pf);  return (frame_type_S_REJ);  break;
+	    case 3: snprintf (desc, DESC_SIZ, "S frame SREJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_SREJ); break;
+	 } 
+	}
+	else {
+
+// Unnumbered			mmm p/f mm 1 1
+
+	  *pf = (c >> 4) & 1;
+	  
+	  switch (c & 0xef) {
+	
+	    case 0x6f: snprintf (desc, DESC_SIZ, "U frame SABME, p=%d", *pf);  return (frame_type_U_SABME); break;
+	    case 0x2f: snprintf (desc, DESC_SIZ, "U frame SABM, p=%d", *pf);   return (frame_type_U_SABM);  break;
+	    case 0x43: snprintf (desc, DESC_SIZ, "U frame DISC, p=%d", *pf);   return (frame_type_U_DISC);  break;
+	    case 0x0f: snprintf (desc, DESC_SIZ, "U frame DM, f=%d", *pf);     return (frame_type_U_DM);    break;
+	    case 0x63: snprintf (desc, DESC_SIZ, "U frame UA, f=%d", *pf);     return (frame_type_U_UA);    break;
+	    case 0x87: snprintf (desc, DESC_SIZ, "U frame FRMR, f=%d", *pf);   return (frame_type_U_FRMR);  break;
+	    case 0x03: snprintf (desc, DESC_SIZ, "U frame UI, pf=%d", *pf);    return (frame_type_U_UI);    break;
+	    case 0xaf: snprintf (desc, DESC_SIZ, "U frame XID, pf=%d", *pf);   return (frame_type_U_XID);   break;
+	    case 0xe3: snprintf (desc, DESC_SIZ, "U frame TEST, pf=%d", *pf);  return (frame_type_U_TEST);  break;
+	    default:   snprintf (desc, DESC_SIZ, "U frame ???");               return (frame_type_U);       break;
+	  }
+	}
+
+	// Should be unreachable but compiler doesn't realize that.
+	// Here only to suppress "warning: control reaches end of non-void function"
+
+	return (frame_not_AX25);
+
+} /* end ax25_frame_type */
+
+/*------------------------------------------------------------------
+ *
+ * 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.
+ *		
+ *------------------------------------------------------------------*/
+
+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]);
+	  }
+	  for (i=n; i<16; i++) {
+	    dw_printf ("   ");
+	  }
+	  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;
+	}
+}
+
+/* Text description of control octet. */
+
+// TODO: use ax25_frame_type() instead.
+
+static void ctrl_to_text (int c, char *out, size_t outsiz)
+{
+	if      ((c & 1) == 0)       { snprintf (out, outsiz, "I frame: n(r)=%d, p=%d, n(s)=%d",  (c>>5)&7, (c>>4)&1, (c>>1)&7); }
+	else if ((c & 0xf) == 0x01)  { snprintf (out, outsiz, "S frame RR: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xf) == 0x05)  { snprintf (out, outsiz, "S frame RNR: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xf) == 0x09)  { snprintf (out, outsiz, "S frame REJ: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xf) == 0x0D)  { snprintf (out, outsiz, "S frame sREJ: n(r)=%d, p/f=%d",  (c>>5)&7, (c>>4)&1); }
+	else if ((c & 0xef) == 0x6f) { snprintf (out, outsiz, "U frame SABME: p=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x2f) { snprintf (out, outsiz, "U frame SABM: p=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x43) { snprintf (out, outsiz, "U frame DISC: p=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x0f) { snprintf (out, outsiz, "U frame DM: f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x63) { snprintf (out, outsiz, "U frame UA: f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x87) { snprintf (out, outsiz, "U frame FRMR: f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0x03) { snprintf (out, outsiz, "U frame UI: p/f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0xAF) { snprintf (out, outsiz, "U frame XID: p/f=%d", (c>>4)&1); }
+	else if ((c & 0xef) == 0xe3) { snprintf (out, outsiz, "U frame TEST: p/f=%d", (c>>4)&1); }
+	else                         { snprintf (out, outsiz, "Unknown frame type for control = 0x%02x", c); }
+}
+
+/* Text description of protocol id octet. */
+
+#define PID_TEXT_SIZE 80
+
+static void pid_to_text (int p, char out[PID_TEXT_SIZE])
+{
+
+	if      ((p & 0x30) == 0x10) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); }
+	else if ((p & 0x30) == 0x20) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); }
+	else if (p == 0x01)          { snprintf (out, PID_TEXT_SIZE, "ISO 8208/CCITT X.25 PLP"); }
+	else if (p == 0x06)          { snprintf (out, PID_TEXT_SIZE, "Compressed TCP/IP packet. Van Jacobson (RFC 1144)"); }
+	else if (p == 0x07)          { snprintf (out, PID_TEXT_SIZE, "Uncompressed TCP/IP packet. Van Jacobson (RFC 1144)"); }
+	else if (p == 0x08)          { snprintf (out, PID_TEXT_SIZE, "Segmentation fragment"); }
+	else if (p == 0xC3)          { snprintf (out, PID_TEXT_SIZE, "TEXNET datagram protocol"); }
+	else if (p == 0xC4)          { snprintf (out, PID_TEXT_SIZE, "Link Quality Protocol"); }
+	else if (p == 0xCA)          { snprintf (out, PID_TEXT_SIZE, "Appletalk"); }
+	else if (p == 0xCB)          { snprintf (out, PID_TEXT_SIZE, "Appletalk ARP"); }
+	else if (p == 0xCC)          { snprintf (out, PID_TEXT_SIZE, "ARPA Internet Protocol"); }
+	else if (p == 0xCD)          { snprintf (out, PID_TEXT_SIZE, "ARPA Address resolution"); }
+	else if (p == 0xCE)          { snprintf (out, PID_TEXT_SIZE, "FlexNet"); }
+	else if (p == 0xCF)          { snprintf (out, PID_TEXT_SIZE, "NET/ROM"); }
+	else if (p == 0xF0)          { snprintf (out, PID_TEXT_SIZE, "No layer 3 protocol implemented."); }
+	else if (p == 0xFF)          { snprintf (out, PID_TEXT_SIZE, "Escape character. Next octet contains more Level 3 protocol information."); }
+	else                         { snprintf (out, PID_TEXT_SIZE, "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, sizeof(cp_text)); // TODO: use ax25_frame_type() instead.
+
+	  if ( (c & 0x01) == 0 ||				/* I   xxxx xxx0 */
+	     	c == 0x03 || c == 0x13) {			/* UI  000x 0011 */
+
+	    char pid_text[PID_TEXT_SIZE];
+
+	    pid_to_text (p, pid_text);
+
+	    strlcat (cp_text, ", ", sizeof(cp_text));
+	    strlcat (cp_text, pid_text, sizeof(cp_text));
+
+	  }
+
+	  snprintf (l_text, sizeof(l_text), ", length = %d", flen);
+	  strlcat (cp_text, l_text, sizeof(cp_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
+ *
+ * Purpose:	Is this packet APRS format?
+ *
+ * Inputs:	this_p	- pointer to packet object.
+ *		
+ * Returns:	True if this frame has the proper control
+ *		octets for an APRS packet.
+ *			control		3 for UI frame
+ *			protocol id	0xf0 for no layer 3
+ *
+ *
+ * Description:	Dire Wolf should be able to act as a KISS TNC for
+ *		any type of AX.25 activity.  However, there are other
+ *		places where we want to process only APRS.
+ *		(e.g. digipeating and IGate.)
+ *
+ *------------------------------------------------------------------*/
+
+
+int ax25_is_aprs (packet_t this_p) 
+{
+	int ctrl, pid, is_aprs;
+
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	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);
+}
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_get_control
+ 		ax25_get_c2
+ *
+ * Purpose:	Get Control field from packet.
+ *
+ * Inputs:	this_p	- pointer to packet object.
+ *		
+ * Returns:	APRS uses AX25_UI_FRAME.
+ *		This could also be used in other situations.
+ *
+ *------------------------------------------------------------------*/
+
+
+int ax25_get_control (packet_t this_p) 
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	if (this_p->num_addr >= 2) {
+	  return (this_p->frame_data[ax25_get_control_offset(this_p)]);
+	}
+	return (-1);
+}
+
+int ax25_get_c2 (packet_t this_p) 
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	if (this_p->num_addr >= 2) {
+	  return (this_p->frame_data[ax25_get_control_offset(this_p)+1]);
+	}
+	return (-1);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_get_pid
+ *
+ * Purpose:	Get protocol ID from packet.
+ *
+ * Inputs:	this_p	- pointer to packet object.
+ *		
+ * 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."
+ *
+ *------------------------------------------------------------------*/
+
+
+int ax25_get_pid (packet_t this_p) 
+{
+	assert (this_p->magic1 == MAGIC);
+	assert (this_p->magic2 == MAGIC);
+
+	// TODO: handle 2 control byte case.
+	// TODO: sanity check: is it I or UI frame?
+
+	if (this_p->num_addr >= 2) {
+	  return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
+	}
+	return (-1);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_dedupe_crc 
+ * 
+ * Purpose:	Calculate a checksum for the packet source, destination, and
+ *		information but NOT the digipeaters.
+ *		This is used for duplicate detection in the digipeater 
+ *		and IGate algorithms.
+ *
+ * Input:	pp	- Pointer to packet object.
+ *		
+ * Returns:	Value which will be the same for a duplicate but very unlikely 
+ *		to match a non-duplicate packet.
+ *
+ * Description:	For detecting duplicates, we need to look
+ *			+ source station
+ *			+ destination 
+ *			+ information field
+ *		but NOT the changing list of digipeaters.
+ *
+ *		Typically, only a checksum is kept to reduce memory 
+ *		requirements and amount of compution for comparisons.
+ *		There is a very very small probability that two unrelated 
+ *		packets will result in the same checksum, and the
+ *		undesired dropping of the packet.
+ *
+ *		There is a 1 / 65536 chance of getting a false positive match
+ *		which is good enough for this application.
+ *		We could reduce that with a 32 bit CRC instead of reusing
+ *		code from the AX.25 frame CRC calculation.
+ *
+ * Version 1.3:	We exclude any trailing CR/LF at the end of the info part
+ *		so we can detect duplicates that are received only over the
+ *		air and those which have gone thru an IGate where the process
+ *		removes any trailing CR/LF.   Example:
+ *
+ *		Original via RF only:
+ *		W1TG-1>APU25N,N3LEE-10*,WIDE2-1:<IGATE,MSG_CNT=30,LOC_CNT=61<0x0d>
+ *
+ *		When we get the same thing via APRS-IS:
+ *		W1TG-1>APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15:<IGATE,MSG_CNT=30,LOC_CNT=61
+ *
+ *		(Actually there is a trailing space.  Maybe some systems
+ *		change control characters to space???)
+ *		Hmmmm.  I guess we should ignore trailing space as well for 
+ *		duplicate detection and suppression.
+ *		
+ *------------------------------------------------------------------------------*/
+
+unsigned short ax25_dedupe_crc (packet_t pp)
+{
+	unsigned short crc;
+	char src[AX25_MAX_ADDR_LEN];
+	char dest[AX25_MAX_ADDR_LEN];
+	unsigned char *pinfo;
+	int info_len;
+
+	ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
+	ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
+	info_len = ax25_get_info (pp, &pinfo);
+
+	while (info_len >= 1 && (pinfo[info_len-1] == '\r' ||
+	                         pinfo[info_len-1] == '\n' ||
+	                         pinfo[info_len-1] == ' ')) {
+
+	// Temporary for debugging!
+
+	//  if (pinfo[info_len-1] == ' ') {
+	//    text_color_set(DW_COLOR_ERROR);
+	//    dw_printf ("DEBUG:  ax25_dedupe_crc ignoring trailing space.\n");
+	//  }
+
+	  info_len--;
+	}
+
+	crc = 0xffff;
+	crc = crc16((unsigned char *)src, strlen(src), crc);
+	crc = crc16((unsigned char *)dest, strlen(dest), crc);
+	crc = crc16(pinfo, info_len, crc);
+
+	return (crc);
+}
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	ax25_m_m_crc 
+ * 
+ * Purpose:	Calculate a checksum for the packet.
+ *		This is used for the multimodem duplicate detection.
+ *
+ * Input:	pp	- Pointer to packet object.
+ *		
+ * Returns:	Value which will be the same for a duplicate but very unlikely 
+ *		to match a non-duplicate packet.
+ *
+ * Description:	For detecting duplicates, we need to look the entire packet.
+ *
+ *		Typically, only a checksum is kept to reduce memory 
+ *		requirements and amount of compution for comparisons.
+ *		There is a very very small probability that two unrelated 
+ *		packets will result in the same checksum, and the
+ *		undesired dropping of the packet.
+		
+ *------------------------------------------------------------------------------*/
+
+unsigned short ax25_m_m_crc (packet_t pp)
+{
+	unsigned short crc;
+	unsigned char fbuf[AX25_MAX_PACKET_LEN];
+	int flen;
+
+	flen = ax25_pack (pp, fbuf); 
+
+	crc = 0xffff;
+	crc = crc16(fbuf, flen, crc);
+
+	return (crc);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_safe_print
+ *
+ * Purpose:	Print given string, changing non printable characters to 
+ *		hexadecimal notation.   Note that character values
+ *		<DEL>, 28, 29, 30, and 31 can appear in MIC-E message.
+ *
+ * Inputs:	pstr	- Pointer to string.
+ *
+ *		len	- Maximum length if not -1.
+ *
+ *		ascii_only	- Restrict output to only ASCII.
+ *				  Normally we allow UTF-8.
+ *		
+ *		Stops after non-zero len characters or at nul.
+ *
+ * Returns:	none
+ *
+ * Description:	Print a string in a "safe" manner.
+ *		Anything that is not a printable character
+ *		will be converted to a hexadecimal representation.
+ *		For example, a Line Feed character will appear as <0x0a>
+ *		rather than dropping down to the next line on the screen.
+ *
+ *		ax25_from_text can accept this format.
+ *
+ *
+ * Example:	W1MED-1>T2QP0S,N1OHZ,N8VIM*,WIDE1-1:'cQBl <0x1c>-/]<0x0d>
+ *		                                          ------   ------
+ *
+ * Questions:	What should we do about UTF-8?  Should that be displayed
+ *		as hexadecimal for troubleshooting? Maybe an option so the
+ *		packet raw data is in hexadecimal but an extracted 
+ *		comment displays UTF-8?  Or a command line option for only ASCII?
+ *
+ * Trailing space:
+ *		I recently noticed a case where a packet has space character
+ *		at the end.  If the last character of the line is a space,
+ *		this will be displayed in hexadecimal to make it obvious.
+ *			
+ *------------------------------------------------------------------*/
+
+#define MAXSAFE 500
+
+void ax25_safe_print (char *pstr, int len, int ascii_only)
+{
+	int ch;
+	char safe_str[MAXSAFE*6+1];
+	int safe_len;
+
+	safe_len = 0;
+	safe_str[safe_len] = '\0';
+
+
+	if (len < 0) 
+	  len = strlen(pstr);
+
+	if (len > MAXSAFE)
+	  len = MAXSAFE;
+
+	while (len > 0 && *pstr != '\0')
+	{
+	  ch = *((unsigned char *)pstr);
+
+	  if (ch == ' ' && (len == 1 || pstr[1] == '\0')) {
+
+	      snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch);
+	      safe_len += 6;
+	  }
+	  else if (ch < ' ' || ch == 0x7f || ch == 0xfe || ch == 0xff ||
+			(ascii_only && ch >= 0x80) ) {
+
+	      /* Control codes and delete. */
+	      /* UTF-8 does not use fe and ff except in a possible */
+	      /* "Byte Order Mark" (BOM) at the beginning. */
+
+	      snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch);
+	      safe_len += 6;
+	    }
+	  else {
+	    /* Let everything else thru so we can handle UTF-8 */
+	    /* Maybe we should have an option to display 0x80 */
+	    /* and above as hexadecimal. */
+
+	    safe_str[safe_len++] = ch;
+	    safe_str[safe_len] = '\0';
+	  }
+
+	  pstr++;
+	  len--;
+	}
+
+// TODO1.2: should return string rather printing to remove a race condition.
+
+	dw_printf ("%s", safe_str);
+
+} /* end ax25_safe_print */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ax25_alevel_to_text
+ *
+ * Purpose:	Convert audio level to text representation.
+ *
+ * Inputs:	alevel	- Audio levels collected from demodulator.
+ *
+ * Outputs:	text	- Text representation for presentation to user.  
+ *			  Currently it will look something like this:
+ *
+ *				r(m/s)
+ *
+ *			  With n,m,s corresponding to received, mark, and space.
+ *			  Comma is to be avoided because one place this 
+ *			  ends up is in a CSV format file.
+ *
+ *			  size should be AX25_ALEVEL_TO_TEXT_SIZE.
+ *
+ * Returns:	True if something to print.  (currently if alevel.original >= 0)
+ *		False if not.
+ *
+ * Description:	Audio level used to be simple; it was a single number.
+ *		In version 1.2, we start collecting more details.
+ *		At the moment, it includes:
+ *
+ *		- Received level from new method.  
+ *		- Levels from mark & space filters to examine the ratio.
+ *
+ *		We print this in multiple places so put it into a function.
+ *			
+ *------------------------------------------------------------------*/
+
+
+int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE])
+{
+	if (alevel.rec < 0) {
+	  strlcpy (text, "", AX25_ALEVEL_TO_TEXT_SIZE);
+	  return (0);
+	}
+
+// TODO1.2: haven't thought much about non-AFSK cases yet.
+// What should we do for 9600 baud?
+
+// For DTMF omit the two extra numbers.
+
+	if (alevel.mark >= 0 &&  alevel.space < 0) {		/* baseband */
+
+	  snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space);
+	}
+	else if (alevel.mark == -2 &&  alevel.space == -2) {		/* DTMF */
+
+	  snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec);
+	}
+	else {		/* AFSK */
+
+	  //snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d:%d(%d/%d=%05.3f=)", alevel.original, alevel.rec, alevel.mark, alevel.space, alevel.ms_ratio);
+	  snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%d/%d)", alevel.rec, alevel.mark, alevel.space);
+	}
+	return (1);	
+
+} /* end ax25_alevel_to_text */
+
+
+/* end ax25_pad.c */
diff --git a/ax25_pad.h b/ax25_pad.h
index 32ba191..75ede36 100644
--- a/ax25_pad.h
+++ b/ax25_pad.h
@@ -1,370 +1,389 @@
-/*-------------------------------------------------------------------
- *
- * Name:	ax25_pad.h
- *
- * Purpose:	Header file for using ax25_pad.c
- *
- *------------------------------------------------------------------*/
-
-#ifndef AX25_PAD_H
-#define AX25_PAD_H 1
-
-
-#define AX25_MAX_REPEATERS 8
-#define AX25_MIN_ADDRS 2	/* Destinatin & Source. */
-#define AX25_MAX_ADDRS 10	/* Destination, Source, 8 digipeaters. */	
-
-#define AX25_DESTINATION  0	/* Address positions in frame. */
-#define AX25_SOURCE       1	
-#define AX25_REPEATER_1   2
-#define AX25_REPEATER_2   3
-#define AX25_REPEATER_3   4
-#define AX25_REPEATER_4   5
-#define AX25_REPEATER_5   6
-#define AX25_REPEATER_6   7
-#define AX25_REPEATER_7   8
-#define AX25_REPEATER_8   9
-
-#define AX25_MAX_ADDR_LEN 12	/* In theory, you would expect the maximum length */
-				/* to be 6 letters, dash, 2 digits, and nul for a */
-				/* total of 10.  However, object labels can be 10 */
-				/* characters so throw in a couple extra bytes */
-				/* to be safe. */
-
-#define AX25_MIN_INFO_LEN 0	/* Previously 1 when considering only APRS. */
-				
-#define AX25_MAX_INFO_LEN 2048	/* Maximum size for APRS. */
-				/* AX.25 starts out with 256 as the default max */
-				/* length but the end stations can negotiate */
-				/* something different. */
-				/* version 0.8:  Change from 256 to 2028 to */
-				/* handle the larger paclen for Linux AX25. */
-
-				/* These don't include the 2 bytes for the */
-				/* HDLC frame FCS. */
-
-/* 
- * Previously, for APRS only.
- * #define AX25_MIN_PACKET_LEN ( 2 * 7 + 2 + AX25_MIN_INFO_LEN)
- * #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN)
- */
-
-/* The more general case. */
-/* An AX.25 frame can have a control byte and no protocol. */
-
-#define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 )
-
-#define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + 3 + AX25_MAX_INFO_LEN)
-
-
-/*
- * packet_t is a pointer to a packet object.
- *
- * The actual implementation is not visible outside ax25_pad.c.
- */
-
-#define AX25_UI_FRAME 3		/* Control field value. */
-#define AX25_NO_LAYER_3 0xf0	/* protocol ID */
-
-
-
-#ifdef AX25_PAD_C	/* Keep this hidden - implementation could change. */
-
-struct packet_s {
-
-	int magic1;		/* for error checking. */
-	int seq;		/* unique sequence number for debugging. */
-
-#define MAGIC 0x41583235
-
-	struct packet_s *nextp;	/* Pointer to next in queue. */
-
-	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. */
-
-
-
-				/* 
- 				 * The 7th octet of each address contains:
-			         *
-				 * Bits:   H  R  R  SSID  0
-				 *
-				 *   H 		for digipeaters set to 0 intially.
-				 *		Changed to 1 when position has been used.
- 				 *
-				 *		for source & destination it is called
-				 *		command/response and is normally 1.
-				 *
-				 *   R	R	Reserved.  Normally set to 1 1.
-				 *
-				 *   SSID	Substation ID.  Range of 0 - 15.
-				 *
-				 *   0		Usually 0 but 1 for last address.
-				 */
-
-#define SSID_H_MASK	0x80
-#define SSID_H_SHIFT	7
-
-#define SSID_RR_MASK	0x60
-#define SSID_RR_SHIFT	5
-
-#define SSID_SSID_MASK	0x1e
-#define SSID_SSID_SHIFT	1
-
-#define SSID_LAST_MASK	0x01
-
-
-	int frame_len;		/* Frame length without CRC. */
-
-
-	unsigned char frame_data[AX25_MAX_PACKET_LEN+1];
-				/* Raw frame contents, without the CRC. */
-				
-
-	int magic2;		/* Will get stomped on if above overflows. */
-};
-
-
-
-
-#else			/* Public view. */
-
-struct packet_s {
-	int secret;
-};
-
-#endif
-
-
-typedef struct packet_s *packet_t;
-
-
-
-#ifdef AX25_PAD_C	/* Keep this hidden - implementation could change. */
-
-/*
- * APRS always has one control octet of 0x03 but the more
- * general AX.25 case is one or two control bytes depending on
- * "modulo 128 operation" is in effect.  Unfortunately, it seems
- * this can be determined only by examining the XID frames and 
- * keeping this information for each connection.
- * We can assume 1 for our current purposes.
- */
-
-static inline int ax25_get_control_offset (packet_t this_p) 
-{
-	//return (0);
-	return (this_p->num_addr*7);
-}
-
-static inline int ax25_get_num_control (packet_t this_p)
-{
-	return (1);	// TODO: always be 1 for U frame.  More complicated for I and S.
-}
-
-
-
-/*
- * APRS always has one protocol octet of 0xF0 meaning no level 3
- * protocol but the more general case is 0, 1 or 2 protocol ID octets.
- */
-
-static inline int ax25_get_pid_offset (packet_t 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)
-{
-	int c;
-	int pid;
-
-	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->frame_data[ax25_get_pid_offset(this_p)];
-	  if (pid == 0xff) {
-	    return (2);			/* pid 1111 1111 means another follows. */
-	  }
-	  return (1);		
-	}
-	return (0);
-}
-
-
-/*
- * AX.25 has info field for 5 frame types depending on the control field.
- *
- *	xxxx xxx0	I
- *	000x 0011	UI		(which includes APRS)
- *	101x 1111	XID
- *	111x 0011	TEST
- *	100x 0111	FRMR
- *
- * APRS always has an Information field with at least one octet for the Data Type Indicator.  
- */
-
-static inline int ax25_get_info_offset (packet_t 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->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);
-}
-
-#endif
-
-
-typedef enum ax25_modulo_e { modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
-
-typedef enum ax25_frame_type_e {
-
-	frame_type_I,		// Information
-	frame_type_RR,		// Receive Ready - System Ready To Receive
-	frame_type_RNR,		// Receive Not Ready - TNC Buffer Full
-	frame_type_REJ,		// Reject Frame - Out of Sequence or Duplicate
-	frame_type_SREJ,	// Selective Reject - Request single frame repeat
-	frame_type_SABME,	// Set Async Balanced Mode, Extended
-	frame_type_SABM,	// Set Async Balanced Mode
-	frame_type_DISC,	// Disconnect
-	frame_type_DM,		// Disconnect Mode
-	frame_type_UA,		// Unnumbered Acknowledge
-	frame_type_FRMR,	// Frame Reject
-	frame_type_UI,		// Unnumbered Information
-	frame_type_XID,		// Exchange Identification
-	frame_type_TEST,	// Test
-	frame_type_U,		// other Unnumbered, not used by AX.25.
-	frame_not_AX25		// Could not get control byte from frame.
-
-} ax25_frame_type_t;
-	
-
-/* 
- * Originally this was a single number. 
- * Let's try something new in version 1.2.
- * Also collect AGC values from the mark and space filters.
- */
-
-typedef struct alevel_s {
-
-	int rec;
-	int mark;
-	int space;
-	//float ms_ratio;	// TODO: take out after temporary investigation.
-} alevel_t;
-
-
-#define AX25MEMDEBUG 1
-
-
-
-#if AX25MEMDEBUG	// to investigate a memory leak problem
-
-
-extern void ax25memdebug_set(void);
-extern int ax25memdebug_get (void);
-extern int ax25memdebug_seq (packet_t this_p);
-
-
-extern packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line);
-#define ax25_from_text(m,s) ax25_from_text_debug(m,s,__FILE__,__LINE__)
-
-extern packet_t ax25_from_frame_debug (unsigned char *data, int len, alevel_t alevel, char *src_file, int src_line);
-#define ax25_from_frame(d,l,a) ax25_from_frame_debug(d,l,a,__FILE__,__LINE__);
-
-extern packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line);
-#define ax25_dup(p) ax25_dup_debug(p,__FILE__,__LINE__);
-
-extern void ax25_delete_debug (packet_t pp, char *src_file, int src_line);
-#define ax25_delete(p) ax25_delete_debug(p,__FILE__,__LINE__);
-
-#else
-
-extern packet_t ax25_from_text (char *monitor, int strict);
-
-extern packet_t ax25_from_frame (unsigned char *data, int len, alevel_t alevel);
-
-extern packet_t ax25_dup (packet_t copy_from);
-
-extern void ax25_delete (packet_t pp);
-
-#endif
-
-
-extern int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard);
-
-extern packet_t ax25_unwrap_third_party (packet_t from_pp);
-
-extern void ax25_set_addr (packet_t pp, int, char *);
-extern void ax25_insert_addr (packet_t this_p, int n, char *ad);
-extern void ax25_remove_addr (packet_t this_p, int n);
-
-extern int ax25_get_num_addr (packet_t pp);
-extern int ax25_get_num_repeaters (packet_t this_p);
-
-extern void ax25_get_addr_with_ssid (packet_t pp, int n, char *);
-
-extern int ax25_get_ssid (packet_t pp, int n);
-extern void ax25_set_ssid (packet_t this_p, int n, int ssid);
-
-extern int ax25_get_h (packet_t pp, int n);
-
-extern void ax25_set_h (packet_t pp, int n);
-
-extern int ax25_get_heard(packet_t this_p);
-
-extern int ax25_get_first_not_repeated(packet_t pp);
-
-extern int ax25_get_info (packet_t pp, unsigned char **paddr);
-
-extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
-
-extern int ax25_get_dti (packet_t this_p);
-
-extern packet_t ax25_get_nextp (packet_t this_p);
-
-extern void ax25_format_addrs (packet_t pp, char *);
-
-extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
-
-extern ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns); 
-
-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); 
-extern int ax25_get_c2 (packet_t this_p); 
-
-extern int ax25_get_pid (packet_t this_p);
-
-extern unsigned short ax25_dedupe_crc (packet_t pp);
-
-extern unsigned short ax25_m_m_crc (packet_t pp);
-
-extern void ax25_safe_print (char *, int, int ascii_only);
-
-extern int ax25_alevel_to_text (alevel_t alevel, char *text);
-
-
-#endif /* AX25_PAD_H */
-
-/* end ax25_pad.h */
-
-
+/*-------------------------------------------------------------------
+ *
+ * Name:	ax25_pad.h
+ *
+ * Purpose:	Header file for using ax25_pad.c
+ *
+ *------------------------------------------------------------------*/
+
+#ifndef AX25_PAD_H
+#define AX25_PAD_H 1
+
+
+#define AX25_MAX_REPEATERS 8
+#define AX25_MIN_ADDRS 2	/* Destinatin & Source. */
+#define AX25_MAX_ADDRS 10	/* Destination, Source, 8 digipeaters. */	
+
+#define AX25_DESTINATION  0	/* Address positions in frame. */
+#define AX25_SOURCE       1	
+#define AX25_REPEATER_1   2
+#define AX25_REPEATER_2   3
+#define AX25_REPEATER_3   4
+#define AX25_REPEATER_4   5
+#define AX25_REPEATER_5   6
+#define AX25_REPEATER_6   7
+#define AX25_REPEATER_7   8
+#define AX25_REPEATER_8   9
+
+#define AX25_MAX_ADDR_LEN 12	/* In theory, you would expect the maximum length */
+				/* to be 6 letters, dash, 2 digits, and nul for a */
+				/* total of 10.  However, object labels can be 10 */
+				/* characters so throw in a couple extra bytes */
+				/* to be safe. */
+
+#define AX25_MIN_INFO_LEN 0	/* Previously 1 when considering only APRS. */
+				
+#define AX25_MAX_INFO_LEN 2048	/* Maximum size for APRS. */
+				/* AX.25 starts out with 256 as the default max */
+				/* length but the end stations can negotiate */
+				/* something different. */
+				/* version 0.8:  Change from 256 to 2028 to */
+				/* handle the larger paclen for Linux AX25. */
+
+				/* These don't include the 2 bytes for the */
+				/* HDLC frame FCS. */
+
+/* 
+ * Previously, for APRS only.
+ * #define AX25_MIN_PACKET_LEN ( 2 * 7 + 2 + AX25_MIN_INFO_LEN)
+ * #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN)
+ */
+
+/* The more general case. */
+/* An AX.25 frame can have a control byte and no protocol. */
+
+#define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 )
+
+#define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + 3 + AX25_MAX_INFO_LEN)
+
+
+/*
+ * packet_t is a pointer to a packet object.
+ *
+ * The actual implementation is not visible outside ax25_pad.c.
+ */
+
+#define AX25_UI_FRAME 3		/* Control field value. */
+#define AX25_NO_LAYER_3 0xf0	/* protocol ID */
+
+
+
+#ifdef AX25_PAD_C	/* Keep this hidden - implementation could change. */
+
+struct packet_s {
+
+	int magic1;		/* for error checking. */
+
+	int seq;		/* unique sequence number for debugging. */
+
+	double release_time;	/* Time stamp in format returned by dtime_now(). */
+				/* When to release from the SATgate mode delay queue. */
+
+#define MAGIC 0x41583235
+
+	struct packet_s *nextp;	/* Pointer to next in queue. */
+
+	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. */
+
+
+
+				/* 
+ 				 * The 7th octet of each address contains:
+			         *
+				 * Bits:   H  R  R  SSID  0
+				 *
+				 *   H 		for digipeaters set to 0 intially.
+				 *		Changed to 1 when position has been used.
+ 				 *
+				 *		for source & destination it is called
+				 *		command/response and is normally 1.
+				 *
+				 *   R	R	Reserved.  Normally set to 1 1.
+				 *
+				 *   SSID	Substation ID.  Range of 0 - 15.
+				 *
+				 *   0		Usually 0 but 1 for last address.
+				 */
+
+#define SSID_H_MASK	0x80
+#define SSID_H_SHIFT	7
+
+#define SSID_RR_MASK	0x60
+#define SSID_RR_SHIFT	5
+
+#define SSID_SSID_MASK	0x1e
+#define SSID_SSID_SHIFT	1
+
+#define SSID_LAST_MASK	0x01
+
+
+	int frame_len;		/* Frame length without CRC. */
+
+
+	unsigned char frame_data[AX25_MAX_PACKET_LEN+1];
+				/* Raw frame contents, without the CRC. */
+				
+
+	int magic2;		/* Will get stomped on if above overflows. */
+};
+
+
+
+
+#else			/* Public view. */
+
+struct packet_s {
+	int secret;
+};
+
+#endif
+
+
+typedef struct packet_s *packet_t;
+
+
+
+#ifdef AX25_PAD_C	/* Keep this hidden - implementation could change. */
+
+extern packet_t ax25_new (void);
+
+/*
+ * APRS always has one control octet of 0x03 but the more
+ * general AX.25 case is one or two control bytes depending on
+ * "modulo 128 operation" is in effect.  Unfortunately, it seems
+ * this can be determined only by examining the XID frames and 
+ * keeping this information for each connection.
+ * We can assume 1 for our current purposes.
+ */
+
+static inline int ax25_get_control_offset (packet_t this_p) 
+{
+	//return (0);
+	return (this_p->num_addr*7);
+}
+
+static inline int ax25_get_num_control (packet_t this_p)
+{
+	return (1);	// TODO: always be 1 for U frame.  More complicated for I and S.
+}
+
+
+
+/*
+ * APRS always has one protocol octet of 0xF0 meaning no level 3
+ * protocol but the more general case is 0, 1 or 2 protocol ID octets.
+ */
+
+static inline int ax25_get_pid_offset (packet_t 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)
+{
+	int c;
+	int pid;
+
+	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->frame_data[ax25_get_pid_offset(this_p)];
+	  if (pid == 0xff) {
+	    return (2);			/* pid 1111 1111 means another follows. */
+	  }
+	  return (1);		
+	}
+	return (0);
+}
+
+
+/*
+ * AX.25 has info field for 5 frame types depending on the control field.
+ *
+ *	xxxx xxx0	I
+ *	000x 0011	UI		(which includes APRS)
+ *	101x 1111	XID
+ *	111x 0011	TEST
+ *	100x 0111	FRMR
+ *
+ * APRS always has an Information field with at least one octet for the Data Type Indicator.  
+ */
+
+static inline int ax25_get_info_offset (packet_t 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->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);
+}
+
+#endif
+
+
+typedef enum ax25_modulo_e { modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
+
+typedef enum ax25_frame_type_e {
+
+	frame_type_I,		// Information
+
+	frame_type_S_RR,	// Receive Ready - System Ready To Receive
+	frame_type_S_RNR,	// Receive Not Ready - TNC Buffer Full
+	frame_type_S_REJ,	// Reject Frame - Out of Sequence or Duplicate
+	frame_type_S_SREJ,	// Selective Reject - Request single frame repeat
+
+	frame_type_U_SABME,	// Set Async Balanced Mode, Extended
+	frame_type_U_SABM,	// Set Async Balanced Mode
+	frame_type_U_DISC,	// Disconnect
+	frame_type_U_DM,	// Disconnect Mode
+	frame_type_U_UA,	// Unnumbered Acknowledge
+	frame_type_U_FRMR,	// Frame Reject
+	frame_type_U_UI,	// Unnumbered Information
+	frame_type_U_XID,	// Exchange Identification
+	frame_type_U_TEST,	// Test
+	frame_type_U,		// other Unnumbered, not used by AX.25.
+
+	frame_not_AX25		// Could not get control byte from frame.
+
+} ax25_frame_type_t;
+	
+
+/* 
+ * Originally this was a single number. 
+ * Let's try something new in version 1.2.
+ * Also collect AGC values from the mark and space filters.
+ */
+
+typedef struct alevel_s {
+
+	int rec;
+	int mark;
+	int space;
+	//float ms_ratio;	// TODO: take out after temporary investigation.
+} alevel_t;
+
+
+#ifndef AXTEST
+// TODO: remove this?
+#define AX25MEMDEBUG 1
+#endif
+
+
+#if AX25MEMDEBUG	// to investigate a memory leak problem
+
+
+extern void ax25memdebug_set(void);
+extern int ax25memdebug_get (void);
+extern int ax25memdebug_seq (packet_t this_p);
+
+
+extern packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line);
+#define ax25_from_text(m,s) ax25_from_text_debug(m,s,__FILE__,__LINE__)
+
+extern packet_t ax25_from_frame_debug (unsigned char *data, int len, alevel_t alevel, char *src_file, int src_line);
+#define ax25_from_frame(d,l,a) ax25_from_frame_debug(d,l,a,__FILE__,__LINE__);
+
+extern packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line);
+#define ax25_dup(p) ax25_dup_debug(p,__FILE__,__LINE__);
+
+extern void ax25_delete_debug (packet_t pp, char *src_file, int src_line);
+#define ax25_delete(p) ax25_delete_debug(p,__FILE__,__LINE__);
+
+#else
+
+extern packet_t ax25_from_text (char *monitor, int strict);
+
+extern packet_t ax25_from_frame (unsigned char *data, int len, alevel_t alevel);
+
+extern packet_t ax25_dup (packet_t copy_from);
+
+extern void ax25_delete (packet_t pp);
+
+#endif
+
+
+
+
+extern int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard);
+extern int ax25_check_addresses (packet_t pp);
+
+extern packet_t ax25_unwrap_third_party (packet_t from_pp);
+
+extern void ax25_set_addr (packet_t pp, int, char *);
+extern void ax25_insert_addr (packet_t this_p, int n, char *ad);
+extern void ax25_remove_addr (packet_t this_p, int n);
+
+extern int ax25_get_num_addr (packet_t pp);
+extern int ax25_get_num_repeaters (packet_t this_p);
+
+extern void ax25_get_addr_with_ssid (packet_t pp, int n, char *station);
+extern void ax25_get_addr_no_ssid (packet_t pp, int n, char *station);
+
+extern int ax25_get_ssid (packet_t pp, int n);
+extern void ax25_set_ssid (packet_t this_p, int n, int ssid);
+
+extern int ax25_get_h (packet_t pp, int n);
+
+extern void ax25_set_h (packet_t pp, int n);
+
+extern int ax25_get_heard(packet_t this_p);
+
+extern int ax25_get_first_not_repeated(packet_t pp);
+
+extern int ax25_get_info (packet_t pp, unsigned char **paddr);
+
+extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
+
+extern int ax25_get_dti (packet_t this_p);
+
+extern packet_t ax25_get_nextp (packet_t this_p);
+
+extern void ax25_set_release_time (packet_t this_p, double release_time);
+extern double ax25_get_release_time (packet_t this_p);
+
+extern void ax25_format_addrs (packet_t pp, char *);
+
+extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
+
+extern ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns); 
+
+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); 
+extern int ax25_get_c2 (packet_t this_p); 
+
+extern int ax25_get_pid (packet_t this_p);
+
+extern unsigned short ax25_dedupe_crc (packet_t pp);
+
+extern unsigned short ax25_m_m_crc (packet_t pp);
+
+extern void ax25_safe_print (char *, int, int ascii_only);
+
+#define AX25_ALEVEL_TO_TEXT_SIZE 32	// overkill but safe.
+extern int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]);
+
+
+#endif /* AX25_PAD_H */
+
+/* end ax25_pad.h */
+
+
diff --git a/beacon.c b/beacon.c
index 28b0d1b..89ef30d 100644
--- a/beacon.c
+++ b/beacon.c
@@ -1,803 +1,930 @@
-//#define DEBUG 1
-//#define DEBUG_SIM 1
-
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      beacon.c
- *
- * Purpose:   	Transmit messages on a fixed schedule.
- *		
- * Description:	Transmit periodic messages as specified in the config file.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <math.h>
-
-#include <time.h>
-#if __WIN32__
-#include <windows.h>
-#endif
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "tq.h"
-#include "xmit.h"
-#include "config.h"
-#include "digipeater.h"
-#include "version.h"
-#include "encode_aprs.h"
-#include "beacon.h"
-#include "latlong.h"
-#include "dwgps.h"
-#include "log.h"
-#include "dlq.h"
-
-
-
-/* 
- * Are we using GPS data?
- * Incremented if tracker beacons configured.  
- * Cleared if dwgps_init fails.
- */
-
-static int g_using_gps = 0;	
-
-/*
- * Save pointers to configuration settings.
- */
-
-static struct audio_s        *g_modem_config_p;
-static struct misc_config_s  *g_misc_config_p;
-static struct digi_config_s  *g_digi_config_p;
-
-
-
-#if __WIN32__
-static unsigned __stdcall beacon_thread (void *arg);
-#else
-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;
-}
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        beacon_init
- *
- * Purpose:     Initialize the beacon process.
- *
- * Inputs:	pmodem		- Aduio device and modem configuration.
- *				  Used only to find valide channels.
- *		pconfig		- misc. configuration from config file.
- *		pdigi		- digipeater configuration from config file.
- *				TODO: Is this needed?
- *
- *
- * Outputs:	Remember required information for future use.
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *		Start up xmit_thread to actually send the packets
- *		at the appropriate time.
- *
- *--------------------------------------------------------------------*/
-
-
-
-void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct digi_config_s *pdigi)
-{
-	time_t now;
-	int j;
-	int count;
-#if __WIN32__
-	HANDLE beacon_th;
-#else
-	pthread_t beacon_tid;
-#endif
-
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("beacon_init ( ... )\n");
-#endif
-
-
-
-/* 
- * Save parameters for later use.
- */
-	g_modem_config_p = pmodem;
-	g_misc_config_p = pconfig;
-	g_digi_config_p = pdigi;
-
-/*
- * Precompute the packet contents so any errors are 
- * Reported once at start up time rather than for each transmission.
- * If a serious error is found, set type to BEACON_IGNORE and that
- * 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].sendto_chan;
-
-	  if (chan < 0) chan = 0;	/* For IGate, use channel 0 call. */
-
-	  if (g_modem_config_p->achan[chan].valid) {
-
-	    if (strlen(g_modem_config_p->achan[chan].mycall) > 0 && strcasecmp(g_modem_config_p->achan[chan].mycall, "NOCALL") != 0) {
-
-              switch (g_misc_config_p->beacon[j].btype) {
-
-	        case BEACON_OBJECT:
-
-		  /* Object name is required. */
-
-		  if (strlen(g_misc_config_p->beacon[j].objname) == 0) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Config file, line %d: OBJNAME is required for OBEACON.\n", g_misc_config_p->beacon[j].lineno);
-		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-		    continue;
-		  }
-		  /* Fall thru.  Ignore any warning about missing break. */
-
-	        case BEACON_POSITION:
-
-		  /* Location is required. */
-
-		  if (g_misc_config_p->beacon[j].lat == G_UNKNOWN || g_misc_config_p->beacon[j].lon == G_UNKNOWN) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Config file, line %d: Latitude and longitude are required.\n", g_misc_config_p->beacon[j].lineno);
-		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-		    continue;
-		  }	
-		  break;
-
-	        case BEACON_TRACKER:
-
-#if defined(ENABLE_GPS) || defined(DEBUG_SIM)
-		  g_using_gps++;
-#else
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Config file, line %d: GPS tracker feature is not enabled.\n", g_misc_config_p->beacon[j].lineno);
-	   	  g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-	   	  continue;
-#endif
-		  break;
-
-	        case BEACON_CUSTOM:
-
-		  /* INFO is required. */
-
-		  if (g_misc_config_p->beacon[j].custom_info == NULL) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Config file, line %d: INFO is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
-		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-		    continue;
-		  }	
-		  break;
-
-	        case BEACON_IGNORE:
-		  break;
-	      }
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: MYCALL must be set for beacon on channel %d. \n", g_misc_config_p->beacon[j].lineno, chan);
-	      g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-	    }
-	  }
-	  else {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Config file, line %d: Invalid channel number %d for beacon. \n", g_misc_config_p->beacon[j].lineno, chan);
-	    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-	  }
-	}
-
-/*
- * Calculate next time for each beacon.
- */
-
-	now = time(NULL);
-
-	for (j=0; j<g_misc_config_p->num_beacons; j++) {
-#if DEBUG
-
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("beacon[%d] chan=%d, delay=%d, every=%d\n",
-		j,
-		g_misc_config_p->beacon[j].sendto_chan,
-		g_misc_config_p->beacon[j].delay,
-		g_misc_config_p->beacon[j].every);
-#endif
-	  g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay;
-	}
-
-
-/*
- * Connect to GPS receiver if any tracker beacons are configured.
- * If open fails, disable all tracker beacons.
- */
-
-#if DEBUG_SIM
-
-	g_using_gps = 1;
-	
-#elif ENABLE_GPS
-
-	if (g_using_gps > 0) {
-	  int err;
-
-	  err = dwgps_init();
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("All tracker beacons disabled.\n");
-	    g_using_gps = 0;
-
-	    for (j=0; j<g_misc_config_p->num_beacons; j++) {
-              if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
-		g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
-	      }
-	    }
-	  }
-
-	}
-#endif
-
-
-/* 
- * Start up thread for processing only if at least one is valid.
- */
-
-	count = 0;
-	for (j=0; j<g_misc_config_p->num_beacons; j++) {
-          if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
-	    count++;
-	  }
-	}
-
-	if (count >= 1) {
-
-#if __WIN32__
-	  beacon_th = (HANDLE)_beginthreadex (NULL, 0, &beacon_thread, NULL, 0, NULL);
-	  if (beacon_th == NULL) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Could not create beacon thread\n");
-	    return;
-	  }
-#else
-	  int e;
-
-	  e = pthread_create (&beacon_tid, NULL, beacon_thread, (void *)0);
-	  if (e != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    perror("Could not create beacon thread");
-	    return;
-	  }
-#endif
-	}
-
-
-} /* end beacon_init */
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        beacon_thread
- *
- * Purpose:     Transmit beacons when it is time.
- *
- * Inputs:	g_misc_config_p->beacon
- *
- * Outputs:	g_misc_config_p->beacon[].next_time
- *
- * Description:	Go to sleep until it is time for the next beacon.
- *		Transmit any beacons scheduled for now.
- *		Repeat.
- *
- *--------------------------------------------------------------------*/
-
-#define MIN(x,y) ((x) < (y) ? (x) : (y))
-
-
-/* Difference between two angles. */
-
-static inline float heading_change (float a, float b)
-{
-	float diff;
-
-	diff = fabs(a - b);
-	if (diff <= 180.)
-	  return (diff);
-	else
-	  return (360. - diff);
-}
-
-
-#if __WIN32__
-static unsigned __stdcall beacon_thread (void *arg)
-#else
-static void * beacon_thread (void *arg)
-#endif
-{
-	int j;
-	time_t earliest;
-	time_t now;
-
-/*
- * Information from GPS.
- */
-	int fix = 0;			/* 0 = none, 2 = 2D, 3 = 3D */
-	double my_lat = 0;		/* degrees */
-	double my_lon = 0;
-	float  my_course = 0;		/* degrees */
-	float  my_speed_knots = 0;
-	float  my_speed_mph = 0;
-	float  my_alt_m = G_UNKNOWN;		/* meters */
-	int    my_alt_ft = G_UNKNOWN;
-
-/*
- * SmartBeaconing state.
- */
-	time_t sb_prev_time = 0;	/* Time of most recent transmission. */
-	float sb_prev_course = 0;	/* Most recent course reported. */
-	//float sb_prev_speed_mph;	/* Most recent speed reported. */
-	int sb_every;			/* Calculated time between transmissions. */
-
-
-#if DEBUG
-	struct tm tm;
-	char hms[20];
-
-	now = time(NULL);
-	localtime_r (&now, &tm);
-	strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("beacon_thread: started %s\n", hms);
-#endif
-	now = time(NULL);
-
-	while (1) {
-
-	  assert (g_misc_config_p->num_beacons >= 1);
-
-/* 
- * Sleep until time for the earliest scheduled or
- * the soonest we could transmit due to corner pegging.
- */
-	  
-	  earliest = g_misc_config_p->beacon[0].next;
-	  for (j=1; j<g_misc_config_p->num_beacons; j++) {
-	    if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
-	      continue;
-	    earliest = MIN(g_misc_config_p->beacon[j].next, earliest);
-	  }
-
-	  if (g_misc_config_p->sb_configured && g_using_gps) {
-	    earliest = MIN(now + g_misc_config_p->sb_turn_time, earliest);
-            earliest = MIN(now + g_misc_config_p->sb_fast_rate, earliest);
-	  }
-
-	  if (earliest > now) {
-	    SLEEP_SEC (earliest - now);
-	  }
-
-/*
- * Woke up.  See what needs to be done.
- */
-	  now = time(NULL);
-
-#if DEBUG
-	  localtime_r (&now, &tm);
-	  strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("beacon_thread: woke up %s\n", hms);
-#endif
-
-/*
- * Get information from GPS if being used.
- * This needs to be done before the next scheduled tracker
- * beacon because corner pegging make it sooner. 
- */
-
-#if DEBUG_SIM
-	  FILE *fp;
-	  char cs[40];
-
-	  fp = fopen ("c:\\cygwin\\tmp\\cs", "r");
-	  if (fp != NULL) {
-	    fscanf (fp, "%f %f", &my_course, &my_speed_knots);
-	    fclose (fp);
-	  }
-	  else {
-	    fprintf (stderr, "Can't read /tmp/cs.\n");
-	  }
-	  fix = 3;
-	  my_speed_mph = DW_KNOTS_TO_MPH(my_speed_knots);
-	  my_lat = 42.99;
-	  my_lon = 71.99;
-	  my_alt_m = 100;
-#else
-	  if (g_using_gps) {
-
-	    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. */
-	  }
-#endif
-
-/*
- * Run SmartBeaconing calculation if configured and GPS data available.
- */
-	  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;
-	      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
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("SB: fast %d %d slow %d %d speed=%.1f every=%d\n",
-			g_misc_config_p->sb_fast_speed, g_misc_config_p->sb_fast_rate,
-			g_misc_config_p->sb_slow_speed, g_misc_config_p->sb_slow_rate,
-			my_speed_mph, sb_every);
-#endif 
-	
-/*
- * Test for "Corner Pegging" if moving.
- */
-	    if (my_speed_mph >= 1.0) {
-	      int turn_threshold = g_misc_config_p->sb_turn_angle + 
-			g_misc_config_p->sb_turn_slope / my_speed_mph;
-
-#if DEBUG_SIM
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("SB-moving: course %.0f  prev %.0f  thresh %d\n",
-		my_course, sb_prev_course, turn_threshold);
-#endif 
-	      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) {
-		    g_misc_config_p->beacon[j].next = now;
-	          }
-	        }
-	      }  /* significant change in direction */
-	    }  /* is moving */
-	  }  /* apply SmartBeaconing */
-	    
-      
-	  for (j=0; j<g_misc_config_p->num_beacons; j++) {
-
-	    if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
-	      continue;
-
-	    if (g_misc_config_p->beacon[j].next <= now) {
-
-	      int strict = 1;	/* Strict packet checking because they will go over air. */
-	      char stemp[20];
-	      char info[AX25_MAX_INFO_LEN];
-	      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.
- * This could potentially be different on different channels.
- * 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");
-
-	      assert (g_misc_config_p->beacon[j].sendto_chan >= 0);
-
-	      strcpy (mycall, g_modem_config_p->achan[g_misc_config_p->beacon[j].sendto_chan].mycall);
-	      
-	      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);
-		continue;
-	      }
-
-/* 
- * Prepare the monitor format header. 
- *
- * 	src > dest [ , via ]
- */
-
-	      strcpy (beacon_text, mycall);
-	      strcat (beacon_text, ">");
-
-	      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);
-	      }
-	      strcat (beacon_text, ":");
-
-/* 
- * Add the info part depending on beacon type. 
- */
-	      switch (g_misc_config_p->beacon[j].btype) {
-
-		case BEACON_POSITION:
-
-		  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 */	
-			g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
-			g_misc_config_p->beacon[j].comment,
-			info);
-		  strcat (beacon_text, info);
-	          g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
-		  break;
-
-		case BEACON_OBJECT:
-
-		  encode_object (g_misc_config_p->beacon[j].objname, g_misc_config_p->beacon[j].compress, 0, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, 
-			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 */
-			g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, g_misc_config_p->beacon[j].comment,
-			info);
-		  strcat (beacon_text, info);
-	          g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
-		  break;
-
-		case BEACON_TRACKER:
-
-		  if (fix >= 2) {
-		    int coarse;		/* APRS encoder wants 1 - 360.  */
-					/* 0 means none or unknown. */
-
-		    coarse = (int)roundf(my_course);
-		    if (coarse == 0) {
-		      coarse = 360;
-		    }
-		    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),	
-			g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
-			g_misc_config_p->beacon[j].comment,
-			info);
-		    strcat (beacon_text, info);
-
-		    /* Remember most recent tracker beacon. */
-
-		    sb_prev_time = now;
-		    sb_prev_course = my_course;
-		    //sb_prev_speed_mph = my_speed_mph;
-
-		    /* Calculate time for next transmission. */
-	            if (g_misc_config_p->sb_configured) {
-	              g_misc_config_p->beacon[j].next = now + sb_every;
-	            }
-	            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;
-		      alevel_t alevel;
-
-		      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. */
-		      memset (&alevel, 0, sizeof(alevel));
-		      log_write (999, &A, NULL, alevel, 0);
-		    }
-	 	  }
-	          else {
-		    g_misc_config_p->beacon[j].next = now + 2;
-	            continue;   /* No fix.  Try again in a couple seconds. */
-		  }
-		  break;
-
-		case BEACON_CUSTOM:
-
-		  if (g_misc_config_p->beacon[j].custom_info != NULL) {
-	            strcat (beacon_text, g_misc_config_p->beacon[j].custom_info);
-		  }
-		  else {
-		    text_color_set(DW_COLOR_ERROR);
-	    	    dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__);
-	            continue;
-	          }
-	          g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
-		  break;
-
-		case BEACON_IGNORE:		
-	        default:
-		  break;
-
-	      } /* switch beacon type. */
-
-/*
- * Parse monitor format into form for transmission.
- */	
-	      pp = ax25_from_text (beacon_text, strict);
-
-              if (pp != NULL) {
-
-		/* Send to desired destination. */
-
-	        alevel_t alevel;
-
-
-	        switch (g_misc_config_p->beacon[j].sendto_type) {
-
-	          case SENDTO_IGATE:
-
-
-#if 1
-	  	    text_color_set(DW_COLOR_XMIT);
-	  	    dw_printf ("[ig] %s\n", beacon_text);
-#endif
-		    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:
-
-	            /* Simulated reception. */
-
-		    memset (&alevel, 0xff, sizeof(alevel));
-	            dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, pp, alevel, 0, "");
-	            break; 
-		}
-	      }
-	      else {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", g_misc_config_p->beacon[j].lineno);
-	        dw_printf ("%s\n", beacon_text);
-	      }
-
-	    }  /* if time to send it */
-
-	  }  /* for each configured beacon */
-
-	}  /* do forever */
-
-} /* end beacon_thread */
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      beacon.c
+ *
+ * Purpose:   	Transmit messages on a fixed schedule.
+ *		
+ * Description:	Transmit periodic messages as specified in the config file.
+ *
+ *---------------------------------------------------------------*/
+
+//#define DEBUG 1
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "tq.h"
+#include "xmit.h"
+#include "config.h"
+#include "version.h"
+#include "encode_aprs.h"
+#include "beacon.h"
+#include "latlong.h"
+#include "dwgps.h"
+#include "log.h"
+#include "dlq.h"
+#include "aprs_tt.h"		// for dw_run_cmd - should relocate someday.
+
+
+#if __WIN32__
+
+/* 
+ * Windows doesn't have localtime_r.
+ * It should have the equivalent localtime_s, with opposite parameter
+ * order,  but I get undefined reference when trying to use it.
+ */
+
+struct tm *localtime_r(time_t *clock, struct tm *res)
+{
+	struct tm *tm;
+
+	tm = localtime (clock);
+	memcpy (res, tm, sizeof(struct tm));
+	return (res);
+}
+
+#endif
+
+
+/*
+ * Save pointers to configuration settings.
+ */
+
+static struct audio_s        *g_modem_config_p;
+static struct misc_config_s  *g_misc_config_p;
+
+
+#if __WIN32__
+static unsigned __stdcall beacon_thread (void *arg);
+#else
+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;
+}
+
+static time_t sb_calculate_next_time (time_t now,
+			float current_speed_mph, float current_course,
+			time_t last_xmit_time, float last_xmit_course);
+
+static void beacon_send (int j, dwgps_info_t *gpsinfo);
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        beacon_init
+ *
+ * Purpose:     Initialize the beacon process.
+ *
+ * Inputs:	pmodem		- Audio device and modem configuration.
+ *				  Used only to find valid channels.
+ *
+ *		pconfig		- misc. configuration from config file.
+ *
+ *
+ * Outputs:	Remember required information for future use.
+ *
+ * Description:	Do some validity checking on the beacon configuration.
+ *
+ *		Start up beacon_thread to actually send the packets
+ *		at the appropriate time.
+ *
+ *--------------------------------------------------------------------*/
+
+
+
+void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig)
+{
+	time_t now;
+	int j;
+	int count;
+#if __WIN32__
+	HANDLE beacon_th;
+#else
+	pthread_t beacon_tid;
+#endif
+
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("beacon_init ( ... )\n");
+#endif
+
+
+
+/* 
+ * Save parameters for later use.
+ */
+	g_modem_config_p = pmodem;
+	g_misc_config_p = pconfig;
+
+/*
+ * Precompute the packet contents so any errors are 
+ * Reported once at start up time rather than for each transmission.
+ * If a serious error is found, set type to BEACON_IGNORE and that
+ * 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].sendto_chan;
+
+	  if (chan < 0) chan = 0;	/* For IGate, use channel 0 call. */
+
+	  if (g_modem_config_p->achan[chan].valid) {
+
+	    if (strlen(g_modem_config_p->achan[chan].mycall) > 0 &&
+			 strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 &&
+			 strcasecmp(g_modem_config_p->achan[chan].mycall, "NOCALL") != 0) {
+
+              switch (g_misc_config_p->beacon[j].btype) {
+
+	        case BEACON_OBJECT:
+
+		  /* Object name is required. */
+
+		  if (strlen(g_misc_config_p->beacon[j].objname) == 0) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Config file, line %d: OBJNAME is required for OBEACON.\n", g_misc_config_p->beacon[j].lineno);
+		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
+		    continue;
+		  }
+		  /* Fall thru.  Ignore any warning about missing break. */
+
+	        case BEACON_POSITION:
+
+		  /* Location is required. */
+
+		  if (g_misc_config_p->beacon[j].lat == G_UNKNOWN || g_misc_config_p->beacon[j].lon == G_UNKNOWN) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Config file, line %d: Latitude and longitude are required.\n", g_misc_config_p->beacon[j].lineno);
+		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
+		    continue;
+		  }	
+		  break;
+
+	        case BEACON_TRACKER:
+
+	          {
+	            dwgps_info_t gpsinfo;
+	            dwfix_t fix;
+
+	            fix = dwgps_read (&gpsinfo);
+		    if (fix == DWFIX_NOT_INIT) {
+
+	              text_color_set(DW_COLOR_ERROR);
+	              dw_printf ("Config file, line %d: GPS must be configured to use TBEACON.\n", g_misc_config_p->beacon[j].lineno);
+	              g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
+	            }
+	          }
+		  break;
+
+	        case BEACON_CUSTOM:
+
+		  /* INFO or INFOCMD is required. */
+
+		  if (g_misc_config_p->beacon[j].custom_info == NULL && g_misc_config_p->beacon[j].custom_infocmd == NULL) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Config file, line %d: INFO or INFOCMD is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
+		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
+		    continue;
+		  }	
+		  break;
+
+	        case BEACON_IGNORE:
+		  break;
+	      }
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: MYCALL must be set for beacon on channel %d. \n", g_misc_config_p->beacon[j].lineno, chan);
+	      g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
+	    }
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file, line %d: Invalid channel number %d for beacon. \n", g_misc_config_p->beacon[j].lineno, chan);
+	    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
+	  }
+	}
+
+/*
+ * Calculate first time for each beacon from the 'delay' value.
+ */
+
+	now = time(NULL);
+
+	for (j=0; j<g_misc_config_p->num_beacons; j++) {
+#if DEBUG
+
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("beacon[%d] chan=%d, delay=%d, every=%d\n",
+		j,
+		g_misc_config_p->beacon[j].sendto_chan,
+		g_misc_config_p->beacon[j].delay,
+		g_misc_config_p->beacon[j].every);
+#endif
+	  g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay;
+	}
+
+
+/* 
+ * Start up thread for processing only if at least one is valid.
+ */
+
+	count = 0;
+	for (j=0; j<g_misc_config_p->num_beacons; j++) {
+          if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
+	    count++;
+	  }
+	}
+
+	if (count >= 1) {
+
+#if __WIN32__
+	  beacon_th = (HANDLE)_beginthreadex (NULL, 0, &beacon_thread, NULL, 0, NULL);
+	  if (beacon_th == NULL) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Could not create beacon thread\n");
+	    return;
+	  }
+#else
+	  int e;
+
+	  e = pthread_create (&beacon_tid, NULL, beacon_thread, (void *)0);
+	  if (e != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    perror("Could not create beacon thread");
+	    return;
+	  }
+#endif
+	}
+
+
+} /* end beacon_init */
+
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        beacon_thread
+ *
+ * Purpose:     Transmit beacons when it is time.
+ *
+ * Inputs:	g_misc_config_p->beacon
+ *
+ * Outputs:	g_misc_config_p->beacon[].next_time
+ *
+ * Description:	Go to sleep until it is time for the next beacon.
+ *		Transmit any beacons scheduled for now.
+ *		Repeat.
+ *
+ *--------------------------------------------------------------------*/
+
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+
+
+
+#if __WIN32__
+static unsigned __stdcall beacon_thread (void *arg)
+#else
+static void * beacon_thread (void *arg)
+#endif
+{
+	int j;				/* Index into array of beacons. */
+	time_t earliest;
+	time_t now;			/* Current time. */
+	int number_of_tbeacons;		/* Number of tracker beacons. */
+
+
+/*
+ * SmartBeaconing state.
+ */
+	time_t sb_prev_time = 0;	/* Time of most recent transmission. */
+	float sb_prev_course = 0;	/* Most recent course reported. */
+
+
+#if DEBUG
+	struct tm tm;
+	char hms[20];
+
+	now = time(NULL);
+
+	localtime_r (&now, &tm);
+
+	strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("beacon_thread: started %s\n", hms);
+#endif
+
+/*
+ * See if any tracker beacons are configured.
+ * No need to obtain GPS data if none.
+ */
+
+	number_of_tbeacons = 0;
+	for (j=0; j<g_misc_config_p->num_beacons; j++) {
+	  if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
+	    number_of_tbeacons++;
+	  }
+	}
+
+	now = time(NULL);
+
+	while (1) {
+
+	  dwgps_info_t gpsinfo;
+
+/* 
+ * Sleep until time for the earliest scheduled or
+ * the soonest we could transmit due to corner pegging.
+ */
+	  
+	  earliest = now + 60 * 60;
+	  for (j=0; j<g_misc_config_p->num_beacons; j++) {
+	    if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
+	      earliest = MIN(g_misc_config_p->beacon[j].next, earliest);
+	    }
+	  }
+
+	  if (g_misc_config_p->sb_configured && number_of_tbeacons > 0) {
+	    earliest = MIN(now + g_misc_config_p->sb_turn_time, earliest);
+            earliest = MIN(now + g_misc_config_p->sb_fast_rate, earliest);
+	  }
+
+	  if (earliest > now) {
+	    SLEEP_SEC (earliest - now);
+	  }
+
+/*
+ * Woke up.  See what needs to be done.
+ */
+	  now = time(NULL);
+
+#if DEBUG
+	  localtime_r (&now, &tm);
+	  strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("beacon_thread: woke up %s\n", hms);
+#endif
+
+/*
+ * Get information from GPS if being used.
+ * This needs to be done before the next scheduled tracker
+ * beacon because corner pegging make it sooner. 
+ */
+
+	  if (number_of_tbeacons > 0) {
+
+	    dwfix_t fix = dwgps_read (&gpsinfo);
+	    float my_speed_mph = DW_KNOTS_TO_MPH(gpsinfo.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, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track, gpsinfo.altitude);
+	      }
+	      else if (fix == 2) {
+	        dw_printf ("%s  2D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track);
+	      }
+	      else {
+	        dw_printf ("%s  No GPS fix\n", hms);
+	      }
+	    }
+
+	    /* Don't complain here for no fix. */
+	    /* Possibly at the point where about to transmit. */
+
+/*
+ * Run SmartBeaconing calculation if configured and GPS data available.
+ */
+	    if (g_misc_config_p->sb_configured && fix >= DWFIX_2D) {
+
+	      time_t tnext = sb_calculate_next_time (now, 
+			DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track,
+			sb_prev_time, sb_prev_course);
+
+	      for (j=0; j<g_misc_config_p->num_beacons; j++) {
+	        if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
+	          /* Haven't thought about the consequences of SmartBeaconing */
+	          /* and having more than one tbeacon configured. */
+	          if (tnext < g_misc_config_p->beacon[j].next) {
+	             g_misc_config_p->beacon[j].next = tnext;
+	          }
+	        }
+	      }  /* Update next time if sooner. */
+	    }  /* apply SmartBeaconing */
+	  }  /* tbeacon(s) configured. */
+
+/*
+ * Send if the time has arrived.
+ */
+	  for (j=0; j<g_misc_config_p->num_beacons; j++) {
+
+	    if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
+	      continue;
+
+	    if (g_misc_config_p->beacon[j].next <= now) {
+
+	      /* Send the beacon. */
+
+	      beacon_send (j, &gpsinfo);
+
+	      /* Calculate when the next one should be sent. */
+	      /* Easy for fixed interval.  SmartBeaconing takes more effort. */
+
+	      if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
+
+	        if (gpsinfo.fix < DWFIX_2D) {
+	          /* Fix not available so beacon was not sent. */
+	          /* Try again in a couple seconds. */
+
+	          g_misc_config_p->beacon[j].next = now + 2;
+	        }
+	        else if (g_misc_config_p->sb_configured) {
+
+		  /* Remember most recent tracker beacon. */
+	          /* Compute next time if not turning. */
+
+		  sb_prev_time = now;
+		  sb_prev_course = gpsinfo.track;
+
+	          g_misc_config_p->beacon[j].next = sb_calculate_next_time (now, 
+			DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track,
+			sb_prev_time, sb_prev_course);
+	        }
+	        else {
+	          g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
+	        }
+	      }
+	      else {
+	        /* non-tracker beacons are at fixed spacing. */
+
+	        g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
+	      }
+
+	    }  /* if time to send it */
+
+	  }  /* for each configured beacon */
+
+	}  /* do forever */
+
+#if __WIN32__
+	return(0);	/* unreachable but warning if not here. */
+#else 
+	return(NULL);
+#endif
+
+} /* end beacon_thread */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        sb_calculate_next_time
+ *
+ * Purpose:     Calculate next transmission time using the SmartBeaconing algorithm.
+ *
+ * Inputs:	now			- Current time.
+ *
+ *		current_speed_mph	- Current speed from GPS.
+ *				  	  Not expecting G_UNKNOWN but should check for it.
+ *
+ *		current_course		- Current direction of travel.
+ *				  	  Could be G_UNKNOWN if stationary.
+ *
+ *		last_xmit_time		- Time of most recent transmission.
+ *
+ *		last_xmit_course	- Direction included in most recent transmission.
+ *
+ * Global In:	g_misc_config_p->
+ *			sb_configured	TRUE if SmartBeaconing is configured.
+ *			sb_fast_speed	MPH
+ *			sb_fast_rate	seconds
+ *			sb_slow_speed	MPH
+ *			sb_slow_rate	seconds
+ *			sb_turn_time	seconds
+ *			sb_turn_angle	degrees
+ *			sb_turn_slope	degrees * MPH
+ *
+ * Returns:	Time of next transmission.
+ *		Could vary from now to sb_slow_rate in the future.
+ *
+ * Caution:	The algorithm is defined in MPH units.    GPS uses knots.
+ *		The caller must be careful about using the proper conversions.
+ *
+ *--------------------------------------------------------------------*/
+
+/* Difference between two angles. */
+
+static float heading_change (float a, float b)
+{
+	float diff;
+
+	diff = fabs(a - b);
+	if (diff <= 180.)
+	  return (diff);
+	else
+	  return (360. - diff);
+}
+
+static time_t sb_calculate_next_time (time_t now,
+			float current_speed_mph, float current_course,
+			time_t last_xmit_time, float last_xmit_course)
+{
+	int beacon_rate;
+	time_t next_time;
+
+/*
+ * Compute time between beacons for travelling in a straight line.
+ */
+
+	if (current_speed_mph == G_UNKNOWN) {
+	  beacon_rate = (int)roundf((g_misc_config_p->sb_fast_rate + g_misc_config_p->sb_slow_rate) / 2.);
+	}
+	else if (current_speed_mph > g_misc_config_p->sb_fast_speed) {
+	  beacon_rate = g_misc_config_p->sb_fast_rate;
+	}
+	else if (current_speed_mph < g_misc_config_p->sb_slow_speed) {
+	  beacon_rate = g_misc_config_p->sb_slow_rate;
+	}
+	else {
+	  /* Can't divide by 0 assuming sb_slow_speed > 0. */
+	  beacon_rate = (int)roundf(( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / current_speed_mph);
+	}
+
+	if (g_tracker_debug_level >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("SmartBeaconing: Beacon Rate = %d seconds for %.1f MPH\n", beacon_rate, current_speed_mph);
+	}
+
+	next_time = last_xmit_time + beacon_rate;
+
+/*
+ * Test for "Corner Pegging" if moving.
+ */
+	if (current_speed_mph != G_UNKNOWN && current_speed_mph >= 1.0 &&
+		current_course != G_UNKNOWN && last_xmit_course != G_UNKNOWN) {
+
+	  float change = heading_change(current_course, last_xmit_course);
+	  float turn_threshold = g_misc_config_p->sb_turn_angle +
+			g_misc_config_p->sb_turn_slope / current_speed_mph;
+
+	  if (change > turn_threshold &&
+		  now >= last_xmit_time + g_misc_config_p->sb_turn_time) {
+
+	    if (g_tracker_debug_level >= 2) {
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("SmartBeaconing: Send now for heading change of %.0f\n", change);
+	    }
+
+	    next_time = now;
+	  }
+	}
+
+	return (next_time);
+
+} /* end sb_calculate_next_time */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        beacon_send
+ *
+ * Purpose:     Transmit one beacon after it was determined to be time.
+ *
+ * Inputs:	j			Index into beacon configuration array below.
+ *
+ *		gpsinfo			Information from GPS.  Used only for TBEACON.
+ *
+ * Global In:	g_misc_config_p->beacon		Array of beacon configurations.
+ *
+ * Outputs:	Destination(s) specified:
+ *		 - Transmit queue.
+ *		 - IGate.
+ *		 - Simulated reception.
+ *
+ * Description:	Prepare text in monitor format.
+ *		Convert to packet object.
+ *		Send to desired destination(s).
+ *
+ *--------------------------------------------------------------------*/
+
+static void beacon_send (int j, dwgps_info_t *gpsinfo)
+{
+
+
+	      int strict = 1;	/* Strict packet checking because they will go over air. */
+	      char stemp[20];
+	      char info[AX25_MAX_INFO_LEN];
+	      char beacon_text[AX25_MAX_PACKET_LEN];
+	      packet_t pp = NULL;
+	      char mycall[AX25_MAX_ADDR_LEN];
+
+	      char super_comment[AX25_MAX_INFO_LEN];	// Fixed part + any dynamic part.
+
+/*
+ * Obtain source call for the beacon.
+ * This could potentially be different on different channels.
+ * 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.
+ */
+	      strlcpy (mycall, "NOCALL", sizeof(mycall));
+
+	      assert (g_misc_config_p->beacon[j].sendto_chan >= 0);
+
+	      strlcpy (mycall, g_modem_config_p->achan[g_misc_config_p->beacon[j].sendto_chan].mycall, sizeof(mycall));
+	      
+	      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);
+		return;
+	      }
+
+/* 
+ * Prepare the monitor format header. 
+ *
+ * 	src > dest [ , via ]
+ */
+
+	      strlcpy (beacon_text, mycall, sizeof(beacon_text));
+	      strlcat (beacon_text, ">", sizeof(beacon_text));
+
+	      if (g_misc_config_p->beacon[j].dest != NULL) {
+	        strlcat (beacon_text, g_misc_config_p->beacon[j].dest, sizeof(beacon_text));
+	      } 
+	      else {
+	         snprintf (stemp, sizeof(stemp), "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
+	         strlcat (beacon_text, stemp, sizeof(beacon_text));
+	      }
+
+	      if (g_misc_config_p->beacon[j].via != NULL) {
+	        strlcat (beacon_text, ",", sizeof(beacon_text));
+	        strlcat (beacon_text, g_misc_config_p->beacon[j].via, sizeof(beacon_text));
+	      }
+	      strlcat (beacon_text, ":", sizeof(beacon_text));
+
+
+/*
+ * If the COMMENTCMD option was specified, run specified command to get variable part.
+ * Result is any fixed part followed by any variable part.
+ */
+
+// TODO: test & document.
+
+	      strlcpy (super_comment, "", sizeof(super_comment));
+	      if (g_misc_config_p->beacon[j].comment != NULL) {
+	        strlcpy (super_comment, g_misc_config_p->beacon[j].comment, sizeof(super_comment));
+	      }
+
+	      if (g_misc_config_p->beacon[j].commentcmd != NULL) {
+	        char var_comment[AX25_MAX_INFO_LEN];
+	        int k;
+
+	        /* Run given command to get variable part of comment. */
+
+	        k = dw_run_cmd (g_misc_config_p->beacon[j].commentcmd, 2, var_comment, sizeof(var_comment));
+	        if (k > 0) {
+	          strlcat (super_comment, var_comment, sizeof(super_comment));
+	        }
+	        else {
+		  text_color_set(DW_COLOR_ERROR);
+	    	  dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", g_misc_config_p->beacon[j].lineno);
+	        }
+	      }
+
+
+/* 
+ * Add the info part depending on beacon type. 
+ */
+	      switch (g_misc_config_p->beacon[j].btype) {
+
+		case BEACON_POSITION:
+
+		  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, 0,
+			(int)roundf(DW_METERS_TO_FEET(g_misc_config_p->beacon[j].alt_m)),
+			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,
+			G_UNKNOWN, G_UNKNOWN, /* course, speed */
+			g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
+			super_comment,
+			info, sizeof(info));
+		  strlcat (beacon_text, info, sizeof(beacon_text));
+		  break;
+
+		case BEACON_OBJECT:
+
+		  encode_object (g_misc_config_p->beacon[j].objname, g_misc_config_p->beacon[j].compress, 0, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, 0,
+			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,
+			G_UNKNOWN, G_UNKNOWN, /* course, speed */
+			g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, super_comment,
+			info, sizeof(info));
+		  strlcat (beacon_text, info, sizeof(beacon_text));
+		  break;
+
+		case BEACON_TRACKER:
+
+		  if (gpsinfo->fix >= DWFIX_2D) {
+
+		    int coarse;		/* Round to nearest integer. retaining unknown state. */
+	            int my_alt_ft;
+
+	            /* Transmit altitude only if user asked for it. */
+		    /* A positive altitude in the config file enables */
+	            /* transmission of altitude from GPS. */
+
+	            my_alt_ft = G_UNKNOWN;
+	            if (gpsinfo->fix >= 3 && gpsinfo->altitude != G_UNKNOWN && g_misc_config_p->beacon[j].alt_m > 0) {
+	              my_alt_ft = (int)roundf(DW_METERS_TO_FEET(gpsinfo->altitude));
+	            }
+
+		    coarse = G_UNKNOWN;
+		    if (gpsinfo->track != G_UNKNOWN) {
+	              coarse = (int)roundf(gpsinfo->track);
+	            }
+
+		    encode_position (g_misc_config_p->beacon[j].messaging, g_misc_config_p->beacon[j].compress, 
+			gpsinfo->dlat, gpsinfo->dlon, 0, 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(gpsinfo->speed_knots),
+			g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
+			super_comment,
+			info, sizeof(info));
+		    strlcat (beacon_text, info, sizeof(beacon_text));
+
+		    /* 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;
+		      alevel_t alevel;
+
+		      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;
+
+		      strlcpy (A.g_src, mycall, sizeof(A.g_src));
+		      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 = gpsinfo->dlat;
+		      A.g_lon = gpsinfo->dlon;
+		      A.g_speed_mph = DW_KNOTS_TO_MPH(gpsinfo->speed_knots);
+		      A.g_course = coarse;
+		      A.g_altitude_ft = DW_METERS_TO_FEET(gpsinfo->altitude);
+
+		      /* Fake channel of 999 to distinguish from real data. */
+		      memset (&alevel, 0, sizeof(alevel));
+		      log_write (999, &A, NULL, alevel, 0);
+		    }
+	 	  }
+	          else {
+	            return;   /* No fix.  Skip this time. */
+		  }
+		  break;
+
+		case BEACON_CUSTOM:
+
+		  if (g_misc_config_p->beacon[j].custom_info != NULL) {
+
+		    /* Fixed handcrafted text. */
+
+	            strlcat (beacon_text, g_misc_config_p->beacon[j].custom_info, sizeof(beacon_text));
+		  }
+		  else if (g_misc_config_p->beacon[j].custom_infocmd != NULL) {
+		    char info_part[AX25_MAX_INFO_LEN];
+		    int k;
+
+	            /* Run given command to obtain the info part for packet. */
+
+		    k = dw_run_cmd (g_misc_config_p->beacon[j].custom_infocmd, 2, info_part, sizeof(info_part));
+		    if (k > 0) {
+	              strlcat (beacon_text, info_part, sizeof(beacon_text));
+	            }
+	            else {
+		      text_color_set(DW_COLOR_ERROR);
+	    	      dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", g_misc_config_p->beacon[j].lineno);
+		      strlcpy (beacon_text, "", sizeof(beacon_text));  // abort!
+	            }
+		  }
+		  else {
+		    text_color_set(DW_COLOR_ERROR);
+	    	    dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__);
+		    strlcpy (beacon_text, "", sizeof(beacon_text));  // abort!
+	          }
+		  break;
+
+		case BEACON_IGNORE:		
+	        default:
+		  break;
+
+	      } /* switch beacon type. */
+
+/*
+ * Parse monitor format into form for transmission.
+ */	
+	      if (strlen(beacon_text) == 0) {
+		return;
+	      }
+	      
+	      pp = ax25_from_text (beacon_text, strict);
+
+              if (pp != NULL) {
+
+		/* Send to desired destination. */
+
+	        alevel_t alevel;
+
+
+	        switch (g_misc_config_p->beacon[j].sendto_type) {
+
+	          case SENDTO_IGATE:
+
+	  	    text_color_set(DW_COLOR_XMIT);
+	  	    dw_printf ("[ig] %s\n", beacon_text);
+
+		    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:
+
+	            /* Simulated reception. */
+
+		    memset (&alevel, 0xff, sizeof(alevel));
+	            dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, "");
+	            break; 
+		}
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", g_misc_config_p->beacon[j].lineno);
+	        dw_printf ("%s\n", beacon_text);
+	      }
+
+} /* end beacon_send */
+
+
 /* end beacon.c */
diff --git a/beacon.h b/beacon.h
index be95408..1dce32b 100644
--- a/beacon.h
+++ b/beacon.h
@@ -1,6 +1,6 @@
-
-/* beacon.h */
-
-void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct digi_config_s *pdigi);
-
-void beacon_tracker_set_debug (int level);
+
+/* beacon.h */
+
+void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig);
+
+void beacon_tracker_set_debug (int level);
diff --git a/config.c b/config.c
index 4ee3202..8090c85 100644
--- a/config.c
+++ b/config.c
@@ -1,3528 +1,4359 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:      config.c
- *
- * Purpose:   	Read configuration information from a file.
- *		
- * Description:	This started out as a simple little application with a few
- *		command line options.  Due to creeping featurism, it's now
- *		time to add a configuration file to specify options.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <ctype.h>
-#include <math.h>
-
-#if __WIN32__
-#include "pthreads/pthread.h"
-#else
-#include <pthread.h>
-#endif
-
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "digipeater.h"
-#include "config.h"
-#include "aprs_tt.h"
-#include "igate.h"
-#include "latlong.h"
-#include "symbols.h"
-#include "xmit.h"
-
-// geotranz
-
-#include "utm.h"
-#include "mgrs.h"
-#include "usng.h"
-#include "error_string.h"
-
-#define D2R(d) ((d) * M_PI / 180.)
-#define R2D(r) ((r) * 180. / M_PI)
-
-
-
-//#include "tq.h"
-
-/* 
- * Conversions from various units to meters.
- * There is some disagreement about the exact values for some of these. 
- * Close enough for our purposes.
- * Parsec, light year, and angstrom are probably not useful.
- */
-
-static const struct units_s {
-	char *name;
-	float meters;
-} units[] = {
-	{	"barleycorn",	0.008466667	},	
-	{	"inch",		0.0254		},
-	{	"in",		0.0254		},
-	{	"hand",		0.1016		},	
-	{	"shaku",	0.3030		},	
-	{	"foot",		0.304801	},	
-	{	"ft",		0.304801	},	
-	{	"cubit",	0.4572		},	
-	{	"megalithicyard", 0.8296	},	
-	{	"my",		0.8296		},	
-	{	"yard",		0.914402	},
-	{	"yd",		0.914402	},
-	{	"m",		1.		},	
-	{	"meter",	1.		},	
-	{	"metre",	1.		},	
-	{	"ell",		1.143		},	
-	{	"ken",		1.818		},	
-	{	"hiro",		1.818		},	
-	{	"fathom",	1.8288		},	
-	{	"fath",		1.8288		},	
-	{	"toise",	1.949		},
-	{	"jo",		3.030		},
-	{	"twain",	3.6576074	},	
-	{	"rod",		5.0292		},	
-	{	"rd",		5.0292		},	
-	{	"perch",	5.0292		},	
-	{	"pole",		5.0292		},	
-	{	"rope",		6.096		},	
-	{	"dekameter",	10.		},	
-	{	"dekametre",	10.		},	
-	{	"dam",		10.		},	
-	{	"chain",	20.1168		},
-	{	"ch",		20.1168		},
-	{	"actus",	35.47872	},	
-	{	"arpent",	58.471		},	
-	{	"hectometer",	100.		},	
-	{	"hectometre",	100.		},	
-	{	"hm",		100.		},	
-	{	"cho",		109.1		},	
-	{	"furlong",	201.168		},
-	{	"fur",		201.168		},
-	{	"kilometer",	1000.		},	
-	{	"kilometre",	1000.		},	
-	{	"km",		1000.		},	
-	{	"mile",		1609.344	},	
-	{	"mi",		1609.344	},	
-	{	"ri",		3927.		},	
-	{	"league",	4828.032	},	
-	{	"lea",		4828.032	} };
-
-#define NUM_UNITS (sizeof(units) / sizeof(struct units_s))
-
-static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config);
-
-/* Do we have a string of all digits? */
-
-static int alldigits(char *p)
-{
-	if (p == NULL) return (0);
-	if (strlen(p) == 0) return (0);
-	while (*p != '\0') {
-	  if ( ! isdigit(*p)) return (0);
-	  p++;
-	}
-	return (1);
-}
-
-/* Do we have a string of all letters or + or -  ? */
-
-static int alllettersorpm(char *p)
-{
-	if (p == NULL) return (0);
-	if (strlen(p) == 0) return (0);
-	while (*p != '\0') {
-	  if ( ! isalpha(*p) && *p != '+' && *p != '-') return (0);
-	  p++;
-	}
-	return (1);
-}
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_ll
- *
- * Purpose:     Parse latitude or longitude from configuration file.
- *
- * Inputs:      str	- String like [-]deg[^min][hemisphere]
- *
- *		which	- LAT or LON for error checking and message.
- *
- *		line	- Line number for use in error message. 
- *
- * Returns:     Coordinate in signed degrees.
- *
- *----------------------------------------------------------------*/
-
-/* Acceptable symbols to separate degrees & minutes. */
-/* Degree symbol is not in ASCII so documentation says to use "^" instead. */
-/* Some wise guy will try to use degree symbol. */
-
-#define DEG1 '^'
-#define DEG2 0xb0	/* ISO Latin1 */
-#define DEG3 0xf8	/* Microsoft code page 437 */
-
-// TODO: recognize UTF-8 degree symbol.
-
-
-enum parse_ll_which_e { LAT, LON };
-
-static double parse_ll (char *str, enum parse_ll_which_e which, int line)
-{
-	char stemp[40];
-	int sign;
-	double degrees, minutes;
-	char *endptr;
-	char hemi;
-	int limit;
-	unsigned char sep;
-
-/*
- * Remove any negative sign.
- */
-	strcpy (stemp, str);
-	sign = +1;
-	if (stemp[0] == '-') {
-	  sign = -1;
-	  stemp[0] = ' ';
-	}
-/*
- * Process any hemisphere on the end.
- */
-	if (strlen(stemp) >= 2) {
-	  endptr = stemp + strlen(stemp) - 1;
-	  if (isalpha(*endptr)) {
-
-	    hemi = *endptr;
-	    *endptr = '\0';
-	    if (islower(hemi)) {
-	      hemi = toupper(hemi);
-	    }
-
-	    if (hemi == 'W' || hemi == 'S') {
-	      sign = -sign;
-	    }
-
-	    if (which == LAT) {
-	      if (hemi != 'N' && hemi != 'S') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: Latitude hemisphere in \"%s\" is not N or S.\n", line, str);
-	      }
-	    }
-	    else {
-	      if (hemi != 'E' && hemi != 'W') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: Longitude hemisphere in \"%s\" is not E or W.\n", line, str);
-	      }
-	    }
-	  }
-	}
-
-/*
- * Parse the degrees part.
- */
-	degrees = strtod (stemp, &endptr);
-
-/*
- * Is there a minutes part?
- */
-	sep = *endptr;
-	if (sep != '\0') {
-
-	  if (sep == DEG1 || sep == DEG2 || sep == DEG3) {
-	 
-	    minutes = strtod (endptr+1, &endptr);
-	    if (*endptr != '\0') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str);
-	    }
-	    if (minutes >= 60.0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Number of minutes in \"%s\" is >= 60.\n", line, str);
-	    }
-	    degrees += minutes / 60.0;
-	  }
-	  else {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str);
-	  }
-	}
-
-	degrees = degrees * sign;
-
-	limit = which == LAT ? 90 : 180;
-	if (degrees < -limit || degrees > limit) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Line %d: Number of degrees in \"%s\" is out of range for %s\n", line, str,
-		which == LAT ? "latitude" : "longitude");
-	}
-	//dw_printf ("%s = %f\n", str, degrees);
-	return (degrees);
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_utm_zone
- *
- * Purpose:     Parse UTM zone from configuration file.
- *
- * Inputs:      szone	- String like [-]number[letter]
- *
- * Output:	hemi	- Hemisphere, 'N' or 'S'.
- *
- * Returns:	Zone as number.  
- *		Type is long because Convert_UTM_To_Geodetic expects that.
- *
- * Errors:	Prints message and return 0.
- *
- * Description:	
- *		It seems there are multiple conventions for specifying the UTM hemisphere.
- *		
- *		  - MGRS latitude band.  North if missing or >= 'N'.
- *		  - Negative zone for south.
- *		  - Separate North or South.
- *		
- *		I'm using the first alternatve.
- *		GEOTRANS uses the third.
- *		We will also recognize the second one but I'm not sure if I want to document it.
- *
- *----------------------------------------------------------------*/
-
-long parse_utm_zone (char *szone, char *hemi)
-{
-	long lzone;
-	char *zlet;
-
-
-	*hemi = 'N';	/* default */
-
-        lzone = strtol(szone, &zlet, 10);
-
-        if (*zlet == '\0') {
-	  /* Number is not followed by letter something else.  */
-	  /* Allow negative number to mean south. */
-
-	  if (lzone < 0) {
-	    *hemi = 'S';
-	    lzone = (- lzone);
-	  }
-	}
-	else {
-	  if (islower (*zlet)) {
-	    *zlet = toupper(*zlet);
-	  }
-	  if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) {
-	    if (*zlet < 'N') {
-	      *hemi = 'S';
-	    }
-	  }
-	  else {
-	    text_color_set(DW_COLOR_ERROR);
-            dw_printf ("Latitudinal band in \"%s\" must be one of CDEFGHJKLMNPQRSTUVWX.\n", szone);
- 	    *hemi = '?';
-	  }
-        }
-
-        if (lzone < 1 || lzone > 60) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf ("UTM Zone number %ld must be in range of 1 to 60.\n", lzone);
-        
-        }
-
-	return (lzone);
-}
-
-
-
-
-#if 0
-main ()
-{
-
-	parse_ll ("12.5", LAT);
-	parse_ll ("12.5N", LAT);
-	parse_ll ("12.5E", LAT);	// error
-
-	parse_ll ("-12.5", LAT);
-	parse_ll ("12.5S", LAT);
-	parse_ll ("12.5W", LAT);	// error
-
-	parse_ll ("12.5", LON);
-	parse_ll ("12.5E", LON);
-	parse_ll ("12.5N", LON);	// error
-
-	parse_ll ("-12.5", LON);
-	parse_ll ("12.5W", LON);
-	parse_ll ("12.5S", LON);	// error
-
-	parse_ll ("12^30", LAT);
-	parse_ll ("12�30", LAT);
-
-	parse_ll ("91", LAT);		// out of range
-	parse_ll ("91", LON);
-	parse_ll ("181", LON);		// out of range
-
-	parse_ll ("12&5", LAT);		// bad character
-}
-#endif
-
-
-/*------------------------------------------------------------------
- *
- * Name:        parse_interval
- *
- * Purpose:     Parse time interval from configuration file.
- *
- * Inputs:      str	- String like 10 or 9:30
- *
- *		line	- Line number for use in error message. 
- *
- * Returns:     Number of seconds.
- *
- * Description:	This is used by the BEACON configuration items
- *		for initial delay or time between beacons.
- *
- *		The format is either minutes or minutes:seconds.
- *
- *----------------------------------------------------------------*/
-
-
-static int parse_interval (char *str, int line)
-{
-	char *p;
-	int sec;
-	int nc = 0;
-	int bad = 0;
-
-	for (p = str; *p != '\0'; p++) {
-	  if (*p == ':') nc++;
-	  else if ( ! isdigit(*p)) bad++;
-	}
-	if (bad > 0 || nc > 1) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Config file, line %d: Time interval must be of the form minutes or minutes:seconds.\n", line);
-	}
-
-	p = strchr (str, ':');
-
-	if (p != NULL) {
-	  sec = atoi(str) * 60 + atoi(p+1);
-	}
-	else {
-	  sec = atoi(str) * 60;
-	}
-
-	return (sec);
-
-} /* end parse_interval */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        config_init
- *
- * Purpose:     Read configuration file when application starts up.
- *
- * Inputs:	fname		- Name of configuration file.
- *
- * Outputs:	p_audio_config		- Radio channel parameters stored here.
- *
- *		p_digi_config	- Digipeater configuration stored here.
- *
- *		p_tt_config	- APRStt stuff.
- *
- *		p_igate_config	- Internet Gateway.
- *	
- *		p_misc_config	- Everything else.  This wasn't thought out well.
- *
- * Description:	Apply default values for various parameters then read the 
- *		the configuration file which can override those values.
- *
- * Errors:	For invalid input, display line number and message on stdout (not stderr).
- *		In many cases this will result in keeping the default rather than aborting.
- *
- * Bugs:	Very simple-minded parsing.
- *		Not much error checking.  (e.g. atoi() will return 0 for invalid string.)
- *		Not very forgiving about sloppy input.
- *
- *--------------------------------------------------------------------*/
-
-
-void config_init (char *fname, struct audio_s *p_audio_config, 
-			struct digi_config_s *p_digi_config,
-			struct tt_config_s *p_tt_config,
-			struct igate_config_s *p_igate_config,
-			struct misc_config_s *p_misc_config)
-{
-	FILE *fp;
-	char stuff[256];
-	//char *p;
-	//int c, p;
-	//int err;
-	int line;
-	int channel;
-	int adevice;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("config_init ( %s )\n", fname);
-#endif
-
-/* 
- * First apply defaults.
- */
-
-	memset (p_audio_config, 0, sizeof(struct audio_s));
-
-	/* First audio device is always available with defaults. */
-	/* Others must be explicitly defined before use. */
-
-	for (adevice=0; adevice<MAX_ADEVS; adevice++) {
-
-	  strcpy (p_audio_config->adev[adevice].adevice_in, DEFAULT_ADEVICE);
-	  strcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE);
-
-	  p_audio_config->adev[adevice].defined = 0;
-	  p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS;		/* -2 stereo */
-	  p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;	/* -r option */
-	  p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;	/* -8 option for 8 instead of 16 bits */
-	}
-
-	p_audio_config->adev[0].defined = 1;
-
-	for (channel=0; channel<MAX_CHANS; channel++) {
-	  int ot;
-
-	  p_audio_config->achan[channel].valid = 0;				/* One or both channels will be */
-								/* set to valid when corresponding */
-								/* audio device is defined. */
-	  p_audio_config->achan[channel].modem_type = MODEM_AFSK;			
-	  p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;		/* -m option */
-	  p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;		/* -s option */
-	  p_audio_config->achan[channel].baud = DEFAULT_BAUD;			/* -b option */
-
-	  /* None.  Will set default later based on other factors. */
-	  strcpy (p_audio_config->achan[channel].profiles, "");	
-
-	  p_audio_config->achan[channel].num_freq = 1;				
-	  p_audio_config->achan[channel].offset = 0;
-
-	  p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
-	  p_audio_config->achan[channel].sanity_test = SANITY_APRS;
-	  p_audio_config->achan[channel].passall = 0;
-
-	  for (ot = 0; ot < NUM_OCTYPES; ot++) {
-	    p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_NONE;
-	    strcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "");
-	    p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE;
-	    p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE;
-	    p_audio_config->achan[channel].octrl[ot].ptt_gpio = 0;
-	    p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = 0;
-	    p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
-	    p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
-	  }
-
-	  p_audio_config->achan[channel].dwait = DEFAULT_DWAIT;				
-	  p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;				
-	  p_audio_config->achan[channel].persist = DEFAULT_PERSIST;				
-	  p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;				
-	  p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;				
-	}
-
-	/* First channel should always be valid. */
-	/* If there is no ADEVICE, it uses default device in mono. */
-
-	p_audio_config->achan[0].valid = 1;
-
-
-	memset (p_digi_config, 0, sizeof(struct digi_config_s));
-	//p_digi_config->num_chans = p_audio_config->adev[0].num_channels;	// TODO: rethink for > 2 case.
-	p_digi_config->dedupe_time = DEFAULT_DEDUPE;
-
-	memset (p_tt_config, 0, sizeof(struct tt_config_s));	
-	p_tt_config->gateway_enabled = 0;
-	p_tt_config->ttloc_size = 2;	/* Start with at least 2.  */
-					/* When full, it will be increased by 50 %. */
-	p_tt_config->ttloc_ptr = malloc (sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	p_tt_config->ttloc_len = 0;
-
-	/* Retention time and decay algorithm from 13 Feb 13 version of */
-	/* http://www.aprs.org/aprstt/aprstt-coding24.txt */
-
-	p_tt_config->retain_time = 80 * 60;
-	p_tt_config->num_xmits = 7;
-	assert (p_tt_config->num_xmits <= TT_MAX_XMITS);
-	p_tt_config->xmit_delay[0] = 3;		/* Before initial transmission. */
-	p_tt_config->xmit_delay[1] = 16;
-	p_tt_config->xmit_delay[2] = 32;
-	p_tt_config->xmit_delay[3] = 64;
-	p_tt_config->xmit_delay[4] = 2 * 60;
-	p_tt_config->xmit_delay[5] = 4 * 60;
-	p_tt_config->xmit_delay[6] = 8 * 60;
-
-	memset (p_misc_config, 0, sizeof(struct misc_config_s));
-	p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
-	p_misc_config->kiss_port = DEFAULT_KISS_PORT;
-	p_misc_config->enable_kiss_pt = 0;				/* -p option */
-
-	/* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */
-
-	p_misc_config->sb_configured = 0;	/* TRUE if SmartBeaconing is configured. */
-	p_misc_config->sb_fast_speed = 60;	/* MPH */
-	p_misc_config->sb_fast_rate = 180;	/* seconds */
-	p_misc_config->sb_slow_speed = 5;	/* MPH */
-	p_misc_config->sb_slow_rate = 1800;	/* seconds */
-	p_misc_config->sb_turn_time = 15;	/* seconds */
-	p_misc_config->sb_turn_angle = 30;	/* degrees */
-	p_misc_config->sb_turn_slope = 255;	/* degrees * MPH */
-
-	memset (p_igate_config, 0, sizeof(struct igate_config_s));
-	p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
-	p_igate_config->tx_chan = -1;			/* IS->RF not enabled */
-	p_igate_config->tx_limit_1 = 6;
-	p_igate_config->tx_limit_5 = 20;
-
-
-	/* People find this confusing. */
-	/* Ideally we'd like to figure out if com0com is installed */
-	/* and automatically enable this.  */
-	
-	//strcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM);
-	strcpy (p_misc_config->nullmodem, "");
-	strcpy (p_misc_config->nmea_port, "");
-	strcpy (p_misc_config->logdir, "");
-
-
-/* 
- * Try to extract options from a file.
- * 
- * Windows:  File must be in current working directory.
- *
- * Linux: Search current directory then home directory.
- */
-
-
-	channel = 0;
-	adevice = 0;
-
-	fp = fopen (fname, "r");
-#ifndef __WIN32__
-	if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) {
-	/* Failed to open the default location.  Try home dir. */
-	  char *p;
-
-	  p = getenv("HOME");
-	  if (p != NULL) {
-	    strcpy (stuff, p);
-	    strcat (stuff, "/direwolf.conf");
-	    fp = fopen (stuff, "r");
-	  } 
-	}
-#endif
-	if (fp == NULL)	{
-	  // TODO: not exactly right for all situations.
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("ERROR - Could not open config file %s\n", fname);
-	  dw_printf ("Try using -c command line option for alternate location.\n");
-	  return;
-	}
-	
-
-	line = 0;
-	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
-	  char *t;
-
-	  line++;
-
-
-	  t = strtok (stuff, " ,\t\n\r");
-	  if (t == NULL) {
-	    continue;
-	  }
-
-	  if (*t == '#' || *t == '*') {
-	    continue;
-	  }
-
-
-
-/*
- * ==================== Audio device parameters ==================== 
- */
-
-/*
- * ADEVICE[n] 		- Name of input sound device, and optionally output, if different.
- */
-
-	  /* Note that ALSA name can contain comma such as hw:1,0 */
-
-	  if (strncasecmp(t, "ADEVICE", 7) == 0) {
-	    adevice = 0;
-	    if (isdigit(t[7])) {
-	      adevice = t[7] - '0';
-	    }
-
-	    if (adevice < 0 || adevice >= MAX_ADEVS) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Device number %d out of range for ADEVICE command on line %d.\n", adevice, line);
-	      adevice = 0;
-	      continue;
-	    }
-
-	    t = strtok (NULL, " \t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line);
-	      continue;
-	    }
-
-	    p_audio_config->adev[adevice].defined = 1;
-	
-	    /* First channel of device is valid. */
-	    p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
-
-	    strncpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)-1);
-	    strncpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)-1);
-
-	    t = strtok (NULL, " \t\n\r");
-	    if (t != NULL) {
-	      strncpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)-1);
-	    }
-	  }
-
-/*
- * ARATE 		- Audio samples per second, 11025, 22050, 44100, etc.
- */
-
-	  else if (strcasecmp(t, "ARATE") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing audio sample rate for ARATE command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= MIN_SAMPLES_PER_SEC && n <= MAX_SAMPLES_PER_SEC) {
-	      p_audio_config->adev[adevice].samples_per_sec = n;
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Use a more reasonable audio sample rate in range of %d - %d.\n", 
-							line, MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC);
-   	    }
-	  }
-
-/*
- * ACHANNELS 		- Number of audio channels for current device: 1 or 2
- */
-
-	  else if (strcasecmp(t, "ACHANNELS") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing number of audio channels for ACHANNELS command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n ==1 || n == 2) {
-	      p_audio_config->adev[adevice].num_channels = n;
-
-	      /* Set valid channels depending on mono or stereo. */
-
-	      p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
-	      if (n == 2) {
-	        p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].valid = 1;
-	      }
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Number of audio channels must be 1 or 2.\n", line);
-   	    }
-	  }
-
-/*
- * ==================== Radio channel parameters ==================== 
- */
-
-/*
- * CHANNEL 		- Set channel for following commands.
- */
-
-	  else if (strcasecmp(t, "CHANNEL") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing channel number for CHANNEL command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n < MAX_CHANS) {
-
-	      channel = n;
-
-	      if ( ! p_audio_config->achan[n].valid) {
-
-	        if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) {
-	          text_color_set(DW_COLOR_ERROR);
-                  dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not defined.\n", 
-								line, n, ACHAN2ADEV(n));
-	        }
-	        else {
-	          text_color_set(DW_COLOR_ERROR);
-                  dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not in stereo.\n", 
-								line, n, ACHAN2ADEV(n));
-	        }
-	      }
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1);
-   	    }
-	  }
-
-/*
- * MYCALL station
- */
-	  else if (strcasecmp(t, "mycall") == 0) {
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Missing value for MYCALL command on line %d.\n", line);
-	      continue;
-	    }
-	    else {
-
-	      // Definitely set for current channel.
-	      // Set for other channels which have not been set yet.
-
-	      int c;
-
-	      for (c = 0; c < MAX_CHANS; c++) {
-
-	        if (c == channel || 
-			strlen(p_audio_config->achan[c].mycall) == 0 || 
-			strcasecmp(p_audio_config->achan[c].mycall, "NOCALL") == 0 ||
-			strcasecmp(p_audio_config->achan[c].mycall, "N0CALL") == 0) {
-
-	          char *p;
-
-	          strncpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall)-1);
-
-	          for (p = p_audio_config->achan[c].mycall; *p != '\0'; p++) {
-	            if (islower(*p)) {
-		      *p = toupper(*p);	/* silently force upper case. */
-	            }
-	          }
-	          // TODO: additional checks if valid
-	        }
-	      }
-	    }
-	  }
-
-
-/*
- * MODEM	- Set modem properties for current channel.
- *
- *
- * Old style:
- * 	MODEM  baud [ mark  space  [A][B][C][+]  [  num-decoders spacing ] ] 
- *
- * New style, version 1.2:
- *	MODEM  speed [ option ] ...
- *
- * Options:
- *	mark:space	- AFSK tones.  Defaults based on speed.
- *	num at offset	- Multiple decoders on different frequencies.
- *	
- */
-
-	  else if (strcasecmp(t, "MODEM") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 100 && n <= 10000) {
-	      p_audio_config->achan[channel].baud = n;
-	      if (n != 300 && n != 1200 && n != 9600) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: Warning: Non-standard baud rate.  Are you sure?\n", line);
-    	      }
-	    }
-	    else {
-	      p_audio_config->achan[channel].baud = DEFAULT_BAUD;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", 
-				 line, p_audio_config->achan[channel].baud);
-   	    }
-
-
-	    /* Set defaults based on speed. */
-	    /* Should be same as -B command line option in direwolf.c. */
-
-	    if (p_audio_config->achan[channel].baud < 600) {
-              p_audio_config->achan[channel].modem_type = MODEM_AFSK;
-              p_audio_config->achan[channel].mark_freq = 1600;
-              p_audio_config->achan[channel].space_freq = 1800;
-	    }
-	    else if (p_audio_config->achan[channel].baud > 2400) {
-              p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
-              p_audio_config->achan[channel].mark_freq = 0;
-              p_audio_config->achan[channel].space_freq = 0;
-	    }
-	    else {
-              p_audio_config->achan[channel].modem_type = MODEM_AFSK;
-              p_audio_config->achan[channel].mark_freq = 1200;
-              p_audio_config->achan[channel].space_freq = 2200;
-	    }
-
-	    /* Get mark frequency. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      /* all done. */
-	      continue;
-	    }
-
-	    if (alldigits(t)) {
-
-/* old style */
-
-	      n = atoi(t);
-	      /* Originally the upper limit was 3000. */
-	      /* Version 1.0 increased to 5000 because someone */
-	      /* wanted to use 2400/4800 Hz AFSK. */
-	      /* Of course the MIC and SPKR connections won't */
-	      /* have enough bandwidth so radios must be modified. */
-              if (n >= 300 && n <= 5000) {
-	        p_audio_config->achan[channel].mark_freq = n;
-	      }
-	      else {
-	        p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", 
-				 line, p_audio_config->achan[channel].mark_freq);
-   	      }
-	    
-	      /* Get space frequency */
-
-	      t = strtok (NULL, " ,\t\n\r");
-	      if (t == NULL) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: Missing tone frequency for space.\n", line);
-	        continue;
-	      }
-	      n = atoi(t);
-              if (n >= 300 && n <= 5000) {
-	        p_audio_config->achan[channel].space_freq = n;
-	      }
-	      else {
-	        p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", 
-					 line, p_audio_config->achan[channel].space_freq);
-   	      }
-
-	      /* Gently guide users toward new format. */
-
-	      if (p_audio_config->achan[channel].baud == 1200 &&
-                  p_audio_config->achan[channel].mark_freq == 1200 &&
-                  p_audio_config->achan[channel].space_freq == 2200) {
-
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 1200 baud default 1200:2200.\n", line);
-	      }
-	      if (p_audio_config->achan[channel].baud == 300 &&
-                  p_audio_config->achan[channel].mark_freq == 1600 &&
-                  p_audio_config->achan[channel].space_freq == 1800) {
-
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 300 baud default 1600:1800.\n", line);
-	      }
-
-	      /* New feature in 0.9 - Optional filter profile(s). */
-
-	      t = strtok (NULL, " ,\t\n\r");
-	      if (t != NULL) {
-
-	        /* Look for some combination of letter(s) and + */
-
-	        if (isalpha(t[0]) || t[0] == '+') {
-		  char *pc;
-
-		  /* Here we only catch something other than letters and + mixed in. */
-		  /* Later, we check for valid letters and no more than one letter if + specified. */
-
-	          for (pc = t; *pc != '\0'; pc++) {
-		    if ( ! isalpha(*pc) && ! *pc == '+') {
-	              text_color_set(DW_COLOR_ERROR);
-                      dw_printf ("Line %d: Demodulator type can only contain letters and + character.\n", line);
-		    }
-		  }    
-		
-		  strncpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles));
-	          t = strtok (NULL, " ,\t\n\r");
-		  if (strlen(p_audio_config->achan[channel].profiles) > 1 && t != NULL) {
-	            text_color_set(DW_COLOR_ERROR);
-                    dw_printf ("Line %d: Can't combine multiple demodulator types and multiple frequencies.\n", line);
-		    continue;
-		  }
-	        }
-	      }
-
-	      /* New feature in 0.9 - optional number of decoders and frequency offset between. */
-
-	      if (t != NULL) {
-	        n = atoi(t);
-                if (n < 1 || n > MAX_SUBCHANS) {
-	          text_color_set(DW_COLOR_ERROR);
-                  dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line);
-		  n = 3;
-	        }
-	        p_audio_config->achan[channel].num_freq = n;
-
-	        t = strtok (NULL, " ,\t\n\r");
-	        if (t != NULL) {
-	          n = atoi(t);
-                  if (n < 5 || n > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) {
-	            text_color_set(DW_COLOR_ERROR);
-                    dw_printf ("Line %d: Unreasonable value for offset between modems.  Using 50 Hz.\n", line);
-		    n = 50;
-	          }
-		  p_audio_config->achan[channel].offset = n;
-
-	          text_color_set(DW_COLOR_ERROR);
-                  dw_printf ("Line %d: New style for multiple demodulators is %d@%d\n", line,
-			p_audio_config->achan[channel].num_freq, p_audio_config->achan[channel].offset);	  
-	        }
-	        else {
-	          text_color_set(DW_COLOR_ERROR);
-                  dw_printf ("Line %d: Missing frequency offset between modems.  Using 50 Hz.\n", line);
-	          p_audio_config->achan[channel].offset = 50;
-	        }    
-	      }
-	    }
-	    else {
-
-/* New style. */
-
-	      while (t != NULL) {
-		char *s;
-
-		if ((s = strchr(t, ':')) != NULL) {		/* mark:space */
-
-	          p_audio_config->achan[channel].mark_freq = atoi(t);
-	          p_audio_config->achan[channel].space_freq = atoi(s+1);
-
-		  if (p_audio_config->achan[channel].mark_freq == 0 && p_audio_config->achan[channel].space_freq == 0) {
-		    p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
-	          }
-	          else {
-		    p_audio_config->achan[channel].modem_type = MODEM_AFSK;
-
-                    if (p_audio_config->achan[channel].mark_freq < 300 || p_audio_config->achan[channel].mark_freq > 5000) {
-	              p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
-	              text_color_set(DW_COLOR_ERROR);
-                      dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d instead.\n", 
-				 line, p_audio_config->achan[channel].mark_freq);
-		    }
-                    if (p_audio_config->achan[channel].space_freq < 300 || p_audio_config->achan[channel].space_freq > 5000) {
-	              p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
-	              text_color_set(DW_COLOR_ERROR);
-                      dw_printf ("Line %d: Unreasonable space tone frequency. Using %d instead.\n", 
-				 line, p_audio_config->achan[channel].space_freq);
-		    }
-	          }
-	        }
-
-		else if ((s = strchr(t, '@')) != NULL) {		/* num at offset */
-
-	          p_audio_config->achan[channel].num_freq = atoi(t);
-	          p_audio_config->achan[channel].offset = atoi(s+1);
-
-                  if (p_audio_config->achan[channel].num_freq < 1 || p_audio_config->achan[channel].num_freq > MAX_SUBCHANS) {
-	            text_color_set(DW_COLOR_ERROR);
-                    dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line);
-	            p_audio_config->achan[channel].num_freq = 3;
-		  }
-
-                  if (p_audio_config->achan[channel].offset < 5 || 
-			p_audio_config->achan[channel].offset > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) {
-	            text_color_set(DW_COLOR_ERROR);
-                    dw_printf ("Line %d: Offset between demodulators is unreasonable. Using 50 Hz.\n", line);
-	            p_audio_config->achan[channel].offset = 50;
-		  }
-		}
-
-	        else if (alllettersorpm(t)) {		/* profile of letter(s) + - */
-
-		  // Will be validated later.
-		  strncpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles));
-	        }
-
-		else if (*t == '/') {		/* /div */
-		  int n = atoi(t+1);
-
-                  if (n >= 1 && n <= 8) {
-	            p_audio_config->achan[channel].decimate = n;
-		  }
-	    	  else {
-	            text_color_set(DW_COLOR_ERROR);
-                    dw_printf ("Line %d: Ignoring unreasonable sample rate division factor of %d.\n", line, n);
-		  }
-		}
-
-		else {
-	          text_color_set(DW_COLOR_ERROR);
-                  dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t);
-	        } 
-
-	        t = strtok (NULL, " ,\t\n\r");
-	      }
-
-	      /* A later place catches disallowed combination of + and @. */
-	      /* A later place sets /n for 300 baud if not specified by user. */
-
-	      //printf ("debug: div = %d\n", p_audio_config->achan[channel].decimate);
-
-	    }
-	  }
-
-/*
- * (deprecated) HBAUD 		- Set data bits per second.  Standard values are 300 & 1200 for AFSK
- *				and 9600 for baseband with scrambling.
- */
-
-#if 0
-	  else if (strcasecmp(t, "HBAUD") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing date transmission rate for HBAUD command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 100 && n <= 10000) {
-	      p_audio_config->achan[channel].baud = n;
-	      if (n != 300 && n != 1200 && n != 9600) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: Warning: Non-standard baud rate.  Are you sure?\n", line);
-    	      }
-	      if (n == 9600) {
-		/* TODO: should be separate option to keep it more general. */
-	        //text_color_set(DW_COLOR_ERROR);
-	        //dw_printf ("Line %d: Note: Using scrambled baseband for 9600 baud.\n", line);
-	        p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;		
-    	      }
-	    }
-	    else {
-	      p_audio_config->achan[channel].baud = DEFAULT_BAUD;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", 
-				 line, p_audio_config->achan[channel].baud);
-   	    }
-	  }
-#endif
-
-/*
- * (deprecated) MARK 		- Mark tone frequency.
- */
-
-#if 0
-	  else if (strcasecmp(t, "MARK") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing tone frequency for MARK command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 300 && n <= 3000) {
-	      p_audio_config->achan[channel].mark_freq = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", 
-				 line, p_audio_config->achan[channel].mark_freq);
-   	    }
-	  }
-#endif
-
-/*
- * (deprecated) SPACE 		- Space tone frequency.
- */
-
-#if 0
-	  else if (strcasecmp(t, "SPACE") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing tone frequency for SPACE command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 300 && n <= 3000) {
-	      p_audio_config->achan[channel].space_freq = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", 
-					 line, p_audio_config->achan[channel].space_freq);
-   	    }
-	  }
-#endif
-
-/*
- * DTMF  		- Enable DTMF decoder.
- *
- * Future possibilities: 
- *	Option to determine if it goes to APRStt gateway and/or application.
- *	Disable normal demodulator to reduce CPU requirements.
- */
-
-
-	  else if (strcasecmp(t, "DTMF") == 0) {
-
-	    p_audio_config->achan[channel].dtmf_decode = DTMF_DECODE_ON;
-
-	  }
-
-
-/*
- * FIX_BITS  n  [ APRS | AX25 | NONE ] [ PASSALL ]
- *
- *	- Attempt to fix frames with bad FCS. 
- */
-
-	  else if (strcasecmp(t, "FIX_BITS") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing value for FIX_BITS command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= RETRY_NONE && n <= RETRY_REMOVE_TWO_SEP) {
-	      p_audio_config->achan[channel].fix_bits = (retry_t)n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid value for FIX_BITS. Using %d.\n", 
-			line, p_audio_config->achan[channel].fix_bits);
-   	    }
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    while (t != NULL) {
-
-	      // If more than one sanity test, we silently take the last one.
-
-	      if (strcasecmp(t, "APRS") == 0) {
-	        p_audio_config->achan[channel].sanity_test = SANITY_APRS;
-	      }
-	      else if (strcasecmp(t, "AX25") == 0 || strcasecmp(t, "AX.25") == 0) {
-	        p_audio_config->achan[channel].sanity_test = SANITY_AX25;
-	      }
-	      else if (strcasecmp(t, "NONE") == 0) {
-	        p_audio_config->achan[channel].sanity_test = SANITY_NONE;
-	      }
-	      else if (strcasecmp(t, "PASSALL") == 0) {
-	        p_audio_config->achan[channel].passall = 1;
-	      }
-	      else {
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: Invalid option '%s' for FIX_BITS.\n", line, t);
-	      }
-	      t = strtok (NULL, " ,\t\n\r");
-	    }
-	  }
-
-
-/*
- * PTT 		- Push To Talk signal line.
- * DCD		- Data Carrier Detect indicator.
- *
- * xxx  serial-port [-]rts-or-dtr [ [-]rts-or-dtr ]
- * xxx  GPIO  [-]gpio-num
- * xxx  LPT  [-]bit-num
- *
- * Applies to most recent CHANNEL command.
- */
-
-	  else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0) {
-	    //int n;
-	    int ot;
-	    char otname[8];
-
-	    if (strcasecmp(t, "PTT") == 0) {
-	      ot = OCTYPE_PTT;
-	      strcpy (otname, "PTT");
-	    }
-	    else if (strcasecmp(t, "DCD") == 0) {
-	      ot = OCTYPE_DCD;
-	      strcpy (otname, "DCD");
-	    }
-	    else {
-	      ot = OCTYPE_FUTURE;
-	      strcpy (otname, "FUTURE");
-	    }
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file line %d: Missing serial port name for %s command.\n", 
-			line, otname);
-	      continue;
-	    }
-
-	    if (strcasecmp(t, "GPIO") == 0) {
-
-/* GPIO case, Linux only. */
-
-#if __WIN32__
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, otname);
-#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 for %s.\n", line, otname);
-	        continue;
-	      }
-
-	      if (*t == '-') {
-	        p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t+1);
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
-	      }
-	      else {
-	        p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t);
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
-	      }
-	      p_audio_config->achan[channel].octrl[ot].ptt_method = 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 for %s.\n", line, otname);
-	        continue;
-	      }
-
-	      if (*t == '-') {
-	        p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t+1);
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
-	      }
-	      else {
-	        p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t);
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
-	      }
-	      p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_LPT;
-#else
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file line %d: %s with LPT is only available on x86 Linux.\n", line, otname);
-#endif		
-	    }
-	    else  {
-
-/* serial port case. */
-
-	      strncpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
-
-	      t = strtok (NULL, " ,\t\n\r");
-	      if (t == NULL) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n", 
-			line, otname);
-	        continue;
-	      }
-
-	      if (strcasecmp(t, "rts") == 0) {
-	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS;
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
-	      }
-	      else if (strcasecmp(t, "dtr") == 0) {
-	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR;
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
-	      }
-	      else if (strcasecmp(t, "-rts") == 0) {
-	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS;
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
-	      }
-	      else if (strcasecmp(t, "-dtr") == 0) {
-	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR;
-		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
-	      }
-	      else {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file line %d: Expected RTS or DTR after %s device name.\n", 
-			line, otname);
-	        continue;
-	      }
-
-
-	      p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_SERIAL;
-
-
-	      /* In version 1.2, we allow a second one for same serial port. */
-	      /* Some interfaces want the two control lines driven with opposite polarity. */
-	      /* e.g.   PTT COM1 RTS -DTR  */
-
-	      t = strtok (NULL, " ,\t\n\r");
-	      if (t != NULL) {
-
-	        if (strcasecmp(t, "rts") == 0) {
-	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS;
-		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
-	        }
-	        else if (strcasecmp(t, "dtr") == 0) {
-	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR;
-		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
-	        }
-	        else if (strcasecmp(t, "-rts") == 0) {
-	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS;
-		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1;
-	        }
-	        else if (strcasecmp(t, "-dtr") == 0) {
-	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR;
-		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1;
-	        }
-	        else {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Config file line %d: Expected RTS or DTR after first RTS or DTR.\n", 
-			line);
-	          continue;
-	        }
-
-		/* Would not make sense to specify the same one twice. */
-
-		if (p_audio_config->achan[channel].octrl[ot].ptt_line == p_audio_config->achan[channel].octrl[ot].ptt_line2) {
-	          dw_printf ("Config file line %d: Doesn't make sense to specify the some control line twice.\n", 
-			line);
-	        }
-
-	      }  /* end of second serial port control line. */
-	    }  /* end of serial port case. */
-
-	  }  /* end of PTT */
-
-
-/*
- * DWAIT 		- Extra delay for receiver squelch.
- */
-
-	  else if (strcasecmp(t, "DWAIT") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing delay time for DWAIT command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n <= 255) {
-	      p_audio_config->achan[channel].dwait = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].dwait = DEFAULT_DWAIT;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid delay time for DWAIT. Using %d.\n", 
-			line, p_audio_config->achan[channel].dwait);
-   	    }
-	  }
-
-/*
- * SLOTTIME 		- For non-digipeat transmit delay timing.
- */
-
-	  else if (strcasecmp(t, "SLOTTIME") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing delay time for SLOTTIME command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n <= 255) {
-	      p_audio_config->achan[channel].slottime = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n", 
-			line, p_audio_config->achan[channel].slottime);
-   	    }
-	  }
-
-/*
- * PERSIST 		- For non-digipeat transmit delay timing.
- */
-
-	  else if (strcasecmp(t, "PERSIST") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing probability for PERSIST command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n <= 255) {
-	      p_audio_config->achan[channel].persist = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n", 
-			line, p_audio_config->achan[channel].persist);
-   	    }
-	  }
-
-/*
- * TXDELAY 		- For transmit delay timing.
- */
-
-	  else if (strcasecmp(t, "TXDELAY") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing time for TXDELAY command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n <= 255) {
-	      p_audio_config->achan[channel].txdelay = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid time for transmit delay. Using %d.\n", 
-			line, p_audio_config->achan[channel].txdelay);
-   	    }
-	  }
-
-/*
- * TXTAIL 		- For transmit timing.
- */
-
-	  else if (strcasecmp(t, "TXTAIL") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing time for TXTAIL command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n <= 255) {
-	      p_audio_config->achan[channel].txtail = n;
-	    }
-	    else {
-	      p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", 
-			line, p_audio_config->achan[channel].txtail);
-   	    }
-	  }
-
-/*
- * SPEECH  script 
- *
- * Specify script for text-to-speech function.		
- */
-
-	  else if (strcasecmp(t, "SPEECH") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing script for Text-to-Speech function.\n", line);
-	      continue;
-	    }
-	    
-	    /* See if we can run it. */
-
-	    if (xmit_speak_it(t, -1, " ") == 0) {
-	      strcpy (p_audio_config->tts_script, t);
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Error trying to run Text-to-Speech function.\n", line);
-	      continue;
-	   }
-	  }
-
-/*
- * ==================== Digipeater parameters ==================== 
- */
-
-/*
- * DIGIPEAT  from-chan  to-chan  alias-pattern  wide-pattern  [ OFF|DROP|MARK|TRACE ] 
- */
-
-	  else if (strcasecmp(t, "digipeat") == 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 >= MAX_CHANS) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    if ( ! p_audio_config->achan[from_chan].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", 
-							line, from_chan);
-	      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 >= MAX_CHANS) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    if ( ! p_audio_config->achan[to_chan].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", 
-							line, to_chan);
-	      continue;
-	    }
-	
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Missing alias pattern on line %d.\n", line);
-	      continue;
-	    }
-	    e = regcomp (&(p_digi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
-	    if (e != 0) {
-	      regerror (e, &(p_digi_config->alias[from_chan][to_chan]), message, sizeof(message));
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n", 
-							line, message);
-	      continue;
-	    }
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Missing wide pattern on line %d.\n", line);
-	      continue;
-	    }
-	    e = regcomp (&(p_digi_config->wide[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
-	    if (e != 0) {
-	      regerror (e, &(p_digi_config->wide[from_chan][to_chan]), message, sizeof(message));
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Invalid wide matching pattern on line %d:\n%s\n", 
-							line, message);
-	      continue;
-	    }
-
-	    p_digi_config->enabled[from_chan][to_chan] = 1;
-	    p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF;
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t != NULL) {
-	      if (strcasecmp(t, "OFF") == 0) {
-	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF;
-	        t = strtok (NULL, " ,\t\n\r");
-	      }
-	      else if (strcasecmp(t, "DROP") == 0) {
-	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP;
-	        t = strtok (NULL, " ,\t\n\r");
-	      }
-	      else if (strcasecmp(t, "MARK") == 0) {
-	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK;
-	        t = strtok (NULL, " ,\t\n\r");
-	      }
-	      else if (strcasecmp(t, "TRACE") == 0) {
-	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE;
-	        t = strtok (NULL, " ,\t\n\r");
-	      }
-	    }
-
-	    if (t != NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t);     
-	    }
-	  }
-
-/*
- * DEDUPE 		- Time to suppress digipeating of duplicate packets.
- */
-
-	  else if (strcasecmp(t, "DEDUPE") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing time for DEDUPE command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if (n >= 0 && n < 600) {
-	      p_digi_config->dedupe_time = n;
-	    }
-	    else {
-	      p_digi_config->dedupe_time = DEFAULT_DEDUPE;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Unreasonable value for dedupe time. Using %d.\n", 
-			line, p_digi_config->dedupe_time);
-   	    }
-	  }
-
-/*
- * 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 >= MAX_CHANS) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    if ( ! p_audio_config->achan[from_chan].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", 
-							line, from_chan);
-	      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 >= MAX_CHANS) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    if ( ! p_audio_config->achan[to_chan].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", 
-							line, to_chan);
-	      continue;
-	    }
-	
-
-	    p_digi_config->regen[from_chan][to_chan] = 1;
-
-	  }
-
-
-/*
- * ==================== Packet Filtering for digipeater or IGate ==================== 
- */
-
-/*
- * FILTER  from-chan  to-chan  filter_specification_expression
- * FILTER  from-chan  IG       filter_specification_expression
- * FILTER  IG         to-chan  filter_specification_expression
- */
-
-	  else if (strcasecmp(t, "FILTER") == 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;
-	    }
-	    if (*t == 'i' || *t == 'I') {
-	      from_chan = MAX_CHANS;
-	    }
-	    else {
-	      from_chan = isdigit(*t) ? atoi(t) : -999;
-	      if (from_chan < 0 || from_chan >= MAX_CHANS) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d or \"IG\" on line %d.\n", 
-							MAX_CHANS-1, line);
-	        continue;
-	      }
-
-	      if ( ! p_audio_config->achan[from_chan].valid) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", 
-							line, from_chan);
-	        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;
-	    }
-	    if (*t == 'i' || *t == 'I') {
-	      to_chan = MAX_CHANS;
-	    }
-	    else {
-	      to_chan = isdigit(*t) ? atoi(t) : -999;
-	      if (to_chan < 0 || to_chan >= MAX_CHANS) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d or \"IG\" on line %d.\n", 
-							MAX_CHANS-1, line);
-	        continue;
-	      }
-	      if ( ! p_audio_config->achan[to_chan].valid) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", 
-							line, to_chan);
-	        continue;
-	      }
-	    }
-
-	    t = strtok (NULL, "\n\r");		/* Take rest of line including spaces. */
-
-	    if (t == NULL) {
-	      t = " ";				/* Empty means permit nothing. */
-	    }
-
-	    p_digi_config->filter_str[from_chan][to_chan] = strdup(t);
-
-//TODO1.2:  Do a test run to see errors now instead of waiting.
-
-	  }
-
-
-/*
- * ==================== APRStt gateway ==================== 
- */
-
-/*
- * TTCORRAL 		- How to handle unknown positions
- *
- * TTCORRAL  latitude  longitude  offset-or-ambiguity 
- */
-
-	  else if (strcasecmp(t, "TTCORRAL") == 0) {
-	    //int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing latitude for TTCORRAL command.\n", line);
-	      continue;
-	    }
-	    p_tt_config->corral_lat = parse_ll(t,LAT,line);
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line);
-	      continue;
-	    }
-	    p_tt_config->corral_lon = parse_ll(t,LON,line);
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line);
-	      continue;
-	    }
-	    p_tt_config->corral_offset = parse_ll(t,LAT,line);
-	    if (p_tt_config->corral_offset == 1 ||
-		p_tt_config->corral_offset == 2 ||
-	 	p_tt_config->corral_offset == 3) {
-	      p_tt_config->corral_ambiguity = p_tt_config->corral_offset;
-	      p_tt_config->corral_offset = 0;
-	    }
-
-	    //dw_printf ("DEBUG: corral %f %f %f %d\n", p_tt_config->corral_lat,
-	    //	p_tt_config->corral_lon, p_tt_config->corral_offset, p_tt_config->corral_ambiguity);
-	  }
-
-/*
- * TTPOINT 		- Define a point represented by touch tone sequence.
- *
- * TTPOINT   pattern  latitude  longitude   
- */
-	  else if (strcasecmp(t, "TTPOINT") == 0) {
-
-	    struct ttloc_s *tl;
-	    int j;
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-	    tl->type = TTLOC_POINT;
-	    strcpy(tl->pattern, "");
-	    tl->point.lat = 0;
-	    tl->point.lon = 0;
-
-	    /* Pattern: B and digits */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line);
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    if (t[0] != 'B') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTPOINT pattern must begin with upper case 'B'.\n", line);
-	    }
-	    for (j=1; j<strlen(t); j++) {
-	      if ( ! isdigit(t[j])) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTPOINT pattern must be B and digits only.\n", line);
-	      }
-	    }
-
-	    /* Latitude */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line);
-	      continue;
-	    }
-	    tl->point.lat = parse_ll(t,LAT,line);
-
-	    /* Longitude */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing longitude for TTPOINT command.\n", line);
-	      continue;
-	    }
-	    tl->point.lon = parse_ll(t,LON,line);
-
-	    /* temp debugging */
-
-	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
-	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
-	    //		p_tt_config->ttloc_ptr[j].pattern);
-	    //}
-	  }
-
-/*
- * TTVECTOR 		- Touch tone location with bearing and distance.
- *
- * TTVECTOR   pattern  latitude  longitude  scale  unit  
- */
-	  else if (strcasecmp(t, "TTVECTOR") == 0) {
-
-	    struct ttloc_s *tl;
-	    int j;
-	    double scale;
-	    double meters;
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-	    tl->type = TTLOC_VECTOR;
-	    strcpy(tl->pattern, "");
-	    tl->vector.lat = 0;
-	    tl->vector.lon = 0;
-	    tl->vector.scale = 1;
-	   
-	    /* Pattern: B5bbbd... */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line);
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    if (t[0] != 'B') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTVECTOR pattern must begin with upper case 'B'.\n", line);
-	    }
-	    if (strncmp(t+1, "5bbb", 4) != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTVECTOR pattern would normally contain \"5bbb\".\n", line);
-	    }
-	    for (j=1; j<strlen(t); j++) {
-	      if ( ! isdigit(t[j]) && t[j] != 'b' && t[j] != 'd') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTVECTOR pattern must contain only B, digits, b, and d.\n", line);
-	      }
-	    }
-
-	    /* Latitude */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line);
-	      continue;
-	    }
-	    tl->vector.lat = parse_ll(t,LAT,line);
-
-	    /* Longitude */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing longitude for TTVECTOR command.\n", line);
-	      continue;
-	    }
-	    tl->vector.lon = parse_ll(t,LON,line);
-
-	    /* Longitude */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing scale for TTVECTOR command.\n", line);
-	      continue;
-	    }
-	    scale = atof(t);
-
-	    /* Unit. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing unit for TTVECTOR command.\n", line);
-	      continue;
-	    }
-	    meters = 0;
-	    for (j=0; j<NUM_UNITS && meters == 0; j++) {
-	      if (strcasecmp(units[j].name, t) == 0) {
-	        meters = units[j].meters;
-	      }
-	    }
-	    if (meters == 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Unrecognized unit for TTVECTOR command.  Using miles.\n", line);
-	      meters = 1609.344;
-	    }
-	    tl->vector.scale = scale * meters;
-
- 	    //dw_printf ("ttvector: %f meters\n", tl->vector.scale);
-
-	    /* temp debugging */
-
-	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
-	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
-	    //		p_tt_config->ttloc_ptr[j].pattern);
-	    //}
-	  }
-
-/*
- * TTGRID 		- Define a grid for touch tone locations.
- *
- * TTGRID   pattern  min-latitude  min-longitude  max-latitude  max-longitude
- */
-	  else if (strcasecmp(t, "TTGRID") == 0) {
-
-	    struct ttloc_s *tl;
-	    int j;
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-	    tl->type = TTLOC_GRID;
-	    strcpy(tl->pattern, "");
-	    tl->grid.lat0 = 0;
-	    tl->grid.lon0 = 0;
-	    tl->grid.lat9 = 0;
-	    tl->grid.lon9 = 0;
-
-	    /* Pattern: B [digit] x... y... */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line);
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    if (t[0] != 'B') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTGRID pattern must begin with upper case 'B'.\n", line);
-	    }
-	    for (j=1; j<strlen(t); j++) {
-	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTGRID pattern must be B, optional digit, xxx, yyy.\n", line);
-	      }
-	    }
-
-	    /* Minimum Latitude - all zeros in received data */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
-	      continue;
-	    }
-	    tl->grid.lat0 = parse_ll(t,LAT,line);
-
-	    /* Minimum Longitude - all zeros in received data */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
-	      continue;
-	    }
-	    tl->grid.lon0 = parse_ll(t,LON,line);
-
-	    /* Maximum Latitude - all nines in received data */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
-	      continue;
-	    }
-	    tl->grid.lat9 = parse_ll(t,LAT,line);
-
-	    /* Maximum Longitude - all nines in received data */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
-	      continue;
-	    }
-	    tl->grid.lon0 = parse_ll(t,LON,line);
-
-	    /* temp debugging */
-
-	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
-	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
-	    //	p_tt_config->ttloc_ptr[j].pattern);
-	    //}
-	  }
-
-/*
- * TTUTM 		- Specify UTM zone for touch tone locations.
- *
- * TTUTM   pattern  zone [ scale [ x-offset y-offset ] ]
- */
-	  else if (strcasecmp(t, "TTUTM") == 0) {
-
-	    struct ttloc_s *tl;
-	    int j;
-	    int znum;
-	    char *zlet;
-	    double dlat, dlon;
-	    long lerr;
-
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-	    tl->type = TTLOC_UTM;
-	    strcpy(tl->pattern, "");
-	    tl->utm.lzone = 0;
-	    tl->utm.scale = 1;
-	    tl->utm.x_offset = 0;
-	    tl->utm.y_offset = 0;
-
-	    /* Pattern: B [digit] x... y... */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    if (t[0] != 'B') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTUTM pattern must begin with upper case 'B'.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-	    for (j=1; j<strlen(t); j++) {
-	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTUTM pattern must be B, optional digit, xxx, yyy.\n", line);
-		// Bail out somehow.  continue would match inner for.
-	      }
-	    }
-
-	    /* Zone 1 - 60 and optional latitudinal letter. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing zone for TTUTM command.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-
-	    tl->utm.lzone = parse_utm_zone (t, &(tl->utm.hemi));
-
- 	    /* Optional scale. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t != NULL) {
-	      
-	      tl->utm.scale = atof(t);
-
-	      /* Optional x offset. */
-
-	      t = strtok (NULL, " ,\t\n\r");
-	      if (t != NULL) {
-
-	        tl->utm.x_offset = atof(t);
-
-	        /* Optional y offset. */
-
-	        t = strtok (NULL, " ,\t\n\r");
-	        if (t != NULL) {
-	     
-	          tl->utm.y_offset = atof(t);
-	        }
-	      }
-	    }
-
-	    /* Practice run to see if conversion might fail later with actual location. */
-
-	    lerr = Convert_UTM_To_Geodetic(tl->utm.lzone, tl->utm.hemi,               
-                        tl->utm.x_offset + 5 * tl->utm.scale,
-                        tl->utm.y_offset + 5 * tl->utm.scale,
-                        &dlat, &dlon);
-
-            if (lerr != 0) {
-	      char message [300];
-
-              utm_error_string (lerr, message);
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message);
-	      p_tt_config->ttloc_len--;
-	      continue;
-            }
-	  }
-
-/*
- * TTUSNG, TTMGRS 		- Specify zone/square for touch tone locations.
- *
- * TTUSNG   pattern  zone_square 
- * TTMGRS   pattern  zone_square 
- */
-	  else if (strcasecmp(t, "TTUSNG") == 0 || strcasecmp(t, "TTMGRS") == 0) {
-
-	    struct ttloc_s *tl;
-	    int j;
-	    int znum;
-	    char *zlet;
-	    int num_x, num_y;
-	    double lat, lon;
-	    long lerr;
-	    char message[300];
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-
-// TODO1.2: in progress...
-	    if (strcasecmp(t, "TTMGRS") == 0) {
-	      tl->type = TTLOC_MGRS;
-	    }
-	    else {
-	      tl->type = TTLOC_USNG;
-	    }
-	    strcpy(tl->pattern, "");
-	    strcpy(tl->mgrs.zone, "");
-
-	    /* Pattern: B [digit] x... y... */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTUSNG/TTMGRS command.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    if (t[0] != 'B') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTUSNG/TTMGRS pattern must begin with upper case 'B'.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-	    num_x = 0;
-	    num_y = 0;
-	    for (j=1; j<strlen(t); j++) {
-	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTUSNG/TTMGRS pattern must be B, optional digit, xxx, yyy.\n", line);
-		// Bail out somehow.  continue would match inner for.
-	      }
-	      if (t[j] == 'x') num_x++;
-	      if (t[j] == 'y') num_y++;
-	    }
-	    if (num_x < 1 || num_x > 5 || num_x != num_y) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTUSNG/TTMGRS must have 1 to 5 x and same number y.\n", line);  
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-
-	    /* Zone 1 - 60 and optional latitudinal letter. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing zone & square for TTUSNG/TTMGRS command.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-	    memset (tl->mgrs.zone, 0, sizeof (tl->mgrs.zone));
-	    strncpy (tl->mgrs.zone, t, sizeof (tl->mgrs.zone) - 1);
-
-	    /* Try converting it rather do our own error checking. */
-
-	    if (tl->type == TTLOC_MGRS) {
-	      lerr = Convert_MGRS_To_Geodetic (tl->mgrs.zone, &lat, &lon);
-	    }
-	    else {
-	      lerr = Convert_USNG_To_Geodetic (tl->mgrs.zone, &lat, &lon);
-	    }
-            if (lerr != 0) {
-
-              mgrs_error_string (lerr, message);
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid USNG/MGRS zone & square:  %s\n%s\n", line, tl->mgrs.zone, message);
-	      p_tt_config->ttloc_len--;
-	      continue;
-            }
-
-	    /* Should be the end. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t != NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Unexpected stuff at end ignored:  %s\n", line, t);
-	    }
-	  }
-
-
-/*
- * TTSATSQ 		- Define pattern to be used for Satellite square.
- *
- * TTSATSQ   pattern    
- *
- *			Pattern would be  B[0-9A-D]xxxx 
- */
-	  else if (strcasecmp(t, "TTSATSQ") == 0) {
-
-// TODO1.2:  TTSATSQ To be continued...
-
-	    struct ttloc_s *tl;
-	    int j;
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-	    tl->type = TTLOC_SATSQ;
-	    strcpy(tl->pattern, "");
-	    tl->point.lat = 0;
-	    tl->point.lon = 0;
-
-	    /* Pattern: B, optional additional button, exactly xxxx for matching */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTSATSQ command.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    if (t[0] != 'B') {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTSATSQ pattern must begin with upper case 'B'.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-
-	    /* Optionally one of 0-9ABCD */
-
-	    if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
-	      j = 2;
-	    }
-	    else {
-	      j = 1;
-	    }
-
-	    if (strcmp(t+j, "xxxx") != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: TTSATSQ pattern must end with exactly xxxx in lower case.\n", line);
-	      p_tt_config->ttloc_len--;
-	      continue;
-	    }
-
-	    /* temp debugging */
-
-	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
-	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
-	    //		p_tt_config->ttloc_ptr[j].pattern);
-	    //}
-	  }
-
-/*
- * TTMACRO 		- Define compact message format with full expansion
- *
- * TTMACRO   pattern  definition
- *
- *		pattern can contain:
- *			0-9 which must match exactly.
- *				In version 1.2, also allow A,B,C,D for exact match.
- *			x, y, z which are used for matching of variable fields.
- *			
- *		definition can contain:
- *			0-9, A, B, C, D, *, #, x, y, z.
- *			Not sure why # was included in there.
- */
-	  else if (strcasecmp(t, "TTMACRO") == 0) {
-
-	    struct ttloc_s *tl;
-	    int j;
-	    //char ch;
-	    int p_count[3], d_count[3];
-
-	    assert (p_tt_config->ttloc_size >= 2);
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    /* Allocate new space, but first, if already full, make larger. */
-	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
-	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
-	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
-	    }
-	    p_tt_config->ttloc_len++;
-	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
-
-	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
-	    tl->type = TTLOC_MACRO;
-	    strcpy(tl->pattern, "");
-
-	    /* Pattern: Any combination of digits, x, y, and z. */
-	    /* Also make note of which letters are used in pattern and defintition. */
- 	    /* Version 1.2: also allow A,B,C,D in the pattern. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line);
-	      continue;
-	    }
-	    strcpy (tl->pattern, t);
-
-	    p_count[0] = p_count[1] = p_count[2] = 0;
-
-	    for (j=0; j<strlen(t); j++) {
-	      if ( strchr ("0123456789ABCDxyz", t[j]) == NULL) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTMACRO pattern can contain only digits, A, B, C, D, and lower case x, y, or z.\n", line);
-	        continue;
-	      }
-	      /* Count how many x, y, z in the pattern. */
-	      if (t[j] >= 'x' && t[j] <= 'z') {
-		p_count[t[j]-'x']++;
-	      }
-	    }
-
-	    //text_color_set(DW_COLOR_DEBUG);
-	    //dw_printf ("Line %d: TTMACRO pattern \"%s\" p_count = %d %d %d.\n", line, t, p_count[0], p_count[1], p_count[2]);
-
-	    /* Now gather up the definition. */
-	    /* It can contain touch tone characters and lower case x, y, z for substitutions. */
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing definition for TTMACRO command.\n", line);
-	      tl->macro.definition = "";	/* Don't die on null pointer later. */
-	      continue;
-	    }
-	    tl->macro.definition = strdup(t);
-
-	    d_count[0] = d_count[1] = d_count[2] = 0;
-
-	    for (j=0; j<strlen(t); j++) {
-	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y' && t[j] != 'z' &&
-			t[j] != 'A' && t[j] != 'B' && t[j] != 'C' && t[j] != 'D' && 
-			t[j] != '*' && t[j] != '#') {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line);
-	        continue;
-	      }
-	      if (t[j] >= 'x' && t[j] <= 'z') {
-		d_count[t[j]-'x']++;
-	      }
-	    }
-
-	    /* A little validity checking. */
-
-	    for (j=0; j<3; j++) {
-	      if (p_count[j] > 0 && d_count[j] == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: '%c' is in TTMACRO pattern but is not used in definition.\n", line, 'x'+j);
-	      }
-	      if (d_count[j] > 0 && p_count[j] == 0) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Line %d: '%c' is referenced in TTMACRO definition but does not appear in the pattern.\n", line, 'x'+j);
-	      }
-	    }
-	  }
-
-/*
- * TTOBJ 		- TT Object Report options.
- *
- * TTOBJ  recv-chan  xmit-chan  [ via-path ] 
- */
-
-
-	  else if (strcasecmp(t, "TTOBJ") == 0) {
-	    int r, x;
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing DTMF receive channel for TTOBJ command.\n", line);
-	      continue;
-	    }
-
-	    r = atoi(t);
-	    if (r < 0 || r > MAX_CHANS-1) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: DTMF receive channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    if ( ! p_audio_config->achan[r].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n", 
-							line, r);
-	      continue;
-	    }
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line);
-	      continue;
-	    }
-
-	    x = atoi(t);
-	    if (x < 0 || x > MAX_CHANS-1) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    if ( ! p_audio_config->achan[x].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", 
-							line, x);
-	      continue;
-	    }
-
-// This enables the DTMF decoder on the specified channel.
-// Additional channels can be enabled with the DTMF command.
-// Note that they do not enable the APRStt gateway.
-
-	    p_audio_config->achan[r].dtmf_decode = DTMF_DECODE_ON;
-	    p_tt_config->gateway_enabled = 1;
-	    p_tt_config->obj_recv_chan = r;
-	    p_tt_config->obj_xmit_chan = x;
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t != NULL) {
-
-	      // TODO: Should do some validity checking.
-	      strncpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via));
-	    }
-	  }
-
-/*
- * ==================== Internet gateway ==================== 
- */
-
-/*
- * IGSERVER 		- Name of IGate server.
- *
- * IGSERVER  hostname [ port ] 				-- original implementation.
- *
- * IGSERVER  hostname:port				-- more in line with usual conventions.
- */
-
-	  else if (strcasecmp(t, "IGSERVER") == 0) {
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line);
-	      continue;
-	    }
-	    strncpy (p_igate_config->t2_server_name, t, sizeof(p_igate_config->t2_server_name)-1);
-
-	    /* If there is a : in the name, split it out as the port number. */
-
-	    t = strchr (p_igate_config->t2_server_name, ':');
-	    if (t != NULL) {
-	      *t = '\0';
-	      t++;
-	      int n = atoi(t);
-              if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) {
-	        p_igate_config->t2_server_port = n;
-	      }
-	      else {
-	        p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", 
-			line, p_igate_config->t2_server_port);
-   	      }
-	    }
-
-	    /* Alternatively, the port number could be separated by white space. */
-	    
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t != NULL) {
-	      int n = atoi(t);
-              if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) {
-	        p_igate_config->t2_server_port = n;
-	      }
-	      else {
-	        p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
-	        text_color_set(DW_COLOR_ERROR);
-                dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", 
-			line, p_igate_config->t2_server_port);
-   	      }
-	    }
-	    //printf ("DEBUG  server=%s   port=%d\n", p_igate_config->t2_server_name, p_igate_config->t2_server_port);
-	    //exit (0);
-	  }
-
-/*
- * IGLOGIN 		- Login callsign and passcode for IGate server
- *
- * IGLOGIN  callsign  passcode
- */
-
-	  else if (strcasecmp(t, "IGLOGIN") == 0) {
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing login callsign for IGLOGIN command.\n", line);
-	      continue;
-	    }
-	    // TODO: Wouldn't hurt to do validity checking of format.
-	    strncpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login)-1);
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line);
-	      continue;
-	    }
-	    strncpy (p_igate_config->t2_passcode, t, sizeof(p_igate_config->t2_passcode)-1);
-	  }
-
-/*
- * IGTXVIA 		- Transmit channel and VIA path for messages from IGate server
- *
- * IGTXVIA  channel  [ path ]
- */
-
-	  else if (strcasecmp(t, "IGTXVIA") == 0) {
-	    int n;
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing transmit channel for IGTXVIA command.\n", line);
-	      continue;
-	    }
-
-	    n = atoi(t);
-	    if (n < 0 || n > MAX_CHANS-1) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", 
-							MAX_CHANS-1, line);
-	      continue;
-	    }
-	    p_igate_config->tx_chan = n;
-
-	    t = strtok (NULL, " \t\n\r");
-	    if (t != NULL) {
-	      char *p;
-	      p_igate_config->tx_via[0] = ',';
-	      strncpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-2);
-	      for (p = p_igate_config->tx_via; *p != '\0'; p++) {
-	        if (islower(*p)) {
-		  *p = toupper(*p);	/* silently force upper case. */
-	        }
-	      }
-	    }
-	  }
-
-/*
- * IGFILTER 		- Filter for messages from IGate server
- *
- * IGFILTER  filter-spec ... 
- */
-
-	  else if (strcasecmp(t, "IGFILTER") == 0) {
-	    //int n;
-
-	    t = strtok (NULL, "\n\r");		/* Take rest of line as one string. */
-
-	    if (t != NULL && strlen(t) > 0) {
-	      p_igate_config->t2_filter = strdup (t);
-	    }
-	  }
-
-
-/*
- * IGTXLIMIT 		- Limit transmissions during 1 and 5 minute intervals.
- *
- * IGTXLIMIT  one-minute-limit  five-minute-limit
- */
-
-	  else if (strcasecmp(t, "IGTXLIMIT") == 0) {
-	    int n;
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line);
-	      continue;
-	    }
-	    
-	    /* limits of 20 and 100 are unfriendly but not insane. */
-
-	    n = atoi(t);
-            if (n >= 1 && n <= 20) {
-	      p_igate_config->tx_limit_1 = n;
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid one minute transmit limit. Using %d.\n", 
-				line, p_igate_config->tx_limit_1);
-   	    }
-
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing five minute limit for IGTXLIMIT command.\n", line);
-	      continue;
-	    }
-	    
-	    n = atoi(t);
-            if (n >= 1 && n <= 100) {
-	      p_igate_config->tx_limit_5 = n;
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid one minute transmit limit. Using %d.\n", 
-				line, p_igate_config->tx_limit_5);
-   	    }
-	  }
-
-/*
- * ==================== All the left overs ==================== 
- */
-
-/*
- * AGWPORT 		- Port number for "AGW TCPIP Socket Interface" 
- *
- * In version 1.2 we allow 0 to disable listening.
- */
-
-	  else if (strcasecmp(t, "AGWPORT") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing port number for AGWPORT command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
-	      p_misc_config->agwpe_port = n;
-	    }
-	    else {
-	      p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n", 
-			line, p_misc_config->agwpe_port);
-   	    }
-	  }
-
-/*
- * KISSPORT 		- Port number for KISS over IP. 
- */
-
-	  else if (strcasecmp(t, "KISSPORT") == 0) {
-	    int n;
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line);
-	      continue;
-	    }
-	    n = atoi(t);
-            if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
-	      p_misc_config->kiss_port = n;
-	    }
-	    else {
-	      p_misc_config->kiss_port = DEFAULT_KISS_PORT;
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid port number for KISS TCPIP Socket Interface. Using %d.\n", 
-			line, p_misc_config->kiss_port);
-   	    }
-	  }
-
-/*
- * NULLMODEM		- Device name for our end of the virtual "null modem"
- */
-	  else if (strcasecmp(t, "nullmodem") == 0) {
-	    t = strtok (NULL, " ,\t\n\r");
-	    if (t == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Missing device name for my end of the 'null modem' on line %d.\n", line);
-	      continue;
-	    }
-	    else {
-	      strncpy (p_misc_config->nullmodem, t, sizeof(p_misc_config->nullmodem)-1);
-	    }
-	  }
-
-/*
- * 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);
-	    }
-	  }
-
-/*
- * BEACON channel delay every message
- *
- * Original handcrafted style.  Removed in version 1.0.
- */
-
-	  else if (strcasecmp(t, "BEACON") == 0) {
-	    	    
-	    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");
-  
-	  }
-
-
-/*
- * PBEACON keyword=value ...
- * OBEACON keyword=value ...
- * TBEACON keyword=value ...
- * CBEACON keyword=value ...
- *
- * New style with keywords for options.
- */
-
-	  else if (strcasecmp(t, "PBEACON") == 0 ||
-		   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));
-	      if (strcasecmp(t, "PBEACON") == 0) {
-	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_POSITION;
-	      }
-	      else if (strcasecmp(t, "OBEACON") == 0) {
-	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_OBJECT;
-	      }
-	      else if (strcasecmp(t, "TBEACON") == 0) {
-	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_TRACKER;
-	      }
-	      else {
-	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_CUSTOM;
-	      }
-
-	      /* Save line number because some errors will be reported later. */
-	      p_misc_config->beacon[p_misc_config->num_beacons].lineno = line;
-
-	      if (beacon_options(t + strlen("xBEACON") + 1, &(p_misc_config->beacon[p_misc_config->num_beacons]), line, p_audio_config)) {
-	        p_misc_config->num_beacons++;
-	      }
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Maximum number of beacons exceeded on line %d.\n", line);
-	      continue;
-	    }
-	  }
-
-
-/*
- * SMARTBEACONING fast_speed fast_rate slow_speed slow_rate turn_time turn_angle turn_slope
- */
-
-	  else if (strcasecmp(t, "SMARTBEACON") == 0 ||	
-	           strcasecmp(t, "SMARTBEACONING") == 0) {
-
-	    int n;
-
-#define SB_NUM(name,sbvar,minn,maxx,unit)  							\
-	    t = strtok (NULL, " ,\t\n\r");							\
-	    if (t == NULL) {									\
-	      text_color_set(DW_COLOR_ERROR);							\
-	      dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name);		\
-	      continue;										\
-	    }											\
-	    n = atoi(t);									\
-            if (n >= minn && n <= maxx) {							\
-	      p_misc_config->sbvar = n;								\
-	    }											\
-	    else {										\
-	      text_color_set(DW_COLOR_ERROR);							\
-              dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n",	\
-			line, name, p_misc_config->sbvar, unit);				\
-   	    }
-
-#define SB_TIME(name,sbvar,minn,maxx,unit)  							\
-	    t = strtok (NULL, " ,\t\n\r");							\
-	    if (t == NULL) {									\
-	      text_color_set(DW_COLOR_ERROR);							\
-	      dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name);		\
-	      continue;										\
-	    }											\
-	    n = parse_interval(t,line);								\
-            if (n >= minn && n <= maxx) {							\
-	      p_misc_config->sbvar = n;								\
-	    }											\
-	    else {										\
-	      text_color_set(DW_COLOR_ERROR);							\
-              dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n",	\
-			line, name, p_misc_config->sbvar, unit);				\
-   	    }
-
-
-	    SB_NUM  ("fast speed", sb_fast_speed,  2,   90,  "MPH")
-	    SB_TIME ("fast rate",  sb_fast_rate,  10,  300,  "seconds")
-
-	    SB_NUM  ("slow speed", sb_slow_speed,  1,   30,  "MPH")
-	    SB_TIME ("slow rate",  sb_slow_rate,  30, 3600,  "seconds")
-
-	    SB_TIME ("turn time",  sb_turn_time,   5,  180,  "seconds")
-	    SB_NUM  ("turn angle", sb_turn_angle,  5,   90,  "degrees")
-	    SB_NUM  ("turn slope", sb_turn_slope,  1,  255,  "deg*mph")
-
-	    /* If I was ambitious, I might allow optional */
-	    /* unit at end for miles or km / hour. */
-
-	    p_misc_config->sb_configured = 1;
-	  }
-
-/*
- * Invalid command.
- */
-	  else {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Config file: Unrecognized command '%s' on line %d.\n", t, line);
-	  }  
-
-	}
-
-	fclose (fp);
-
-/*
- * A little error checking for option interactions.
- */
-
-/*
- * Require that MYCALL be set when digipeating or IGating.
- *
- * Suggest that beaconing be enabled when digipeating.
- */
-	int i, j, k, b;
-
-	for (i=0; i<MAX_CHANS; i++) {
-	  for (j=0; j<MAX_CHANS; j++) {
-
-	    if (p_digi_config->enabled[i][j]) {
-
-	      if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || 
-		   strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || 
-		   strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
-
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i);
-	        p_digi_config->enabled[i][j] = 0;
-	      }
-
-	      if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || 
-	           strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 ||
-		   strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) {
-
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); 
-	        p_digi_config->enabled[i][j] = 0;
-	      }
-
-	      b = 0;
-	      for (k=0; k<p_misc_config->num_beacons; k++) {
-	        if (p_misc_config->beacon[k].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", j); 
-		// 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;
-	      }
-	    }
-	  }
-
-	  if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) {
-
-	    if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0  || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i);
-	      strcpy (p_igate_config->t2_login, "");
-	    }
-	    if (p_igate_config->tx_chan >= 0 && 
-			( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 ||
-		          strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 ||
-			  strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "N0CALL") == 0)) {
-
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: MYCALL must be set for transmit channel %d before Tx IGate is allowed.\n", i);
-	      p_igate_config->tx_chan = -1;
-	    }
-	  }
-
-	}
-
-} /* end config_init */
-
-
-/*
- * Parse the PBEACON or OBEACON options.
- * Returns 1 for success, 0 for serious error.
- */
-
-static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config)
-{
-	char options[1000];
-	char *o;
-	char *t;
-	char *p;
-	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->sendto_type = SENDTO_XMIT;
-	b->sendto_chan = 0;
-	b->delay = 60;
-	b->every = 600;
-	//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 */
-
-/*
- * cmd should be rest of command line after ?BEACON was removed.
- *
- * Quoting is required for any values containing spaces.
- * This could happen for an object name, comment, symbol description, ...
- * To prevent strtok from stopping at those spaces, change them to 
- * non-breaking space character temporarily.  After spliting everything
- * up at white space, change them back to normal spaces.
- */
- 
-#define NBSP (' ' + 0x80)
-
-	p = cmd;		/* Process from here. */
-	o = options;		/* to here. */
-	q = 0;			/* Keep track of whether in quoted part. */
-
-	for ( ; *p != '\0' ; p++) {
-
-	  switch (*p) {
-
-	    case '"':
-	      if (!q) {		/* opening quote */
-	        if (*(p-1) != '=') {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Config file: line %d: Suspicious use of \" not after =.\n", line);
-	 	  dw_printf ("Suggestion: Double it and quote entire value.\n");
-	          *o++ = '"';	/* Treat as regular character. */
-	        }
-	        else {
-	          q = 1;
-	        }
-	      }
-	      else {		/* embedded or closing quote */
-	        if (*(p+1) == '"') {
-	          *o++ = '"';	/* reduce double to single */
-	          p++;
-	        }
-                else if (isspace(*(p+1)) || *(p+1) == '\0') {
-	          q = 0;
-	        }
-	        else {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Config file: line %d: Suspicious use of \" not at end of value.\n", line);
-	 	  dw_printf ("Suggestion: Double it and quote entire value.\n");
-	          *o++ = '"';	/* Treat as regular character. */
-	        }		  
-	      }
-	      break;
-
-	    case ' ':
-
-	      *o++ = q ? NBSP : ' ';
-	      break;
-
-	    default:
-	      *o++ = *p;
-	      break;
-	  }
-	}
-	*o = '\0';
-
-	for (t = strtok (options, " \t\n\r"); t != NULL; t = strtok (NULL, " \t\n\r")) {
-
-	  char keyword[20];
-	  char value[200];
-	  char *e;
-	  char *p;
-	  //int q;
-
-
-	  e = strchr(t, '=');
-	  if (e == NULL) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Config file: No = found in, %s, on line %d.\n", t, line);
-	    return (0);
-	  }
-	  *e = '\0';
-	  strcpy (keyword, t);
-	  strcpy (value, e+1);
-
-/* Put back normal spaces. */
-
-	  for (p = value; *p != '\0'; p++) {
-	    // char is signed for MinGW!
-	    if (((int)(*p) & 0xff) == NBSP) *p = ' ';
-	  }
-
-	  if (strcasecmp(keyword, "DELAY") == 0) {
-	    b->delay = parse_interval(value,line);
-	  }
-	  else if (strcasecmp(keyword, "EVERY") == 0) {
-	    b->every = parse_interval(value,line);
-	  }
-	  else if (strcasecmp(keyword, "SENDTO") == 0) {
-	    if (value[0] == 'i' || value[0] == 'I') {
-	       b->sendto_type = SENDTO_IGATE;
-	       b->sendto_chan = 0;
-	    }
-	    else if (value[0] == 'r' || value[0] == 'R') {
-	       int n = atoi(value+1);
-	       if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
-	         text_color_set(DW_COLOR_ERROR);
-	         dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
-	         continue;
-	       }
-	       b->sendto_type = SENDTO_RECV;
-	       b->sendto_chan = n;
-	    }
-	    else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') {
-	      int n = atoi(value+1);
-	      if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
-	        continue;
-	      }
-
-	      b->sendto_type = SENDTO_XMIT;
-	      b->sendto_chan = n;
-	    }
-	    else {
-	       int n = atoi(value);
-	       if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
-	         text_color_set(DW_COLOR_ERROR);
-	         dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
-	         continue;
-	       }
-	       b->sendto_type = SENDTO_XMIT;
-	       b->sendto_chan = n;
-	    }
-	  }
-	  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) {
-	    b->via = strdup(value);
-	    for (p = b->via; *p != '\0'; p++) {
-	      if (islower(*p)) {
-	        *p = toupper(*p);	/* silently force upper case. */
-	      }
-	    }
-	  }
-	  else if (strcasecmp(keyword, "INFO") == 0) {
-	    b->custom_info = strdup(value);
-	  }
-	  else if (strcasecmp(keyword, "OBJNAME") == 0) {
-	    strncpy(b->objname, value, 9);
-	  }
-	  else if (strcasecmp(keyword, "LAT") == 0) {
-	    b->lat = parse_ll (value, LAT, 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);
-	  }
-	  else if (strcasecmp(keyword, "OVERLAY") == 0) {
-	    if (strlen(value) == 1 && (isupper(value[0]) || isdigit(value[0]))) {
-	      b->symtab = value[0];
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file: Overlay must be one character in range of 0-9 or A-Z, upper case only, on line %d.\n", line);
-	    }
-	  }
-	  else if (strcasecmp(keyword, "POWER") == 0) {
-	    b->power = atoi(value);
-	  }
-	  else if (strcasecmp(keyword, "HEIGHT") == 0) {
-	    b->height = atoi(value);
-	  }
-	  else if (strcasecmp(keyword, "GAIN") == 0) {
-	    b->gain = atoi(value);
-	  }
-	  else if (strcasecmp(keyword, "DIR") == 0 || strcasecmp(keyword, "DIRECTION") == 0) {
-	    strncpy(b->dir, value, 2);
-	  }
-	  else if (strcasecmp(keyword, "FREQ") == 0) {
-	    b->freq = atof(value);
-	  }
-	  else if (strcasecmp(keyword, "TONE") == 0) {
-	    b->tone = atof(value);
-	  }
-	  else if (strcasecmp(keyword, "OFFSET") == 0 || strcasecmp(keyword, "OFF") == 0) {
-	    b->offset = atof(value);
-	  }
-	  else if (strcasecmp(keyword, "COMMENT") == 0) {
-	    b->comment = strdup(value);
-	  }
-	  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);
-	    return (0);
-	  }
-	}
-
-/*
- * 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) {
-
-	    long lzone;
-	    char hemi;
-	    long lerr;
-	    double dlat, dlon;
-
-	    lzone = parse_utm_zone (zone, &hemi);
-
-	    lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon);
-
-            if (lerr == 0) {
-              b->lat = R2D(dlat);
-	      b->lon = R2D(dlon);
-	    }
-	    else {
-	      char message [300];
-
-              utm_error_string (lerr, message);
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message);
-            }
-	  }
-	  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) {
-
-	  if (strlen(temp_symbol) == 2 && 
-		(temp_symbol[0] == '/' || temp_symbol[0] == '\\' || isupper(temp_symbol[0]) || isdigit(temp_symbol[0])) &&
-		temp_symbol[1] >= '!' && temp_symbol[1] <= '~') {
-
-	    /* Explicit table and symbol. */
-
-	    if (isupper(b->symtab) || isdigit(b->symtab)) {
-	      b->symbol = temp_symbol[1];
-	    } 
-	    else {
-	      b->symtab = temp_symbol[0];
-	      b->symbol = temp_symbol[1];
-	    }
-	  }
-	  else {
-
-	    /* Try to look up by description. */
-	    ok = symbols_code_from_description (b->symtab, temp_symbol, &(b->symtab), &(b->symbol));
-	    if (!ok) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Config file, line %d: Could not find symbol matching %s.\n", line, temp_symbol);
-	    }
-	  }
-	}
-
-/* Check is here because could be using default channel when SENDTO= is not specified. */
-
-	if (b->sendto_type == SENDTO_XMIT) {
-
-	  if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || ! p_audio_config->achan[b->sendto_chan].valid) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan);
-	    return (0);
-	  }
-
-	  if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || 
-	       strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || 
-	       strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) {
-
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); 
-	    return (0);
-	  }
-	}
-
-	return (1);
-}
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016  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 CONFIG_C 1
+
+
+// #define DEBUG 1
+
+/*------------------------------------------------------------------
+ *
+ * Module:      config.c
+ *
+ * Purpose:   	Read configuration information from a file.
+ *		
+ * Description:	This started out as a simple little application with a few
+ *		command line options.  Due to creeping featurism, it's now
+ *		time to add a configuration file to specify options.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+#if ENABLE_GPSD
+#include <gps.h>		/* for DEFAULT_GPSD_PORT  (2947) */
+#endif
+
+
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "digipeater.h"
+#include "config.h"
+#include "aprs_tt.h"
+#include "igate.h"
+#include "latlong.h"
+#include "symbols.h"
+#include "xmit.h"
+#include "tt_text.h"
+
+// geotranz
+
+#include "utm.h"
+#include "mgrs.h"
+#include "usng.h"
+#include "error_string.h"
+
+#define D2R(d) ((d) * M_PI / 180.)
+#define R2D(r) ((r) * 180. / M_PI)
+
+
+
+//#include "tq.h"
+
+/* 
+ * Conversions from various units to meters.
+ * There is some disagreement about the exact values for some of these. 
+ * Close enough for our purposes.
+ * Parsec, light year, and angstrom are probably not useful.
+ */
+
+static const struct units_s {
+	char *name;
+	float meters;
+} units[] = {
+	{	"barleycorn",	0.008466667	},	
+	{	"inch",		0.0254		},
+	{	"in",		0.0254		},
+	{	"hand",		0.1016		},	
+	{	"shaku",	0.3030		},	
+	{	"foot",		0.304801	},	
+	{	"ft",		0.304801	},	
+	{	"cubit",	0.4572		},	
+	{	"megalithicyard", 0.8296	},	
+	{	"my",		0.8296		},	
+	{	"yard",		0.914402	},
+	{	"yd",		0.914402	},
+	{	"m",		1.		},	
+	{	"meter",	1.		},	
+	{	"metre",	1.		},	
+	{	"ell",		1.143		},	
+	{	"ken",		1.818		},	
+	{	"hiro",		1.818		},	
+	{	"fathom",	1.8288		},	
+	{	"fath",		1.8288		},	
+	{	"toise",	1.949		},
+	{	"jo",		3.030		},
+	{	"twain",	3.6576074	},	
+	{	"rod",		5.0292		},	
+	{	"rd",		5.0292		},	
+	{	"perch",	5.0292		},	
+	{	"pole",		5.0292		},	
+	{	"rope",		6.096		},	
+	{	"dekameter",	10.		},	
+	{	"dekametre",	10.		},	
+	{	"dam",		10.		},	
+	{	"chain",	20.1168		},
+	{	"ch",		20.1168		},
+	{	"actus",	35.47872	},	
+	{	"arpent",	58.471		},	
+	{	"hectometer",	100.		},	
+	{	"hectometre",	100.		},	
+	{	"hm",		100.		},	
+	{	"cho",		109.1		},	
+	{	"furlong",	201.168		},
+	{	"fur",		201.168		},
+	{	"kilometer",	1000.		},	
+	{	"kilometre",	1000.		},	
+	{	"km",		1000.		},	
+	{	"mile",		1609.344	},	
+	{	"mi",		1609.344	},	
+	{	"ri",		3927.		},	
+	{	"league",	4828.032	},	
+	{	"lea",		4828.032	} };
+
+#define NUM_UNITS (sizeof(units) / sizeof(struct units_s))
+
+static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config);
+
+/* Do we have a string of all digits? */
+
+static int alldigits(char *p)
+{
+	if (p == NULL) return (0);
+	if (strlen(p) == 0) return (0);
+	while (*p != '\0') {
+	  if ( ! isdigit(*p)) return (0);
+	  p++;
+	}
+	return (1);
+}
+
+/* Do we have a string of all letters or + or -  ? */
+
+static int alllettersorpm(char *p)
+{
+	if (p == NULL) return (0);
+	if (strlen(p) == 0) return (0);
+	while (*p != '\0') {
+	  if ( ! isalpha(*p) && *p != '+' && *p != '-') return (0);
+	  p++;
+	}
+	return (1);
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_ll
+ *
+ * Purpose:     Parse latitude or longitude from configuration file.
+ *
+ * Inputs:      str	- String like [-]deg[^min][hemisphere]
+ *
+ *		which	- LAT or LON for error checking and message.
+ *
+ *		line	- Line number for use in error message. 
+ *
+ * Returns:     Coordinate in signed degrees.
+ *
+ *----------------------------------------------------------------*/
+
+/* Acceptable symbols to separate degrees & minutes. */
+/* Degree symbol is not in ASCII so documentation says to use "^" instead. */
+/* Some wise guy will try to use degree symbol. */
+/* UTF-8 is more difficult because it is a two byte sequence, c2 b0. */
+
+#define DEG1 '^'
+#define DEG2 0xb0	/* ISO Latin1 */
+#define DEG3 0xf8	/* Microsoft code page 437 */
+
+
+enum parse_ll_which_e { LAT, LON };
+
+static double parse_ll (char *str, enum parse_ll_which_e which, int line)
+{
+	char stemp[40];
+	int sign;
+	double degrees, minutes;
+	char *endptr;
+	char hemi;
+	int limit;
+	unsigned char sep;
+
+/*
+ * Remove any negative sign.
+ */
+	strlcpy (stemp, str, sizeof(stemp));
+	sign = +1;
+	if (stemp[0] == '-') {
+	  sign = -1;
+	  stemp[0] = ' ';
+	}
+/*
+ * Process any hemisphere on the end.
+ */
+	if (strlen(stemp) >= 2) {
+	  endptr = stemp + strlen(stemp) - 1;
+	  if (isalpha(*endptr)) {
+
+	    hemi = *endptr;
+	    *endptr = '\0';
+	    if (islower(hemi)) {
+	      hemi = toupper(hemi);
+	    }
+
+	    if (hemi == 'W' || hemi == 'S') {
+	      sign = -sign;
+	    }
+
+	    if (which == LAT) {
+	      if (hemi != 'N' && hemi != 'S') {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: Latitude hemisphere in \"%s\" is not N or S.\n", line, str);
+	      }
+	    }
+	    else {
+	      if (hemi != 'E' && hemi != 'W') {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: Longitude hemisphere in \"%s\" is not E or W.\n", line, str);
+	      }
+	    }
+	  }
+	}
+
+/*
+ * Parse the degrees part.
+ */
+	degrees = strtod (stemp, &endptr);
+
+/*
+ * Is there a minutes part?
+ */
+	sep = *endptr;
+	if (sep != '\0') {
+
+	  if (sep == DEG1 || sep == DEG2 || sep == DEG3) {
+	 
+	    minutes = strtod (endptr+1, &endptr);
+	    if (*endptr != '\0') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str);
+	    }
+	    if (minutes >= 60.0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Number of minutes in \"%s\" is >= 60.\n", line, str);
+	    }
+	    degrees += minutes / 60.0;
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str);
+	  }
+	}
+
+	degrees = degrees * sign;
+
+	limit = which == LAT ? 90 : 180;
+	if (degrees < -limit || degrees > limit) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Line %d: Number of degrees in \"%s\" is out of range for %s\n", line, str,
+		which == LAT ? "latitude" : "longitude");
+	}
+	//dw_printf ("%s = %f\n", str, degrees);
+	return (degrees);
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_utm_zone
+ *
+ * Purpose:     Parse UTM zone from configuration file.
+ *
+ * Inputs:      szone	- String like [-]number[letter]
+ *
+ * Outputs:	latband	- Latitude band if specified, otherwise space or -.
+ *
+ *		hemi	- Hemisphere, always one of 'N' or 'S'.
+ *
+ * Returns:	Zone as number.  
+ *		Type is long because Convert_UTM_To_Geodetic expects that.
+ *
+ * Errors:	Prints message and return 0.
+ *
+ * Description:	
+ *		It seems there are multiple conventions for specifying the UTM hemisphere.
+ *		
+ *		  - MGRS latitude band.  North if missing or >= 'N'.
+ *		  - Negative zone for south.
+ *		  - Separate North or South.
+ *		
+ *		I'm using the first alternatve.
+ *		GEOTRANS uses the third.
+ *		We will also recognize the second one but I'm not sure if I want to document it.
+ *
+ *----------------------------------------------------------------*/
+
+long parse_utm_zone (char *szone, char *latband, char *hemi)
+{
+	long lzone;
+	char *zlet;
+
+
+	*latband = ' ';
+	*hemi = 'N';	/* default */
+
+        lzone = strtol(szone, &zlet, 10);
+
+        if (*zlet == '\0') {
+	  /* Number is not followed by letter something else.  */
+	  /* Allow negative number to mean south. */
+
+	  if (lzone < 0) {
+	    *latband = '-';
+	    *hemi = 'S';
+	    lzone = (- lzone);
+	  }
+	}
+	else {
+	  if (islower (*zlet)) {
+	    *zlet = toupper(*zlet);
+	  }
+	  *latband = *zlet;
+	  if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) {
+	    if (*zlet < 'N') {
+	      *hemi = 'S';
+	    }
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+            dw_printf ("Latitudinal band in \"%s\" must be one of CDEFGHJKLMNPQRSTUVWX.\n", szone);
+ 	    *hemi = '?';
+	  }
+        }
+
+        if (lzone < 1 || lzone > 60) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf ("UTM Zone number %ld must be in range of 1 to 60.\n", lzone);
+        
+        }
+
+	return (lzone);
+
+} /* end parse_utm_zone */
+
+
+
+
+#if 0
+main ()
+{
+
+	parse_ll ("12.5", LAT);
+	parse_ll ("12.5N", LAT);
+	parse_ll ("12.5E", LAT);	// error
+
+	parse_ll ("-12.5", LAT);
+	parse_ll ("12.5S", LAT);
+	parse_ll ("12.5W", LAT);	// error
+
+	parse_ll ("12.5", LON);
+	parse_ll ("12.5E", LON);
+	parse_ll ("12.5N", LON);	// error
+
+	parse_ll ("-12.5", LON);
+	parse_ll ("12.5W", LON);
+	parse_ll ("12.5S", LON);	// error
+
+	parse_ll ("12^30", LAT);
+	parse_ll ("12\xb030", LAT);			// ISO Latin-1 degree symbol
+
+	parse_ll ("91", LAT);		// out of range
+	parse_ll ("91", LON);
+	parse_ll ("181", LON);		// out of range
+
+	parse_ll ("12&5", LAT);		// bad character
+}
+#endif
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        parse_interval
+ *
+ * Purpose:     Parse time interval from configuration file.
+ *
+ * Inputs:      str	- String like 10 or 9:30
+ *
+ *		line	- Line number for use in error message. 
+ *
+ * Returns:     Number of seconds.
+ *
+ * Description:	This is used by the BEACON configuration items
+ *		for initial delay or time between beacons.
+ *
+ *		The format is either minutes or minutes:seconds.
+ *
+ *----------------------------------------------------------------*/
+
+
+static int parse_interval (char *str, int line)
+{
+	char *p;
+	int sec;
+	int nc = 0;
+	int bad = 0;
+
+	for (p = str; *p != '\0'; p++) {
+	  if (*p == ':') nc++;
+	  else if ( ! isdigit(*p)) bad++;
+	}
+	if (bad > 0 || nc > 1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Config file, line %d: Time interval must be of the form minutes or minutes:seconds.\n", line);
+	}
+
+	p = strchr (str, ':');
+
+	if (p != NULL) {
+	  sec = atoi(str) * 60 + atoi(p+1);
+	}
+	else {
+	  sec = atoi(str) * 60;
+	}
+
+	return (sec);
+
+} /* end parse_interval */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        split
+ *
+ * Purpose:     Separate a line into command and parameters.
+ *
+ * Inputs:	string		- Complete command line to start process.
+ *				  NULL for subsequent calls.
+ *
+ *		rest_of_line	- Caller wants remainder of line, not just
+ *				  the next parameter.
+ *
+ * Returns:	Pointer to next part with any quoting removed.
+ *
+ * Description:	the configuration file started out very simple and strtok
+ *		was used to split up the lines.  As more complicated options
+ *		were added, there were several different situations where
+ *		parameter values might contain spaces.  These were handled
+ *		inconsistently in different places.  In version 1.3, we now
+ *		treat them consistently in one place.
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+#define MAXCMDLEN 256
+
+
+static char *split (char *string, int rest_of_line)
+{
+	static char cmd[MAXCMDLEN];
+	static char token[MAXCMDLEN];
+	static char *c;		// current position in cmd.
+	char *s, *t;
+	int in_quotes;
+
+/*
+ * If string is provided, make a copy.
+ * Drop any CRLF at the end.
+ * Change any tabs to spaces so we don't have to check for it later.
+ */
+	if (string != NULL) {
+
+	  // dw_printf("split in: '%s'\n", string);
+
+	  c = cmd;
+	  for (s = string; *s != '\0'; s++) {
+	    if (*s == '\t') {
+	      *c++ = ' ';
+	    }
+	    else if (*s == '\r' || *s == '\n') {
+	      ;
+	    }
+	    else {
+	      *c++ = *s;
+	    }
+	  }
+	  *c = '\0';
+	  c = cmd;
+	}
+
+/*
+ * Get next part, separated by whitespace, keeping spaces within quotes.
+ * Quotation marks inside need to be doubled.
+ */
+
+	while (*c == ' ') {
+	  c++;
+	};
+
+	t = token;
+	in_quotes = 0;
+	for ( ; *c != '\0'; c++) {
+
+	  if (*c == '"') {
+	    if (in_quotes) {
+	      if (c[1] == '"') {
+	        *t++ = *c++;
+	      }
+	      else {
+	        in_quotes = 0;
+	      }
+	    }
+	    else {
+	      in_quotes = 1;
+	    }
+	  }
+	  else if (*c == ' ') {
+	    if (in_quotes || rest_of_line) {
+	      *t++ = *c;
+	    }
+	    else {
+	      break;
+	    }
+	  }
+	  else {
+	    *t++ = *c;
+	  }
+	}
+	*t = '\0';
+
+	// dw_printf("split out: '%s'\n", token);
+
+	t = token;
+	if (*t == '\0') {
+	  return (NULL);
+	}
+
+	return (t);
+
+} /* end split */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        config_init
+ *
+ * Purpose:     Read configuration file when application starts up.
+ *
+ * Inputs:	fname		- Name of configuration file.
+ *
+ * Outputs:	p_audio_config		- Radio channel parameters stored here.
+ *
+ *		p_digi_config	- Digipeater configuration stored here.
+ *
+ *		p_tt_config	- APRStt stuff.
+ *
+ *		p_igate_config	- Internet Gateway.
+ *	
+ *		p_misc_config	- Everything else.  This wasn't thought out well.
+ *
+ * Description:	Apply default values for various parameters then read the 
+ *		the configuration file which can override those values.
+ *
+ * Errors:	For invalid input, display line number and message on stdout (not stderr).
+ *		In many cases this will result in keeping the default rather than aborting.
+ *
+ * Bugs:	Very simple-minded parsing.
+ *		Not much error checking.  (e.g. atoi() will return 0 for invalid string.)
+ *		Not very forgiving about sloppy input.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void config_init (char *fname, struct audio_s *p_audio_config, 
+			struct digi_config_s *p_digi_config,
+			struct tt_config_s *p_tt_config,
+			struct igate_config_s *p_igate_config,
+			struct misc_config_s *p_misc_config)
+{
+	FILE *fp;
+	char filepath[128];
+	char stuff[MAXCMDLEN];
+	int line;
+	int channel;
+	int adevice;
+	int m;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("config_init ( %s )\n", fname);
+#endif
+
+/* 
+ * First apply defaults.
+ */
+
+	memset (p_audio_config, 0, sizeof(struct audio_s));
+
+	/* First audio device is always available with defaults. */
+	/* Others must be explicitly defined before use. */
+
+	for (adevice=0; adevice<MAX_ADEVS; adevice++) {
+
+	  strlcpy (p_audio_config->adev[adevice].adevice_in, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_in));
+	  strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out));
+
+	  p_audio_config->adev[adevice].defined = 0;
+	  p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS;		/* -2 stereo */
+	  p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;	/* -r option */
+	  p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;	/* -8 option for 8 instead of 16 bits */
+	}
+
+	p_audio_config->adev[0].defined = 1;
+
+	for (channel=0; channel<MAX_CHANS; channel++) {
+	  int ot;
+
+	  p_audio_config->achan[channel].valid = 0;				/* One or both channels will be */
+								/* set to valid when corresponding */
+								/* audio device is defined. */
+	  p_audio_config->achan[channel].modem_type = MODEM_AFSK;			
+	  p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;		/* -m option */
+	  p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;		/* -s option */
+	  p_audio_config->achan[channel].baud = DEFAULT_BAUD;			/* -b option */
+
+	  /* None.  Will set default later based on other factors. */
+	  strlcpy (p_audio_config->achan[channel].profiles, "", sizeof(p_audio_config->achan[channel].profiles));	
+
+	  p_audio_config->achan[channel].num_freq = 1;				
+	  p_audio_config->achan[channel].offset = 0;
+
+	  p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
+	  p_audio_config->achan[channel].sanity_test = SANITY_APRS;
+	  p_audio_config->achan[channel].passall = 0;
+
+	  for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	    p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_NONE;
+	    strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "", sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
+	    p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE;
+	    p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE;
+	    p_audio_config->achan[channel].octrl[ot].ptt_gpio = 0;
+	    p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = 0;
+	    p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
+	    p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
+	  }
+
+	  p_audio_config->achan[channel].dwait = DEFAULT_DWAIT;				
+	  p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;				
+	  p_audio_config->achan[channel].persist = DEFAULT_PERSIST;				
+	  p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;				
+	  p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;				
+	}
+
+	/* First channel should always be valid. */
+	/* If there is no ADEVICE, it uses default device in mono. */
+
+	p_audio_config->achan[0].valid = 1;
+
+
+	memset (p_digi_config, 0, sizeof(struct digi_config_s));
+	p_digi_config->dedupe_time = DEFAULT_DEDUPE;
+
+	memset (p_tt_config, 0, sizeof(struct tt_config_s));	
+	p_tt_config->gateway_enabled = 0;
+	p_tt_config->ttloc_size = 2;	/* Start with at least 2.  */
+					/* When full, it will be increased by 50 %. */
+	p_tt_config->ttloc_ptr = malloc (sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	p_tt_config->ttloc_len = 0;
+
+	/* Retention time and decay algorithm from 13 Feb 13 version of */
+	/* http://www.aprs.org/aprstt/aprstt-coding24.txt */
+	/* Reduced by transmit count by one.  An 8 minute delay in between transmissions seems awful long. */
+
+	p_tt_config->retain_time = 80 * 60;
+	p_tt_config->num_xmits = 6;
+	assert (p_tt_config->num_xmits <= TT_MAX_XMITS);
+	p_tt_config->xmit_delay[0] = 3;		/* Before initial transmission. */
+	p_tt_config->xmit_delay[1] = 16;
+	p_tt_config->xmit_delay[2] = 32;
+	p_tt_config->xmit_delay[3] = 64;
+	p_tt_config->xmit_delay[4] = 2 * 60;
+	p_tt_config->xmit_delay[5] = 4 * 60;
+	p_tt_config->xmit_delay[6] = 8 * 60;		// not currently used.
+
+	strlcpy (p_tt_config->status[0], "", 		sizeof(p_tt_config->status[0]));
+	strlcpy (p_tt_config->status[1], "/off duty", 	sizeof(p_tt_config->status[1]));
+	strlcpy (p_tt_config->status[2], "/enroute", 	sizeof(p_tt_config->status[2]));
+	strlcpy (p_tt_config->status[3], "/in service", sizeof(p_tt_config->status[3]));
+	strlcpy (p_tt_config->status[4], "/returning", 	sizeof(p_tt_config->status[4]));
+	strlcpy (p_tt_config->status[5], "/committed", 	sizeof(p_tt_config->status[5]));
+	strlcpy (p_tt_config->status[6], "/special", 	sizeof(p_tt_config->status[6]));
+	strlcpy (p_tt_config->status[7], "/priority", 	sizeof(p_tt_config->status[7]));
+	strlcpy (p_tt_config->status[8], "/emergency", 	sizeof(p_tt_config->status[8]));
+	strlcpy (p_tt_config->status[9], "/custom 1", 	sizeof(p_tt_config->status[9]));
+
+	for (m = 0; m < TT_ERROR_MAXP1; m++) {
+	  strlcpy (p_tt_config->response[m].method, "MORSE",	sizeof(p_tt_config->response[m].method));
+	  strlcpy (p_tt_config->response[m].mtext, "?",		sizeof(p_tt_config->response[m].mtext));
+	}
+	strlcpy (p_tt_config->response[TT_ERROR_OK].mtext, "R", sizeof(p_tt_config->response[TT_ERROR_OK].mtext));
+
+
+	memset (p_misc_config, 0, sizeof(struct misc_config_s));
+	p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
+	p_misc_config->kiss_port = DEFAULT_KISS_PORT;
+	p_misc_config->enable_kiss_pt = 0;				/* -p option */
+
+	/* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */
+
+	p_misc_config->sb_configured = 0;	/* TRUE if SmartBeaconing is configured. */
+	p_misc_config->sb_fast_speed = 60;	/* MPH */
+	p_misc_config->sb_fast_rate = 180;	/* seconds */
+	p_misc_config->sb_slow_speed = 5;	/* MPH */
+	p_misc_config->sb_slow_rate = 1800;	/* seconds */
+	p_misc_config->sb_turn_time = 15;	/* seconds */
+	p_misc_config->sb_turn_angle = 30;	/* degrees */
+	p_misc_config->sb_turn_slope = 255;	/* degrees * MPH */
+
+	memset (p_igate_config, 0, sizeof(struct igate_config_s));
+	p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
+	p_igate_config->tx_chan = -1;			/* IS->RF not enabled */
+	p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_DEFAULT;
+	p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_DEFAULT;
+
+
+	/* People find this confusing. */
+	/* Ideally we'd like to figure out if com0com is installed */
+	/* and automatically enable this.  */
+	
+	//strlcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM, sizeof(p_misc_config->nullmodem));
+	strlcpy (p_misc_config->nullmodem, "", sizeof(p_misc_config->nullmodem));
+	strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port));
+	strlcpy (p_misc_config->nmea_port, "", sizeof(p_misc_config->nmea_port));
+	strlcpy (p_misc_config->logdir, "", sizeof(p_misc_config->logdir));
+
+
+/* 
+ * Try to extract options from a file.
+ * 
+ * Windows:  File must be in current working directory.
+ *
+ * Linux: Search current directory then home directory.
+ *
+ * Future possibility - Could also search home directory
+ * for Windows by combinting two variables:
+ *	HOMEDRIVE=C:
+ *	HOMEPATH=\Users\John
+ *
+ * It's not clear if this always points to same location:
+ *	USERPROFILE=C:\Users\John
+ */
+
+
+	channel = 0;
+	adevice = 0;
+
+// TODO: Would be better to have a search list and loop thru it.
+
+        strlcpy(filepath, fname, sizeof(filepath));
+
+        fp = fopen (filepath, "r");
+	
+#ifndef __WIN32__
+	if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) {
+	/* Failed to open the default location.  Try home dir. */
+	  char *p;
+
+
+          strlcpy (filepath, "", sizeof(filepath));
+
+	  p = getenv("HOME");
+	  if (p != NULL) {
+	    strlcpy (filepath, p, sizeof(filepath));
+	    strlcat (filepath, "/direwolf.conf", sizeof(filepath));
+	    fp = fopen (filepath, "r");
+	  } 
+	}
+#endif
+	if (fp == NULL)	{
+	  // TODO: not exactly right for all situations.
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Could not open config file %s\n", filepath);
+	  dw_printf ("Try using -c command line option for alternate location.\n");
+	  return;
+	}
+	
+	dw_printf ("\nReading config file %s\n", filepath);
+
+	line = 0;
+	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
+	  char *t;
+
+	  line++;
+
+	  t = split(stuff,0);
+
+	  if (t == NULL) {
+	    continue;
+	  }
+
+	  if (*t == '#' || *t == '*') {
+	    continue;
+	  }
+
+
+
+/*
+ * ==================== Audio device parameters ==================== 
+ */
+
+/*
+ * ADEVICE[n] 		- Name of input sound device, and optionally output, if different.   
+ *
+ *			ADEVICE    plughw:1,0			-- same for in and out.
+ *			ADEVICE	   plughw:2,0  plughw:3,0	-- different in/out for a channel or channel pair.
+ *	
+ */
+
+	  /* Note that ALSA name can contain comma such as hw:1,0 */
+
+	  if (strncasecmp(t, "ADEVICE", 7) == 0) {
+	    adevice = 0;
+	    if (isdigit(t[7])) {
+	      adevice = t[7] - '0';
+	    }
+
+	    if (adevice < 0 || adevice >= MAX_ADEVS) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Device number %d out of range for ADEVICE command on line %d.\n", adevice, line);
+	      adevice = 0;
+	      continue;
+	    }
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line);
+	      continue;
+	    }
+
+	    p_audio_config->adev[adevice].defined = 1;
+	
+	    /* First channel of device is valid. */
+	    p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
+
+	    strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in));
+	    strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
+	    }
+	  }
+
+
+/*
+ * PAIDEVICE[n]  input-device
+ * PAODEVICE[n]  output-device
+ *
+ *			This was submitted by KK5VD for the Mac OS X version.  (__APPLE__)
+ *
+ *			It looks like device names can contain spaces making it a little
+ *			more difficult to put two names on the same line unless we come up with
+ *			some other delimiter between them or a quoting scheme to handle
+ *			embedded spaces in a name.
+ *
+ *			It concerns me that we could have one defined without the other
+ *			if we don't put in more error checking later.
+ *
+ *	version 1.3 dev snapshot C:
+ *
+ *		We now have a general quoting scheme so the original ADEVICE can handle this.
+ *		These options will probably be removed before general 1.3 release.
+ */
+
+	  else if (strcasecmp(t, "PAIDEVICE") == 0) {
+		  adevice = 0;
+		  if (isdigit(t[9])) {
+			  adevice = t[9] - '0';
+		  }
+
+		  if (adevice < 0 || adevice >= MAX_ADEVS) {
+			  text_color_set(DW_COLOR_ERROR);
+			  dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line);
+			  adevice = 0;
+			  continue;
+		  }
+
+		  t = split(NULL,1);
+		  if (t == NULL) {
+			  text_color_set(DW_COLOR_ERROR);
+			  dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line);
+			  continue;
+		  }
+
+		  p_audio_config->adev[adevice].defined = 1;
+
+		  /* First channel of device is valid. */
+		  p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
+
+		  strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in));
+	  }
+	  else if (strcasecmp(t, "PAODEVICE") == 0) {
+		  adevice = 0;
+		  if (isdigit(t[9])) {
+			  adevice = t[9] - '0';
+		  }
+
+		  if (adevice < 0 || adevice >= MAX_ADEVS) {
+			  text_color_set(DW_COLOR_ERROR);
+			  dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line);
+			  adevice = 0;
+			  continue;
+		  }
+
+		  t = split(NULL,1);
+		  if (t == NULL) {
+			  text_color_set(DW_COLOR_ERROR);
+			  dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line);
+			  continue;
+		  }
+
+		  p_audio_config->adev[adevice].defined = 1;
+
+		  /* First channel of device is valid. */
+		  p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
+
+		  strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));		  
+	  }
+
+
+/*
+ * ARATE 		- Audio samples per second, 11025, 22050, 44100, etc.
+ */
+
+	  else if (strcasecmp(t, "ARATE") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing audio sample rate for ARATE command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= MIN_SAMPLES_PER_SEC && n <= MAX_SAMPLES_PER_SEC) {
+	      p_audio_config->adev[adevice].samples_per_sec = n;
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Use a more reasonable audio sample rate in range of %d - %d.\n", 
+							line, MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC);
+   	    }
+	  }
+
+/*
+ * ACHANNELS 		- Number of audio channels for current device: 1 or 2
+ */
+
+	  else if (strcasecmp(t, "ACHANNELS") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing number of audio channels for ACHANNELS command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n ==1 || n == 2) {
+	      p_audio_config->adev[adevice].num_channels = n;
+
+	      /* Set valid channels depending on mono or stereo. */
+
+	      p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
+	      if (n == 2) {
+	        p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].valid = 1;
+	      }
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Number of audio channels must be 1 or 2.\n", line);
+   	    }
+	  }
+
+/*
+ * ==================== Radio channel parameters ==================== 
+ */
+
+/*
+ * CHANNEL 		- Set channel for following commands.
+ */
+
+	  else if (strcasecmp(t, "CHANNEL") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing channel number for CHANNEL command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n < MAX_CHANS) {
+
+	      channel = n;
+
+	      if ( ! p_audio_config->achan[n].valid) {
+
+	        if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) {
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not defined.\n", 
+								line, n, ACHAN2ADEV(n));
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not in stereo.\n", 
+								line, n, ACHAN2ADEV(n));
+	        }
+	      }
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1);
+   	    }
+	  }
+
+/*
+ * MYCALL station
+ */
+	  else if (strcasecmp(t, "mycall") == 0) {
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing value for MYCALL command on line %d.\n", line);
+	      continue;
+	    }
+	    else {
+
+	      // Definitely set for current channel.
+	      // Set for other channels which have not been set yet.
+
+	      int c;
+
+	      for (c = 0; c < MAX_CHANS; c++) {
+
+	        if (c == channel || 
+			strlen(p_audio_config->achan[c].mycall) == 0 || 
+			strcasecmp(p_audio_config->achan[c].mycall, "NOCALL") == 0 ||
+			strcasecmp(p_audio_config->achan[c].mycall, "N0CALL") == 0) {
+
+	          char *p;
+
+	          strlcpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall));
+
+	          for (p = p_audio_config->achan[c].mycall; *p != '\0'; p++) {
+	            if (islower(*p)) {
+		      *p = toupper(*p);	/* silently force upper case. */
+	            }
+	          }
+	          // TODO: additional checks if valid.
+		  //  Should have a function to check for valid callsign[-ssid]
+	        }
+	      }
+	    }
+	  }
+
+
+/*
+ * MODEM	- Set modem properties for current channel.
+ *
+ *
+ * Old style:
+ * 	MODEM  baud [ mark  space  [A][B][C][+]  [  num-decoders spacing ] ] 
+ *
+ * New style, version 1.2:
+ *	MODEM  speed [ option ] ...
+ *
+ * Options:
+ *	mark:space	- AFSK tones.  Defaults based on speed.
+ *	num at offset	- Multiple decoders on different frequencies.
+ *	
+ */
+
+	  else if (strcasecmp(t, "MODEM") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= MIN_BAUD && n <= MAX_BAUD) {
+	      p_audio_config->achan[channel].baud = n;
+	      if (n != 300 && n != 1200 && n != 9600) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: Warning: Non-standard baud rate.  Are you sure?\n", line);
+    	      }
+	    }
+	    else {
+	      p_audio_config->achan[channel].baud = DEFAULT_BAUD;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", 
+				 line, p_audio_config->achan[channel].baud);
+   	    }
+
+
+	    /* Set defaults based on speed. */
+	    /* Should be same as -B command line option in direwolf.c. */
+
+	    if (p_audio_config->achan[channel].baud < 600) {
+              p_audio_config->achan[channel].modem_type = MODEM_AFSK;
+              p_audio_config->achan[channel].mark_freq = 1600;
+              p_audio_config->achan[channel].space_freq = 1800;
+	    }
+	    else if (p_audio_config->achan[channel].baud > 2400) {
+              p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
+              p_audio_config->achan[channel].mark_freq = 0;
+              p_audio_config->achan[channel].space_freq = 0;
+	    }
+	    else {
+              p_audio_config->achan[channel].modem_type = MODEM_AFSK;
+              p_audio_config->achan[channel].mark_freq = 1200;
+              p_audio_config->achan[channel].space_freq = 2200;
+	    }
+
+	    /* Get mark frequency. */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      /* all done. */
+	      continue;
+	    }
+
+	    if (alldigits(t)) {
+
+/* old style */
+
+	      n = atoi(t);
+	      /* Originally the upper limit was 3000. */
+	      /* Version 1.0 increased to 5000 because someone */
+	      /* wanted to use 2400/4800 Hz AFSK. */
+	      /* Of course the MIC and SPKR connections won't */
+	      /* have enough bandwidth so radios must be modified. */
+              if (n >= 300 && n <= 5000) {
+	        p_audio_config->achan[channel].mark_freq = n;
+	      }
+	      else {
+	        p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", 
+				 line, p_audio_config->achan[channel].mark_freq);
+   	      }
+	    
+	      /* Get space frequency */
+
+	      t = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: Missing tone frequency for space.\n", line);
+	        continue;
+	      }
+	      n = atoi(t);
+              if (n >= 300 && n <= 5000) {
+	        p_audio_config->achan[channel].space_freq = n;
+	      }
+	      else {
+	        p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", 
+					 line, p_audio_config->achan[channel].space_freq);
+   	      }
+
+	      /* Gently guide users toward new format. */
+
+	      if (p_audio_config->achan[channel].baud == 1200 &&
+                  p_audio_config->achan[channel].mark_freq == 1200 &&
+                  p_audio_config->achan[channel].space_freq == 2200) {
+
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 1200 baud default 1200:2200.\n", line);
+	      }
+	      if (p_audio_config->achan[channel].baud == 300 &&
+                  p_audio_config->achan[channel].mark_freq == 1600 &&
+                  p_audio_config->achan[channel].space_freq == 1800) {
+
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 300 baud default 1600:1800.\n", line);
+	      }
+
+	      /* New feature in 0.9 - Optional filter profile(s). */
+
+	      t = split(NULL,0);
+	      if (t != NULL) {
+
+	        /* Look for some combination of letter(s) and + */
+
+	        if (isalpha(t[0]) || t[0] == '+') {
+		  char *pc;
+
+		  /* Here we only catch something other than letters and + mixed in. */
+		  /* Later, we check for valid letters and no more than one letter if + specified. */
+
+	          for (pc = t; *pc != '\0'; pc++) {
+		    if ( ! isalpha(*pc) && ! (*pc == '+')) {
+	              text_color_set(DW_COLOR_ERROR);
+                      dw_printf ("Line %d: Demodulator type can only contain letters and + character.\n", line);
+		    }
+		  }    
+		
+		  strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles));
+	          t = split(NULL,0);
+		  if (strlen(p_audio_config->achan[channel].profiles) > 1 && t != NULL) {
+	            text_color_set(DW_COLOR_ERROR);
+                    dw_printf ("Line %d: Can't combine multiple demodulator types and multiple frequencies.\n", line);
+		    continue;
+		  }
+	        }
+	      }
+
+	      /* New feature in 0.9 - optional number of decoders and frequency offset between. */
+
+	      if (t != NULL) {
+	        n = atoi(t);
+                if (n < 1 || n > MAX_SUBCHANS) {
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line);
+		  n = 3;
+	        }
+	        p_audio_config->achan[channel].num_freq = n;
+
+	        t = split(NULL,0);
+	        if (t != NULL) {
+	          n = atoi(t);
+                  if (n < 5 || n > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) {
+	            text_color_set(DW_COLOR_ERROR);
+                    dw_printf ("Line %d: Unreasonable value for offset between modems.  Using 50 Hz.\n", line);
+		    n = 50;
+	          }
+		  p_audio_config->achan[channel].offset = n;
+
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: New style for multiple demodulators is %d@%d\n", line,
+			p_audio_config->achan[channel].num_freq, p_audio_config->achan[channel].offset);	  
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: Missing frequency offset between modems.  Using 50 Hz.\n", line);
+	          p_audio_config->achan[channel].offset = 50;
+	        }    
+	      }
+	    }
+	    else {
+
+/* New style in version 1.2. */
+
+	      while (t != NULL) {
+		char *s;
+
+		if ((s = strchr(t, ':')) != NULL) {		/* mark:space */
+
+	          p_audio_config->achan[channel].mark_freq = atoi(t);
+	          p_audio_config->achan[channel].space_freq = atoi(s+1);
+
+		  if (p_audio_config->achan[channel].mark_freq == 0 && p_audio_config->achan[channel].space_freq == 0) {
+		    p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
+	          }
+	          else {
+		    p_audio_config->achan[channel].modem_type = MODEM_AFSK;
+
+                    if (p_audio_config->achan[channel].mark_freq < 300 || p_audio_config->achan[channel].mark_freq > 5000) {
+	              p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
+	              text_color_set(DW_COLOR_ERROR);
+                      dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d instead.\n", 
+				 line, p_audio_config->achan[channel].mark_freq);
+		    }
+                    if (p_audio_config->achan[channel].space_freq < 300 || p_audio_config->achan[channel].space_freq > 5000) {
+	              p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
+	              text_color_set(DW_COLOR_ERROR);
+                      dw_printf ("Line %d: Unreasonable space tone frequency. Using %d instead.\n", 
+				 line, p_audio_config->achan[channel].space_freq);
+		    }
+	          }
+	        }
+
+		else if ((s = strchr(t, '@')) != NULL) {		/* num at offset */
+
+	          p_audio_config->achan[channel].num_freq = atoi(t);
+	          p_audio_config->achan[channel].offset = atoi(s+1);
+
+                  if (p_audio_config->achan[channel].num_freq < 1 || p_audio_config->achan[channel].num_freq > MAX_SUBCHANS) {
+	            text_color_set(DW_COLOR_ERROR);
+                    dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line);
+	            p_audio_config->achan[channel].num_freq = 3;
+		  }
+
+                  if (p_audio_config->achan[channel].offset < 5 || 
+			p_audio_config->achan[channel].offset > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) {
+	            text_color_set(DW_COLOR_ERROR);
+                    dw_printf ("Line %d: Offset between demodulators is unreasonable. Using 50 Hz.\n", line);
+	            p_audio_config->achan[channel].offset = 50;
+		  }
+		}
+
+	        else if (alllettersorpm(t)) {		/* profile of letter(s) + - */
+
+		  // Will be validated later.
+		  strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles));
+	        }
+
+		else if (*t == '/') {		/* /div */
+		  int n = atoi(t+1);
+
+                  if (n >= 1 && n <= 8) {
+	            p_audio_config->achan[channel].decimate = n;
+		  }
+	    	  else {
+	            text_color_set(DW_COLOR_ERROR);
+                    dw_printf ("Line %d: Ignoring unreasonable sample rate division factor of %d.\n", line, n);
+		  }
+		}
+
+		else {
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t);
+	        } 
+
+	        t = split(NULL,0);
+	      }
+
+	      /* A later place catches disallowed combination of + and @. */
+	      /* A later place sets /n for 300 baud if not specified by user. */
+
+	      //dw_printf ("debug: div = %d\n", p_audio_config->achan[channel].decimate);
+
+	    }
+	  }
+
+
+
+/*
+ * DTMF  		- Enable DTMF decoder.
+ *
+ * Future possibilities: 
+ *	Option to determine if it goes to APRStt gateway and/or application.
+ *	Disable normal demodulator to reduce CPU requirements.
+ */
+
+
+	  else if (strcasecmp(t, "DTMF") == 0) {
+
+	    p_audio_config->achan[channel].dtmf_decode = DTMF_DECODE_ON;
+
+	  }
+
+
+/*
+ * FIX_BITS  n  [ APRS | AX25 | NONE ] [ PASSALL ]
+ *
+ *	- Attempt to fix frames with bad FCS. 
+ */
+
+	  else if (strcasecmp(t, "FIX_BITS") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing value for FIX_BITS command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= RETRY_NONE && n < RETRY_MAX) {		// MAX is actually last valid +1
+	      p_audio_config->achan[channel].fix_bits = (retry_t)n;
+	    }
+	    else {
+	      p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid value %d for FIX_BITS. Using default of %d.\n",
+			line, n, p_audio_config->achan[channel].fix_bits);
+	    }
+
+	    if (p_audio_config->achan[channel].fix_bits > RETRY_INVERT_SINGLE) {
+	      text_color_set(DW_COLOR_INFO);
+              dw_printf ("Line %d: Using a FIX_BITS value greater than %d is not recommended for normal operation.\n",
+			line, RETRY_INVERT_SINGLE);
+	    }
+
+	    if (p_audio_config->achan[channel].fix_bits >= RETRY_INVERT_TWO_SEP) {
+	      text_color_set(DW_COLOR_INFO);
+              dw_printf ("Line %d: Using a FIX_BITS value of %d will waste a lot of CPU power and produce wrong results.\n",
+			line, RETRY_INVERT_TWO_SEP);
+	    }
+
+	    t = split(NULL,0);
+	    while (t != NULL) {
+
+	      // If more than one sanity test, we silently take the last one.
+
+	      if (strcasecmp(t, "APRS") == 0) {
+	        p_audio_config->achan[channel].sanity_test = SANITY_APRS;
+	      }
+	      else if (strcasecmp(t, "AX25") == 0 || strcasecmp(t, "AX.25") == 0) {
+	        p_audio_config->achan[channel].sanity_test = SANITY_AX25;
+	      }
+	      else if (strcasecmp(t, "NONE") == 0) {
+	        p_audio_config->achan[channel].sanity_test = SANITY_NONE;
+	      }
+	      else if (strcasecmp(t, "PASSALL") == 0) {
+	        p_audio_config->achan[channel].passall = 1;
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: There is an old saying, \"Be careful what you ask for because you might get it.\"\n", line);
+                dw_printf ("The PASSALL option means allow all frames even when they are invalid.\n");
+                dw_printf ("You are asking to receive random trash and you WILL get your wish.\n");
+                dw_printf ("Don't complain when you see all sorts of random garbage.  That's what you asked for.\n");
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: Invalid option '%s' for FIX_BITS.\n", line, t);
+	      }
+	      t = split(NULL,0);
+	    }
+	  }
+
+
+/*
+ * PTT 		- Push To Talk signal line.
+ * DCD		- Data Carrier Detect indicator.
+ *
+ * xxx  serial-port [-]rts-or-dtr [ [-]rts-or-dtr ]
+ * xxx  GPIO  [-]gpio-num
+ * xxx  LPT  [-]bit-num
+ * PTT  RIG  model  port
+ * PTT  RIG  AUTO  port
+ *
+ * 		When model is 2, port would host:port like 127.0.0.1:4532
+ *		Otherwise, port would be a serial port like /dev/ttyS0
+ *
+ *
+ * Applies to most recent CHANNEL command.
+ */
+
+	  else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0) {
+	    int ot;
+	    char otname[8];
+
+	    if (strcasecmp(t, "PTT") == 0) {
+	      ot = OCTYPE_PTT;
+	      strlcpy (otname, "PTT", sizeof(otname));
+	    }
+	    else if (strcasecmp(t, "DCD") == 0) {
+	      ot = OCTYPE_DCD;
+	      strlcpy (otname, "DCD", sizeof(otname));
+	    }
+	    else {
+	      ot = OCTYPE_FUTURE;
+	      strlcpy (otname, "FUTURE", sizeof(otname));
+	    }
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: Missing serial port name for %s command.\n", 
+			line, otname);
+	      continue;
+	    }
+
+	    if (strcasecmp(t, "GPIO") == 0) {
+
+/* GPIO case, Linux only. */
+
+#if __WIN32__
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, otname);
+#else		
+	      t = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, otname);
+	        continue;
+	      }
+
+	      if (*t == '-') {
+	        p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t+1);
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
+	      }
+	      else {
+	        p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t);
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
+	      }
+	      p_audio_config->achan[channel].octrl[ot].ptt_method = 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 = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing LPT bit number for %s.\n", line, otname);
+	        continue;
+	      }
+
+	      if (*t == '-') {
+	        p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t+1);
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
+	      }
+	      else {
+	        p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t);
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
+	      }
+	      p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_LPT;
+#else
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: %s with LPT is only available on x86 Linux.\n", line, otname);
+#endif		
+	    }
+	    else if (strcasecmp(t, "RIG") == 0) {
+#ifdef USE_HAMLIB
+
+	      t = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing model number for hamlib.\n", line);
+	        continue;
+	      }
+	      if (strcasecmp(t, "AUTO") == 0) {
+	        p_audio_config->achan[channel].octrl[ot].ptt_model = -1;
+	      }
+	      else {
+	        int n = atoi(t);
+		if (n < 1 || n > 9999) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Config file line %d: Unreasonable model number %d for hamlib.\n", line, n);
+	          continue;
+	        }
+	        p_audio_config->achan[channel].octrl[ot].ptt_model = n;
+	      }
+
+	      t = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing port for hamlib.\n", line);
+	        continue;
+	      }
+	      strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
+
+	      t = split(NULL,0);
+	      if (t != NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: %s was not expected after model & port for hamlib.\n", line, t);
+	      }
+
+	      p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_HAMLIB;
+
+#else
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: %s with RIG is only available when hamlib support is enabled.\n", line, otname);
+#endif
+	    }
+	    else  {
+
+/* serial port case. */
+
+	      strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
+
+	      t = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n", 
+			line, otname);
+	        continue;
+	      }
+
+	      if (strcasecmp(t, "rts") == 0) {
+	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS;
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
+	      }
+	      else if (strcasecmp(t, "dtr") == 0) {
+	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR;
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
+	      }
+	      else if (strcasecmp(t, "-rts") == 0) {
+	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS;
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
+	      }
+	      else if (strcasecmp(t, "-dtr") == 0) {
+	        p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR;
+		p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file line %d: Expected RTS or DTR after %s device name.\n", 
+			line, otname);
+	        continue;
+	      }
+
+
+	      p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_SERIAL;
+
+
+	      /* In version 1.2, we allow a second one for same serial port. */
+	      /* Some interfaces want the two control lines driven with opposite polarity. */
+	      /* e.g.   PTT COM1 RTS -DTR  */
+
+	      t = split(NULL,0);
+	      if (t != NULL) {
+
+	        if (strcasecmp(t, "rts") == 0) {
+	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS;
+		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
+	        }
+	        else if (strcasecmp(t, "dtr") == 0) {
+	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR;
+		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
+	        }
+	        else if (strcasecmp(t, "-rts") == 0) {
+	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS;
+		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1;
+	        }
+	        else if (strcasecmp(t, "-dtr") == 0) {
+	          p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR;
+		  p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1;
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Config file line %d: Expected RTS or DTR after first RTS or DTR.\n", 
+			line);
+	          continue;
+	        }
+
+		/* Would not make sense to specify the same one twice. */
+
+		if (p_audio_config->achan[channel].octrl[ot].ptt_line == p_audio_config->achan[channel].octrl[ot].ptt_line2) {
+	          dw_printf ("Config file line %d: Doesn't make sense to specify the some control line twice.\n", 
+			line);
+	        }
+
+	      }  /* end of second serial port control line. */
+	    }  /* end of serial port case. */
+
+	  }  /* end of PTT */
+
+/*
+ * INPUTS
+ *
+ * TXINH - TX holdoff input
+ *
+ * xxx GPIO [-]gpio-num (only type supported yet)
+ */
+
+	  else if (strcasecmp(t, "TXINH") == 0) {
+	    int it;
+	    char itname[8];
+
+	    it = ICTYPE_TXINH;
+	    strlcpy (itname, "TXINH", sizeof(itname));
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: Missing input type name for %s command.\n", line, itname);
+	      continue;
+	    }
+
+	    if (strcasecmp(t, "GPIO") == 0) {
+
+#if __WIN32__
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, itname);
+#else
+	      t = split(NULL,0);
+	      if (t == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+		dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, itname);
+		continue;
+	      }
+
+	      if (*t == '-') {
+	        p_audio_config->achan[channel].ictrl[it].gpio = atoi(t+1);
+		p_audio_config->achan[channel].ictrl[it].invert = 1;
+	      }
+	      else {
+	        p_audio_config->achan[channel].ictrl[it].gpio = atoi(t);
+		p_audio_config->achan[channel].ictrl[it].invert = 0;
+	      }
+	      p_audio_config->achan[channel].ictrl[it].method = PTT_METHOD_GPIO;
+#endif
+	    }
+	  }
+
+
+/*
+ * DWAIT 		- Extra delay for receiver squelch.
+ */
+
+	  else if (strcasecmp(t, "DWAIT") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing delay time for DWAIT command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n <= 255) {
+	      p_audio_config->achan[channel].dwait = n;
+	    }
+	    else {
+	      p_audio_config->achan[channel].dwait = DEFAULT_DWAIT;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid delay time for DWAIT. Using %d.\n", 
+			line, p_audio_config->achan[channel].dwait);
+   	    }
+	  }
+
+/*
+ * SLOTTIME 		- For non-digipeat transmit delay timing.
+ */
+
+	  else if (strcasecmp(t, "SLOTTIME") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing delay time for SLOTTIME command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n <= 255) {
+	      p_audio_config->achan[channel].slottime = n;
+	    }
+	    else {
+	      p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n", 
+			line, p_audio_config->achan[channel].slottime);
+   	    }
+	  }
+
+/*
+ * PERSIST 		- For non-digipeat transmit delay timing.
+ */
+
+	  else if (strcasecmp(t, "PERSIST") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing probability for PERSIST command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n <= 255) {
+	      p_audio_config->achan[channel].persist = n;
+	    }
+	    else {
+	      p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n", 
+			line, p_audio_config->achan[channel].persist);
+   	    }
+	  }
+
+/*
+ * TXDELAY 		- For transmit delay timing.
+ */
+
+	  else if (strcasecmp(t, "TXDELAY") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing time for TXDELAY command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n <= 255) {
+	      p_audio_config->achan[channel].txdelay = n;
+	    }
+	    else {
+	      p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid time for transmit delay. Using %d.\n", 
+			line, p_audio_config->achan[channel].txdelay);
+   	    }
+	  }
+
+/*
+ * TXTAIL 		- For transmit timing.
+ */
+
+	  else if (strcasecmp(t, "TXTAIL") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing time for TXTAIL command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n <= 255) {
+	      p_audio_config->achan[channel].txtail = n;
+	    }
+	    else {
+	      p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", 
+			line, p_audio_config->achan[channel].txtail);
+   	    }
+	  }
+
+/*
+ * SPEECH  script 
+ *
+ * Specify script for text-to-speech function.		
+ */
+
+	  else if (strcasecmp(t, "SPEECH") == 0) {
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing script for Text-to-Speech function.\n", line);
+	      continue;
+	    }
+	    
+	    /* See if we can run it. */
+
+	    if (xmit_speak_it(t, -1, " ") == 0) {
+	      if (strlcpy (p_audio_config->tts_script, t, sizeof(p_audio_config->tts_script)) >= sizeof(p_audio_config->tts_script)) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: Script for text-to-speech function is too long.\n", line);
+	      }
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Error trying to run Text-to-Speech function.\n", line);
+	      continue;
+	   }
+	  }
+
+/*
+ * ==================== Digipeater parameters ==================== 
+ */
+
+/*
+ * DIGIPEAT  from-chan  to-chan  alias-pattern  wide-pattern  [ OFF|DROP|MARK|TRACE ] 
+ */
+
+	  else if (strcasecmp(t, "digipeat") == 0) {
+	    int from_chan, to_chan;
+	    int e;
+	    char message[100];
+	    	    
+
+	    t = split(NULL,0);
+	    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 >= MAX_CHANS) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", 
+							MAX_CHANS-1, line);
+	      continue;
+	    }
+	    if ( ! p_audio_config->achan[from_chan].valid) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", 
+							line, from_chan);
+	      continue;
+	    }
+
+	    t = split(NULL,0);
+	    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 >= MAX_CHANS) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", 
+							MAX_CHANS-1, line);
+	      continue;
+	    }
+	    if ( ! p_audio_config->achan[to_chan].valid) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", 
+							line, to_chan);
+	      continue;
+	    }
+	
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing alias pattern on line %d.\n", line);
+	      continue;
+	    }
+	    e = regcomp (&(p_digi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
+	    if (e != 0) {
+	      regerror (e, &(p_digi_config->alias[from_chan][to_chan]), message, sizeof(message));
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n", 
+							line, message);
+	      continue;
+	    }
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing wide pattern on line %d.\n", line);
+	      continue;
+	    }
+	    e = regcomp (&(p_digi_config->wide[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
+	    if (e != 0) {
+	      regerror (e, &(p_digi_config->wide[from_chan][to_chan]), message, sizeof(message));
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Invalid wide matching pattern on line %d:\n%s\n", 
+							line, message);
+	      continue;
+	    }
+
+	    p_digi_config->enabled[from_chan][to_chan] = 1;
+	    p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF;
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      if (strcasecmp(t, "OFF") == 0) {
+	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF;
+	        t = split(NULL,0);
+	      }
+	      else if (strcasecmp(t, "DROP") == 0) {
+	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP;
+	        t = split(NULL,0);
+	      }
+	      else if (strcasecmp(t, "MARK") == 0) {
+	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK;
+	        t = split(NULL,0);
+	      }
+	      else if (strcasecmp(t, "TRACE") == 0) {
+	        p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE;
+	        t = split(NULL,0);
+	      }
+	    }
+
+	    if (t != NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t);     
+	    }
+	  }
+
+/*
+ * DEDUPE 		- Time to suppress digipeating of duplicate packets.
+ */
+
+	  else if (strcasecmp(t, "DEDUPE") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing time for DEDUPE command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if (n >= 0 && n < 600) {
+	      p_digi_config->dedupe_time = n;
+	    }
+	    else {
+	      p_digi_config->dedupe_time = DEFAULT_DEDUPE;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Unreasonable value for dedupe time. Using %d.\n", 
+			line, p_digi_config->dedupe_time);
+   	    }
+	  }
+
+/*
+ * REGEN 		- Signal regeneration.
+ */
+
+	  else if (strcasecmp(t, "regen") == 0) {
+	    int from_chan, to_chan;
+	    	    
+
+	    t = split(NULL,0);
+	    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 >= MAX_CHANS) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", 
+							MAX_CHANS-1, line);
+	      continue;
+	    }
+	    if ( ! p_audio_config->achan[from_chan].valid) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", 
+							line, from_chan);
+	      continue;
+	    }
+
+	    t = split(NULL,0);
+	    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 >= MAX_CHANS) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", 
+							MAX_CHANS-1, line);
+	      continue;
+	    }
+	    if ( ! p_audio_config->achan[to_chan].valid) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", 
+							line, to_chan);
+	      continue;
+	    }
+	
+
+	    p_digi_config->regen[from_chan][to_chan] = 1;
+
+	  }
+
+
+/*
+ * ==================== Packet Filtering for digipeater or IGate ==================== 
+ */
+
+/*
+ * FILTER  from-chan  to-chan  filter_specification_expression
+ * FILTER  from-chan  IG       filter_specification_expression
+ * FILTER  IG         to-chan  filter_specification_expression
+ */
+
+	  else if (strcasecmp(t, "FILTER") == 0) {
+	    int from_chan, to_chan;
+	    	    
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
+	      continue;
+	    }
+	    if (*t == 'i' || *t == 'I') {
+	      from_chan = MAX_CHANS;
+	    }
+	    else {
+	      from_chan = isdigit(*t) ? atoi(t) : -999;
+	      if (from_chan < 0 || from_chan >= MAX_CHANS) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d or \"IG\" on line %d.\n", 
+							MAX_CHANS-1, line);
+	        continue;
+	      }
+
+	      if ( ! p_audio_config->achan[from_chan].valid) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", 
+							line, from_chan);
+	        continue;
+	      }
+	    }
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
+	      continue;
+	    }
+	    if (*t == 'i' || *t == 'I') {
+	      to_chan = MAX_CHANS;
+	    }
+	    else {
+	      to_chan = isdigit(*t) ? atoi(t) : -999;
+	      if (to_chan < 0 || to_chan >= MAX_CHANS) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d or \"IG\" on line %d.\n", 
+							MAX_CHANS-1, line);
+	        continue;
+	      }
+	      if ( ! p_audio_config->achan[to_chan].valid) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", 
+							line, to_chan);
+	        continue;
+	      }
+	    }
+
+	    t = split(NULL,1);		/* Take rest of line including spaces. */
+
+	    if (t == NULL) {
+	      t = " ";				/* Empty means permit nothing. */
+	    }
+
+	    p_digi_config->filter_str[from_chan][to_chan] = strdup(t);
+
+//TODO1.2:  Do a test run to see errors now instead of waiting.
+
+	  }
+
+
+/*
+ * ==================== APRStt gateway ==================== 
+ */
+
+/*
+ * TTCORRAL 		- How to handle unknown positions
+ *
+ * TTCORRAL  latitude  longitude  offset-or-ambiguity 
+ */
+
+	  else if (strcasecmp(t, "TTCORRAL") == 0) {
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing latitude for TTCORRAL command.\n", line);
+	      continue;
+	    }
+	    p_tt_config->corral_lat = parse_ll(t,LAT,line);
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line);
+	      continue;
+	    }
+	    p_tt_config->corral_lon = parse_ll(t,LON,line);
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line);
+	      continue;
+	    }
+	    p_tt_config->corral_offset = parse_ll(t,LAT,line);
+	    if (p_tt_config->corral_offset == 1 ||
+		p_tt_config->corral_offset == 2 ||
+	 	p_tt_config->corral_offset == 3) {
+	      p_tt_config->corral_ambiguity = p_tt_config->corral_offset;
+	      p_tt_config->corral_offset = 0;
+	    }
+
+	    //dw_printf ("DEBUG: corral %f %f %f %d\n", p_tt_config->corral_lat,
+	    //	p_tt_config->corral_lon, p_tt_config->corral_offset, p_tt_config->corral_ambiguity);
+	  }
+
+/*
+ * TTPOINT 		- Define a point represented by touch tone sequence.
+ *
+ * TTPOINT   pattern  latitude  longitude   
+ */
+	  else if (strcasecmp(t, "TTPOINT") == 0) {
+
+	    struct ttloc_s *tl;
+	    int j;
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    // Should make this a function/macro instead of repeating code.
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_POINT;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    tl->point.lat = 0;
+	    tl->point.lon = 0;
+
+	    /* Pattern: B and digits */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line);
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTPOINT pattern must begin with upper case 'B'.\n", line);
+	    }
+	    for (j=1; j<strlen(t); j++) {
+	      if ( ! isdigit(t[j])) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTPOINT pattern must be B and digits only.\n", line);
+	      }
+	    }
+
+	    /* Latitude */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line);
+	      continue;
+	    }
+	    tl->point.lat = parse_ll(t,LAT,line);
+
+	    /* Longitude */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing longitude for TTPOINT command.\n", line);
+	      continue;
+	    }
+	    tl->point.lon = parse_ll(t,LON,line);
+
+	    /* temp debugging */
+
+	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
+	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
+	    //		p_tt_config->ttloc_ptr[j].pattern);
+	    //}
+	  }
+
+/*
+ * TTVECTOR 		- Touch tone location with bearing and distance.
+ *
+ * TTVECTOR   pattern  latitude  longitude  scale  unit  
+ */
+	  else if (strcasecmp(t, "TTVECTOR") == 0) {
+
+	    struct ttloc_s *tl;
+	    int j;
+	    double scale;
+	    double meters;
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_VECTOR;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    tl->vector.lat = 0;
+	    tl->vector.lon = 0;
+	    tl->vector.scale = 1;
+	   
+	    /* Pattern: B5bbbd... */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line);
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTVECTOR pattern must begin with upper case 'B'.\n", line);
+	    }
+	    if (strncmp(t+1, "5bbb", 4) != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTVECTOR pattern would normally contain \"5bbb\".\n", line);
+	    }
+	    for (j=1; j<strlen(t); j++) {
+	      if ( ! isdigit(t[j]) && t[j] != 'b' && t[j] != 'd') {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTVECTOR pattern must contain only B, digits, b, and d.\n", line);
+	      }
+	    }
+
+	    /* Latitude */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line);
+	      continue;
+	    }
+	    tl->vector.lat = parse_ll(t,LAT,line);
+
+	    /* Longitude */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing longitude for TTVECTOR command.\n", line);
+	      continue;
+	    }
+	    tl->vector.lon = parse_ll(t,LON,line);
+
+	    /* Longitude */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing scale for TTVECTOR command.\n", line);
+	      continue;
+	    }
+	    scale = atof(t);
+
+	    /* Unit. */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing unit for TTVECTOR command.\n", line);
+	      continue;
+	    }
+	    meters = 0;
+	    for (j=0; j<NUM_UNITS && meters == 0; j++) {
+	      if (strcasecmp(units[j].name, t) == 0) {
+	        meters = units[j].meters;
+	      }
+	    }
+	    if (meters == 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Unrecognized unit for TTVECTOR command.  Using miles.\n", line);
+	      meters = 1609.344;
+	    }
+	    tl->vector.scale = scale * meters;
+
+ 	    //dw_printf ("ttvector: %f meters\n", tl->vector.scale);
+
+	    /* temp debugging */
+
+	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
+	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
+	    //		p_tt_config->ttloc_ptr[j].pattern);
+	    //}
+	  }
+
+/*
+ * TTGRID 		- Define a grid for touch tone locations.
+ *
+ * TTGRID   pattern  min-latitude  min-longitude  max-latitude  max-longitude
+ */
+	  else if (strcasecmp(t, "TTGRID") == 0) {
+
+	    struct ttloc_s *tl;
+	    int j;
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_GRID;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    tl->grid.lat0 = 0;
+	    tl->grid.lon0 = 0;
+	    tl->grid.lat9 = 0;
+	    tl->grid.lon9 = 0;
+
+	    /* Pattern: B [digit] x... y... */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line);
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTGRID pattern must begin with upper case 'B'.\n", line);
+	    }
+	    for (j=1; j<strlen(t); j++) {
+	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTGRID pattern must be B, optional digit, xxx, yyy.\n", line);
+	      }
+	    }
+
+	    /* Minimum Latitude - all zeros in received data */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
+	      continue;
+	    }
+	    tl->grid.lat0 = parse_ll(t,LAT,line);
+
+	    /* Minimum Longitude - all zeros in received data */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
+	      continue;
+	    }
+	    tl->grid.lon0 = parse_ll(t,LON,line);
+
+	    /* Maximum Latitude - all nines in received data */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
+	      continue;
+	    }
+	    tl->grid.lat9 = parse_ll(t,LAT,line);
+
+	    /* Maximum Longitude - all nines in received data */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
+	      continue;
+	    }
+	    tl->grid.lon0 = parse_ll(t,LON,line);
+
+	    /* temp debugging */
+
+	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
+	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
+	    //	p_tt_config->ttloc_ptr[j].pattern);
+	    //}
+	  }
+
+/*
+ * TTUTM 		- Specify UTM zone for touch tone locations.
+ *
+ * TTUTM   pattern  zone [ scale [ x-offset y-offset ] ]
+ */
+	  else if (strcasecmp(t, "TTUTM") == 0) {
+
+	    struct ttloc_s *tl;
+	    int j;
+	    double dlat, dlon;
+	    long lerr;
+
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_UTM;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    tl->utm.lzone = 0;
+	    tl->utm.scale = 1;
+	    tl->utm.x_offset = 0;
+	    tl->utm.y_offset = 0;
+
+	    /* Pattern: B [digit] x... y... */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTUTM pattern must begin with upper case 'B'.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    for (j=1; j<strlen(t); j++) {
+	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTUTM pattern must be B, optional digit, xxx, yyy.\n", line);
+		// Bail out somehow.  continue would match inner for.
+	      }
+	    }
+
+	    /* Zone 1 - 60 and optional latitudinal letter. */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing zone for TTUTM command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    tl->utm.lzone = parse_utm_zone (t, &(tl->utm.latband), &(tl->utm.hemi));
+
+ 	    /* Optional scale. */
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      
+	      tl->utm.scale = atof(t);
+
+	      /* Optional x offset. */
+
+	      t = split(NULL,0);
+	      if (t != NULL) {
+
+	        tl->utm.x_offset = atof(t);
+
+	        /* Optional y offset. */
+
+	        t = split(NULL,0);
+	        if (t != NULL) {
+	     
+	          tl->utm.y_offset = atof(t);
+	        }
+	      }
+	    }
+
+	    /* Practice run to see if conversion might fail later with actual location. */
+
+	    lerr = Convert_UTM_To_Geodetic(tl->utm.lzone, tl->utm.hemi,               
+                        tl->utm.x_offset + 5 * tl->utm.scale,
+                        tl->utm.y_offset + 5 * tl->utm.scale,
+                        &dlat, &dlon);
+
+            if (lerr != 0) {
+	      char message [300];
+
+              utm_error_string (lerr, message);
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message);
+	      p_tt_config->ttloc_len--;
+	      continue;
+            }
+	  }
+
+/*
+ * TTUSNG, TTMGRS 		- Specify zone/square for touch tone locations.
+ *
+ * TTUSNG   pattern  zone_square 
+ * TTMGRS   pattern  zone_square 
+ */
+	  else if (strcasecmp(t, "TTUSNG") == 0 || strcasecmp(t, "TTMGRS") == 0) {
+
+	    struct ttloc_s *tl;
+	    int j;
+	    int num_x, num_y;
+	    double lat, lon;
+	    long lerr;
+	    char message[300];
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+
+// TODO1.2: in progress...
+	    if (strcasecmp(t, "TTMGRS") == 0) {
+	      tl->type = TTLOC_MGRS;
+	    }
+	    else {
+	      tl->type = TTLOC_USNG;
+	    }
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    strlcpy(tl->mgrs.zone, "", sizeof(tl->mgrs.zone));
+
+	    /* Pattern: B [digit] x... y... */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTUSNG/TTMGRS command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTUSNG/TTMGRS pattern must begin with upper case 'B'.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    num_x = 0;
+	    num_y = 0;
+	    for (j=1; j<strlen(t); j++) {
+	      if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTUSNG/TTMGRS pattern must be B, optional digit, xxx, yyy.\n", line);
+		// Bail out somehow.  continue would match inner for.
+	      }
+	      if (t[j] == 'x') num_x++;
+	      if (t[j] == 'y') num_y++;
+	    }
+	    if (num_x < 1 || num_x > 5 || num_x != num_y) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTUSNG/TTMGRS must have 1 to 5 x and same number y.\n", line);  
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* Zone 1 - 60 and optional latitudinal letter. */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing zone & square for TTUSNG/TTMGRS command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->mgrs.zone, t, sizeof(tl->mgrs.zone));
+
+	    /* Try converting it rather do our own error checking. */
+
+	    if (tl->type == TTLOC_MGRS) {
+	      lerr = Convert_MGRS_To_Geodetic (tl->mgrs.zone, &lat, &lon);
+	    }
+	    else {
+	      lerr = Convert_USNG_To_Geodetic (tl->mgrs.zone, &lat, &lon);
+	    }
+            if (lerr != 0) {
+
+              mgrs_error_string (lerr, message);
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid USNG/MGRS zone & square:  %s\n%s\n", line, tl->mgrs.zone, message);
+	      p_tt_config->ttloc_len--;
+	      continue;
+            }
+
+	    /* Should be the end. */
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Unexpected stuff at end ignored:  %s\n", line, t);
+	    }
+	  }
+
+/*
+ * TTMHEAD 		- Define pattern to be used for Maidenhead Locator.
+ *
+ * TTMHEAD   pattern   [ prefix ] 
+ *
+ *			Pattern would be  B[0-9A-D]xxxx...	
+ *			Optional prefix is 10, 6, or 4 digits.
+ *
+ *			The total number of digts in both must be 4, 6, 10, or 12.
+ */
+	  else if (strcasecmp(t, "TTMHEAD") == 0) {
+
+// TODO1.3:  TTMHEAD needs testing. 
+
+	    struct ttloc_s *tl;
+	    int j;
+	    int k;
+	    int count_x;
+	    int count_other;
+
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_MHEAD;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    strlcpy(tl->mhead.prefix, "", sizeof(tl->mhead.prefix));
+
+	    /* Pattern: B, optional additional button, some number of xxxx... for matching */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTMHEAD command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTMHEAD pattern must begin with upper case 'B'.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* Optionally one of 0-9ABCD */
+
+	    if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
+	      j = 2;
+	    }
+	    else {
+	      j = 1;
+	    }
+
+	    count_x = 0;
+	    count_other = 0;
+	    for (k = j ; k < strlen(t); k++) {
+	      if (t[k] == 'x') count_x++;
+	      else count_other++;
+	    }
+
+	    if (count_other != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTMHEAD must have only lower case x to match received data.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    // optional prefix
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      char mh[30];
+
+	      strlcpy(tl->mhead.prefix, t, sizeof(tl->mhead.prefix));
+
+	      if (!alldigits(t) || (strlen(t) != 4 &&  strlen(t) != 6 && strlen(t) != 10)) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTMHEAD prefix must be 4, 6, or 10 digits.\n", line);
+	        p_tt_config->ttloc_len--;
+	        continue;
+	      }
+	      if (tt_mhead_to_text(t, 0, mh, sizeof(mh)) != 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTMHEAD prefix not a valid DTMF sequence.\n", line);
+	        p_tt_config->ttloc_len--;
+	        continue;
+	      }      
+	    }
+
+	    k = strlen(tl->mhead.prefix) + count_x;
+
+	    if (k != 4 && k != 6  && k != 10  && k != 12 ) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTMHEAD prefix and user data must have a total of 4, 6, 10, or 12 digits.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    
+	  }
+
+
+/*
+ * TTSATSQ 		- Define pattern to be used for Satellite square.
+ *
+ * TTSATSQ   pattern    
+ *
+ *			Pattern would be  B[0-9A-D]xxxx 
+ *			
+ *			Must have exactly 4 x.
+ */
+
+	  else if (strcasecmp(t, "TTSATSQ") == 0) {
+
+// TODO1.2:  TTSATSQ To be continued...
+
+	    struct ttloc_s *tl;
+	    int j;
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_SATSQ;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+	    tl->point.lat = 0;
+	    tl->point.lon = 0;
+
+	    /* Pattern: B, optional additional button, exactly xxxx for matching */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTSATSQ command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTSATSQ pattern must begin with upper case 'B'.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* Optionally one of 0-9ABCD */
+
+	    if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
+	      j = 2;
+	    }
+	    else {
+	      j = 1;
+	    }
+
+	    if (strcmp(t+j, "xxxx") != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTSATSQ pattern must end with exactly xxxx in lower case.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* temp debugging */
+
+	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
+	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, 
+	    //		p_tt_config->ttloc_ptr[j].pattern);
+	    //}
+	  }
+
+/*
+ * TTAMBIG 		- Define pattern to be used for Object Location Ambiguity.
+ *
+ * TTAMBIG   pattern
+ *
+ *			Pattern would be  B[0-9A-D]x
+ *
+ *			Must have exactly one x.
+ */
+
+	  else if (strcasecmp(t, "TTAMBIG") == 0) {
+
+// TODO1.3:  TTAMBIG To be continued...
+
+	    struct ttloc_s *tl;
+	    int j;
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_AMBIG;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+
+	    /* Pattern: B, optional additional button, exactly x for matching */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTAMBIG command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    if (t[0] != 'B') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTAMBIG pattern must begin with upper case 'B'.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* Optionally one of 0-9ABCD */
+
+	    if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
+	      j = 2;
+	    }
+	    else {
+	      j = 1;
+	    }
+
+	    if (strcmp(t+j, "x") != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: TTAMBIG pattern must end with exactly one x in lower case.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* temp debugging */
+
+	    //for (j=0; j<p_tt_config->ttloc_len; j++) {
+	    //  dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
+	    //		p_tt_config->ttloc_ptr[j].pattern);
+	    //}
+	  }
+
+
+/*
+ * TTMACRO 		- Define compact message format with full expansion
+ *
+ * TTMACRO   pattern  definition
+ *
+ *		pattern can contain:
+ *			0-9 which must match exactly.
+ *				In version 1.2, also allow A,B,C,D for exact match.
+ *			x, y, z which are used for matching of variable fields.
+ *			
+ *		definition can contain:
+ *			0-9, A, B, C, D, *, #, x, y, z.
+ *			Not sure why # was included in there.
+ *
+ *	    new for version 1.3 - in progress
+ *
+ *			AA{objname}
+ *			AB{symbol}
+ *			AC{call}
+ *
+ *		These provide automatic conversion from plain text to the TT encoding.
+ *		
+ */
+	  else if (strcasecmp(t, "TTMACRO") == 0) {
+
+	    struct ttloc_s *tl;
+	    int j;
+	    int p_count[3], d_count[3];
+	    int tt_error = 0;
+
+	    assert (p_tt_config->ttloc_size >= 2);
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    /* Allocate new space, but first, if already full, make larger. */
+	    if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
+	      p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
+	      p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
+	    }
+	    p_tt_config->ttloc_len++;
+	    assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
+
+	    tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
+	    tl->type = TTLOC_MACRO;
+	    strlcpy(tl->pattern, "", sizeof(tl->pattern));
+
+	    /* Pattern: Any combination of digits, x, y, and z. */
+	    /* Also make note of which letters are used in pattern and defintition. */
+ 	    /* Version 1.2: also allow A,B,C,D in the pattern. */
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line);
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+	    strlcpy (tl->pattern, t, sizeof(tl->pattern));
+
+	    p_count[0] = p_count[1] = p_count[2] = 0;
+
+	    for (j=0; j<strlen(t); j++) {
+	      if ( strchr ("0123456789ABCDxyz", t[j]) == NULL) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTMACRO pattern can contain only digits, A, B, C, D, and lower case x, y, or z.\n", line);
+	        p_tt_config->ttloc_len--;
+	        continue;
+	      }
+	      /* Count how many x, y, z in the pattern. */
+	      if (t[j] >= 'x' && t[j] <= 'z') {
+		p_count[t[j]-'x']++;
+	      }
+	    }
+
+	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("Line %d: TTMACRO pattern \"%s\" p_count = %d %d %d.\n", line, t, p_count[0], p_count[1], p_count[2]);
+
+	    /* Next we should find the definition. */
+	    /* It can contain touch tone characters and lower case x, y, z for substitutions. */
+
+	    t = split(NULL,1);;
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing definition for TTMACRO command.\n", line);
+	      tl->macro.definition = "";	/* Don't die on null pointer later. */
+	      p_tt_config->ttloc_len--;
+	      continue;
+	    }
+
+	    /* Make a pass over the definition, looking for the xx{...} substitutions. */
+	    /* These are done just once when reading the configuration file. */
+
+	    char *pi;
+	    char *ps;
+	    char stemp[100];  // text inside of xx{...}
+	    char ttemp[300];  // Converted to tone sequences.
+	    char otemp[1000]; // Result after any substitutions.
+	    char t2[2];
+
+	    strlcpy (otemp, "", sizeof(otemp));
+	    t2[1] = '\0';
+	    pi = t;
+	    while (*pi == ' ' || *pi == '\t') {
+	      pi++;
+	    }
+	    for ( ; *pi != '\0'; pi++) {
+
+	      if (strncmp(pi, "AC{", 3) == 0) {
+
+		// Convert to fixed length 10 digit callsign.
+
+	        pi += 3;
+	        ps = stemp;
+	        while (*pi != '}' && *pi != '*' && *pi != '\0') {
+	          *ps++ = *pi++;
+	        }
+	        if (*pi == '}') {
+	          *ps = '\0';
+	          if (tt_text_to_call10 (stemp, 0, ttemp) == 0) {
+	            //text_color_set(DW_COLOR_DEBUG);
+	            //dw_printf ("DEBUG Line %d: AC{%s} -> AC%s\n", line, stemp, ttemp);
+	            strlcat (otemp, "AC", sizeof(otemp));
+	            strlcat (otemp, ttemp, sizeof(otemp));
+	          }
+	          else {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Line %d: AC{%s} could not be converted to tones for callsign.\n", line, stemp);
+		    tt_error++;
+	          }
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Line %d: AC{... is missing matching } in TTMACRO definition.\n", line);
+		  tt_error++;
+	        }
+	      }
+
+	      else if (strncmp(pi, "AA{", 3) == 0) {
+
+		// Convert to object name.
+
+	        pi += 3;
+	        ps = stemp;
+	        while (*pi != '}' && *pi != '*' && *pi != '\0') {
+	          *ps++ = *pi++;
+	        }
+	        if (*pi == '}') {
+	          *ps = '\0';
+	          if (strlen(stemp) > 9) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Line %d: Object name %s has been truncated to 9 characters.\n", line, stemp);
+	            stemp[9] = '\0';
+	          }
+	          if (tt_text_to_two_key (stemp, 0, ttemp) == 0) {
+	            //text_color_set(DW_COLOR_DEBUG);
+	            //dw_printf ("DEBUG Line %d: AA{%s} -> AA%s\n", line, stemp, ttemp);
+	            strlcat (otemp, "AA", sizeof(otemp));
+	            strlcat (otemp, ttemp, sizeof(otemp));
+	          }
+	          else {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Line %d: AA{%s} could not be converted to tones for object name.\n", line, stemp);
+		    tt_error++;
+	          }
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Line %d: AA{... is missing matching } in TTMACRO definition.\n", line);
+		  tt_error++;
+	        }
+	      }
+
+	      else if (strncmp(pi, "AB{", 3) == 0) {
+
+		// Attempt conversion from description to symbol code.
+
+	        pi += 3;
+	        ps = stemp;
+	        while (*pi != '}' && *pi != '*' && *pi != '\0') {
+	          *ps++ = *pi++;
+	        }
+	        if (*pi == '}') {
+	          char symtab;
+	          char symbol;
+
+	          *ps = '\0';
+
+	          // First try to find something matching the description.
+
+	          if (symbols_code_from_description (' ', stemp, &symtab, &symbol) == 0) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Line %d: Couldn't convert \"%s\" to APRS symbol code.  Using default.\n", line, stemp);
+	            symtab = '\\';	// Alternate
+	            symbol = 'A';	// Box
+	          }
+
+		  // Convert symtab(overlay) & symbol to tone sequence.
+
+		  symbols_to_tones (symtab, symbol, ttemp, sizeof(ttemp));
+
+	          //text_color_set(DW_COLOR_DEBUG);
+	          //dw_printf ("DEBUG config file Line %d: AB{%s} -> %s\n", line, stemp, ttemp);
+
+		  strlcat (otemp, ttemp, sizeof(otemp));
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Line %d: AB{... is missing matching } in TTMACRO definition.\n", line);
+		  tt_error++;
+	        }
+	      }
+
+	      else if (strncmp(pi, "CA{", 3) == 0) {
+
+		// Convert to enhanced comment that can contain any ASCII character.
+
+	        pi += 3;
+	        ps = stemp;
+	        while (*pi != '}' && *pi != '*' && *pi != '\0') {
+	          *ps++ = *pi++;
+	        }
+	        if (*pi == '}') {
+	          *ps = '\0';
+	          if (tt_text_to_ascii2d (stemp, 0, ttemp) == 0) {
+	            //text_color_set(DW_COLOR_DEBUG);
+	            //dw_printf ("DEBUG Line %d: CA{%s} -> CA%s\n", line, stemp, ttemp);
+	            strlcat (otemp, "CA", sizeof(otemp));
+	            strlcat (otemp, ttemp, sizeof(otemp));
+	          }
+	          else {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Line %d: CA{%s} could not be converted to tones for enhanced comment.\n", line, stemp);
+		    tt_error++;
+	          }
+	        }
+	        else {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Line %d: CA{... is missing matching } in TTMACRO definition.\n", line);
+		  tt_error++;
+	        }
+	      }
+
+
+	      else if (strchr("0123456789ABCD*#xyz", *pi) != NULL) {
+	        t2[0] = *pi;
+	        strlcat (otemp, t2, sizeof(otemp));
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line);
+	        tt_error++;
+	      }
+	    } 
+
+	    /* Make sure that number of x, y, z, in pattern and definition match. */
+
+	    d_count[0] = d_count[1] = d_count[2] = 0;
+
+	    for (j=0; j<strlen(otemp); j++) {
+	      if (otemp[j] >= 'x' && otemp[j] <= 'z') {
+		d_count[otemp[j]-'x']++;
+	      }
+	    }
+
+	    /* A little validity checking. */
+
+	    for (j=0; j<3; j++) {
+	      if (p_count[j] > 0 && d_count[j] == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: '%c' is in TTMACRO pattern but is not used in definition.\n", line, 'x'+j);
+	      }
+	      if (d_count[j] > 0 && p_count[j] == 0) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Line %d: '%c' is referenced in TTMACRO definition but does not appear in the pattern.\n", line, 'x'+j);
+	      }
+	    }
+
+	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("DEBUG Config Line %d: %s -> %s\n", line, t, otemp);
+
+	    if (tt_error == 0) {
+	      tl->macro.definition = strdup(otemp);
+	    }
+	    else {
+	      p_tt_config->ttloc_len--;
+	    }
+	  }
+
+/*
+ * TTOBJ 		- TT Object Report options.
+ *
+ * TTOBJ  recv-chan  where-to  [ via-path ] 
+ *
+ *	whereto is any combination of transmit channel, APP, IG.
+ */
+
+
+	  else if (strcasecmp(t, "TTOBJ") == 0) {
+	    int r, x = -1;
+	    int app = 0;
+	    int ig = 0;
+	    char *p;
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing DTMF receive channel for TTOBJ command.\n", line);
+	      continue;
+	    }
+
+	    r = atoi(t);
+	    if (r < 0 || r > MAX_CHANS-1) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: DTMF receive channel must be in range of 0 to %d on line %d.\n", 
+							MAX_CHANS-1, line);
+	      continue;
+	    }
+	    if ( ! p_audio_config->achan[r].valid) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n", 
+							line, r);
+	      continue;
+	    }
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line);
+	      continue;
+	    }
+
+	    // Can have any combination of number, APP, IG.  
+    	    // Would it be easier with strtok?
+
+	    for (p = t; *p != '\0'; p++) {
+
+	      if (isdigit(*p)) {
+	        x = *p - '0';
+	        if (x < 0 || x > MAX_CHANS-1) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line);
+	          x = -1;
+	        }
+	        else if ( ! p_audio_config->achan[x].valid) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x);
+	          x = -1;
+	        }
+	      }
+	      else if (*p == 'a' || *p == 'A') {
+	        app = 1;
+	      }
+	      else if (*p == 'i' || *p == 'I') {
+	        ig = 1;
+	      }
+	      else if (strchr("pPgG,", *p) != NULL) {
+	        ;
+	      }
+	      else {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file, line %d: Expected comma separated list with some combination of transmit channel, APP, and IG.\n", line);
+	      }              
+	    }
+
+// This enables the DTMF decoder on the specified channel.
+// Additional channels can be enabled with the DTMF command.
+// Note that DTMF command does not enable the APRStt gateway.
+
+
+	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("Debug TTOBJ r=%d, x=%d, app=%d, ig=%d\n", r, x, app, ig);
+
+	    p_audio_config->achan[r].dtmf_decode = DTMF_DECODE_ON;
+	    p_tt_config->gateway_enabled = 1;
+	    p_tt_config->obj_recv_chan = r;
+	    p_tt_config->obj_xmit_chan = x;
+	    p_tt_config->obj_send_to_app = app;
+	    p_tt_config->obj_send_to_ig = ig;
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+
+	      // TODO: Should do some validity checking on the path.
+	      strlcpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via));
+	    }
+	  }
+
+/*
+ * TTERR 		- TT responses for success or errors.
+ *
+ * TTERR  msg_id  method  text...  
+ */
+
+	  else if (strcasecmp(t, "TTERR") == 0) {
+	    int n, msg_num;
+	    char *p;
+	    char method[AX25_MAX_ADDR_LEN];
+	    int ssid;
+	    int heard;
+
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing message identifier for TTERR command.\n", line);
+	      continue;
+	    }
+	    
+	    msg_num = -1;
+	    for (n=0; n<TT_ERROR_MAXP1; n++) {
+	      if (strcasecmp(t, tt_msg_id[n]) == 0) {
+	        msg_num = n;
+	        break;
+	      }
+	    }
+	    if (msg_num < 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Invalid message identifier for TTERR command.\n", line);	
+		// pick one of ...
+	      continue;
+	    }
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing method (SPEECH, MORSE) for TTERR command.\n", line);
+	      continue;
+	    }
+
+	    for (p=t; *p!= '\0'; p++) {
+	      if (islower(*p)) *p = toupper(*p);
+	    }
+
+	    if ( ! ax25_parse_addr(-1, t, 1, method, &ssid, &heard)) {
+	       continue;  // function above prints any error message
+	    }
+
+	    if (strcmp(method,"MORSE") != 0 && strcmp(method,"SPEECH") != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Response method of %s must be SPEECH or MORSE for TTERR command.\n", line, method);
+	      continue;
+	    }
+
+	    t = split(NULL,1);;
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing response text for TTERR command.\n", line);
+	      continue;
+	    }
+
+  	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("Line %d: TTERR debug %d %s-%d \"%s\"\n", line, msg_num, method, ssid, t);
+ 
+	    assert (msg_num >= 0 && msg_num < TT_ERROR_MAXP1);
+
+	    strlcpy (p_tt_config->response[msg_num].method, method, sizeof(p_tt_config->response[msg_num].method));
+
+// TODO1.3: Need SSID too!
+
+	    strlcpy (p_tt_config->response[msg_num].mtext, t, sizeof(p_tt_config->response[msg_num].mtext));
+	    p_tt_config->response[msg_num].mtext[TT_MTEXT_LEN-1] = '\0';
+
+	  }
+
+/*
+ * TTSTATUS 		- TT custom status messages.
+ *
+ * TTSTATUS  status_id  text...  
+ */
+
+	  else if (strcasecmp(t, "TTSTATUS") == 0) {
+	    int status_num;
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing status number for TTSTATUS command.\n", line);
+	      continue;
+	    }
+	    
+	    status_num = atoi(t);
+
+	    if (status_num < 1 || status_num > 9) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Status number for TTSTATUS command must be in range of 1 to 9.\n", line);	
+	      continue;
+	    }
+
+	    t = split(NULL,1);;
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing status text for TTSTATUS command.\n", line);
+	      continue;
+	    }
+
+  	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("Line %d: TTSTATUS debug %d \"%s\"\n", line, status_num, t);
+ 
+	    while (*t == ' ' || *t == '\t') t++;   // remove leading white space.
+
+	    strlcpy (p_tt_config->status[status_num], t, sizeof(p_tt_config->status[status_num]));
+	  }
+
+
+/*
+ * TTCMD 		- Command to run when valid sequence is received.
+ *			  Any text generated will be sent back to user.
+ *
+ * TTCMD ...  
+ */
+
+	  else if (strcasecmp(t, "TTCMD") == 0) {
+
+	    t = split(NULL,1);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing command for TTCMD command.\n", line);
+	      continue;
+	    }
+	    
+	    strlcpy (p_tt_config->ttcmd, t, sizeof(p_tt_config->ttcmd));
+	  }
+
+
+/*
+ * ==================== Internet gateway ==================== 
+ */
+
+/*
+ * IGSERVER 		- Name of IGate server.
+ *
+ * IGSERVER  hostname [ port ] 				-- original implementation.
+ *
+ * IGSERVER  hostname:port				-- more in line with usual conventions.
+ */
+
+	  else if (strcasecmp(t, "IGSERVER") == 0) {
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line);
+	      continue;
+	    }
+	    strlcpy (p_igate_config->t2_server_name, t, sizeof(p_igate_config->t2_server_name));
+
+	    /* If there is a : in the name, split it out as the port number. */
+
+	    t = strchr (p_igate_config->t2_server_name, ':');
+	    if (t != NULL) {
+	      *t = '\0';
+	      t++;
+	      int n = atoi(t);
+              if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) {
+	        p_igate_config->t2_server_port = n;
+	      }
+	      else {
+	        p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", 
+			line, p_igate_config->t2_server_port);
+   	      }
+	    }
+
+	    /* Alternatively, the port number could be separated by white space. */
+	    
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      int n = atoi(t);
+              if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) {
+	        p_igate_config->t2_server_port = n;
+	      }
+	      else {
+	        p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", 
+			line, p_igate_config->t2_server_port);
+   	      }
+	    }
+	    //dw_printf ("DEBUG  server=%s   port=%d\n", p_igate_config->t2_server_name, p_igate_config->t2_server_port);
+	    //exit (0);
+	  }
+
+/*
+ * IGLOGIN 		- Login callsign and passcode for IGate server
+ *
+ * IGLOGIN  callsign  passcode
+ */
+
+	  else if (strcasecmp(t, "IGLOGIN") == 0) {
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing login callsign for IGLOGIN command.\n", line);
+	      continue;
+	    }
+	    // TODO: Wouldn't hurt to do validity checking of format.
+	    strlcpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login));
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line);
+	      continue;
+	    }
+	    strlcpy (p_igate_config->t2_passcode, t, sizeof(p_igate_config->t2_passcode));
+	  }
+
+/*
+ * IGTXVIA 		- Transmit channel and VIA path for messages from IGate server
+ *
+ * IGTXVIA  channel  [ path ]
+ */
+
+	  else if (strcasecmp(t, "IGTXVIA") == 0) {
+	    int n;
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing transmit channel for IGTXVIA command.\n", line);
+	      continue;
+	    }
+
+	    n = atoi(t);
+	    if (n < 0 || n > MAX_CHANS-1) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", 
+							MAX_CHANS-1, line);
+	      continue;
+	    }
+	    p_igate_config->tx_chan = n;
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      char *p;
+	      p_igate_config->tx_via[0] = ',';
+	      strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1);
+	      for (p = p_igate_config->tx_via; *p != '\0'; p++) {
+	        if (islower(*p)) {
+		  *p = toupper(*p);	/* silently force upper case. */
+	        }
+	      }
+	    }
+	  }
+
+/*
+ * IGFILTER 		- Filter for messages from IGate server
+ *
+ * IGFILTER  filter-spec ... 
+ */
+
+	  else if (strcasecmp(t, "IGFILTER") == 0) {
+
+	    t = split(NULL,1);		/* Take rest of line as one string. */
+
+	    if (t != NULL && strlen(t) > 0) {
+	      p_igate_config->t2_filter = strdup (t);
+	    }
+	  }
+
+
+/*
+ * IGTXLIMIT 		- Limit transmissions during 1 and 5 minute intervals.
+ *
+ * IGTXLIMIT  one-minute-limit  five-minute-limit
+ */
+
+	  else if (strcasecmp(t, "IGTXLIMIT") == 0) {
+	    int n;
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line);
+	      continue;
+	    }
+	    
+	    n = atoi(t);
+            if (n < 1) {
+	      p_igate_config->tx_limit_1 = 1;
+	    }
+            else if (n <= IGATE_TX_LIMIT_1_MAX) {
+	      p_igate_config->tx_limit_1 = n;
+	    }
+	    else {
+	      p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_MAX;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: One minute transmit limit has been reduced to %d.\n",
+				line, p_igate_config->tx_limit_1);
+	      dw_printf ("You won't make friends by setting a limit this high.\n");
+   	    }
+
+
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing five minute limit for IGTXLIMIT command.\n", line);
+	      continue;
+	    }
+	    
+	    n = atoi(t);
+            if (n < 1) {
+	      p_igate_config->tx_limit_5 = 1;
+	    }
+            else if (n <= IGATE_TX_LIMIT_5_MAX) {
+	      p_igate_config->tx_limit_5 = n;
+	    }
+	    else {
+	      p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_MAX;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Five minute transmit limit has been reduced to %d.\n",
+				line, p_igate_config->tx_limit_5);
+	      dw_printf ("You won't make friends by setting a limit this high.\n");
+   	    }
+	  }
+
+/*
+ * SATGATE 		- Special SATgate mode to delay packets heard directly.
+ *
+ * SATGATE [ n ]
+ */
+
+	  else if (strcasecmp(t, "SATGATE") == 0) {
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+
+	      int n = atoi(t);
+              if (n >= MIN_SATGATE_DELAY && n <= MAX_SATGATE_DELAY) {
+	        p_igate_config->satgate_delay = n;
+	      }
+	      else {
+	        p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY;
+	        text_color_set(DW_COLOR_ERROR);
+                dw_printf ("Line %d: Unreasonable SATgate delay.  Using default.\n", line);
+	      }
+	    }
+	    else {
+	      p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY;
+	    }
+	  }
+
+
+
+/*
+ * ==================== All the left overs ==================== 
+ */
+
+/*
+ * AGWPORT 		- Port number for "AGW TCPIP Socket Interface" 
+ *
+ * In version 1.2 we allow 0 to disable listening.
+ */
+
+	  else if (strcasecmp(t, "AGWPORT") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing port number for AGWPORT command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
+	      p_misc_config->agwpe_port = n;
+	    }
+	    else {
+	      p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n", 
+			line, p_misc_config->agwpe_port);
+   	    }
+	  }
+
+/*
+ * KISSPORT 		- Port number for KISS over IP. 
+ */
+
+	  else if (strcasecmp(t, "KISSPORT") == 0) {
+	    int n;
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line);
+	      continue;
+	    }
+	    n = atoi(t);
+            if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
+	      p_misc_config->kiss_port = n;
+	    }
+	    else {
+	      p_misc_config->kiss_port = DEFAULT_KISS_PORT;
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid port number for KISS TCPIP Socket Interface. Using %d.\n", 
+			line, p_misc_config->kiss_port);
+   	    }
+	  }
+
+/*
+ * NULLMODEM		- Device name for our end of the virtual "null modem"
+ */
+	  else if (strcasecmp(t, "nullmodem") == 0) {
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Missing device name for my end of the 'null modem' on line %d.\n", line);
+	      continue;
+	    }
+	    else {
+	      strlcpy (p_misc_config->nullmodem, t, sizeof(p_misc_config->nullmodem));
+	    }
+	  }
+
+/*
+ * GPSNMEA		- Device name for reading from GPS receiver.
+ */
+	  else if (strcasecmp(t, "gpsnmea") == 0) {
+	    t = split(NULL,0);
+	    if (t == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: Missing serial port name for GPS receiver.\n", line);
+	      continue;
+	    }
+	    else {
+	      strlcpy (p_misc_config->gpsnmea_port, t, sizeof(p_misc_config->gpsnmea_port));
+	    }
+	  }
+
+/*
+ * GPSD		- Use GPSD server.
+ *
+ * GPSD [ host [ port ] ]
+ */
+	  else if (strcasecmp(t, "gpsd") == 0) {
+
+#if __WIN32__
+
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file, line %d: The GPSD interface is not available for Windows.\n", line);
+	    continue;
+
+#elif ENABLE_GPSD
+
+	    strlcpy (p_misc_config->gpsd_host, "localhost", sizeof(p_misc_config->gpsd_host));
+	    p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT);
+
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      strlcpy (p_misc_config->gpsd_host, t, sizeof(p_misc_config->gpsd_host));
+
+	      t = split(NULL,0);
+	      if (t != NULL) {
+
+	        int n = atoi(t);
+                if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
+	          p_misc_config->gpsd_port = n;
+	        }
+	        else {
+	          p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT);
+	          text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("Line %d: Invalid port number for GPSD Socket Interface. Using default of %d.\n", 
+			line, p_misc_config->gpsd_port);
+	        }
+	      }
+	    }
+#else
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file, line %d: The GPSD interface has not been enabled.\n", line);
+	    dw_printf ("Install gpsd and libgps-dev packages then rebuild direwolf.\n");
+	    continue;
+#endif
+
+	  }
+
+/*
+ * NMEA		- Device name for communication with NMEA device.
+ *		  Wasn't documented will probably use WAYPOINT instead.
+ */
+	  else if (strcasecmp(t, "nmea") == 0) {
+	    t = split(NULL,0);
+	    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 {
+	      strlcpy (p_misc_config->nmea_port, t, sizeof(p_misc_config->nmea_port));
+	    }
+	  }
+
+/*
+ * LOGDIR	- Directory name for storing log files.  Use "." for current working directory.
+ */
+	  else if (strcasecmp(t, "logdir") == 0) {
+	    t = split(NULL,0);
+	    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 {
+	      strlcpy (p_misc_config->logdir, t, sizeof(p_misc_config->logdir));
+	    }
+	    t = split(NULL,0);
+	    if (t != NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: LOGDIR on line %d should have directory path and nothing more.\n", line);
+	    }
+	  }
+
+/*
+ * BEACON channel delay every message
+ *
+ * Original handcrafted style.  Removed in version 1.0.
+ */
+
+	  else if (strcasecmp(t, "BEACON") == 0) {
+	    	    
+	    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");
+  
+	  }
+
+
+/*
+ * PBEACON keyword=value ...
+ * OBEACON keyword=value ...
+ * TBEACON keyword=value ...
+ * CBEACON keyword=value ...
+ *
+ * New style with keywords for options.
+ */
+
+	  else if (strcasecmp(t, "PBEACON") == 0 ||
+		   strcasecmp(t, "OBEACON") == 0 ||
+		   strcasecmp(t, "TBEACON") == 0 ||
+		   strcasecmp(t, "CBEACON") == 0) {
+
+	    if (p_misc_config->num_beacons < MAX_BEACONS) {
+
+	      memset (&(p_misc_config->beacon[p_misc_config->num_beacons]), 0, sizeof(struct beacon_s));
+	      if (strcasecmp(t, "PBEACON") == 0) {
+	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_POSITION;
+	      }
+	      else if (strcasecmp(t, "OBEACON") == 0) {
+	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_OBJECT;
+	      }
+	      else if (strcasecmp(t, "TBEACON") == 0) {
+	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_TRACKER;
+	      }
+	      else {
+	        p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_CUSTOM;
+	      }
+
+	      /* Save line number because some errors will be reported later. */
+	      p_misc_config->beacon[p_misc_config->num_beacons].lineno = line;
+
+	      if (beacon_options(t + strlen("xBEACON") + 1, &(p_misc_config->beacon[p_misc_config->num_beacons]), line, p_audio_config)) {
+	        p_misc_config->num_beacons++;
+	      }
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Maximum number of beacons exceeded on line %d.\n", line);
+	      continue;
+	    }
+	  }
+
+
+/*
+ * SMARTBEACONING [ fast_speed fast_rate slow_speed slow_rate turn_time turn_angle turn_slope ]
+ *
+ * Parameters must be all or nothing.
+ */
+
+	  else if (strcasecmp(t, "SMARTBEACON") == 0 ||	
+	           strcasecmp(t, "SMARTBEACONING") == 0) {
+
+	    int n;
+
+#define SB_NUM(name,sbvar,minn,maxx,unit)  							\
+	    t = split(NULL,0);									\
+	    if (t == NULL) {									\
+	      if (strcmp(name, "fast speed") == 0) {						\
+	        p_misc_config->sb_configured = 1;						\
+	        continue;									\
+	      }											\
+	      text_color_set(DW_COLOR_ERROR);							\
+	      dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name);		\
+	      continue;										\
+	    }											\
+	    n = atoi(t);									\
+            if (n >= minn && n <= maxx) {							\
+	      p_misc_config->sbvar = n;								\
+	    }											\
+	    else {										\
+	      text_color_set(DW_COLOR_ERROR);							\
+              dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n",	\
+			line, name, p_misc_config->sbvar, unit);				\
+   	    }
+
+#define SB_TIME(name,sbvar,minn,maxx,unit)  							\
+	    t = split(NULL,0);									\
+	    if (t == NULL) {									\
+	      text_color_set(DW_COLOR_ERROR);							\
+	      dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name);		\
+	      continue;										\
+	    }											\
+	    n = parse_interval(t,line);								\
+            if (n >= minn && n <= maxx) {							\
+	      p_misc_config->sbvar = n;								\
+	    }											\
+	    else {										\
+	      text_color_set(DW_COLOR_ERROR);							\
+              dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n",	\
+			line, name, p_misc_config->sbvar, unit);				\
+   	    }
+
+
+	    SB_NUM  ("fast speed", sb_fast_speed,  2,   90,  "MPH")
+	    SB_TIME ("fast rate",  sb_fast_rate,  10,  300,  "seconds")
+
+	    SB_NUM  ("slow speed", sb_slow_speed,  1,   30,  "MPH")
+	    SB_TIME ("slow rate",  sb_slow_rate,  30, 3600,  "seconds")
+
+	    SB_TIME ("turn time",  sb_turn_time,   5,  180,  "seconds")
+	    SB_NUM  ("turn angle", sb_turn_angle,  5,   90,  "degrees")
+	    SB_NUM  ("turn slope", sb_turn_slope,  1,  255,  "deg*mph")
+
+	    /* If I was ambitious, I might allow optional */
+	    /* unit at end for miles or km / hour. */
+
+	    p_misc_config->sb_configured = 1;
+	  }
+
+/*
+ * Invalid command.
+ */
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file: Unrecognized command '%s' on line %d.\n", t, line);
+	  }  
+
+	}
+
+	fclose (fp);
+
+/*
+ * A little error checking for option interactions.
+ */
+
+/*
+ * Require that MYCALL be set when digipeating or IGating.
+ *
+ * Suggest that beaconing be enabled when digipeating.
+ */
+	int i, j, k, b;
+
+	for (i=0; i<MAX_CHANS; i++) {
+	  for (j=0; j<MAX_CHANS; j++) {
+
+	    if (p_digi_config->enabled[i][j]) {
+
+	      if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || 
+		   strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || 
+		   strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
+
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i);
+	        p_digi_config->enabled[i][j] = 0;
+	      }
+
+	      if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || 
+	           strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 ||
+		   strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) {
+
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); 
+	        p_digi_config->enabled[i][j] = 0;
+	      }
+
+	      b = 0;
+	      for (k=0; k<p_misc_config->num_beacons; k++) {
+	        if (p_misc_config->beacon[k].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", j); 
+		// 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;
+	      }
+	    }
+	  }
+
+	  if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) {
+
+	    if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0  || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i);
+	      strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login));
+	    }
+	    if (p_igate_config->tx_chan >= 0 && 
+			( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 ||
+		          strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 ||
+			  strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "N0CALL") == 0)) {
+
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: MYCALL must be set for transmit channel %d before Tx IGate is allowed.\n", i);
+	      p_igate_config->tx_chan = -1;
+	    }
+	  }
+
+	}
+
+} /* end config_init */
+
+
+/*
+ * Parse the PBEACON or OBEACON options.
+ * Returns 1 for success, 0 for serious error.
+ */
+
+static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config)
+{
+	char *t;
+	char temp_symbol[100];
+	int ok;
+	char zone[8];
+	double easting = G_UNKNOWN;
+	double northing = G_UNKNOWN;
+
+	strlcpy (temp_symbol, "", sizeof(temp_symbol));
+	strlcpy (zone, "", sizeof(zone));
+
+	b->sendto_type = SENDTO_XMIT;
+	b->sendto_chan = 0;
+	b->delay = 60;
+	b->every = 600;
+	//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 */
+	b->freq = G_UNKNOWN;
+	b->tone = G_UNKNOWN;
+	b->offset = G_UNKNOWN;
+
+	while ((t = split(NULL,0)) != NULL) {
+
+	  char keyword[20];
+	  char value[200];
+	  char *e;
+	  char *p;
+
+
+	  e = strchr(t, '=');
+	  if (e == NULL) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file: No = found in, %s, on line %d.\n", t, line);
+	    return (0);
+	  }
+	  *e = '\0';
+	  strlcpy (keyword, t, sizeof(keyword));
+	  strlcpy (value, e+1, sizeof(value));
+
+	  if (strcasecmp(keyword, "DELAY") == 0) {
+	    b->delay = parse_interval(value,line);
+	  }
+	  else if (strcasecmp(keyword, "EVERY") == 0) {
+	    b->every = parse_interval(value,line);
+	  }
+	  else if (strcasecmp(keyword, "SENDTO") == 0) {
+	    if (value[0] == 'i' || value[0] == 'I') {
+	       b->sendto_type = SENDTO_IGATE;
+	       b->sendto_chan = 0;
+	    }
+	    else if (value[0] == 'r' || value[0] == 'R') {
+	       int n = atoi(value+1);
+	       if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
+	         text_color_set(DW_COLOR_ERROR);
+	         dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
+	         continue;
+	       }
+	       b->sendto_type = SENDTO_RECV;
+	       b->sendto_chan = n;
+	    }
+	    else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') {
+	      int n = atoi(value+1);
+	      if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
+	        continue;
+	      }
+
+	      b->sendto_type = SENDTO_XMIT;
+	      b->sendto_chan = n;
+	    }
+	    else {
+	       int n = atoi(value);
+	       if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
+	         text_color_set(DW_COLOR_ERROR);
+	         dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
+	         continue;
+	       }
+	       b->sendto_type = SENDTO_XMIT;
+	       b->sendto_chan = n;
+	    }
+	  }
+	  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) {
+	    b->via = strdup(value);
+	    for (p = b->via; *p != '\0'; p++) {
+	      if (islower(*p)) {
+	        *p = toupper(*p);	/* silently force upper case. */
+	      }
+	    }
+	  }
+	  else if (strcasecmp(keyword, "INFO") == 0) {
+	    b->custom_info = strdup(value);
+	  }
+	  else if (strcasecmp(keyword, "INFOCMD") == 0) {
+	    b->custom_infocmd = strdup(value);
+	  }
+	  else if (strcasecmp(keyword, "OBJNAME") == 0) {
+	    strlcpy(b->objname, value, sizeof(b->objname));
+	  }
+	  else if (strcasecmp(keyword, "LAT") == 0) {
+	    b->lat = parse_ll (value, LAT, 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) {
+	    strlcpy(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. */
+	    strlcpy (temp_symbol, value, sizeof(temp_symbol));
+	  }
+	  else if (strcasecmp(keyword, "OVERLAY") == 0) {
+	    if (strlen(value) == 1 && (isupper(value[0]) || isdigit(value[0]))) {
+	      b->symtab = value[0];
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file: Overlay must be one character in range of 0-9 or A-Z, upper case only, on line %d.\n", line);
+	    }
+	  }
+	  else if (strcasecmp(keyword, "POWER") == 0) {
+	    b->power = atoi(value);
+	  }
+	  else if (strcasecmp(keyword, "HEIGHT") == 0) {
+	    b->height = atoi(value);
+	  }
+	  else if (strcasecmp(keyword, "GAIN") == 0) {
+	    b->gain = atoi(value);
+	  }
+	  else if (strcasecmp(keyword, "DIR") == 0 || strcasecmp(keyword, "DIRECTION") == 0) {
+	    strlcpy(b->dir, value, sizeof(b->dir));
+	  }
+	  else if (strcasecmp(keyword, "FREQ") == 0) {
+	    b->freq = atof(value);
+	  }
+	  else if (strcasecmp(keyword, "TONE") == 0) {
+	    b->tone = atof(value);
+	  }
+	  else if (strcasecmp(keyword, "OFFSET") == 0 || strcasecmp(keyword, "OFF") == 0) {
+	    b->offset = atof(value);
+	  }
+	  else if (strcasecmp(keyword, "COMMENT") == 0) {
+	    b->comment = strdup(value);
+	  }
+	  else if (strcasecmp(keyword, "COMMENTCMD") == 0) {
+	    b->commentcmd = strdup(value);
+	  }
+	  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);
+	    return (0);
+	  }
+	}
+
+	if (b->custom_info != NULL && b->custom_infocmd != NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time..\n", 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) {
+
+	    long lzone;
+	    char latband, hemi;
+	    long lerr;
+	    double dlat, dlon;
+
+	    lzone = parse_utm_zone (zone, &latband, &hemi);
+
+	    lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon);
+
+            if (lerr == 0) {
+              b->lat = R2D(dlat);
+	      b->lon = R2D(dlon);
+	    }
+	    else {
+	      char message [300];
+
+              utm_error_string (lerr, message);
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message);
+            }
+	  }
+	  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) {
+
+	  if (strlen(temp_symbol) == 2 && 
+		(temp_symbol[0] == '/' || temp_symbol[0] == '\\' || isupper(temp_symbol[0]) || isdigit(temp_symbol[0])) &&
+		temp_symbol[1] >= '!' && temp_symbol[1] <= '~') {
+
+	    /* Explicit table and symbol. */
+
+	    if (isupper(b->symtab) || isdigit(b->symtab)) {
+	      b->symbol = temp_symbol[1];
+	    } 
+	    else {
+	      b->symtab = temp_symbol[0];
+	      b->symbol = temp_symbol[1];
+	    }
+	  }
+	  else {
+
+	    /* Try to look up by description. */
+	    ok = symbols_code_from_description (b->symtab, temp_symbol, &(b->symtab), &(b->symbol));
+	    if (!ok) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Config file, line %d: Could not find symbol matching %s.\n", line, temp_symbol);
+	    }
+	  }
+	}
+
+/* Check is here because could be using default channel when SENDTO= is not specified. */
+
+	if (b->sendto_type == SENDTO_XMIT) {
+
+	  if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || ! p_audio_config->achan[b->sendto_chan].valid) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan);
+	    return (0);
+	  }
+
+	  if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || 
+	       strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || 
+	       strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) {
+
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); 
+	    return (0);
+	  }
+	}
+
+	return (1);
+}
+
 /* end config.c */
diff --git a/config.h b/config.h
index 3d9fc7e..4ea2eda 100644
--- a/config.h
+++ b/config.h
@@ -1,154 +1,168 @@
-
-/*----------------------------------------------------------------------------
- * 
- * Name:	config.h
- *
- * Purpose:	
- *
- * Description:	
- *
- *-----------------------------------------------------------------------------*/
-
-
-#ifndef CONFIG_H
-#define CONFIG_H 1
-
-#include "audio.h"		/* for struct audio_s */
-#include "digipeater.h"		/* for struct digi_config_s */
-#include "aprs_tt.h"		/* for struct tt_config_s */
-#include "igate.h"		/* for struct igate_config_s */
-
-/*
- * All the leftovers.
- * This wasn't thought out.  It just happened.
- */
-
-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 {
-
-	int agwpe_port;		/* Port number for the �AGW TCPIP Socket Interface� */
-	int kiss_port;		/* Port number for the �KISS� protocol. */
-	int enable_kiss_pt;	/* Enable pseudo terminal for KISS. */
-				/* Want this to be off by default because it hangs */
-				/* after a while if nothing is reading from other end. */
-
-	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 */
-	int sb_slow_speed;	/* MPH */
-	int sb_slow_rate;	/* seconds */
-	int sb_turn_time;	/* seconds */
-	int sb_turn_angle;	/* degrees */
-	int sb_turn_slope;	/* degrees * MPH */
-
- 			
-	int num_beacons;	/* Number of beacons defined. */
-
-	struct beacon_s {
-
-	  enum beacon_type_e btype;	/* Position or object. */
-
-	  int lineno;		/* Line number from config file for later error messages. */
-
-	  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. */
-
-	  int every;		/* Time between transmissions, seconds. */
-				/* Remains fixed for PBEACON and OBEACON. */
-				/* Dynamically adjusted for TBEACON. */
-
-	  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. */
-
-	  char *via;		/* Path, e.g. "WIDE1-1,WIDE2-1" or NULL. */
-
-	  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. */
-
-	  float power;		/* For PHG. */
-	  float height;
-	  float gain;		/* Original protocol spec was unclear. */
-				/* Addendum 1.1 clarifies it is dBi not dBd. */
-
-	  char dir[3];		/* 1 or 2 of N,E,W,S, or empty for omni. */
-
-	  float freq;		/* MHz. */
-	  float tone;		/* Hz. */
-	  float offset;		/* MHz. */
-	
-	  char *comment;	/* Comment or NULL. */
-
-
-	} beacon[MAX_BEACONS];
-
-};
-
-
-#define MIN_IP_PORT_NUMBER 1024
-#define MAX_IP_PORT_NUMBER 49151
-
-
-#define DEFAULT_AGWPE_PORT 8000		/* Like everyone else. */
-#define DEFAULT_KISS_PORT 8001		/* Above plus 1. */
-
-
-#define DEFAULT_NULLMODEM "COM3"  	/* should be equiv. to /dev/ttyS2 on Cygwin */
-
-
-
-
-extern void config_init (char *fname, struct audio_s *p_modem, 
-			struct digi_config_s *digi_config,
-			struct tt_config_s *p_tt_config,
-			struct igate_config_s *p_igate_config,
-			struct misc_config_s *misc_config);
-
-
-
-#endif /* CONFIG_H */
-
-/* end config.h */
-
-
+
+/*----------------------------------------------------------------------------
+ * 
+ * Name:	config.h
+ *
+ * Purpose:	
+ *
+ * Description:	
+ *
+ *-----------------------------------------------------------------------------*/
+
+
+#ifndef CONFIG_H
+#define CONFIG_H 1
+
+#include "audio.h"		/* for struct audio_s */
+#include "digipeater.h"		/* for struct digi_config_s */
+#include "aprs_tt.h"		/* for struct tt_config_s */
+#include "igate.h"		/* for struct igate_config_s */
+
+/*
+ * All the leftovers.
+ * This wasn't thought out.  It just happened.
+ */
+
+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 {
+
+	int agwpe_port;		/* Port number for the "AGW TCPIP Socket Interface" */
+	int kiss_port;		/* Port number for the "KISS" protocol. */
+	int enable_kiss_pt;	/* Enable pseudo terminal for KISS. */
+				/* Want this to be off by default because it hangs */
+				/* after a while if nothing is reading from other end. */
+
+	char nullmodem[20];	/* Serial port name for our end of the */
+				/* virtual null modem for native Windows apps. */
+
+	char gpsnmea_port[20];	/* Serial port name for reading NMEA sentences from GPS. */
+				/* e.g. COM22, /dev/ttyACM0 */
+
+	char gpsd_host[20];	/* Host for gpsd server. */
+				/* e.g. localhost, 192.168.1.2 */
+
+	int gpsd_port;		/* Port number for gpsd server. */
+				/* Default is  2947. */
+
+				/* e.g. COM22, /dev/ttyACM0 */
+	char nmea_port[20];	/* Serial port name for NMEA communication with GPS */
+				/* receiver and/or mapping application. Change this. */
+
+	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 */
+	int sb_slow_speed;	/* MPH */
+	int sb_slow_rate;	/* seconds */
+	int sb_turn_time;	/* seconds */
+	int sb_turn_angle;	/* degrees */
+	int sb_turn_slope;	/* degrees * MPH */
+
+ 			
+	int num_beacons;	/* Number of beacons defined. */
+
+	struct beacon_s {
+
+	  enum beacon_type_e btype;	/* Position or object. */
+
+	  int lineno;		/* Line number from config file for later error messages. */
+
+	  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. */
+
+	  int every;		/* Time between transmissions, seconds. */
+				/* Remains fixed for PBEACON and OBEACON. */
+				/* Dynamically adjusted for TBEACON. */
+
+	  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. */
+
+	  char *via;		/* Path, e.g. "WIDE1-1,WIDE2-1" or NULL. */
+
+	  char *custom_info;	/* Info part for handcrafted custom beacon. */
+				/* Ignore the rest below if this is set. */
+
+	  char *custom_infocmd;	/* Command to generate info part. */
+				/* Again, other options below are then ignored. */
+
+	  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. */
+
+	  float power;		/* For PHG. */
+	  float height;
+	  float gain;		/* Original protocol spec was unclear. */
+				/* Addendum 1.1 clarifies it is dBi not dBd. */
+
+	  char dir[3];		/* 1 or 2 of N,E,W,S, or empty for omni. */
+
+	  float freq;		/* MHz. */
+	  float tone;		/* Hz. */
+	  float offset;		/* MHz. */
+	
+	  char *comment;	/* Comment or NULL. */
+	  char *commentcmd;	/* Command to append more to Comment or NULL. */
+
+
+	} beacon[MAX_BEACONS];
+
+};
+
+
+#define MIN_IP_PORT_NUMBER 1024
+#define MAX_IP_PORT_NUMBER 49151
+
+
+#define DEFAULT_AGWPE_PORT 8000		/* Like everyone else. */
+#define DEFAULT_KISS_PORT 8001		/* Above plus 1. */
+
+
+#define DEFAULT_NULLMODEM "COM3"  	/* should be equiv. to /dev/ttyS2 on Cygwin */
+
+
+
+
+extern void config_init (char *fname, struct audio_s *p_modem, 
+			struct digi_config_s *digi_config,
+			struct tt_config_s *p_tt_config,
+			struct igate_config_s *p_igate_config,
+			struct misc_config_s *misc_config);
+
+
+
+#endif /* CONFIG_H */
+
+/* end config.h */
+
+
diff --git a/decode_aprs.c b/decode_aprs.c
index e1a474d..b259aa4 100644
--- a/decode_aprs.c
+++ b/decode_aprs.c
@@ -1,4465 +1,4582 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	decode_aprs.c
- *
- * Purpose:	Decode the information part of APRS frame.
- *
- * Description: Present the packet contents in human readable format.
- *		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>
-#include <time.h>
-#include <assert.h>
-#include <stdlib.h>	/* for atof */
-#include <string.h>	/* for strtok */
-#if __WIN32__
-char *strsep(char **stringp, const char *delim);
-#endif
-#include <math.h>	/* for pow */
-#include <ctype.h>	/* for isdigit */
-#include <fcntl.h>
-
-#ifndef _POSIX_C_SOURCE
-#define _POSIX_C_SOURCE 1
-#endif
-#include "regex.h"
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#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
-
-
-
-/* Position & symbol fields common to several message formats. */
-
-typedef struct {
-	  char lat[8];
-	  char sym_table_id;		/* / \ 0-9 A-Z */
-	  char lon[9];
-	  char symbol_code;
-	} position_t;
-
-typedef struct {
-	  char sym_table_id;		/* / \ a-j A-Z */
-					/* "The presence of the leading Symbol Table Identifier */
-					/* instead of a digit indicates that this is a compressed */
-					/* Position Report and not a normal lat/long report." */
-					/* "a-j" is not a typographical error. */
-					/* The first 10 lower case letters represent the overlay */
-					/* characters of 0-9 in the compressed format. */
-
-	  char y[4];			/* Compressed Latitude. */
-	  char x[4];			/* Compressed Longitude. */
-	  char symbol_code;
-	  char c;			/* Course/speed or altitude. */
-	  char s;
-	  char t	;		/* Compression type. */
-	} compressed_position_t;
-
-
-/* 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, int quiet);
-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, int quiet);
-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, int quiet);
-static double get_longitude_9 (char *p, int quiet);
-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);
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	decode_aprs
- *
- * Purpose:	Split APRS packet into separate properties that it contains.
- *
- * Inputs:	pp	- APRS packet object.
- *
- *		quiet	- Suppress error messages.
- *
- * Outputs:	A->	g_symbol_table, g_symbol_code,
- *			g_lat, g_lon, 
- *			g_speed, g_course, g_altitude,
- *			g_comment
- *			... and many others...
- *
- * Major Revisions: 1.1	Reorganized so parts are returned in a structure.
- *			Print function is now called separately.
- *
- *------------------------------------------------------------------*/
-
-void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
-{
-
-	char dest[AX25_MAX_ADDR_LEN];
-	unsigned char *pinfo;
-	int info_len;
-
-
-  	info_len = ax25_get_info (pp, &pinfo);
-
-	memset (A, 0, sizeof (*A));
-
-	A->g_quiet = quiet;
-
-	sprintf (A->g_msg_type, "Unknown message type %c", *pinfo);
-
-	A->g_symbol_table = '/';		/* Default to primary table. */
-	A->g_symbol_code = ' ';		/* What should we have for default symbol? */
-
-	A->g_lat = G_UNKNOWN;
-	A->g_lon = G_UNKNOWN;
-	//strcpy (A->g_maidenhead, "");
-
-	//strcpy (A->g_name, "");
-	A->g_speed = G_UNKNOWN;
-	A->g_course = G_UNKNOWN;
-
-	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, A->g_src);
-	ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
-
-
-	switch (*pinfo) {	/* "DTI" data type identifier. */
-
-	    case '!':		/* Position without timestamp (no APRS messaging). */
-				/* or Ultimeter 2000 WX Station */
-
-	    case '=':		/* Position without timestamp (with APRS messaging). */
-
-	      if (strncmp((char*)pinfo, "!!", 2) == 0)
-	      {
-		aprs_ultimeter (A, (char*)pinfo, info_len);
-	      }
-	      else
-	      {	     
-	        aprs_ll_pos (A, pinfo, info_len);
-	      }
-	      break;
-
-
-	    //case '#':		/* Peet Bros U-II Weather station */
-	    //case '*':		/* Peet Bros U-II Weather station */
-	      //break;
-		
-	    case '$':		/* Raw GPS data or Ultimeter 2000 */
-		
-	      if (strncmp((char*)pinfo, "$ULTW", 5) == 0)
-	      {
-		aprs_ultimeter (A, (char*)pinfo, info_len);
-	      }
-	      else
-	      {
-	        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 (A, pp, pinfo, info_len);
-	      break;
-
-	    case ')':		/* Item. */
-
-	      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 (A, pinfo, info_len);
-	      break;
-
-
-	    case ':':		/* Message */
-
-	      aprs_message (A, pinfo, info_len, quiet);
-	      break;
-
-	    case ';':		/* Object */
-
-	      aprs_object (A, pinfo, info_len);
-	      break;
-
-	    case '<':		/* Station Capabilities */
-
-	      aprs_station_capabilities (A, (char*)pinfo, info_len);
-	      break;
-
-	    case '>':		/* Status Report */
-
-	      aprs_status_report (A, (char*)pinfo, info_len);
-	      break;
-
-	    //case '?':		/* Query */
-	      //break;
-		
-	    case 'T':		/* Telemetry */
-	      aprs_telemetry (A, (char*)pinfo, info_len, quiet);
-	      break;
-
-	    case '_':		/* Positionless Weather Report */
-
-	      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 (A, (char*)pinfo, info_len);
-	      }
-	      else if (strncmp((char*)pinfo, "{mc", 3) == 0) {
-	        aprs_morse_code (A, (char*)pinfo, info_len);
-	      }
-	      else {
-	        //aprs_user_defined (A, pinfo, info_len);
-	      }
-	      break;
-
-	    case 't':		/* Raw touch tone data - NOT PART OF STANDARD */
-				/* Used to convey raw touch tone sequences to */
-				/* to an application that might want to interpret them. */
-				/* Might move into user defined data, above. */
-
-	      aprs_raw_touch_tone (A, (char*)pinfo, info_len);
-	      break;
-
-	    case 'm':		/* Morse Code data - NOT PART OF STANDARD */
-				/* Used by APRStt gateway to put audible responses */
-				/* into the transmit queue.  Could potentially find */
-				/* other uses such as CW ID for station. */
-				/* Might move into user defined data, above. */
-
-	      aprs_morse_code (A, (char*)pinfo, info_len);
-	      break;
-
-	    case '}':		/* third party header */
-
-	      third_party_header (A, (char*)pinfo, info_len);
-	      break;
-
-
-	    //case '\r':		/* CR or LF? */
-	    //case '\n':
-	
-	      //break;
-
-	    default:
-
-	      break;
-	}
-
-
-/*
- * Look in other locations if not found in information field.
- */
-
-	if (A->g_symbol_table == ' ' || A->g_symbol_code == ' ') {
-
-	  symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
-	}
-
-/*
- * Application might be in the destination field for most message types.
- * MIC-E format has part of location in the destination field.
- */
-
-	switch (*pinfo) {	/* "DTI" data type identifier. */
-
-	  case '\'':		/* Old Mic-E Data */
-	  case '`':		/* Current Mic-E Data */
-	    break;
-
-	  default:
-	    decode_tocall (A, dest);
-	    break;
-	}
-	
-} /* end decode_aprs */
-
-
-void decode_aprs_print (decode_aprs_t *A) {
-
-	char stemp[200];
-	//char tmp2[2];
-	double absll;
-	char news;
-	int deg;
-	double min;
-	char s_lat[30];
-	char s_lon[30];
-	int n;
-	char symbol_description[100];
-
-/*
- * First line has:
- * - message type 
- * - object name
- * - symbol
- * - manufacturer/application
- * - mic-e status
- * - power/height/gain, range
- */
-	strcpy (stemp, A->g_msg_type);
-
-	if (strlen(A->g_name) > 0) {
-	  strcat (stemp, ", \"");
-	  strcat (stemp, A->g_name);
-	  strcat (stemp, "\"");
-	}
-
-	symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description);	
-	strcat (stemp, ", ");
-	strcat (stemp, symbol_description);
-
-	if (strlen(A->g_mfr) > 0) {
-	  strcat (stemp, ", ");
-	  strcat (stemp, A->g_mfr);
-	}
-
-	if (strlen(A->g_mic_e_status) > 0) {
-	  strcat (stemp, ", ");
-	  strcat (stemp, A->g_mic_e_status);
-	}
-
-
-	if (A->g_power > 0) {
-	  char phg[100];
-
-	  sprintf (phg, ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity);
-	  strcat (stemp, phg);
-	}
-
-	if (A->g_range > 0) {
-	  char rng[100];
-
-	  sprintf (rng, ", range=%.1f", A->g_range);
-	  strcat (stemp, rng);
-	}
-	text_color_set(DW_COLOR_DECODED);
-	dw_printf("%s\n", stemp);
-
-/*
- * Second line has:
- * - Latitude
- * - Longitude
- * - speed
- * - direction
- * - altitude
- * - frequency
- */
-
-
-/*
- * Convert Maidenhead locator to latitude and longitude.
- * 
- * Any example was checked for each hemihemisphere using
- * http://www.amsat.org/cgi-bin/gridconv
- */
-
-	if (strlen(A->g_maidenhead) > 0) {
-
-	  if (A->g_lat == G_UNKNOWN && A->g_lon == G_UNKNOWN) {
-
-	    ll_from_grid_square (A->g_maidenhead, &(A->g_lat), &(A->g_lon));
-	  }
-
-	  dw_printf("Grid square = %s, ", A->g_maidenhead);
-	}
-
-	strcpy (stemp, "");
-
-	if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) {
-
-// Have location but it is posible one part is invalid.
-
-	  if (A->g_lat != G_UNKNOWN) {
-  
-	    if (A->g_lat >= 0) {
-	      absll = A->g_lat;
-	      news = 'N';
-	    }
-	    else {
-	      absll = - A->g_lat;
-	      news = 'S';
-	    }
-	    deg = (int) absll;
-	    min = (absll - deg) * 60.0;
-	    sprintf (s_lat, "%c %02d%s%07.4f", news, deg, CH_DEGREE, min);
-	  }
-	  else {
-	    strcpy (s_lat, "Invalid Latitude");
-	  }
-
-	  if (A->g_lon != G_UNKNOWN) {
-
-	    if (A->g_lon >= 0) {
-	      absll = A->g_lon;
-	      news = 'E';
-	    }
-	    else {
-	      absll = - A->g_lon;
-	      news = 'W';
-	    }
-	    deg = (int) absll;
-	    min = (absll - deg) * 60.0;
-	    sprintf (s_lon, "%c %03d%s%07.4f", news, deg, CH_DEGREE, min);
-	  }
-	  else {
-	    strcpy (s_lon, "Invalid Longitude");
-	  }	
-
-	  sprintf (stemp, "%s, %s", s_lat, s_lon);
-	}
-
-	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", A->g_speed);
-	  strcat (stemp, spd);
-	};
-
-	if (A->g_course != G_UNKNOWN) {
-	  char cse[20];
-
-	  if (strlen(stemp) > 0) strcat (stemp, ", ");
-	  sprintf (cse, "course %.0f", A->g_course);
-	  strcat (stemp, cse);
-	};
-
-	if (A->g_altitude != G_UNKNOWN) {
-	  char alt[20];
-
-	  if (strlen(stemp) > 0) strcat (stemp, ", ");
-	  sprintf (alt, "alt %.0f ft", A->g_altitude);
-	  strcat (stemp, alt);
-	};
-
-	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);
-	  dw_printf("%s\n", stemp);
-	}
-
-
-/*
- * 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->
- */
-
-	n = strlen(A->g_weather);
-	if (n >= 1 && A->g_weather[n-1] == '\n') {
-	  A->g_weather[n-1] = '\0';
-	  n--;
-	}
-	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 (A->g_comment, -1, 0);
-	  dw_printf("\n");
-
-/*
- * Point out incorrect attempts a degree symbol.
- * 0xb0 is degree in ISO Latin1.
- * To be part of a valid UTF-8 sequence, it would need to be preceded by 11xxxxxx or 10xxxxxx.
- * 0xf8 is degree in Microsoft code page 437.
- * To be part of a valid UTF-8 sequence, it would need to be followed by 10xxxxxx.
- */
-
-	  if ( ! A->g_quiet) {
-
-	    for (j=0; j<n; j++) {
-	      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)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");	    	
-	      }	
-	    }
-	  }	
-	}
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_ll_pos
- *
- * Purpose:	Decode "Lat/Long Position Report - without Timestamp"
- *
- *		Reports without a timestamp can be regarded as real-time.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * 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.
- *
- *		The location can be in either compressed or human-readable form.
- *
- *		When the symbol code is '_' this is a weather report.
- *
- * Examples:	!4309.95NS07307.13W#PHG3320 W2,NY2 Mt Equinox VT k2lm at arrl.net
- *		!4237.14NS07120.83W#
- * 		=4246.40N/07115.15W# {UIV32}
- *
- *		TODO: (?) Special case, DF report when sym table id = '/' and symbol code = '\'.
- *
- * 		=4903.50N/07201.75W\088/036/270/729
- *
- *------------------------------------------------------------------*/
-
-static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) 
-{
-
-	struct aprs_ll_pos_s {
-	  char dti;			/* ! or = */
-	  position_t pos;
-	  char comment[43]; 		/* Start of comment could be data extension(s). */
-	} *p;
-
-	struct aprs_compressed_pos_s {
-	  char dti;			/* ! or = */
-	  compressed_position_t cpos;
-	  char comment[40]; 		/* No data extension allowed for compressed location. */
-	} *q;
-
-
-	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 (A, &(p->pos));
-
-	  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 (A->g_msg_type, "Weather Report");
-	    weather_data (A, p->comment, TRUE);
-	  } 
-	  else {
-	    /* Regular position report. */
-
-	    data_extension_comment (A, p->comment);
-	  }
-	}
-	else					/* Compressed location. */
-	{
-	  decode_compressed_position (A, &(q->cpos));
-
-	  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 (A->g_msg_type, "Weather Report");
-	    weather_data (A, q->comment, FALSE);
-	  } 
-	  else {
-	    /* Regular position report. */
-
-	    process_comment (A, q->comment, -1);
-	  }
-	}
-
-
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_ll_pos_time
- *
- * Purpose:	Decode "Lat/Long Position Report - with Timestamp"
- *
- *		Reports sent with a timestamp might contain very old information.
- *
- *		Otherwise, same as above.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * 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.
- *
- *		The location can be in either compressed or human-readable form.
- *
- *		When the symbol code is '_' this is a weather report.
- *
- * Examples:	@041025z4232.32N/07058.81W_124/000g000t036r000p000P000b10229h65/wx rpt
- * 		@281621z4237.55N/07120.20W_017/002g006t022r000p000P000h85b10195.Dvs
- *		/092345z4903.50N/07201.75W>Test1234
- *
- * 		I think the symbol code of "_" indicates weather report.
- *
- *		(?) Special case, DF report when sym table id = '/' and symbol code = '\'.
- *
- *		@092345z4903.50N/07201.75W\088/036/270/729
- *		/092345z4903.50N/07201.75W\000/000/270/729
- *
- *------------------------------------------------------------------*/
-
-
-
-static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) 
-{
-
-	struct aprs_ll_pos_time_s {
-	  char dti;			/* / or @ */
-	  char time_stamp[7];
-	  position_t pos;
-	  char comment[43]; 		/* First 7 bytes could be data extension. */
-	} *p;
-
-	struct aprs_compressed_pos_time_s {
-	  char dti;			/* / or @ */
-	  char time_stamp[7];
-	  compressed_position_t cpos;
-	  char comment[40]; 		/* No data extension in this case. */
-	} *q;
-
-
-	strcpy (A->g_msg_type, "Position with time");
-
-	time_t ts = 0;
-
-
-	p = (struct aprs_ll_pos_time_s *)info;
-	q = (struct aprs_compressed_pos_time_s *)info;
-	
-
-	if (isdigit((unsigned char)(p->pos.lat[0]))) 		/* Human-readable location. */
-        {
-	  ts = get_timestamp (A, p->time_stamp);
-	  decode_position (A, &(p->pos));
-
-	  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 (A->g_msg_type, "Weather Report");
-	    weather_data (A, p->comment, TRUE);
-	  } 
-	  else {
-	    /* Regular position report. */
-
-	    data_extension_comment (A, p->comment);
-	  }
-	}
-	else					/* Compressed location. */
-	{
-	  ts = get_timestamp (A, p->time_stamp);
-
-	  decode_compressed_position (A, &(q->cpos));
-
-	  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 (A->g_msg_type, "Weather Report");
-	    weather_data (A, q->comment, FALSE);
-	  } 
-	  else {
-	    /* Regular position report. */
-
-	    process_comment (A, q->comment, -1);
-	  }
-	}
-
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_raw_nmea
- *
- * Purpose:	Decode "Raw NMEA Position Report"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	??? TBD
- *
- * Description:	APRS recognizes raw ASCII data strings conforming to the NMEA 0183
- *		Version 2.0 specification, originating from navigation equipment such 
- *		as GPS and LORAN receivers. It is recommended that APRS stations 
- *		interpret at least the following NMEA Received Sentence types:
- *
- *		GGA Global Positioning System Fix Data
- *		GLL Geographic Position, Latitude/Longitude Data
- *		RMC Recommended Minimum Specific GPS/Transit Data
- *		VTG Velocity and Track Data
- *		WPL Way Point Location
- *
- * Examples:	$GPGGA,102705,5157.9762,N,00029.3256,W,1,04,2.0,75.7,M,47.6,M,,*62
- *		$GPGLL,2554.459,N,08020.187,W,154027.281,A
- *		$GPRMC,063909,A,3349.4302,N,11700.3721,W,43.022,89.3,291099,13.6,E*52
- *		$GPVTG,318.7,T,,M,35.1,N,65.0,K*69
- *
- *
- *------------------------------------------------------------------*/
-
-static void nmea_checksum (decode_aprs_t *A, 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) {
-	  if ( ! A->g_quiet) {
-	    text_color_set (DW_COLOR_INFO);
-            dw_printf("Missing GPS checksum.\n");
-	  }
-          return;
-        }
-        if (cs != strtoul(p+1, NULL, 16)) {
-	  if ( ! A->g_quiet) {
-	    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 aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) 
-{
-	char stemp[256];
-	char *ptype;
-	char *next;
-
-
-	strcpy (A->g_msg_type, "Raw NMEA");
-
-	strncpy (stemp, (char *)info, ilen);
-	stemp[ilen] = '\0';
-	nmea_checksum (A, stemp);
-
-	next = stemp;
-	ptype = strsep(&next, ",");
-
-	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 *pquality;		/* Fix Quality: 0=invalid, 1=GPS, 2=DGPS */
-	  char *pnsat;			/* Number of satellites. */
-	  char *phdop;			/* Horizontal dilution of precision. */
-	  char *paltitude;		/* Altitude, meters above mean sea level. */
-   	  char *pm;			/* "M" = meters */
-					/* Various other stuff... */
-
-
-	  ptime = strsep(&next, ",");	
-	  plat = strsep(&next, ",");
-	  pns = strsep(&next, ",");
-	  plon = strsep(&next, ",");
-	  pew = strsep(&next, ",");
-	  pquality = strsep(&next, ",");
-	  pnsat = strsep(&next, ",");
-	  phdop = strsep(&next, ",");
-	  paltitude = strsep(&next, ",");
-	  pm = strsep(&next, ",");
-
-	  /* Process time??? */
-
-	  if (plat != NULL && strlen(plat) > 0) {
-	    A->g_lat = latitude_from_nmea(plat, pns);
-	  }
-	  if (plon != NULL && strlen(plon) > 0) {
-	    A->g_lon = longitude_from_nmea(plon, pew);
-	  }
-	  if (paltitude != NULL && strlen(paltitude) > 0) {
-	    A->g_altitude = DW_METERS_TO_FEET(atof(paltitude));
-	  }
-	}
-	else if (strcmp(ptype, "$GPGLL") == 0)
-	{
-	  char *plat;		/* Latitude */
-	  char *pns;		/* North/South */
-	  char *plon;		/* Longitude */
-	  char *pew;		/* East/West */
-				/* optional Time hhmmss[.sss] */
-				/* optional 'A' for data valid */
-
-	  plat = strsep(&next, ",");
-	  pns = strsep(&next, ",");
-	  plon = strsep(&next, ",");
-	  pew = strsep(&next, ",");
-
-	  if (plat != NULL && strlen(plat) > 0) {
-	    A->g_lat = latitude_from_nmea(plat, pns);
-	  }
-	  if (plon != NULL && strlen(plon) > 0) {
-	    A->g_lon = longitude_from_nmea(plon, pew);
-	  }
-
-	}
-	else if (strcmp(ptype, "$GPRMC") == 0)
-	{
-	  //char *ptime, *pstatus, *plat, *pns, *plon, *pew, *pspeed, *ptrack, *pdate;
-
-	  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, ",");	
-
-	  /* process time ??? date ??? */
-
-	  if (plat != NULL && strlen(plat) > 0) {
-	    A->g_lat = latitude_from_nmea(plat, pns);
-	  }
-	  if (plon != NULL && strlen(plon) > 0) {
-	    A->g_lon = longitude_from_nmea(plon, pew);
-	  }
-	  if (pknots != NULL && strlen(pknots) > 0) {
-	    A->g_speed = DW_KNOTS_TO_MPH(atof(pknots));
-	  }
-	  if (pcourse != NULL && strlen(pcourse) > 0) {
-	    A->g_course = atof(pcourse);
-	  }
-	}
-	else if (strcmp(ptype, "$GPVTG") == 0)
-	{
-
-	  /* Speed and direction but NO location! */
-
-	  char *ptcourse;		/* True course, degrees. */
-	  char *pt;			/* "T" */
-	  char *pmcourse;		/* Magnetic course, degrees. */
-	  char *pm;			/* "M" */
-	  char *pknots;			/* Ground speed, knots. */
-	  char *pn;			/* "N" = Knots */
-	  char *pkmh;			/* Ground speed, km/hr */
-	  char *pk;			/* "K" = Kilometers per hour */
-	  char *pmode;			/* New in NMEA 0183 version 3.0 */
-					/* Mode: A=Autonomous, D=Differential, */
-	
-	  ptcourse = strsep(&next, ",");
-	  pt = strsep(&next, ",");
-	  pmcourse = strsep(&next, ",");
-	  pm = strsep(&next, ",");
-	  pknots = strsep(&next, ",");
-	  pn = strsep(&next, ",");
-	  pkmh = strsep(&next, ",");
-	  pk = strsep(&next, ",");
-	  pmode	 = strsep(&next, ",");	
-
-	  if (pknots != NULL && strlen(pknots) > 0) {
-	    A->g_speed = DW_KNOTS_TO_MPH(atof(pknots));
-	  }
-	  if (ptcourse != NULL && strlen(ptcourse) > 0) {
-	    A->g_course = atof(ptcourse);
-	  }
-
-	}
-	else if (strcmp(ptype, "$GPWPL") == 0)
-	{
-	  //char *plat, *pns, *plon, *pew, *pident;
-
-	  char *plat;			/* Latitude */
-	  char *pns;			/* North/South */
-	  char *plon;			/* Longitude */
-	  char *pew;			/* East/West */
-	  char *pident;			/* Identifier for Waypoint.  rules??? */
-					/* checksum */
-
-	  plat = strsep(&next, ",");
-	  pns = strsep(&next, ",");
-	  plon = strsep(&next, ",");
-	  pew = strsep(&next, ",");
-	  pident = strsep(&next, ",");
-
-	  if (plat != NULL && strlen(plat) > 0) {
-	    A->g_lat = latitude_from_nmea(plat, pns);
-	  }
-	  if (plon != NULL && strlen(plon) > 0) {
-	    A->g_lon = longitude_from_nmea(plon, pew);
-	  }
-
-	  /* do something with identifier? */
-
-	}
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_mic_e
- *
- * Purpose:	Decode MIC-E (also Kenwood D7 & D700) packet.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	
- *
- * Description:	
- *
- *		Destination Address Field � 
- *
- *		The 7-byte Destination Address field contains
- *		the following encoded information:
- *
- *		* The 6 latitude digits.
- *		* A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E
- *		   Message Codes or one of 7 Custom Message Codes or an Emergency
- *		   Message Code.
- *		* The North/South and West/East Indicators.
- *		* The Longitude Offset Indicator.
- *		* The generic APRS digipeater path code.
- *
- *		"Although the destination address appears to be quite unconventional, it is
- *		still a valid AX.25 address, consisting only of printable 7-bit ASCII values."
- *
- * References:	Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt
- *
- *		Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt 
- *		
- * Examples:	`b9Z!4y>/>"4N}Paul's_TH-D7
- *
- * TODO:	Destination SSID can contain generic digipeater path.
- *
- * Bugs:	Doesn't handle ambiguous position.  "space" treated as zero.
- *		Invalid data results in a message but latitude is not set to unknown.
- *
- *------------------------------------------------------------------*/
-
-/* 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 (decode_aprs_t *A, char c, int mask, int *std_msg, int *cust_msg)
-{
-
- 	if (c >= '0' && c <= '9') {
-	  return (c - '0');
-	}
-
-	if (c >= 'A' && c <= 'J') {
-	  *cust_msg |= mask;
-	  return (c - 'A');
-	}
-
-	if (c >= 'P' && c <= 'Y') {
-	  *std_msg |= mask;
-	  return (c - 'P');
-	}
-
-	/* K, L, Z should be converted to space. */
-	/* others are invalid. */
-	/* But caller expects only values 0 - 9. */
-
-	if (c == 'K') {
-	  *cust_msg |= mask;
-	  return (0);
-	}
-
-	if (c == 'L') {
-	  return (0);
-	}
-
-	if (c == 'Z') {
-	  *std_msg |= mask;
-	  return (0);
-	}
-
-	if ( ! A->g_quiet) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("Invalid character \"%c\" in MIC-E destination/latitude.\n", c);
-	}
-
-	return (0);
-}
-
-
-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 ` */
-	  unsigned char lon[3];		/* "d+28", "m+28", "h+28" */
-	  unsigned char speed_course[3];		
-	  char symbol_code;
-	  char sym_table_id;
-	} *p;
-
-	char dest[10];
-	int ch;
-	int n;
-	int offset;
-	int std_msg = 0;
-	int cust_msg = 0;
-	const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" };
-	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 (A->g_msg_type, "MIC-E");
-
-	p = (struct aprs_mic_e_s *)info;
-
-/* Destination is really latitude of form ddmmhh. */
-/* Message codes are buried in the first 3 digits. */
-
-	ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
-
-	A->g_lat = mic_e_digit(A, dest[0], 4, &std_msg, &cust_msg) * 10 + 
-		mic_e_digit(A, dest[1], 2, &std_msg, &cust_msg) +
-		(mic_e_digit(A, dest[2], 1, &std_msg, &cust_msg) * 1000 + 
-		 mic_e_digit(A, dest[3], 0, &std_msg, &cust_msg) * 100 + 
-		 mic_e_digit(A, dest[4], 0, &std_msg, &cust_msg) * 10 + 
-		 mic_e_digit(A, dest[5], 0, &std_msg, &cust_msg)) / 6000.0;
-
-
-/* 4th character of desination indicates north / south. */
-
-	if ((dest[3] >= '0' && dest[3] <= '9') || dest[3] == 'L') {
-	  /* South */
-	  A->g_lat = ( - A->g_lat);
-	}
-	else if (dest[3] >= 'P' && dest[3] <= 'Z') 
-	{
-	  /* North */
-	}
-	else 
-	{
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid MIC-E N/S encoding in 4th character of destination.\n");	  
-	  }
-	}
-
-
-/* Longitude is mostly packed into 3 bytes of message but */
-/* has a couple bits of information in the destination. */
-
-	if ((dest[4] >= '0' && dest[4] <= '9') || dest[4] == 'L') 
-	{
-	  offset = 0;
-	}
-	else if (dest[4] >= 'P' && dest[4] <= 'Z') 
-	{
-	  offset = 1;
-	}
-	else 
-	{
-	  offset = 0;
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid MIC-E Longitude Offset in 5th character of destination.\n");
-	  }
-	}
-
-/* First character of information field is longitude in degrees. */
-/* It is possible for the unprintable DEL character to occur here. */
-
-/* 5th character of desination indicates longitude offset of +100. */
-/* Not quite that simple :-( */
-
-	ch = p->lon[0];
-
-	if (offset && ch >= 118 && ch <= 127) 
-	{
-	    A->g_lon = ch - 118;			/* 0 - 9 degrees */
-	}
-	else if ( ! offset && ch >= 38 && ch <= 127)
-	{
-	    A->g_lon = (ch - 38) + 10;		/* 10 - 99 degrees */
-	}
-	else if (offset && ch >= 108 && ch <= 117)
-	{
-	    A->g_lon = (ch - 108) + 100;		/* 100 - 109 degrees */
-	}
-	else if (offset && ch >= 38 && ch <= 107)
-	{
-	    A->g_lon = (ch - 38) + 110;		/* 110 - 179 degrees */
-	}
-	else 
-	{
-	   A->g_lon = G_UNKNOWN;
-	   if ( ! A->g_quiet) {
-	     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 A->g_longitude minutes. */
-/* These are all printable characters. */
-
-/* 
- * More than once I've see the TH-D72A put <0x1a> here and flip between north and south.
- *
- * WB2OSZ>TRSW1R,WIDE1-1,WIDE2-2:`c0ol!O[/>=<0x0d>
- * N 42 37.1200, W 071 20.8300, 0 MPH, course 151
- *
- * WB2OSZ>TRS7QR,WIDE1-1,WIDE2-2:`v<0x1a>n<0x1c>"P[/>=<0x0d>
- * Invalid character 0x1a for MIC-E Longitude Minutes.
- * S 42 37.1200, Invalid Longitude, 0 MPH, course 252
- *
- * This was direct over the air with no opportunity for a digipeater
- * or anything else to corrupt the message.
- */
-
-	if (A->g_lon != G_UNKNOWN) 
-	{
-	  ch = p->lon[1];
-
-	  if (ch >= 88 && ch <= 97)
-	  {
-	    A->g_lon += (ch - 88) / 60.0;	/* 0 - 9 minutes*/
-	  }
-	  else if (ch >= 38 && ch <= 87)
-	  {
-    	    A->g_lon += ((ch - 38) + 10) / 60.0;	/* 10 - 59 minutes */
-	  }
-	  else {
-	    A->g_lon = G_UNKNOWN;
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Invalid character 0x%02x for MIC-E Longitude Minutes.\n", ch);
-	    }
-	  }
-
-/* Third character of information field is longitude hundredths of minutes. */
-/* There are 100 possible values, from 0 to 99. */
-/* Note that the range includes 4 unprintable control characters and DEL. */
-
-	  if (A->g_lon != G_UNKNOWN) 
-	  {
-	    ch = p->lon[2];
-
-	    if (ch >= 28 && ch <= 127) 
-	    {
-	      A->g_lon += ((ch - 28) + 0) / 6000.0;	/* 0 - 99 hundredths of minutes*/
-	    }
-	    else {
-	      A->g_lon = G_UNKNOWN;
-	      if ( ! A->g_quiet) { 
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf("Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes.\n", ch);
-	      }
-	    }
-	  }
-	}
-
-/* 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 (A->g_lon != G_UNKNOWN) {
-	    A->g_lon = ( - A->g_lon);
-	  }
-	}
-	else 
-	{
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid MIC-E E/W encoding in 6th character of destination.\n");	  
-	  }
-	}
-
-/* Symbol table and codes like everyone else. */
-
-	A->g_symbol_table = p->sym_table_id;
-	A->g_symbol_code = p->symbol_code;
-
-	if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
-		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
-	{
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n");	
-	  }
-	  A->g_symbol_table = '/';
-	}
-
-/* Message type from two 3-bit codes. */
-
-	if (std_msg == 0 && cust_msg == 0) {
-	  strcpy (A->g_mic_e_status, "Emergency");
-	}
-	else if (std_msg == 0 && cust_msg != 0) {
-	  strcpy (A->g_mic_e_status, cust_text[cust_msg]);
-	}
-	else if (std_msg != 0 && cust_msg == 0) {
-	  strcpy (A->g_mic_e_status, std_text[std_msg]);
-	}
-	else {
-	  strcpy (A->g_mic_e_status, "Unknown MIC-E Message Type");
-	}
-
-/* Speed and course from next 3 bytes. */
-
-	n = ((p->speed_course[0] - 28) * 10) + ((p->speed_course[1] - 28) / 10);
-	if (n >= 800) n -= 800;
-
-	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;
-
-	/* Result is 0 for unknown and 1 - 360 where 360 is north. */
-	/* Convert to 0 - 360 and reserved value for unknown. */
-
-	if (n == 0) 
-	  A->g_course = G_UNKNOWN;
-	else if (n == 360)
-	  A->g_course = 0;
-	else
-	  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--;
-
-#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
-
-
-	if (isT(*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-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-1) == '_' && *plast == ')') { strcpy (A->g_mfr, "Yaesu FTM-100D"); pfirst++; plast-=2; }
-	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strcpy (A->g_mfr, "Yaesu FT2D"); pfirst++; plast-=2; }
-
-	  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 (*(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; }
-
-	  // Should Original Mic-E and Kenwood be moved down to here?
-
-	  else if (*pfirst == '`') { strcpy (A->g_mfr, "Mic-Emsg"); pfirst++; plast-=2; }
-	  else if (*pfirst == '\'') { strcpy (A->g_mfr, "McTrackr"); pfirst++; plast-=2; }
-	}
-
-/*
- * An optional altitude is next.
- * It is three base-91 digits followed by "}".
- * The TM-D710A might have encoding bug.  This was observed:
- *
- * KJ4ETP-9>SUUP9Q,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV$n6:>/]"7&}162.475MHz <Knox,TN> clintserman at gmail=
- * N 35 50.9100, W 083 58.0800, 25 MPH, course 230, alt 945 ft, 162.475MHz
- *
- * KJ4ETP-9>SUUP6Y,GRNTOP-3*,WIDE2-1,qAR,KI4HDU-2:`oU~nT >/]<0x9a>xt}162.475MHz <Knox,TN> clintserman at gmail=
- * Invalid character in MIC-E altitude.  Must be in range of '!' to '{'.
- * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 3280843 ft, 162.475MHz
- *
- * KJ4ETP-9>SUUP6Y,N4NEQ-3,K4EGA-1,WIDE2*,qAS,N5CWH-1:`oU~nT >/]?xt}162.475MHz <Knox,TN> clintserman at gmail=
- * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 808497 ft, 162.475MHz
- *
- * KJ4ETP-9>SUUP2W,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV2o"J>/]"7)}162.475MHz <Knox,TN> clintserman at gmail=
- * N 35 50.2700, W 083 58.2200, 35 MPH, course 246, alt 955 ft, 162.475MHz
- * 
- * Note the <0x9a> which is outside of the 7-bit ASCII range.  Clearly very wrong.
- */
-
-	if (plast > pfirst && pfirst[3] == '}') {
-
-	  A->g_altitude = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000);
-
-	  if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2])) 
-	  {
-	    if ( ! A->g_quiet) {
-	      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", A->g_altitude);
-	    }
-	    A->g_altitude = G_UNKNOWN;
-	  }
-	  
-	  pfirst += 4;
-	}
-
-	process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1);
-
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_message
- *
- * Purpose:	Decode "Message Format"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *		quiet	- supress error messages.
- *
- * Outputs:	??? TBD
- *
- * Description:	An APRS message is a text string with a specifed addressee.
- *
- *		It's a lot more complicated with different types of addressees
- *		and replies with acknowledgement or rejection.
- *
- *		Displaying and logging these messages could be useful.
- *
- * Examples:	
- *		
- *
- *------------------------------------------------------------------*/
-
-static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int quiet) 
-{
-
-	struct aprs_message_s {
-	  char dti;			/* : */
-	  char addressee[9];
-	  char colon;			/* : */
-	  char message[73];		/* 0-67 characters for message */
-					/* { followed by 1-5 characters for message number */
-	} *p;
-
-	char addressee[AX25_MAX_ADDR_LEN];
-	int i;
-
-
-	p = (struct aprs_message_s *)info;
-
-	memset (addressee, 0, sizeof(addressee));
-	strncpy (addressee, p->addressee, sizeof(p->addressee));
-
-	/* Trim trailing spaces. */
-	i = strlen(addressee) - 1;
-	while (i >= 0 && addressee[i] == ' ') {
-	  addressee[i--] = '\0';
-	}
-
-	strcpy (A->g_addressee, addressee);
-
-/*
- * 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, quiet);
-	}
-	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, quiet);
-	}
-	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);
-	}
-
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_object
- *
- * Purpose:	Decode "Object Report Format"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * 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.
- *
- *		This can also be a weather report when the symbol id is '_'.
- *
- * Examples:	;WA2PNU   *050457z4051.72N/07325.53W]BBS & FlexNet 145.070 MHz
- *
- *		;ActonEOC *070352z4229.20N/07125.95WoFire, EMS, Police, Heli-pad, Dial 911
- *
- *		;IRLPC494@*012112zI9*n*<ONV0   446325-146IDLE<CR>
- *
- *------------------------------------------------------------------*/
-
-static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) 
-{
-
-	struct aprs_object_s {
-	  char dti;			/* ; */
-	  char name[9];
-	  char live_killed;		/* * for live or _ for killed */
-	  char time_stamp[7];
-	  position_t pos;
-	  char comment[43]; 		/* First 7 bytes could be data extension. */
-	} *p;
-
-	struct aprs_compressed_object_s {
-	  char dti;			/* ; */
-	  char name[9];
-	  char live_killed;		/* * for live or _ for killed */
-	  char time_stamp[7];
-	  compressed_position_t cpos;
-	  char comment[40]; 		/* No data extension in this case. */
-	} *q;
-
-
-	time_t ts = 0;
-	int i;
-
-
-	p = (struct aprs_object_s *)info;
-	q = (struct aprs_compressed_object_s *)info;
-
-	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 (A->g_msg_type, "Object");
-	else if (p->live_killed == '_')
-	  strcpy (A->g_msg_type, "Killed Object");
-	else
-	  strcpy (A->g_msg_type, "Object - invalid live/killed");
-
-	ts = get_timestamp (A, p->time_stamp);
-
-	if (isdigit((unsigned char)(p->pos.lat[0]))) 	/* Human-readable location. */
-        {
-	  decode_position (A, &(p->pos));
-
-	  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 (A->g_msg_type, "Weather Report with Object");
-	    weather_data (A, p->comment, TRUE);
-	  } 
-	  else {
-	    /* Regular object. */
-
-	    data_extension_comment (A, p->comment);
-	  }
-	}
-	else					/* Compressed location. */
-	{
-	  decode_compressed_position (A, &(q->cpos));
-
-	  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 (A->g_msg_type, "Weather Report with Object");
-	    weather_data (A, q->comment, FALSE);
-	  } 
-	  else {
-	    /* Regular position report. */
-
-	    process_comment (A, q->comment, -1);
-	  }
-	}
-
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_item
- *
- * Purpose:	Decode "Item Report Format"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * 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 
- *
- *		-- It doesn't have a time.
- *		-- Name is a VARIABLE length 3 to 9 instead of fixed 9.
- *		-- "live" indicator is ! rather than *
- *
- * Examples:	
- *
- *------------------------------------------------------------------*/
-
-static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) 
-{
-
-	struct aprs_item_s {
-	  char dti;			/* ) */
-	  char name[9];			/* Actually variable length 3 - 9 bytes. */
-	  char live_killed;		/* ! for live or _ for killed */
-	  position_t pos;
-	  char comment[43]; 		/* First 7 bytes could be data extension. */
-	} *p;
-
-	struct aprs_compressed_item_s {
-	  char dti;			/* ) */
-	  char name[9];			/* Actually variable length 3 - 9 bytes. */
-	  char live_killed;		/* ! for live or _ for killed */
-	  compressed_position_t cpos;
-	  char comment[40]; 		/* No data extension in this case. */
-	} *q;
-
-
-	time_t ts = 0;
-	int i;
-	char *ppos;
-
-
-	p = (struct aprs_item_s *)info;
-	q = (struct aprs_compressed_item_s *)info;
-
-	i = 0;
-	while (i < 9 && p->name[i] != '!' && p->name[i] != '_') {
-	  A->g_name[i] = p->name[i];
-	  i++;
-	  A->g_name[i] = '\0';
-	}
-
-	if (p->name[i] == '!')
-	  strcpy (A->g_msg_type, "Item");
-	else if (p->name[i] == '_')
-	  strcpy (A->g_msg_type, "Killed Item");
-	else {
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Item name too long or not followed by ! or _.\n");
-	  }
-	  strcpy (A->g_msg_type, "Object - invalid live/killed");
-	}
-
-	ppos = p->name + i + 1;
- 
-	if (isdigit(*ppos)) 		/* Human-readable location. */
-        {
-	  decode_position (A, (position_t*) ppos);
-
-	  data_extension_comment (A, ppos + sizeof(position_t));
-	}
-	else					/* Compressed location. */
-	{
-	  decode_compressed_position (A, (compressed_position_t*)ppos);
-
-	  process_comment (A, ppos + sizeof(compressed_position_t), -1);
-	}
-
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_station_capabilities
- *
- * Purpose:	Decode "Station Capabilities"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	???
- *
- * Description:	Each capability is a TOKEN or TOKEN=VALUE pair.
- *
- *
- * Example:	<IGATE,MSG_CNT=3,LOC_CNT=49<CR>
- *		
- * Bugs:	Not implemented yet.  Treat whole thing as comment.	
- *
- *------------------------------------------------------------------*/
-
-static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen) 
-{
-
-	strcpy (A->g_msg_type, "Station Capabilities");
-
-	// 	Is process_comment() applicable?
-
-	strcpy (A->g_comment, info+1);
-}
-
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_status_report
- *
- * Purpose:	Decode "Status Report"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	???
- *
- * Description:	There are 3 different formats:
- *
- *		(1)	'>'
- *			7 char - timestamp, DHM z format
- *			0-55 char - status text
- *
- *		(3)	'>'
- *			4 or 6 char - Maidenhead Locator
- *			2 char - symbol table & code
- *			' ' character
- *			0-53 char - status text	
- *
- *		(2)	'>'
- *			0-62 char - status text
- *
- *		
- *		In all cases, Beam heading and ERP can be at the
- *		very end by using '^' and two other characters.
- *		
- *
- * Examples from specification:	
- *		
- *
- *		>Net Control Center without timestamp.
- *		>092345zNet Control Center with timestamp.
- *		>IO91SX/G
- *		>IO91/G
- *		>IO91SX/- My house 		(Note the space at the start of the status text).
- *		>IO91SX/- ^B7 			Meteor Scatter beam heading = 110 degrees, ERP = 490 watts.
- *	
- *------------------------------------------------------------------*/
-
-static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) 
-{
-	struct aprs_status_time_s {
-	  char dti;			/* > */
-	  char ztime[7];		/* Time stamp ddhhmmz */
-	  char comment[55]; 		
-	} *pt;
-
-	struct aprs_status_m4_s {
-	  char dti;			/* > */
-	  char mhead4[4];		/* 4 character Maidenhead locator. */
-	  char sym_table_id;
-	  char symbol_code;
-	  char space;			/* Should be space after symbol code. */
-	  char comment[54]; 		
-	} *pm4;
-
-	struct aprs_status_m6_s {
-	  char dti;			/* > */
-	  char mhead6[6];		/* 6 character Maidenhead locator. */
-	  char sym_table_id;
-	  char symbol_code;
-	  char space;			/* Should be space after symbol code. */
-	  char comment[54]; 		
-	} *pm6;
-
-	struct aprs_status_s {
-	  char dti;			/* > */
-	  char comment[62]; 		
-	} *ps;
-
-
-	strcpy (A->g_msg_type, "Status Report");
-
-	pt = (struct aprs_status_time_s *)info;
-	pm4 = (struct aprs_status_m4_s *)info;
-	pm6 = (struct aprs_status_m6_s *)info;
-	ps = (struct aprs_status_s *)info;
-
-/*
- * Do we have format with time?
- */
-	if (isdigit(pt->ztime[0]) &&
-	    isdigit(pt->ztime[1]) &&
-	    isdigit(pt->ztime[2]) &&
-	    isdigit(pt->ztime[3]) &&
-	    isdigit(pt->ztime[4]) &&
-	    isdigit(pt->ztime[5]) &&
-	    pt->ztime[6] == 'z') {
-
-	  strcpy (A->g_comment, pt->comment);
-	}
-
-/*
- * Do we have format with 6 character Maidenhead locator?
- */
-	else if (get_maidenhead (A, pm6->mhead6) == 6) {
-
-	  strncpy (A->g_maidenhead, pm6->mhead6, 6);
-	  A->g_maidenhead[6] = '\0';
-
-	  A->g_symbol_table = pm6->sym_table_id;
-	  A->g_symbol_code = pm6->symbol_code;
-
-	  if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
-		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
-	  {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      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') {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space);
-	    }	
-	  }
-
-	  strcpy (A->g_comment, pm6->comment);
-	}
-
-/*
- * Do we have format with 4 character Maidenhead locator?
- */
-	else if (get_maidenhead (A, pm4->mhead4) == 4) {
-
-	  strncpy (A->g_maidenhead, pm4->mhead4, 4);
-	  A->g_maidenhead[4] = '\0';
-
-	  A->g_symbol_table = pm4->sym_table_id;
-	  A->g_symbol_code = pm4->symbol_code;
-
-	  if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
-		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
-	  {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      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') {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space);
-	    }
-	  }
-
-	  strcpy (A->g_comment, pm4->comment);
-	}
-
-/*
- * Whole thing is status text.
- */
-	else {
-	  strcpy (A->g_comment, ps->comment);
-	}
-
-
-/*
- * Last 3 characters can represent beam heading and ERP.
- */
-
-	if (strlen(A->g_comment) >= 3) {
-	  char *hp = A->g_comment + strlen(A->g_comment) - 3;
-	
-	  if (*hp == '^') {
-
-	    char h = hp[1];
-	    char p = hp[2];
-	    int beam = -1;
-	    int erp = -1;
-
-	    if (h >= '0' && h <= '9') {
-	      beam = (h - '0') * 10;
-	    }
-	    else if (h >= 'A' && h <= 'Z') {
-	      beam = (h - 'A') * 10 + 100;
-	    }
-
-	    if (p >= '1' && p <= 'K') {
-	      erp = (p - '0') * (p - '0') * 10;
-	    }
-
-	// TODO:  put result somewhere.
-	// could use A->g_directivity and need new variable for erp.
-
-	    *hp = '\0';
-	  }
-	}
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_Telemetry
- *
- * Purpose:	Decode "Telemetry"
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *		quiet	- suppress error messages.
- *
- * Outputs:	???
- *
- * Description:	TBD.
- *
- * Examples from specification:	
- *		
- *
- *		TBD
- *	
- *------------------------------------------------------------------*/
-
-static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet) 
-{
-
-	strcpy (A->g_msg_type, "Telemetry");
-
-	telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, A->g_comment);
-
-
-} /* end aprs_telemetry */
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_raw_touch_tone
- *
- * Purpose:	Decode raw touch tone datA->
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Description:	Touch tone data is converted to a packet format
- *		so it can be conveyed to an application for processing.
- *
- * 		This is not part of the APRS standard.	
- *		
- *------------------------------------------------------------------*/
-
-static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen) 
-{
-
-	strcpy (A->g_msg_type, "Raw Touch Tone Data");
-
-	/* Just copy the info field without the message type. */
-
-	if (*info == '{') 
-	  strcpy (A->g_comment, info+3);
-	else
-	  strcpy (A->g_comment, info+1);
-
-
-} /* end aprs_raw_touch_tone */
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_morse_code
- *
- * Purpose:	Convey message in packet format to be transmitted as 
- *		Morse Code.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Description:	This is not part of the APRS standard.	
- *		
- *------------------------------------------------------------------*/
-
-static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen) 
-{
-
-	strcpy (A->g_msg_type, "Morse Code Data");
-
-	/* Just copy the info field without the message type. */
-
-	if (*info == '{') 
-	  strcpy (A->g_comment, info+3);
-	else
-	  strcpy (A->g_comment, info+1);
-
-
-} /* end aprs_morse_code */
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_ll_pos_time
- *
- * Purpose:	Decode weather report without a position.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	A->g_symbol_table, A->g_symbol_code.
- *
- * Description:	Type identifier '_' is a weather report without a position.
- *
- *------------------------------------------------------------------*/
-
-
-
-static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *info, int ilen) 
-{
-
-	struct aprs_positionless_weather_s {
-	  char dti;			/* _ */
-	  char time_stamp[8];		/* MDHM format */
-	  char comment[99]; 		
-	} *p;
-
-
-	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 (A, p->time_stamp);
-
-	weather_data (A, p->comment, FALSE);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	weather_data
- *
- * Purpose:	Decode weather data in position or object report.
- *
- * Inputs:	info 	- Pointer to first byte after location
- *			  and symbol code.
- *
- *		wind_prefix 	- Expecting leading wind info
- *				  for human-readable location.
- *				  (Currently ignored.  We are very
- *				  forgiving in what is accepted.)
- * TODO: call this context instead and have 3 enumerated values.
- *
- * Global In:	A->g_course	- Wind info for compressed location.
- *		A->g_speed
- *
- * 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 A->g_course and A->g_speed.
- *		Otherwise, for positionless weather data, the 
- *		wind is in the form c999s999.
- *
- * References:	APRS Weather specification comments.
- *		http://aprs.org/aprs11/spec-wx.txt
- *
- *		Weather updates to the spec.
- *		http://aprs.org/aprs12/weather-new.txt
- *
- * Examples:
- *	
- *	_10090556c220s004g005t077r000p000P000h50b09900wRSW
- *	!4903.50N/07201.75W_220/004g005t077r000p000P000h50b09900wRSW
- *	!4903.50N/07201.75W_220/004g005t077r000p000P000h50b.....wRSW
- *	@092345z4903.50N/07201.75W_220/004g005t-07r000p000P000h50b09900wRSW
- *	=/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW
- *	@092345z/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW
- *	;BRENDA   *092345z4903.50N/07201.75W_220/004g005b0990
- *
- *------------------------------------------------------------------*/
-
-static int getwdata (char **wpp, char ch, int dlen, float *val) 
-{
-	char stemp[8];
-	int i;
-
-
-	//dw_printf("debug: getwdata (wp=%p, ch=%c, dlen=%d)\n", *wpp, ch, dlen);
-
-	*val = G_UNKNOWN;
-
-	assert (dlen >= 2 && dlen <= 6);
-
-	if (**wpp != ch) {
-	  /* Not specified element identifier. */
-	  return (0);	
-	}
-	
-	if (strncmp((*wpp)+1, "......", dlen) == 0 || strncmp((*wpp)+1, "      ", dlen) == 0) {
-	  /* Field present, unknown value */
-	  *wpp += 1 + dlen;
-	  return (1); 
-	}
-
-	/* Data field can contain digits, decimal point, leading negative. */
-
-	for (i=1; i<=dlen; i++) {
-	  if ( ! isdigit((*wpp)[i]) && (*wpp)[i] != '.' && (*wpp)[i] != '-' ) {
-	    return(0);
-	  }
-	} 
-
-	strncpy (stemp, (*wpp)+1, dlen);
-	stemp[dlen] = '\0';
-	*val = atof(stemp);
-
-	//dw_printf("debug: getwdata returning %f\n", *val);
-
-	*wpp += 1 + dlen;
-	return (1); 
-}	
-
-static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) 
-{
-	int n;
-	float fval;
-	char *wp = wdata;
-	int keep_going;
-
-	
-	if (wp[3] == '/')
-	{
-	  if (sscanf (wp, "%3d", &n))
-	  {
-	    // Data Extension format.
-	    // Fine point:  Officially, should be values of 001-360.
-	    // "000" or "..." or "   " means unknown. 
-	    // In practice we see do see "000" here.
-	    A->g_course = n;
-	  }
-	  if (sscanf (wp+4, "%3d", &n))
-	  {
-	    A->g_speed = DW_KNOTS_TO_MPH(n);  /* yes, in knots */
-	  }
-	  wp += 7;
-	}
-	else if ( A->g_speed == G_UNKNOWN) {
-
-	  if ( ! getwdata (&wp, 'c', 3, &A->g_course)) {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Didn't find wind direction in form c999.\n");
-	    }
-	  }
-	  if ( ! getwdata (&wp, 's', 3, &A->g_speed)) {	/* MPH here */
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Didn't find wind speed in form s999.\n");
-	    }
-	  }
-	}
-
-// At this point, we should have the wind direction and speed
-// from one of three methods.
-
-	if (A->g_speed != G_UNKNOWN) {
-	  char ctemp[30];
-
-	  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. */
-	A->g_speed = G_UNKNOWN;
-	A->g_course = G_UNKNOWN;
-
-/*
- * After the mandatory wind direction and speed (in 1 of 3 formats), the
- * next two must be in fixed positions:
- * - gust (peak in mph last 5 minutes)
- * - temperature, degrees F, can be negative e.g. -01
- */
-	if (getwdata (&wp, 'g', 3, &fval)) {
-	  if (fval != G_UNKNOWN) {
-	    char ctemp[30];
-	    sprintf (ctemp, ", gust %.0f", fval);
-	    strcat (A->g_weather, ctemp);
-	  }
-	}
-	else {
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Didn't find wind gust in form g999.\n");
-	  }
-	}
-
-	if (getwdata (&wp, 't', 3, &fval)) {
-	  if (fval != G_UNKNOWN) {
-	    char ctemp[30];
-	    sprintf (ctemp, ", temperature %.0f", fval);
-	    strcat (A->g_weather, ctemp);
-	  }
-	}
-	else {
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Didn't find temperature in form t999.\n");
-	  }
-	}
-
-/*
- * Now pick out other optional fields in any order.
- */
-	keep_going = 1;
-	while (keep_going) {
-
-	  if (getwdata (&wp, 'r', 3, &fval)) {	
-
-	/* r = rainfall, 1/100 inch, last hour */
-
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", rain %.2f in last hour", fval / 100.);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'p', 3, &fval)) {	
-
-	/* p = rainfall, 1/100 inch, last 24 hours */
-
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", rain %.2f in last 24 hours", fval / 100.);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'P', 3, &fval)) {	
-
-	/* P = rainfall, 1/100 inch, since midnight */
-
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", rain %.2f since midnight", fval / 100.);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'h', 2, &fval)) {	
-
-	/* h = humidity %, 00 means 100%  */
-
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      if (fval == 0) fval = 100;
-	      sprintf (ctemp, ", humidity %.0f", fval);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'b', 5, &fval)) {	
-
-	/* b = barometric presure (tenths millibars / tenths of hPascal)  */
-	/* Here, display as inches of mercury. */
-
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      fval = DW_MBAR_TO_INHG(fval * 0.1);
-	      sprintf (ctemp, ", barometer %.2f", fval);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'L', 3, &fval)) {	
-
-	/* L = Luminosity, watts/ sq meter, 000-999  */
-	
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", %.0f watts/m^2", fval);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'l', 3, &fval)) {	
-
-	/* l = Luminosity, watts/ sq meter, 1000-1999  */
-	
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", %.0f watts/m^2", fval + 1000);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 's', 3, &fval)) {	
-
-	/* s = Snowfall in last 24 hours, inches  */
-	/* Data can have decimal point so we don't have to worry about scaling. */
-	/* 's' is also used by wind speed but that must be in a fixed */
-	/* position in the message so there is no confusion. */
-	
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", %.1f snow in 24 hours", fval);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 's', 3, &fval)) {	
-
-	/* # = Raw rain counter  */
-	
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", raw rain counter %.f", fval);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-	  else if (getwdata (&wp, 'X', 3, &fval)) {	
-
-	/* X = Nuclear Radiation.  */
-	/* Encoded as two significant digits and order of magnitude */
-	/* like resistor color code. */
-
-// TODO: decode this properly
-	
-	    if (fval != G_UNKNOWN) {
-	      char ctemp[30];
-	      sprintf (ctemp, ", nuclear Radiation %.f", fval);
-	      strcat (A->g_weather, ctemp);
-	    }
-	  }
-
-// TODO: add new flood level, battery voltage, etc.
-
-	  else {
-	    keep_going = 0;
-	  }
-	}
-
-/*
- * We should be left over with:
- * - one character for software.
- * - two to four characters for weather station type.
- * Examples: tU2k, wRSW
- *
- * But few people follow the protocol spec here.  Instead more often we see things like:
- *  sunny/WX
- *  / {UIV32N}
- */
-
-	strcat (A->g_weather, ", \"");
-	strcat (A->g_weather, wp);
-/*
- * Drop any CR / LF character at the end.
- */
-	n = strlen(A->g_weather);
-	if (n >= 1 && A->g_weather[n-1] == '\n') {
-	  A->g_weather[n-1] = '\0';
-	}
-
-	n = strlen(A->g_weather);
-	if (n >= 1 && A->g_weather[n-1] == '\r') {
-	  A->g_weather[n-1] = '\0';
-	}
-
-	strcat (A->g_weather, "\"");
-
-	return;
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	aprs_ultimeter
- *
- * Purpose:	Decode Peet Brothers ULTIMETER Weather Station Info.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	A->g_weather
- *
- * Description:	http://www.peetbros.com/shop/custom.aspx?recid=7 
- *
- * 		There are two different data formats in use.
- *		One begins with $ULTW and is called "Packet Mode."  Example:
- *
- *		$ULTW009400DC00E21B8027730008890200010309001E02100000004C<CR><LF>
- *
- *		The other begins with !! and is called "logging mode."  Example:
- *
- *		!!000000A600B50000----------------001C01D500000017<CR><LF>
- *
- *
- * Bugs:	Implementation is incomplete.
- *		The example shown in the APRS protocol spec has a couple "----"
- *		fields in the $ULTW message.  This should be rewritten to handle
- *		each field separately to deal with missing pieces.
- *
- *------------------------------------------------------------------*/
-
-static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) 
-{
-
-				// Header = $ULTW 
-				// Data Fields 
-	short h_windpeak;	// 1. Wind Speed Peak over last 5 min. (0.1 kph) 
-	short h_wdir;		// 2. Wind Direction of Wind Speed Peak (0-255) 
-	short h_otemp;		// 3. Current Outdoor Temp (0.1 deg F) 
-	short h_totrain;	// 4. Rain Long Term Total (0.01 in.) 
-	short h_baro;		// 5. Current Barometer (0.1 mbar) 
-	short h_barodelta;	// 6. Barometer Delta Value(0.1 mbar) 
-	short h_barocorrl;	// 7. Barometer Corr. Factor(LSW) 
-	short h_barocorrm;	// 8. Barometer Corr. Factor(MSW) 
-	short h_ohumid;		// 9. Current Outdoor Humidity (0.1%) 
-	short h_date;		// 10. Date (day of year) 
-	short h_time;		// 11. Time (minute of day) 
-	short h_raintoday;	// 12. Today's Rain Total (0.01 inches)* 
-	short h_windave;	// 13. 5 Minute Wind Speed Average (0.1kph)* 
-				// Carriage Return & Line Feed
-				// *Some instruments may not include field 13, some may 
-				// not include 12 or 13. 
-				// Total size: 44, 48 or 52 characters (hex digits) + 
-				// header, carriage return and line feed. 
-
-	int n;
-
-	strcpy (A->g_msg_type, "Ultimeter");
-
-	if (*info == '$')
- 	{
-	  n = sscanf (info+5, "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx",
-			&h_windpeak,		
-			&h_wdir,		
-			&h_otemp,
-			&h_totrain,		
-			&h_baro,		
-			&h_barodelta,	
-			&h_barocorrl,	
-			&h_barocorrm,	
-			&h_ohumid,		 
-			&h_date,		
-			&h_time,		
-			&h_raintoday,	 	// not on some models.
-			&h_windave);		// not on some models.
-
-	  if (n >= 11 && n <= 13) {
-
-	    float windpeak, wdir, otemp, baro, ohumid;
-
-	    windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
-	    wdir = (h_wdir & 0xff) * 360. / 256.;
-	    otemp = h_otemp * 0.1;
-	    baro = DW_MBAR_TO_INHG(h_baro * 0.1);
-	    ohumid = h_ohumid * 0.1;
-	  
-	    sprintf (A->g_weather, "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f",
-			windpeak, wdir, otemp, baro, ohumid);
-	  }
-	}
-
-	
-		// Header = !! 
-		// Data Fields 
-		// 1. Wind Speed (0.1 kph) 
-		// 2. Wind Direction (0-255) 
-		// 3. Outdoor Temp (0.1 deg F) 
-		// 4. Rain* Long Term Total (0.01 inches)  
-		// 5. Barometer (0.1 mbar) 	[ can be ---- ]
-		// 6. Indoor Temp (0.1 deg F) 	[ can be ---- ]
-		// 7. Outdoor Humidity (0.1%) 	[ can be ---- ]
-		// 8. Indoor Humidity (0.1%) 	[ can be ---- ]
-		// 9. Date (day of year) 
-		// 10. Time (minute of day) 
-		// 11. Today's Rain Total (0.01 inches)* 
-		// 12. 1 Minute Wind Speed Average (0.1kph)* 
-		// Carriage Return & Line Feed 
-		//
-		// *Some instruments may not include field 12, some may not include 11 or 12. 
-		// Total size: 40, 44 or 48 characters (hex digits) + header, carriage return and line feed
-
-	if (*info == '!')
- 	{
-	  n = sscanf (info+2, "%4hx%4hx%4hx%4hx",
-			&h_windpeak,		
-			&h_wdir,		
-			&h_otemp,
-			&h_totrain);
-
-	  if (n == 4) {
-
-	    float windpeak, wdir, otemp;
-
-	    windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
-	    wdir = (h_wdir & 0xff) * 360. / 256.;
-	    otemp = h_otemp * 0.1;
-	  
-	    sprintf (A->g_weather, "wind %.1f mph, direction %.0f, temperature %.1f\n",
-			windpeak, wdir, otemp);
-	  }
-
-	}
-
-} /* end aprs_ultimeter */
-
-
-/*------------------------------------------------------------------
- *
- * Function:	third_party_header
- *
- * Purpose:	Decode packet from a third party network.
- *
- * Inputs:	info 	- Pointer to Information field.
- *		ilen 	- Information field length.
- *
- * Outputs:	A->g_comment
- *
- * Description:	
- *
- *------------------------------------------------------------------*/
-
-static void third_party_header (decode_aprs_t *A, char *info, int ilen) 
-{
-	int n;
-
-	strcpy (A->g_msg_type, "Third Party Header");
-
-	/* more later? */
-
-} /* end third_party_header */
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	decode_position
- *
- * Purpose:	Decode the position & symbol information common to many message formats.
- *
- * Inputs:	ppos 	- Pointer to position & symbol fields.
- *
- * 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.
- *
- *------------------------------------------------------------------*/
-
-
-static void decode_position (decode_aprs_t *A, position_t *ppos)
-{
-
-	  A->g_lat = get_latitude_8 (ppos->lat, A->g_quiet);
-	  A->g_lon = get_longitude_9 (ppos->lon, A->g_quiet);
-
-	  A->g_symbol_table = ppos->sym_table_id;
-	  A->g_symbol_code = ppos->symbol_code;
-}
-
-/*------------------------------------------------------------------
- *
- * Function:	decode_compressed_position
- *
- * Purpose:	Decode the compressed position & symbol information common to many message formats.
- *
- * Inputs:	ppos 	- Pointer to compressed position & symbol fields.
- *
- * Returns:	A->g_lat
- *		A->g_lon
- *		A->g_symbol_table
- *		A->g_symbol_code
- *
- *		One of the following:
- *			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.
- *
- *		It contains 13 bytes of the format:
- *
- *			symbol table	/, \, or overlay A-Z, a-j is mapped into 0-9
- *
- *			yyyy		Latitude, base 91.
- * 
- *			xxxx		Longitude, base 91.
- *
- *			symbol code
- *
- *			cs		Course/Speed or altitude.
- *
- *			t		Various "type" info.
- *
- *------------------------------------------------------------------*/
-
-
-static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *pcpos)
-{
-	if (isdigit91(pcpos->y[0]) && isdigit91(pcpos->y[1]) && isdigit91(pcpos->y[2]) && isdigit91(pcpos->y[3]))
-	{
-	  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
- 	{
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in compressed latitude.  Must be in range of '!' to '{'.\n");
-	  }
-	  A->g_lat = G_UNKNOWN;
-	}
-	  
-	if (isdigit91(pcpos->x[0]) && isdigit91(pcpos->x[1]) && isdigit91(pcpos->x[2]) && isdigit91(pcpos->x[3]))
-	{
-	  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 
-	{
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in compressed longitude.  Must be in range of '!' to '{'.\n");
-	  }
-	  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. */
-	  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. */
-	  A->g_symbol_table = pcpos->sym_table_id - 'a' + '0';
-	}
-	else {
-	  if ( ! A->g_quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid symbol table id for compressed position.\n");
-	  }
-	  A->g_symbol_table = '/';
-	}
-
-	A->g_symbol_code = pcpos->symbol_code;
-
-	if (pcpos->c == ' ') {
-	  ; /* ignore other two bytes */
-	}
-	else if (((pcpos->t - 33) & 0x18) == 0x10) {
-	  A->g_altitude = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33);
-	}
-	else if (pcpos->c == '{')
-	{
-	  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. */
-	  A->g_course = (pcpos->c - 33) * 4;
-	  A->g_speed = DW_KNOTS_TO_MPH(pow(1.08, pcpos->s - 33) - 1.0);
-	}
-
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	get_latitude_8
- *
- * Purpose:	Convert 8 byte latitude encoding to degrees.
- *
- * Inputs:	plat 	- Pointer to first byte.
- *
- * Returns:	Double precision value in degrees.  Negative for South.
- *
- * Description:	Latitude is expressed as a fixed 8-character field, in degrees 
- *		and decimal minutes (to two decimal places), followed by the 
- *		letter N for north or S for south.
- *		The protocol spec specifies upper case but I've seen lower
- *		case so this will accept either one.
- *		Latitude degrees are in the range 00 to 90. Latitude minutes 
- *		are expressed as whole minutes and hundredths of a minute, 
- *		separated by a decimal point.
- *		For example:
- *		4903.50N is 49 degrees 3 minutes 30 seconds north.
- *		In generic format examples, the latitude is shown as the 8-character 
- *		string ddmm.hhN (i.e. degrees, minutes and hundredths of a minute north).
- *
- * Bug:		We don't properly deal with position ambiguity where trailing
- *		digits might be replaced by spaces.  We simply treat them like zeros.	
- *
- * Errors:	Return G_UNKNOWN for any type of error.
- *
- *		Should probably print an error message.
- *
- *------------------------------------------------------------------*/
-
-double get_latitude_8 (char *p, int quiet)
-{
-	struct lat_s {
-	  unsigned char deg[2];
-	  unsigned char minn[2];
-	  char dot;
-	  unsigned char hmin[2];
-	  char ns;
-	} *plat;
-
-	double result = 0;
-	
-	plat = (void *)p;
-
-	if (isdigit(plat->deg[0]))
-	  result += ((plat->deg[0]) - '0') * 10;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for tens of degrees.\n", plat->deg[0]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plat->deg[1]))
-	  result += ((plat->deg[1]) - '0') * 1;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for degrees.\n", plat->deg[1]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (plat->minn[0] >= '0' || plat->minn[0] <= '5')
-	  result += ((plat->minn[0]) - '0') * (10. / 60.);
-	else if (plat->minn[0] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-5 for tens of minutes.\n", plat->minn[0]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plat->minn[1]))
-	  result += ((plat->minn[1]) - '0') * (1. / 60.);
-	else if (plat->minn[1] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for minutes.\n", plat->minn[1]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (plat->dot != '.') {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Unexpected character \"%c\" found where period expected in latitude.\n", plat->dot);
-	  }
-	  return (G_UNKNOWN);
-	} 
-
-	if (isdigit(plat->hmin[0]))
-	  result += ((plat->hmin[0]) - '0') * (0.1 / 60.);
-	else if (plat->hmin[0] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for tenths of minutes.\n", plat->hmin[0]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plat->hmin[1]))
-	  result += ((plat->hmin[1]) - '0') * (0.01 / 60.);
-	else if (plat->hmin[1] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for hundredths of minutes.\n", plat->hmin[1]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-// The spec requires upper case for hemisphere.  Accept lower case but warn.
-
-	if (plat->ns == 'N') {
-	  return (result);
-        }
-        else if (plat->ns == 'n') {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Warning: Lower case n found for latitude hemisphere.  Specification requires upper case N or S.\n");
-	  }	  
-	  return (result);
-	}
-	else if (plat->ns == 'S') {
-	  return ( - result);
-	}
-	else if (plat->ns == 's') {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Warning: Lower case s found for latitude hemisphere.  Specification requires upper case N or S.\n");	
-	  }  
-	  return ( - result);
-	}
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Error: '%c' found for latitude hemisphere.  Specification requires upper case N or s.\n", plat->ns);	 
-	  } 
-	  return (G_UNKNOWN);	
-	}	
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	get_longitude_9
- *
- * Purpose:	Convert 9 byte longitude encoding to degrees.
- *
- * Inputs:	plat 	- Pointer to first byte.
- *
- * Returns:	Double precision value in degrees.  Negative for West.
- *
- * Description:	Longitude is expressed as a fixed 9-character field, in degrees and 
- *		decimal minutes (to two decimal places), followed by the letter E 
- *		for east or W for west.
- *		Longitude degrees are in the range 000 to 180. Longitude minutes are
- *		expressed as whole minutes and hundredths of a minute, separated by a
- *		decimal point.
- *		For example:
- *		07201.75W is 72 degrees 1 minute 45 seconds west.
- *		In generic format examples, the longitude is shown as the 9-character 
- *		string dddmm.hhW (i.e. degrees, minutes and hundredths of a minute west).
- *
- * Bug:		We don't properly deal with position ambiguity where trailing
- *		digits might be replaced by spaces.  We simply treat them like zeros.	
- *
- * Errors:	Return G_UNKNOWN for any type of error.
- *
- * Example:	
- *
- *------------------------------------------------------------------*/
-
-
-double get_longitude_9 (char *p, int quiet)
-{
-	struct lat_s {
-	  unsigned char deg[3];
-	  unsigned char minn[2];
-	  char dot;
-	  unsigned char hmin[2];
-	  char ew;
-	} *plon;
-
-	double result = 0;
-	
-	plon = (void *)p;
-
-	if (plon->deg[0] == '0' || plon->deg[0] == '1')
-	  result += ((plon->deg[0]) - '0') * 100;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0 or 1 for hundreds of degrees.\n", plon->deg[0]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plon->deg[1]))
-	  result += ((plon->deg[1]) - '0') * 10;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for tens of degrees.\n", plon->deg[1]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plon->deg[2]))
-	  result += ((plon->deg[2]) - '0') * 1;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for degrees.\n", plon->deg[2]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (plon->minn[0] >= '0' || plon->minn[0] <= '5')
-	  result += ((plon->minn[0]) - '0') * (10. / 60.);
-	else if (plon->minn[0] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-5 for tens of minutes.\n", plon->minn[0]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plon->minn[1]))
-	  result += ((plon->minn[1]) - '0') * (1. / 60.);
-	else if (plon->minn[1] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for minutes.\n", plon->minn[1]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (plon->dot != '.') {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Unexpected character \"%c\" found where period expected in longitude.\n", plon->dot);
-	  }
-	  return (G_UNKNOWN);
-	} 
-
-	if (isdigit(plon->hmin[0]))
-	  result += ((plon->hmin[0]) - '0') * (0.1 / 60.);
-	else if (plon->hmin[0] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for tenths of minutes.\n", plon->hmin[0]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-	if (isdigit(plon->hmin[1]))
-	  result += ((plon->hmin[1]) - '0') * (0.01 / 60.);
-	else if (plon->hmin[1] == ' ')
-	  ;
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for hundredths of minutes.\n", plon->hmin[1]);
-	  }
-	  return (G_UNKNOWN);
-	}
-
-// The spec requires upper case for hemisphere.  Accept lower case but warn.
-
-	if (plon->ew == 'E') {
-	  return (result);
-        }
-        else if (plon->ew == 'e') {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Warning: Lower case e found for longitude hemisphere.  Specification requires upper case E or W.\n");
-	  }	  
-	  return (result);
-	}
-	else if (plon->ew == 'W') {
-	  return ( - result);
-	}
-	else if (plon->ew == 'w') {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Warning: Lower case w found for longitude hemisphere.  Specification requires upper case E or W.\n");
-	  }	  
-	  return ( - result);
-	}
-	else {
-	  if ( ! quiet) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("Error: '%c' found for longitude hemisphere.  Specification requires upper case E or W.\n", plon->ew);	
-	  }  
-	  return (G_UNKNOWN);	
-	}		
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	get_timestamp
- *
- * Purpose:	Convert 7 byte timestamp to unix time value.
- *
- * Inputs:	p 	- Pointer to first byte.
- *
- * Returns:	time_t data type. (UTC)
- *
- * Description:	
- *
- *		Day/Hours/Minutes (DHM) format is a fixed 7-character field, consisting of
- *		a 6-digit day/time group followed by a single time indicator character (z or
- *		/). The day/time group consists of a two-digit day-of-the-month (01�31) and
- *		a four-digit time in hours and minutes.
- *		Times can be expressed in zulu (UTC/GMT) or local time. For example:
- *
- *		  092345z is 2345 hours zulu time on the 9th day of the month.
- *		  092345/ is 2345 hours local time on the 9th day of the month.
- *
- *		It is recommended that future APRS implementations only transmit zulu
- *		format on the air.
- *
- *		Note: The time in Status Reports may only be in zulu format.
- *
- *		Hours/Minutes/Seconds (HMS) format is a fixed 7-character field,
- *		consisting of a 6-digit time in hours, minutes and seconds, followed by the h
- *		time-indicator character. For example:
- *
- *		  234517h is 23 hours 45 minutes and 17 seconds zulu.
- *
- *		Note: This format may not be used in Status Reports.
- *
- *		Month/Day/Hours/Minutes (MDHM) format is a fixed 8-character field,
- *		consisting of the month (01�12) and day-of-the-month (01�31), followed by
- *		the time in hours and minutes zulu. For example:
- *
- *		  10092345 is 23 hours 45 minutes zulu on October 9th.
- *
- *		This format is only used in reports from stand-alone �positionless� weather
- *		stations (i.e. reports that do not contain station position information).
- *
- *
- * Bugs:	Local time not implemented yet.
- *		8 character form not implemented yet.
- *
- *		Boundary conditions are not handled properly.
- *		For example, suppose it is 00:00:03 on January 1.
- *		We receive a timestamp of 23:59:58 (which was December 31).
- *		If we simply replace the time, and leave the current date alone,
- *		the result is about a day into the future.
- *
- *
- * Example:	
- *
- *------------------------------------------------------------------*/
-
-
-time_t get_timestamp (decode_aprs_t *A, char *p)
-{
-	struct dhm_s {
-	  char day[2];
-	  char hours[2];
-	  char minutes[2];
-	  char tic;		/* Time indicator character. */
-				/* z = UTC. */
-				/* / = local - not implemented yet. */
-	} *pdhm;
-
-	struct hms_s {
-	  char hours[2];
-	  char minutes[2];
-	  char seconds[2];
-	  char tic;		/* Time indicator character. */
-				/* h = UTC. */
-	} *phms;
-
-	struct tm *ptm;
-
-	time_t ts;
-
-	ts = time(NULL);
-	ptm = gmtime(&ts);
-
-	pdhm = (void *)p;
-	phms = (void *)p;
-
-	if (pdhm->tic == 'z' || pdhm->tic == '/')   /* Wrong! */
-	{
-	  int j;
-
-	  j = (pdhm->day[0] - '0') * 10 + pdhm->day[1] - '0';
-	  //text_color_set(DW_COLOR_DECODED);
-	  //dw_printf("Changing day from %d to %d\n", ptm->tm_mday, j);
-	  ptm->tm_mday = j;
-
-	  j = (pdhm->hours[0] - '0') * 10 + pdhm->hours[1] - '0';
-	  //dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
-	  ptm->tm_hour = j;
-
-	  j = (pdhm->minutes[0] - '0') * 10 + pdhm->minutes[1] - '0';
-	  //dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
-	  ptm->tm_min = j;
-
-	} 
-	else if (phms->tic == 'h') 
-	{
-	  int j;
-
-	  j = (phms->hours[0] - '0') * 10 + phms->hours[1] - '0';
-	  //text_color_set(DW_COLOR_DECODED);
-	  //dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
-	  ptm->tm_hour = j;
-
-	  j = (phms->minutes[0] - '0') * 10 + phms->minutes[1] - '0';
-	  //dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
-	  ptm->tm_min = j;
-
-	  j = (phms->seconds[0] - '0') * 10 + phms->seconds[1] - '0';
-	  //dw_printf("%sChanging seconds from %d to %d\n", ptm->tm_sec, j);
-	  ptm->tm_sec = j;
-	} 
-	
-	return (mktime(ptm));
-}
-
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	get_maidenhead
- *
- * Purpose:	See if we have a maidenhead locator.
- *
- * Inputs:	p 	- Pointer to first byte.
- *
- * Returns:	0 = not found.
- *		4 = possible 4 character locator found.
- *		6 = possible 6 character locator found.
- *
- *		It is not stored anywhere or processed.
- *
- * Description:	
- *
- *		The maidenhead locator system is sometimes used as a more compact, 
- *		and less precise, alternative to numeric latitude and longitude.
- *
- *		It is composed of:
- *			a pair of letters in range A to R.
- *			a pair of digits in range of 0 to 9.
- *			a pair of letters in range of A to X.
- *
- * 		The APRS spec says that all letters must be transmitted in upper case.
- *
- *
- * Examples from APRS spec:	
- *
- *		IO91SX
- *		IO91
- *
- *
- *------------------------------------------------------------------*/
-
-
-int get_maidenhead (decode_aprs_t *A, char *p)
-{
-
-	if (toupper(p[0]) >= 'A' && toupper(p[0]) <= 'R' &&
-	    toupper(p[1]) >= 'A' && toupper(p[1]) <= 'R' &&
-	    isdigit(p[2]) && isdigit(p[3])) {
-
-	  /* We have 4 characters matching the rule. */
-
-	  if (islower(p[0]) || islower(p[1])) {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Warning: Lower case letter in Maidenhead locator.  Specification requires upper case.\n");
-	    }	  
-	  }
-
-	  if (toupper(p[4]) >= 'A' && toupper(p[4]) <= 'X' &&
-	      toupper(p[5]) >= 'A' && toupper(p[5]) <= 'X') {
-
-	    /* We have 6 characters matching the rule. */
-
-	    if (islower(p[4]) || islower(p[5])) {
-	      if ( ! A->g_quiet) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf("Warning: Lower case letter in Maidenhead locator.  Specification requires upper case.\n");	
-	      }	  
-	    }
-	  
-	    return 6;
-	  }
-	
-	  return 4;
-	}
-
-	return 0;
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	data_extension_comment
- *
- * Purpose:	A fixed length 7-byte field may follow APRS position datA->
- *
- * Inputs:	pdext	- Pointer to optional data extension and comment.
- *
- * Returns:	true if a data extension was found.
- *
- * Outputs:	One or more of the following, depending the data found:
- *	
- *			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 
- *
- *			A->g_comment			
- *
- * Description:	
- *
- *
- *
- *------------------------------------------------------------------*/
-
-const char *dir[9] = { "omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N" };
-
-static int data_extension_comment (decode_aprs_t *A, char *pdext)
-{
-	int n;
-
-	if (strlen(pdext) < 7) {
-	  strcpy (A->g_comment, pdext);
-	  return 0;
-	}
-
-/* Tyy/Cxx - Area object descriptor. */
-
-	if (pdext[0] == 'T' &&
-		pdext[3] == '/' &&
-	 	pdext[4] == 'C')
-	{
-	  /* not decoded at this time */
-	  process_comment (A, pdext+7, -1);
-	  return 1;
-	}
-
-/* CSE/SPD */
-/* For a weather station (symbol code _) this is wind. */
-/* For others, it would be course and speed. */
-
-	if (pdext[3] == '/')
-	{
-	  if (sscanf (pdext, "%3d", &n))
-	  {
-	    A->g_course = n;
-	  }
-	  if (sscanf (pdext+4, "%3d", &n))
-	  {
-	    A->g_speed = DW_KNOTS_TO_MPH(n);
-	  }
-
-	  /* Bearing and Number/Range/Quality? */
-
-	  if (pdext[7] == '/' && pdext[11] == '/') 
-	  {
-	    process_comment (A, pdext + 7 + 8, -1);
-	  }
-	  else {
-	    process_comment (A, pdext+7, -1);
-	  }
-	  return 1;
-	}
-
-/* check for Station power, height, gain. */
-
-	if (strncmp(pdext, "PHG", 3) == 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 (A->g_directivity, dir[pdext[6]-'0']);
-	  }
-
-	  process_comment (A, pdext+7, -1);
-	  return 1;
-	}
-
-/* check for precalculated radio range. */
-
-	if (strncmp(pdext, "RNG", 3) == 0)
-	{
-	  if (sscanf (pdext+3, "%4d", &n))
-	  {
-	    A->g_range = n;
-	  }
-	  process_comment (A, pdext+7, -1);
-	  return 1;
-	}
-
-/* DF signal strength,  */
-
-	if (strncmp(pdext, "DFS", 3) == 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 (A->g_directivity, dir[pdext[6]-'0']);
-	  }
-
-	  process_comment (A, pdext+7, -1);
-	  return 1;
-	}
-
-	process_comment (A, pdext, -1);
-	return 0;
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	decode_tocall
- *
- * Purpose:	Extract application from the destination.
- *
- * Inputs:	dest	- Destination address.
- *			Don't care if SSID is present or not.
- *
- * Outputs:	A->g_mfr
- *
- * Description:	For maximum flexibility, we will read the
- *		data file at run time rather than compiling it in.
- *
- *		For the most recent version, download from:
- *
- *		http://www.aprs.org/aprs11/tocalls.txt
- *
- *		Windows version:  File must be in current working directory.
- *
- *		Linux version: Search order is current working directory
- *			then /usr/share/direwolf directory.
- *
- *------------------------------------------------------------------*/
-
-#define MAX_TOCALLS 150
-
-static struct tocalls_s {
-	unsigned char len;
-	char prefix[7];
-	char *description;
-} tocalls[MAX_TOCALLS];
-
-static int num_tocalls = 0;
-
-static int tocall_cmp (const struct tocalls_s *x, const struct tocalls_s *y) 
-{
-	if (x->len != y->len) return (y->len - x->len);
-	return (strcmp(x->prefix, y->prefix));
-}
-
-static void decode_tocall (decode_aprs_t *A, char *dest)
-{
-	FILE *fp;
-	int n;
-	static int first_time = 1;
-	char stuff[100];
-	char *p;
-	char *r;
-
-	//dw_printf("debug: decode_tocall(\"%s\")\n", dest);
-
-/*
- * Extract the calls and descriptions from the file.
- *
- * Use only lines with exactly these formats:
- *
- *       APN          Network nodes, digis, etc
- *	      APWWxx  APRSISCE win32 version
- *	|     |       |
- *	00000000001111111111      	
- *	01234567890123456789...
- *
- * Matching will be with only leading upper case and digits.
- */
-
-// TODO:  Look for this in multiple locations.
-// For example, if application was installed in /usr/local/bin,
-// we might want to put this in /usr/local/share/aprs
-
-// If search strategy changes, be sure to keep symbols_init in sync.
-
-	if (first_time) {
-
-	  fp = fopen("tocalls.txt", "r");
-#ifndef __WIN32__
-	  if (fp == NULL) {
-	    fp = fopen("/usr/share/direwolf/tocalls.txt", "r");
-	  }
-#endif
-	  if (fp != NULL) {
-
-	    while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) {
-	      
-	      p = stuff + strlen(stuff) - 1;
-	      while (p >= stuff && (*p == '\r' || *p == '\n')) {
-	        *p-- = '\0';
-	      }
-
-	      // printf("debug: %s\n", stuff);
-
-	      if (stuff[0] == ' ' && 
-		  stuff[4] == ' ' &&
-		  stuff[5] == ' ' &&
-		  stuff[6] == 'A' && 
-		  stuff[7] == 'P' && 
-		  stuff[12] == ' ' &&
-		  stuff[13] == ' ' ) {
-
-	        p = stuff + 6;
-	        r = tocalls[num_tocalls].prefix;
-	        while (isupper((int)(*p)) || isdigit((int)(*p))) {
-	          *r++ = *p++;
-	        }
-	        *r = '\0';
-	        if (strlen(tocalls[num_tocalls].prefix) > 2) {
-	          tocalls[num_tocalls].description = strdup(stuff+14);
-		  tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
-	          // dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
-
-	          num_tocalls++;
-	        }
-	      }
-	      else if (stuff[0] == ' ' && 
-		  stuff[1] == 'A' && 
-		  stuff[2] == 'P' && 
-		  isupper((int)(stuff[3])) &&
-		  stuff[4] == ' ' &&
-		  stuff[5] == ' ' &&
-		  stuff[6] == ' ' &&
-		  stuff[12] == ' ' &&
-		  stuff[13] == ' ' ) {
-
-	        p = stuff + 1;
-	        r = tocalls[num_tocalls].prefix;
-	        while (isupper((int)(*p)) || isdigit((int)(*p))) {
-	          *r++ = *p++;
-	        }
-	        *r = '\0';
-	        if (strlen(tocalls[num_tocalls].prefix) > 2) {
-	          tocalls[num_tocalls].description = strdup(stuff+14);
-		  tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
-	          // dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
-
-	          num_tocalls++;
-	        }
-	      }
-	    }
-	    fclose(fp);
-
-/*
- * Sort by decreasing length so the search will go
- * from most specific to least specific.
- * Example:  APY350 or APY008 would match those specific
- * models before getting to the more generic APY.
- */
-
-#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);
-#endif
-	  }
-	  else {
-	    if ( ! A->g_quiet) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Warning: Could not open 'tocalls.txt'.\n");
-	      dw_printf("System types in the destination field will not be decoded.\n");
-	    }
-	  }
-
-	
-	  first_time = 0;
-	}
-
-
-	for (n=0; n<num_tocalls; n++) {
-	  if (strncmp(dest, tocalls[n].prefix, tocalls[n].len) == 0) {
-	    strncpy (A->g_mfr, tocalls[n].description, sizeof(A->g_mfr)-1);
-	    A->g_mfr[sizeof(A->g_mfr)-1] = '\0';
-	    return;
-	  }
-	}
-
-} /* 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
- *
- * Purpose:	Extract optional items from the comment.
- *
- * Inputs:	pstart		- Pointer to start of left over information field.
- *
- *		clen		- Length of comment or -1 to take it all.
- *
- * 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.
- *
- *		Except!!!
- *
- *		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 A->g_comment.
- *
- * References:	http://www.aprs.org/info/freqspec.txt
- *
- *			999.999MHz T100 +060	Voice frequency.
- *		
- *		http://www.aprs.org/datum.txt
- *
- *			!DAO!			APRS precision and Datum option.
- *
- *		Protocol reference, end of chaper 6.
- *
- *			/A=123456		Altitude
- *
- *
- *------------------------------------------------------------------*/
-
-/* 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 (decode_aprs_t *A, char *pstart, int clen)
-{
-	static int first_time = 1;
-	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 4
-	regmatch_t match[MAXMATCH];
-	char temp[256];
-	int keep_going;
-
-
-/*
- * No sense in recompiling the patterns and freeing every time.
- */	
-	if (first_time) 
-	{
-/*
- * Frequency must be at the at the beginning.
- * Others can be anywhere in the comment.
- */
-		
-	  //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, &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][!-{ ][!-{ ]|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);
-	  }
-
-	  e = regcomp (&alt_re, "/A=[0-9][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED);
-	  if (e) {
-	    regerror (e, &alt_re, emsg, sizeof(emsg));
-	    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(A->g_comment));
-	  memcpy (A->g_comment, pstart, (size_t)clen);
-	  A->g_comment[clen] = '\0';
-	}
-	else {
-	  strcpy (A->g_comment, pstart);
-	}
-	//dw_printf("\nInitial comment='%s'\n", A->g_comment);
-
-
-/*
- * Look for frequency in the standard format at start of comment.
- * If that fails, try to obtain from object name.
- */
-
-	if (regexec (&std_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) 
-	{
-	  char sftemp[30];
-	  char smtemp[10];
-
-          //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) {
-	    if ( ! A->g_quiet) {
-	      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) {
-	      if ( ! A->g_quiet) {
-	        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;
-
-	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
-
-	    A->g_offset = 10 * atoi(sttemp);
-
-	    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.
- *
- * 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, A->g_comment, MAXMATCH, match, 0) == 0) 
-	{
-
-	  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));
-
-
-/*
- * 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:
- *
- *		Lat:	 DD MM.HHa
- *		Lon:	DDD HH.HHo
- */
- 	    if (isdigit(a)) {
-	      A->g_lat += (a - '0') / 60000.0 * sign(A->g_lat);
-	    }
- 	    if (isdigit(o)) {
-	      A->g_lon += (o - '0') / 60000.0 * sign(A->g_lon);
-	    }
-	  }
-	  else if (islower(d)) 
-	  {
-/*
- * This adds almost two extra digits to each like this:
- *
- *		Lat:	 DD MM.HHxx
- *		Lon:	DDD HH.HHxx
- *
- * 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.
- */
-
-/* 
- * 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 (isdigit91(o)) {
-	      A->g_lon += (o - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lon);
-	    }
-	  }
-
-	  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, 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, A->g_comment + match[0].rm_eo);
-
-	  A->g_comment[match[0].rm_eo] = '\0';
-          A->g_altitude = atoi(A->g_comment + match[0].rm_so + 3);
-
-	  strcpy (A->g_comment + match[0].rm_so, temp);
-	}
-
-	//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)) { 
-
-	    if ( ! A->g_quiet) {
-	      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) {
-
-	      if ( ! A->g_quiet) {
-                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) {
-	  if ( ! A->g_quiet) {
-	    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
- */
-
-}
-
-/* end process_comment */
-
-
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	main
- *
- * Purpose:	Main program for standalone test program.
- *
- * Inputs:	stdin for raw data to decode.
- *		This is in the usual display format either from
- *		a TNC, findu.com, aprs.fi, etc.  e.g.
- *
- *		N1EDF-9>T2QT8Y,W1CLA-1,WIDE1*,WIDE2-2,00000:`bSbl!Mv/`"4%}_ <0x0d>
- *
- *		WB2OSZ-1>APN383,qAR,N1EDU-2:!4237.14NS07120.83W#PHG7130Chelmsford, MA
- *
- *
- * Outputs:	stdout
- *
- * Description:	Compile like this to make a standalone test program.
- *
- *		gcc -o decode_aprs -DTEST decode_aprs.c ax25_pad.c	
- *
- *		./decode_aprs < decode_aprs.txt
- *
- *		aprs.fi precedes raw data with a time stamp which you
- *		would need to remove first.
- *
- *		cut -c26-999 tmp/kj4etp-9.txt | decode_aprs.exe
- *
- *
- * Restriction:	MIC-E message type can be problematic because it
- *		it can use unprintable characters in the information field.
- *
- *		Dire Wolf and aprs.fi print it in hexadecimal.  Example:
- *
- *		KB1KTR-8>TR3U6T,KB1KTR-9*,WB2OSZ-1*,WIDE2*,qAR,W1XM:`c1<0x1f>l!t>/>"4^}
- *		                                                       ^^^^^^
- *		                                                       ||||||
- *		What does findu.com do in this case?
- *
- *		ax25_from_text recognizes this representation so it can be used
- *		to decode raw data later.
- *
- * TODO:	To make it more useful,
- *			- Remove any leading timestamp.
- *			- Remove any "qA*" and following from the path.
- *
- *------------------------------------------------------------------*/
-
-#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[]) 
-{
-	char stuff[300];
-	char *p;	
-	packet_t pp;
-
-#if __WIN32__
-
-// Select UTF-8 code page for console output.
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx
-// This is the default I see for windows terminal:  
-// >chcp
-// Active code page: 437
-
-	//Restore on exit? oldcp = GetConsoleOutputCP();
-	SetConsoleOutputCP(CP_UTF8);
-
-#else
-
-/*
- * Default on Raspian & Ubuntu Linux is fine.  Don't know about others.
- *
- * Should we look at LANG environment variable and issue a warning
- * if it doesn't look something like  en_US.UTF-8 ?
- */
-
-#endif	
-	if (argc >= 2) {
-	  if (freopen (argv[1], "r", stdin) == NULL) {
-	    fprintf(stderr, "Can't open %s for read.\n", argv[1]);
-	    exit(1);
-	  }
-	}
-
-	text_color_init(1);
-	text_color_set(DW_COLOR_INFO);
-
-	while (fgets(stuff, sizeof(stuff), stdin) != NULL) 
-        {
-	  p = stuff + strlen(stuff) - 1;
-	  while (p >= stuff && (*p == '\r' || *p == '\n')) {
-	    *p-- = '\0';
-	  }
-
-	  if (strlen(stuff) == 0 || stuff[0] == '#') 
-          {
-	    /* comment or blank line */
-	    text_color_set(DW_COLOR_INFO);
-	    dw_printf("%s\n", stuff);
-	    continue;
-          }
-  	  else 
-	  {
-	    /* Try to process it. */
-
-	    text_color_set(DW_COLOR_REC);
-	    dw_printf("\n%s\n", stuff);	    
-	 
-	    pp = ax25_from_text(stuff, 1);
-	    if (pp != NULL) 
-            {
-	      decode_aprs_t A;
-
-	      // log directory option someday?
-	      decode_aprs (&A, pp, 0);
-
-	      //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 
-	    {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("\n%s\n", "ERROR - Could not parse input!\n");
-    	    }
-	  }
-	}
-	return (0);
-}
-
-#endif /* TEST */
-
-/* end decode_aprs.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	decode_aprs.c
+ *
+ * Purpose:	Decode the information part of APRS frame.
+ *
+ * Description: Present the packet contents in human readable format.
+ *		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>
+#include <time.h>
+#include <assert.h>
+#include <stdlib.h>	/* for atof */
+#include <string.h>	/* for strtok */
+
+#include <math.h>	/* for pow */
+#include <ctype.h>	/* for isdigit */
+#include <fcntl.h>
+
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 1
+#endif
+#include "regex.h"
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "symbols.h"
+#include "latlong.h"
+#include "dwgpsnmea.h"
+#include "decode_aprs.h"
+#include "telemetry.h"
+
+
+#define TRUE 1
+#define FALSE 0
+
+
+
+/* Position & symbol fields common to several message formats. */
+
+typedef struct {
+	  char lat[8];
+	  char sym_table_id;		/* / \ 0-9 A-Z */
+	  char lon[9];
+	  char symbol_code;
+	} position_t;
+
+typedef struct {
+	  char sym_table_id;		/* / \ a-j A-Z */
+					/* "The presence of the leading Symbol Table Identifier */
+					/* instead of a digit indicates that this is a compressed */
+					/* Position Report and not a normal lat/long report." */
+					/* "a-j" is not a typographical error. */
+					/* The first 10 lower case letters represent the overlay */
+					/* characters of 0-9 in the compressed format. */
+
+	  char y[4];			/* Compressed Latitude. */
+	  char x[4];			/* Compressed Longitude. */
+	  char symbol_code;
+	  char c;			/* Course/speed or altitude. */
+	  char s;
+	  char t	;		/* Compression type. */
+	} compressed_position_t;
+
+
+/* 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, int quiet);
+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_general_query (decode_aprs_t *A, char *, int, int quiet);
+static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet);
+static void aprs_telemetry (decode_aprs_t *A, char *, int, int quiet);
+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, int quiet);
+static double get_longitude_9 (char *p, int quiet);
+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);
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	decode_aprs
+ *
+ * Purpose:	Split APRS packet into separate properties that it contains.
+ *
+ * Inputs:	pp	- APRS packet object.
+ *
+ *		quiet	- Suppress error messages.
+ *
+ * Outputs:	A->	g_symbol_table, g_symbol_code,
+ *			g_lat, g_lon, 
+ *			g_speed_mph, g_course, g_altitude_ft,
+ *			g_comment
+ *			... and many others...
+ *
+ * Major Revisions: 1.1	Reorganized so parts are returned in a structure.
+ *			Print function is now called separately.
+ *
+ *------------------------------------------------------------------*/
+
+void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
+{
+
+	char dest[AX25_MAX_ADDR_LEN];
+	unsigned char *pinfo;
+	int info_len;
+
+
+  	info_len = ax25_get_info (pp, &pinfo);
+
+	memset (A, 0, sizeof (*A));
+
+	A->g_quiet = quiet;
+
+	snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown message type %c", *pinfo);
+
+	A->g_symbol_table = '/';	/* Default to primary table. */
+	A->g_symbol_code = ' ';		/* What should we have for default symbol? */
+
+	A->g_lat = G_UNKNOWN;
+	A->g_lon = G_UNKNOWN;
+
+	A->g_speed_mph = G_UNKNOWN;
+	A->g_course = G_UNKNOWN;
+
+	A->g_power = G_UNKNOWN;
+	A->g_height = G_UNKNOWN;
+	A->g_gain = G_UNKNOWN;
+
+	A->g_range = G_UNKNOWN;
+	A->g_altitude_ft = G_UNKNOWN;
+	A->g_freq = G_UNKNOWN;
+	A->g_tone = G_UNKNOWN;
+	A->g_dcs = G_UNKNOWN;
+	A->g_offset = G_UNKNOWN;
+
+	A->g_footprint_lat = G_UNKNOWN;
+	A->g_footprint_lon = G_UNKNOWN;
+	A->g_footprint_radius = G_UNKNOWN;
+
+
+
+/*
+ * Extract source and destination including the SSID.
+ */
+	
+	ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
+	ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
+
+
+	switch (*pinfo) {	/* "DTI" data type identifier. */
+
+	    case '!':		/* Position without timestamp (no APRS messaging). */
+				/* or Ultimeter 2000 WX Station */
+
+	    case '=':		/* Position without timestamp (with APRS messaging). */
+
+	      if (strncmp((char*)pinfo, "!!", 2) == 0)
+	      {
+		aprs_ultimeter (A, (char*)pinfo, info_len);
+	      }
+	      else
+	      {	     
+	        aprs_ll_pos (A, pinfo, info_len);
+	      }
+	      break;
+
+
+	    //case '#':		/* Peet Bros U-II Weather station */
+	    //case '*':		/* Peet Bros U-II Weather station */
+	      //break;
+		
+	    case '$':		/* Raw GPS data or Ultimeter 2000 */
+		
+	      if (strncmp((char*)pinfo, "$ULTW", 5) == 0)
+	      {
+		aprs_ultimeter (A, (char*)pinfo, info_len);
+	      }
+	      else
+	      {
+	        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 (A, pp, pinfo, info_len);
+	      break;
+
+	    case ')':		/* Item. */
+
+	      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 (A, pinfo, info_len);
+	      break;
+
+
+	    case ':':		/* Message */
+				/* Directed Station Query */
+
+	      aprs_message (A, pinfo, info_len, quiet);
+	      break;
+
+	    case ';':		/* Object */
+
+	      aprs_object (A, pinfo, info_len);
+	      break;
+
+	    case '<':		/* Station Capabilities */
+
+	      aprs_station_capabilities (A, (char*)pinfo, info_len);
+	      break;
+
+	    case '>':		/* Status Report */
+
+	      aprs_status_report (A, (char*)pinfo, info_len);
+	      break;
+
+	    
+	    case '?':		/* General Query */
+
+	      aprs_general_query (A, (char*)pinfo, info_len, quiet);
+	      break;
+		
+	    case 'T':		/* Telemetry */
+
+	      aprs_telemetry (A, (char*)pinfo, info_len, quiet);
+	      break;
+
+	    case '_':		/* Positionless Weather Report */
+
+	      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 (A, (char*)pinfo, info_len);
+	      }
+	      else if (strncmp((char*)pinfo, "{mc", 3) == 0) {
+	        aprs_morse_code (A, (char*)pinfo, info_len);
+	      }
+	      else {
+	        //aprs_user_defined (A, pinfo, info_len);
+	      }
+	      break;
+
+	    case 't':		/* Raw touch tone data - NOT PART OF STANDARD */
+				/* Used to convey raw touch tone sequences to */
+				/* to an application that might want to interpret them. */
+				/* Might move into user defined data, above. */
+
+	      aprs_raw_touch_tone (A, (char*)pinfo, info_len);
+	      break;
+
+	    case 'm':		/* Morse Code data - NOT PART OF STANDARD */
+				/* Used by APRStt gateway to put audible responses */
+				/* into the transmit queue.  Could potentially find */
+				/* other uses such as CW ID for station. */
+				/* Might move into user defined data, above. */
+
+	      aprs_morse_code (A, (char*)pinfo, info_len);
+	      break;
+
+	    case '}':		/* third party header */
+
+	      third_party_header (A, (char*)pinfo, info_len);
+	      break;
+
+
+	    //case '\r':		/* CR or LF? */
+	    //case '\n':
+	
+	      //break;
+
+	    default:
+
+	      break;
+	}
+
+
+/*
+ * Look in other locations if not found in information field.
+ */
+
+	if (A->g_symbol_table == ' ' || A->g_symbol_code == ' ') {
+
+	  symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
+	}
+
+/*
+ * Application might be in the destination field for most message types.
+ * MIC-E format has part of location in the destination field.
+ */
+
+	switch (*pinfo) {	/* "DTI" data type identifier. */
+
+	  case '\'':		/* Old Mic-E Data */
+	  case '`':		/* Current Mic-E Data */
+	    break;
+
+	  default:
+	    decode_tocall (A, dest);
+	    break;
+	}
+	
+} /* end decode_aprs */
+
+
+void decode_aprs_print (decode_aprs_t *A) {
+
+	char stemp[200];
+	//char tmp2[2];
+	double absll;
+	char news;
+	int deg;
+	double min;
+	char s_lat[30];
+	char s_lon[30];
+	int n;
+	char symbol_description[100];
+
+/*
+ * First line has:
+ * - message type 
+ * - object name
+ * - symbol
+ * - manufacturer/application
+ * - mic-e status
+ * - power/height/gain, range
+ */
+	strlcpy (stemp, A->g_msg_type, sizeof(stemp));
+
+	if (strlen(A->g_name) > 0) {
+	  strlcat (stemp, ", \"", sizeof(stemp));
+	  strlcat (stemp, A->g_name, sizeof(stemp));
+	  strlcat (stemp, "\"", sizeof(stemp));
+	}
+
+	if (A->g_symbol_code != ' ') {
+	  symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description, sizeof(symbol_description));	
+	  strlcat (stemp, ", ", sizeof(stemp));
+	  strlcat (stemp, symbol_description, sizeof(stemp));
+	}
+
+	if (strlen(A->g_mfr) > 0) {
+	  strlcat (stemp, ", ", sizeof(stemp));
+	  strlcat (stemp, A->g_mfr, sizeof(stemp));
+	}
+
+	if (strlen(A->g_mic_e_status) > 0) {
+	  strlcat (stemp, ", ", sizeof(stemp));
+	  strlcat (stemp, A->g_mic_e_status, sizeof(stemp));
+	}
+
+
+	if (A->g_power > 0) {
+	  char phg[100];
+
+	  /* Protcol spec doesn't mention whether this is dBd or dBi.  */
+	  /* Clarified later. */
+	  /* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */
+	  /* "The Antenna Gain in the PHG format on page 28 is in dBi." */
+
+	  snprintf (phg, sizeof(phg), ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity);
+	  strlcat (stemp, phg, sizeof(stemp));
+	}
+
+	if (A->g_range > 0) {
+	  char rng[100];
+
+	  snprintf (rng, sizeof(rng), ", range=%.1f", A->g_range);
+	  strlcat (stemp, rng, sizeof(stemp));
+	}
+	text_color_set(DW_COLOR_DECODED);
+	dw_printf("%s\n", stemp);
+
+/*
+ * Second line has:
+ * - Latitude
+ * - Longitude
+ * - speed
+ * - direction
+ * - altitude
+ * - frequency
+ */
+
+
+/*
+ * Convert Maidenhead locator to latitude and longitude.
+ * 
+ * Any example was checked for each hemihemisphere using
+ * http://www.amsat.org/cgi-bin/gridconv
+ */
+
+	if (strlen(A->g_maidenhead) > 0) {
+
+	  if (A->g_lat == G_UNKNOWN && A->g_lon == G_UNKNOWN) {
+
+	    ll_from_grid_square (A->g_maidenhead, &(A->g_lat), &(A->g_lon));
+	  }
+
+	  dw_printf("Grid square = %s, ", A->g_maidenhead);
+	}
+
+	strlcpy (stemp, "", sizeof(stemp));
+
+	if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) {
+
+// Have location but it is posible one part is invalid.
+
+	  if (A->g_lat != G_UNKNOWN) {
+  
+	    if (A->g_lat >= 0) {
+	      absll = A->g_lat;
+	      news = 'N';
+	    }
+	    else {
+	      absll = - A->g_lat;
+	      news = 'S';
+	    }
+	    deg = (int) absll;
+	    min = (absll - deg) * 60.0;
+	    snprintf (s_lat, sizeof(s_lat), "%c %02d%s%07.4f", news, deg, CH_DEGREE, min);
+	  }
+	  else {
+	    strlcpy (s_lat, "Invalid Latitude", sizeof(s_lat));
+	  }
+
+	  if (A->g_lon != G_UNKNOWN) {
+
+	    if (A->g_lon >= 0) {
+	      absll = A->g_lon;
+	      news = 'E';
+	    }
+	    else {
+	      absll = - A->g_lon;
+	      news = 'W';
+	    }
+	    deg = (int) absll;
+	    min = (absll - deg) * 60.0;
+	    snprintf (s_lon, sizeof(s_lon), "%c %03d%s%07.4f", news, deg, CH_DEGREE, min);
+	  }
+	  else {
+	    strlcpy (s_lon, "Invalid Longitude", sizeof(s_lon));
+	  }	
+
+	  snprintf (stemp, sizeof(stemp), "%s, %s", s_lat, s_lon);
+	}
+
+	if (strlen(A->g_aprstt_loc) > 0) {
+	  if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
+	  strlcat (stemp, A->g_aprstt_loc, sizeof(stemp));
+	};
+
+	if (A->g_speed_mph != G_UNKNOWN) {
+	  char spd[20];
+
+	  if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
+	  snprintf (spd, sizeof(spd), "%.0f MPH", A->g_speed_mph);
+	  strlcat (stemp, spd, sizeof(stemp));
+	};
+
+	if (A->g_course != G_UNKNOWN) {
+	  char cse[20];
+
+	  if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
+	  snprintf (cse, sizeof(cse), "course %.0f", A->g_course);
+	  strlcat (stemp, cse, sizeof(stemp));
+	};
+
+	if (A->g_altitude_ft != G_UNKNOWN) {
+	  char alt[20];
+
+	  if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
+	  snprintf (alt, sizeof(alt), "alt %.0f ft", A->g_altitude_ft);
+	  strlcat (stemp, alt, sizeof(stemp));
+	};
+
+	if (A->g_freq != G_UNKNOWN) {
+	  char ftemp[30];
+
+	  snprintf (ftemp, sizeof(ftemp), ", %.3f MHz", A->g_freq);
+	  strlcat (stemp, ftemp, sizeof(stemp));
+	}
+
+	if (A->g_offset != G_UNKNOWN) {
+	  char ftemp[30];
+
+	  if (A->g_offset % 1000 == 0) {
+	    snprintf (ftemp, sizeof(ftemp), ", %+dM", A->g_offset/1000);
+	  }
+	  else {
+	    snprintf (ftemp, sizeof(ftemp), ", %+dk", A->g_offset);
+	  }
+	  strlcat (stemp, ftemp, sizeof(stemp));
+	}
+
+	if (A->g_tone != G_UNKNOWN) {
+	  if (A->g_tone == 0) {
+	    strlcat (stemp, ", no PL", sizeof(stemp));
+	  }
+	  else {
+	    char ftemp[30];
+
+	    snprintf (ftemp, sizeof(ftemp), ", PL %.1f", A->g_tone);
+	    strlcat (stemp, ftemp, sizeof(stemp));
+	  }
+	}
+
+	if (A->g_dcs != G_UNKNOWN) {
+
+	  char ftemp[30];
+
+	  snprintf (ftemp, sizeof(ftemp), ", DCS %03o", A->g_dcs);
+	  strlcat (stemp, ftemp, sizeof(stemp));
+	}
+
+	if (strlen (stemp) > 0) {
+	  text_color_set(DW_COLOR_DECODED);
+	  dw_printf("%s\n", stemp);
+	}
+
+
+/*
+ * 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->
+ */
+
+	n = strlen(A->g_weather);
+	if (n >= 1 && A->g_weather[n-1] == '\n') {
+	  A->g_weather[n-1] = '\0';
+	  n--;
+	}
+	if (n >= 1 && A->g_weather[n-1] == '\r') {
+	  A->g_weather[n-1] = '\0';
+	  n--;
+	}
+	if (n > 0) {  
+	  ax25_safe_print (A->g_weather, -1, 0);
+	  dw_printf("\n");
+	}
+
+
+	if (strlen(A->g_telemetry) > 0) {
+	  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 (A->g_comment, -1, 0);
+	  dw_printf("\n");
+
+/*
+ * Point out incorrect attempts a degree symbol.
+ * 0xb0 is degree in ISO Latin1.
+ * To be part of a valid UTF-8 sequence, it would need to be preceded by 11xxxxxx or 10xxxxxx.
+ * 0xf8 is degree in Microsoft code page 437.
+ * To be part of a valid UTF-8 sequence, it would need to be followed by 10xxxxxx.
+ */
+
+	  if ( ! A->g_quiet) {
+
+	    for (j=0; j<n; j++) {
+	      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)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");	    	
+	      }	
+	    }
+	  }	
+	}
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_ll_pos
+ *
+ * Purpose:	Decode "Lat/Long Position Report - without Timestamp"
+ *
+ *		Reports without a timestamp can be regarded as real-time.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
+ *
+ * Description:	Type identifier '=' has APRS messaging.
+ *		Type identifier '!' does not have APRS messaging.
+ *
+ *		The location can be in either compressed or human-readable form.
+ *
+ *		When the symbol code is '_' this is a weather report.
+ *
+ * Examples:	!4309.95NS07307.13W#PHG3320 W2,NY2 Mt Equinox VT k2lm at arrl.net
+ *		!4237.14NS07120.83W#
+ * 		=4246.40N/07115.15W# {UIV32}
+ *
+ *		TODO: (?) Special case, DF report when sym table id = '/' and symbol code = '\'.
+ *
+ * 		=4903.50N/07201.75W\088/036/270/729
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) 
+{
+
+	struct aprs_ll_pos_s {
+	  char dti;			/* ! or = */
+	  position_t pos;
+	  char comment[43]; 		/* Start of comment could be data extension(s). */
+	} *p;
+
+	struct aprs_compressed_pos_s {
+	  char dti;			/* ! or = */
+	  compressed_position_t cpos;
+	  char comment[40]; 		/* No data extension allowed for compressed location. */
+	} *q;
+
+
+	strlcpy (A->g_msg_type, "Position", sizeof(A->g_msg_type));
+
+	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 (A, &(p->pos));
+
+	  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. */
+
+	    strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+	    weather_data (A, p->comment, TRUE);
+	  } 
+	  else {
+	    /* Regular position report. */
+
+	    data_extension_comment (A, p->comment);
+	  }
+	}
+	else					/* Compressed location. */
+	{
+	  decode_compressed_position (A, &(q->cpos));
+
+	  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. */
+
+	    strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+	    weather_data (A, q->comment, FALSE);
+	  } 
+	  else {
+	    /* Regular position report. */
+
+	    process_comment (A, q->comment, -1);
+	  }
+	}
+
+
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_ll_pos_time
+ *
+ * Purpose:	Decode "Lat/Long Position Report - with Timestamp"
+ *
+ *		Reports sent with a timestamp might contain very old information.
+ *
+ *		Otherwise, same as above.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
+ *
+ * Description:	Type identifier '@' has APRS messaging.
+ *		Type identifier '/' does not have APRS messaging.
+ *
+ *		The location can be in either compressed or human-readable form.
+ *
+ *		When the symbol code is '_' this is a weather report.
+ *
+ * Examples:	@041025z4232.32N/07058.81W_124/000g000t036r000p000P000b10229h65/wx rpt
+ * 		@281621z4237.55N/07120.20W_017/002g006t022r000p000P000h85b10195.Dvs
+ *		/092345z4903.50N/07201.75W>Test1234
+ *
+ * 		I think the symbol code of "_" indicates weather report.
+ *
+ *		(?) Special case, DF report when sym table id = '/' and symbol code = '\'.
+ *
+ *		@092345z4903.50N/07201.75W\088/036/270/729
+ *		/092345z4903.50N/07201.75W\000/000/270/729
+ *
+ *------------------------------------------------------------------*/
+
+
+
+static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) 
+{
+
+	struct aprs_ll_pos_time_s {
+	  char dti;			/* / or @ */
+	  char time_stamp[7];
+	  position_t pos;
+	  char comment[43]; 		/* First 7 bytes could be data extension. */
+	} *p;
+
+	struct aprs_compressed_pos_time_s {
+	  char dti;			/* / or @ */
+	  char time_stamp[7];
+	  compressed_position_t cpos;
+	  char comment[40]; 		/* No data extension in this case. */
+	} *q;
+
+
+	strlcpy (A->g_msg_type, "Position with time", sizeof(A->g_msg_type));
+
+	time_t ts = 0;
+
+
+	p = (struct aprs_ll_pos_time_s *)info;
+	q = (struct aprs_compressed_pos_time_s *)info;
+	
+
+	if (isdigit((unsigned char)(p->pos.lat[0]))) 		/* Human-readable location. */
+        {
+	  ts = get_timestamp (A, p->time_stamp);
+	  decode_position (A, &(p->pos));
+
+	  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. */
+
+	    strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+	    weather_data (A, p->comment, TRUE);
+	  } 
+	  else {
+	    /* Regular position report. */
+
+	    data_extension_comment (A, p->comment);
+	  }
+	}
+	else					/* Compressed location. */
+	{
+	  ts = get_timestamp (A, p->time_stamp);
+
+	  decode_compressed_position (A, &(q->cpos));
+
+	  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. */
+
+	    strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+	    weather_data (A, q->comment, FALSE);
+	  } 
+	  else {
+	    /* Regular position report. */
+
+	    process_comment (A, q->comment, -1);
+	  }
+	}
+
+	(void)(ts);	// suppress 'set but not used' warning.
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_raw_nmea
+ *
+ * Purpose:	Decode "Raw NMEA Position Report"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A-> ...
+ *
+ * Description:	APRS recognizes raw ASCII data strings conforming to the NMEA 0183
+ *		Version 2.0 specification, originating from navigation equipment such 
+ *		as GPS and LORAN receivers. It is recommended that APRS stations 
+ *		interpret at least the following NMEA Received Sentence types:
+ *
+ *		GGA Global Positioning System Fix Data
+ *		GLL Geographic Position, Latitude/Longitude Data
+ *		RMC Recommended Minimum Specific GPS/Transit Data
+ *		VTG Velocity and Track Data
+ *		WPL Way Point Location
+ *
+ *		We presently recognize only RMC and GGA.
+ *
+ * Examples:	$GPGGA,102705,5157.9762,N,00029.3256,W,1,04,2.0,75.7,M,47.6,M,,*62
+ *		$GPGLL,2554.459,N,08020.187,W,154027.281,A
+ *		$GPRMC,063909,A,3349.4302,N,11700.3721,W,43.022,89.3,291099,13.6,E*52
+ *		$GPVTG,318.7,T,,M,35.1,N,65.0,K*69
+ *
+ *------------------------------------------------------------------*/
+
+
+static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) 
+{
+	if (strncmp((char*)info, "$GPRMC,", 7) == 0)
+	{
+	  float speed_knots = G_UNKNOWN;
+
+	  (void) dwgpsnmea_gprmc ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &speed_knots, &(A->g_course));
+	  A->g_speed_mph = DW_KNOTS_TO_MPH(speed_knots);
+	}
+	else if (strncmp((char*)info, "$GPGGA,", 7) == 0)
+	{
+	  float alt_meters = G_UNKNOWN;
+	  int num_sat = 0;
+
+	  (void) dwgpsnmea_gpgga ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &alt_meters, &num_sat);
+	  A->g_altitude_ft = DW_METERS_TO_FEET(alt_meters);
+	}
+
+	// TODO (low): add a few other sentence types.
+
+} /* end aprs_raw_nmea */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_mic_e
+ *
+ * Purpose:	Decode MIC-E (also Kenwood D7 & D700) packet.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	
+ *
+ * Description:	
+ *
+ *		Destination Address Field - 
+ *
+ *		The 7-byte Destination Address field contains
+ *		the following encoded information:
+ *
+ *		* The 6 latitude digits.
+ *		* A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E
+ *		   Message Codes or one of 7 Custom Message Codes or an Emergency
+ *		   Message Code.
+ *		* The North/South and West/East Indicators.
+ *		* The Longitude Offset Indicator.
+ *		* The generic APRS digipeater path code.
+ *
+ *		"Although the destination address appears to be quite unconventional, it is
+ *		still a valid AX.25 address, consisting only of printable 7-bit ASCII values."
+ *
+ * References:	Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt
+ *
+ *		Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt 
+ *		
+ * Examples:	`b9Z!4y>/>"4N}Paul's_TH-D7
+ *
+ * TODO:	Destination SSID can contain generic digipeater path.
+ *
+ * Bugs:	Doesn't handle ambiguous position.  "space" treated as zero.
+ *		Invalid data results in a message but latitude is not set to unknown.
+ *
+ *------------------------------------------------------------------*/
+
+/* 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 (decode_aprs_t *A, char c, int mask, int *std_msg, int *cust_msg)
+{
+
+ 	if (c >= '0' && c <= '9') {
+	  return (c - '0');
+	}
+
+	if (c >= 'A' && c <= 'J') {
+	  *cust_msg |= mask;
+	  return (c - 'A');
+	}
+
+	if (c >= 'P' && c <= 'Y') {
+	  *std_msg |= mask;
+	  return (c - 'P');
+	}
+
+	/* K, L, Z should be converted to space. */
+	/* others are invalid. */
+	/* But caller expects only values 0 - 9. */
+
+	if (c == 'K') {
+	  *cust_msg |= mask;
+	  return (0);
+	}
+
+	if (c == 'L') {
+	  return (0);
+	}
+
+	if (c == 'Z') {
+	  *std_msg |= mask;
+	  return (0);
+	}
+
+	if ( ! A->g_quiet) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Invalid character \"%c\" in MIC-E destination/latitude.\n", c);
+	}
+
+	return (0);
+}
+
+
+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 ` */
+	  unsigned char lon[3];		/* "d+28", "m+28", "h+28" */
+	  unsigned char speed_course[3];		
+	  char symbol_code;
+	  char sym_table_id;
+	} *p;
+
+	char dest[10];
+	int ch;
+	int n;
+	int offset;
+	int std_msg = 0;
+	int cust_msg = 0;
+	const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" };
+	const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" }; 
+	unsigned char *pfirst, *plast;
+
+	strlcpy (A->g_msg_type, "MIC-E", sizeof(A->g_msg_type));
+
+	p = (struct aprs_mic_e_s *)info;
+
+/* Destination is really latitude of form ddmmhh. */
+/* Message codes are buried in the first 3 digits. */
+
+	ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
+
+	A->g_lat = mic_e_digit(A, dest[0], 4, &std_msg, &cust_msg) * 10 + 
+		mic_e_digit(A, dest[1], 2, &std_msg, &cust_msg) +
+		(mic_e_digit(A, dest[2], 1, &std_msg, &cust_msg) * 1000 + 
+		 mic_e_digit(A, dest[3], 0, &std_msg, &cust_msg) * 100 + 
+		 mic_e_digit(A, dest[4], 0, &std_msg, &cust_msg) * 10 + 
+		 mic_e_digit(A, dest[5], 0, &std_msg, &cust_msg)) / 6000.0;
+
+
+/* 4th character of desination indicates north / south. */
+
+	if ((dest[3] >= '0' && dest[3] <= '9') || dest[3] == 'L') {
+	  /* South */
+	  A->g_lat = ( - A->g_lat);
+	}
+	else if (dest[3] >= 'P' && dest[3] <= 'Z') 
+	{
+	  /* North */
+	}
+	else 
+	{
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid MIC-E N/S encoding in 4th character of destination.\n");	  
+	  }
+	}
+
+
+/* Longitude is mostly packed into 3 bytes of message but */
+/* has a couple bits of information in the destination. */
+
+	if ((dest[4] >= '0' && dest[4] <= '9') || dest[4] == 'L') 
+	{
+	  offset = 0;
+	}
+	else if (dest[4] >= 'P' && dest[4] <= 'Z') 
+	{
+	  offset = 1;
+	}
+	else 
+	{
+	  offset = 0;
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid MIC-E Longitude Offset in 5th character of destination.\n");
+	  }
+	}
+
+/* First character of information field is longitude in degrees. */
+/* It is possible for the unprintable DEL character to occur here. */
+
+/* 5th character of desination indicates longitude offset of +100. */
+/* Not quite that simple :-( */
+
+	ch = p->lon[0];
+
+	if (offset && ch >= 118 && ch <= 127) 
+	{
+	    A->g_lon = ch - 118;			/* 0 - 9 degrees */
+	}
+	else if ( ! offset && ch >= 38 && ch <= 127)
+	{
+	    A->g_lon = (ch - 38) + 10;		/* 10 - 99 degrees */
+	}
+	else if (offset && ch >= 108 && ch <= 117)
+	{
+	    A->g_lon = (ch - 108) + 100;		/* 100 - 109 degrees */
+	}
+	else if (offset && ch >= 38 && ch <= 107)
+	{
+	    A->g_lon = (ch - 38) + 110;		/* 110 - 179 degrees */
+	}
+	else 
+	{
+	   A->g_lon = G_UNKNOWN;
+	   if ( ! A->g_quiet) {
+	     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 A->g_longitude minutes. */
+/* These are all printable characters. */
+
+/* 
+ * More than once I've see the TH-D72A put <0x1a> here and flip between north and south.
+ *
+ * WB2OSZ>TRSW1R,WIDE1-1,WIDE2-2:`c0ol!O[/>=<0x0d>
+ * N 42 37.1200, W 071 20.8300, 0 MPH, course 151
+ *
+ * WB2OSZ>TRS7QR,WIDE1-1,WIDE2-2:`v<0x1a>n<0x1c>"P[/>=<0x0d>
+ * Invalid character 0x1a for MIC-E Longitude Minutes.
+ * S 42 37.1200, Invalid Longitude, 0 MPH, course 252
+ *
+ * This was direct over the air with no opportunity for a digipeater
+ * or anything else to corrupt the message.
+ */
+
+	if (A->g_lon != G_UNKNOWN) 
+	{
+	  ch = p->lon[1];
+
+	  if (ch >= 88 && ch <= 97)
+	  {
+	    A->g_lon += (ch - 88) / 60.0;	/* 0 - 9 minutes*/
+	  }
+	  else if (ch >= 38 && ch <= 87)
+	  {
+    	    A->g_lon += ((ch - 38) + 10) / 60.0;	/* 10 - 59 minutes */
+	  }
+	  else {
+	    A->g_lon = G_UNKNOWN;
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Invalid character 0x%02x for MIC-E Longitude Minutes.\n", ch);
+	    }
+	  }
+
+/* Third character of information field is longitude hundredths of minutes. */
+/* There are 100 possible values, from 0 to 99. */
+/* Note that the range includes 4 unprintable control characters and DEL. */
+
+	  if (A->g_lon != G_UNKNOWN) 
+	  {
+	    ch = p->lon[2];
+
+	    if (ch >= 28 && ch <= 127) 
+	    {
+	      A->g_lon += ((ch - 28) + 0) / 6000.0;	/* 0 - 99 hundredths of minutes*/
+	    }
+	    else {
+	      A->g_lon = G_UNKNOWN;
+	      if ( ! A->g_quiet) { 
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf("Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes.\n", ch);
+	      }
+	    }
+	  }
+	}
+
+/* 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 (A->g_lon != G_UNKNOWN) {
+	    A->g_lon = ( - A->g_lon);
+	  }
+	}
+	else 
+	{
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid MIC-E E/W encoding in 6th character of destination.\n");	  
+	  }
+	}
+
+/* Symbol table and codes like everyone else. */
+
+	A->g_symbol_table = p->sym_table_id;
+	A->g_symbol_code = p->symbol_code;
+
+	if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
+		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
+	{
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n");	
+	  }
+	  A->g_symbol_table = '/';
+	}
+
+/* Message type from two 3-bit codes. */
+
+	if (std_msg == 0 && cust_msg == 0) {
+	  strlcpy (A->g_mic_e_status, "Emergency", sizeof(A->g_mic_e_status));
+	}
+	else if (std_msg == 0 && cust_msg != 0) {
+	  strlcpy (A->g_mic_e_status, cust_text[cust_msg], sizeof(A->g_mic_e_status));
+	}
+	else if (std_msg != 0 && cust_msg == 0) {
+	  strlcpy (A->g_mic_e_status, std_text[std_msg], sizeof(A->g_mic_e_status));
+	}
+	else {
+	  strlcpy (A->g_mic_e_status, "Unknown MIC-E Message Type", sizeof(A->g_mic_e_status));
+	}
+
+/* Speed and course from next 3 bytes. */
+
+	n = ((p->speed_course[0] - 28) * 10) + ((p->speed_course[1] - 28) / 10);
+	if (n >= 800) n -= 800;
+
+	A->g_speed_mph = DW_KNOTS_TO_MPH(n);
+
+	n = ((p->speed_course[1] - 28) % 10) * 100 + (p->speed_course[2] - 28);
+	if (n >= 400) n -= 400;
+
+	/* Result is 0 for unknown and 1 - 360 where 360 is north. */
+	/* Convert to 0 - 360 and reserved value for unknown. */
+
+	if (n == 0) 
+	  A->g_course = G_UNKNOWN;
+	else if (n == 360)
+	  A->g_course = 0;
+	else
+	  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. */
+  
+	strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr));
+
+	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--;
+
+#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
+
+
+	if (isT(*pfirst)) {
+	
+	  if (*pfirst == ' ') { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; }
+
+	  else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; }
+	  else if (*pfirst == '>') { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; }
+
+	  else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; }
+	  else if (*pfirst == ']') { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; }
+
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+
+	  else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+
+	  else if (*(plast-1) == '\\') { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '/') { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '^') { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '*') { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*(plast-1) == '~') { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+
+	  // Should Original Mic-E and Kenwood be moved down to here?
+
+	  else if (*pfirst == '`') { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	  else if (*pfirst == '\'') { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+	}
+
+/*
+ * An optional altitude is next.
+ * It is three base-91 digits followed by "}".
+ * The TM-D710A might have encoding bug.  This was observed:
+ *
+ * KJ4ETP-9>SUUP9Q,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV$n6:>/]"7&}162.475MHz <Knox,TN> clintserman at gmail=
+ * N 35 50.9100, W 083 58.0800, 25 MPH, course 230, alt 945 ft, 162.475MHz
+ *
+ * KJ4ETP-9>SUUP6Y,GRNTOP-3*,WIDE2-1,qAR,KI4HDU-2:`oU~nT >/]<0x9a>xt}162.475MHz <Knox,TN> clintserman at gmail=
+ * Invalid character in MIC-E altitude.  Must be in range of '!' to '{'.
+ * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 3280843 ft, 162.475MHz
+ *
+ * KJ4ETP-9>SUUP6Y,N4NEQ-3,K4EGA-1,WIDE2*,qAS,N5CWH-1:`oU~nT >/]?xt}162.475MHz <Knox,TN> clintserman at gmail=
+ * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 808497 ft, 162.475MHz
+ *
+ * KJ4ETP-9>SUUP2W,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV2o"J>/]"7)}162.475MHz <Knox,TN> clintserman at gmail=
+ * N 35 50.2700, W 083 58.2200, 35 MPH, course 246, alt 955 ft, 162.475MHz
+ * 
+ * Note the <0x9a> which is outside of the 7-bit ASCII range.  Clearly very wrong.
+ */
+
+	if (plast > pfirst && pfirst[3] == '}') {
+
+	  A->g_altitude_ft = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000);
+
+	  if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2])) 
+	  {
+	    if ( ! A->g_quiet) {
+	      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", A->g_altitude_ft);
+	    }
+	    A->g_altitude_ft = G_UNKNOWN;
+	  }
+	  
+	  pfirst += 4;
+	}
+
+	process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1);
+
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_message
+ *
+ * Purpose:	Decode "Message Format"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *		quiet	- supress error messages.
+ *
+ * Outputs:	??? TBD
+ *
+ * Description:	An APRS message is a text string with a specifed addressee.
+ *
+ *		It's a lot more complicated with different types of addressees
+ *		and replies with acknowledgement or rejection.
+ *
+ *
+ * Examples:	...
+ *		
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int quiet) 
+{
+
+	struct aprs_message_s {
+	  char dti;			/* : */
+	  char addressee[9];
+	  char colon;			/* : */
+	  char message[73];		/* 0-67 characters for message */
+					/* { followed by 1-5 characters for message number */
+
+					/* If the first chracter is '?' it is a Directed Station Query. */
+	} *p;
+
+	char addressee[AX25_MAX_ADDR_LEN];
+	int i;
+
+	p = (struct aprs_message_s *)info;
+
+	strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type));
+
+	if (ilen < 11) {
+	  if (! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Message must have a minimum of 11 characters for : addressee :\n");
+	  }
+	  return;
+	}
+
+	if (p->colon != ':') {
+	  if (! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Message must begin with : addressee :\n");
+	  }
+	  return;
+	}
+
+	memset (addressee, 0, sizeof(addressee));
+	memcpy (addressee, p->addressee, sizeof(p->addressee));	// copy exactly 9 bytes.
+
+	/* Trim trailing spaces. */
+	i = strlen(addressee) - 1;
+	while (i >= 0 && addressee[i] == ' ') {
+	  addressee[i--] = '\0';
+	}
+
+	strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee));
+
+/*
+ * 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) {
+	  snprintf (A->g_msg_type, sizeof(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) {
+	  snprintf (A->g_msg_type, sizeof(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) {
+	  snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee);
+	  telemetry_coefficents_message (addressee, p->message+5, quiet);
+	}
+	else if (strncmp(p->message,"BITS.",5) == 0) {
+	  snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
+	  telemetry_bit_sense_message (addressee, p->message+5, quiet);
+	}
+
+/*
+ * If first character of message is "?" it is a query directed toward a specific station.
+ */
+
+	else if (p->message[0] == '?') {
+
+	  strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type));
+
+	  aprs_directed_station_query (A, addressee, p->message+1, quiet);
+	}
+	else {
+	  snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message for \"%s\"", addressee);
+
+	  /* No location so don't use  process_comment () */
+
+	  strlcpy (A->g_comment, p->message, sizeof(A->g_comment));
+	}
+
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_object
+ *
+ * Purpose:	Decode "Object Report Format"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
+ *
+ * Description:	Message has a 9 character object name which could be quite different than
+ *		the source station.
+ *
+ *		This can also be a weather report when the symbol id is '_'.
+ *
+ * Examples:	;WA2PNU   *050457z4051.72N/07325.53W]BBS & FlexNet 145.070 MHz
+ *
+ *		;ActonEOC *070352z4229.20N/07125.95WoFire, EMS, Police, Heli-pad, Dial 911
+ *
+ *		;IRLPC494@*012112zI9*n*<ONV0   446325-146IDLE<CR>
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) 
+{
+
+	struct aprs_object_s {
+	  char dti;			/* ; */
+	  char name[9];
+	  char live_killed;		/* * for live or _ for killed */
+	  char time_stamp[7];
+	  position_t pos;
+	  char comment[43]; 		/* First 7 bytes could be data extension. */
+	} *p;
+
+	struct aprs_compressed_object_s {
+	  char dti;			/* ; */
+	  char name[9];
+	  char live_killed;		/* * for live or _ for killed */
+	  char time_stamp[7];
+	  compressed_position_t cpos;
+	  char comment[40]; 		/* No data extension in this case. */
+	} *q;
+
+
+	time_t ts = 0;
+	int i;
+
+
+	p = (struct aprs_object_s *)info;
+	q = (struct aprs_compressed_object_s *)info;
+
+	//assert (sizeof(A->g_name) > sizeof(p->name));
+
+	memset (A->g_name, 0, sizeof(A->g_name));
+	memcpy (A->g_name, p->name, sizeof(p->name));	// copy exactly 9 bytes.
+
+	/* Trim trailing spaces. */
+	i = strlen(A->g_name) - 1;
+	while (i >= 0 && A->g_name[i] == ' ') {
+	  A->g_name[i--] = '\0';
+	}
+
+ 	if (p->live_killed == '*')
+	  strlcpy (A->g_msg_type, "Object", sizeof(A->g_msg_type));
+	else if (p->live_killed == '_')
+	  strlcpy (A->g_msg_type, "Killed Object", sizeof(A->g_msg_type));
+	else
+	  strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type));
+
+	ts = get_timestamp (A, p->time_stamp);
+
+	if (isdigit((unsigned char)(p->pos.lat[0]))) 	/* Human-readable location. */
+        {
+	  decode_position (A, &(p->pos));
+
+	  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. */
+
+	    strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type));
+	    weather_data (A, p->comment, TRUE);
+	  } 
+	  else {
+	    /* Regular object. */
+
+	    data_extension_comment (A, p->comment);
+	  }
+	}
+	else					/* Compressed location. */
+	{
+	  decode_compressed_position (A, &(q->cpos));
+
+	  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. */
+
+	    strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type));
+	    weather_data (A, q->comment, FALSE);
+	  } 
+	  else {
+	    /* Regular position report. */
+
+	    process_comment (A, q->comment, -1);
+	  }
+	}
+
+	(void)(ts);
+
+} /* end aprs_object */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_item
+ *
+ * Purpose:	Decode "Item Report Format"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
+ *
+ * Description:	An "item" is very much like an "object" except 
+ *
+ *		-- It doesn't have a time.
+ *		-- Name is a VARIABLE length 3 to 9 instead of fixed 9.
+ *		-- "live" indicator is ! rather than *
+ *
+ * Examples:	
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) 
+{
+
+	struct aprs_item_s {
+	  char dti;			/* ) */
+	  char name[9];			/* Actually variable length 3 - 9 bytes. */
+					/* DON'T refer to the rest of this structure; */
+					/* the offsets will be wrong! */
+
+	  char live_killed;		/* ! for live or _ for killed */
+	  position_t pos;
+	  char comment[43]; 		/* First 7 bytes could be data extension. */
+	} *p;
+
+	struct aprs_compressed_item_s {
+	  char dti;			/* ) */
+	  char name[9];			/* Actually variable length 3 - 9 bytes. */
+					/* DON'T refer to the rest of this structure; */
+					/* the offsets will be wrong! */
+
+	  char live_killed;		/* ! for live or _ for killed */
+	  compressed_position_t cpos;
+	  char comment[40]; 		/* No data extension in this case. */
+	} *q;
+
+
+	int i;
+	char *ppos;
+
+
+	p = (struct aprs_item_s *)info;
+	q = (struct aprs_compressed_item_s *)info;
+	(void)(q);
+
+	memset (A->g_name, 0, sizeof(A->g_name));
+	i = 0;
+	while (i < 9 && p->name[i] != '!' && p->name[i] != '_') {
+	  A->g_name[i] = p->name[i];
+	  i++;
+	  A->g_name[i] = '\0';
+	}
+
+	if (p->name[i] == '!')
+	  strlcpy (A->g_msg_type, "Item", sizeof(A->g_msg_type));
+	else if (p->name[i] == '_')
+	  strlcpy (A->g_msg_type, "Killed Item", sizeof(A->g_msg_type));
+	else {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Item name too long or not followed by ! or _.\n");
+	  }
+	  strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type));
+	}
+
+	ppos = p->name + i + 1;
+ 
+	if (isdigit(*ppos)) 		/* Human-readable location. */
+        {
+	  decode_position (A, (position_t*) ppos);
+
+	  data_extension_comment (A, ppos + sizeof(position_t));
+	}
+	else					/* Compressed location. */
+	{
+	  decode_compressed_position (A, (compressed_position_t*)ppos);
+
+	  process_comment (A, ppos + sizeof(compressed_position_t), -1);
+	}
+
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_station_capabilities
+ *
+ * Purpose:	Decode "Station Capabilities"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	???
+ *
+ * Description:	Each capability is a TOKEN or TOKEN=VALUE pair.
+ *
+ *
+ * Example:	<IGATE,MSG_CNT=3,LOC_CNT=49<CR>
+ *		
+ * Bugs:	Not implemented yet.  Treat whole thing as comment.	
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen) 
+{
+
+	strlcpy (A->g_msg_type, "Station Capabilities", sizeof(A->g_msg_type));
+
+	// 	process_comment() not applicable here because it 
+	//	extracts information found in certain formats.
+
+	strlcpy (A->g_comment, info+1, sizeof(A->g_comment));
+
+} /* end aprs_station_capabilities */
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_status_report
+ *
+ * Purpose:	Decode "Status Report"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	???
+ *
+ * Description:	There are 3 different formats:
+ *
+ *		(1)	'>'
+ *			7 char - timestamp, DHM z format
+ *			0-55 char - status text
+ *
+ *		(3)	'>'
+ *			4 or 6 char - Maidenhead Locator
+ *			2 char - symbol table & code
+ *			' ' character
+ *			0-53 char - status text	
+ *
+ *		(2)	'>'
+ *			0-62 char - status text
+ *
+ *		
+ *		In all cases, Beam heading and ERP can be at the
+ *		very end by using '^' and two other characters.
+ *		
+ *
+ * Examples from specification:	
+ *		
+ *
+ *		>Net Control Center without timestamp.
+ *		>092345zNet Control Center with timestamp.
+ *		>IO91SX/G
+ *		>IO91/G
+ *		>IO91SX/- My house 		(Note the space at the start of the status text).
+ *		>IO91SX/- ^B7 			Meteor Scatter beam heading = 110 degrees, ERP = 490 watts.
+ *	
+ *------------------------------------------------------------------*/
+
+static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) 
+{
+	struct aprs_status_time_s {
+	  char dti;			/* > */
+	  char ztime[7];		/* Time stamp ddhhmmz */
+	  char comment[55]; 		
+	} *pt;
+
+	struct aprs_status_m4_s {
+	  char dti;			/* > */
+	  char mhead4[4];		/* 4 character Maidenhead locator. */
+	  char sym_table_id;
+	  char symbol_code;
+	  char space;			/* Should be space after symbol code. */
+	  char comment[54]; 		
+	} *pm4;
+
+	struct aprs_status_m6_s {
+	  char dti;			/* > */
+	  char mhead6[6];		/* 6 character Maidenhead locator. */
+	  char sym_table_id;
+	  char symbol_code;
+	  char space;			/* Should be space after symbol code. */
+	  char comment[54]; 		
+	} *pm6;
+
+	struct aprs_status_s {
+	  char dti;			/* > */
+	  char comment[62]; 		
+	} *ps;
+
+
+	strlcpy (A->g_msg_type, "Status Report", sizeof(A->g_msg_type));
+
+	pt = (struct aprs_status_time_s *)info;
+	pm4 = (struct aprs_status_m4_s *)info;
+	pm6 = (struct aprs_status_m6_s *)info;
+	ps = (struct aprs_status_s *)info;
+
+/*
+ * Do we have format with time?
+ */
+	if (isdigit(pt->ztime[0]) &&
+	    isdigit(pt->ztime[1]) &&
+	    isdigit(pt->ztime[2]) &&
+	    isdigit(pt->ztime[3]) &&
+	    isdigit(pt->ztime[4]) &&
+	    isdigit(pt->ztime[5]) &&
+	    pt->ztime[6] == 'z') {
+
+	  // 	process_comment() not applicable here because it 
+	  //	extracts information found in certain formats.
+
+	  strlcpy (A->g_comment, pt->comment, sizeof(A->g_comment));
+	}
+
+/*
+ * Do we have format with 6 character Maidenhead locator?
+ */
+	else if (get_maidenhead (A, pm6->mhead6) == 6) {
+
+	  memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead));
+	  memcpy (A->g_maidenhead, pm6->mhead6, sizeof(pm6->mhead6));
+
+	  A->g_symbol_table = pm6->sym_table_id;
+	  A->g_symbol_code = pm6->symbol_code;
+
+	  if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
+		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
+	  {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      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') {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space);
+	    }	
+	  }
+
+	  // 	process_comment() not applicable here because it 
+	  //	extracts information found in certain formats.
+
+	  strlcpy (A->g_comment, pm6->comment, sizeof(A->g_comment));
+	}
+
+/*
+ * Do we have format with 4 character Maidenhead locator?
+ */
+	else if (get_maidenhead (A, pm4->mhead4) == 4) {
+
+	  memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead));
+	  memcpy (A->g_maidenhead, pm4->mhead4, sizeof(pm4->mhead4));
+
+	  A->g_symbol_table = pm4->sym_table_id;
+	  A->g_symbol_code = pm4->symbol_code;
+
+	  if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' 
+		&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
+	  {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      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') {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space);
+	    }
+	  }
+
+	  // 	process_comment() not applicable here because it 
+	  //	extracts information found in certain formats.
+
+	  strlcpy (A->g_comment, pm4->comment, sizeof(A->g_comment));
+	}
+
+/*
+ * Whole thing is status text.
+ */
+	else {
+	  strlcpy (A->g_comment, ps->comment, sizeof(A->g_comment));
+	}
+
+
+/*
+ * Last 3 characters can represent beam heading and ERP.
+ */
+
+	if (strlen(A->g_comment) >= 3) {
+	  char *hp = A->g_comment + strlen(A->g_comment) - 3;
+	
+	  if (*hp == '^') {
+
+	    char h = hp[1];
+	    char p = hp[2];
+	    int beam = -1;
+	    int erp = -1;
+
+	    if (h >= '0' && h <= '9') {
+	      beam = (h - '0') * 10;
+	    }
+	    else if (h >= 'A' && h <= 'Z') {
+	      beam = (h - 'A') * 10 + 100;
+	    }
+
+	    if (p >= '1' && p <= 'K') {
+	      erp = (p - '0') * (p - '0') * 10;
+	    }
+
+	// TODO (low):  put result somewhere.
+	// could use A->g_directivity and need new variable for erp.
+
+	    *hp = '\0';
+
+	    (void)(beam);
+	    (void)(erp);
+	  }
+	}
+
+} /* end aprs_status_report */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_general_query
+ *
+ * Purpose:	Decode "General Query" for all stations.
+ *
+ * Inputs:	info 	- Pointer to Information field.  First character should be "?".
+ *		ilen 	- Information field length.
+ *		quiet	- suppress error messages.
+ *
+ * Outputs:	A	- Decoded packet structure
+ *				A->g_query_type
+ *				A->g_query_lat		(optional)
+ *				A->g_query_lon		(optional)
+ *				A->g_query_radius	(optional)
+ *
+ * Description:	Formats are:
+ *	
+ *			?query?
+ *			?query?lat,long,radius
+ *
+ *		'query' is one of APRS, IGATE, WX, ...
+ *		optional footprint, in degrees and miles radius, means only
+ *			those in the specified circle should respond.
+ *
+ * Examples from specification, Chapter 15:		
+ *
+ *		?APRS?
+ *		?APRS? 34.02,-117.15,0200
+ *		?IGATE?
+ *	
+ *------------------------------------------------------------------*/
+
+static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet) 
+{
+	char *q2;
+	char *p;
+	char *tok;
+	char stemp[256];		
+	double lat, lon;
+	float radius;
+
+	strlcpy (A->g_msg_type, "General Query", sizeof(A->g_msg_type));
+
+/*
+ * First make a copy because we will modify it while parsing it.
+ */
+
+	strlcpy (stemp, info, sizeof(stemp));
+
+/*
+ * There should be another "?" after the query type.
+ */
+	q2 = strchr(stemp+1, '?');
+	if (q2 == NULL) {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("General Query must have ? after the query type.\n");
+	  }
+	  return; 
+	}
+
+	*q2 = '\0';
+	strlcpy (A->g_query_type, stemp+1, sizeof(A->g_query_type));
+
+// TODO: remove debug
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("DEBUG: General Query type = \"%s\"\n", A->g_query_type);
+
+	p = q2 + 1;
+	if (strlen(p) == 0) {
+	  return;
+	}
+
+/*
+ * Try to extract footprint.
+ * Spec says positive coordinate would be preceded by space
+ * and radius must be exactly 4 digits.  We are more forgiving. 
+ */
+	tok = strsep(&p, ",");
+	if (tok != NULL) {
+	  lat = atof(tok);
+	  tok = strsep(&p, ",");
+	  if (tok != NULL) {
+	    lon = atof(tok);
+	    tok = strsep(&p, ",");
+	    if (tok != NULL) {
+	      radius = atof(tok);
+
+	      if (lat < -90 || lat > 90) {
+	        if ( ! A->g_quiet) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf("Invalid latitude for General Query footprint.\n");
+	        }
+	        return; 
+	      }
+
+	      if (lon < -180 || lon > 180) {
+	        if ( ! A->g_quiet) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf("Invalid longitude for General Query footprint.\n");
+	        }
+	        return; 
+	      }
+
+	      if (radius <= 0 || radius > 9999) {
+	        if ( ! A->g_quiet) {
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf("Invalid radius for General Query footprint.\n");
+	        }
+	        return; 
+	      }
+
+	      A->g_footprint_lat = lat;
+	      A->g_footprint_lon = lon;
+	      A->g_footprint_radius = radius;
+	    }
+	    else {
+	      if ( ! A->g_quiet) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf("Can't get radius for General Query footprint.\n");
+	      }
+	      return;
+	    }
+	  }
+	  else {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Can't get longitude for General Query footprint.\n");
+	    }
+	    return;
+	  }
+	}
+	else {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Can't get latitude for General Query footprint.\n");
+	  }
+	  return;
+	}
+	
+// TODO: remove debug
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf("DEBUG: General Query footprint = %.6f %.6f %.2f\n", lat, lon, radius);
+
+
+} /* end aprs_general_query */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_directed_station_query
+ *
+ * Purpose:	Decode "Directed Station Query" aimed at specific station.
+ *		This is actually a special format of the more general "message."
+ *
+ * Inputs:	addressee	- To whom it is directed.
+ *				  Redundant because it is already in A->addressee.
+ *
+ *		query	 	- What's left over after ":addressee:?" in info part.
+ *
+ *		quiet		- suppress error messages.
+ *
+ * Outputs:	A	- Decoded packet structure
+ *				A->g_query_type
+ *				A->g_query_callsign	(optional)
+ *
+ * Description:	The caller has already removed the :addressee:? part so we are left 
+ *		with a query type of exactly 5 characters and optional "callsign 
+ *		of heard station."
+ *	
+ * Examples from specification, Chapter 15.   Our "query" argument.	
+ *
+ *		:KH2Z     :?APRSD		APRSD
+ *		:KH2Z     :?APRSHVN0QBF     	APRSHVN0QBF
+ *		:KH2Z     :?APRST		APRST
+ *		:KH2Z     :?PING?		PING?
+ *	
+ *		"PING?" contains "?" only to pad it out to exactly 5 characters.
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet)
+{
+	//char query_type[20];		/* Does the query type always need to be exactly 5 characters? */
+					/* If not, how would we know where the extra optional information starts? */
+
+	//char callsign[AX25_MAX_ADDR_LEN];
+
+	//if (strlen(query) < 5) ...
+
+
+}  /* end aprs_directed_station_query */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_Telemetry
+ *
+ * Purpose:	Decode "Telemetry"
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *		quiet	- suppress error messages.
+ *
+ * Outputs:	A->g_telemetry
+ *		A->g_comment
+ *
+ * Description:	TBD.
+ *
+ * Examples from specification:	
+ *		
+ *
+ *		TBD
+ *	
+ *------------------------------------------------------------------*/
+
+static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet) 
+{
+
+	strlcpy (A->g_msg_type, "Telemetry", sizeof(A->g_msg_type));
+
+	telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, sizeof(A->g_telemetry), A->g_comment, sizeof(A->g_comment));
+
+
+} /* end aprs_telemetry */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_raw_touch_tone
+ *
+ * Purpose:	Decode raw touch tone datA->
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Description:	Touch tone data is converted to a packet format
+ *		so it can be conveyed to an application for processing.
+ *
+ * 		This is not part of the APRS standard.	
+ *		
+ *------------------------------------------------------------------*/
+
+static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen) 
+{
+
+	strlcpy (A->g_msg_type, "Raw Touch Tone Data", sizeof(A->g_msg_type));
+
+	/* Just copy the info field without the message type. */
+
+	if (*info == '{') 
+	  strlcpy (A->g_comment, info+3, sizeof(A->g_comment));
+	else
+	  strlcpy (A->g_comment, info+1, sizeof(A->g_comment));
+
+
+} /* end aprs_raw_touch_tone */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_morse_code
+ *
+ * Purpose:	Convey message in packet format to be transmitted as 
+ *		Morse Code.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Description:	This is not part of the APRS standard.	
+ *		
+ *------------------------------------------------------------------*/
+
+static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen) 
+{
+
+	strlcpy (A->g_msg_type, "Morse Code Data", sizeof(A->g_msg_type));
+
+	/* Just copy the info field without the message type. */
+
+	if (*info == '{') 
+	  strlcpy (A->g_comment, info+3, sizeof(A->g_comment));
+	else
+	  strlcpy (A->g_comment, info+1, sizeof(A->g_comment));
+
+
+} /* end aprs_morse_code */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_ll_pos_time
+ *
+ * Purpose:	Decode weather report without a position.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_symbol_table, A->g_symbol_code.
+ *
+ * Description:	Type identifier '_' is a weather report without a position.
+ *
+ *------------------------------------------------------------------*/
+
+
+
+static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *info, int ilen) 
+{
+
+	struct aprs_positionless_weather_s {
+	  char dti;			/* _ */
+	  char time_stamp[8];		/* MDHM format */
+	  char comment[99]; 		
+	} *p;
+
+
+	strlcpy (A->g_msg_type, "Positionless Weather Report", sizeof(A->g_msg_type));
+
+	//time_t ts = 0;
+
+
+	p = (struct aprs_positionless_weather_s *)info;
+	
+	// not yet implemented for 8 character format // ts = get_timestamp (A, p->time_stamp);
+
+	weather_data (A, p->comment, FALSE);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	weather_data
+ *
+ * Purpose:	Decode weather data in position or object report.
+ *
+ * Inputs:	info 	- Pointer to first byte after location
+ *			  and symbol code.
+ *
+ *		wind_prefix 	- Expecting leading wind info
+ *				  for human-readable location.
+ *				  (Currently ignored.  We are very
+ *				  forgiving in what is accepted.)
+ * TODO: call this context instead and have 3 enumerated values.
+ *
+ * Global In:	A->g_course	- Wind info for compressed location.
+ *		A->g_speed_mph
+ *
+ * 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 A->g_course and A->g_speed_mph.
+ *		Otherwise, for positionless weather data, the 
+ *		wind is in the form c999s999.
+ *
+ * References:	APRS Weather specification comments.
+ *		http://aprs.org/aprs11/spec-wx.txt
+ *
+ *		Weather updates to the spec.
+ *		http://aprs.org/aprs12/weather-new.txt
+ *
+ * Examples:
+ *	
+ *	_10090556c220s004g005t077r000p000P000h50b09900wRSW
+ *	!4903.50N/07201.75W_220/004g005t077r000p000P000h50b09900wRSW
+ *	!4903.50N/07201.75W_220/004g005t077r000p000P000h50b.....wRSW
+ *	@092345z4903.50N/07201.75W_220/004g005t-07r000p000P000h50b09900wRSW
+ *	=/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW
+ *	@092345z/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW
+ *	;BRENDA   *092345z4903.50N/07201.75W_220/004g005b0990
+ *
+ *------------------------------------------------------------------*/
+
+static int getwdata (char **wpp, char ch, int dlen, float *val) 
+{
+	char stemp[8];	// larger than maximum dlen.
+	int i;
+
+
+	//dw_printf("debug: getwdata (wp=%p, ch=%c, dlen=%d)\n", *wpp, ch, dlen);
+
+	*val = G_UNKNOWN;
+
+	assert (dlen >= 2 && dlen <= 6);
+
+	if (**wpp != ch) {
+	  /* Not specified element identifier. */
+	  return (0);	
+	}
+	
+	if (strncmp((*wpp)+1, "......", dlen) == 0 || strncmp((*wpp)+1, "      ", dlen) == 0) {
+	  /* Field present, unknown value */
+	  *wpp += 1 + dlen;
+	  return (1); 
+	}
+
+	/* Data field can contain digits, decimal point, leading negative. */
+
+	for (i=1; i<=dlen; i++) {
+	  if ( ! isdigit((*wpp)[i]) && (*wpp)[i] != '.' && (*wpp)[i] != '-' ) {
+	    return(0);
+	  }
+	} 
+
+	memset (stemp, 0, sizeof(stemp));
+	memcpy (stemp, (*wpp)+1, dlen);
+	*val = atof(stemp);
+
+	//dw_printf("debug: getwdata returning %f\n", *val);
+
+	*wpp += 1 + dlen;
+	return (1); 
+}	
+
+static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) 
+{
+	int n;
+	float fval;
+	char *wp = wdata;
+	int keep_going;
+
+	
+	if (wp[3] == '/')
+	{
+	  if (sscanf (wp, "%3d", &n))
+	  {
+	    // Data Extension format.
+	    // Fine point:  Officially, should be values of 001-360.
+	    // "000" or "..." or "   " means unknown. 
+	    // In practice we see do see "000" here.
+	    A->g_course = n;
+	  }
+	  if (sscanf (wp+4, "%3d", &n))
+	  {
+	    A->g_speed_mph = DW_KNOTS_TO_MPH(n);  /* yes, in knots */
+	  }
+	  wp += 7;
+	}
+	else if ( A->g_speed_mph == G_UNKNOWN) {
+
+	  if ( ! getwdata (&wp, 'c', 3, &A->g_course)) {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Didn't find wind direction in form c999.\n");
+	    }
+	  }
+	  if ( ! getwdata (&wp, 's', 3, &A->g_speed_mph)) {	/* MPH here */
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Didn't find wind speed in form s999.\n");
+	    }
+	  }
+	}
+
+// At this point, we should have the wind direction and speed
+// from one of three methods.
+
+	if (A->g_speed_mph != G_UNKNOWN) {
+
+	  snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph", A->g_speed_mph);
+	  if (A->g_course != G_UNKNOWN) {
+	    char ctemp[40];
+	    snprintf (ctemp, sizeof(ctemp), ", direction %.0f", A->g_course);
+	    strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	  }
+	}
+
+	/* We don't want this to show up on the location line. */
+	A->g_speed_mph = G_UNKNOWN;
+	A->g_course = G_UNKNOWN;
+
+/*
+ * After the mandatory wind direction and speed (in 1 of 3 formats), the
+ * next two must be in fixed positions:
+ * - gust (peak in mph last 5 minutes)
+ * - temperature, degrees F, can be negative e.g. -01
+ */
+	if (getwdata (&wp, 'g', 3, &fval)) {
+	  if (fval != G_UNKNOWN) {
+	    char ctemp[40];
+	    snprintf (ctemp, sizeof(ctemp), ", gust %.0f", fval);
+	    strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	  }
+	}
+	else {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Didn't find wind gust in form g999.\n");
+	  }
+	}
+
+	if (getwdata (&wp, 't', 3, &fval)) {
+	  if (fval != G_UNKNOWN) {
+	    char ctemp[40];
+	    snprintf (ctemp, sizeof(ctemp), ", temperature %.0f", fval);
+	    strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	  }
+	}
+	else {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Didn't find temperature in form t999.\n");
+	  }
+	}
+
+/*
+ * Now pick out other optional fields in any order.
+ */
+	keep_going = 1;
+	while (keep_going) {
+
+	  if (getwdata (&wp, 'r', 3, &fval)) {	
+
+	/* r = rainfall, 1/100 inch, last hour */
+
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last hour", fval / 100.);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'p', 3, &fval)) {	
+
+	/* p = rainfall, 1/100 inch, last 24 hours */
+
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last 24 hours", fval / 100.);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'P', 3, &fval)) {	
+
+	/* P = rainfall, 1/100 inch, since midnight */
+
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", rain %.2f since midnight", fval / 100.);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'h', 2, &fval)) {	
+
+	/* h = humidity %, 00 means 100%  */
+
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[30];
+	      if (fval == 0) fval = 100;
+	      snprintf (ctemp, sizeof(ctemp), ", humidity %.0f", fval);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'b', 5, &fval)) {	
+
+	/* b = barometric presure (tenths millibars / tenths of hPascal)  */
+	/* Here, display as inches of mercury. */
+
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      fval = DW_MBAR_TO_INHG(fval * 0.1);
+	      snprintf (ctemp, sizeof(ctemp), ", barometer %.2f", fval);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'L', 3, &fval)) {	
+
+	/* L = Luminosity, watts/ sq meter, 000-999  */
+	
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'l', 3, &fval)) {	
+
+	/* l = Luminosity, watts/ sq meter, 1000-1999  */
+	
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval + 1000);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 's', 3, &fval)) {	
+
+	/* s = Snowfall in last 24 hours, inches  */
+	/* Data can have decimal point so we don't have to worry about scaling. */
+	/* 's' is also used by wind speed but that must be in a fixed */
+	/* position in the message so there is no confusion. */
+	
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", %.1f snow in 24 hours", fval);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 's', 3, &fval)) {	
+
+	/* # = Raw rain counter  */
+	
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", raw rain counter %.f", fval);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+	  else if (getwdata (&wp, 'X', 3, &fval)) {	
+
+	/* X = Nuclear Radiation.  */
+	/* Encoded as two significant digits and order of magnitude */
+	/* like resistor color code. */
+
+// TODO: decode this properly
+	
+	    if (fval != G_UNKNOWN) {
+	      char ctemp[40];
+	      snprintf (ctemp, sizeof(ctemp), ", nuclear Radiation %.f", fval);
+	      strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
+	    }
+	  }
+
+// TODO: add new flood level, battery voltage, etc.
+
+	  else {
+	    keep_going = 0;
+	  }
+	}
+
+/*
+ * We should be left over with:
+ * - one character for software.
+ * - two to four characters for weather station type.
+ * Examples: tU2k, wRSW
+ *
+ * But few people follow the protocol spec here.  Instead more often we see things like:
+ *  sunny/WX
+ *  / {UIV32N}
+ */
+
+	strlcat (A->g_weather, ", \"", sizeof(A->g_weather));
+	strlcat (A->g_weather, wp, sizeof(A->g_weather));
+/*
+ * Drop any CR / LF character at the end.
+ */
+	n = strlen(A->g_weather);
+	if (n >= 1 && A->g_weather[n-1] == '\n') {
+	  A->g_weather[n-1] = '\0';
+	}
+
+	n = strlen(A->g_weather);
+	if (n >= 1 && A->g_weather[n-1] == '\r') {
+	  A->g_weather[n-1] = '\0';
+	}
+
+	strlcat (A->g_weather, "\"", sizeof(A->g_weather));
+
+	return;
+
+} /* end weather_data */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	aprs_ultimeter
+ *
+ * Purpose:	Decode Peet Brothers ULTIMETER Weather Station Info.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_weather
+ *
+ * Description:	http://www.peetbros.com/shop/custom.aspx?recid=7 
+ *
+ * 		There are two different data formats in use.
+ *		One begins with $ULTW and is called "Packet Mode."  Example:
+ *
+ *		$ULTW009400DC00E21B8027730008890200010309001E02100000004C<CR><LF>
+ *
+ *		The other begins with !! and is called "logging mode."  Example:
+ *
+ *		!!000000A600B50000----------------001C01D500000017<CR><LF>
+ *
+ *
+ * Bugs:	Implementation is incomplete.
+ *		The example shown in the APRS protocol spec has a couple "----"
+ *		fields in the $ULTW message.  This should be rewritten to handle
+ *		each field separately to deal with missing pieces.
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) 
+{
+
+				// Header = $ULTW 
+				// Data Fields 
+	short h_windpeak;	// 1. Wind Speed Peak over last 5 min. (0.1 kph) 
+	short h_wdir;		// 2. Wind Direction of Wind Speed Peak (0-255) 
+	short h_otemp;		// 3. Current Outdoor Temp (0.1 deg F) 
+	short h_totrain;	// 4. Rain Long Term Total (0.01 in.) 
+	short h_baro;		// 5. Current Barometer (0.1 mbar) 
+	short h_barodelta;	// 6. Barometer Delta Value(0.1 mbar) 
+	short h_barocorrl;	// 7. Barometer Corr. Factor(LSW) 
+	short h_barocorrm;	// 8. Barometer Corr. Factor(MSW) 
+	short h_ohumid;		// 9. Current Outdoor Humidity (0.1%) 
+	short h_date;		// 10. Date (day of year) 
+	short h_time;		// 11. Time (minute of day) 
+	short h_raintoday;	// 12. Today's Rain Total (0.01 inches)* 
+	short h_windave;	// 13. 5 Minute Wind Speed Average (0.1kph)* 
+				// Carriage Return & Line Feed
+				// *Some instruments may not include field 13, some may 
+				// not include 12 or 13. 
+				// Total size: 44, 48 or 52 characters (hex digits) + 
+				// header, carriage return and line feed. 
+
+	int n;
+
+	strlcpy (A->g_msg_type, "Ultimeter", sizeof(A->g_msg_type));
+
+	if (*info == '$')
+ 	{
+	  n = sscanf (info+5, "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx",
+			&h_windpeak,		
+			&h_wdir,		
+			&h_otemp,
+			&h_totrain,		
+			&h_baro,		
+			&h_barodelta,	
+			&h_barocorrl,	
+			&h_barocorrm,	
+			&h_ohumid,		 
+			&h_date,		
+			&h_time,		
+			&h_raintoday,	 	// not on some models.
+			&h_windave);		// not on some models.
+
+	  if (n >= 11 && n <= 13) {
+
+	    float windpeak, wdir, otemp, baro, ohumid;
+
+	    windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
+	    wdir = (h_wdir & 0xff) * 360. / 256.;
+	    otemp = h_otemp * 0.1;
+	    baro = DW_MBAR_TO_INHG(h_baro * 0.1);
+	    ohumid = h_ohumid * 0.1;
+	  
+	    snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f",
+			windpeak, wdir, otemp, baro, ohumid);
+	  }
+	}
+
+	
+		// Header = !! 
+		// Data Fields 
+		// 1. Wind Speed (0.1 kph) 
+		// 2. Wind Direction (0-255) 
+		// 3. Outdoor Temp (0.1 deg F) 
+		// 4. Rain* Long Term Total (0.01 inches)  
+		// 5. Barometer (0.1 mbar) 	[ can be ---- ]
+		// 6. Indoor Temp (0.1 deg F) 	[ can be ---- ]
+		// 7. Outdoor Humidity (0.1%) 	[ can be ---- ]
+		// 8. Indoor Humidity (0.1%) 	[ can be ---- ]
+		// 9. Date (day of year) 
+		// 10. Time (minute of day) 
+		// 11. Today's Rain Total (0.01 inches)* 
+		// 12. 1 Minute Wind Speed Average (0.1kph)* 
+		// Carriage Return & Line Feed 
+		//
+		// *Some instruments may not include field 12, some may not include 11 or 12. 
+		// Total size: 40, 44 or 48 characters (hex digits) + header, carriage return and line feed
+
+	if (*info == '!')
+ 	{
+	  n = sscanf (info+2, "%4hx%4hx%4hx%4hx",
+			&h_windpeak,		
+			&h_wdir,		
+			&h_otemp,
+			&h_totrain);
+
+	  if (n == 4) {
+
+	    float windpeak, wdir, otemp;
+
+	    windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
+	    wdir = (h_wdir & 0xff) * 360. / 256.;
+	    otemp = h_otemp * 0.1;
+	  
+	    snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f\n",
+			windpeak, wdir, otemp);
+	  }
+
+	}
+
+} /* end aprs_ultimeter */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	third_party_header
+ *
+ * Purpose:	Decode packet from a third party network.
+ *
+ * Inputs:	info 	- Pointer to Information field.
+ *		ilen 	- Information field length.
+ *
+ * Outputs:	A->g_comment
+ *
+ * Description:	
+ *
+ *------------------------------------------------------------------*/
+
+static void third_party_header (decode_aprs_t *A, char *info, int ilen) 
+{
+
+	strlcpy (A->g_msg_type, "Third Party Header", sizeof(A->g_msg_type));
+
+	/* more later? */
+
+} /* end third_party_header */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	decode_position
+ *
+ * Purpose:	Decode the position & symbol information common to many message formats.
+ *
+ * Inputs:	ppos 	- Pointer to position & symbol fields.
+ *
+ * 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.
+ *
+ *------------------------------------------------------------------*/
+
+
+static void decode_position (decode_aprs_t *A, position_t *ppos)
+{
+
+	  A->g_lat = get_latitude_8 (ppos->lat, A->g_quiet);
+	  A->g_lon = get_longitude_9 (ppos->lon, A->g_quiet);
+
+	  A->g_symbol_table = ppos->sym_table_id;
+	  A->g_symbol_code = ppos->symbol_code;
+}
+
+/*------------------------------------------------------------------
+ *
+ * Function:	decode_compressed_position
+ *
+ * Purpose:	Decode the compressed position & symbol information common to many message formats.
+ *
+ * Inputs:	ppos 	- Pointer to compressed position & symbol fields.
+ *
+ * Returns:	A->g_lat
+ *		A->g_lon
+ *		A->g_symbol_table
+ *		A->g_symbol_code
+ *
+ *		One of the following:
+ *			A->g_course & A->g_speeed
+ *			A->g_altitude_ft
+ *			A->g_range
+ *
+ * Description:	The compressed position provides resolution of around ???
+ *		This also includes course/speed or altitude.
+ *
+ *		It contains 13 bytes of the format:
+ *
+ *			symbol table	/, \, or overlay A-Z, a-j is mapped into 0-9
+ *
+ *			yyyy		Latitude, base 91.
+ * 
+ *			xxxx		Longitude, base 91.
+ *
+ *			symbol code
+ *
+ *			cs		Course/Speed or altitude.
+ *
+ *			t		Various "type" info.
+ *
+ *------------------------------------------------------------------*/
+
+
+static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *pcpos)
+{
+	if (isdigit91(pcpos->y[0]) && isdigit91(pcpos->y[1]) && isdigit91(pcpos->y[2]) && isdigit91(pcpos->y[3]))
+	{
+	  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
+ 	{
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in compressed latitude.  Must be in range of '!' to '{'.\n");
+	  }
+	  A->g_lat = G_UNKNOWN;
+	}
+	  
+	if (isdigit91(pcpos->x[0]) && isdigit91(pcpos->x[1]) && isdigit91(pcpos->x[2]) && isdigit91(pcpos->x[3]))
+	{
+	  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 
+	{
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in compressed longitude.  Must be in range of '!' to '{'.\n");
+	  }
+	  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. */
+	  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. */
+	  A->g_symbol_table = pcpos->sym_table_id - 'a' + '0';
+	}
+	else {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid symbol table id for compressed position.\n");
+	  }
+	  A->g_symbol_table = '/';
+	}
+
+	A->g_symbol_code = pcpos->symbol_code;
+
+	if (pcpos->c == ' ') {
+	  ; /* ignore other two bytes */
+	}
+	else if (((pcpos->t - 33) & 0x18) == 0x10) {
+	  A->g_altitude_ft = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33);
+	}
+	else if (pcpos->c == '{')
+	{
+	  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. */
+	  A->g_course = (pcpos->c - 33) * 4;
+	  A->g_speed_mph = DW_KNOTS_TO_MPH(pow(1.08, pcpos->s - 33) - 1.0);
+	}
+
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	get_latitude_8
+ *
+ * Purpose:	Convert 8 byte latitude encoding to degrees.
+ *
+ * Inputs:	plat 	- Pointer to first byte.
+ *
+ * Returns:	Double precision value in degrees.  Negative for South.
+ *
+ * Description:	Latitude is expressed as a fixed 8-character field, in degrees 
+ *		and decimal minutes (to two decimal places), followed by the 
+ *		letter N for north or S for south.
+ *		The protocol spec specifies upper case but I've seen lower
+ *		case so this will accept either one.
+ *		Latitude degrees are in the range 00 to 90. Latitude minutes 
+ *		are expressed as whole minutes and hundredths of a minute, 
+ *		separated by a decimal point.
+ *		For example:
+ *		4903.50N is 49 degrees 3 minutes 30 seconds north.
+ *		In generic format examples, the latitude is shown as the 8-character 
+ *		string ddmm.hhN (i.e. degrees, minutes and hundredths of a minute north).
+ *
+ * Bug:		We don't properly deal with position ambiguity where trailing
+ *		digits might be replaced by spaces.  We simply treat them like zeros.	
+ *
+ * Errors:	Return G_UNKNOWN for any type of error.
+ *
+ *		Should probably print an error message.
+ *
+ *------------------------------------------------------------------*/
+
+double get_latitude_8 (char *p, int quiet)
+{
+	struct lat_s {
+	  unsigned char deg[2];
+	  unsigned char minn[2];
+	  char dot;
+	  unsigned char hmin[2];
+	  char ns;
+	} *plat;
+
+	double result = 0;
+	
+	plat = (void *)p;
+
+	if (isdigit(plat->deg[0]))
+	  result += ((plat->deg[0]) - '0') * 10;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for tens of degrees.\n", plat->deg[0]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plat->deg[1]))
+	  result += ((plat->deg[1]) - '0') * 1;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for degrees.\n", plat->deg[1]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (plat->minn[0] >= '0' || plat->minn[0] <= '5')
+	  result += ((plat->minn[0]) - '0') * (10. / 60.);
+	else if (plat->minn[0] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-5 for tens of minutes.\n", plat->minn[0]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plat->minn[1]))
+	  result += ((plat->minn[1]) - '0') * (1. / 60.);
+	else if (plat->minn[1] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for minutes.\n", plat->minn[1]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (plat->dot != '.') {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Unexpected character \"%c\" found where period expected in latitude.\n", plat->dot);
+	  }
+	  return (G_UNKNOWN);
+	} 
+
+	if (isdigit(plat->hmin[0]))
+	  result += ((plat->hmin[0]) - '0') * (0.1 / 60.);
+	else if (plat->hmin[0] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for tenths of minutes.\n", plat->hmin[0]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plat->hmin[1]))
+	  result += ((plat->hmin[1]) - '0') * (0.01 / 60.);
+	else if (plat->hmin[1] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in latitude.  Found '%c' when expecting 0-9 for hundredths of minutes.\n", plat->hmin[1]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+// The spec requires upper case for hemisphere.  Accept lower case but warn.
+
+	if (plat->ns == 'N') {
+	  return (result);
+        }
+        else if (plat->ns == 'n') {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Warning: Lower case n found for latitude hemisphere.  Specification requires upper case N or S.\n");
+	  }	  
+	  return (result);
+	}
+	else if (plat->ns == 'S') {
+	  return ( - result);
+	}
+	else if (plat->ns == 's') {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Warning: Lower case s found for latitude hemisphere.  Specification requires upper case N or S.\n");	
+	  }  
+	  return ( - result);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Error: '%c' found for latitude hemisphere.  Specification requires upper case N or s.\n", plat->ns);	 
+	  } 
+	  return (G_UNKNOWN);	
+	}	
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	get_longitude_9
+ *
+ * Purpose:	Convert 9 byte longitude encoding to degrees.
+ *
+ * Inputs:	plat 	- Pointer to first byte.
+ *
+ * Returns:	Double precision value in degrees.  Negative for West.
+ *
+ * Description:	Longitude is expressed as a fixed 9-character field, in degrees and 
+ *		decimal minutes (to two decimal places), followed by the letter E 
+ *		for east or W for west.
+ *		Longitude degrees are in the range 000 to 180. Longitude minutes are
+ *		expressed as whole minutes and hundredths of a minute, separated by a
+ *		decimal point.
+ *		For example:
+ *		07201.75W is 72 degrees 1 minute 45 seconds west.
+ *		In generic format examples, the longitude is shown as the 9-character 
+ *		string dddmm.hhW (i.e. degrees, minutes and hundredths of a minute west).
+ *
+ * Bug:		We don't properly deal with position ambiguity where trailing
+ *		digits might be replaced by spaces.  We simply treat them like zeros.	
+ *
+ * Errors:	Return G_UNKNOWN for any type of error.
+ *
+ * Example:	
+ *
+ *------------------------------------------------------------------*/
+
+
+double get_longitude_9 (char *p, int quiet)
+{
+	struct lat_s {
+	  unsigned char deg[3];
+	  unsigned char minn[2];
+	  char dot;
+	  unsigned char hmin[2];
+	  char ew;
+	} *plon;
+
+	double result = 0;
+	
+	plon = (void *)p;
+
+	if (plon->deg[0] == '0' || plon->deg[0] == '1')
+	  result += ((plon->deg[0]) - '0') * 100;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0 or 1 for hundreds of degrees.\n", plon->deg[0]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plon->deg[1]))
+	  result += ((plon->deg[1]) - '0') * 10;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for tens of degrees.\n", plon->deg[1]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plon->deg[2]))
+	  result += ((plon->deg[2]) - '0') * 1;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for degrees.\n", plon->deg[2]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (plon->minn[0] >= '0' || plon->minn[0] <= '5')
+	  result += ((plon->minn[0]) - '0') * (10. / 60.);
+	else if (plon->minn[0] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-5 for tens of minutes.\n", plon->minn[0]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plon->minn[1]))
+	  result += ((plon->minn[1]) - '0') * (1. / 60.);
+	else if (plon->minn[1] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for minutes.\n", plon->minn[1]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (plon->dot != '.') {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Unexpected character \"%c\" found where period expected in longitude.\n", plon->dot);
+	  }
+	  return (G_UNKNOWN);
+	} 
+
+	if (isdigit(plon->hmin[0]))
+	  result += ((plon->hmin[0]) - '0') * (0.1 / 60.);
+	else if (plon->hmin[0] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for tenths of minutes.\n", plon->hmin[0]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+	if (isdigit(plon->hmin[1]))
+	  result += ((plon->hmin[1]) - '0') * (0.01 / 60.);
+	else if (plon->hmin[1] == ' ')
+	  ;
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Invalid character in longitude.  Found '%c' when expecting 0-9 for hundredths of minutes.\n", plon->hmin[1]);
+	  }
+	  return (G_UNKNOWN);
+	}
+
+// The spec requires upper case for hemisphere.  Accept lower case but warn.
+
+	if (plon->ew == 'E') {
+	  return (result);
+        }
+        else if (plon->ew == 'e') {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Warning: Lower case e found for longitude hemisphere.  Specification requires upper case E or W.\n");
+	  }	  
+	  return (result);
+	}
+	else if (plon->ew == 'W') {
+	  return ( - result);
+	}
+	else if (plon->ew == 'w') {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Warning: Lower case w found for longitude hemisphere.  Specification requires upper case E or W.\n");
+	  }	  
+	  return ( - result);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Error: '%c' found for longitude hemisphere.  Specification requires upper case E or W.\n", plon->ew);	
+	  }  
+	  return (G_UNKNOWN);	
+	}		
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	get_timestamp
+ *
+ * Purpose:	Convert 7 byte timestamp to unix time value.
+ *
+ * Inputs:	p 	- Pointer to first byte.
+ *
+ * Returns:	time_t data type. (UTC)
+ *
+ * Description:	
+ *
+ *		Day/Hours/Minutes (DHM) format is a fixed 7-character field, consisting of
+ *		a 6-digit day/time group followed by a single time indicator character (z or
+ *		/). The day/time group consists of a two-digit day-of-the-month (01-31) and
+ *		a four-digit time in hours and minutes.
+ *		Times can be expressed in zulu (UTC/GMT) or local time. For example:
+ *
+ *		  092345z is 2345 hours zulu time on the 9th day of the month.
+ *		  092345/ is 2345 hours local time on the 9th day of the month.
+ *
+ *		It is recommended that future APRS implementations only transmit zulu
+ *		format on the air.
+ *
+ *		Note: The time in Status Reports may only be in zulu format.
+ *
+ *		Hours/Minutes/Seconds (HMS) format is a fixed 7-character field,
+ *		consisting of a 6-digit time in hours, minutes and seconds, followed by the h
+ *		time-indicator character. For example:
+ *
+ *		  234517h is 23 hours 45 minutes and 17 seconds zulu.
+ *
+ *		Note: This format may not be used in Status Reports.
+ *
+ *		Month/Day/Hours/Minutes (MDHM) format is a fixed 8-character field,
+ *		consisting of the month (01-12) and day-of-the-month (01-31), followed by
+ *		the time in hours and minutes zulu. For example:
+ *
+ *		  10092345 is 23 hours 45 minutes zulu on October 9th.
+ *
+ *		This format is only used in reports from stand-alone "positionless" weather
+ *		stations (i.e. reports that do not contain station position information).
+ *
+ *
+ * Bugs:	Local time not implemented yet.
+ *		8 character form not implemented yet.
+ *
+ *		Boundary conditions are not handled properly.
+ *		For example, suppose it is 00:00:03 on January 1.
+ *		We receive a timestamp of 23:59:58 (which was December 31).
+ *		If we simply replace the time, and leave the current date alone,
+ *		the result is about a day into the future.
+ *
+ *
+ * Example:	
+ *
+ *------------------------------------------------------------------*/
+
+
+time_t get_timestamp (decode_aprs_t *A, char *p)
+{
+	struct dhm_s {
+	  char day[2];
+	  char hours[2];
+	  char minutes[2];
+	  char tic;		/* Time indicator character. */
+				/* z = UTC. */
+				/* / = local - not implemented yet. */
+	} *pdhm;
+
+	struct hms_s {
+	  char hours[2];
+	  char minutes[2];
+	  char seconds[2];
+	  char tic;		/* Time indicator character. */
+				/* h = UTC. */
+	} *phms;
+
+	struct tm *ptm;
+
+	time_t ts;
+
+	ts = time(NULL);
+	ptm = gmtime(&ts);
+
+	pdhm = (void *)p;
+	phms = (void *)p;
+
+	if (pdhm->tic == 'z' || pdhm->tic == '/')   /* Wrong! */
+	{
+	  int j;
+
+	  j = (pdhm->day[0] - '0') * 10 + pdhm->day[1] - '0';
+	  //text_color_set(DW_COLOR_DECODED);
+	  //dw_printf("Changing day from %d to %d\n", ptm->tm_mday, j);
+	  ptm->tm_mday = j;
+
+	  j = (pdhm->hours[0] - '0') * 10 + pdhm->hours[1] - '0';
+	  //dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
+	  ptm->tm_hour = j;
+
+	  j = (pdhm->minutes[0] - '0') * 10 + pdhm->minutes[1] - '0';
+	  //dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
+	  ptm->tm_min = j;
+
+	} 
+	else if (phms->tic == 'h') 
+	{
+	  int j;
+
+	  j = (phms->hours[0] - '0') * 10 + phms->hours[1] - '0';
+	  //text_color_set(DW_COLOR_DECODED);
+	  //dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
+	  ptm->tm_hour = j;
+
+	  j = (phms->minutes[0] - '0') * 10 + phms->minutes[1] - '0';
+	  //dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
+	  ptm->tm_min = j;
+
+	  j = (phms->seconds[0] - '0') * 10 + phms->seconds[1] - '0';
+	  //dw_printf("%sChanging seconds from %d to %d\n", ptm->tm_sec, j);
+	  ptm->tm_sec = j;
+	} 
+	
+	return (mktime(ptm));
+}
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	get_maidenhead
+ *
+ * Purpose:	See if we have a maidenhead locator.
+ *
+ * Inputs:	p 	- Pointer to first byte.
+ *
+ * Returns:	0 = not found.
+ *		4 = possible 4 character locator found.
+ *		6 = possible 6 character locator found.
+ *
+ *		It is not stored anywhere or processed.
+ *
+ * Description:	
+ *
+ *		The maidenhead locator system is sometimes used as a more compact, 
+ *		and less precise, alternative to numeric latitude and longitude.
+ *
+ *		It is composed of:
+ *			a pair of letters in range A to R.
+ *			a pair of digits in range of 0 to 9.
+ *			a pair of letters in range of A to X.
+ *
+ * 		The APRS spec says that all letters must be transmitted in upper case.
+ *
+ *
+ * Examples from APRS spec:	
+ *
+ *		IO91SX
+ *		IO91
+ *
+ *
+ *------------------------------------------------------------------*/
+
+
+int get_maidenhead (decode_aprs_t *A, char *p)
+{
+
+	if (toupper(p[0]) >= 'A' && toupper(p[0]) <= 'R' &&
+	    toupper(p[1]) >= 'A' && toupper(p[1]) <= 'R' &&
+	    isdigit(p[2]) && isdigit(p[3])) {
+
+	  /* We have 4 characters matching the rule. */
+
+	  if (islower(p[0]) || islower(p[1])) {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Warning: Lower case letter in Maidenhead locator.  Specification requires upper case.\n");
+	    }	  
+	  }
+
+	  if (toupper(p[4]) >= 'A' && toupper(p[4]) <= 'X' &&
+	      toupper(p[5]) >= 'A' && toupper(p[5]) <= 'X') {
+
+	    /* We have 6 characters matching the rule. */
+
+	    if (islower(p[4]) || islower(p[5])) {
+	      if ( ! A->g_quiet) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf("Warning: Lower case letter in Maidenhead locator.  Specification requires upper case.\n");	
+	      }	  
+	    }
+	  
+	    return 6;
+	  }
+	
+	  return 4;
+	}
+
+	return 0;
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	data_extension_comment
+ *
+ * Purpose:	A fixed length 7-byte field may follow APRS position datA->
+ *
+ * Inputs:	pdext	- Pointer to optional data extension and comment.
+ *
+ * Returns:	true if a data extension was found.
+ *
+ * Outputs:	One or more of the following, depending the data found:
+ *	
+ *			A->g_course
+ *			A->g_speed_mph
+ *			A->g_power 
+ *			A->g_height 
+ *			A->g_gain 
+ *			A->g_directivity 
+ *			A->g_range
+ *
+ *		Anything left over will be put in 
+ *
+ *			A->g_comment			
+ *
+ * Description:	
+ *
+ *
+ *
+ *------------------------------------------------------------------*/
+
+const char *dir[9] = { "omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N" };
+
+static int data_extension_comment (decode_aprs_t *A, char *pdext)
+{
+	int n;
+
+	if (strlen(pdext) < 7) {
+	  strlcpy (A->g_comment, pdext, sizeof(A->g_comment));
+	  return 0;
+	}
+
+/* Tyy/Cxx - Area object descriptor. */
+
+	if (pdext[0] == 'T' &&
+		pdext[3] == '/' &&
+	 	pdext[4] == 'C')
+	{
+	  /* not decoded at this time */
+	  process_comment (A, pdext+7, -1);
+	  return 1;
+	}
+
+/* CSE/SPD */
+/* For a weather station (symbol code _) this is wind. */
+/* For others, it would be course and speed. */
+
+	if (pdext[3] == '/')
+	{
+	  if (sscanf (pdext, "%3d", &n))
+	  {
+	    A->g_course = n;
+	  }
+	  if (sscanf (pdext+4, "%3d", &n))
+	  {
+	    A->g_speed_mph = DW_KNOTS_TO_MPH(n);
+	  }
+
+	  /* Bearing and Number/Range/Quality? */
+
+	  if (pdext[7] == '/' && pdext[11] == '/') 
+	  {
+	    process_comment (A, pdext + 7 + 8, -1);
+	  }
+	  else {
+	    process_comment (A, pdext+7, -1);
+	  }
+	  return 1;
+	}
+
+/* check for Station power, height, gain. */
+
+	if (strncmp(pdext, "PHG", 3) == 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') {
+	    strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity));
+	  }
+
+	  process_comment (A, pdext+7, -1);
+	  return 1;
+	}
+
+/* check for precalculated radio range. */
+
+	if (strncmp(pdext, "RNG", 3) == 0)
+	{
+	  if (sscanf (pdext+3, "%4d", &n))
+	  {
+	    A->g_range = n;
+	  }
+	  process_comment (A, pdext+7, -1);
+	  return 1;
+	}
+
+/* DF signal strength,  */
+
+	if (strncmp(pdext, "DFS", 3) == 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') {
+	    strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity));
+	  }
+
+	  process_comment (A, pdext+7, -1);
+	  return 1;
+	}
+
+	process_comment (A, pdext, -1);
+	return 0;
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	decode_tocall
+ *
+ * Purpose:	Extract application from the destination.
+ *
+ * Inputs:	dest	- Destination address.
+ *			Don't care if SSID is present or not.
+ *
+ * Outputs:	A->g_mfr
+ *
+ * Description:	For maximum flexibility, we will read the
+ *		data file at run time rather than compiling it in.
+ *
+ *		For the most recent version, download from:
+ *
+ *		http://www.aprs.org/aprs11/tocalls.txt
+ *
+ *		Windows version:  File must be in current working directory.
+ *
+ *		Linux version: Search order is current working directory
+ *			then /usr/share/direwolf directory.
+ *
+ *------------------------------------------------------------------*/
+
+#define MAX_TOCALLS 150
+
+static struct tocalls_s {
+	unsigned char len;
+	char prefix[7];
+	char *description;
+} tocalls[MAX_TOCALLS];
+
+static int num_tocalls = 0;
+
+// Make sure the array is null terminated.
+static const char *search_locations[] = {
+	(const char *) "tocalls.txt",
+#ifndef __WIN32__
+	(const char *) "/usr/share/direwolf/tocalls.txt",
+	(const char *) "/usr/local/share/direwolf/tocalls.txt",
+#endif
+	(const char *) NULL
+};
+
+static int tocall_cmp (const void *px, const void *py)
+{
+	const struct tocalls_s *x = (struct tocalls_s *)px;
+	const struct tocalls_s *y = (struct tocalls_s *)py;
+
+	if (x->len != y->len) return (y->len - x->len);
+	return (strcmp(x->prefix, y->prefix));
+}
+
+static void decode_tocall (decode_aprs_t *A, char *dest)
+{
+	FILE *fp = 0;
+	int n = 0;
+	static int first_time = 1;
+	char stuff[100];
+	char *p = NULL;
+	char *r = NULL;
+
+	//dw_printf("debug: decode_tocall(\"%s\")\n", dest);
+
+/*
+ * Extract the calls and descriptions from the file.
+ *
+ * Use only lines with exactly these formats:
+ *
+ *       APN          Network nodes, digis, etc
+ *	      APWWxx  APRSISCE win32 version
+ *	|     |       |
+ *	00000000001111111111      	
+ *	01234567890123456789...
+ *
+ * Matching will be with only leading upper case and digits.
+ */
+
+// TODO:  Look for this in multiple locations.
+// For example, if application was installed in /usr/local/bin,
+// we might want to put this in /usr/local/share/aprs
+
+// If search strategy changes, be sure to keep symbols_init in sync.
+
+	if (first_time) {
+
+	  n = 0;
+	  fp = NULL;
+	  do {
+	    if(search_locations[n] == NULL) break;
+	    fp = fopen(search_locations[n++], "r");
+	  } while (fp == NULL);
+
+	  if (fp != NULL) {
+
+	    while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) {
+	      
+	      p = stuff + strlen(stuff) - 1;
+	      while (p >= stuff && (*p == '\r' || *p == '\n')) {
+	        *p-- = '\0';
+	      }
+
+	      // dw_printf("debug: %s\n", stuff);
+
+	      if (stuff[0] == ' ' && 
+		  stuff[4] == ' ' &&
+		  stuff[5] == ' ' &&
+		  stuff[6] == 'A' && 
+		  stuff[7] == 'P' && 
+		  stuff[12] == ' ' &&
+		  stuff[13] == ' ' ) {
+
+	        p = stuff + 6;
+	        r = tocalls[num_tocalls].prefix;
+	        while (isupper((int)(*p)) || isdigit((int)(*p))) {
+	          *r++ = *p++;
+	        }
+	        *r = '\0';
+	        if (strlen(tocalls[num_tocalls].prefix) > 2) {
+	          tocalls[num_tocalls].description = strdup(stuff+14);
+		  tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
+	          // dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
+
+	          num_tocalls++;
+	        }
+	      }
+	      else if (stuff[0] == ' ' && 
+		  stuff[1] == 'A' && 
+		  stuff[2] == 'P' && 
+		  isupper((int)(stuff[3])) &&
+		  stuff[4] == ' ' &&
+		  stuff[5] == ' ' &&
+		  stuff[6] == ' ' &&
+		  stuff[12] == ' ' &&
+		  stuff[13] == ' ' ) {
+
+	        p = stuff + 1;
+	        r = tocalls[num_tocalls].prefix;
+	        while (isupper((int)(*p)) || isdigit((int)(*p))) {
+	          *r++ = *p++;
+	        }
+	        *r = '\0';
+	        if (strlen(tocalls[num_tocalls].prefix) > 2) {
+	          tocalls[num_tocalls].description = strdup(stuff+14);
+		  tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
+	          // dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
+
+	          num_tocalls++;
+	        }
+	      }
+	    }
+	    fclose(fp);
+
+/*
+ * Sort by decreasing length so the search will go
+ * from most specific to least specific.
+ * Example:  APY350 or APY008 would match those specific
+ * models before getting to the more generic APY.
+ */
+
+#if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
+	    qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp);
+#else
+	    qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), (__compar_fn_t)tocall_cmp);
+#endif
+	  }
+	  else {
+	    if ( ! A->g_quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Warning: Could not open 'tocalls.txt'.\n");
+	      dw_printf("System types in the destination field will not be decoded.\n");
+	    }
+	  }
+
+	
+	  first_time = 0;
+	}
+
+
+	for (n=0; n<num_tocalls; n++) {
+	  if (strncmp(dest, tocalls[n].prefix, tocalls[n].len) == 0) {
+	    strlcpy (A->g_mfr, tocalls[n].description, sizeof(A->g_mfr));
+	    return;
+	  }
+	}
+
+} /* 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.
+ *
+ *------------------------------------------------------------------*/
+
+// TODO: potential for buffer overflow here.
+
+static void substr_se (char *dest, const char *src, int start, int endp1)
+{
+	int len = endp1 - start;
+
+	if (start < 0 || endp1 < 0 || len <= 0) {
+	  dest[0] = '\0';
+	  return;
+	}
+	memcpy (dest, src + start, len);
+	dest[len] = '\0';
+
+} /* end substr_se */
+	
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	process_comment
+ *
+ * Purpose:	Extract optional items from the comment.
+ *
+ * Inputs:	pstart		- Pointer to start of left over information field.
+ *
+ *		clen		- Length of comment or -1 to take it all.
+ *
+ * Outputs:	A->g_telemetry	- Base 91 telemetry |ss1122|
+ *		A->g_altitude_ft	- 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.
+ *
+ *		Except!!!
+ *
+ *		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 A->g_comment.
+ *
+ * References:	http://www.aprs.org/info/freqspec.txt
+ *
+ *			999.999MHz T100 +060	Voice frequency.
+ *		
+ *		http://www.aprs.org/datum.txt
+ *
+ *			!DAO!			APRS precision and Datum option.
+ *
+ *		Protocol reference, end of chaper 6.
+ *
+ *			/A=123456		Altitude
+ *
+ *
+ *------------------------------------------------------------------*/
+
+/* 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 (decode_aprs_t *A, char *pstart, int clen)
+{
+	static int first_time = 1;
+	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 4
+	regmatch_t match[MAXMATCH];
+	char temp[sizeof(A->g_comment)];
+	int keep_going;
+
+
+/*
+ * No sense in recompiling the patterns and freeing every time.
+ */	
+	if (first_time) 
+	{
+/*
+ * Frequency must be at the at the beginning.
+ * Others can be anywhere in the comment.
+ */
+		
+	  //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, &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][!-{ ][!-{ ]|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);
+	  }
+
+	  e = regcomp (&alt_re, "/A=[0-9][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED);
+	  if (e) {
+	    regerror (e, &alt_re, emsg, sizeof(emsg));
+	    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 is >= 0, take only specified number of characters.
+ * Otherwise, take it all.
+ */
+	if (clen < 0) {
+	  clen = strlen(pstart);
+	}
+
+/*
+ * Watch out for buffer overflow.
+ * KG6AZZ reports that there is a local digipeater that seems to 
+ * malfunction ocassionally.  It corrupts the packet, as it is
+ * digipeated, causing the comment to be hundreds of characters long.
+ */
+
+	if (clen > sizeof(A->g_comment) - 1) {
+	  if ( ! A->g_quiet) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("Comment is extremely long, %d characters.\n", clen);
+	    dw_printf("Please report this, along with surrounding lines, so we can find the cause.\n");
+	  }
+	  clen = sizeof(A->g_comment) - 1;
+	}
+
+	if (clen > 0) {
+	  memcpy (A->g_comment, pstart, (size_t)clen);
+	  A->g_comment[clen] = '\0';
+	}
+	else {
+	  A->g_comment[0] = '\0';
+	}
+
+
+/*
+ * Look for frequency in the standard format at start of comment.
+ * If that fails, try to obtain from object name.
+ */
+
+	if (regexec (&std_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) 
+	{
+	  char sftemp[30];
+	  char smtemp[10];
+
+          //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) {
+	    if ( ! A->g_quiet) {
+	      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");
+	    }
+	  }
+
+	  strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	  strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment));
+	}
+	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) {
+	      if ( ! A->g_quiet) {
+	        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");
+	      }
+	    }
+
+	    strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	    strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment));
+	  }
+	  else if (regexec (&std_toff_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    dw_printf ("NO tone\n");
+	    A->g_tone = 0;
+
+	    strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	    strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment));
+	  }
+	  else if (regexec (&std_dcs_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    char sttemp[10];	/* three octal digits */
+
+	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
+
+	    A->g_dcs = strtoul (sttemp, NULL, 8);
+
+	    strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	    strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
+	  }
+	  else if (regexec (&std_offset_re, A->g_comment, MAXMATCH, match, 0) == 0) {
+
+	    char sttemp[10];	/* includes leading sign */
+
+	    substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
+
+	    A->g_offset = 10 * atoi(sttemp);
+
+	    strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	    strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
+	  }
+	  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 */
+
+	    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));
+	    }
+
+	    strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	    strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
+	  }
+	  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, sizeof(A->g_telemetry));
+
+	  strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	  strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
+	}
+
+
+/*
+ * 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.
+ *
+ * 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, A->g_comment, MAXMATCH, match, 0) == 0) 
+	{
+
+	  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));
+
+
+/*
+ * Private extension for APRStt
+ */
+
+	  if (d == 'T') {
+
+	    if (a == ' ' && o == ' ') {
+	      snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt corral location");
+	    }
+	    else if (isdigit(a) && o == ' ') {
+	      snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c of 10", a);
+	    }
+	    else if (isdigit(a) && isdigit(o)) {
+	      snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c of 100", a, o);
+	    }
+	    else if (a == 'B' && isdigit(o)) {
+	      snprintf (A->g_aprstt_loc, sizeof(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:
+ *
+ *		Lat:	 DD MM.HHa
+ *		Lon:	DDD HH.HHo
+ */
+ 	    if (isdigit(a)) {
+	      A->g_lat += (a - '0') / 60000.0 * sign(A->g_lat);
+	    }
+ 	    if (isdigit(o)) {
+	      A->g_lon += (o - '0') / 60000.0 * sign(A->g_lon);
+	    }
+	  }
+	  else if (islower(d)) 
+	  {
+/*
+ * This adds almost two extra digits to each like this:
+ *
+ *		Lat:	 DD MM.HHxx
+ *		Lon:	DDD HH.HHxx
+ *
+ * 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.
+ */
+
+/* 
+ * 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 (isdigit91(o)) {
+	      A->g_lon += (o - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lon);
+	    }
+	  }
+
+	  strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+	  strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
+	}
+
+/*
+ * Altitude in feet.  /A=123456
+ */
+
+	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));
+
+	  strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
+
+	  A->g_comment[match[0].rm_eo] = '\0';
+          A->g_altitude_ft = atoi(A->g_comment + match[0].rm_so + 3);
+
+	  strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
+	}
+
+	//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.
+ * Don't complain if we have already found a valid value.
+ */
+	if (A->g_freq == G_UNKNOWN && 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)) { 
+
+	    if ( ! A->g_quiet) {
+	      snprintf (good, sizeof(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 (A->g_tone == G_UNKNOWN && 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);
+	  strlcpy (bad2, bad1, sizeof(bad2));
+	  if (strcmp(bad2, "67") == 0 || strcmp(bad2, "77") == 0 || strcmp(bad2, "100") == 0 || strcmp(bad2, "123") == 0) {
+	    strlcat (bad2, ".0", sizeof(bad2));
+	  }
+
+// 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) {
+
+	      if ( ! A->g_quiet) {
+                snprintf (good, sizeof(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) {
+	  if ( ! A->g_quiet) {
+	    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
+ */
+
+}
+
+/* end process_comment */
+
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	main
+ *
+ * Purpose:	Main program for standalone test program.
+ *
+ * Inputs:	stdin for raw data to decode.
+ *		This is in the usual display format either from
+ *		a TNC, findu.com, aprs.fi, etc.  e.g.
+ *
+ *		N1EDF-9>T2QT8Y,W1CLA-1,WIDE1*,WIDE2-2,00000:`bSbl!Mv/`"4%}_ <0x0d>
+ *
+ *		WB2OSZ-1>APN383,qAR,N1EDU-2:!4237.14NS07120.83W#PHG7130Chelmsford, MA
+ *
+ *
+ * Outputs:	stdout
+ *
+ * Description:	Compile like this to make a standalone test program.
+ *
+ *		gcc -o decode_aprs -DDECAMAIN decode_aprs.c ax25_pad.c ...
+ *
+ *		./decode_aprs < decode_aprs.txt
+ *
+ *		aprs.fi precedes raw data with a time stamp which you
+ *		would need to remove first.
+ *
+ *		cut -c26-999 tmp/kj4etp-9.txt | decode_aprs.exe
+ *
+ *
+ * Restriction:	MIC-E message type can be problematic because it
+ *		it can use unprintable characters in the information field.
+ *
+ *		Dire Wolf and aprs.fi print it in hexadecimal.  Example:
+ *
+ *		KB1KTR-8>TR3U6T,KB1KTR-9*,WB2OSZ-1*,WIDE2*,qAR,W1XM:`c1<0x1f>l!t>/>"4^}
+ *		                                                       ^^^^^^
+ *		                                                       ||||||
+ *		What does findu.com do in this case?
+ *
+ *		ax25_from_text recognizes this representation so it can be used
+ *		to decode raw data later.
+ *
+ * TODO:	To make it more useful,
+ *			- Remove any leading timestamp.
+ *			- Remove any "qA*" and following from the path.
+ *
+ *------------------------------------------------------------------*/
+
+#if DECAMAIN
+
+/* 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[]) 
+{
+	char stuff[300];
+	char *p;	
+	packet_t pp;
+
+#if __WIN32__
+
+// Select UTF-8 code page for console output.
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx
+// This is the default I see for windows terminal:  
+// >chcp
+// Active code page: 437
+
+	//Restore on exit? oldcp = GetConsoleOutputCP();
+	SetConsoleOutputCP(CP_UTF8);
+
+#else
+
+/*
+ * Default on Raspian & Ubuntu Linux is fine.  Don't know about others.
+ *
+ * Should we look at LANG environment variable and issue a warning
+ * if it doesn't look something like  en_US.UTF-8 ?
+ */
+
+#endif	
+	if (argc >= 2) {
+	  if (freopen (argv[1], "r", stdin) == NULL) {
+	    fprintf(stderr, "Can't open %s for read.\n", argv[1]);
+	    exit(1);
+	  }
+	}
+
+	text_color_init(1);
+	text_color_set(DW_COLOR_INFO);
+
+	while (fgets(stuff, sizeof(stuff), stdin) != NULL) 
+        {
+	  p = stuff + strlen(stuff) - 1;
+	  while (p >= stuff && (*p == '\r' || *p == '\n')) {
+	    *p-- = '\0';
+	  }
+
+	  if (strlen(stuff) == 0 || stuff[0] == '#') 
+          {
+	    /* comment or blank line */
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf("%s\n", stuff);
+	    continue;
+          }
+  	  else 
+	  {
+	    /* Try to process it. */
+
+	    text_color_set(DW_COLOR_REC);
+	    dw_printf("\n%s\n", stuff);	    
+	 
+	    pp = ax25_from_text(stuff, 1);
+	    if (pp != NULL) 
+            {
+	      decode_aprs_t A;
+
+	      // log directory option someday?
+	      decode_aprs (&A, pp, 0);
+
+	      //Print it all out in human readable format.
+
+	      decode_aprs_print (&A);
+
+	      /*
+	       * Perform validity check on each address.
+	       * This should print an error message if any issues.
+	       */
+	      (void)ax25_check_addresses(pp);
+
+	      // Send to log file?
+
+	      // if (logdir != NULL && *logdir != '\0') {
+	      //   log_write (&A, pp, logdir);
+	      // }
+
+	      ax25_delete (pp);
+	    }
+	    else 
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("\n%s\n", "ERROR - Could not parse input!\n");
+    	    }
+	  }
+	}
+	return (0);
+}
+
+#endif /* DECAMAIN */
+
+/* end decode_aprs.c */
diff --git a/decode_aprs.h b/decode_aprs.h
index 5e4d0c7..a767ed2 100644
--- a/decode_aprs.h
+++ b/decode_aprs.h
@@ -1,110 +1,128 @@
-
-/* decode_aprs.h */
-
-
-#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 {
-
-	int g_quiet;			/* Suppress error messages when decoding. */
-
-        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[12];		/* 4 or 6 (or 8?) character maidenhead locator. */
-
-        char g_name[12];		/* Object or item name. Max. 9 characters. */
-
-	char g_addressee[12];		/* Addressee for a "message."  Max. 9 characters. */
-
-        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[12];		/* 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[32];	/* 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, int quiet);
-
-extern void decode_aprs_print (decode_aprs_t *A);
-
-
+
+/* decode_aprs.h */
+
+
+#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 {
+
+	int g_quiet;			/* Suppress error messages when decoding. */
+
+        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[12];		/* 4 or 6 (or 8?) character maidenhead locator. */
+
+        char g_name[12];		/* Object or item name. Max. 9 characters. */
+
+	char g_addressee[12];		/* Addressee for a "message."  Max. 9 characters. */
+					/* Also for Directed Station Query which is a */
+					/* special case of message. */
+
+        float g_speed_mph;		/* 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[12];		/* Direction of max signal strength */
+
+        float g_range;			/* Precomputed radio range in miles. */
+
+        float g_altitude_ft;		/* Feet above median sea level.  */
+
+        char g_mfr[80];			/* Manufacturer or application. */
+
+        char g_mic_e_status[32];	/* 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_query_type[12];		/* General Query: APRS, IGATE, WX, ... */
+					/* Addressee is NOT set. */
+
+					/* Directed Station Query: exactly 5 characters. */
+					/* APRSD, APRST, PING?, ... */
+					/* Addressee is set. */
+	
+	double g_footprint_lat;		/* A general query may contain a foot print. */
+	double g_footprint_lon;		/* Set all to G_UNKNOWN if not used. */
+	float g_footprint_radius;	/* Radius in miles. */
+
+	char g_query_callsign[12];	/* Directed query may contain callsign.  */
+					/* e.g. tell me all objects from that callsign. */
+
+
+        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, int quiet);
+
+extern void decode_aprs_print (decode_aprs_t *A);
+
+
 #endif
\ No newline at end of file
diff --git a/dedupe.c b/dedupe.c
index 396e279..66a035f 100644
--- a/dedupe.c
+++ b/dedupe.c
@@ -1,243 +1,254 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2013  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Name:	dedupe.c
- *
- * Purpose:	Avoid transmitting duplicate packets which are too
- *		close together.
- *
- *
- * Description:	We want to avoid digipeating duplicate packets to
- *		to help reduce radio channel congestion with 
- *		redundant information.
- *		Duplicate packets can occur in several ways:
- *
- *		(1) A digipeated packet can loop between 2 or more
- *			digipeaters.  For example:
- *
- *			W1ABC>APRS,WIDE3-3
- *			W1ABC>APRS,mycall*,WIDE3-2
- *			W1ABC>APRS,mycall,RPT1*,WIDE3-1
- *			W1ABC>APRS,mycall,RPT1,mycall*
- *
- *		(2) We could hear our own original transmission
- *			repeated by someone else.  Example:
- *
- *			mycall>APRS,WIDE3-3
- *			mycall>APRS,RPT1*,WIDE3-2
- *			mycall>APRS,RPT1*,mycall*,WIDE3-1
- *
- *		(3) We could hear the same packet from multiple
- *			digipeaters (with or without the original).
- *
- *			W1ABC>APRS,WIDE3-2
- *			W1ABC>APRS,RPT1*,WIDE3-2
- *			W1ABC>APRS,RPT2*,WIDE3-2
- *			W1ABC>APRS,RPT3*,WIDE3-2
- *
- *		(4) Someone could be sending the same thing over and 
- *			over with very little delay in between.
- *
- *			W1ABC>APRS,WIDE3-3
- *			W1ABC>APRS,WIDE3-3
- *			W1ABC>APRS,WIDE3-3
- *
- *		We can catch the first two by looking for 'mycall' in
- *		the source or digipeater fields.
- *
- *		The other two cases require us to keep a record of what
- *		we transmitted recently and test for duplicates that 
- *		should be dropped.
- *		
- *		Once we have the solution to catch cases (3) and (4)
- *		there is no reason for the special case of looking for
- *		mycall.  The same technique catches all four situations.
- *
- *		For detecting duplicates, we need to look
- *			+ source station
- *			+ destination 
- *			+ information field
- *		but NOT the changing list of digipeaters.
- *
- *		Typically, only a checksum is kept to reduce memory 
- *		requirements and amount of compution for comparisons.
- *		There is a very very small probability that two unrelated 
- *		packets will result in the same checksum, and the
- *		undesired dropping of the packet.
- *
- * References:	Original APRS specification:
- *
- *			TBD...
- *
- *		"The New n-N Paradigm"
- *
- *			http://www.aprs.org/fix14439.html
- *		
- *------------------------------------------------------------------*/
-
-#define DEDUPE_C
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdio.h>
-#include <time.h>
-
-
-#include "ax25_pad.h"
-#include "dedupe.h"
-#include "fcs_calc.h"
-#include "textcolor.h"
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	dedupe_init
- * 
- * Purpose:	Initialize the duplicate detection subsystem.
- *
- * Input:	ttl	- Number of seconds to retain information
- *			  about recent transmissions.
- *	
- *		
- * Returns:	None
- *
- * Description:	This should be called at application startup.
- *
- *		
- *------------------------------------------------------------------------------*/
-
-static int history_time = 30;		/* Number of seconds to keep information */
-					/* about recent transmissions. */
-
-#define HISTORY_MAX 25			/* Maximum number of transmission */
-					/* records to keep.  If we run out of */
-					/* room the oldest ones are overwritten */
-					/* before they expire. */
-
-static int insert_next;			/* Index, in array below, where next */
-					/* item should be stored. */
-					
-static struct {
-
-	time_t time_stamp;		/* When the packet was transmitted. */
-
-	unsigned short checksum;	/* Some sort of checksum for the */
-					/* source, destination, and information. */
-					/* is is not used anywhere else. */
-
-	short xmit_channel;		/* Radio channel number. */
-
-} history[HISTORY_MAX];
-
-
-void dedupe_init (int ttl)
-{
-	history_time = ttl;
-	insert_next = 0;
-	memset (history, 0, sizeof(history));
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	dedupe_remember
- * 
- * Purpose:	Save information about a packet being transmitted so we
- *		can detect, and avoid, duplicates later.
- *
- * Input:	pp	- Pointer to packet object.
- *		
- *		chan	- Radio channel for transmission.
- *		
- * Returns:	None
- *
- * Rambling:	At one time, my thinking is that we want to keep track of
- *		ALL transmitted packets regardless of origin or type.
- *
- *			+ my beacons
- *			+ anything from a connected application 
- *			+ anything digipeated
- *
- *		The easiest way to catch all cases is to call dedup_remember()
- *		from inside tq_append().  
- *
- *		But I don't think that is the right approach.
- *		When acting as a KISS TNC, we should just shovel everything
- *		through and not question what the application is doing.
- *		If the connected application has a digipeating function,
- *		it's responsible for those decisions.
- *
- *		My current thinking is that dedupe_remember() should be 
- *		called BEFORE tq_append() in the digipeater case.
- *
- *		We should also capture our own beacon transmissions.
- *		
- *------------------------------------------------------------------------------*/
-
-void dedupe_remember (packet_t pp, int chan)
-{
-	history[insert_next].time_stamp = time(NULL);
-	history[insert_next].checksum = ax25_dedupe_crc(pp);
-	history[insert_next].xmit_channel = chan;
-
-	insert_next++;
-	if (insert_next >= HISTORY_MAX) {
-	  insert_next = 0;
-	}
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	dedupe_check
- * 
- * Purpose:	Check whether this is a duplicate of another sent recently.
- *
- * Input:	pp	- Pointer to packet object.
- *		
- *		chan	- Radio channel for transmission.
- *		
- * Returns:	True if it is a duplicate.
- *
- *		
- *------------------------------------------------------------------------------*/
-
-int dedupe_check (packet_t pp, int chan)
-{
-	unsigned short crc = ax25_dedupe_crc(pp);
-	time_t now = time(NULL);
-	int j;
-
-	for (j=0; j<HISTORY_MAX; j++) {
-	  if (history[j].time_stamp >= now - history_time &&
-	      history[j].checksum == crc && 
-	      history[j].xmit_channel == chan) {
-	    return 1;
-	  }
-	}
-	return 0;
-}
-
-
-/* end dedupe.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:	dedupe.c
+ *
+ * Purpose:	Avoid transmitting duplicate packets which are too
+ *		close together.
+ *
+ *
+ * Description:	We want to avoid digipeating duplicate packets to
+ *		to help reduce radio channel congestion with 
+ *		redundant information.
+ *		Duplicate packets can occur in several ways:
+ *
+ *		(1) A digipeated packet can loop between 2 or more
+ *			digipeaters.  For example:
+ *
+ *			W1ABC>APRS,WIDE3-3
+ *			W1ABC>APRS,mycall*,WIDE3-2
+ *			W1ABC>APRS,mycall,RPT1*,WIDE3-1
+ *			W1ABC>APRS,mycall,RPT1,mycall*
+ *
+ *		(2) We could hear our own original transmission
+ *			repeated by someone else.  Example:
+ *
+ *			mycall>APRS,WIDE3-3
+ *			mycall>APRS,RPT1*,WIDE3-2
+ *			mycall>APRS,RPT1*,mycall*,WIDE3-1
+ *
+ *		(3) We could hear the same packet from multiple
+ *			digipeaters (with or without the original).
+ *
+ *			W1ABC>APRS,WIDE3-2
+ *			W1ABC>APRS,RPT1*,WIDE3-2
+ *			W1ABC>APRS,RPT2*,WIDE3-2
+ *			W1ABC>APRS,RPT3*,WIDE3-2
+ *
+ *		(4) Someone could be sending the same thing over and 
+ *			over with very little delay in between.
+ *
+ *			W1ABC>APRS,WIDE3-3
+ *			W1ABC>APRS,WIDE3-3
+ *			W1ABC>APRS,WIDE3-3
+ *
+ *		We can catch the first two by looking for 'mycall' in
+ *		the source or digipeater fields.
+ *
+ *		The other two cases require us to keep a record of what
+ *		we transmitted recently and test for duplicates that 
+ *		should be dropped.
+ *		
+ *		Once we have the solution to catch cases (3) and (4)
+ *		there is no reason for the special case of looking for
+ *		mycall.  The same technique catches all four situations.
+ *
+ *		For detecting duplicates, we need to look
+ *			+ source station
+ *			+ destination 
+ *			+ information field
+ *		but NOT the changing list of digipeaters.
+ *
+ *		Typically, only a checksum is kept to reduce memory 
+ *		requirements and amount of compution for comparisons.
+ *		There is a very very small probability that two unrelated 
+ *		packets will result in the same checksum, and the
+ *		undesired dropping of the packet.
+ *
+ * References:	Original APRS specification:
+ *
+ *			TBD...
+ *
+ *		"The New n-N Paradigm"
+ *
+ *			http://www.aprs.org/fix14439.html
+ *		
+ *------------------------------------------------------------------*/
+
+#define DEDUPE_C
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <time.h>
+
+
+#include "ax25_pad.h"
+#include "dedupe.h"
+#include "fcs_calc.h"
+#include "textcolor.h"
+#ifndef DIGITEST
+#include "igate.h"
+#endif
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	dedupe_init
+ * 
+ * Purpose:	Initialize the duplicate detection subsystem.
+ *
+ * Input:	ttl	- Number of seconds to retain information
+ *			  about recent transmissions.
+ *	
+ *		
+ * Returns:	None
+ *
+ * Description:	This should be called at application startup.
+ *
+ *		
+ *------------------------------------------------------------------------------*/
+
+static int history_time = 30;		/* Number of seconds to keep information */
+					/* about recent transmissions. */
+
+#define HISTORY_MAX 25			/* Maximum number of transmission */
+					/* records to keep.  If we run out of */
+					/* room the oldest ones are overwritten */
+					/* before they expire. */
+
+static int insert_next;			/* Index, in array below, where next */
+					/* item should be stored. */
+					
+static struct {
+
+	time_t time_stamp;		/* When the packet was transmitted. */
+
+	unsigned short checksum;	/* Some sort of checksum for the */
+					/* source, destination, and information. */
+					/* is is not used anywhere else. */
+
+	short xmit_channel;		/* Radio channel number. */
+
+} history[HISTORY_MAX];
+
+
+void dedupe_init (int ttl)
+{
+	history_time = ttl;
+	insert_next = 0;
+	memset (history, 0, sizeof(history));
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	dedupe_remember
+ * 
+ * Purpose:	Save information about a packet being transmitted so we
+ *		can detect, and avoid, duplicates later.
+ *
+ * Input:	pp	- Pointer to packet object.
+ *		
+ *		chan	- Radio channel for transmission.
+ *		
+ * Returns:	None
+ *
+ * Rambling:	At one time, my thinking is that we want to keep track of
+ *		ALL transmitted packets regardless of origin or type.
+ *
+ *			+ my beacons
+ *			+ anything from a connected application 
+ *			+ anything digipeated
+ *
+ *		The easiest way to catch all cases is to call dedup_remember()
+ *		from inside tq_append().  
+ *
+ *		But I don't think that is the right approach.
+ *		When acting as a KISS TNC, we should just shovel everything
+ *		through and not question what the application is doing.
+ *		If the connected application has a digipeating function,
+ *		it's responsible for those decisions.
+ *
+ *		My current thinking is that dedupe_remember() should be 
+ *		called BEFORE tq_append() in the digipeater case.
+ *
+ *		We should also capture our own beacon transmissions.
+ *		
+ *------------------------------------------------------------------------------*/
+
+void dedupe_remember (packet_t pp, int chan)
+{
+	history[insert_next].time_stamp = time(NULL);
+	history[insert_next].checksum = ax25_dedupe_crc(pp);
+	history[insert_next].xmit_channel = chan;
+
+	insert_next++;
+	if (insert_next >= HISTORY_MAX) {
+	  insert_next = 0;
+	}
+
+	/* If we send something by digipeater, we don't */
+	/* want to do it again if it comes from APRS-IS. */
+	/* Not sure about the other way around. */
+
+#ifndef DIGITEST
+	ig_to_tx_remember (pp, chan, 1);
+#endif
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	dedupe_check
+ * 
+ * Purpose:	Check whether this is a duplicate of another sent recently.
+ *
+ * Input:	pp	- Pointer to packet object.
+ *		
+ *		chan	- Radio channel for transmission.
+ *		
+ * Returns:	True if it is a duplicate.
+ *
+ *		
+ *------------------------------------------------------------------------------*/
+
+int dedupe_check (packet_t pp, int chan)
+{
+	unsigned short crc = ax25_dedupe_crc(pp);
+	time_t now = time(NULL);
+	int j;
+
+	for (j=0; j<HISTORY_MAX; j++) {
+	  if (history[j].time_stamp >= now - history_time &&
+	      history[j].checksum == crc && 
+	      history[j].xmit_channel == chan) {
+	    return 1;
+	  }
+	}
+	return 0;
+}
+
+
+/* end dedupe.c */
diff --git a/dedupe.h b/dedupe.h
index abac2ae..9c0613c 100644
--- a/dedupe.h
+++ b/dedupe.h
@@ -1,10 +1,10 @@
-
-
-void dedupe_init (int ttl);
-
-void dedupe_remember (packet_t pp, int chan);
-
-int dedupe_check (packet_t pp, int chan);
-
-
-/* end dedupe.h */
+
+
+void dedupe_init (int ttl);
+
+void dedupe_remember (packet_t pp, int chan);
+
+int dedupe_check (packet_t pp, int chan);
+
+
+/* end dedupe.h */
diff --git a/demod.c b/demod.c
index 282c704..a4944af 100644
--- a/demod.c
+++ b/demod.c
@@ -1,854 +1,899 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-// 
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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     /* display debugging info */
-
-// #define DEBUG3 1	/* print carrier detect changes. */
-
-// #define DEBUG4 1	/* capture AFSK demodulator output to log files */
-
-// #define DEBUG5 1	/* capture 9600 output to log files */
-
-
-/*------------------------------------------------------------------
- *
- * Module:      demod.c
- *
- * Purpose:   	Common entry point for multiple types of demodulators.
- *		
- * Input:	Audio samples from either a file or the "sound card."
- *
- * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <assert.h>
-#include <ctype.h>
-
-#include "direwolf.h"
-#include "audio.h"
-#include "demod.h"
-#include "tune.h"
-#include "fsk_demod_state.h"
-#include "fsk_gen_filter.h"
-#include "fsk_fast_filter.h"
-#include "hdlc_rec.h"
-#include "textcolor.h"
-#include "demod_9600.h"
-#include "demod_afsk.h"
-
-
-
-// Properties of the radio channels.
-
-static struct audio_s          *save_audio_config_p;
-
-
-// Current state of all the decoders.
-
-static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS];
-
-
-#define UPSAMPLE 2
-
-static int sample_sum[MAX_CHANS][MAX_SUBCHANS];
-static int sample_count[MAX_CHANS][MAX_SUBCHANS];
-
-
-/*------------------------------------------------------------------
- *
- * Name:        demod_init
- *
- * Purpose:     Initialize the demodulator(s) used for reception.
- *
- * Inputs:      pa		- Pointer to audio_s structure with
- *				  various parameters for the modem(s).
- *
- * Returns:     0 for success, -1 for failure.
- *		
- *
- * Bugs:	This doesn't do much error checking so don't give it
- *		anything crazy.
- *
- *----------------------------------------------------------------*/
-
-int demod_init (struct audio_s *pa)
-{
-	int j;
-	int chan;		/* Loop index over number of radio channels. */
-	char profile;
-	
-
-
-/*
- * Save audio configuration for later use.
- */
-
-	save_audio_config_p = pa;
-
-	for (chan = 0; chan < MAX_CHANS; chan++) {
-
-	 if (save_audio_config_p->achan[chan].valid) {
-
-	  char *p;
-	  char just_letters[16];
-	  int num_letters;
-	  int have_plus;
-
-	  switch (save_audio_config_p->achan[chan].modem_type) {
-
-	    case MODEM_OFF:
-	      break;
-
-	    case MODEM_AFSK:
-
-/*
- * Tear apart the profile and put it back together in a normalized form:
- *	- At least one letter, supply suitable default if necessary.
- *	- Upper case only.
- *	- Any plus will be at the end.
- */
-	      num_letters = 0;
-	      just_letters[num_letters] = '\0';
-	      have_plus = 0;
-	      for (p = save_audio_config_p->achan[chan].profiles; *p != '\0'; p++) {
-
-	        if (islower(*p)) {
-	          just_letters[num_letters] = toupper(*p);
-	          num_letters++;
-	          just_letters[num_letters] = '\0';
-	        }
-
-	        else if (isupper(*p)) {
-	          just_letters[num_letters] = *p;
-	          num_letters++;
-	          just_letters[num_letters] = '\0';
-	        }
-
-	        else if (*p == '+') {
-	          have_plus = 1;
-	          if (p[1] != '\0') {
-		    text_color_set(DW_COLOR_ERROR);
-		    dw_printf ("Channel %d: + option must appear at end of demodulator types \"%s\" \n", 
-					chan, save_audio_config_p->achan[chan].profiles);
-		  }	    
-	        }
-
-	        else if (*p == '-') {
-	          have_plus = -1;
-	          if (p[1] != '\0') {
-		    text_color_set(DW_COLOR_ERROR);
-		    dw_printf ("Channel %d: - option must appear at end of demodulator types \"%s\" \n", 
-					chan, save_audio_config_p->achan[chan].profiles);
-		  }	
-    
-	        } else {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Channel %d: Demodulator types \"%s\" can contain only letters and + - characters.\n", 
-					chan, save_audio_config_p->achan[chan].profiles);
-	        }
-	      }
-
-	      assert (num_letters == strlen(just_letters));
-
-/*
- * Pick a good default demodulator if none specified. 
- */
-	      if (num_letters == 0) {
-
-	        if (save_audio_config_p->achan[chan].baud < 600) {
-
-	          /* This has been optimized for 300 baud. */
-
-	          strcpy (just_letters, "D");
-
-	        }
-	        else {
-#if __arm__
-	          /* We probably don't have a lot of CPU power available. */
-	          /* Previously we would use F if possible otherwise fall back to A. */
-#if 0
-	          if (save_audio_config_p->achan[chan].baud == FFF_BAUD &&
-		      save_audio_config_p->achan[chan].mark_freq == FFF_MARK_FREQ && 
-		      save_audio_config_p->achan[chan].space_freq == FFF_SPACE_FREQ &&
-		      save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec == FFF_SAMPLES_PER_SEC) {
-
-	            just_letters[0] = FFF_PROFILE;
-	            just_letters[1] = '\0';
-	          }
-	          else {
-	            strcpy (just_letters, "A");
-	          }
-#else
-	          /* In version 1.2, new default is E+ /3. */
-	          strcpy (just_letters, "E");			// version 1.2 now E.
-	          if (have_plus != -1) have_plus = 1;		// Add as default for version 1.2
-								// If not explicitly turned off.
-	          if (save_audio_config_p->achan[chan].decimate == 0) {
-	            save_audio_config_p->achan[chan].decimate = 3;
-	          }
-#endif
-
-#else
-	          strcpy (just_letters, "E");			// version 1.2 changed C to E.
-	          if (have_plus != -1) have_plus = 1;		// Add as default for version 1.2
-								// If not explicitly turned off.
-#endif
-	        }
-	        num_letters = 1;
-	      }
-
-	      assert (num_letters == strlen(just_letters));
-
-/*
- * Put it back together again.
- */
-
-		/* At this point, have_plus can have 3 values: */
-		/* 	1 = turned on, either explicitly or by applied default */
-		/*	-1 = explicitly turned off.  change to 0 here so it is false. */
-		/* 	0 = off by default. */
-
-	      if (have_plus == -1) have_plus = 0;
-
-	      strcpy (save_audio_config_p->achan[chan].profiles, just_letters);
-	      
-	      assert (strlen(save_audio_config_p->achan[chan].profiles) >= 1);
-
-	      if (have_plus) {
-	        strcat (save_audio_config_p->achan[chan].profiles, "+");
-	      }
-
-	      /* These can be increased later for the multi-frequency case. */
-
-	      save_audio_config_p->achan[chan].num_subchan = num_letters;
-	      save_audio_config_p->achan[chan].num_demod = num_letters;		
-	   
-
-/*
- * Some error checking - Can use only one of these:
- *
- *	- Multiple letters.
- *	- New + multi-slicer.
- *	- Multiple frequencies.
- */
-
-	      if (have_plus && num_letters > 1) {
-
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Channel %d: Demodulator + option can't be combined with multiple letters.\n", chan);
-
-	          strcpy (save_audio_config_p->achan[chan].profiles, "C+");	// Reduce to one letter.
-		  num_letters = 1;
-	          save_audio_config_p->achan[chan].num_demod = 1;
-	          save_audio_config_p->achan[chan].num_subchan = 1;	// Will be set higher later.
-	          save_audio_config_p->achan[chan].num_freq = 1;
-	      }
-
-	      if (have_plus && save_audio_config_p->achan[chan].num_freq > 1) {
-
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Channel %d: Demodulator + option can't be combined with multiple frequencies.\n", chan);
-
-	          save_audio_config_p->achan[chan].num_demod = 1;
-	          save_audio_config_p->achan[chan].num_subchan = 1;	// Will be set higher later.
-	          save_audio_config_p->achan[chan].num_freq = 1;
-	      }
-
-	      if (num_letters > 1 && save_audio_config_p->achan[chan].num_freq > 1) {
-
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Channel %d: Multiple demodulator types can't be combined with multiple frequencies.\n", chan);
-
-	          save_audio_config_p->achan[chan].profiles[1] = '\0';
-		  num_letters = 1;
-	      }
-
-	      if (save_audio_config_p->achan[chan].decimate == 0) {
-	        save_audio_config_p->achan[chan].decimate = 1;
-		if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
-		  save_audio_config_p->achan[chan].decimate = 3;
-		}
-	      }
-
-	      text_color_set(DW_COLOR_DEBUG);
-	      dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate",
-		    chan, save_audio_config_p->achan[chan].baud, 
-		    save_audio_config_p->achan[chan].mark_freq, save_audio_config_p->achan[chan].space_freq,
-		    save_audio_config_p->achan[chan].profiles,
-		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
-	      if (save_audio_config_p->achan[chan].decimate != 1) 
-	        dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
-	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) 
-	        dw_printf (", DTMF decoder enabled");
-	      dw_printf (".\n");
-
-
-/* 
- * Initialize the demodulator(s).
- *
- * We have 3 cases to consider.
- */
-
-
-	      if (num_letters > 1) {
-	        int d;
-
-/*	
- * Multiple letters, usually for 1200 baud.
- * Each one corresponds to a demodulator and subchannel.
- *
- * An interesting experiment but probably not too useful.
- * Can't have multiple frequency pairs or the + option.
- */
-
-	        save_audio_config_p->achan[chan].num_subchan = num_letters;
-	        save_audio_config_p->achan[chan].num_demod = num_letters;
-		
-
-		if (save_audio_config_p->achan[chan].num_demod != num_letters) {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_demod(%d) != strlen(\"%s\")\n",
-				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_demod, save_audio_config_p->achan[chan].profiles);
-		}
-
-	        if (save_audio_config_p->achan[chan].num_freq != 1) {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
-				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
-		}
-	  
-	        for (d = 0; d < save_audio_config_p->achan[chan].num_demod; d++) {
-
-	          int mark, space;
-	          assert (d >= 0 && d < MAX_SUBCHANS);
-
-	          struct demodulator_state_s *D;
-	          D = &demodulator_state[chan][d];
-
-	          profile = save_audio_config_p->achan[chan].profiles[d];
-	          mark = save_audio_config_p->achan[chan].mark_freq;
-	          space = save_audio_config_p->achan[chan].space_freq;
-
-	          if (save_audio_config_p->achan[chan].num_demod != 1) {
-	            text_color_set(DW_COLOR_DEBUG);
-	            dw_printf ("        %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
-	          }
-      
-	          demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
-			    save_audio_config_p->achan[chan].baud,
-		            mark, 
-	                    space,
-			    profile,
-			    D);
-
-	          /* For siginal level reporting, we want a longer term view. */
-		  // TODO: Should probably move this into the init functions.
-
-	          D->quick_attack = D->agc_fast_attack * 0.2;
-	          D->sluggish_decay = D->agc_slow_decay * 0.2;
-	        }
-	      }
-	      else if (have_plus) {
-	       
-/*
- * PLUS - which implies we have only one letter and one frequency pair.
- *
- * One demodulator feeds multiple slicers, each a subchannel.
- */
-
-	        if (num_letters != 1) {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
-				__FILE__, __LINE__, chan, just_letters);
-		}
-
-	        if (save_audio_config_p->achan[chan].num_freq != 1) {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
-				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
-		}
-
-	        if (save_audio_config_p->achan[chan].num_demod != save_audio_config_p->achan[chan].num_demod) {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_demod(%d)\n",
-				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_demod);
-		}
-	  
-
-	        struct demodulator_state_s *D;
-	        D = &demodulator_state[chan][0];
-
-		/* I'm not happy about putting this hack here. */
-		/* This belongs in demod_afsk_init but it doesn't have access to the audio config. */
-
-	        save_audio_config_p->achan[chan].num_demod = 1;
-	        save_audio_config_p->achan[chan].num_subchan = MAX_SUBCHANS;
-     
-	        demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
-			save_audio_config_p->achan[chan].baud,
-			save_audio_config_p->achan[chan].mark_freq, 
-	                save_audio_config_p->achan[chan].space_freq,
-			save_audio_config_p->achan[chan].profiles[0],
-			D);
-
-		/* I'm not happy about putting this hack here. */
-		/* should pass in as a parameter rather than adding on later. */
-
-		D->num_slicers = MAX_SUBCHANS;
-
-	        /* For siginal level reporting, we want a longer term view. */
-
-	        D->quick_attack = D->agc_fast_attack * 0.2;
-	        D->sluggish_decay = D->agc_slow_decay * 0.2;
-	      }	
-	      else {
-	        int d;
-/*
- * One letter.
- * Can be combined with multiple frequencies.
- */
-
-	        if (num_letters != 1) {
-		  text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
-				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].profiles);
-		}
-
-	        save_audio_config_p->achan[chan].num_demod = save_audio_config_p->achan[chan].num_freq;
-	        save_audio_config_p->achan[chan].num_subchan = save_audio_config_p->achan[chan].num_freq;
-
-	        for (d = 0; d < save_audio_config_p->achan[chan].num_freq; d++) {
-
-	          int mark, space, k;
-	          assert (d >= 0 && d < MAX_SUBCHANS);
-
-	          struct demodulator_state_s *D;
-	          D = &demodulator_state[chan][d];
-
-	          profile = save_audio_config_p->achan[chan].profiles[0];
-
-	          k = d * save_audio_config_p->achan[chan].offset - ((save_audio_config_p->achan[chan].num_freq - 1) * save_audio_config_p->achan[chan].offset) / 2;
-	          mark = save_audio_config_p->achan[chan].mark_freq + k;
-	          space = save_audio_config_p->achan[chan].space_freq + k;
-
-	          if (save_audio_config_p->achan[chan].num_freq != 1) {
-	            text_color_set(DW_COLOR_DEBUG);
-	            dw_printf ("        %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
-	          }
-      
-	          demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
-			save_audio_config_p->achan[chan].baud,
-			mark, space,
-			profile,
-			D);
-
-	          /* For siginal level reporting, we want a longer term view. */
-
-	          D->quick_attack = D->agc_fast_attack * 0.2;
-	          D->sluggish_decay = D->agc_slow_decay * 0.2;
-
-	        } 	  /* for each freq pair */
-	      }	
-	      break;
-
-//TODO: how about MODEM_OFF case?
-
-	    case MODEM_BASEBAND:
-	    case MODEM_SCRAMBLE:
-	    default:	/* Not AFSK */
-	      {
-
-	      if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) {
-
-		/* Apply default if not set earlier. */
-		/* Not sure if it should be on for ARM too. */
-		/* Need to take a look at CPU usage and performance difference. */
-
-#ifndef __arm__
-	        strcpy (save_audio_config_p->achan[chan].profiles, "+");
-#endif
-	      }
-
-	      text_color_set(DW_COLOR_DEBUG);
-	      dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d",
-		    chan, save_audio_config_p->achan[chan].baud, 
-		    save_audio_config_p->achan[chan].profiles,
-		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, UPSAMPLE);
-	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) 
-	        dw_printf (", DTMF decoder enabled");
-	      dw_printf (".\n");
-	      
-	      struct demodulator_state_s *D;
-	      D = &demodulator_state[chan][0];	// first subchannel
-
-	      save_audio_config_p->achan[chan].num_subchan = 1;
-	      save_audio_config_p->achan[chan].num_demod = 1;		
-
-	      if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
-
-		/* I'm not happy about putting this hack here. */
-		/* This belongs in demod_9600_init but it doesn't have access to the audio config. */
-
-	        save_audio_config_p->achan[chan].num_demod = 1;
-	        save_audio_config_p->achan[chan].num_subchan = MAX_SUBCHANS;
-     	      }
-	        
-	      demod_9600_init (UPSAMPLE * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D);
-
-	      if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
-
-		/* I'm not happy about putting this hack here. */
-		/* should pass in as a parameter rather than adding on later. */
-
-		D->num_slicers = MAX_SUBCHANS;
-	      }
-
-	      /* For siginal level reporting, we want a longer term view. */
-
-	      D->quick_attack = D->agc_fast_attack * 0.2;
-	      D->sluggish_decay = D->agc_slow_decay * 0.2;
-	      }
-	      break;
-
-	  }  /* switch on modulation type. */
-    
-	 }  /* if channel number is valid */
-
-	}  /* for chan ... */
-
-
-        return (0);
-
-} /* end demod_init */
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        demod_get_sample
- *
- * Purpose:     Get one audio sample fromt the specified sound input source.
- *
- * Inputs:	a	- Index for audio device.  0 = first.
- *
- * Returns:     -32768 .. 32767 for a valid audio sample.
- *              256*256 for end of file or other error.
- *
- * Global In:	save_audio_config_p->adev[ACHAN2ADEV(chan)].bits_per_sample - So we know whether to 
- *			read 1 or 2 bytes from audio stream.
- *
- * Description:	Grab 1 or two btyes depending on data source.
- *
- *		When processing stereo, the caller will call this
- *		at twice the normal rate to obtain alternating left 
- *		and right samples.
- *
- *----------------------------------------------------------------*/
-
-#define FSK_READ_ERR (256*256)
-
-
-__attribute__((hot))
-int demod_get_sample (int a)		
-{
-	int x1, x2;
-	signed short sam;	/* short to force sign extention. */
-
-
-	assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16);
-
-
-	if (save_audio_config_p->adev[a].bits_per_sample == 8) {
-
-	  x1 = audio_get(a);				
-	  if (x1 < 0) return(FSK_READ_ERR);
-
-	  assert (x1 >= 0 && x1 <= 255);
-
-	  /* Scale 0..255 into -32k..+32k */
-
-	  sam = (x1 - 128) * 256;
-
-	}
-	else {
-	  x1 = audio_get(a);	/* lower byte first */
-	  if (x1 < 0) return(FSK_READ_ERR);
-
-	  x2 = audio_get(a);
-	  if (x2 < 0) return(FSK_READ_ERR);
-
-	  assert (x1 >= 0 && x1 <= 255);
-	  assert (x2 >= 0 && x2 <= 255);
-
-          sam = ( x2 << 8 ) | x1;
-	}
-
-	return (sam);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        demod_process_sample
- *
- * Purpose:     (1) Demodulate the AFSK signal.
- *		(2) Recover clock and data.
- *
- * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
- *		subchan - modem of the channel.
- *		sam	- One sample of audio.
- *			  Should be in range of -32768 .. 32767.
- *
- * Returns:	None 
- *
- * Descripion:	We start off with two bandpass filters tuned to
- *		the given frequencies.  In the case of VHF packet
- *		radio, this would be 1200 and 2200 Hz.
- *
- *		The bandpass filter amplitudes are compared to 
- *		obtain the demodulated signal.
- *
- *		We also have a digital phase locked loop (PLL)
- *		to recover the clock and pick out data bits at
- *		the proper rate.
- *
- *		For each recovered data bit, we call:
- *
- *			  hdlc_rec (channel, demodulated_bit);
- *
- *		to decode HDLC frames from the stream of bits.
- *
- * Future:	This could be generalized by passing in the name
- *		of the function to be called for each bit recovered
- *		from the demodulator.  For now, it's simply hard-coded.
- *
- *--------------------------------------------------------------------*/
-
-
-__attribute__((hot))
-void demod_process_sample (int chan, int subchan, int sam)
-{
-	float fsam, abs_fsam;
-	int k;
-
-
-#if DEBUG4
-	static FILE *demod_log_fp = NULL;
-	static int seq = 0;			/* for log file name */
-#endif
-
-	int j;
-	int demod_data;
-	struct demodulator_state_s *D;
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-	D = &demodulator_state[chan][subchan];
-
-
-	/* Scale to nice number, actually -2.0 to +2.0 for extra headroom */
-
-	fsam = sam / 16384.0f;
-
-/*
- * Accumulate measure of the input signal level.
- */
-
-
-/*
- * Version 1.2: Try new approach to capturing the amplitude.
- * This is same as the later AGC without the normalization step.
- * We want decay to be substantially slower to get a longer
- * range idea of the received audio.
- */
-
-	if (fsam >= D->alevel_rec_peak) {
-	  D->alevel_rec_peak = fsam * D->quick_attack + D->alevel_rec_peak * (1.0f - D->quick_attack);
-	}
-	else {
-	  D->alevel_rec_peak = fsam * D->sluggish_decay + D->alevel_rec_peak * (1.0f - D->sluggish_decay);
-	}
-
-	if (fsam <= D->alevel_rec_valley) {
-	  D->alevel_rec_valley = fsam * D->quick_attack + D->alevel_rec_valley * (1.0f - D->quick_attack);
-	}
-	else  {   
-	  D->alevel_rec_valley = fsam * D->sluggish_decay + D->alevel_rec_valley * (1.0f - D->sluggish_decay);
-	}
-
-
-/*
- * Select decoder based on modulation type.
- */
-
-	switch (save_audio_config_p->achan[chan].modem_type) {
-
-	  case MODEM_OFF:
-
-	    // Might have channel only listening to DTMF for APRStt gateway.
-	    // Don't waste CPU time running a demodulator here.
-	    break;
-
-	  case MODEM_AFSK:
-
-	    if (save_audio_config_p->achan[chan].decimate > 1) {
-
-	      sample_sum[chan][subchan] += sam;
-	      sample_count[chan][subchan]++;
-	      if (sample_count[chan][subchan] >= save_audio_config_p->achan[chan].decimate) {
-  	        demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / save_audio_config_p->achan[chan].decimate, D);
-	        sample_sum[chan][subchan] = 0;
-	        sample_count[chan][subchan] = 0;
-	      }
-	    }
-	    else {
-  	      demod_afsk_process_sample (chan, subchan, sam, D);
-	    }
-	    break;
-
-	  case MODEM_BASEBAND:
-	  case MODEM_SCRAMBLE:
-	  default:
-
-#define ZEROSTUFF 1
-
-	
-#if ZEROSTUFF
-	    /* Literature says this is better if followed */
-	    /* by appropriate low pass filter. */
-	    /* So far, both are same in tests with different */
-	    /* optimal low pass filter parameters. */
-
-	    for (k=1; k<UPSAMPLE; k++) {
-	      demod_9600_process_sample (chan, 0, D);
-	    }
-	    demod_9600_process_sample (chan, sam*UPSAMPLE, D);
-#else
-	    /* Linear interpolation. */
-	    static int prev_sam;
-	    switch (UPSAMPLE) {
-	      case 1:
-	        demod_9600_process_sample (chan, sam);
-
-	        break;
-	      case 2:
-	        demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
-	        demod_9600_process_sample (chan, sam, D);
-	        break;
-              case 3:
-                demod_9600_process_sample (chan, (2 * prev_sam + sam) / 3, D);
-                demod_9600_process_sample (chan, (prev_sam + 2 * sam) / 3, D);
-                demod_9600_process_sample (chan, sam, D);
-                break;
-              case 4:
-                demod_9600_process_sample (chan, (3 * prev_sam + sam) / 4, D);
-                demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
-                demod_9600_process_sample (chan, (prev_sam + 3 * sam) / 4, D);
-                demod_9600_process_sample (chan, sam, D);
-                break;
-              default:
-                assert (0);
-                break;
-	    }
-	    prev_sam = sam;
-#endif
-	    break;
-	}
-	return;
-
-} /* end demod_process_sample */
-
-
-
-
-
-
-/* Doesn't seem right.  Need to revisit this. */
-/* Resulting scale is 0 to almost 100. */
-/* Cranking up the input level produces no more than 97 or 98. */
-/* We currently produce a message when this goes over 90. */
-
-alevel_t demod_get_audio_level (int chan, int subchan) 
-{
-	struct demodulator_state_s *D;
-	alevel_t alevel;
-	int pk;
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-	/* We have to consider two different cases here. */
-	/* N demodulators, each with own slicer and HDLC decoder. */
-	/* Single demodulator, multiple slicers each with own HDLC decoder. */
-
-	if (demodulator_state[chan][0].num_slicers > 1) {
-	  subchan = 0;
-	}
-
-	D = &demodulator_state[chan][subchan];
-
-	// Take half of peak-to-peak for received audio level.
-
-	alevel.rec = (int) (( D->alevel_rec_peak - D->alevel_rec_valley ) * 50.0f + 0.5f);
-
-	if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) {
-
-	  /* For AFSK, we have mark and space amplitudes. */
-
-	  alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f);
-	  alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f);
-
-	  //alevel.ms_ratio = D->alevel_mark_peak / D->alevel_space_peak;	// TODO: remove after temp test
-	}
-	else {
-
-#if 1	
-	  /* Display the + and - peaks.  */
-	  /* Normally we'd expect them to be about the same. */
-	  /* However, with SDR, or other DC coupling, we could have an offset. */
-
-	  alevel.mark = (int) ((D->alevel_mark_peak) * 200.0f  + 0.5f);
-	  alevel.space = (int) ((D->alevel_space_peak) * 200.0f - 0.5f);
-
-
-#else
-	  /* Here we have + and - peaks after filtering. */
-	  /* Take half of the peak to peak. */
-	  /* The "5/6" factor worked out right for the current low pass filter. */
-	  /* Will it need to be different if the filter is tweaked? */
-
-	  alevel.mark = (int) ((D->alevel_mark_peak - D->alevel_space_peak) * 100.0f * 5.0f/6.0f + 0.5f);
-	  alevel.space = -1;		/* to print one number inside of ( ) */
-#endif
-	}
-	return (alevel);
-}
-
-
-/* end demod.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+// 
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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     /* display debugging info */
+
+// #define DEBUG3 1	/* print carrier detect changes. */
+
+// #define DEBUG4 1	/* capture AFSK demodulator output to log files */
+
+// #define DEBUG5 1	/* capture 9600 output to log files */
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      demod.c
+ *
+ * Purpose:   	Common entry point for multiple types of demodulators.
+ *		
+ * Input:	Audio samples from either a file or the "sound card."
+ *
+ * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "direwolf.h"
+#include "audio.h"
+#include "demod.h"
+#include "tune.h"
+#include "fsk_demod_state.h"
+#include "fsk_gen_filter.h"
+#include "fsk_fast_filter.h"
+#include "hdlc_rec.h"
+#include "textcolor.h"
+#include "demod_9600.h"
+#include "demod_afsk.h"
+
+
+
+// Properties of the radio channels.
+
+static struct audio_s          *save_audio_config_p;
+
+
+// Current state of all the decoders.
+
+static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS];
+
+
+#define UPSAMPLE 2
+
+static int sample_sum[MAX_CHANS][MAX_SUBCHANS];
+static int sample_count[MAX_CHANS][MAX_SUBCHANS];
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        demod_init
+ *
+ * Purpose:     Initialize the demodulator(s) used for reception.
+ *
+ * Inputs:      pa		- Pointer to audio_s structure with
+ *				  various parameters for the modem(s).
+ *
+ * Returns:     0 for success, -1 for failure.
+ *		
+ *
+ * Bugs:	This doesn't do much error checking so don't give it
+ *		anything crazy.
+ *
+ *----------------------------------------------------------------*/
+
+int demod_init (struct audio_s *pa)
+{
+	//int j;
+	int chan;		/* Loop index over number of radio channels. */
+	char profile;
+	
+
+
+/*
+ * Save audio configuration for later use.
+ */
+
+	save_audio_config_p = pa;
+
+	for (chan = 0; chan < MAX_CHANS; chan++) {
+
+	 if (save_audio_config_p->achan[chan].valid) {
+
+	  char *p;
+	  char just_letters[16];
+	  int num_letters;
+	  int have_plus;
+
+	  /*
+	   * These are derived from config file parameters.
+	   *
+	   * num_subchan is number of demodulators.
+	   * This can be increased by:
+	   *	Multiple frequencies.
+	   *	Multiple letters (not sure if I will continue this).
+	   *	New interleaved decoders.
+	   *
+	   * num_slicers is set to max by the "+" option.
+	   */
+
+	  save_audio_config_p->achan[chan].num_subchan = 1;
+	  save_audio_config_p->achan[chan].num_slicers = 1;
+
+	  switch (save_audio_config_p->achan[chan].modem_type) {
+
+	    case MODEM_OFF:
+	      break;
+
+	    case MODEM_AFSK:
+
+/*
+ * Tear apart the profile and put it back together in a normalized form:
+ *	- At least one letter, supply suitable default if necessary.
+ *	- Upper case only.
+ *	- Any plus will be at the end.
+ */
+	      num_letters = 0;
+	      just_letters[num_letters] = '\0';
+	      have_plus = 0;
+	      for (p = save_audio_config_p->achan[chan].profiles; *p != '\0'; p++) {
+
+	        if (islower(*p)) {
+	          just_letters[num_letters] = toupper(*p);
+	          num_letters++;
+	          just_letters[num_letters] = '\0';
+	        }
+
+	        else if (isupper(*p)) {
+	          just_letters[num_letters] = *p;
+	          num_letters++;
+	          just_letters[num_letters] = '\0';
+	        }
+
+	        else if (*p == '+') {
+	          have_plus = 1;
+	          if (p[1] != '\0') {
+		    text_color_set(DW_COLOR_ERROR);
+		    dw_printf ("Channel %d: + option must appear at end of demodulator types \"%s\" \n", 
+					chan, save_audio_config_p->achan[chan].profiles);
+		  }	    
+	        }
+
+	        else if (*p == '-') {
+	          have_plus = -1;
+	          if (p[1] != '\0') {
+		    text_color_set(DW_COLOR_ERROR);
+		    dw_printf ("Channel %d: - option must appear at end of demodulator types \"%s\" \n", 
+					chan, save_audio_config_p->achan[chan].profiles);
+		  }	
+    
+	        } else {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Channel %d: Demodulator types \"%s\" can contain only letters and + - characters.\n", 
+					chan, save_audio_config_p->achan[chan].profiles);
+	        }
+	      }
+
+	      assert (num_letters == strlen(just_letters));
+
+/*
+ * Pick a good default demodulator if none specified. 
+ */
+	      if (num_letters == 0) {
+
+	        if (save_audio_config_p->achan[chan].baud < 600) {
+
+	          /* This has been optimized for 300 baud. */
+
+	          strlcpy (just_letters, "D", sizeof(just_letters));
+
+	        }
+	        else {
+#if __arm__
+	          /* We probably don't have a lot of CPU power available. */
+	          /* Previously we would use F if possible otherwise fall back to A. */
+
+	          /* In version 1.2, new default is E+ /3. */
+	          strlcpy (just_letters, "E", sizeof(just_letters));			// version 1.2 now E.
+	          if (have_plus != -1) have_plus = 1;		// Add as default for version 1.2
+								// If not explicitly turned off.
+	          if (save_audio_config_p->achan[chan].decimate == 0) {
+	            if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
+	              save_audio_config_p->achan[chan].decimate = 3;
+	            }
+	          }
+#else
+	          strlcpy (just_letters, "E", sizeof(just_letters));			// version 1.2 changed C to E.
+	          if (have_plus != -1) have_plus = 1;		// Add as default for version 1.2
+								// If not explicitly turned off.
+#endif
+	        }
+	        num_letters = 1;
+	      }
+
+	      assert (num_letters == strlen(just_letters));
+
+/*
+ * Put it back together again.
+ */
+
+		/* At this point, have_plus can have 3 values: */
+		/* 	1 = turned on, either explicitly or by applied default */
+		/*	-1 = explicitly turned off.  change to 0 here so it is false. */
+		/* 	0 = off by default. */
+
+	      if (have_plus == -1) have_plus = 0;
+
+	      strlcpy (save_audio_config_p->achan[chan].profiles, just_letters, sizeof(save_audio_config_p->achan[chan].profiles));
+	      
+	      assert (strlen(save_audio_config_p->achan[chan].profiles) >= 1);
+
+	      if (have_plus) {
+	        strlcat (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
+	      }
+
+	      /* These can be increased later for the multi-frequency case. */
+
+	      save_audio_config_p->achan[chan].num_subchan = num_letters;
+	      save_audio_config_p->achan[chan].num_slicers = 1;
+
+/*
+ * Some error checking - Can use only one of these:
+ *
+ *	- Multiple letters.
+ *	- New + multi-slicer.
+ *	- Multiple frequencies.
+ */
+
+	      if (have_plus && save_audio_config_p->achan[chan].num_freq > 1) {
+
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Channel %d: Demodulator + option can't be combined with multiple frequencies.\n", chan);
+	          save_audio_config_p->achan[chan].num_subchan = 1;	// Will be set higher later.
+	          save_audio_config_p->achan[chan].num_freq = 1;
+	      }
+
+	      if (num_letters > 1 && save_audio_config_p->achan[chan].num_freq > 1) {
+
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Channel %d: Multiple demodulator types can't be combined with multiple frequencies.\n", chan);
+
+	          save_audio_config_p->achan[chan].profiles[1] = '\0';
+		  num_letters = 1;
+	      }
+
+	      if (save_audio_config_p->achan[chan].decimate == 0) {
+	        save_audio_config_p->achan[chan].decimate = 1;
+		if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
+		  save_audio_config_p->achan[chan].decimate = 3;
+		}
+	      }
+
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate",
+		    chan, save_audio_config_p->achan[chan].baud, 
+		    save_audio_config_p->achan[chan].mark_freq, save_audio_config_p->achan[chan].space_freq,
+		    save_audio_config_p->achan[chan].profiles,
+		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
+	      if (save_audio_config_p->achan[chan].decimate != 1) 
+	        dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
+	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) 
+	        dw_printf (", DTMF decoder enabled");
+	      dw_printf (".\n");
+
+
+/* 
+ * Initialize the demodulator(s).
+ *
+ * We have 3 cases to consider.
+ */
+
+// TODO1.3: revisit this logic now that it is less restrictive.
+
+	      if (num_letters > 1) {
+	        int d;
+
+/*	
+ * Multiple letters, usually for 1200 baud.
+ * Each one corresponds to a demodulator and subchannel.
+ *
+ * An interesting experiment but probably not too useful.
+ * Can't have multiple frequency pairs.
+ * In version 1.3 this can be combined with the + option.
+ */
+
+	        save_audio_config_p->achan[chan].num_subchan = num_letters;
+		
+/*
+ * Quick hack with special case for another experiment.
+ * Do this in a more general way if it turns out to be useful.
+ */
+	        save_audio_config_p->achan[chan].interleave = 1;
+	        if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EE") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 2;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEE") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 3;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEE") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 4;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEEE") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 5;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GG") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 2;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 3;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG+") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 3;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGG") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 4;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+	        else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGGG") == 0) {
+	          save_audio_config_p->achan[chan].interleave = 5;
+	          save_audio_config_p->achan[chan].decimate = 1;
+	        }
+
+		if (save_audio_config_p->achan[chan].num_subchan != num_letters) {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_subchan(%d) != strlen(\"%s\")\n",
+				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_subchan, save_audio_config_p->achan[chan].profiles);
+		}
+
+	        if (save_audio_config_p->achan[chan].num_freq != 1) {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
+				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
+		}
+
+	        for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
+	          int mark, space;
+	          assert (d >= 0 && d < MAX_SUBCHANS);
+
+	          struct demodulator_state_s *D;
+	          D = &demodulator_state[chan][d];
+
+	          profile = save_audio_config_p->achan[chan].profiles[d];
+	          mark = save_audio_config_p->achan[chan].mark_freq;
+	          space = save_audio_config_p->achan[chan].space_freq;
+
+	          if (save_audio_config_p->achan[chan].num_subchan != 1) {
+	            text_color_set(DW_COLOR_DEBUG);
+	            dw_printf ("        %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
+	          }
+
+	          demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / (save_audio_config_p->achan[chan].decimate * save_audio_config_p->achan[chan].interleave), 
+			    save_audio_config_p->achan[chan].baud,
+		            mark, 
+	                    space,
+			    profile,
+			    D);
+
+	          if (have_plus) {
+		    /* I'm not happy about putting this hack here. */
+		    /* should pass in as a parameter rather than adding on later. */
+
+	            save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
+		    D->num_slicers = MAX_SLICERS;
+	          }
+
+	          /* For siginal level reporting, we want a longer term view. */
+		  // TODO: Should probably move this into the init functions.
+
+	          D->quick_attack = D->agc_fast_attack * 0.2;
+	          D->sluggish_decay = D->agc_slow_decay * 0.2;
+	        }
+	      }
+	      else if (have_plus) {
+	       
+/*
+ * PLUS - which (formerly) implies we have only one letter and one frequency pair.
+ *
+ * One demodulator feeds multiple slicers, each a subchannel.
+ */
+
+	        if (num_letters != 1) {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
+				__FILE__, __LINE__, chan, just_letters);
+		}
+
+	        if (save_audio_config_p->achan[chan].num_freq != 1) {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
+				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
+		}
+
+	        if (save_audio_config_p->achan[chan].num_freq != save_audio_config_p->achan[chan].num_subchan) {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_subchan(%d)\n",
+				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_subchan);
+		}
+
+	        struct demodulator_state_s *D;
+	        D = &demodulator_state[chan][0];
+
+		/* I'm not happy about putting this hack here. */
+		/* This belongs in demod_afsk_init but it doesn't have access to the audio config. */
+
+	        save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
+     
+	        demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
+			save_audio_config_p->achan[chan].baud,
+			save_audio_config_p->achan[chan].mark_freq, 
+	                save_audio_config_p->achan[chan].space_freq,
+			save_audio_config_p->achan[chan].profiles[0],
+			D);
+
+	        if (have_plus) {
+		  /* I'm not happy about putting this hack here. */
+		  /* should pass in as a parameter rather than adding on later. */
+
+	          save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
+		  D->num_slicers = MAX_SLICERS;
+	        }
+
+	        /* For siginal level reporting, we want a longer term view. */
+
+	        D->quick_attack = D->agc_fast_attack * 0.2;
+	        D->sluggish_decay = D->agc_slow_decay * 0.2;
+	      }	
+	      else {
+	        int d;
+/*
+ * One letter.
+ * Can be combined with multiple frequencies.
+ */
+
+	        if (num_letters != 1) {
+		  text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
+				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].profiles);
+		}
+
+	        save_audio_config_p->achan[chan].num_subchan = save_audio_config_p->achan[chan].num_freq;
+
+	        for (d = 0; d < save_audio_config_p->achan[chan].num_freq; d++) {
+
+	          int mark, space, k;
+	          assert (d >= 0 && d < MAX_SUBCHANS);
+
+	          struct demodulator_state_s *D;
+	          D = &demodulator_state[chan][d];
+
+	          profile = save_audio_config_p->achan[chan].profiles[0];
+
+	          k = d * save_audio_config_p->achan[chan].offset - ((save_audio_config_p->achan[chan].num_freq - 1) * save_audio_config_p->achan[chan].offset) / 2;
+	          mark = save_audio_config_p->achan[chan].mark_freq + k;
+	          space = save_audio_config_p->achan[chan].space_freq + k;
+
+	          if (save_audio_config_p->achan[chan].num_freq != 1) {
+	            text_color_set(DW_COLOR_DEBUG);
+	            dw_printf ("        %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
+	          }
+      
+	          demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
+			save_audio_config_p->achan[chan].baud,
+			mark, space,
+			profile,
+			D);
+
+	          if (have_plus) {
+		    /* I'm not happy about putting this hack here. */
+		    /* should pass in as a parameter rather than adding on later. */
+
+	            save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
+		    D->num_slicers = MAX_SLICERS;
+	          }
+
+	          /* For siginal level reporting, we want a longer term view. */
+
+	          D->quick_attack = D->agc_fast_attack * 0.2;
+	          D->sluggish_decay = D->agc_slow_decay * 0.2;
+
+	        } 	  /* for each freq pair */
+	      }	
+	      break;
+
+//TODO: how about MODEM_OFF case?
+
+	    case MODEM_BASEBAND:
+	    case MODEM_SCRAMBLE:
+	    default:	/* Not AFSK */
+	      {
+
+	      if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) {
+
+		/* Apply default if not set earlier. */
+		/* Not sure if it should be on for ARM too. */
+		/* Need to take a look at CPU usage and performance difference. */
+
+#ifndef __arm__
+	        strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
+#endif
+	      }
+
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d",
+		    chan, save_audio_config_p->achan[chan].baud, 
+		    save_audio_config_p->achan[chan].profiles,
+		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, UPSAMPLE);
+	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) 
+	        dw_printf (", DTMF decoder enabled");
+	      dw_printf (".\n");
+	      
+	      struct demodulator_state_s *D;
+	      D = &demodulator_state[chan][0];	// first subchannel
+
+	      save_audio_config_p->achan[chan].num_subchan = 1;
+              save_audio_config_p->achan[chan].num_slicers = 1;
+
+	      if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
+
+		/* I'm not happy about putting this hack here. */
+		/* This belongs in demod_9600_init but it doesn't have access to the audio config. */
+
+	        save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
+     	      }
+	        
+	      demod_9600_init (UPSAMPLE * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D);
+
+	      if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
+
+		/* I'm not happy about putting this hack here. */
+		/* should pass in as a parameter rather than adding on later. */
+
+	        save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
+		D->num_slicers = MAX_SLICERS;
+	      }
+
+	      /* For siginal level reporting, we want a longer term view. */
+
+	      D->quick_attack = D->agc_fast_attack * 0.2;
+	      D->sluggish_decay = D->agc_slow_decay * 0.2;
+	      }
+	      break;
+
+	  }  /* switch on modulation type. */
+    
+	 }  /* if channel number is valid */
+
+	}  /* for chan ... */
+
+
+        return (0);
+
+} /* end demod_init */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        demod_get_sample
+ *
+ * Purpose:     Get one audio sample fromt the specified sound input source.
+ *
+ * Inputs:	a	- Index for audio device.  0 = first.
+ *
+ * Returns:     -32768 .. 32767 for a valid audio sample.
+ *              256*256 for end of file or other error.
+ *
+ * Global In:	save_audio_config_p->adev[ACHAN2ADEV(chan)].bits_per_sample - So we know whether to 
+ *			read 1 or 2 bytes from audio stream.
+ *
+ * Description:	Grab 1 or two btyes depending on data source.
+ *
+ *		When processing stereo, the caller will call this
+ *		at twice the normal rate to obtain alternating left 
+ *		and right samples.
+ *
+ *----------------------------------------------------------------*/
+
+#define FSK_READ_ERR (256*256)
+
+
+__attribute__((hot))
+int demod_get_sample (int a)		
+{
+	int x1, x2;
+	signed short sam;	/* short to force sign extention. */
+
+
+	assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16);
+
+
+	if (save_audio_config_p->adev[a].bits_per_sample == 8) {
+
+	  x1 = audio_get(a);				
+	  if (x1 < 0) return(FSK_READ_ERR);
+
+	  assert (x1 >= 0 && x1 <= 255);
+
+	  /* Scale 0..255 into -32k..+32k */
+
+	  sam = (x1 - 128) * 256;
+
+	}
+	else {
+	  x1 = audio_get(a);	/* lower byte first */
+	  if (x1 < 0) return(FSK_READ_ERR);
+
+	  x2 = audio_get(a);
+	  if (x2 < 0) return(FSK_READ_ERR);
+
+	  assert (x1 >= 0 && x1 <= 255);
+	  assert (x2 >= 0 && x2 <= 255);
+
+          sam = ( x2 << 8 ) | x1;
+	}
+
+	return (sam);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        demod_process_sample
+ *
+ * Purpose:     (1) Demodulate the AFSK signal.
+ *		(2) Recover clock and data.
+ *
+ * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
+ *		subchan - modem of the channel.
+ *		sam	- One sample of audio.
+ *			  Should be in range of -32768 .. 32767.
+ *
+ * Returns:	None 
+ *
+ * Descripion:	We start off with two bandpass filters tuned to
+ *		the given frequencies.  In the case of VHF packet
+ *		radio, this would be 1200 and 2200 Hz.
+ *
+ *		The bandpass filter amplitudes are compared to 
+ *		obtain the demodulated signal.
+ *
+ *		We also have a digital phase locked loop (PLL)
+ *		to recover the clock and pick out data bits at
+ *		the proper rate.
+ *
+ *		For each recovered data bit, we call:
+ *
+ *			  hdlc_rec (channel, demodulated_bit);
+ *
+ *		to decode HDLC frames from the stream of bits.
+ *
+ * Future:	This could be generalized by passing in the name
+ *		of the function to be called for each bit recovered
+ *		from the demodulator.  For now, it's simply hard-coded.
+ *
+ *--------------------------------------------------------------------*/
+
+
+__attribute__((hot))
+void demod_process_sample (int chan, int subchan, int sam)
+{
+	float fsam;
+	//float abs_fsam;
+	int k;
+
+
+#if DEBUG4
+	static FILE *demod_log_fp = NULL;
+	static int seq = 0;			/* for log file name */
+#endif
+
+	//int j;
+	//int demod_data;
+	struct demodulator_state_s *D;
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+
+	D = &demodulator_state[chan][subchan];
+
+
+	/* Scale to nice number, actually -2.0 to +2.0 for extra headroom */
+
+	fsam = sam / 16384.0f;
+
+/*
+ * Accumulate measure of the input signal level.
+ */
+
+
+/*
+ * Version 1.2: Try new approach to capturing the amplitude.
+ * This is same as the later AGC without the normalization step.
+ * We want decay to be substantially slower to get a longer
+ * range idea of the received audio.
+ */
+
+	if (fsam >= D->alevel_rec_peak) {
+	  D->alevel_rec_peak = fsam * D->quick_attack + D->alevel_rec_peak * (1.0f - D->quick_attack);
+	}
+	else {
+	  D->alevel_rec_peak = fsam * D->sluggish_decay + D->alevel_rec_peak * (1.0f - D->sluggish_decay);
+	}
+
+	if (fsam <= D->alevel_rec_valley) {
+	  D->alevel_rec_valley = fsam * D->quick_attack + D->alevel_rec_valley * (1.0f - D->quick_attack);
+	}
+	else  {   
+	  D->alevel_rec_valley = fsam * D->sluggish_decay + D->alevel_rec_valley * (1.0f - D->sluggish_decay);
+	}
+
+
+/*
+ * Select decoder based on modulation type.
+ */
+
+	switch (save_audio_config_p->achan[chan].modem_type) {
+
+	  case MODEM_OFF:
+
+	    // Might have channel only listening to DTMF for APRStt gateway.
+	    // Don't waste CPU time running a demodulator here.
+	    break;
+
+	  case MODEM_AFSK:
+
+	    if (save_audio_config_p->achan[chan].decimate > 1) {
+
+	      sample_sum[chan][subchan] += sam;
+	      sample_count[chan][subchan]++;
+	      if (sample_count[chan][subchan] >= save_audio_config_p->achan[chan].decimate) {
+  	        demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / save_audio_config_p->achan[chan].decimate, D);
+	        sample_sum[chan][subchan] = 0;
+	        sample_count[chan][subchan] = 0;
+	      }
+	    }
+	    else {
+  	      demod_afsk_process_sample (chan, subchan, sam, D);
+	    }
+	    break;
+
+	  case MODEM_BASEBAND:
+	  case MODEM_SCRAMBLE:
+	  default:
+
+#define ZEROSTUFF 1
+
+	
+#if ZEROSTUFF
+	    /* Literature says this is better if followed */
+	    /* by appropriate low pass filter. */
+	    /* So far, both are same in tests with different */
+	    /* optimal low pass filter parameters. */
+
+	    for (k=1; k<UPSAMPLE; k++) {
+	      demod_9600_process_sample (chan, 0, D);
+	    }
+	    demod_9600_process_sample (chan, sam*UPSAMPLE, D);
+#else
+	    /* Linear interpolation. */
+	    static int prev_sam;
+	    switch (UPSAMPLE) {
+	      case 1:
+	        demod_9600_process_sample (chan, sam);
+
+	        break;
+	      case 2:
+	        demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
+	        demod_9600_process_sample (chan, sam, D);
+	        break;
+              case 3:
+                demod_9600_process_sample (chan, (2 * prev_sam + sam) / 3, D);
+                demod_9600_process_sample (chan, (prev_sam + 2 * sam) / 3, D);
+                demod_9600_process_sample (chan, sam, D);
+                break;
+              case 4:
+                demod_9600_process_sample (chan, (3 * prev_sam + sam) / 4, D);
+                demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
+                demod_9600_process_sample (chan, (prev_sam + 3 * sam) / 4, D);
+                demod_9600_process_sample (chan, sam, D);
+                break;
+              default:
+                assert (0);
+                break;
+	    }
+	    prev_sam = sam;
+#endif
+	    break;
+	}
+	return;
+
+} /* end demod_process_sample */
+
+
+
+
+
+
+/* Doesn't seem right.  Need to revisit this. */
+/* Resulting scale is 0 to almost 100. */
+/* Cranking up the input level produces no more than 97 or 98. */
+/* We currently produce a message when this goes over 90. */
+
+alevel_t demod_get_audio_level (int chan, int subchan) 
+{
+	struct demodulator_state_s *D;
+	alevel_t alevel;
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+
+	/* We have to consider two different cases here. */
+	/* N demodulators, each with own slicer and HDLC decoder. */
+	/* Single demodulator, multiple slicers each with own HDLC decoder. */
+
+	if (demodulator_state[chan][0].num_slicers > 1) {
+	  subchan = 0;
+	}
+
+	D = &demodulator_state[chan][subchan];
+
+	// Take half of peak-to-peak for received audio level.
+
+	alevel.rec = (int) (( D->alevel_rec_peak - D->alevel_rec_valley ) * 50.0f + 0.5f);
+
+	if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) {
+
+	  /* For AFSK, we have mark and space amplitudes. */
+
+	  alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f);
+	  alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f);
+
+	  //alevel.ms_ratio = D->alevel_mark_peak / D->alevel_space_peak;	// TODO: remove after temp test
+	}
+	else {
+
+#if 1	
+	  /* Display the + and - peaks.  */
+	  /* Normally we'd expect them to be about the same. */
+	  /* However, with SDR, or other DC coupling, we could have an offset. */
+
+	  alevel.mark = (int) ((D->alevel_mark_peak) * 200.0f  + 0.5f);
+	  alevel.space = (int) ((D->alevel_space_peak) * 200.0f - 0.5f);
+
+
+#else
+	  /* Here we have + and - peaks after filtering. */
+	  /* Take half of the peak to peak. */
+	  /* The "5/6" factor worked out right for the current low pass filter. */
+	  /* Will it need to be different if the filter is tweaked? */
+
+	  alevel.mark = (int) ((D->alevel_mark_peak - D->alevel_space_peak) * 100.0f * 5.0f/6.0f + 0.5f);
+	  alevel.space = -1;		/* to print one number inside of ( ) */
+#endif
+	}
+	return (alevel);
+}
+
+
+/* end demod.c */
diff --git a/demod.h b/demod.h
index af56811..3233b9b 100644
--- a/demod.h
+++ b/demod.h
@@ -1,17 +1,17 @@
-
-
-/* demod.h */
-
-#include "audio.h" 	/* for struct audio_s */
-#include "ax25_pad.h"	/* for alevel_t */
-
-
-int demod_init (struct audio_s *pa);
-
-int demod_get_sample (int a);
-
-void demod_process_sample (int chan, int subchan, int sam);
-
-void demod_print_agc (int chan, int subchan);
-
+
+
+/* demod.h */
+
+#include "audio.h" 	/* for struct audio_s */
+#include "ax25_pad.h"	/* for alevel_t */
+
+
+int demod_init (struct audio_s *pa);
+
+int demod_get_sample (int a);
+
+void demod_process_sample (int chan, int subchan, int sam);
+
+void demod_print_agc (int chan, int subchan);
+
 alevel_t demod_get_audio_level (int chan, int subchan);
\ No newline at end of file
diff --git a/demod_9600.c b/demod_9600.c
index ed83af7..62faa01 100644
--- a/demod_9600.c
+++ b/demod_9600.c
@@ -1,527 +1,517 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-// 
-//    Copyright (C) 2011, 2012, 2013, 2015  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 DEBUG5 1	/* capture 9600 output to log files */
-
-
-/*------------------------------------------------------------------
- *
- * Module:      demod_9600.c
- *
- * Purpose:   	Demodulator for scrambled baseband encoding.
- *		
- * Input:	Audio samples from either a file or the "sound card."
- *
- * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
- *
- *---------------------------------------------------------------*/
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <assert.h>
-#include <ctype.h>
-
-#include "direwolf.h"
-#include "tune.h"
-#include "fsk_demod_state.h"
-#include "hdlc_rec.h"
-#include "demod_9600.h"
-#include "textcolor.h"
-#include "dsp.h"
-
-
-static float slice_point[MAX_SUBCHANS];
-
-
-/* Add sample to buffer and shift the rest down. */
-
-__attribute__((hot))
-static inline void push_sample (float val, float *buff, int size)
-{
-	memmove(buff+1,buff,(size-1)*sizeof(float));
-	buff[0] = val; 
-}
-
-
-/* FIR filter kernel. */
-
-__attribute__((hot))
-static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
-{
-	float sum = 0.0f;
-	int j;
-
-#if 0
-	//  As suggested here,  http://locklessinc.com/articles/vectorize/
-	//  Unfortunately, older compilers don't recognize it.
-
-	//  Get more information by using -ftree-vectorizer-verbose=5
-
-	float *d = __builtin_assume_aligned(data, 16);
-	float *f = __builtin_assume_aligned(filter, 16);
-
-	for (j=0; j<filter_size; j++) {
-	    sum += f[j] * d[j];
-	}
-#else
-	for (j=0; j<filter_size; j++) {
-	    sum += filter[j] * data[j];
-	}
-#endif
-	return (sum);
-}
-
-/* Automatic gain control. */
-/* Result should settle down to 1 unit peak to peak.  i.e. -0.5 to +0.5 */
-
-__attribute__((hot))
-static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
-{
-	if (in >= *ppeak) {
-	  *ppeak = in * fast_attack + *ppeak * (1. - fast_attack);
-	}
-	else {
-	  *ppeak = in * slow_decay + *ppeak * (1. - slow_decay);
-	}
-
-	if (in <= *pvalley) {
-	  *pvalley = in * fast_attack + *pvalley * (1. - fast_attack);
-	}
-	else  {   
-	  *pvalley = in * slow_decay + *pvalley * (1. - slow_decay);
-	}
-
-	if (*ppeak > *pvalley) {
-	  return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
-	}
-	return (0.0);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        demod_9600_init
- *
- * Purpose:     Initialize the 9600 baud demodulator.
- *
- * Inputs:      samples_per_sec	- Number of samples per second.
- *				Might be upsampled in hopes of 
- *				reducing the PLL jitter.
- *
- *		baud		- Data rate in bits per second.
- *
- *		D		- Address of demodulator state.
- *
- * Returns:     None
- *		
- *----------------------------------------------------------------*/
-
-void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D)
-{	
-	float fc;
-	int j;
-
-	memset (D, 0, sizeof(struct demodulator_state_s));
-
-	//dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud);
-
-        D->pll_step_per_sample = 
-		(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
-	
-	D->lp_filter_len_bits =  72 * 9600.0 / (44100.0 * 2.0);		
-	D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5);
-	D->lp_window = BP_WINDOW_HAMMING;
-	D->lpf_baud = 0.59;	
-
-	D->agc_fast_attack = 0.080;	
-	D->agc_slow_decay = 0.00012;
-
-	D->pll_locked_inertia = 0.88;
-	D->pll_searching_inertia = 0.67;
-
-
-#ifdef TUNE_LP_WINDOW
-	D->lp_window = TUNE_LP_WINDOW;
-#endif
-
-#if TUNE_LP_FILTER_SIZE
-	D->lp_filter_size = TUNE_LP_FILTER_SIZE;
-#endif
-
-#ifdef TUNE_LPF_BAUD
-	D->lpf_baud = TUNE_LPF_BAUD;
-#endif	
-
-#ifdef TUNE_AGC_FAST
-	D->agc_fast_attack = TUNE_AGC_FAST;
-#endif
-
-#ifdef TUNE_AGC_SLOW
-	D->agc_slow_decay = TUNE_AGC_SLOW;
-#endif
-
-#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
-	D->pll_locked_inertia = TUNE_PLL_LOCKED;
-	D->pll_searching_inertia = TUNE_PLL_SEARCHING;
-#endif
-
-	fc = (float)baud * D->lpf_baud / (float)samples_per_sec;
-
-	//dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size);
-
-	gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
-
-	/* Version 1.2: Experiment with different slicing levels. */
-
-	for (j = 0; j < MAX_SUBCHANS; j++) {
-	  slice_point[j] = 0.02 * (j - 0.5 * (MAX_SUBCHANS-1));
-	  //dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]);
-	}
-
-} /* end fsk_demod_init */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        demod_9600_process_sample
- *
- * Purpose:     (1) Filter & slice the signal.
- *		(2) Descramble it.
- *		(2) Recover clock and data.
- *
- * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
- *
- *		sam	- One sample of audio.
- *			  Should be in range of -32768 .. 32767.
- *
- * Returns:	None 
- *
- * Descripion:	"9600 baud" packet is FSK for an FM voice transceiver.
- *		By the time it gets here, it's really a baseband signal.
- *		At one extreme, we could have a 4800 Hz square wave.
- *		A the other extreme, we could go a considerable number
- *		of bit times without any transitions.
- *
- *		The trick is to extract the digital data which has
- *		been distorted by going thru voice transceivers not
- *		intended to pass this sort of "audio" signal.
- *
- *		Data is "scrambled" to reduce the amount of DC bias.
- *		The data stream must be unscrambled at the receiving end.
- *
- *		We also have a digital phase locked loop (PLL)
- *		to recover the clock and pick out data bits at
- *		the proper rate.
- *
- *		For each recovered data bit, we call:
- *
- *			  hdlc_rec (channel, demodulated_bit);
- *
- *		to decode HDLC frames from the stream of bits.
- *
- * Future:	This could be generalized by passing in the name
- *		of the function to be called for each bit recovered
- *		from the demodulator.  For now, it's simply hard-coded.
- *
- * References:	9600 Baud Packet Radio Modem Design
- *		http://www.amsat.org/amsat/articles/g3ruh/109.html
- *
- *		The KD2BD 9600 Baud Modem
- *		http://www.amsat.org/amsat/articles/kd2bd/9k6modem/
- *
- *		9600 Baud Packet Handbook
- * 		ftp://ftp.tapr.org/general/9600baud/96man2x0.txt
- *
- *
- *--------------------------------------------------------------------*/
-
-static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D);
-
-__attribute__((hot))
-void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D)
-{
-
-	float fsam;
-	float abs_fsam;
-	float amp;
-	float demod_out;
-
-#if DEBUG5
-	static FILE *demod_log_fp = NULL;
-	static int seq = 0;			/* for log file name */
-#endif
-
-	int j;
-	int subchan = 0;
-	int demod_data;				/* Still scrambled. */
-
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-
-/* 
- * Filters use last 'filter_size' samples.
- *
- * First push the older samples down. 
- *
- * Finally, put the most recent at the beginning.
- *
- * Future project?  Rather than shifting the samples,
- * it might be faster to add another variable to keep
- * track of the most recent sample and change the 
- * indexing in the later loops that multipy and add.
- */
-
-	/* Scale to nice number for convenience. */
-	/* Consistent with the AFSK demodulator, we'd like to use */
-	/* only half of the dynamic range to have some headroom. */
-	/* i.e.  input range +-16k becomes +-1 here and is */
-	/* displayed in the heard line as audio level 100. */
-
-	fsam = sam / 16384.0;
-
-	push_sample (fsam, D->raw_cb, D->lp_filter_size);
-
-/*
- * Low pass filter to reduce noise yet pass the data. 
- */
-
-	amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size);
-
-
-/*
- * Version 1.2: Capture the post-filtering amplitude for display.
- * This is similar to the AGC without the normalization step.
- * We want decay to be substantially slower to get a longer
- * range idea of the received audio.
- * For AFSK, we keep mark and space amplitudes.
- * Here we keep + and - peaks because there could be a DC bias.
- */
-
-	if (amp >= D->alevel_mark_peak) {
-	  D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1. - D->quick_attack);
-	}
-	else {
-	  D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1. - D->sluggish_decay);
-	}
-
-	if (amp <= D->alevel_space_peak) {
-	  D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1. - D->quick_attack);
-	}
-	else {
-	  D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1. - D->sluggish_decay);
-	}
-
-/* 
- * The input level can vary greatly.
- * More importantly, there could be a DC bias which we need to remove.
- *
- * Normalize the signal with automatic gain control (AGC). 
- * This works by looking at the minimum and maximum signal peaks
- * and scaling the results to be roughly in the -1.0 to +1.0 range.
- */
-
-	demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
-
-
-// TODO: There is potential for multiple decoders with one filter.
-
-//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm);
-
-	/* Throw in a little Hysteresis??? */
-	/* (Not to be confused with Hysteria.) */
-	/* Doesn't seem to have any value. */
-	/* Using a level of .02 makes things worse. */
-	/* Might want to experiment with this again someday. */
-
-
-//	if (demod_out > 0.03) {
-//	  demod_data = 1;
-//	}
-//	else if (demod_out < -0.03) {
-//	  demod_data = 0;
-//	} 
-//	else {
-//	  demod_data = D->slicer[subchan].prev_demod_data;
-//	}
-
-	if (D->num_slicers <= 1) {
-
-	  /* Normal case of one demodulator to one HDLC decoder. */
-	  /* Demodulator output is difference between response from two filters. */
-	  /* AGC should generally keep this around -1 to +1 range. */
-
-	  demod_data = demod_out > 0;
-
-	  nudge_pll (chan, subchan, demod_data, D);
-	}
-	else {
-	  int s;
-
-	  assert (subchan == 0);
-
-	  /* Multiple slicers each feeding its own HDLC decoder. */
-
-	  for (s=0; s<D->num_slicers; s++) {
-	    demod_data = demod_out > slice_point[s];
-	    nudge_pll (chan, s, demod_data, D);		
-	  }
-	}
-
-} /* end demod_9600_process_sample */
-
-
-__attribute__((hot))
-static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D)
-{
-	int descram;				/* Data bit de-scrambled. */
-
-/*
- * Next, a PLL is used to sample near the centers of the data bits.
- *
- * D->data_clock_pll is a SIGNED 32 bit variable.
- * When it overflows from a large positive value to a negative value, we 
- * sample a data bit from the demodulated signal.
- *
- * Ideally, the the demodulated signal transitions should be near
- * zero we we sample mid way between the transitions.
- *
- * Nudge the PLL by removing some small fraction from the value of 
- * data_clock_pll, pushing it closer to zero.
- * 
- * This adjustment will never change the sign so it won't cause
- * any erratic data bit sampling.
- *
- * If we adjust it too quickly, the clock will have too much jitter.
- * If we adjust it too slowly, it will take too long to lock on to a new signal.
- *
- * I don't think the optimal value will depend on the audio sample rate
- * because this happens for each transition from the demodulator.
- *
- * This was optimized for 1200 baud AFSK.  There might be some opportunity
- * for improvement here.
- */
-	D->slicer[subchan].prev_d_c_pll = D->slicer[subchan].data_clock_pll;
-	D->slicer[subchan].data_clock_pll += D->pll_step_per_sample;
-
-	if (D->slicer[subchan].data_clock_pll < 0 && D->slicer[subchan].prev_d_c_pll > 0) {
-
-	  /* Overflow. */
-
-/*
- * At this point, we need to descramble the data as
- * in hardware based designs by G3RUH and K9NG.
- *
- * Future Idea:  allow unscrambled baseband data.
- *
- * http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif
- */
-
-	  //assert (modem.modem_type[chan] == MODEM_SCRAMBLE);
-
-	  //if (modem.modem_type[chan] == MODEM_SCRAMBLE) {
-
-
-	    descram = descramble (demod_data, &(D->slicer[subchan].lfsr));
-
-	    hdlc_rec_bit (chan, subchan, demod_data, 1, D->slicer[subchan].lfsr);
-
-	    //D->prev_descram = descram;
-	  //}
-	  //else {
-	    /* Baseband signal for completeness - not in common use. */
-	    //hdlc_rec_bit (chan, subchan, demod_data);
-	  //}
-	}
-
-        if (demod_data != D->slicer[subchan].prev_demod_data) {
-
-	  // Note:  Test for this demodulator, not overall for channel.
-
-	  if (hdlc_rec_gathering (chan, subchan)) {
-	    D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_locked_inertia);
-	  }
-	  else {
-	    D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_searching_inertia);
-	  }
-	}
-
-
-#if DEBUG5
-
-	//if (chan == 0) {
-	if (hdlc_rec_gathering (chan,subchan)) {
-	
-	  char fname[30];
-
-	  
-	  if (demod_log_fp == NULL) {
-	    seq++;
-	    sprintf (fname, "demod96/%04d.csv", seq);
-	    if (seq == 1) mkdir ("demod96"
-#ifndef __WIN32__
-					, 0777
-#endif
-						);
-
-	    demod_log_fp = fopen (fname, "w");
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("Starting 9600 decoder log file %s\n", fname);
-	    fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n");
-	  }
-	  fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", 
-			0.5 * fsam + 3.5,  
-			0.5 * D->m_peak + 3.5,
-			0.5 * D->m_valley + 3.5,
-			0.5 * demod_out + 2.0,
-			demod_data ? 1.35 : 1.0,  
-			descram ? .9 : .55,  
-			(D->data_clock_pll & 0x80000000) ? .1 : .45);
-	}
-	else {
-	  if (demod_log_fp != NULL) {
-	    fclose (demod_log_fp);
-	    demod_log_fp = NULL;
-	  }
-	}
-	//}
-
-#endif
-
-
-/*
- * Remember demodulator output (pre-descrambling) so we can compare next time
- * for the DPLL sync.
- */
-	D->slicer[subchan].prev_demod_data = demod_data;
-
-} /* end nudge_pll */
-
-
-
-
-
-/* end demod_9600.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+// 
+//    Copyright (C) 2011, 2012, 2013, 2015  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 DEBUG5 1	/* capture 9600 output to log files */
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      demod_9600.c
+ *
+ * Purpose:   	Demodulator for scrambled baseband encoding.
+ *		
+ * Input:	Audio samples from either a file or the "sound card."
+ *
+ * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "direwolf.h"
+#include "tune.h"
+#include "fsk_demod_state.h"
+#include "hdlc_rec.h"
+#include "demod_9600.h"
+#include "textcolor.h"
+#include "dsp.h"
+
+
+static float slice_point[MAX_SUBCHANS];
+
+
+/* Add sample to buffer and shift the rest down. */
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline void push_sample (float val, float *buff, int size)
+{
+	memmove(buff+1,buff,(size-1)*sizeof(float));
+	buff[0] = val; 
+}
+
+
+/* FIR filter kernel. */
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
+{
+	float sum = 0.0f;
+	int j;
+
+#if 0
+	//  As suggested here,  http://locklessinc.com/articles/vectorize/
+	//  Unfortunately, older compilers don't recognize it.
+
+	//  Get more information by using -ftree-vectorizer-verbose=5
+
+	float *d = __builtin_assume_aligned(data, 16);
+	float *f = __builtin_assume_aligned(filter, 16);
+
+#pragma GCC ivdep
+	for (j=0; j<filter_size; j++) {
+	    sum += f[j] * d[j];
+	}
+#else
+#pragma GCC ivdep				// ignored until gcc 4.9
+	for (j=0; j<filter_size; j++) {
+	    sum += filter[j] * data[j];
+	}
+#endif
+	return (sum);
+}
+
+/* Automatic gain control. */
+/* Result should settle down to 1 unit peak to peak.  i.e. -0.5 to +0.5 */
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
+{
+	if (in >= *ppeak) {
+	  *ppeak = in * fast_attack + *ppeak * (1. - fast_attack);
+	}
+	else {
+	  *ppeak = in * slow_decay + *ppeak * (1. - slow_decay);
+	}
+
+	if (in <= *pvalley) {
+	  *pvalley = in * fast_attack + *pvalley * (1. - fast_attack);
+	}
+	else  {   
+	  *pvalley = in * slow_decay + *pvalley * (1. - slow_decay);
+	}
+
+	if (*ppeak > *pvalley) {
+	  return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
+	}
+	return (0.0);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        demod_9600_init
+ *
+ * Purpose:     Initialize the 9600 baud demodulator.
+ *
+ * Inputs:      samples_per_sec	- Number of samples per second.
+ *				Might be upsampled in hopes of 
+ *				reducing the PLL jitter.
+ *
+ *		baud		- Data rate in bits per second.
+ *
+ *		D		- Address of demodulator state.
+ *
+ * Returns:     None
+ *		
+ *----------------------------------------------------------------*/
+
+void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D)
+{	
+	float fc;
+	int j;
+
+	memset (D, 0, sizeof(struct demodulator_state_s));
+	D->num_slicers = 1;
+
+	//dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud);
+
+        D->pll_step_per_sample = 
+		(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
+	
+	D->lp_filter_len_bits =  72 * 9600.0 / (44100.0 * 2.0);		
+	D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5);
+	D->lp_window = BP_WINDOW_HAMMING;
+	D->lpf_baud = 0.59;	
+
+	D->agc_fast_attack = 0.080;	
+	D->agc_slow_decay = 0.00012;
+
+	D->pll_locked_inertia = 0.88;
+	D->pll_searching_inertia = 0.67;
+
+
+#ifdef TUNE_LP_WINDOW
+	D->lp_window = TUNE_LP_WINDOW;
+#endif
+
+#if TUNE_LP_FILTER_SIZE
+	D->lp_filter_size = TUNE_LP_FILTER_SIZE;
+#endif
+
+#ifdef TUNE_LPF_BAUD
+	D->lpf_baud = TUNE_LPF_BAUD;
+#endif	
+
+#ifdef TUNE_AGC_FAST
+	D->agc_fast_attack = TUNE_AGC_FAST;
+#endif
+
+#ifdef TUNE_AGC_SLOW
+	D->agc_slow_decay = TUNE_AGC_SLOW;
+#endif
+
+#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
+	D->pll_locked_inertia = TUNE_PLL_LOCKED;
+	D->pll_searching_inertia = TUNE_PLL_SEARCHING;
+#endif
+
+	fc = (float)baud * D->lpf_baud / (float)samples_per_sec;
+
+	//dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size);
+
+	gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
+
+	/* Version 1.2: Experiment with different slicing levels. */
+
+	for (j = 0; j < MAX_SUBCHANS; j++) {
+	  slice_point[j] = 0.02 * (j - 0.5 * (MAX_SUBCHANS-1));
+	  //dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]);
+	}
+
+} /* end fsk_demod_init */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        demod_9600_process_sample
+ *
+ * Purpose:     (1) Filter & slice the signal.
+ *		(2) Descramble it.
+ *		(2) Recover clock and data.
+ *
+ * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
+ *
+ *		sam	- One sample of audio.
+ *			  Should be in range of -32768 .. 32767.
+ *
+ * Returns:	None 
+ *
+ * Descripion:	"9600 baud" packet is FSK for an FM voice transceiver.
+ *		By the time it gets here, it's really a baseband signal.
+ *		At one extreme, we could have a 4800 Hz square wave.
+ *		A the other extreme, we could go a considerable number
+ *		of bit times without any transitions.
+ *
+ *		The trick is to extract the digital data which has
+ *		been distorted by going thru voice transceivers not
+ *		intended to pass this sort of "audio" signal.
+ *
+ *		Data is "scrambled" to reduce the amount of DC bias.
+ *		The data stream must be unscrambled at the receiving end.
+ *
+ *		We also have a digital phase locked loop (PLL)
+ *		to recover the clock and pick out data bits at
+ *		the proper rate.
+ *
+ *		For each recovered data bit, we call:
+ *
+ *			  hdlc_rec (channel, demodulated_bit);
+ *
+ *		to decode HDLC frames from the stream of bits.
+ *
+ * Future:	This could be generalized by passing in the name
+ *		of the function to be called for each bit recovered
+ *		from the demodulator.  For now, it's simply hard-coded.
+ *
+ * References:	9600 Baud Packet Radio Modem Design
+ *		http://www.amsat.org/amsat/articles/g3ruh/109.html
+ *
+ *		The KD2BD 9600 Baud Modem
+ *		http://www.amsat.org/amsat/articles/kd2bd/9k6modem/
+ *
+ *		9600 Baud Packet Handbook
+ * 		ftp://ftp.tapr.org/general/9600baud/96man2x0.txt
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D);
+
+__attribute__((hot))
+void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D)
+{
+
+	float fsam;
+	//float abs_fsam;
+	float amp;
+	float demod_out;
+
+#if DEBUG5
+	static FILE *demod_log_fp = NULL;
+	static int seq = 0;			/* for log file name */
+#endif
+
+	//int j;
+	int subchan = 0;
+	int demod_data;				/* Still scrambled. */
+
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+
+
+/* 
+ * Filters use last 'filter_size' samples.
+ *
+ * First push the older samples down. 
+ *
+ * Finally, put the most recent at the beginning.
+ *
+ * Future project?  Rather than shifting the samples,
+ * it might be faster to add another variable to keep
+ * track of the most recent sample and change the 
+ * indexing in the later loops that multipy and add.
+ */
+
+	/* Scale to nice number for convenience. */
+	/* Consistent with the AFSK demodulator, we'd like to use */
+	/* only half of the dynamic range to have some headroom. */
+	/* i.e.  input range +-16k becomes +-1 here and is */
+	/* displayed in the heard line as audio level 100. */
+
+	fsam = sam / 16384.0;
+
+	push_sample (fsam, D->raw_cb, D->lp_filter_size);
+
+/*
+ * Low pass filter to reduce noise yet pass the data. 
+ */
+
+	amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size);
+
+
+/*
+ * Version 1.2: Capture the post-filtering amplitude for display.
+ * This is similar to the AGC without the normalization step.
+ * We want decay to be substantially slower to get a longer
+ * range idea of the received audio.
+ * For AFSK, we keep mark and space amplitudes.
+ * Here we keep + and - peaks because there could be a DC bias.
+ */
+
+	if (amp >= D->alevel_mark_peak) {
+	  D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1. - D->quick_attack);
+	}
+	else {
+	  D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1. - D->sluggish_decay);
+	}
+
+	if (amp <= D->alevel_space_peak) {
+	  D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1. - D->quick_attack);
+	}
+	else {
+	  D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1. - D->sluggish_decay);
+	}
+
+/* 
+ * The input level can vary greatly.
+ * More importantly, there could be a DC bias which we need to remove.
+ *
+ * Normalize the signal with automatic gain control (AGC). 
+ * This works by looking at the minimum and maximum signal peaks
+ * and scaling the results to be roughly in the -1.0 to +1.0 range.
+ */
+
+	demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
+
+
+// TODO: There is potential for multiple decoders with one filter.
+
+//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm);
+
+	/* Throw in a little Hysteresis??? */
+	/* (Not to be confused with Hysteria.) */
+	/* Doesn't seem to have any value. */
+	/* Using a level of .02 makes things worse. */
+	/* Might want to experiment with this again someday. */
+
+
+//	if (demod_out > 0.03) {
+//	  demod_data = 1;
+//	}
+//	else if (demod_out < -0.03) {
+//	  demod_data = 0;
+//	} 
+//	else {
+//	  demod_data = D->slicer[subchan].prev_demod_data;
+//	}
+
+	if (D->num_slicers <= 1) {
+
+	  /* Normal case of one demodulator to one HDLC decoder. */
+	  /* Demodulator output is difference between response from two filters. */
+	  /* AGC should generally keep this around -1 to +1 range. */
+
+	  demod_data = demod_out > 0;
+	  nudge_pll (chan, subchan, 0, demod_data, D);
+	}
+	else {
+	  int slice;
+
+	  /* Multiple slicers each feeding its own HDLC decoder. */
+
+	  for (slice=0; slice<D->num_slicers; slice++) {
+	    demod_data = demod_out > slice_point[slice];
+	    nudge_pll (chan, subchan, slice, demod_data, D);
+	  }
+	}
+
+} /* end demod_9600_process_sample */
+
+
+__attribute__((hot))
+static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D)
+{
+
+/*
+ * Next, a PLL is used to sample near the centers of the data bits.
+ *
+ * D->data_clock_pll is a SIGNED 32 bit variable.
+ * When it overflows from a large positive value to a negative value, we 
+ * sample a data bit from the demodulated signal.
+ *
+ * Ideally, the the demodulated signal transitions should be near
+ * zero we we sample mid way between the transitions.
+ *
+ * Nudge the PLL by removing some small fraction from the value of 
+ * data_clock_pll, pushing it closer to zero.
+ * 
+ * This adjustment will never change the sign so it won't cause
+ * any erratic data bit sampling.
+ *
+ * If we adjust it too quickly, the clock will have too much jitter.
+ * If we adjust it too slowly, it will take too long to lock on to a new signal.
+ *
+ * I don't think the optimal value will depend on the audio sample rate
+ * because this happens for each transition from the demodulator.
+ *
+ * This was optimized for 1200 baud AFSK.  There might be some opportunity
+ * for improvement here.
+ */
+
+	D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
+	D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
+
+	if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) {
+
+	  /* Overflow. */
+
+/*
+ * At this point, we need to descramble the data as
+ * in hardware based designs by G3RUH and K9NG.
+ *
+ * Future Idea:  allow unscrambled baseband data.
+ *
+ * http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif
+ */
+	  // Warning: 'descram' set but not used.
+	  // It's used in conditional debug code below.
+	  // descram =
+	  descramble (demod_data, &(D->slicer[slice].lfsr));
+
+	  hdlc_rec_bit (chan, subchan, slice, demod_data, 1, D->slicer[slice].lfsr);
+	}
+
+        if (demod_data != D->slicer[slice].prev_demod_data) {
+
+	  // Note:  Test for this demodulator, not overall for channel.
+
+	  if (hdlc_rec_gathering (chan, subchan, slice)) {
+	    D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia);
+	  }
+	  else {
+	    D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia);
+	  }
+	}
+
+
+#if DEBUG5
+
+	//if (chan == 0) {
+	if (hdlc_rec_gathering (chan,subchan,slice)) {
+	
+	  char fname[30];
+
+	  
+	  if (demod_log_fp == NULL) {
+	    seq++;
+	    snprintf (fname, sizeof(fname), "demod96/%04d.csv", seq);
+	    if (seq == 1) mkdir ("demod96"
+#ifndef __WIN32__
+					, 0777
+#endif
+						);
+
+	    demod_log_fp = fopen (fname, "w");
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Starting 9600 decoder log file %s\n", fname);
+	    fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n");
+	  }
+	  fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", 
+			0.5 * fsam + 3.5,  
+			0.5 * D->m_peak + 3.5,
+			0.5 * D->m_valley + 3.5,
+			0.5 * demod_out + 2.0,
+			demod_data ? 1.35 : 1.0,  
+			descram ? .9 : .55,  
+			(D->data_clock_pll & 0x80000000) ? .1 : .45);
+	}
+	else {
+	  if (demod_log_fp != NULL) {
+	    fclose (demod_log_fp);
+	    demod_log_fp = NULL;
+	  }
+	}
+	//}
+
+#endif
+
+
+/*
+ * Remember demodulator output (pre-descrambling) so we can compare next time
+ * for the DPLL sync.
+ */
+	D->slicer[slice].prev_demod_data = demod_data;
+
+} /* end nudge_pll */
+
+
+
+
+
+/* end demod_9600.c */
diff --git a/demod_9600.h b/demod_9600.h
index f80d1da..a764711 100644
--- a/demod_9600.h
+++ b/demod_9600.h
@@ -1,25 +1,25 @@
-
-
-/* demod_9600.h */
-
-
-#include "fsk_demod_state.h"
-
-
-void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D);
-
-void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D);
-
-
-
-
-/* Undo data scrambling for 9600 baud. */
-
-static inline int descramble (int in, int *state)
-{
-	int out;
-
-	out = (in ^ (*state >> 16) ^ (*state >> 11)) & 1;
-	*state = (*state << 1) | (in & 1);
-	return (out);
-}
+
+
+/* demod_9600.h */
+
+
+#include "fsk_demod_state.h"
+
+
+void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D);
+
+void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D);
+
+
+
+
+/* Undo data scrambling for 9600 baud. */
+
+static inline int descramble (int in, int *state)
+{
+	int out;
+
+	out = (in ^ (*state >> 16) ^ (*state >> 11)) & 1;
+	*state = (*state << 1) | (in & 1);
+	return (out);
+}
diff --git a/demod_afsk.c b/demod_afsk.c
index 7fa7ab8..b769e21 100644
--- a/demod_afsk.c
+++ b/demod_afsk.c
@@ -1,1143 +1,1158 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-// 
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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     /* display debugging info */
-
-// #define DEBUG3 1	/* print carrier detect changes. */
-
-// #define DEBUG4 1	/* capture AFSK demodulator output to log files */
-
-// #define DEBUG5 1	/* capture 9600 output to log files */
-
-
-/*------------------------------------------------------------------
- *
- * Module:      demod_afsk.c
- *
- * Purpose:   	Demodulator for Audio Frequency Shift Keying (AFSK).
- *		
- * Input:	Audio samples from either a file or the "sound card."
- *
- * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
- *
- *---------------------------------------------------------------*/
-
-
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <assert.h>
-#include <ctype.h>
-
-#include "direwolf.h"
-#include "audio.h"
-
-#include "tune.h"
-#include "fsk_demod_state.h"
-#include "fsk_gen_filter.h"
-#include "hdlc_rec.h"
-#include "textcolor.h"
-#include "demod_afsk.h"
-#include "dsp.h"
-
-#define MIN(a,b) ((a)<(b)?(a):(b))
-#define MAX(a,b) ((a)>(b)?(a):(b))
-
-
-
-
-/* Quick approximation to sqrt(x*x+y*y) */
-/* No benefit for regular PC. */
-/* Should help with microcomputer platform. */
-
-
-__attribute__((hot))
-static inline float z (float x, float y)
-{
-        x = fabsf(x);
-        y = fabsf(y);
-
-        if (x > y) {
-          return (x * .941246f + y * .41f);
-        }
-        else {
-          return (y * .941246f + x * .41f);
-        }
-}
-
-/* Add sample to buffer and shift the rest down. */
-
-__attribute__((hot))
-static inline void push_sample (float val, float *buff, int size)
-{
-	memmove(buff+1,buff,(size-1)*sizeof(float));
-	buff[0] = val; 
-}
-
-
-/* FIR filter kernel. */
-
-__attribute__((hot))
-static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
-{
-	float sum = 0.0f;
-	int j;
-
-#if 0
-	//  As suggested here,  http://locklessinc.com/articles/vectorize/
-	//  Unfortunately, older compilers don't recognize it.
-
-	//  Get more information by using -ftree-vectorizer-verbose=5
-
-	float *d = __builtin_assume_aligned(data, 16);
-	float *f = __builtin_assume_aligned(filter, 16);
-
-	for (j=0; j<filter_size; j++) {
-	    sum += f[j] * d[j];
-	}
-#else
-	for (j=0; j<filter_size; j++) {
-	    sum += filter[j] * data[j];
-	}
-#endif
-	return (sum);
-}
-
-/* Automatic gain control. */
-/* Result should settle down to 1 unit peak to peak.  i.e. -0.5 to +0.5 */
-
-__attribute__((hot))
-static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
-{
-	if (in >= *ppeak) {
-	  *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
-	}
-	else {
-	  *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
-	}
-
-	if (in <= *pvalley) {
-	  *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
-	}
-	else  {   
-	  *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
-	}
-
-	if (*ppeak > *pvalley) {
-	  return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
-	}
-	return (0.0f);
-}
-
-
-/*
- * for multi-slicer experiment.
- */
-
-#define MIN_G 0.5f
-#define MAX_G 4.0f
-
-/* TODO: static */  float space_gain[MAX_SUBCHANS];
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        demod_afsk_init
- *
- * Purpose:     Initialization for an AFSK demodulator.
- *		Select appropriate parameters and set up filters.
- *
- * Inputs:   	samples_per_sec
- *		baud
- *		mark_freq
- *		space_freq
- *	
- *		D		- Pointer to demodulator state for given channel.
- *
- * Outputs:	D->ms_filter_size
- *		D->m_sin_table[] 
- *		D->m_cos_table[]
- *		D->s_sin_table[] 
- *		D->s_cos_table[]
- *
- * Returns:     None.
- *		
- * Bugs:	This doesn't do much error checking so don't give it
- *		anything crazy.
- *
- *----------------------------------------------------------------*/
-
-void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
-			int space_freq, char profile, struct demodulator_state_s *D)
-{
-	
-	int j;
-	
-	memset (D, 0, sizeof(struct demodulator_state_s));
-
-#if DEBUG1
-	dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n",
-		samples_per_sec, baud, mark_freq, space_freq, profile);
-#endif
-				
-#ifdef TUNE_PROFILE
-	profile = TUNE_PROFILE;
-#endif
-
-
-	if (profile == 'F') {
-
-	  if (baud != DEFAULT_BAUD ||
-	      mark_freq != DEFAULT_MARK_FREQ ||
-	      space_freq!= DEFAULT_SPACE_FREQ ||
-	      samples_per_sec != DEFAULT_SAMPLES_PER_SEC) {
-
-	    	text_color_set(DW_COLOR_INFO);
-		dw_printf ("Note: Decoder 'F' works only for %d baud, %d/%d tones, %d samples/sec.\n",
-			DEFAULT_BAUD, DEFAULT_MARK_FREQ, DEFAULT_SPACE_FREQ, DEFAULT_SAMPLES_PER_SEC);
-		dw_printf ("Using Decoder 'A' instead.\n");
-		profile = 'A';
-	  }
-	}
-
-	D->profile = profile;		// so we know whether to take fast path later.
-
-	switch (profile) {
-
-	  case 'A':
-	  case 'F':
-
-		/* Original.  52 taps, truncated bandpass, IIR lowpass */
-		/* 'F' is the fast version for low end processors. */
-		/* It is a special case that works only for a particular */
-		/* baud rate, tone pair, and sampling rate. */
-
-	    D->use_prefilter = 0;		
-
-	    D->ms_filter_len_bits = 1.415;		/* 52 @ 44100, 1200 */
-	    D->ms_window = BP_WINDOW_TRUNCATED;
-
-	    //D->bp_window = BP_WINDOW_TRUNCATED;
-
-	    D->lpf_use_fir = 0;
-	    D->lpf_iir = 0.195;
-
-	    D->agc_fast_attack = 0.250;		
-	    D->agc_slow_decay = 0.00012;
-	    D->hysteresis = 0.005;
-
-	    D->pll_locked_inertia = 0.700;
-	    D->pll_searching_inertia = 0.580;
-	    break;
-
-	  case 'B':
-
-		/* Original bandpass.  Use FIR lowpass instead. */
-
-	    D->use_prefilter = 0;		
-
-	    D->ms_filter_len_bits = 1.415;		/* 52 @ 44100, 1200 */
-	    D->ms_window = BP_WINDOW_TRUNCATED;
-
-	    //D->bp_window = BP_WINDOW_TRUNCATED;
-
-	    D->lpf_use_fir = 1;
-	    D->lpf_baud = 1.09;
-	    D->lp_filter_len_bits = D->ms_filter_len_bits;		
-	    D->lp_window = BP_WINDOW_TRUNCATED;
-
-	    D->agc_fast_attack = 0.370;		
-	    D->agc_slow_decay = 0.00014;
-	    D->hysteresis = 0.003;
-
-	    D->pll_locked_inertia = 0.620;
-	    D->pll_searching_inertia = 0.350;
-	    break;
-
-	  case 'C':
-
-		/* Cosine window, 76 taps for bandpass, FIR lowpass. */
-
-	    D->use_prefilter = 0;		
-
-	    D->ms_filter_len_bits = 2.068;		/* 76 @ 44100, 1200 */
-	    D->ms_window = BP_WINDOW_COSINE;
-
-	    //D->bp_window = BP_WINDOW_COSINE;
-
-	    D->lpf_use_fir = 1;
-	    D->lpf_baud = 1.09;
-	    D->lp_filter_len_bits = D->ms_filter_len_bits;		
-	    D->lp_window = BP_WINDOW_TRUNCATED;
-
-	    D->agc_fast_attack = 0.495;		
-	    D->agc_slow_decay = 0.00022;
-	    D->hysteresis = 0.005;
-
-	    D->pll_locked_inertia = 0.620;
-	    D->pll_searching_inertia = 0.350;
-	    break;
-
-	  case 'D':
-
-		/* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */
-
-	    D->use_prefilter = 1;		/* first, a bandpass filter. */
-	    D->prefilter_baud = 0.87;		
-	    D->pre_filter_len_bits = 1.857;	
-	    D->pre_window = BP_WINDOW_COSINE;
-
-	    D->ms_filter_len_bits = 1.857;		/* 91 @ 44100/3, 300 */
-	    D->ms_window = BP_WINDOW_COSINE;
-		
-	    //D->bp_window = BP_WINDOW_COSINE;
-
-	    D->lpf_use_fir = 1;
-	    D->lpf_baud = 1.10;
-	    D->lp_filter_len_bits = D->ms_filter_len_bits;	
-	    D->lp_window = BP_WINDOW_TRUNCATED;
-
-	    D->agc_fast_attack = 0.495;		
-	    D->agc_slow_decay = 0.00022;
-	    D->hysteresis = 0.027;
-
-	    D->pll_locked_inertia = 0.620;
-	    D->pll_searching_inertia = 0.350;
-	    break;
-
-	  case 'E':
-
-		/* 1200 baud - Started out similar to C but add prefilter. */
-		/* Version 1.2 - EXPERIMENTAL - Needs more fine tuning. */
-		/* Enhancements: 					*/
-		/*  + Add prefilter.  Previously used for 300 baud D, but not 1200. */
-		/*  + Prefilter length now independent of M/S filters.	*/
-		/*  + Lowpass filter length now independent of M/S filters.	*/
-		/*  + Allow mixed window types.	*/
-
-	    //D->bp_window = BP_WINDOW_COSINE;	/* The name says BP but it is used for all of them. */
-
-	    D->use_prefilter = 1;		/* first, a bandpass filter. */
-	    D->prefilter_baud = 0.23;		
-	    D->pre_filter_len_bits = 156 * 1200. / 44100.;	
-	    D->pre_window = BP_WINDOW_TRUNCATED;
-
-	    D->ms_filter_len_bits = 74 * 1200. / 44100.;		
-	    D->ms_window = BP_WINDOW_COSINE;
-
-	    D->lpf_use_fir = 1;
-	    D->lpf_baud = 1.18;
-	    D->lp_filter_len_bits = 63 * 1200. / 44100.;		
-	    D->lp_window = BP_WINDOW_TRUNCATED;
-
-	    //D->agc_fast_attack = 0.300;		
-	    //D->agc_slow_decay = 0.000185;
-	    D->agc_fast_attack = 0.820;		
-	    D->agc_slow_decay = 0.000214;
-	    D->hysteresis = 0.01;
-
-	    //D->pll_locked_inertia = 0.57;
-	    //D->pll_searching_inertia = 0.33;
-	    D->pll_locked_inertia = 0.74;
-	    D->pll_searching_inertia = 0.50;
-	    break;
-
-	  default:
-
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Invalid filter profile = %c\n", profile);
-	    exit (1);
-	}
-
-#ifdef TUNE_PRE_WINDOW
-	D->pre_window = TUNE_PRE_WINDOW;
-#endif
-#ifdef TUNE_MS_WINDOW
-	D->ms_window = TUNE_MS_WINDOW;
-#endif
-#ifdef TUNE_LP_WINDOW
-	D->lp_window = TUNE_LP_WINDOW;
-#endif
-
-
-#if defined(TUNE_AGC_FAST) && defined(TUNE_AGC_SLOW)
-	D->agc_fast_attack = TUNE_AGC_FAST;
-	D->agc_slow_decay = TUNE_AGC_SLOW;
-#endif
-#ifdef TUNE_HYST
-	D->hysteresis = TUNE_HYST;
-#endif
-#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
-	D->pll_locked_inertia = TUNE_PLL_LOCKED;
-	D->pll_searching_inertia = TUNE_PLL_SEARCHING;
-#endif
-#ifdef TUNE_LPF_BAUD
-	D->lpf_baud = TUNE_LPF_BAUD;
-#endif
-#ifdef TUNE_PRE_BAUD
-	D->prefilter_baud = TUNE_PRE_BAUD;
-#endif
-
-
-/*
- * Calculate constants used for timing.
- * The audio sample rate must be at least a few times the data rate.
- */
-
-	D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec));
-
-/*
- * Convert number of bit times to number of taps.
- */
-
-	D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud );
-	D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud );
-	D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud );
-	  	 
-/* Experiment with other sizes. */
-
-#ifdef TUNE_PRE_FILTER_SIZE
-	D->pre_filter_size = TUNE_PRE_FILTER_SIZE;
-#endif
-#ifdef TUNE_MS_FILTER_SIZE
-	D->ms_filter_size = TUNE_MS_FILTER_SIZE;
-#endif
-#ifdef TUNE_LP_FILTER_SIZE
-	D->lp_filter_size = TUNE_LP_FILTER_SIZE;
-#endif
-
-	//assert (D->pre_filter_size >= 4);
-	assert (D->ms_filter_size >= 4);
-	//assert (D->lp_filter_size >= 4);
-
-	if (D->pre_filter_size > MAX_FILTER_SIZE) 
-	{
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
-	  dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
-	  dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
-							MAX_FILTER_SIZE);
-	  exit (1);
-	}
-
-	if (D->ms_filter_size > MAX_FILTER_SIZE) 
-	{
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size);
-	  dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
-	  dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
-							MAX_FILTER_SIZE);
-	  exit (1);
-	}
-
-	if (D->lp_filter_size > MAX_FILTER_SIZE) 
-	{
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
-	  dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
-	  dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
-							MAX_FILTER_SIZE);
-	  exit (1);
-	}
-
-/* 
- * Optionally apply a bandpass ("pre") filter to attenuate
- * frequencies outside the range of interest.
- * This was first used for the "D" profile for 300 baud
- * which uses narrow shift.  We expect it to have significant
- * benefit for a narrow shift.
- * In version 1.2, we will also try it with 1200 baud "E" as
- * an experiment to see how much it actually helps.
- */
-
-	if (D->use_prefilter) {
-	  float f1, f2;
-
-	  f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud;
-	  f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud;
-#if 0
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2);
-#endif
-	  f1 = f1 / (float)samples_per_sec;
-	  f2 = f2 / (float)samples_per_sec;
-	  
-	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_HAMMING);
-	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_BLACKMAN);
-	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_COSINE);
-	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->bp_window);
-	  gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window);
-	}
-
-/*
- * Filters for detecting mark and space tones.
- */
-
-#if DEBUG1
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("%s:  \n", __FILE__);
-	  dw_printf ("%d baud, %d samples_per_sec\n", baud, samples_per_sec);
-	  dw_printf ("AFSK %d & %d Hz\n", mark_freq, space_freq);
-	  dw_printf ("spll_step_per_sample = %d = 0x%08x\n", D->pll_step_per_sample, D->pll_step_per_sample);
-	  dw_printf ("D->ms_filter_size = %d = 0x%08x\n", D->ms_filter_size, D->ms_filter_size);
-	  dw_printf ("\n");
-	  dw_printf ("Mark\n");
-	  dw_printf ("   j     shape   M sin   M cos \n");
-#endif
-
-	  float Gs = 0, Gc = 0;
-
-          for (j=0; j<D->ms_filter_size; j++) {
-	    float am;
-	    float center;
-	    float shape = 1;		/* Shape is an attempt to smooth out the */
-					/* abrupt edges in hopes of reducing */
-					/* overshoot and ringing. */
-					/* My first thought was to use a cosine shape. */
-					/* Should investigate Hamming and Blackman */
-					/* windows mentioned in the literature. */
-					/* http://en.wikipedia.org/wiki/Window_function */
-
-	    center = 0.5 * (D->ms_filter_size - 1);
-	    am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI);
-
-	    shape = window (D->ms_window, D->ms_filter_size, j);
-
-	    D->m_sin_table[j] = sin(am) * shape;
-  	    D->m_cos_table[j] = cos(am) * shape;
-
-	    Gs += D->m_sin_table[j] * sin(am);
-	    Gc += D->m_cos_table[j] * cos(am);
-
-#if DEBUG1
-	    dw_printf ("%6d  %6.2f  %6.2f  %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ;
-#endif
-          }
-
-
-/* Normalize for unity gain */
-
-#if DEBUG1
-	  dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ;
-#endif
-          for (j=0; j<D->ms_filter_size; j++) {
-	    D->m_sin_table[j] = D->m_sin_table[j] / Gs;
-	    D->m_cos_table[j] = D->m_cos_table[j] / Gc;
-	  }
-
-
-#if DEBUG1
-	  text_color_set(DW_COLOR_DEBUG);
-
-	  dw_printf ("Space\n");
-	  dw_printf ("   j     shape   S sin   S cos\n");
-#endif
-	  Gs = 0;
-	  Gc = 0;
-
-          for (j=0; j<D->ms_filter_size; j++) {
-	    float as;
-	    float center;
-	    float shape = 1;
-
-	    center = 0.5 * (D->ms_filter_size - 1);
-	    as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI);
-
-	    shape = window (D->ms_window, D->ms_filter_size, j);
-
-	    D->s_sin_table[j] = sin(as) * shape;
-  	    D->s_cos_table[j] = cos(as) * shape;
-
-	    Gs += D->s_sin_table[j] * sin(as);
-	    Gc += D->s_cos_table[j] * cos(as);
-
-#if DEBUG1
-	    dw_printf ("%6d  %6.2f  %6.2f  %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ;
-#endif
-          }
-
-
-/* Normalize for unity gain */
-
-#if DEBUG1
-	  dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ;
-#endif
-          for (j=0; j<D->ms_filter_size; j++) {
-	    D->s_sin_table[j] = D->s_sin_table[j] / Gs;
-	    D->s_cos_table[j] = D->s_cos_table[j] / Gc;
-	  }
-
-/*
- * Now the lowpass filter.
- * I thought we'd want a cutoff of about 0.5 the baud rate 
- * but it turns out about 1.1x is better.  Still investigating...
- */
-
-	if (D->lpf_use_fir) {
-	  float fc;
-	  fc = baud * D->lpf_baud / (float)samples_per_sec;
-	  gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
-	}
-
-/*
- * A non-whole number of cycles results in a DC bias. 
- * Let's see if it helps to take it out.
- * Actually makes things worse:  20 fewer decoded.
- * Might want to try again after EXPERIMENTC.
- */
-
-#if 0
-#ifndef AVOID_FLOATING_POINT
-
-failed experiment
-
-	dc_bias = 0;
-        for (j=0; j<D->ms_filter_size; j++) {
-	  dc_bias += D->m_sin_table[j];
-	}
-        for (j=0; j<D->ms_filter_size; j++) {
-	  D->m_sin_table[j] -= dc_bias / D->ms_filter_size;
-	}
-
-	dc_bias = 0;
-        for (j=0; j<D->ms_filter_size; j++) {
-	  dc_bias += D->m_cos_table[j];
-	}
-        for (j=0; j<D->ms_filter_size; j++) {
-	  D->m_cos_table[j] -= dc_bias / D->ms_filter_size;
-	}
-
-
-	dc_bias = 0;
-        for (j=0; j<D->ms_filter_size; j++) {
-	  dc_bias += D->s_sin_table[j];
-	}
-        for (j=0; j<D->ms_filter_size; j++) {
-	  D->s_sin_table[j] -= dc_bias / D->ms_filter_size;
-	}
-
-	dc_bias = 0;
-        for (j=0; j<D->ms_filter_size; j++) {
-	  dc_bias += D->s_cos_table[j];
-	}
-        for (j=0; j<D->ms_filter_size; j++) {
-	  D->s_cos_table[j] -= dc_bias / D->ms_filter_size;
-	}
-
-#endif
-#endif
-
-/*
- * In version 1.2 we try another experiment.
- * Try using multiple slicing points instead of the traditional AGC.
- */
-
-	space_gain[0] = MIN_G;
-	float step = powf(10.0, log10f(MAX_G/MIN_G) / (MAX_SUBCHANS-1));
-	for (j=1; j<MAX_SUBCHANS; j++) {
-	  space_gain[j] = space_gain[j-1] * step;
-	}
-
-#ifndef GEN_FFF
-#if 0
-	text_color_set(DW_COLOR_DEBUG);
-	for (j=0; j<MAX_SUBCHANS; j++) {
-	  float db = 20.0 * log10f(space_gain[j]);
-	  dw_printf ("G = %.3f, %+.1f dB\n", space_gain[j], db);
-	}
-#endif
-#endif
-
-}  /* fsk_gen_filter */
-
-
-#if GEN_FFF
-
-
-
-// Properties of the radio channels.
-
-static struct audio_s modem;
-
-
-// Filters will be stored here. 
-
-static struct demodulator_state_s ds;
-
-
-#define SPARSE 3
-
-
-static void emit_macro (char *name, int size, float *coeff)
-{
-	int i;
-
-	dw_printf ("#define %s(x) \\\n", name);
-
-	for (i=SPARSE/2; i<size; i+=SPARSE) {
-	  dw_printf ("\t%c (%.6ff * x[%d]) \\\n", (i==0 ? ' ' : '+'), coeff[i], i);
-	}
-	dw_printf ("\n");
-}
-
-int main ()
-{
-	//int n;
-	char fff_profile;
-
-	fff_profile = 'F';		
-
-	memset (&modem, 0, sizeof(modem));
-	memset (&ds, 0, sizeof(ds));
-
-	modem.adev[0].num_channels = 1;
-	modem.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
-	modem.achan[0].mark_freq = DEFAULT_MARK_FREQ;
-	modem.achan[0].space_freq = DEFAULT_SPACE_FREQ;
-	modem.achan[0].baud = DEFAULT_BAUD;
- 	modem.achan[0].num_demod = 1;
- 	modem.achan[0].num_subchan = 1;
-
-
-	demod_afsk_init (modem.adev[0].samples_per_sec, modem.achan[0].baud,
-			modem.achan[0].mark_freq, modem.achan[0].space_freq, fff_profile, &ds);
-	
-	printf ("/* This is an automatically generated file.  Do not edit. */\n");
-	printf ("\n");
-	printf ("#define FFF_SAMPLES_PER_SEC %d\n", modem.adev[0].samples_per_sec);
-	printf ("#define FFF_BAUD %d\n", modem.achan[0].baud);
-	printf ("#define FFF_MARK_FREQ %d\n", modem.achan[0].mark_freq);
-	printf ("#define FFF_SPACE_FREQ %d\n", modem.achan[0].space_freq);
-	printf ("#define FFF_PROFILE '%c'\n", fff_profile);
-	printf ("\n");
-
-	emit_macro ("CALC_M_SUM1", ds.ms_filter_size, ds.m_sin_table);
-	emit_macro ("CALC_M_SUM2", ds.ms_filter_size, ds.m_cos_table);
-	emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table);
-	emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table);
-
-	exit(0);
-}
-
-#endif
-
-
-
-#ifndef GEN_FFF
-
-/* Optimization for slow processors. */
-
-#include "fsk_fast_filter.h"
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        demod_afsk_process_sample
- *
- * Purpose:     (1) Demodulate the AFSK signal.
- *		(2) Recover clock and data.
- *
- * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
- *		subchan - modem of the channel.
- *		sam	- One sample of audio.
- *			  Should be in range of -32768 .. 32767.
- *
- * Returns:	None 
- *
- * Descripion:	We start off with two bandpass filters tuned to
- *		the given frequencies.  In the case of VHF packet
- *		radio, this would be 1200 and 2200 Hz.
- *
- *		The bandpass filter amplitudes are compared to 
- *		obtain the demodulated signal.
- *
- *		We also have a digital phase locked loop (PLL)
- *		to recover the clock and pick out data bits at
- *		the proper rate.
- *
- *		For each recovered data bit, we call:
- *
- *			  hdlc_rec (channel, demodulated_bit);
- *
- *		to decode HDLC frames from the stream of bits.
- *
- * Future:	This could be generalized by passing in the name
- *		of the function to be called for each bit recovered
- *		from the demodulator.  For now, it's simply hard-coded.
- *
- *--------------------------------------------------------------------*/
-
-static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D);
-
-__attribute__((hot))
-void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D)
-{
-	float fsam, abs_fsam;
-	float m_sum1, m_sum2, s_sum1, s_sum2;
-	float m_amp, s_amp;
-	float m_norm, s_norm;
-	float demod_out;
-#if DEBUG4
-	static FILE *demod_log_fp = NULL;
-	static int seq = 0;			/* for log file name */
-#endif
-
-
-	int j;
-	int demod_data;
-
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-/* 
- * Filters use last 'filter_size' samples.
- *
- * First push the older samples down. 
- *
- * Finally, put the most recent at the beginning.
- *
- * Future project?  Can we do better than shifting each time?
- */
-
-	/* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */
-
-	fsam = sam / 16384.0f;
-
-	abs_fsam = fsam >= 0.0f ? fsam : -fsam;
-
-
-/*
- * Optional bandpass filter before the mark/space discriminator.
- */
-
-	if (D->use_prefilter) {
-	  float cleaner;
-
-	  push_sample (fsam, D->raw_cb, D->pre_filter_size);
-	  cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size);
-	  push_sample (cleaner, D->ms_in_cb, D->ms_filter_size);
-	}
-	else {
-	  push_sample (fsam, D->ms_in_cb, D->ms_filter_size);
-	}
-
-/*
- * Next we have bandpass filters for the mark and space tones.
- *
- * This takes a lot of computation.
- * It's not a problem on a typical (Intel x86 based) PC.
- * Dire Wolf takes only about 2 or 3% of the CPU time.
- *
- * It might be too much for a little microcomputer to handle.
- *
- * Here we have an optimized case for the default values.
- */
-
-
-
-// TODO1.2:   is this right or do we need to store profile in the modulator info?
-
-	
-	if (D->profile == toupper(FFF_PROFILE)) {
-
-				/* ========== Faster for default values on slower processors. ========== */
-
-	  m_sum1 = CALC_M_SUM1(D->ms_in_cb);
-	  m_sum2 = CALC_M_SUM2(D->ms_in_cb);
-	  m_amp = z(m_sum1,m_sum2);
-
-	  s_sum1 = CALC_S_SUM1(D->ms_in_cb);
-	  s_sum2 = CALC_S_SUM2(D->ms_in_cb);
-	  s_amp = z(s_sum1,s_sum2);
-	}
-	else {
-
-				/* ========== General case to handle all situations. ========== */
-	
-/*
- * find amplitude of "Mark" tone.
- */
-	  m_sum1 = convolve (D->ms_in_cb, D->m_sin_table, D->ms_filter_size);
-	  m_sum2 = convolve (D->ms_in_cb, D->m_cos_table, D->ms_filter_size);
-
-	  m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2);
-
-/*
- * Find amplitude of "Space" tone.
- */
-	  s_sum1 = convolve (D->ms_in_cb, D->s_sin_table, D->ms_filter_size);
-	  s_sum2 = convolve (D->ms_in_cb, D->s_cos_table, D->ms_filter_size);
-
-	  s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2);
-
-				/* ========== End of general case. ========== */
-	}
-		
-
-/* 
- * Apply some low pass filtering BEFORE the AGC to remove
- * overshoot, ringing, and other bad stuff.
- *
- * A simple IIR filter is faster but FIR produces better results.
- *
- * It is a balancing act between removing high frequency components
- * from the tone dectection while letting the data thru.
- */
-
-	if (D->lpf_use_fir) {
-
-	  push_sample (m_amp, D->m_amp_cb, D->lp_filter_size);
-	  m_amp = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size);
-
-	  push_sample (s_amp, D->s_amp_cb, D->lp_filter_size);
-	  s_amp = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size);
-	}
-	else {
-	
-	  /* Original, but faster, IIR. */
-
-	  m_amp = D->lpf_iir * m_amp + (1.0f - D->lpf_iir) * D->m_amp_prev;
-	  D->m_amp_prev = m_amp;
-
-	  s_amp = D->lpf_iir * s_amp + (1.0f - D->lpf_iir) * D->s_amp_prev;
-	  D->s_amp_prev = s_amp;
-	}
-
-/*
- * Version 1.2: Try new approach to capturing the amplitude for display.
- * This is same as the AGC above without the normalization step.
- * We want decay to be substantially slower to get a longer
- * range idea of the received audio.
- */
-
-	if (m_amp >= D->alevel_mark_peak) {
-	  D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
-	}
-	else {
-	  D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
-	}
-
-	if (s_amp >= D->alevel_space_peak) {
-	  D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
-	}
-	else {
-	  D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
-	}
-
-
-/* 
- * Which tone is stronger?
- *
- * In an ideal world, simply compare.  In my first naive attempt, that
- * worked perfectly with perfect signals. In the real world, we don't
- * have too many perfect signals.
- *
- * Here is an excellent explanation:
- * http://www.febo.com/packet/layer-one/transmit.html
- *
- * Under real conditions, we find that the higher tone has a
- * considerably smaller amplitude due to the passband characteristics
- * of the transmitter and receiver.  To make matters worse, it
- * varies considerably from one station to another.
- *
- * The two filters also have different amounts of DC bias.
- *
- * My solution was to apply automatic gain control (AGC) to the mark and space 
- * levels.  This works by looking at the minimum and maximum outputs
- * for each filter and scaling the results to be roughly in the -0.5 to +0.5 range.
- * Results were excellent after tweaking the attack and decay times.
- *
- * 4X6IZ took a different approach.  See QEX Jul-Aug 2012.
- *
- * He ran two different demodulators in parallel.  One of them boosted the higher
- * frequency tone by 6 dB.  Any duplicates were removed.  This produced similar results.
- * He also used a bandpass filter before the mark/space filters.  
- * I haven't tried this combination yet for 1200 baud.
- *
- * First, let's take a look at Track 1 of the TNC test CD.  Here the receiver
- * has a flat response.  We find the mark/space strength ratios very from 0.53 to 1.38
- * with a median of 0.81.  This in in line with expections because most
- * transmitters add pre-emphasis to boost the higher audio frequencies.
- * Track 2 should more closely resemble what comes out of the speaker on a typical
- * transceiver.  Here we see a ratio from 1.73 to 3.81 with a median of 2.48.
- * 
- * This is similar to my observations of local signals, from the speaker.
- * The amplitude ratio varies from 1.48 to 3.41 with a median of 2.70. 
- *
- * Rather than only two filters, let's try slicing the data in more places. 
- */
-
-	/* Fast attack and slow decay. */
-	/* Numbers were obtained by trial and error from actual */
-	/* recorded less-than-optimal signals. */
-
-	/* See fsk_demod_agc.h for more information. */
-
-	m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
-	s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley));
-
-	if (D->num_slicers <= 1) {
-
-	  /* Normal case of one demodulator to one HDLC decoder. */
-	  /* Demodulator output is difference between response from two filters. */
-	  /* AGC should generally keep this around -1 to +1 range. */
-
-	  demod_out = m_norm - s_norm;
-
-	  /* Try adding some Hysteresis. */
-	  /* (Not to be confused with Hysteria.) */
-
-	  if (demod_out > D->hysteresis) {
-	    demod_data = 1;
-	  }
-	  else if (demod_out < (- (D->hysteresis))) {
-	    demod_data = 0;
-	  } 
-	  else {
-	    demod_data = D->slicer[subchan].prev_demod_data;
-	  }
-
-	  nudge_pll (chan, subchan, demod_data, D);
-	}
-	else {
-	  int s;
-
-	  assert (subchan == 0);
-
-	  /* "G" profile with one demodulator and multiple slicers */
-	  /* each feeding its own HDLC decoder. */
-
-	  for (s=0; s<D->num_slicers; s++) {
-	    demod_data = m_amp > s_amp * space_gain[s];
-	    nudge_pll (chan, s, demod_data, D);		
-	  }
-	}
-
-
-
-
-#if DEBUG4
-
-	if (chan == 0) {
-	if (hdlc_rec_gathering (chan, subchan)) {
-	  char fname[30];
-
-	  
-	  if (demod_log_fp == NULL) {
-	    seq++;
-	    sprintf (fname, "demod/%04d.csv", seq);
-	    if (seq == 1) mkdir ("demod", 0777);
-
-	    demod_log_fp = fopen (fname, "w");
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("Starting demodulator log file %s\n", fname);
-	    fprintf (demod_log_fp, "Audio, Mark, Space, Demod, Data, Clock\n");
-	  }
-	  fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f\n", fsam + 3.5, m_norm + 2, s_norm + 2, 
-			(m_norm - s_norm) / 2 + 1.5,
-			demod_data ? .9 : .55,  
-			(D->data_clock_pll & 0x80000000) ? .1 : .45);
-	}
-	else {
-	  if (demod_log_fp != NULL) {
-	    fclose (demod_log_fp);
-	    demod_log_fp = NULL;
-	  }
-	}
-	}
-
-#endif
-
-
-} /* end demod_afsk_process_sample */
-
-
-__attribute__((hot))
-static void nudge_pll (int chan, int subchan, int demod_data, struct demodulator_state_s *D)
-{
-
-/*
- * Finally, a PLL is used to sample near the centers of the data bits.
- *
- * D->data_clock_pll is a SIGNED 32 bit variable.
- * When it overflows from a large positive value to a negative value, we 
- * sample a data bit from the demodulated signal.
- *
- * Ideally, the the demodulated signal transitions should be near
- * zero we we sample mid way between the transitions.
- *
- * Nudge the PLL by removing some small fraction from the value of 
- * data_clock_pll, pushing it closer to zero.
- * 
- * This adjustment will never change the sign so it won't cause
- * any erratic data bit sampling.
- *
- * If we adjust it too quickly, the clock will have too much jitter.
- * If we adjust it too slowly, it will take too long to lock on to a new signal.
- *
- * Be a little more agressive about adjusting the PLL
- * phase when searching for a signal.  Don't change it as much when
- * locked on to a signal.
- *
- * I don't think the optimal value will depend on the audio sample rate
- * because this happens for each transition from the demodulator.
- */
-	D->slicer[subchan].prev_d_c_pll = D->slicer[subchan].data_clock_pll;
-	D->slicer[subchan].data_clock_pll += D->pll_step_per_sample;
-
-	  //text_color_set(DW_COLOR_DEBUG);
-	  // dw_printf ("prev = %lx, new data clock pll = %lx\n" D->prev_d_c_pll, D->data_clock_pll);
-
-	if (D->slicer[subchan].data_clock_pll < 0 && D->slicer[subchan].prev_d_c_pll > 0) {
-
-	  /* Overflow. */
-
-	  hdlc_rec_bit (chan, subchan, demod_data, 0, -1);
-	}
-
-        if (demod_data != D->slicer[subchan].prev_demod_data) {
-
-	  if (hdlc_rec_gathering (chan, subchan)) {
-	    D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_locked_inertia);
-	  }
-	  else {
-	    D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_searching_inertia);
-	  }
-	}
-
-/*
- * Remember demodulator output so we can compare next time.
- */
-	D->slicer[subchan].prev_demod_data = demod_data;
-
-} /* end nudge_pll */
-
-
-#endif   /* GEN_FFF */
-
-
-/* end demod_afsk.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+// 
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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     /* display debugging info */
+
+// #define DEBUG3 1	/* print carrier detect changes. */
+
+// #define DEBUG4 1	/* capture AFSK demodulator output to log files */
+
+// #define DEBUG5 1	/* capture 9600 output to log files */
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      demod_afsk.c
+ *
+ * Purpose:   	Demodulator for Audio Frequency Shift Keying (AFSK).
+ *		
+ * Input:	Audio samples from either a file or the "sound card."
+ *
+ * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
+ *
+ *---------------------------------------------------------------*/
+
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "direwolf.h"
+#include "audio.h"
+
+#include "tune.h"
+#include "fsk_demod_state.h"
+#include "fsk_gen_filter.h"
+#include "hdlc_rec.h"
+#include "textcolor.h"
+#include "demod_afsk.h"
+#include "dsp.h"
+
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+
+
+
+/* Quick approximation to sqrt(x*x+y*y) */
+/* No benefit for regular PC. */
+/* Should help with microcomputer platform. */
+
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline float z (float x, float y)
+{
+        x = fabsf(x);
+        y = fabsf(y);
+
+        if (x > y) {
+          return (x * .941246f + y * .41f);
+        }
+        else {
+          return (y * .941246f + x * .41f);
+        }
+}
+
+/* Add sample to buffer and shift the rest down. */
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline void push_sample (float val, float *buff, int size)
+{
+	memmove(buff+1,buff,(size-1)*sizeof(float));
+	buff[0] = val; 
+}
+
+
+/* FIR filter kernel. */
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
+{
+	float sum = 0.0f;
+	int j;
+
+
+#pragma GCC ivdep				// ignored until gcc 4.9
+	for (j=0; j<filter_size; j++) {
+	    sum += filter[j] * data[j];
+	}
+
+	return (sum);
+}
+
+/* Automatic gain control. */
+/* Result should settle down to 1 unit peak to peak.  i.e. -0.5 to +0.5 */
+
+__attribute__((hot)) __attribute__((always_inline))
+static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
+{
+	if (in >= *ppeak) {
+	  *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
+	}
+	else {
+	  *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
+	}
+
+	if (in <= *pvalley) {
+	  *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
+	}
+	else  {   
+	  *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
+	}
+
+	if (*ppeak > *pvalley) {
+	  return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
+	}
+	return (0.0f);
+}
+
+
+/*
+ * for multi-slicer experiment.
+ */
+
+#define MIN_G 0.5f
+#define MAX_G 4.0f
+
+/* TODO: static */  float space_gain[MAX_SUBCHANS];
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        demod_afsk_init
+ *
+ * Purpose:     Initialization for an AFSK demodulator.
+ *		Select appropriate parameters and set up filters.
+ *
+ * Inputs:   	samples_per_sec
+ *		baud
+ *		mark_freq
+ *		space_freq
+ *	
+ *		D		- Pointer to demodulator state for given channel.
+ *
+ * Outputs:	D->ms_filter_size
+ *		D->m_sin_table[] 
+ *		D->m_cos_table[]
+ *		D->s_sin_table[] 
+ *		D->s_cos_table[]
+ *
+ * Returns:     None.
+ *		
+ * Bugs:	This doesn't do much error checking so don't give it
+ *		anything crazy.
+ *
+ *----------------------------------------------------------------*/
+
+void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
+			int space_freq, char profile, struct demodulator_state_s *D)
+{
+	
+	int j;
+	
+	memset (D, 0, sizeof(struct demodulator_state_s));
+	D->num_slicers = 1;
+
+#if DEBUG1
+	dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n",
+		samples_per_sec, baud, mark_freq, space_freq, profile);
+#endif
+				
+#ifdef TUNE_PROFILE
+	profile = TUNE_PROFILE;
+#endif
+
+
+	if (profile == 'F') {
+
+	  if (baud != DEFAULT_BAUD ||
+	      mark_freq != DEFAULT_MARK_FREQ ||
+	      space_freq!= DEFAULT_SPACE_FREQ ||
+	      samples_per_sec != DEFAULT_SAMPLES_PER_SEC) {
+
+	    	text_color_set(DW_COLOR_INFO);
+		dw_printf ("Note: Decoder 'F' works only for %d baud, %d/%d tones, %d samples/sec.\n",
+			DEFAULT_BAUD, DEFAULT_MARK_FREQ, DEFAULT_SPACE_FREQ, DEFAULT_SAMPLES_PER_SEC);
+		dw_printf ("Using Decoder 'A' instead.\n");
+		profile = 'A';
+	  }
+	}
+
+	D->profile = profile;		// so we know whether to take fast path later.
+
+	switch (profile) {
+
+	  case 'A':
+	  case 'F':
+
+		/* Original.  52 taps, truncated bandpass, IIR lowpass */
+		/* 'F' is the fast version for low end processors. */
+		/* It is a special case that works only for a particular */
+		/* baud rate, tone pair, and sampling rate. */
+
+	    D->use_prefilter = 0;		
+
+	    D->ms_filter_len_bits = 1.415;		/* 52 @ 44100, 1200 */
+	    D->ms_window = BP_WINDOW_TRUNCATED;
+
+	    //D->bp_window = BP_WINDOW_TRUNCATED;
+
+	    D->lpf_use_fir = 0;
+	    D->lpf_iir = 0.195;
+
+	    D->agc_fast_attack = 0.250;		
+	    D->agc_slow_decay = 0.00012;
+	    D->hysteresis = 0.005;
+
+	    D->pll_locked_inertia = 0.700;
+	    D->pll_searching_inertia = 0.580;
+	    break;
+
+	  case 'B':
+
+		/* Original bandpass.  Use FIR lowpass instead. */
+
+	    D->use_prefilter = 0;		
+
+	    D->ms_filter_len_bits = 1.415;		/* 52 @ 44100, 1200 */
+	    D->ms_window = BP_WINDOW_TRUNCATED;
+
+	    //D->bp_window = BP_WINDOW_TRUNCATED;
+
+	    D->lpf_use_fir = 1;
+	    D->lpf_baud = 1.09;
+	    D->lp_filter_len_bits = D->ms_filter_len_bits;		
+	    D->lp_window = BP_WINDOW_TRUNCATED;
+
+	    D->agc_fast_attack = 0.370;		
+	    D->agc_slow_decay = 0.00014;
+	    D->hysteresis = 0.003;
+
+	    D->pll_locked_inertia = 0.620;
+	    D->pll_searching_inertia = 0.350;
+	    break;
+
+	  case 'C':
+
+		/* Cosine window, 76 taps for bandpass, FIR lowpass. */
+
+	    D->use_prefilter = 0;		
+
+	    D->ms_filter_len_bits = 2.068;		/* 76 @ 44100, 1200 */
+	    D->ms_window = BP_WINDOW_COSINE;
+
+	    //D->bp_window = BP_WINDOW_COSINE;
+
+	    D->lpf_use_fir = 1;
+	    D->lpf_baud = 1.09;
+	    D->lp_filter_len_bits = D->ms_filter_len_bits;		
+	    D->lp_window = BP_WINDOW_TRUNCATED;
+
+	    D->agc_fast_attack = 0.495;		
+	    D->agc_slow_decay = 0.00022;
+	    D->hysteresis = 0.005;
+
+	    D->pll_locked_inertia = 0.620;
+	    D->pll_searching_inertia = 0.350;
+	    break;
+
+	  case 'D':
+
+		/* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */
+
+	    D->use_prefilter = 1;		/* first, a bandpass filter. */
+	    D->prefilter_baud = 0.87;		
+	    D->pre_filter_len_bits = 1.857;	
+	    D->pre_window = BP_WINDOW_COSINE;
+
+	    D->ms_filter_len_bits = 1.857;		/* 91 @ 44100/3, 300 */
+	    D->ms_window = BP_WINDOW_COSINE;
+		
+	    //D->bp_window = BP_WINDOW_COSINE;
+
+	    D->lpf_use_fir = 1;
+	    D->lpf_baud = 1.10;
+	    D->lp_filter_len_bits = D->ms_filter_len_bits;	
+	    D->lp_window = BP_WINDOW_TRUNCATED;
+
+	    D->agc_fast_attack = 0.495;		
+	    D->agc_slow_decay = 0.00022;
+	    D->hysteresis = 0.027;
+
+	    D->pll_locked_inertia = 0.620;
+	    D->pll_searching_inertia = 0.350;
+	    break;
+
+	  case 'E':
+
+		/* 1200 baud - Started out similar to C but add prefilter. */
+		/* Version 1.2 */
+		/* Enhancements: 					*/
+		/*  + Add prefilter.  Previously used for 300 baud D, but not 1200. */
+		/*  + Prefilter length now independent of M/S filters.	*/
+		/*  + Lowpass filter length now independent of M/S filters.	*/
+		/*  + Allow mixed window types.	*/
+
+	    //D->bp_window = BP_WINDOW_COSINE;	/* The name says BP but it is used for all of them. */
+
+	    D->use_prefilter = 1;		/* first, a bandpass filter. */
+	    D->prefilter_baud = 0.23;		
+	    D->pre_filter_len_bits = 156 * 1200. / 44100.;	
+	    D->pre_window = BP_WINDOW_TRUNCATED;
+
+	    D->ms_filter_len_bits = 74 * 1200. / 44100.;		
+	    D->ms_window = BP_WINDOW_COSINE;
+
+	    D->lpf_use_fir = 1;
+	    D->lpf_baud = 1.18;
+	    D->lp_filter_len_bits = 63 * 1200. / 44100.;		
+	    D->lp_window = BP_WINDOW_TRUNCATED;
+
+	    //D->agc_fast_attack = 0.300;		
+	    //D->agc_slow_decay = 0.000185;
+	    D->agc_fast_attack = 0.820;		
+	    D->agc_slow_decay = 0.000214;
+	    D->hysteresis = 0.01;
+
+	    //D->pll_locked_inertia = 0.57;
+	    //D->pll_searching_inertia = 0.33;
+	    D->pll_locked_inertia = 0.74;
+	    D->pll_searching_inertia = 0.50;
+	    break;
+
+	  case 'G':
+
+		/* 1200 baud - Started out same as E but add 3 way interleave. */
+		/* Version 1.3 - EXPERIMENTAL - Needs more fine tuning. */
+
+	    //D->bp_window = BP_WINDOW_COSINE;	/* The name says BP but it is used for all of them. */
+
+	    D->use_prefilter = 1;		/* first, a bandpass filter. */
+	    D->prefilter_baud = 0.15;
+	    D->pre_filter_len_bits = 128 * 1200. / (44100. / 3.);
+	    D->pre_window = BP_WINDOW_TRUNCATED;
+
+	    D->ms_filter_len_bits = 25 * 1200. / (44100. / 3.);
+	    D->ms_window = BP_WINDOW_COSINE;
+
+	    D->lpf_use_fir = 1;
+	    D->lpf_baud = 1.16;
+	    D->lp_filter_len_bits = 21 * 1200. / (44100. / 3.);
+	    D->lp_window = BP_WINDOW_TRUNCATED;
+
+	    D->agc_fast_attack = 0.130;
+	    D->agc_slow_decay = 0.00013;
+	    D->hysteresis = 0.01;
+
+	    D->pll_locked_inertia = 0.73;
+	    D->pll_searching_inertia = 0.64;
+	    break;
+
+	  default:
+
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Invalid filter profile = %c\n", profile);
+	    exit (1);
+	}
+
+#ifdef TUNE_PRE_WINDOW
+	D->pre_window = TUNE_PRE_WINDOW;
+#endif
+#ifdef TUNE_MS_WINDOW
+	D->ms_window = TUNE_MS_WINDOW;
+#endif
+#ifdef TUNE_LP_WINDOW
+	D->lp_window = TUNE_LP_WINDOW;
+#endif
+
+
+#if defined(TUNE_AGC_FAST) && defined(TUNE_AGC_SLOW)
+	D->agc_fast_attack = TUNE_AGC_FAST;
+	D->agc_slow_decay = TUNE_AGC_SLOW;
+#endif
+#ifdef TUNE_HYST
+	D->hysteresis = TUNE_HYST;
+#endif
+#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
+	D->pll_locked_inertia = TUNE_PLL_LOCKED;
+	D->pll_searching_inertia = TUNE_PLL_SEARCHING;
+#endif
+#ifdef TUNE_LPF_BAUD
+	D->lpf_baud = TUNE_LPF_BAUD;
+#endif
+#ifdef TUNE_PRE_BAUD
+	D->prefilter_baud = TUNE_PRE_BAUD;
+#endif
+
+
+/*
+ * Calculate constants used for timing.
+ * The audio sample rate must be at least a few times the data rate.
+ */
+
+	D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec));
+
+/*
+ * Convert number of bit times to number of taps.
+ */
+
+	D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud );
+	D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud );
+	D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud );
+	  	 
+/* Experiment with other sizes. */
+
+#ifdef TUNE_PRE_FILTER_SIZE
+	D->pre_filter_size = TUNE_PRE_FILTER_SIZE;
+#endif
+#ifdef TUNE_MS_FILTER_SIZE
+	D->ms_filter_size = TUNE_MS_FILTER_SIZE;
+#endif
+#ifdef TUNE_LP_FILTER_SIZE
+	D->lp_filter_size = TUNE_LP_FILTER_SIZE;
+#endif
+
+	//assert (D->pre_filter_size >= 4);
+	assert (D->ms_filter_size >= 4);
+	//assert (D->lp_filter_size >= 4);
+
+	if (D->pre_filter_size > MAX_FILTER_SIZE) 
+	{
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
+	  dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
+	  dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
+							MAX_FILTER_SIZE);
+	  exit (1);
+	}
+
+	if (D->ms_filter_size > MAX_FILTER_SIZE) 
+	{
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size);
+	  dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
+	  dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
+							MAX_FILTER_SIZE);
+	  exit (1);
+	}
+
+	if (D->lp_filter_size > MAX_FILTER_SIZE) 
+	{
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
+	  dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
+	  dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
+							MAX_FILTER_SIZE);
+	  exit (1);
+	}
+
+/* 
+ * Optionally apply a bandpass ("pre") filter to attenuate
+ * frequencies outside the range of interest.
+ * This was first used for the "D" profile for 300 baud
+ * which uses narrow shift.  We expect it to have significant
+ * benefit for a narrow shift.
+ * In version 1.2, we will also try it with 1200 baud "E" as
+ * an experiment to see how much it actually helps.
+ */
+
+	if (D->use_prefilter) {
+	  float f1, f2;
+
+	  f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud;
+	  f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud;
+#if 0
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2);
+#endif
+	  f1 = f1 / (float)samples_per_sec;
+	  f2 = f2 / (float)samples_per_sec;
+	  
+	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_HAMMING);
+	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_BLACKMAN);
+	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_COSINE);
+	  //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->bp_window);
+	  gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window);
+	}
+
+/*
+ * Filters for detecting mark and space tones.
+ */
+
+#if DEBUG1
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("%s:  \n", __FILE__);
+	  dw_printf ("%d baud, %d samples_per_sec\n", baud, samples_per_sec);
+	  dw_printf ("AFSK %d & %d Hz\n", mark_freq, space_freq);
+	  dw_printf ("spll_step_per_sample = %d = 0x%08x\n", D->pll_step_per_sample, D->pll_step_per_sample);
+	  dw_printf ("D->ms_filter_size = %d = 0x%08x\n", D->ms_filter_size, D->ms_filter_size);
+	  dw_printf ("\n");
+	  dw_printf ("Mark\n");
+	  dw_printf ("   j     shape   M sin   M cos \n");
+#endif
+
+	  float Gs = 0, Gc = 0;
+
+          for (j=0; j<D->ms_filter_size; j++) {
+	    float am;
+	    float center;
+	    float shape = 1;		/* Shape is an attempt to smooth out the */
+					/* abrupt edges in hopes of reducing */
+					/* overshoot and ringing. */
+					/* My first thought was to use a cosine shape. */
+					/* Should investigate Hamming and Blackman */
+					/* windows mentioned in the literature. */
+					/* http://en.wikipedia.org/wiki/Window_function */
+
+	    center = 0.5 * (D->ms_filter_size - 1);
+	    am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI);
+
+	    shape = window (D->ms_window, D->ms_filter_size, j);
+
+	    D->m_sin_table[j] = sin(am) * shape;
+  	    D->m_cos_table[j] = cos(am) * shape;
+
+	    Gs += D->m_sin_table[j] * sin(am);
+	    Gc += D->m_cos_table[j] * cos(am);
+
+#if DEBUG1
+	    dw_printf ("%6d  %6.2f  %6.2f  %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ;
+#endif
+          }
+
+
+/* Normalize for unity gain */
+
+#if DEBUG1
+	  dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ;
+#endif
+          for (j=0; j<D->ms_filter_size; j++) {
+	    D->m_sin_table[j] = D->m_sin_table[j] / Gs;
+	    D->m_cos_table[j] = D->m_cos_table[j] / Gc;
+	  }
+
+
+#if DEBUG1
+	  text_color_set(DW_COLOR_DEBUG);
+
+	  dw_printf ("Space\n");
+	  dw_printf ("   j     shape   S sin   S cos\n");
+#endif
+	  Gs = 0;
+	  Gc = 0;
+
+          for (j=0; j<D->ms_filter_size; j++) {
+	    float as;
+	    float center;
+	    float shape = 1;
+
+	    center = 0.5 * (D->ms_filter_size - 1);
+	    as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI);
+
+	    shape = window (D->ms_window, D->ms_filter_size, j);
+
+	    D->s_sin_table[j] = sin(as) * shape;
+  	    D->s_cos_table[j] = cos(as) * shape;
+
+	    Gs += D->s_sin_table[j] * sin(as);
+	    Gc += D->s_cos_table[j] * cos(as);
+
+#if DEBUG1
+	    dw_printf ("%6d  %6.2f  %6.2f  %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ;
+#endif
+          }
+
+
+/* Normalize for unity gain */
+
+#if DEBUG1
+	  dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ;
+#endif
+          for (j=0; j<D->ms_filter_size; j++) {
+	    D->s_sin_table[j] = D->s_sin_table[j] / Gs;
+	    D->s_cos_table[j] = D->s_cos_table[j] / Gc;
+	  }
+
+/*
+ * Now the lowpass filter.
+ * I thought we'd want a cutoff of about 0.5 the baud rate 
+ * but it turns out about 1.1x is better.  Still investigating...
+ */
+
+	if (D->lpf_use_fir) {
+	  float fc;
+	  fc = baud * D->lpf_baud / (float)samples_per_sec;
+	  gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
+	}
+
+/*
+ * A non-whole number of cycles results in a DC bias. 
+ * Let's see if it helps to take it out.
+ * Actually makes things worse:  20 fewer decoded.
+ * Might want to try again after EXPERIMENTC.
+ */
+
+#if 0
+#ifndef AVOID_FLOATING_POINT
+
+failed experiment
+
+	dc_bias = 0;
+        for (j=0; j<D->ms_filter_size; j++) {
+	  dc_bias += D->m_sin_table[j];
+	}
+        for (j=0; j<D->ms_filter_size; j++) {
+	  D->m_sin_table[j] -= dc_bias / D->ms_filter_size;
+	}
+
+	dc_bias = 0;
+        for (j=0; j<D->ms_filter_size; j++) {
+	  dc_bias += D->m_cos_table[j];
+	}
+        for (j=0; j<D->ms_filter_size; j++) {
+	  D->m_cos_table[j] -= dc_bias / D->ms_filter_size;
+	}
+
+
+	dc_bias = 0;
+        for (j=0; j<D->ms_filter_size; j++) {
+	  dc_bias += D->s_sin_table[j];
+	}
+        for (j=0; j<D->ms_filter_size; j++) {
+	  D->s_sin_table[j] -= dc_bias / D->ms_filter_size;
+	}
+
+	dc_bias = 0;
+        for (j=0; j<D->ms_filter_size; j++) {
+	  dc_bias += D->s_cos_table[j];
+	}
+        for (j=0; j<D->ms_filter_size; j++) {
+	  D->s_cos_table[j] -= dc_bias / D->ms_filter_size;
+	}
+
+#endif
+#endif
+
+/*
+ * In version 1.2 we try another experiment.
+ * Try using multiple slicing points instead of the traditional AGC.
+ */
+
+	space_gain[0] = MIN_G;
+	float step = powf(10.0, log10f(MAX_G/MIN_G) / (MAX_SUBCHANS-1));
+	for (j=1; j<MAX_SUBCHANS; j++) {
+	  space_gain[j] = space_gain[j-1] * step;
+	}
+
+#ifndef GEN_FFF
+#if 0
+	text_color_set(DW_COLOR_DEBUG);
+	for (j=0; j<MAX_SUBCHANS; j++) {
+	  float db = 20.0 * log10f(space_gain[j]);
+	  dw_printf ("G = %.3f, %+.1f dB\n", space_gain[j], db);
+	}
+#endif
+#endif
+
+}  /* fsk_gen_filter */
+
+
+#if GEN_FFF
+
+
+
+// Properties of the radio channels.
+
+static struct audio_s modem;
+
+
+// Filters will be stored here. 
+
+static struct demodulator_state_s ds;
+
+
+#define SPARSE 3
+
+
+static void emit_macro (char *name, int size, float *coeff)
+{
+	int i;
+
+	dw_printf ("#define %s(x) \\\n", name);
+
+	for (i=SPARSE/2; i<size; i+=SPARSE) {
+	  dw_printf ("\t%c (%.6ff * x[%d]) \\\n", (i==0 ? ' ' : '+'), coeff[i], i);
+	}
+	dw_printf ("\n");
+}
+
+int main (void)
+{
+	//int n;
+	char fff_profile;
+
+	fff_profile = 'F';		
+
+	memset (&modem, 0, sizeof(modem));
+	memset (&ds, 0, sizeof(ds));
+
+	modem.adev[0].num_channels = 1;
+	modem.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
+	modem.achan[0].mark_freq = DEFAULT_MARK_FREQ;
+	modem.achan[0].space_freq = DEFAULT_SPACE_FREQ;
+	modem.achan[0].baud = DEFAULT_BAUD;
+ 	modem.achan[0].num_subchan = 1;
+ 	modem.achan[0].num_slicers = 1;
+
+
+	demod_afsk_init (modem.adev[0].samples_per_sec, modem.achan[0].baud,
+			modem.achan[0].mark_freq, modem.achan[0].space_freq, fff_profile, &ds);
+	
+	printf ("/* This is an automatically generated file.  Do not edit. */\n");
+	printf ("\n");
+	printf ("#define FFF_SAMPLES_PER_SEC %d\n", modem.adev[0].samples_per_sec);
+	printf ("#define FFF_BAUD %d\n", modem.achan[0].baud);
+	printf ("#define FFF_MARK_FREQ %d\n", modem.achan[0].mark_freq);
+	printf ("#define FFF_SPACE_FREQ %d\n", modem.achan[0].space_freq);
+	printf ("#define FFF_PROFILE '%c'\n", fff_profile);
+	printf ("\n");
+
+	emit_macro ("CALC_M_SUM1", ds.ms_filter_size, ds.m_sin_table);
+	emit_macro ("CALC_M_SUM2", ds.ms_filter_size, ds.m_cos_table);
+	emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table);
+	emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table);
+
+	exit(0);
+}
+
+#endif
+
+
+
+#ifndef GEN_FFF
+
+/* Optimization for slow processors. */
+
+#include "fsk_fast_filter.h"
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        demod_afsk_process_sample
+ *
+ * Purpose:     (1) Demodulate the AFSK signal.
+ *		(2) Recover clock and data.
+ *
+ * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
+ *		subchan - modem of the channel.
+ *		sam	- One sample of audio.
+ *			  Should be in range of -32768 .. 32767.
+ *
+ * Returns:	None 
+ *
+ * Descripion:	We start off with two bandpass filters tuned to
+ *		the given frequencies.  In the case of VHF packet
+ *		radio, this would be 1200 and 2200 Hz.
+ *
+ *		The bandpass filter amplitudes are compared to 
+ *		obtain the demodulated signal.
+ *
+ *		We also have a digital phase locked loop (PLL)
+ *		to recover the clock and pick out data bits at
+ *		the proper rate.
+ *
+ *		For each recovered data bit, we call:
+ *
+ *			  hdlc_rec (channel, demodulated_bit);
+ *
+ *		to decode HDLC frames from the stream of bits.
+ *
+ * Future:	This could be generalized by passing in the name
+ *		of the function to be called for each bit recovered
+ *		from the demodulator.  For now, it's simply hard-coded.
+ *
+ *--------------------------------------------------------------------*/
+
+static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D);
+
+__attribute__((hot))
+void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D)
+{
+	float fsam;
+	//float abs_fsam;
+	float m_sum1, m_sum2, s_sum1, s_sum2;
+	float m_amp, s_amp;
+	float m_norm, s_norm;
+	float demod_out;
+#if DEBUG4
+	static FILE *demod_log_fp = NULL;
+	static int seq = 0;			/* for log file name */
+#endif
+
+
+	//int j;
+	int demod_data;
+
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+
+/* 
+ * Filters use last 'filter_size' samples.
+ *
+ * First push the older samples down. 
+ *
+ * Finally, put the most recent at the beginning.
+ *
+ * Future project?  Can we do better than shifting each time?
+ */
+
+	/* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */
+
+	fsam = sam / 16384.0f;
+
+	//abs_fsam = fsam >= 0.0f ? fsam : -fsam;
+
+
+/*
+ * Optional bandpass filter before the mark/space discriminator.
+ */
+
+	if (D->use_prefilter) {
+	  float cleaner;
+
+	  push_sample (fsam, D->raw_cb, D->pre_filter_size);
+	  cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size);
+	  push_sample (cleaner, D->ms_in_cb, D->ms_filter_size);
+	}
+	else {
+	  push_sample (fsam, D->ms_in_cb, D->ms_filter_size);
+	}
+
+/*
+ * Next we have bandpass filters for the mark and space tones.
+ *
+ * This takes a lot of computation.
+ * It's not a problem on a typical (Intel x86 based) PC.
+ * Dire Wolf takes only about 2 or 3% of the CPU time.
+ *
+ * It might be too much for a little microcomputer to handle.
+ *
+ * Here we have an optimized case for the default values.
+ */
+
+
+
+// TODO1.2:   is this right or do we need to store profile in the modulator info?
+
+	
+	if (D->profile == toupper(FFF_PROFILE)) {
+
+				/* ========== Faster for default values on slower processors. ========== */
+
+	  m_sum1 = CALC_M_SUM1(D->ms_in_cb);
+	  m_sum2 = CALC_M_SUM2(D->ms_in_cb);
+	  m_amp = z(m_sum1,m_sum2);
+
+	  s_sum1 = CALC_S_SUM1(D->ms_in_cb);
+	  s_sum2 = CALC_S_SUM2(D->ms_in_cb);
+	  s_amp = z(s_sum1,s_sum2);
+	}
+	else {
+
+				/* ========== General case to handle all situations. ========== */
+	
+/*
+ * find amplitude of "Mark" tone.
+ */
+	  m_sum1 = convolve (D->ms_in_cb, D->m_sin_table, D->ms_filter_size);
+	  m_sum2 = convolve (D->ms_in_cb, D->m_cos_table, D->ms_filter_size);
+
+	  m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2);
+
+/*
+ * Find amplitude of "Space" tone.
+ */
+	  s_sum1 = convolve (D->ms_in_cb, D->s_sin_table, D->ms_filter_size);
+	  s_sum2 = convolve (D->ms_in_cb, D->s_cos_table, D->ms_filter_size);
+
+	  s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2);
+
+				/* ========== End of general case. ========== */
+	}
+		
+
+/* 
+ * Apply some low pass filtering BEFORE the AGC to remove
+ * overshoot, ringing, and other bad stuff.
+ *
+ * A simple IIR filter is faster but FIR produces better results.
+ *
+ * It is a balancing act between removing high frequency components
+ * from the tone dectection while letting the data thru.
+ */
+
+	if (D->lpf_use_fir) {
+
+	  push_sample (m_amp, D->m_amp_cb, D->lp_filter_size);
+	  m_amp = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size);
+
+	  push_sample (s_amp, D->s_amp_cb, D->lp_filter_size);
+	  s_amp = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size);
+	}
+	else {
+	
+	  /* Original, but faster, IIR. */
+
+	  m_amp = D->lpf_iir * m_amp + (1.0f - D->lpf_iir) * D->m_amp_prev;
+	  D->m_amp_prev = m_amp;
+
+	  s_amp = D->lpf_iir * s_amp + (1.0f - D->lpf_iir) * D->s_amp_prev;
+	  D->s_amp_prev = s_amp;
+	}
+
+/*
+ * Version 1.2: Try new approach to capturing the amplitude for display.
+ * This is same as the AGC above without the normalization step.
+ * We want decay to be substantially slower to get a longer
+ * range idea of the received audio.
+ */
+
+	if (m_amp >= D->alevel_mark_peak) {
+	  D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
+	}
+	else {
+	  D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
+	}
+
+	if (s_amp >= D->alevel_space_peak) {
+	  D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
+	}
+	else {
+	  D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
+	}
+
+
+/* 
+ * Which tone is stronger?
+ *
+ * In an ideal world, simply compare.  In my first naive attempt, that
+ * worked perfectly with perfect signals. In the real world, we don't
+ * have too many perfect signals.
+ *
+ * Here is an excellent explanation:
+ * http://www.febo.com/packet/layer-one/transmit.html
+ *
+ * Under real conditions, we find that the higher tone has a
+ * considerably smaller amplitude due to the passband characteristics
+ * of the transmitter and receiver.  To make matters worse, it
+ * varies considerably from one station to another.
+ *
+ * The two filters also have different amounts of DC bias.
+ *
+ * My solution was to apply automatic gain control (AGC) to the mark and space 
+ * levels.  This works by looking at the minimum and maximum outputs
+ * for each filter and scaling the results to be roughly in the -0.5 to +0.5 range.
+ * Results were excellent after tweaking the attack and decay times.
+ *
+ * 4X6IZ took a different approach.  See QEX Jul-Aug 2012.
+ *
+ * He ran two different demodulators in parallel.  One of them boosted the higher
+ * frequency tone by 6 dB.  Any duplicates were removed.  This produced similar results.
+ * He also used a bandpass filter before the mark/space filters.  
+ * I haven't tried this combination yet for 1200 baud.
+ *
+ * First, let's take a look at Track 1 of the TNC test CD.  Here the receiver
+ * has a flat response.  We find the mark/space strength ratios very from 0.53 to 1.38
+ * with a median of 0.81.  This in in line with expections because most
+ * transmitters add pre-emphasis to boost the higher audio frequencies.
+ * Track 2 should more closely resemble what comes out of the speaker on a typical
+ * transceiver.  Here we see a ratio from 1.73 to 3.81 with a median of 2.48.
+ * 
+ * This is similar to my observations of local signals, from the speaker.
+ * The amplitude ratio varies from 1.48 to 3.41 with a median of 2.70. 
+ *
+ * Rather than only two filters, let's try slicing the data in more places. 
+ */
+
+	/* Fast attack and slow decay. */
+	/* Numbers were obtained by trial and error from actual */
+	/* recorded less-than-optimal signals. */
+
+	/* See fsk_demod_agc.h for more information. */
+
+	m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
+	s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley));
+
+	if (D->num_slicers <= 1) {
+
+	  /* Normal case of one demodulator to one HDLC decoder. */
+	  /* Demodulator output is difference between response from two filters. */
+	  /* AGC should generally keep this around -1 to +1 range. */
+
+	  demod_out = m_norm - s_norm;
+
+	  /* Try adding some Hysteresis. */
+	  /* (Not to be confused with Hysteria.) */
+
+	  if (demod_out > D->hysteresis) {
+	    demod_data = 1;
+	  }
+	  else if (demod_out < (- (D->hysteresis))) {
+	    demod_data = 0;
+	  } 
+	  else {
+	    demod_data = D->slicer[subchan].prev_demod_data;
+	  }
+	  nudge_pll (chan, subchan, 0, demod_data, D);
+	}
+	else {
+	  int slice;
+
+	  for (slice=0; slice<D->num_slicers; slice++) {
+	    demod_data = m_amp > s_amp * space_gain[slice];
+	    nudge_pll (chan, subchan, slice, demod_data, D);
+	  }
+	}
+
+
+#if DEBUG4
+
+	if (chan == 0) {
+	if (hdlc_rec_gathering (chan, subchan)) {
+	  char fname[30];
+
+	  
+	  if (demod_log_fp == NULL) {
+	    seq++;
+	    snprintf (fname, sizeof(fname), "demod/%04d.csv", seq);
+	    if (seq == 1) mkdir ("demod", 0777);
+
+	    demod_log_fp = fopen (fname, "w");
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Starting demodulator log file %s\n", fname);
+	    fprintf (demod_log_fp, "Audio, Mark, Space, Demod, Data, Clock\n");
+	  }
+	  fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f\n", fsam + 3.5, m_norm + 2, s_norm + 2, 
+			(m_norm - s_norm) / 2 + 1.5,
+			demod_data ? .9 : .55,  
+			(D->data_clock_pll & 0x80000000) ? .1 : .45);
+	}
+	else {
+	  if (demod_log_fp != NULL) {
+	    fclose (demod_log_fp);
+	    demod_log_fp = NULL;
+	  }
+	}
+	}
+
+#endif
+
+
+} /* end demod_afsk_process_sample */
+
+
+__attribute__((hot))
+static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D)
+{
+
+/*
+ * Finally, a PLL is used to sample near the centers of the data bits.
+ *
+ * D points to a demodulator for a channel/subchannel pair so we don't
+ * have to keep recalculating it.
+ *
+ * D->data_clock_pll is a SIGNED 32 bit variable.
+ * When it overflows from a large positive value to a negative value, we 
+ * sample a data bit from the demodulated signal.
+ *
+ * Ideally, the the demodulated signal transitions should be near
+ * zero we we sample mid way between the transitions.
+ *
+ * Nudge the PLL by removing some small fraction from the value of 
+ * data_clock_pll, pushing it closer to zero.
+ * 
+ * This adjustment will never change the sign so it won't cause
+ * any erratic data bit sampling.
+ *
+ * If we adjust it too quickly, the clock will have too much jitter.
+ * If we adjust it too slowly, it will take too long to lock on to a new signal.
+ *
+ * Be a little more agressive about adjusting the PLL
+ * phase when searching for a signal.  Don't change it as much when
+ * locked on to a signal.
+ *
+ * I don't think the optimal value will depend on the audio sample rate
+ * because this happens for each transition from the demodulator.
+ */
+
+	D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
+	D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
+
+	  //text_color_set(DW_COLOR_DEBUG);
+	  // dw_printf ("prev = %lx, new data clock pll = %lx\n" D->prev_d_c_pll, D->data_clock_pll);
+
+	if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) {
+
+	  /* Overflow. */
+
+	  hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1);
+	}
+
+        if (demod_data != D->slicer[slice].prev_demod_data) {
+
+	  if (hdlc_rec_gathering (chan, subchan, slice)) {
+	    D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia);
+	  }
+	  else {
+	    D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia);
+	  }
+	}
+
+/*
+ * Remember demodulator output so we can compare next time.
+ */
+	D->slicer[slice].prev_demod_data = demod_data;
+
+} /* end nudge_pll */
+
+
+#endif   /* GEN_FFF */
+
+
+/* end demod_afsk.c */
diff --git a/demod_afsk.h b/demod_afsk.h
index 0969467..e44a44e 100644
--- a/demod_afsk.h
+++ b/demod_afsk.h
@@ -1,8 +1,8 @@
-
-/* demod_afsk.h */
-
-
-void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
-			int space_freq, char profile, struct demodulator_state_s *D);
-
-void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D);
+
+/* demod_afsk.h */
+
+
+void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
+			int space_freq, char profile, struct demodulator_state_s *D);
+
+void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D);
diff --git a/digipeater.c b/digipeater.c
index e309b36..7e90d91 100644
--- a/digipeater.c
+++ b/digipeater.c
@@ -1,913 +1,947 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Name:	digipeater.c
- *
- * Purpose:	Act as an APRS digital repeater.
- *
- *
- * Description:	Decide whether the specified packet should
- *		be digipeated and make necessary modifications.
- *
- *
- * References:	APRS Protocol Reference, document version 1.0.1
- *
- *			http://www.aprs.org/doc/APRS101.PDF
- *
- *		APRS SPEC Addendum 1.1
- *
- *			http://www.aprs.org/aprs11.html
- *
- *		APRS SPEC Addendum 1.2
- *
- *			http://www.aprs.org/aprs12.html
- *
- *		"The New n-N Paradigm"
- *
- *			http://www.aprs.org/fix14439.html
- *
- *		Preemptive Digipeating  (new in version 0.8)
- *
- *			http://www.aprs.org/aprs12/preemptive-digipeating.txt
- *		
- *------------------------------------------------------------------*/
-
-#define DIGIPEATER_C
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdio.h>
-#include <ctype.h>	/* for isdigit, isupper */
-#include "regex.h"
-#include <sys/unistd.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "digipeater.h"
-#include "textcolor.h"
-#include "dedupe.h"
-#include "tq.h"
-#include "pfilter.h"
-
-
-static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, 
-				regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter);
-
-//static int filter_by_type (char *source, char *infop, char *type_filter);
-
-
-/*
- * Keep pointer to configuration options.
- * Set by digipeater_init and used later.
- */
-
-
-static struct audio_s	    *save_audio_config_p;
-static struct digi_config_s *save_digi_config_p;
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	digipeater_init
- * 
- * Purpose:	Initialize with stuff from configuration file.
- *
- * Inputs:	p_audio_config	- Configuration for audio channels.
- *
- *		p_digi_config	- Digipeater configuration details.
- *		
- * Outputs:	Save pointers to configuration for later use.
- *		
- * Description:	Called once at application startup time.
- *
- *------------------------------------------------------------------------------*/
-
-void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config) 
-{
-	save_audio_config_p = p_audio_config;
-	save_digi_config_p = p_digi_config;
-	
-	dedupe_init (p_digi_config->dedupe_time);
-}
-
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	digipeater
- * 
- * Purpose:	Re-transmit packet if it matches the rules.
- *
- * Inputs:	chan	- Radio channel where it was received.
- *		
- * 		pp	- Packet object.
- *		
- * Returns:	None.
- *		
- *
- *------------------------------------------------------------------------------*/
-
-
-
-void digipeater (int from_chan, packet_t pp)
-{
-	int to_chan;
-	packet_t result;
-
-
-	// dw_printf ("digipeater()\n");
-	
-	assert (from_chan >= 0 && from_chan < MAX_CHANS);
-
-	if ( ! save_audio_config_p->achan[from_chan].valid) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("digipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
-	}
-
-
-/*
- * First pass:  Look at packets being digipeated to same channel.
- *
- * We want these to get out quickly.
- */
-
-	for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
-	  if (save_digi_config_p->enabled[from_chan][to_chan]) {
-	    if (to_chan == from_chan) {
-	      result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, 
-					   save_audio_config_p->achan[to_chan].mycall, 
-			&save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], 
-			to_chan, save_digi_config_p->preempt[from_chan][to_chan],
-				save_digi_config_p->filter_str[from_chan][to_chan]);
-	      if (result != NULL) {
-		dedupe_remember (pp, to_chan);
-	        tq_append (to_chan, TQ_PRIO_0_HI, result);
-	      }
-	    }
-	  }
-	}
-
-
-/*
- * Second pass:  Look at packets being digipeated to different channel.
- *
- * These are lower priority
- */
-
-	for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
-	  if (save_digi_config_p->enabled[from_chan][to_chan]) {
-	    if (to_chan != from_chan) {
-	      result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, 
-					   save_audio_config_p->achan[to_chan].mycall, 
-			&save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], 
-			to_chan, save_digi_config_p->preempt[from_chan][to_chan],
-				save_digi_config_p->filter_str[from_chan][to_chan]);
-	      if (result != NULL) {
-		dedupe_remember (pp, to_chan);
-	        tq_append (to_chan, TQ_PRIO_1_LO, result);
-	      }
-	    }
-	  }
-	}
-
-} /* end digipeater */
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	digipeat_match
- * 
- * Purpose:	A simple digipeater for APRS.
- *
- * Input:	pp		- Pointer to a packet object.
- *	
- *		mycall_rec	- Call of my station, with optional SSID,
- *				  associated with the radio channel where the 
- *				  packet was received.
- *
- *		mycall_xmit	- Call of my station, with optional SSID,
- *				  associated with the radio channel where the 
- *				  packet is to be transmitted.  Could be the same as
- *				  mycall_rec or different.
- *
- *		alias		- Compiled pattern for my station aliases or 
- *				  "trapping" (repeating only once).
- *
- *		wide		- Compiled pattern for normal WIDEn-n digipeating.
- *
- *		to_chan		- Channel number that we are transmitting to.
- *				  This is needed to maintain a history for 
- *			 	  removing duplicates during specified time period.
- *
- *		preempt		- Option for "preemptive" digipeating.
- *
- *		filter_str	- Filter expression string or NULL.
- *		
- * Returns:	Packet object for transmission or NULL.
- *
- * Description:	The packet will be digipeated if the next unused digipeater
- *		field matches one of the following:
- *
- *			- mycall_rec
- *			- udigi list (only once)
- *			- wide list (usual wideN-N rules)
- *
- *------------------------------------------------------------------------------*/
-
-static char *dest_ssid_path[16] = { 	
-			"",		/* Use VIA path */
-			"WIDE1-1",
-			"WIDE2-2",
-			"WIDE3-3",
-			"WIDE4-4",
-			"WIDE5-5",
-			"WIDE6-6",
-			"WIDE7-7",
-			"WIDE1-1",	/* North */
-			"WIDE1-1",	/* South */
-			"WIDE1-1",	/* East */
-			"WIDE1-1",	/* West */
-			"WIDE2-2",	/* North */
-			"WIDE2-2",	/* South */
-			"WIDE2-2",	/* East */
-			"WIDE2-2"  };	/* West */
-				  
-
-static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, 
-				regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str)
-{
-	int ssid;
-	int r;
-	char repeater[AX25_MAX_ADDR_LEN];
-	packet_t result = NULL;
-	int err;
-	char err_msg[100];
-
-/*
- * First check if filtering has been configured.
- */
-
-
-	if (filter_str != NULL) {
-
-	  if (pfilter(from_chan, to_chan, filter_str, pp) != 1) {
-
-// TODO1.2: take out debug message
-//#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("Packet was rejected for digipeating from channel %d to %d by filter: %s\n", from_chan, to_chan, filter_str);
-//#endif
-	    return(NULL);
-	  }
-	}
-
-/*
- * The spec says:
- *
- * 	The SSID in the Destination Address field of all packets is coded to specify
- * 	the APRS digipeater path.
- * 	If the Destination Address SSID is �0, the packet follows the standard AX.25
- * 	digipeater (�VIA�) path contained in the Digipeater Addresses field of the
- * 	AX.25 frame.
- * 	If the Destination Address SSID is non-zero, the packet follows one of 15
- * 	generic APRS digipeater paths.
- * 
- *
- * What if this is non-zero but there is also a digipeater path?
- * I will ignore this if there is an explicit path.
- *
- * Note that this modifies the input.  But only once!
- * Otherwise we don't want to modify the input because this could be called multiple times.
- */
-
-	if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
-	  ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
-	  ax25_set_ssid(pp, AX25_DESTINATION, 0);
-	  /* Continue with general case, below. */
-	}
-
-/* 
- * Find the first repeater station which doesn't have "has been repeated" set.
- *
- * r = index of the address position in the frame.
- */
-	r = ax25_get_first_not_repeated(pp);
-
-	if (r < AX25_REPEATER_1) {
-	  return NULL;
-	}
-
-	ax25_get_addr_with_ssid(pp, r, repeater);
-	ssid = ax25_get_ssid(pp, r);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("First unused digipeater is %s, ssid=%d\n", repeater, ssid);
-#endif
-
-
-/*
- * First check for explicit use of my call.
- * In this case, we don't check the history so it would be possible
- * to have a loop (of limited size) if someone constructed the digipeater paths
- * correctly.
- */
-	
-	if (strcmp(repeater, mycall_rec) == 0) {
-	  result = ax25_dup (pp);
-	  /* If using multiple radio channels, they */
-	  /* could have different calls. */
-	  ax25_set_addr (result, r, mycall_xmit);	
-	  ax25_set_h (result, r);
-	  return (result);
-	}
-
-/*
- * Next try to avoid retransmitting redundant information.
- * Duplicates are detected by comparing only:
- *	- source
- *	- destination
- *	- info part
- *	- but none of the digipeaters
- * A history is kept for some amount of time, typically 30 seconds.
- * For efficiency, only a checksum, rather than the complete fields
- * might be kept but the result is the same.
- * Packets transmitted recently will not be transmitted again during
- * the specified time period.
- *
- */
-
-
-	if (dedupe_check(pp, to_chan)) {
-//#if DEBUG
-	  /* Might be useful if people are wondering why */
-	  /* some are not repeated.  Might also cause confusion. */
-
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Digipeater: Drop redundant packet to channel %d.\n", to_chan);
-//#endif
-	  assert (result == NULL);
-	  return NULL;
-	}
-
-/*
- * For the alias pattern, we unconditionally digipeat it once.
- * i.e.  Just replace it with MYCALL don't even look at the ssid.
- */
-	err = regexec(alias,repeater,0,NULL,0);
-	if (err == 0) {
-	  result = ax25_dup (pp);
-	  ax25_set_addr (result, r, mycall_xmit);	
-	  ax25_set_h (result, r);
-	  return (result);
-	}
-	else if (err != REG_NOMATCH) {
-	  regerror(err, alias, err_msg, sizeof(err_msg));
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("%s\n", err_msg);
-	}
-
-/* 
- * If preemptive digipeating is enabled, try matching my call 
- * and aliases against all remaining unused digipeaters.
- */
-
-	if (preempt != PREEMPT_OFF) {
-	  int r2;
-
-	  for (r2 = r+1; r2 < ax25_get_num_addr(pp); r2++) {
-	    char repeater2[AX25_MAX_ADDR_LEN];
-
-	    ax25_get_addr_with_ssid(pp, r2, repeater2);
-
-	    //text_color_set (DW_COLOR_DEBUG);
-	    //dw_printf ("test match %d %s\n", r2, repeater2);
-
-	    if (strcmp(repeater2, mycall_rec) == 0 ||
-	        regexec(alias,repeater2,0,NULL,0) == 0) {
-
-	      result = ax25_dup (pp);
-	      ax25_set_addr (result, r2, mycall_xmit);	
-	      ax25_set_h (result, r2);
-
-	      switch (preempt) {
-	        case PREEMPT_DROP:	/* remove all prior */
-	          while (r2 > AX25_REPEATER_1) {
-	            ax25_remove_addr (result, r2-1);
- 		    r2--;
-	          }
-	          break;
-
-	        case PREEMPT_MARK:
-	          r2--;
-	          while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) {
-	            ax25_set_h (result, r2);
- 		    r2--;
-	          }
-	          break;
-
-		case PREEMPT_TRACE:	/* remove prior unused */
-	        default:
-	          while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) {
-	            ax25_remove_addr (result, r2-1);
- 		    r2--;
-	          }
-	          break;
-	      }
-
-	      return (result);
-	    }
- 	  }
-	}
-
-/*
- * For the wide pattern, we check the ssid and decrement it.
- */
-
-	err = regexec(wide,repeater,0,NULL,0);
-	if (err == 0) {
-
-/*
- * If ssid == 1, we simply replace the repeater with my call and
- *	mark it as being used.
- *
- * Otherwise, if ssid in range of 2 to 7, 
- *	Decrement y and don't mark repeater as being used.
- * 	Insert own call ahead of this one for tracing if we don't already have the 
- *	maximum number of repeaters.
- */
-
-	  if (ssid == 1) {
-	    result = ax25_dup (pp);
- 	    ax25_set_addr (result, r, mycall_xmit);	
-	    ax25_set_h (result, r);
-	    return (result);
-	  }
-
-	  if (ssid >= 2 && ssid <= 7) {
-	    result = ax25_dup (pp);
-	    ax25_set_ssid(result, r, ssid-1);	// should be at least 1
-
-	    if (ax25_get_num_repeaters(pp) < AX25_MAX_REPEATERS) {
-	      ax25_insert_addr (result, r, mycall_xmit);	
-	      ax25_set_h (result, r);
-	    }
-	    return (result);
-	  }
-	} 
-	else if (err != REG_NOMATCH) {
-	  regerror(err, wide, err_msg, sizeof(err_msg));
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("%s\n", err_msg);
-	}
-
-
-/*
- * Don't repeat it if we get here.
- */
-	assert (result == NULL);
-	return NULL;
-}
-
-
-
-/*------------------------------------------------------------------------------
- *
- * 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 < MAX_CHANS);
-
-	for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
-	  if (save_digi_config_p->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
- * 
- * Purpose:	Standalone test case for this funtionality.
- *
- * Usage:	make -f Makefile.<platform> dtest
- *		./dtest 
- *
- *------------------------------------------------------------------------*/
-
-#if TEST
-
-static char mycall[] = "WB2OSZ-9";
-
-static regex_t alias_re;     
-
-static regex_t wide_re;   
-
-static int failed;
-
-static enum preempt_e preempt = PREEMPT_OFF;
-
-static char typefilter[20] = "";
-
-
-static void test (char *in, char *out)
-{
-	packet_t pp, result;
-	//int should_repeat;
-	char rec[256];
-	char xmit[256];
-	unsigned char *pinfo;
-	int info_len;
-	unsigned char frame[AX25_MAX_PACKET_LEN];
-	int frame_len;
-
-	dw_printf ("\n");
-
-/*
- * As an extra test, change text to internal format back to 
- * text again to make sure it comes out the same.
- */
-	pp = ax25_from_text (in, 1);
-	assert (pp != NULL);
-
-	ax25_format_addrs (pp, rec);
-	info_len = ax25_get_info (pp, &pinfo);
-	strcat (rec, (char*)pinfo);
-
-	if (strcmp(in, rec) != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Text/internal/text error-1 %s -> %s\n", in, rec);
-	}
-
-/*
- * Just for more fun, write as the frame format, read it back
- * again, and make sure it is still the same.
- */
-
-	frame_len = ax25_pack (pp, frame);
-	ax25_delete (pp);
-
-	pp = ax25_from_frame (frame, frame_len, 50);
-	ax25_format_addrs (pp, rec);
-	info_len = ax25_get_info (pp, &pinfo);
-	strcat (rec, (char*)pinfo);
-
-	if (strcmp(in, rec) != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("internal/frame/internal/text error-2 %s -> %s\n", in, rec);
-	}
-
-/*
- * On with the digipeater test.
- */	
-	
-	text_color_set(DW_COLOR_REC);
-	dw_printf ("Rec\t%s\n", rec);
-
-//TODO:											Add filtering to test.
-//											V
-	result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL);
-	
-	if (result != NULL) {
-
-	  dedupe_remember (result, 0);
-	  ax25_format_addrs (result, xmit);
-	  info_len = ax25_get_info (result, &pinfo);
-	  strcat (xmit, (char*)pinfo);
-	  ax25_delete (result);
-	}
-	else {
-	  strcpy (xmit, "");
-	}
-
-	text_color_set(DW_COLOR_XMIT);
-	dw_printf ("Xmit\t%s\n", xmit);
-	
-	if (strcmp(xmit, out) == 0) {
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("OK\n");
-	}
-	else {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Expect\t%s\n", out);
- 	  failed++;
-	}
-
-	dw_printf ("\n");
-}
-
-int main (int argc, char *argv[])
-{
-	int e;
-	failed = 0;
-	char message[256];
-
-	dedupe_init (4);
-
-/* 
- * Compile the patterns. 
- */
-	e = regcomp (&alias_re, "^WIDE[4-7]-[1-7]|CITYD$", REG_EXTENDED|REG_NOSUB);
-	if (e != 0) {
-	  regerror (e, &alias_re, message, sizeof(message));
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\n%s\n\n", message);
-	  exit (1);
-	}
-
-	e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB);
-	if (e != 0) {
-	  regerror (e, &wide_re, message, sizeof(message));
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\n%s\n\n", message);
-	  exit (1);
-	}
-
-/*
- * Let's start with the most basic cases.
- */
-
-	test (	"W1ABC>TEST01,TRACE3-3:",
-		"W1ABC>TEST01,WB2OSZ-9*,TRACE3-2:");
-
-	test (	"W1ABC>TEST02,WIDE3-3:",
-		"W1ABC>TEST02,WB2OSZ-9*,WIDE3-2:");
-
-	test (	"W1ABC>TEST03,WIDE3-2:",
-		"W1ABC>TEST03,WB2OSZ-9*,WIDE3-1:");
-
-	test (	"W1ABC>TEST04,WIDE3-1:",
-		"W1ABC>TEST04,WB2OSZ-9*:");
-
-/*
- * Look at edge case of maximum number of digipeaters.
- */
-	test (	"W1ABC>TEST11,R1,R2,R3,R4,R5,R6*,WIDE3-3:",
-		"W1ABC>TEST11,R1,R2,R3,R4,R5,R6,WB2OSZ-9*,WIDE3-2:");
-
-	test (	"W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-3:",
-		"W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-2:");
-
-	test (	"W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7*,WIDE3-1:",
-		"W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7,WB2OSZ-9*:");
-
-/*
- * "Trap" large values of "N" by repeating only once.
- */
-	test (	"W1ABC>TEST21,WIDE4-4:",
-		"W1ABC>TEST21,WB2OSZ-9*:");
-
-	test (	"W1ABC>TEST22,WIDE7-7:",
-		"W1ABC>TEST22,WB2OSZ-9*:");
-
-/*
- * Only values in range of 1 thru 7 are valid.
- */
-	test (	"W1ABC>TEST31,WIDE0-4:",
-		"");
-
-	test (	"W1ABC>TEST32,WIDE8-4:",
-		"");
-
-	test (	"W1ABC>TEST33,WIDE2:",
-		"");
-
-
-/*
- * and a few cases actually heard.
- */
-
-	test (	"WA1ENO>FN42ND,W1MV-1*,WIDE3-2:",
-		"WA1ENO>FN42ND,W1MV-1,WB2OSZ-9*,WIDE3-1:");
-
-	test (	"W1ON-3>BEACON:",
-		"");
-
-	test (	"W1CMD-9>TQ3Y8P,N1RCW-2,W1CLA-1,N8VIM,WIDE2*:",
-		"");
-
-	test (	"W1CLA-1>APX192,W1GLO-1,WIDE2*:",
-		"");
-
-	test (	"AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM*,WIDE2-1:",
-		"AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM,WB2OSZ-9*:");
-
-/*
- * Someone is still using the old style and will probably be disappointed.
- */
-
-	test (	"K1CPD-1>T2SR5R,RELAY*,WIDE,WIDE,SGATE,WIDE:",
-		"");
-
-
-/* 
- * Change destination SSID to normal digipeater if none specified.
- */
-	test (	"W1ABC>TEST-3:",
-		"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
-
-	test (	"W1DEF>TEST-3,WIDE2-2:",
-		"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
-
-/*
- * Drop duplicates within specified time interval.
- * Only the first 1 of 3 should be retransmitted.
- */
-
-	test (	"W1XYZ>TEST,R1*,WIDE3-2:info1",
-		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info1");
-
-	test (	"W1XYZ>TEST,R2*,WIDE3-2:info1",
-		"");
-
-	test (	"W1XYZ>TEST,R3*,WIDE3-2:info1",
-		"");
-
-/*
- * Allow same thing after adequate time.
- */
-	SLEEP_SEC (5);
-
-	test (	"W1XYZ>TEST,R3*,WIDE3-2:info1",
-		"W1XYZ>TEST,R3,WB2OSZ-9*,WIDE3-1:info1");
-
-/*
- * Although source and destination match, the info field is different.
- */
-
-	test (	"W1XYZ>TEST,R1*,WIDE3-2:info4",
-		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info4");
-
-	test (	"W1XYZ>TEST,R1*,WIDE3-2:info5",
-		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info5");
-
-	test (	"W1XYZ>TEST,R1*,WIDE3-2:info6",
-		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info6");
-
-/*
- * New in version 0.8.
- * "Preemptive" digipeating looks ahead beyond the first unused digipeater.
- */
-
-	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:off",
-		"");
-
-	preempt = PREEMPT_DROP;
-
-	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:drop",
-		"W1ABC>TEST11,WB2OSZ-9*,CITYE:drop");
-
-	preempt = PREEMPT_MARK;
-
-	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:mark1",
-		"W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark1");
-
-	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,WB2OSZ-9,CITYE:mark2",
-		"W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark2");
-
-	preempt = PREEMPT_TRACE;
-
-	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:trace1",
-		"W1ABC>TEST11,CITYA,WB2OSZ-9*,CITYE:trace1");
-
-	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD:trace2",
-		"W1ABC>TEST11,CITYA,WB2OSZ-9*:trace2");
-
-	test (	"W1ABC>TEST11,CITYB,CITYC,CITYD:trace3",
-		"W1ABC>TEST11,WB2OSZ-9*:trace3");
-
-	test (	"W1ABC>TEST11,CITYA*,CITYW,CITYX,CITYY,CITYZ:nomatch",
-		"");
-
-
-#if 0	/* changed strategy */
-/*
- * New in version 1.2.
- */
-
-
-	// no filter.
-	if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "") != 1) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 1\n"); failed++; }
-	
-	// message should not match psqt
-	if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "pqst") != 0) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 2\n"); failed++; }
-	
-	// This should match position
-	if (filter_by_type ("N3LEE-7", "`cHDl <0x1c>[/\"5j}", "qstp") != 1) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 3\n"); failed++; }
-	
-	// This should match nws
-	if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 1) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 4\n"); failed++; }
-	
-	// But not this.
-	if (filter_by_type ("CWAPID", ":zzz-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 0) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 5\n"); failed++; }
-
-	// This should match nws
-	if (filter_by_type ("CWAPID", ";CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 1) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 6\n"); failed++; }
-
-	// But not this due do addressee prefix mismatch
-	if (filter_by_type ("CWAPID", ";NWSttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 0) 
-	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 7\n"); failed++; }
-
-
-/*
- * Filtering integrated with rest of process...
- */
-
-	strcpy (typefilter, "w");
-
-	test (	"N8VIM>APN391,WIDE2-1:$ULTW00000000010E097D2884FFF389DC000102430002033400000000",
-		"N8VIM>APN391,WB2OSZ-9*:$ULTW00000000010E097D2884FFF389DC000102430002033400000000");
-
-	test (	"AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational",
-		"");
- 
-	strcpy (typefilter, "s");
-
-	test (	"AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational",
-		"AB1OC-10>APWW10,WB2OSZ-9*,WIDE2-1:>FN42er/# Hollis, NH iGate Operational");
-
-	test (	"K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%",
-		"");
-
-	strcpy (typefilter, "up");
-
-	test (	"K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%",
-		"K1ABC-9>TR4R8R,WB2OSZ-9*:`c6LlIb>/`\"4K}_%");
-
-	strcpy (typefilter, "");
-#endif
-
-/* 
- * Did I miss any cases?
- */
-
-	if (failed == 0) {
-	  dw_printf ("SUCCESS -- All digipeater tests passed.\n");
-	}
-	else {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("ERROR - %d digipeater tests failed.\n", failed);
-	}
-
-	return ( failed != 0 ); 
-
-} /* end main */
-
-#endif  /* if TEST */
-
-/* end digipeater.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:	digipeater.c
+ *
+ * Purpose:	Act as an APRS digital repeater.
+ *
+ *
+ * Description:	Decide whether the specified packet should
+ *		be digipeated and make necessary modifications.
+ *
+ *
+ * References:	APRS Protocol Reference, document version 1.0.1
+ *
+ *			http://www.aprs.org/doc/APRS101.PDF
+ *
+ *		APRS SPEC Addendum 1.1
+ *
+ *			http://www.aprs.org/aprs11.html
+ *
+ *		APRS SPEC Addendum 1.2
+ *
+ *			http://www.aprs.org/aprs12.html
+ *
+ *		"The New n-N Paradigm"
+ *
+ *			http://www.aprs.org/fix14439.html
+ *
+ *		Preemptive Digipeating  (new in version 0.8)
+ *
+ *			http://www.aprs.org/aprs12/preemptive-digipeating.txt
+ *		
+ *------------------------------------------------------------------*/
+
+#define DIGIPEATER_C
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <ctype.h>	/* for isdigit, isupper */
+#include "regex.h"
+#include <sys/unistd.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "digipeater.h"
+#include "textcolor.h"
+#include "dedupe.h"
+#include "tq.h"
+#include "pfilter.h"
+
+
+static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, 
+				regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter);
+
+//static int filter_by_type (char *source, char *infop, char *type_filter);
+
+
+/*
+ * Keep pointer to configuration options.
+ * Set by digipeater_init and used later.
+ */
+
+
+static struct audio_s	    *save_audio_config_p;
+static struct digi_config_s *save_digi_config_p;
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	digipeater_init
+ * 
+ * Purpose:	Initialize with stuff from configuration file.
+ *
+ * Inputs:	p_audio_config	- Configuration for audio channels.
+ *
+ *		p_digi_config	- Digipeater configuration details.
+ *		
+ * Outputs:	Save pointers to configuration for later use.
+ *		
+ * Description:	Called once at application startup time.
+ *
+ *------------------------------------------------------------------------------*/
+
+void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config) 
+{
+	save_audio_config_p = p_audio_config;
+	save_digi_config_p = p_digi_config;
+	
+	dedupe_init (p_digi_config->dedupe_time);
+}
+
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	digipeater
+ * 
+ * Purpose:	Re-transmit packet if it matches the rules.
+ *
+ * Inputs:	chan	- Radio channel where it was received.
+ *		
+ * 		pp	- Packet object.
+ *		
+ * Returns:	None.
+ *		
+ *
+ *------------------------------------------------------------------------------*/
+
+
+
+void digipeater (int from_chan, packet_t pp)
+{
+	int to_chan;
+
+
+	// dw_printf ("digipeater()\n");
+	
+	assert (from_chan >= 0 && from_chan < MAX_CHANS);
+
+	if ( ! save_audio_config_p->achan[from_chan].valid) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("digipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
+	}
+
+
+/*
+ * First pass:  Look at packets being digipeated to same channel.
+ *
+ * We want these to get out quickly.
+ */
+
+	for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
+	  if (save_digi_config_p->enabled[from_chan][to_chan]) {
+	    if (to_chan == from_chan) {
+	      packet_t result;
+
+	      result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, 
+					   save_audio_config_p->achan[to_chan].mycall, 
+			&save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], 
+			to_chan, save_digi_config_p->preempt[from_chan][to_chan],
+				save_digi_config_p->filter_str[from_chan][to_chan]);
+	      if (result != NULL) {
+		dedupe_remember (pp, to_chan);
+	        tq_append (to_chan, TQ_PRIO_0_HI, result);
+	      }
+	    }
+	  }
+	}
+
+
+/*
+ * Second pass:  Look at packets being digipeated to different channel.
+ *
+ * These are lower priority
+ */
+
+	for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
+	  if (save_digi_config_p->enabled[from_chan][to_chan]) {
+	    if (to_chan != from_chan) {
+	      packet_t result;
+
+	      result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, 
+					   save_audio_config_p->achan[to_chan].mycall, 
+			&save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], 
+			to_chan, save_digi_config_p->preempt[from_chan][to_chan],
+				save_digi_config_p->filter_str[from_chan][to_chan]);
+	      if (result != NULL) {
+		dedupe_remember (pp, to_chan);
+	        tq_append (to_chan, TQ_PRIO_1_LO, result);
+	      }
+	    }
+	  }
+	}
+
+} /* end digipeater */
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	digipeat_match
+ * 
+ * Purpose:	A simple digipeater for APRS.
+ *
+ * Input:	pp		- Pointer to a packet object.
+ *	
+ *		mycall_rec	- Call of my station, with optional SSID,
+ *				  associated with the radio channel where the 
+ *				  packet was received.
+ *
+ *		mycall_xmit	- Call of my station, with optional SSID,
+ *				  associated with the radio channel where the 
+ *				  packet is to be transmitted.  Could be the same as
+ *				  mycall_rec or different.
+ *
+ *		alias		- Compiled pattern for my station aliases or 
+ *				  "trapping" (repeating only once).
+ *
+ *		wide		- Compiled pattern for normal WIDEn-n digipeating.
+ *
+ *		to_chan		- Channel number that we are transmitting to.
+ *				  This is needed to maintain a history for 
+ *			 	  removing duplicates during specified time period.
+ *
+ *		preempt		- Option for "preemptive" digipeating.
+ *
+ *		filter_str	- Filter expression string or NULL.
+ *		
+ * Returns:	Packet object for transmission or NULL.
+ *		The original packet is not modified.  (with one exception, probably obsolete)
+ *		We make a copy and return that modified copy!
+ *		This is very important because we could digipeat from one channel to many.
+ *
+ * Description:	The packet will be digipeated if the next unused digipeater
+ *		field matches one of the following:
+ *
+ *			- mycall_rec
+ *			- udigi list (only once)
+ *			- wide list (usual wideN-N rules)
+ *
+ *------------------------------------------------------------------------------*/
+
+static char *dest_ssid_path[16] = { 	
+			"",		/* Use VIA path */
+			"WIDE1-1",
+			"WIDE2-2",
+			"WIDE3-3",
+			"WIDE4-4",
+			"WIDE5-5",
+			"WIDE6-6",
+			"WIDE7-7",
+			"WIDE1-1",	/* North */
+			"WIDE1-1",	/* South */
+			"WIDE1-1",	/* East */
+			"WIDE1-1",	/* West */
+			"WIDE2-2",	/* North */
+			"WIDE2-2",	/* South */
+			"WIDE2-2",	/* East */
+			"WIDE2-2"  };	/* West */
+				  
+
+static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, 
+				regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str)
+{
+	int ssid;
+	int r;
+	char repeater[AX25_MAX_ADDR_LEN];
+	int err;
+	char err_msg[100];
+
+/*
+ * First check if filtering has been configured.
+ */
+
+
+	if (filter_str != NULL) {
+
+	  if (pfilter(from_chan, to_chan, filter_str, pp) != 1) {
+
+// TODO1.2: take out debug message
+// Actually it turns out to be useful.
+// Maybe add a quiet option to suppress it although no one has complained about it yet.
+//#if DEBUG
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Packet was rejected for digipeating from channel %d to %d by filter: %s\n", from_chan, to_chan, filter_str);
+//#endif
+	    return(NULL);
+	  }
+	}
+
+/*
+ * The spec says:
+ *
+ * 	The SSID in the Destination Address field of all packets is coded to specify
+ * 	the APRS digipeater path.
+ * 	If the Destination Address SSID is -0, the packet follows the standard AX.25
+ * 	digipeater ("VIA") path contained in the Digipeater Addresses field of the
+ * 	AX.25 frame.
+ * 	If the Destination Address SSID is non-zero, the packet follows one of 15
+ * 	generic APRS digipeater paths.
+ * 
+ *
+ * What if this is non-zero but there is also a digipeater path?
+ * I will ignore this if there is an explicit path.
+ *
+ * Note that this modifies the input.  But only once!
+ * Otherwise we don't want to modify the input because this could be called multiple times.
+ */
+
+	if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
+	  ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
+	  ax25_set_ssid(pp, AX25_DESTINATION, 0);
+	  /* Continue with general case, below. */
+	}
+
+/* 
+ * Find the first repeater station which doesn't have "has been repeated" set.
+ *
+ * r = index of the address position in the frame.
+ */
+	r = ax25_get_first_not_repeated(pp);
+
+	if (r < AX25_REPEATER_1) {
+	  return (NULL);
+	}
+
+	ax25_get_addr_with_ssid(pp, r, repeater);
+	ssid = ax25_get_ssid(pp, r);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("First unused digipeater is %s, ssid=%d\n", repeater, ssid);
+#endif
+
+
+/*
+ * First check for explicit use of my call.
+ * In this case, we don't check the history so it would be possible
+ * to have a loop (of limited size) if someone constructed the digipeater paths
+ * correctly.
+ */
+	
+	if (strcmp(repeater, mycall_rec) == 0) {
+	  packet_t result;
+
+	  result = ax25_dup (pp);
+	  assert (result != NULL);
+
+	  /* If using multiple radio channels, they */
+	  /* could have different calls. */
+	  ax25_set_addr (result, r, mycall_xmit);	
+	  ax25_set_h (result, r);
+	  return (result);
+	}
+
+/*
+ * Next try to avoid retransmitting redundant information.
+ * Duplicates are detected by comparing only:
+ *	- source
+ *	- destination
+ *	- info part
+ *	- but none of the digipeaters
+ * A history is kept for some amount of time, typically 30 seconds.
+ * For efficiency, only a checksum, rather than the complete fields
+ * might be kept but the result is the same.
+ * Packets transmitted recently will not be transmitted again during
+ * the specified time period.
+ *
+ */
+
+
+	if (dedupe_check(pp, to_chan)) {
+//#if DEBUG
+	  /* Might be useful if people are wondering why */
+	  /* some are not repeated.  Might also cause confusion. */
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Digipeater: Drop redundant packet to channel %d.\n", to_chan);
+//#endif
+	  return NULL;
+	}
+
+/*
+ * For the alias pattern, we unconditionally digipeat it once.
+ * i.e.  Just replace it with MYCALL don't even look at the ssid.
+ */
+	err = regexec(alias,repeater,0,NULL,0);
+	if (err == 0) {
+	  packet_t result;
+
+	  result = ax25_dup (pp);
+	  assert (result != NULL);
+
+	  ax25_set_addr (result, r, mycall_xmit);	
+	  ax25_set_h (result, r);
+	  return (result);
+	}
+	else if (err != REG_NOMATCH) {
+	  regerror(err, alias, err_msg, sizeof(err_msg));
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("%s\n", err_msg);
+	}
+
+/* 
+ * If preemptive digipeating is enabled, try matching my call 
+ * and aliases against all remaining unused digipeaters.
+ */
+
+	if (preempt != PREEMPT_OFF) {
+	  int r2;
+
+	  for (r2 = r+1; r2 < ax25_get_num_addr(pp); r2++) {
+	    char repeater2[AX25_MAX_ADDR_LEN];
+
+	    ax25_get_addr_with_ssid(pp, r2, repeater2);
+
+	    //text_color_set (DW_COLOR_DEBUG);
+	    //dw_printf ("test match %d %s\n", r2, repeater2);
+
+	    if (strcmp(repeater2, mycall_rec) == 0 ||
+	        regexec(alias,repeater2,0,NULL,0) == 0) {
+	      packet_t result;
+
+	      result = ax25_dup (pp);
+	      assert (result != NULL);
+
+	      ax25_set_addr (result, r2, mycall_xmit);	
+	      ax25_set_h (result, r2);
+
+	      switch (preempt) {
+	        case PREEMPT_DROP:	/* remove all prior */
+	          while (r2 > AX25_REPEATER_1) {
+	            ax25_remove_addr (result, r2-1);
+ 		    r2--;
+	          }
+	          break;
+
+	        case PREEMPT_MARK:
+	          r2--;
+	          while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) {
+	            ax25_set_h (result, r2);
+ 		    r2--;
+	          }
+	          break;
+
+		case PREEMPT_TRACE:	/* remove prior unused */
+	        default:
+	          while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) {
+	            ax25_remove_addr (result, r2-1);
+ 		    r2--;
+	          }
+	          break;
+	      }
+
+	      return (result);
+	    }
+ 	  }
+	}
+
+/*
+ * For the wide pattern, we check the ssid and decrement it.
+ */
+
+	err = regexec(wide,repeater,0,NULL,0);
+	if (err == 0) {
+
+/*
+ * If ssid == 1, we simply replace the repeater with my call and
+ *	mark it as being used.
+ *
+ * Otherwise, if ssid in range of 2 to 7, 
+ *	Decrement y and don't mark repeater as being used.
+ * 	Insert own call ahead of this one for tracing if we don't already have the 
+ *	maximum number of repeaters.
+ */
+
+	  if (ssid == 1) {
+	    packet_t result;
+
+	    result = ax25_dup (pp);
+	    assert (result != NULL);
+
+ 	    ax25_set_addr (result, r, mycall_xmit);	
+	    ax25_set_h (result, r);
+	    return (result);
+	  }
+
+	  if (ssid >= 2 && ssid <= 7) {
+	    packet_t result;
+
+	    result = ax25_dup (pp);
+	    assert (result != NULL);
+
+	    ax25_set_ssid(result, r, ssid-1);	// should be at least 1
+
+	    if (ax25_get_num_repeaters(pp) < AX25_MAX_REPEATERS) {
+	      ax25_insert_addr (result, r, mycall_xmit);	
+	      ax25_set_h (result, r);
+	    }
+	    return (result);
+	  }
+	} 
+	else if (err != REG_NOMATCH) {
+	  regerror(err, wide, err_msg, sizeof(err_msg));
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("%s\n", err_msg);
+	}
+
+
+/*
+ * Don't repeat it if we get here.
+ */
+
+	return (NULL);
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * 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...
+ *
+ *		Initial reports were favorable.
+ *		Should document what this is all about if there is still interest...
+ *		
+ *------------------------------------------------------------------------------*/
+
+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 < MAX_CHANS);
+
+	for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
+	  if (save_digi_config_p->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
+ * 
+ * Purpose:	Standalone test case for this funtionality.
+ *
+ * Usage:	make -f Makefile.<platform> dtest
+ *		./dtest 
+ *
+ *------------------------------------------------------------------------*/
+
+#if DIGITEST
+
+static char mycall[] = "WB2OSZ-9";
+
+static regex_t alias_re;     
+
+static regex_t wide_re;   
+
+static int failed;
+
+static enum preempt_e preempt = PREEMPT_OFF;
+
+static char typefilter[20] = "";
+
+
+static void test (char *in, char *out)
+{
+	packet_t pp, result;
+	//int should_repeat;
+	char rec[256];
+	char xmit[256];
+	unsigned char *pinfo;
+	int info_len;
+	unsigned char frame[AX25_MAX_PACKET_LEN];
+	int frame_len;
+	alevel_t alevel;
+
+	dw_printf ("\n");
+
+/*
+ * As an extra test, change text to internal format back to 
+ * text again to make sure it comes out the same.
+ */
+	pp = ax25_from_text (in, 1);
+	assert (pp != NULL);
+
+	ax25_format_addrs (pp, rec);
+	info_len = ax25_get_info (pp, &pinfo);
+	strlcat (rec, (char*)pinfo, sizeof(rec));
+
+	if (strcmp(in, rec) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Text/internal/text error-1 %s -> %s\n", in, rec);
+	}
+
+/*
+ * Just for more fun, write as the frame format, read it back
+ * again, and make sure it is still the same.
+ */
+
+	frame_len = ax25_pack (pp, frame);
+	ax25_delete (pp);
+
+	alevel.rec = 50;
+	alevel.mark = 50;
+	alevel.space = 50;
+
+	pp = ax25_from_frame (frame, frame_len, alevel);
+	assert (pp != NULL);
+	ax25_format_addrs (pp, rec);
+	info_len = ax25_get_info (pp, &pinfo);
+	strlcat (rec, (char*)pinfo, sizeof(rec));
+
+	if (strcmp(in, rec) != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("internal/frame/internal/text error-2 %s -> %s\n", in, rec);
+	}
+
+/*
+ * On with the digipeater test.
+ */	
+	
+	text_color_set(DW_COLOR_REC);
+	dw_printf ("Rec\t%s\n", rec);
+
+//TODO:											Add filtering to test.
+//											V
+	result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL);
+	
+	if (result != NULL) {
+
+	  dedupe_remember (result, 0);
+	  ax25_format_addrs (result, xmit);
+	  info_len = ax25_get_info (result, &pinfo);
+	  strlcat (xmit, (char*)pinfo, sizeof(xmit));
+	  ax25_delete (result);
+	}
+	else {
+	  strlcpy (xmit, "", sizeof(xmit));
+	}
+
+	text_color_set(DW_COLOR_XMIT);
+	dw_printf ("Xmit\t%s\n", xmit);
+	
+	if (strcmp(xmit, out) == 0) {
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("OK\n");
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Expect\t%s\n", out);
+ 	  failed++;
+	}
+
+	dw_printf ("\n");
+}
+
+int main (int argc, char *argv[])
+{
+	int e;
+	failed = 0;
+	char message[256];
+
+	dedupe_init (4);
+
+/* 
+ * Compile the patterns. 
+ */
+	e = regcomp (&alias_re, "^WIDE[4-7]-[1-7]|CITYD$", REG_EXTENDED|REG_NOSUB);
+	if (e != 0) {
+	  regerror (e, &alias_re, message, sizeof(message));
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\n%s\n\n", message);
+	  exit (1);
+	}
+
+	e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB);
+	if (e != 0) {
+	  regerror (e, &wide_re, message, sizeof(message));
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\n%s\n\n", message);
+	  exit (1);
+	}
+
+/*
+ * Let's start with the most basic cases.
+ */
+
+	test (	"W1ABC>TEST01,TRACE3-3:",
+		"W1ABC>TEST01,WB2OSZ-9*,TRACE3-2:");
+
+	test (	"W1ABC>TEST02,WIDE3-3:",
+		"W1ABC>TEST02,WB2OSZ-9*,WIDE3-2:");
+
+	test (	"W1ABC>TEST03,WIDE3-2:",
+		"W1ABC>TEST03,WB2OSZ-9*,WIDE3-1:");
+
+	test (	"W1ABC>TEST04,WIDE3-1:",
+		"W1ABC>TEST04,WB2OSZ-9*:");
+
+/*
+ * Look at edge case of maximum number of digipeaters.
+ */
+	test (	"W1ABC>TEST11,R1,R2,R3,R4,R5,R6*,WIDE3-3:",
+		"W1ABC>TEST11,R1,R2,R3,R4,R5,R6,WB2OSZ-9*,WIDE3-2:");
+
+	test (	"W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-3:",
+		"W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-2:");
+
+	test (	"W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7*,WIDE3-1:",
+		"W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7,WB2OSZ-9*:");
+
+/*
+ * "Trap" large values of "N" by repeating only once.
+ */
+	test (	"W1ABC>TEST21,WIDE4-4:",
+		"W1ABC>TEST21,WB2OSZ-9*:");
+
+	test (	"W1ABC>TEST22,WIDE7-7:",
+		"W1ABC>TEST22,WB2OSZ-9*:");
+
+/*
+ * Only values in range of 1 thru 7 are valid.
+ */
+	test (	"W1ABC>TEST31,WIDE0-4:",
+		"");
+
+	test (	"W1ABC>TEST32,WIDE8-4:",
+		"");
+
+	test (	"W1ABC>TEST33,WIDE2:",
+		"");
+
+
+/*
+ * and a few cases actually heard.
+ */
+
+	test (	"WA1ENO>FN42ND,W1MV-1*,WIDE3-2:",
+		"WA1ENO>FN42ND,W1MV-1,WB2OSZ-9*,WIDE3-1:");
+
+	test (	"W1ON-3>BEACON:",
+		"");
+
+	test (	"W1CMD-9>TQ3Y8P,N1RCW-2,W1CLA-1,N8VIM,WIDE2*:",
+		"");
+
+	test (	"W1CLA-1>APX192,W1GLO-1,WIDE2*:",
+		"");
+
+	test (	"AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM*,WIDE2-1:",
+		"AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM,WB2OSZ-9*:");
+
+/*
+ * Someone is still using the old style and will probably be disappointed.
+ */
+
+	test (	"K1CPD-1>T2SR5R,RELAY*,WIDE,WIDE,SGATE,WIDE:",
+		"");
+
+
+/* 
+ * Change destination SSID to normal digipeater if none specified.
+ */
+	test (	"W1ABC>TEST-3:",
+		"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
+
+	test (	"W1DEF>TEST-3,WIDE2-2:",
+		"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
+
+/*
+ * Drop duplicates within specified time interval.
+ * Only the first 1 of 3 should be retransmitted.
+ */
+
+	test (	"W1XYZ>TEST,R1*,WIDE3-2:info1",
+		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info1");
+
+	test (	"W1XYZ>TEST,R2*,WIDE3-2:info1",
+		"");
+
+	test (	"W1XYZ>TEST,R3*,WIDE3-2:info1",
+		"");
+
+/*
+ * Allow same thing after adequate time.
+ */
+	SLEEP_SEC (5);
+
+	test (	"W1XYZ>TEST,R3*,WIDE3-2:info1",
+		"W1XYZ>TEST,R3,WB2OSZ-9*,WIDE3-1:info1");
+
+/*
+ * Although source and destination match, the info field is different.
+ */
+
+	test (	"W1XYZ>TEST,R1*,WIDE3-2:info4",
+		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info4");
+
+	test (	"W1XYZ>TEST,R1*,WIDE3-2:info5",
+		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info5");
+
+	test (	"W1XYZ>TEST,R1*,WIDE3-2:info6",
+		"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info6");
+
+/*
+ * New in version 0.8.
+ * "Preemptive" digipeating looks ahead beyond the first unused digipeater.
+ */
+
+	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:off",
+		"");
+
+	preempt = PREEMPT_DROP;
+
+	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:drop",
+		"W1ABC>TEST11,WB2OSZ-9*,CITYE:drop");
+
+	preempt = PREEMPT_MARK;
+
+	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:mark1",
+		"W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark1");
+
+	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,WB2OSZ-9,CITYE:mark2",
+		"W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark2");
+
+	preempt = PREEMPT_TRACE;
+
+	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:trace1",
+		"W1ABC>TEST11,CITYA,WB2OSZ-9*,CITYE:trace1");
+
+	test (	"W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD:trace2",
+		"W1ABC>TEST11,CITYA,WB2OSZ-9*:trace2");
+
+	test (	"W1ABC>TEST11,CITYB,CITYC,CITYD:trace3",
+		"W1ABC>TEST11,WB2OSZ-9*:trace3");
+
+	test (	"W1ABC>TEST11,CITYA*,CITYW,CITYX,CITYY,CITYZ:nomatch",
+		"");
+
+
+#if 0	/* changed strategy */
+/*
+ * New in version 1.2.
+ */
+
+
+	// no filter.
+	if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "") != 1) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 1\n"); failed++; }
+	
+	// message should not match psqt
+	if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "pqst") != 0) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 2\n"); failed++; }
+	
+	// This should match position
+	if (filter_by_type ("N3LEE-7", "`cHDl <0x1c>[/\"5j}", "qstp") != 1) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 3\n"); failed++; }
+	
+	// This should match nws
+	if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 1) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 4\n"); failed++; }
+	
+	// But not this.
+	if (filter_by_type ("CWAPID", ":zzz-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 0) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 5\n"); failed++; }
+
+	// This should match nws
+	if (filter_by_type ("CWAPID", ";CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 1) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 6\n"); failed++; }
+
+	// But not this due do addressee prefix mismatch
+	if (filter_by_type ("CWAPID", ";NWSttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 0) 
+	  { text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 7\n"); failed++; }
+
+
+/*
+ * Filtering integrated with rest of process...
+ */
+
+	strlcpy (typefilter, "w", sizeof(typefilter));
+
+	test (	"N8VIM>APN391,WIDE2-1:$ULTW00000000010E097D2884FFF389DC000102430002033400000000",
+		"N8VIM>APN391,WB2OSZ-9*:$ULTW00000000010E097D2884FFF389DC000102430002033400000000");
+
+	test (	"AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational",
+		"");
+ 
+	strlcpy (typefilter, "s", sizeof(typefilter));
+
+	test (	"AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational",
+		"AB1OC-10>APWW10,WB2OSZ-9*,WIDE2-1:>FN42er/# Hollis, NH iGate Operational");
+
+	test (	"K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%",
+		"");
+
+	strlcpy (typefilter, "up", sizeof(typefilter));
+
+	test (	"K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%",
+		"K1ABC-9>TR4R8R,WB2OSZ-9*:`c6LlIb>/`\"4K}_%");
+
+	strlcpy (typefilter, "", sizeof(typefilter));
+#endif
+
+/* 
+ * Did I miss any cases?
+ */
+
+	if (failed == 0) {
+	  dw_printf ("SUCCESS -- All digipeater tests passed.\n");
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - %d digipeater tests failed.\n", failed);
+	}
+
+	return ( failed != 0 ); 
+
+} /* end main */
+
+#endif  /* if DIGITEST */
+
+/* end digipeater.c */
diff --git a/digipeater.h b/digipeater.h
index 08f8486..d1b26b8 100644
--- a/digipeater.h
+++ b/digipeater.h
@@ -1,71 +1,71 @@
-
-
-#ifndef DIGIPEATER_H
-#define DIGIPEATER_H 1
-
-#include "regex.h"
-
-#include "direwolf.h"		/* for MAX_CHANS */
-#include "ax25_pad.h"		/* for packet_t */
-#include "audio.h"		/* for radio channel properties */
-
-
-/*
- * Information required for digipeating.
- *
- * The configuration file reader fills in this information
- * and it is passed to digipeater_init at application start up time.
- */
-
-
-struct digi_config_s {
-
-
-	int	dedupe_time;	/* Don't digipeat duplicate packets */
-				/* within this number of seconds. */
-
-#define DEFAULT_DEDUPE 30
-
-/*
- * Rules for each of the [from_chan][to_chan] combinations.
- */
-
-	regex_t	alias[MAX_CHANS][MAX_CHANS];
-
-	regex_t	wide[MAX_CHANS][MAX_CHANS];
-
-	int	enabled[MAX_CHANS][MAX_CHANS];
-
-	enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
-
-	//char type_filter[MAX_CHANS][MAX_CHANS][20]; // TODO1.2: remove this
-
-	char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
-						// NULL or optional Packet Filter strings such as "t/m".
-						// Notice the size of arrays is one larger than normal.
-						// That extra position is for the IGate.
-
-	int regen[MAX_CHANS][MAX_CHANS];	// Regenerate packet.  
-						// Sort of like digipeating but passed along unchanged.
-};
-
-/*
- * Call once at application start up time.
- */
-
-extern void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config);
-
-/*
- * Call this for each packet received.
- * Suitable packets will be queued for transmission.
- */
-
-extern void digipeater (int from_chan, packet_t pp);
-
-void digi_regen (int from_chan, packet_t pp);
-
-
-#endif 
-
-/* end digipeater.h */
-
+
+
+#ifndef DIGIPEATER_H
+#define DIGIPEATER_H 1
+
+#include "regex.h"
+
+#include "direwolf.h"		/* for MAX_CHANS */
+#include "ax25_pad.h"		/* for packet_t */
+#include "audio.h"		/* for radio channel properties */
+
+
+/*
+ * Information required for digipeating.
+ *
+ * The configuration file reader fills in this information
+ * and it is passed to digipeater_init at application start up time.
+ */
+
+
+struct digi_config_s {
+
+
+	int	dedupe_time;	/* Don't digipeat duplicate packets */
+				/* within this number of seconds. */
+
+#define DEFAULT_DEDUPE 30
+
+/*
+ * Rules for each of the [from_chan][to_chan] combinations.
+ */
+
+	regex_t	alias[MAX_CHANS][MAX_CHANS];
+
+	regex_t	wide[MAX_CHANS][MAX_CHANS];
+
+	int	enabled[MAX_CHANS][MAX_CHANS];
+
+	enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
+
+	//char type_filter[MAX_CHANS][MAX_CHANS][20]; // TODO1.2: remove this
+
+	char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
+						// NULL or optional Packet Filter strings such as "t/m".
+						// Notice the size of arrays is one larger than normal.
+						// That extra position is for the IGate.
+
+	int regen[MAX_CHANS][MAX_CHANS];	// Regenerate packet.  
+						// Sort of like digipeating but passed along unchanged.
+};
+
+/*
+ * Call once at application start up time.
+ */
+
+extern void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config);
+
+/*
+ * Call this for each packet received.
+ * Suitable packets will be queued for transmission.
+ */
+
+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 a4bf397..3ea54c7 100644
--- a/direwolf.c
+++ b/direwolf.c
@@ -1,1044 +1,1113 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      direwolf.c
- *
- * Purpose:   	Main program for "Dire Wolf" which includes:
- *			
- *			AFSK modem using the "sound card."
- *			AX.25 encoder/decoder.
- *			APRS data encoder / decoder.
- *			APRS digipeater.
- *			KISS TNC emulator.
- *			APRStt (touch tone input) gateway
- *			Internet Gateway (IGate)
- *		
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <math.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <assert.h>
-#include <string.h>
-#include <signal.h>
-#include <ctype.h>
-
-#if __ARM__
-//#include <asm/hwcap.h>
-//#include <sys/auxv.h>		// Doesn't seem to be there.
-				// We have libc 2.13.  Looks like we might need 2.17 & gcc 4.8
-#endif
-
-#if __WIN32__
-#else
-#include <unistd.h>
-#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>
-#endif
-
-
-#define DIREWOLF_C 1
-
-#include "direwolf.h"
-#include "version.h"
-#include "audio.h"
-#include "config.h"
-#include "multi_modem.h"
-#include "demod.h"
-#include "hdlc_rec.h"
-#include "hdlc_rec2.h"
-#include "ax25_pad.h"
-#include "decode_aprs.h"
-#include "textcolor.h"
-#include "server.h"
-#include "kiss.h"
-#include "kissnet.h"
-#include "kiss_frame.h"
-#include "nmea.h"
-#include "gen_tone.h"
-#include "digipeater.h"
-#include "tq.h"
-#include "xmit.h"
-#include "ptt.h"
-#include "beacon.h"
-#include "redecode.h"
-#include "dtmf.h"
-#include "aprs_tt.h"
-#include "tt_user.h"
-#include "igate.h"
-#include "symbols.h"
-#include "dwgps.h"
-#include "log.h"
-#include "recv.h"
-
-
-//static int idx_decoded = 0;
-
-#if __WIN32__
-static BOOL cleanup_win (int);
-#else
-static void cleanup_linux (int);
-#endif
-
-static void usage (char **argv);
-
-#if __SSE__
-
-static void __cpuid(int cpuinfo[4], int infotype){
-    __asm__ __volatile__ (
-        "cpuid":
-        "=a" (cpuinfo[0]),
-        "=b" (cpuinfo[1]),
-        "=c" (cpuinfo[2]),
-        "=d" (cpuinfo[3]) :
-        "a" (infotype)
-    );
-}
-
-#endif
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Main program for packet radio virtual TNC.
- *
- * Inputs:	Command line arguments.
- *		See usage message for details.
- *
- * Outputs:	Decoded information is written to stdout.
- *
- *		A socket and pseudo terminal are created for 
- *		for communication with other applications.
- *
- *--------------------------------------------------------------------*/
-
-static struct audio_s audio_config;
-static struct tt_config_s tt_config;
-struct digi_config_s digi_config;
-
-
-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 int q_h_opt = 0;			/* "-q h" Quiet, suppress the "heard" line with audio level. */
-static int q_d_opt = 0;			/* "-q d" Quiet, suppress the decoding of APRS packets. */
-
-
-static struct misc_config_s misc_config;
-
-
-int main (int argc, char *argv[])
-{
-	int err;
-	int eof;
-	int j;
-	char config_file[100];
-	int xmit_calibrate_option = 0;
-	int enable_pseudo_terminal = 0;
-	struct digi_config_s digi_config;
-	struct igate_config_s igate_config;
-	int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0;	/* Command line options. */
-	char P_opt[16];
-	char l_opt[80];
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      direwolf.c
+ *
+ * Purpose:   	Main program for "Dire Wolf" which includes:
+ *			
+ *			AFSK modem using the "sound card."
+ *			AX.25 encoder/decoder.
+ *			APRS data encoder / decoder.
+ *			APRS digipeater.
+ *			KISS TNC emulator.
+ *			APRStt (touch tone input) gateway
+ *			Internet Gateway (IGate)
+ *		
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+
+#if __ARM__
+//#include <asm/hwcap.h>
+//#include <sys/auxv.h>		// Doesn't seem to be there.
+				// We have libc 2.13.  Looks like we might need 2.17 & gcc 4.8
+#endif
+
+#if __WIN32__
+#else
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#ifdef __OpenBSD__
+#include <soundcard.h>
+#elif __APPLE__
+#else
+#include <sys/soundcard.h>
+#endif
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#endif
+
+#if USE_HAMLIB
+#include <hamlib/rig.h>
+#endif
+
+
+#define DIREWOLF_C 1
+
+#include "direwolf.h"
+#include "version.h"
+#include "audio.h"
+#include "config.h"
+#include "multi_modem.h"
+#include "demod.h"
+#include "hdlc_rec.h"
+#include "hdlc_rec2.h"
+#include "ax25_pad.h"
+#include "decode_aprs.h"
+#include "textcolor.h"
+#include "server.h"
+#include "kiss.h"
+#include "kissnet.h"
+#include "kiss_frame.h"
+#include "nmea.h"
+#include "gen_tone.h"
+#include "digipeater.h"
+#include "tq.h"
+#include "xmit.h"
+#include "ptt.h"
+#include "beacon.h"
+#include "redecode.h"
+#include "dtmf.h"
+#include "aprs_tt.h"
+#include "tt_user.h"
+#include "igate.h"
+#include "symbols.h"
+#include "dwgps.h"
+#include "log.h"
+#include "recv.h"
+#include "morse.h"
+
+
+//static int idx_decoded = 0;
+
+#if __WIN32__
+static BOOL cleanup_win (int);
+#else
+static void cleanup_linux (int);
+#endif
+
+static void usage (char **argv);
+
+#if defined(__SSE__) && !defined(__APPLE__)
+
+static void __cpuid(int cpuinfo[4], int infotype){
+    __asm__ __volatile__ (
+        "cpuid":
+        "=a" (cpuinfo[0]),
+        "=b" (cpuinfo[1]),
+        "=c" (cpuinfo[2]),
+        "=d" (cpuinfo[3]):
+        "a" (infotype)
+    );
+}
+
+#endif
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Main program for packet radio virtual TNC.
+ *
+ * Inputs:	Command line arguments.
+ *		See usage message for details.
+ *
+ * Outputs:	Decoded information is written to stdout.
+ *
+ *		A socket and pseudo terminal are created for 
+ *		for communication with other applications.
+ *
+ *--------------------------------------------------------------------*/
+
+static struct audio_s audio_config;
+static struct tt_config_s tt_config;
+struct digi_config_s digi_config;
+
+
+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 int q_h_opt = 0;			/* "-q h" Quiet, suppress the "heard" line with audio level. */
+static int q_d_opt = 0;			/* "-q d" Quiet, suppress the decoding of APRS packets. */
+
+
+static struct misc_config_s misc_config;
+
+
+int main (int argc, char *argv[])
+{
+	int err;
+	//int eof;
+	int j;
+	char config_file[100];
+	int xmit_calibrate_option = 0;
+	int enable_pseudo_terminal = 0;
+	struct digi_config_s digi_config;
+	struct igate_config_s igate_config;
+	int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0;	/* Command line options. */
+	char P_opt[16];
+	char l_opt[80];
 	char input_file[80];
-	
-	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. */	
-	int d_o_opt = 0;	/* "-d o" option for output control such as PTT and DCD. */	
-			
-	
-
-	strcpy(l_opt, "");
-	strcpy(P_opt, "");
-
-#if __WIN32__
-
-// Select UTF-8 code page for console output.
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx
-// This is the default I see for windows terminal:  
-// >chcp
-// Active code page: 437
-
-	//Restore on exit? oldcp = GetConsoleOutputCP();
-	SetConsoleOutputCP(CP_UTF8);
-
-#elif __CYGWIN__
-
-/*
- * Without this, the ISO Latin 1 characters are displayed as gray boxes.
- */
-	//setenv ("LANG", "C.ISO-8859-1", 1);
-#else
-
-/*
- * Default on Raspian & Ubuntu Linux is fine.  Don't know about others.
- *
- * Should we look at LANG environment variable and issue a warning
- * if it doesn't look something like  en_US.UTF-8 ?
- */
-
-#endif
-
-/*
- * Pre-scan the command line options for the text color option.
- * We need to set this before any text output.
- */
-
-	t_opt = 1;		/* 1 = normal, 0 = no text colors. */
-	for (j=1; j<argc-1; j++) {
-	  if (strcmp(argv[j], "-t") == 0) {
-	    t_opt = atoi (argv[j+1]);
-	    //dw_printf ("DEBUG: text color option = %d.\n", t_opt);
-	  }
-	}
-
-	// TODO: control development/beta/release by versio.h instead of changing here.
-
-	text_color_init(t_opt);
-	text_color_set(DW_COLOR_INFO);
-	//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
-	//dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "F", __DATE__);
-	dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
-
-
-#if __WIN32__
-	SetConsoleCtrlHandler (cleanup_win, TRUE);
-#else
-	setlinebuf (stdout);
-	signal (SIGINT, cleanup_linux);
-#endif
-
-
-/* 
- * Starting with version 0.9, the prebuilt Windows version 
- * requires a minimum of a Pentium 3 or equivalent so we can
- * use the SSE instructions.
- * Try to warn anyone using a CPU from the previous
- * century rather than just dying for no apparent reason.
- *
- * Now, where can I find a Pentium 2 or earlier to test this?
- */
-
-#if __SSE__
-	int cpuinfo[4];
-	__cpuid (cpuinfo, 0);
-	if (cpuinfo[0] >= 1) {
-	  __cpuid (cpuinfo, 1);
-	  //dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
-	  if ( ! ( cpuinfo[3] & (1 << 25))) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("------------------------------------------------------------------\n");
-	    dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\n");
-	    dw_printf ("If you are seeing this message, you are probably using a computer\n");
-	    dw_printf ("from the previous century.  See comments in Makefile.win for\n");
-	    dw_printf ("information on how you can recompile it for use with your antique.\n");
-	    dw_printf ("------------------------------------------------------------------\n");
-	  }
-	}
-	text_color_set(DW_COLOR_INFO);
-#endif
-
-/*
- * This has not been very well tested in 64 bit mode.
- */
-
-#if 0
-	if (sizeof(int) != 4 || sizeof(long) != 4 || sizeof(char *) != 4) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("------------------------------------------------------------------\n");
-	    dw_printf ("This might not work properly when compiled for a 64 bit target.\n");
-	    dw_printf ("It is recommended that you rebuild it with gcc -m32 option.\n");
-	    dw_printf ("------------------------------------------------------------------\n");
-	}
-#endif
-
-/*
- * Default location of configuration file is current directory.
- * Can be overridden by -c command line option.
- * TODO:  Automatically search other places.
- */
-	
-	strcpy (config_file, "direwolf.conf");
-
-/*
- * Look at command line options.
- * So far, the only one is the configuration file location.
- */
-
-	strcpy (input_file, "");
-	while (1) {
-          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},
-            {"future3", 1, 0, 'c'},
-            {0, 0, 0, 0}
-          };
-
-	  /* ':' following option character means arg is required. */
-
-          c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:",
-                        long_options, &option_index);
-          if (c == -1)
-            break;
-
-          switch (c) {
-
-          case 0:				/* possible future use */
-	    text_color_set(DW_COLOR_DEBUG);
-            dw_printf("option %s", long_options[option_index].name);
-            if (optarg) {
-                dw_printf(" with arg %s", optarg);
-            }
-            dw_printf("\n");
-            break;
-
-
-          case 'c':				/* -c for configuration file name */
-
-	    strcpy (config_file, optarg);
-            break;
-
-#if __WIN32__
-#else
-          case 'p':				/* -p enable pseudo terminal */
-		
-	    /* We want this to be off by default because it hangs */
-	    /* eventually when nothing is reading from other side. */
-
-	    enable_pseudo_terminal = 1;
-            break;
-#endif
-
-          case 'B':				/* -B baud rate and modem properties. */
-	 
-	    B_opt = atoi(optarg);
-            if (B_opt < 100 || B_opt > 10000) {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Use a more reasonable data baud rate in range of 100 - 10000.\n");
-              exit (EXIT_FAILURE);
-            }
-            break;
-
-	  case 'P':				/* -P for modem profile. */
-
-	    //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg);
-	    strcpy (P_opt, optarg); 
-	    break;	
-
-          case 'D':				/* -D decrease AFSK demodulator sample rate */
-	 
-	    D_opt = atoi(optarg);
-            if (D_opt < 1 || D_opt > 8) {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf ("Crazy value for -D. \n");
-              exit (EXIT_FAILURE);
-            }
-            break;
-
-          case 'x':				/* -x for transmit calibration tones. */
-
-	    xmit_calibrate_option = 1;
-            break;
-
-          case 'r':				/* -r audio samples/sec.  e.g. 44100 */
-	 
-	    r_opt = atoi(optarg);
-	    if (r_opt < MIN_SAMPLES_PER_SEC || r_opt > MAX_SAMPLES_PER_SEC) 
-	    {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf("-r option, audio samples/sec, is out of range.\n");
-	      r_opt = 0;
-   	    }
-            break;
-
-          case 'n':				/* -n number of audio channels for first audio device.  1 or 2. */
-	 
-	    n_opt = atoi(optarg);
-	    if (n_opt < 1 || n_opt > 2) 
-	    {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf("-n option, number of audio channels, is out of range.\n");
-	      n_opt = 0;
-   	    }
-            break;
-
-          case 'b':				/* -b bits per sample.  8 or 16. */
-	 
-	    b_opt = atoi(optarg);
-	    if (b_opt != 8 && b_opt != 16) 
-	    {
-	      text_color_set(DW_COLOR_ERROR);
-              dw_printf("-b option, bits per sample, must be 8 or 16.\n");
-	      b_opt = 0;
-   	    }
-            break;
-
-          case '?':
-
-            /* Unknown option message was already printed. */
-            usage (argv);
-            break;
-
-	  case 'd':				/* Set debug option. */
-	
-	    /* 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':  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.
-	      case 'o':  d_o_opt++; ptt_set_debug(d_o_opt); break;	
-#if AX25MEMDEBUG
-	      case 'm':  ax25memdebug_set(); break;		// Track down memory leak.  Not documented.		
-#endif
-	      default: break;
-	     }
-	    }
-	    break;
-	      
-	  case 'q':				/* Set quiet option. */
-	
-	    /* New in 1.2.  Quiet option to suppress some types of printing. */
-	    /* Can combine multiple such as "-q hd" */
-
-	    for (p=optarg; *p!='\0'; p++) {
-	     switch (*p) {
-	      case 'h':  q_h_opt = 1; break;
-	      case 'd':  q_d_opt = 1; break;
-	      default: break;
-	     }
-	    }
-	    break;
-	      
-	  case 't':				/* Was handled earlier. */
-	    break;
-
-
-	  case 'U':				/* Print UTF-8 test and exit. */
-
-	    dw_printf ("\n  UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n", 
-			0xc3, 0xb1,
-			0xc2, 0xb0,
-			0xc3, 0xbc, 0xc3, 0x9f);
-
-	    exit (0);
-	    break;
-
-          case 'l':				/* -l for log file directory name */
-
-	    strncpy (l_opt, optarg, sizeof(l_opt)-1);
-            break;
-
-          default:
-
-            /* Should not be here. */
-	    text_color_set(DW_COLOR_DEBUG);
-            dw_printf("?? getopt returned character code 0%o ??\n", c);
-            usage (argv);
-          }
-	}  /* end while(1) for options */
-
-	if (optind < argc) 
-	{
-
-          if (optind < argc - 1) 
-	  {
-	    text_color_set(DW_COLOR_ERROR);
-            dw_printf ("Warning: File(s) beyond the first are ignored.\n");
-          }
-
-	  strcpy (input_file, argv[optind]);
-
-	}
-
-/*
- * Get all types of configuration settings from configuration file.
- *
- * Possibly override some by command line options.
- */
-
-	symbols_init ();
-
-	config_init (config_file, &audio_config, &digi_config, &tt_config, &igate_config, &misc_config);
-
-	if (r_opt != 0) {
-	  audio_config.adev[0].samples_per_sec = r_opt;
-	}
-	if (n_opt != 0) {
-	  audio_config.adev[0].num_channels = n_opt;
-	  if (n_opt == 2) {
-	    audio_config.achan[1].valid = 1;
-	  }
-	}
-	if (b_opt != 0) {
-	  audio_config.adev[0].bits_per_sample = b_opt;
-	}
-	if (B_opt != 0) {
-	  audio_config.achan[0].baud = B_opt;
-
-	  if (audio_config.achan[0].baud < 600) {
-            audio_config.achan[0].modem_type = MODEM_AFSK;
-            audio_config.achan[0].mark_freq = 1600;
-            audio_config.achan[0].space_freq = 1800;
-	    audio_config.achan[0].decimate = 3;
-	  }
-	  else if (audio_config.achan[0].baud > 2400) {
-            audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
-            audio_config.achan[0].mark_freq = 0;
-            audio_config.achan[0].space_freq = 0;
-	  }
-	  else {
-            audio_config.achan[0].modem_type = MODEM_AFSK;
-            audio_config.achan[0].mark_freq = 1200;
-            audio_config.achan[0].space_freq = 2200;
-	  }
-	}
-
-	if (strlen(P_opt) > 0) { 
-	  /* -P for modem profile. */
-	  /* TODO: Not yet documented.  Should probably since it is consistent with atest. */
-	  strcpy (audio_config.achan[0].profiles, P_opt); 
-	}	
-
-	if (D_opt != 0) {
-	    // Reduce audio sampling rate to reduce CPU requirements.
-	    audio_config.achan[0].decimate = 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;
+	
+	int t_opt = 1;		/* Text color option. */				
+	int a_opt = 0;		/* "-a n" interval, in seconds, for audio statistics report.  0 for none. */
+
+	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. */	
+	int d_g_opt = 0;	/* "-d g" option for GPS. Can be repeated for more detail. */
+	int d_o_opt = 0;	/* "-d o" option for output control such as PTT and DCD. */	
+	int d_i_opt = 0;	/* "-d i" option for IGate.  Repeat for more detail */
+#if USE_HAMLIB
+	int d_h_opt = 0;	/* "-d h" option for hamlib debugging.  Repeat for more detail */
+#endif
+
+	strlcpy(l_opt, "", sizeof(l_opt));
+	strlcpy(P_opt, "", sizeof(P_opt));
+
+#if __WIN32__
+
+// Select UTF-8 code page for console output.
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx
+// This is the default I see for windows terminal:  
+// >chcp
+// Active code page: 437
+
+	//Restore on exit? oldcp = GetConsoleOutputCP();
+	SetConsoleOutputCP(CP_UTF8);
+
+#else
+
+/*
+ * Default on Raspian & Ubuntu Linux is fine.  Don't know about others.
+ *
+ * Should we look at LANG environment variable and issue a warning
+ * if it doesn't look something like  en_US.UTF-8 ?
+ */
+
+#endif
+
+/*
+ * Pre-scan the command line options for the text color option.
+ * We need to set this before any text output.
+ */
+
+	t_opt = 1;		/* 1 = normal, 0 = no text colors. */
+	for (j=1; j<argc-1; j++) {
+	  if (strcmp(argv[j], "-t") == 0) {
+	    t_opt = atoi (argv[j+1]);
+	    //dw_printf ("DEBUG: text color option = %d.\n", t_opt);
+	  }
+	}
+
+	// TODO: control development/beta/release by version.h instead of changing here.
+	// Print platform.  This will provide more information when people send a copy the information displayed.
+
+	text_color_init(t_opt);
+	text_color_set(DW_COLOR_INFO);
+	//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
+	//dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "K", __DATE__);
+	dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
+
+#if defined(ENABLE_GPSD) || defined(USE_HAMLIB)
+	dw_printf ("Includes optional support for: ");
+#if defined(ENABLE_GPSD)
+	dw_printf (" gpsd");
+#endif
+#if defined(USE_HAMLIB)
+	dw_printf (" hamlib");
+#endif
+	dw_printf ("\n");
+#endif
+
+
+#if __WIN32__
+	SetConsoleCtrlHandler ((PHANDLER_ROUTINE)cleanup_win, TRUE);
+#else
+	setlinebuf (stdout);
+	signal (SIGINT, cleanup_linux);
+#endif
+
+
+/* 
+ * Starting with version 0.9, the prebuilt Windows version 
+ * requires a minimum of a Pentium 3 or equivalent so we can
+ * use the SSE instructions.
+ * Try to warn anyone using a CPU from the previous
+ * century rather than just dying for no apparent reason.
+ *
+ * Apple computers with Intel processors started with P6. Since the
+ * cpu test code was giving Clang compiler grief it has been excluded.
+ *
+ * Now, where can I find a Pentium 2 or earlier to test this?
+ */
+
+#if defined(__SSE__) && !defined(__APPLE__)
+	int cpuinfo[4];
+	__cpuid (cpuinfo, 0);
+	if (cpuinfo[0] >= 1) {
+	  __cpuid (cpuinfo, 1);
+	  //dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
+	  if ( ! ( cpuinfo[3] & (1 << 25))) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("------------------------------------------------------------------\n");
+	    dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\n");
+	    dw_printf ("If you are seeing this message, you are probably using a computer\n");
+	    dw_printf ("from the previous century.  See comments in Makefile.win for\n");
+	    dw_printf ("information on how you can recompile it for use with your antique.\n");
+	    dw_printf ("------------------------------------------------------------------\n");
+	  }
+	}
+	text_color_set(DW_COLOR_INFO);
+#endif
+
+
+
+/*
+ * Default location of configuration file is current directory.
+ * Can be overridden by -c command line option.
+ * TODO:  Automatically search other places.
+ */
+	
+	strlcpy (config_file, "direwolf.conf", sizeof(config_file));
+
+/*
+ * Look at command line options.
+ * So far, the only one is the configuration file location.
+ */
+
+	strlcpy (input_file, "", sizeof(input_file));
+	while (1) {
+          //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},
+            {"future3", 1, 0, 'c'},
+            {0, 0, 0, 0}
+          };
+
+	  /* ':' following option character means arg is required. */
+
+          c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:",
+                        long_options, &option_index);
+          if (c == -1)
+            break;
+
+          switch (c) {
+
+          case 0:				/* possible future use */
+	    text_color_set(DW_COLOR_DEBUG);
+            dw_printf("option %s", long_options[option_index].name);
+            if (optarg) {
+                dw_printf(" with arg %s", optarg);
+            }
+            dw_printf("\n");
+            break;
+
+          case 'a':				/* -a for audio statistics interval */
+
+	    a_opt = atoi(optarg);
+	    if (a_opt < 0) a_opt = 0;
+	    if (a_opt < 10) {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("Setting such a small audio statistics interval will produce inaccurate sample rate display.\n");
+   	    }
+            break;
+
+          case 'c':				/* -c for configuration file name */
+
+	    strlcpy (config_file, optarg, sizeof(config_file));
+            break;
+
+#if __WIN32__
+#else
+          case 'p':				/* -p enable pseudo terminal */
+		
+	    /* We want this to be off by default because it hangs */
+	    /* eventually when nothing is reading from other side. */
+
+	    enable_pseudo_terminal = 1;
+            break;
+#endif
+
+          case 'B':				/* -B baud rate and modem properties. */
+	 
+	    B_opt = atoi(optarg);
+            if (B_opt < 100 || B_opt > 10000) {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Use a more reasonable data baud rate in range of 100 - 10000.\n");
+              exit (EXIT_FAILURE);
+            }
+            break;
+
+	  case 'P':				/* -P for modem profile. */
+
+	    //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg);
+	    strlcpy (P_opt, optarg, sizeof(P_opt)); 
+	    break;	
+
+          case 'D':				/* -D decrease AFSK demodulator sample rate */
+	 
+	    D_opt = atoi(optarg);
+            if (D_opt < 1 || D_opt > 8) {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf ("Crazy value for -D. \n");
+              exit (EXIT_FAILURE);
+            }
+            break;
+
+          case 'x':				/* -x for transmit calibration tones. */
+
+	    xmit_calibrate_option = 1;
+            break;
+
+          case 'r':				/* -r audio samples/sec.  e.g. 44100 */
+	 
+	    r_opt = atoi(optarg);
+	    if (r_opt < MIN_SAMPLES_PER_SEC || r_opt > MAX_SAMPLES_PER_SEC) 
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("-r option, audio samples/sec, is out of range.\n");
+	      r_opt = 0;
+   	    }
+            break;
+
+          case 'n':				/* -n number of audio channels for first audio device.  1 or 2. */
+	 
+	    n_opt = atoi(optarg);
+	    if (n_opt < 1 || n_opt > 2) 
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("-n option, number of audio channels, is out of range.\n");
+	      n_opt = 0;
+   	    }
+            break;
+
+          case 'b':				/* -b bits per sample.  8 or 16. */
+	 
+	    b_opt = atoi(optarg);
+	    if (b_opt != 8 && b_opt != 16) 
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+              dw_printf("-b option, bits per sample, must be 8 or 16.\n");
+	      b_opt = 0;
+   	    }
+            break;
+
+          case '?':
+
+            /* Unknown option message was already printed. */
+            usage (argv);
+            break;
+
+	  case 'd':				/* Set debug option. */
+	
+	    /* 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':  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 'g':  d_g_opt++; break;
+	      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.
+	      case 'o':  d_o_opt++; ptt_set_debug(d_o_opt); break;	
+	      case 'i':  d_i_opt++; break;
+#if AX25MEMDEBUG
+	      case 'm':  ax25memdebug_set(); break;		// Track down memory leak.  Not documented.		
+#endif
+#if USE_HAMLIB
+	      case 'h':  d_h_opt++; break;			// Hamlib verbose level.
+#endif
+	      default: break;
+	     }
+	    }
+	    break;
+	      
+	  case 'q':				/* Set quiet option. */
+	
+	    /* New in 1.2.  Quiet option to suppress some types of printing. */
+	    /* Can combine multiple such as "-q hd" */
+
+	    for (p=optarg; *p!='\0'; p++) {
+	     switch (*p) {
+	      case 'h':  q_h_opt = 1; break;
+	      case 'd':  q_d_opt = 1; break;
+	      default: break;
+	     }
+	    }
+	    break;
+	      
+	  case 't':				/* Was handled earlier. */
+	    break;
+
+
+	  case 'U':				/* Print UTF-8 test and exit. */
+
+	    dw_printf ("\n  UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n", 
+			0xc3, 0xb1,
+			0xc2, 0xb0,
+			0xc3, 0xbc, 0xc3, 0x9f);
+
+	    exit (0);
+	    break;
+
+          case 'l':				/* -l for log file directory name */
+
+	    strlcpy (l_opt, optarg, sizeof(l_opt));
+            break;
+
+	  case 'S':				/* Print symbol tables and exit. */
+
+	    symbols_init ();
+	    symbols_list ();
+	    exit (0);
+	    break;
+
+          default:
+
+            /* Should not be here. */
+	    text_color_set(DW_COLOR_DEBUG);
+            dw_printf("?? getopt returned character code 0%o ??\n", c);
+            usage (argv);
+          }
+	}  /* end while(1) for options */
+
+	if (optind < argc) 
+	{
+
+          if (optind < argc - 1) 
+	  {
+	    text_color_set(DW_COLOR_ERROR);
+            dw_printf ("Warning: File(s) beyond the first are ignored.\n");
+          }
+
+	  strlcpy (input_file, argv[optind], sizeof(input_file));
+
+	}
+
+/*
+ * Get all types of configuration settings from configuration file.
+ *
+ * Possibly override some by command line options.
+ */
+
+#if USE_HAMLIB
+        rig_set_debug(d_h_opt);
+#endif
+
+	symbols_init ();
+
+	config_init (config_file, &audio_config, &digi_config, &tt_config, &igate_config, &misc_config);
+
+	if (r_opt != 0) {
+	  audio_config.adev[0].samples_per_sec = r_opt;
+	}
+	if (n_opt != 0) {
+	  audio_config.adev[0].num_channels = n_opt;
+	  if (n_opt == 2) {
+	    audio_config.achan[1].valid = 1;
+	  }
+	}
+	if (b_opt != 0) {
+	  audio_config.adev[0].bits_per_sample = b_opt;
+	}
+	if (B_opt != 0) {
+	  audio_config.achan[0].baud = B_opt;
+
+	  if (audio_config.achan[0].baud < 600) {
+            audio_config.achan[0].modem_type = MODEM_AFSK;
+            audio_config.achan[0].mark_freq = 1600;
+            audio_config.achan[0].space_freq = 1800;
+	    audio_config.achan[0].decimate = 3;
+	  }
+	  else if (audio_config.achan[0].baud > 2400) {
+            audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
+            audio_config.achan[0].mark_freq = 0;
+            audio_config.achan[0].space_freq = 0;
+	  }
+	  else {
+            audio_config.achan[0].modem_type = MODEM_AFSK;
+            audio_config.achan[0].mark_freq = 1200;
+            audio_config.achan[0].space_freq = 2200;
+	  }
+	}
+
+	audio_config.statistics_interval = a_opt;
+
+	if (strlen(P_opt) > 0) { 
+	  /* -P for modem profile. */
+	  /* TODO: Not yet documented.  Should probably since it is consistent with atest. */
+	  strlcpy (audio_config.achan[0].profiles, P_opt, sizeof(audio_config.achan[0].profiles)); 
+	}	
+
+	if (D_opt != 0) {
+	    // Reduce audio sampling rate to reduce CPU requirements.
+	    audio_config.achan[0].decimate = D_opt;
+	}
+
+	if (strlen(l_opt) > 0) {
+	  strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir));
+	}
+
+	misc_config.enable_kiss_pt = enable_pseudo_terminal;
 
 	if (strlen(input_file) > 0) {
-	  strcpy (audio_config.adev[0].adevice_in, input_file);
+
+	  strlcpy (audio_config.adev[0].adevice_in, input_file, sizeof(audio_config.adev[0].adevice_in));
+
 	}
-
-/*
- * Open the audio source 
- *	- soundcard
- *	- stdin
- *	- UDP
- * Files not supported at this time.
- * Can always "cat" the file and pipe it into stdin.
- */
-
-	err = audio_open (&audio_config);
-	if (err < 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Pointless to continue without audio device.\n");
-	  SLEEP_SEC(5);
-	  exit (1);
-	}
-
-/*
- * Initialize the AFSK demodulator and HDLC decoder.
- */
-	multi_modem_init (&audio_config);
-
-/*
- * Initialize the touch tone decoder & APRStt gateway.
- */
-	dtmf_init (&audio_config);
-	aprs_tt_init (&tt_config);
-	tt_user_init (&audio_config, &tt_config);
-
-/*
- * Should there be an option for audio output level?
- * Note:  This is not the same as a volume control you would see on the screen.
- * It is the range of the digital sound representation.
-*/
-	gen_tone_init (&audio_config, 100);
-
-	assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16);
-	assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2);
-	assert (audio_config.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && audio_config.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC);
-
-/*
- * Initialize the transmit queue.
- */
-
-	xmit_init (&audio_config, d_p_opt);
-
-/*
- * If -x option specified, transmit alternating tones for transmitter
- * audio level adjustment, up to 1 minute then quit.
- * TODO:  enhance for more than one channel.
- */
-
-	if (xmit_calibrate_option) {
-
-	  int max_duration = 60;  /* seconds */
-	  int n = audio_config.achan[0].baud * max_duration;
-	  int chan = 0;
-	
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("\nSending transmit calibration tones.  Press control-C to terminate.\n");
-
-	  ptt_set (OCTYPE_PTT, chan, 1);
-	  while (n-- > 0) {
-
-	    tone_gen_put_bit (chan, n & 1);
-
-	  }
-	  ptt_set (OCTYPE_PTT, chan, 0);
-	  exit (0);
-	}
-
-/*
- * Initialize the digipeater and IGate functions.
- */
-	digipeater_init (&audio_config, &digi_config);
-	igate_init (&audio_config, &igate_config, &digi_config);
-
-/*
- * Provide the AGW & KISS socket interfaces for use by a client application.
- */
-	server_init (&audio_config, &misc_config);
-	kissnet_init (&misc_config);
-
-/*
- * Create a pseudo terminal and KISS TNC emulator.
- */
-	kiss_init (&misc_config);
-	kiss_frame_init (&audio_config);
-
-/*
- * Open port for communication with GPS.
- */
-	nmea_init (&misc_config);
-
-/* 
- * Create thread for trying to salvage frames with bad FCS.
- */
-	redecode_init (&audio_config);
-
-/*
- * Enable beaconing.
- */
-	beacon_init (&audio_config, &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.
- */
-
-
-	recv_init (&audio_config);
-	recv_process ();
-
-	exit (EXIT_SUCCESS);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        app_process_rec_frame
- *
- * Purpose:     This is called when we receive a frame with a valid 
- *		FCS and acceptable size.
- *
- * Inputs:	chan	- Audio channel number, 0 or 1.
- *		subchan	- Which modem caught it.  
- *			  Special case -1 for DTMF decoder.
- *		pp	- Packet handle.
- *		alevel	- Audio level, range of 0 - 100.
- *				(Special case, use negative to skip
- *				 display of audio level line.
- *				 Use -2 to indicate DTMF message.)
- *		retries	- Level of bit correction used.
- *		spectrum - Display of how well multiple decoders did.
- *
- *
- * Description:	Print decoded packet.
- *		Optionally send to another application.
- *
- *--------------------------------------------------------------------*/
-
-// TODO:  Use only one printf per line so output doesn't get jumbled up with stuff from other threads.
-
-
-void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)  
-{	
-	
-	char stemp[500];
-	unsigned char *pinfo;
-	int info_len;
-	char heard[AX25_MAX_ADDR_LEN];
-	//int j;
-	int h;
-	char display_retries[32];
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= -1 && subchan < MAX_SUBCHANS);
-	assert (pp != NULL);	// 1.1J+
-     
-	strcpy (display_retries, "");
-	if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) {
-	  sprintf (display_retries, " [%s] ", retry_text[(int)retries]);
-	}
-
-	ax25_format_addrs (pp, stemp);
-
-	info_len = ax25_get_info (pp, &pinfo);
-
-	/* Print so we can see what is going on. */
-
-	/* Display audio input level. */
-        /* Who are we hearing?   Original station or digipeater. */
-
-	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);
-	}
-
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("\n");
-
-	if (( ! q_h_opt ) && alevel.rec >= 0) {    /* suppress if "-q h" option */
-
-	  if (h != -1 && h != AX25_SOURCE) {
-	    dw_printf ("Digipeater ");
-	  }
-
-	  char alevel_text[32];
-
-	  ax25_alevel_to_text (alevel, alevel_text);
-
-
-	  /* As suggested by KJ4ERJ, if we are receiving from */
-	  /* WIDEn-0, it is quite likely (but not guaranteed), that */
-	  /* we are actually hearing the preceding station in the path. */
-
-	  if (h >= AX25_REPEATER_2 && 
-	        strncmp(heard, "WIDE", 4) == 0 &&
-	        isdigit(heard[4]) &&
-	        heard[5] == '\0') {
-
-	    char probably_really[AX25_MAX_ADDR_LEN];
-
-
-	    ax25_get_addr_with_ssid(pp, h-1, probably_really);
-
-	    dw_printf ("%s (probably %s) audio level = %s  %s  %s\n", heard, probably_really, alevel_text, display_retries, spectrum);
-
-	  }
-	  else {
-
-	    dw_printf ("%s audio level = %s  %s  %s\n", heard, alevel_text, display_retries, spectrum);
-	  }
-	}
-
-	/* Version 1.2:   Cranking the input level way up produces 199. */
-	/* Keeping it under 100 gives us plenty of headroom to avoid saturation. */
-
-	// TODO:  suppress this message if not using soundcard input.
-	// i.e. we have no control over the situation when using SDR.
-
-	if (alevel.rec > 110) {
-
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Audio input level is too high.  Reduce so most stations are around 50.\n");
-	}
-
-
-// Display non-APRS packets in a different color.
-
-// Display subchannel only when multiple modems configured for channel.
-
-// -1 for APRStt DTMF decoder.
-
-	if (subchan == -1) {
-	  text_color_set(DW_COLOR_REC);
-	  dw_printf ("[%d.dtmf] ", chan);
-	}
-	else {
-	  if (ax25_is_aprs(pp)) {
-	    text_color_set(DW_COLOR_REC);
-	  }
-	  else {
-	    text_color_set(DW_COLOR_DEBUG);
-	  }
-	  if (audio_config.achan[chan].num_subchan > 1) {
-	    dw_printf ("[%d.%d] ", chan, subchan);
-	  }
-	  else {
-	    dw_printf ("[%d] ", chan);
-	  }
-	}
-
-	dw_printf ("%s", stemp);			/* stations followed by : */
-
-	// 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");
-
-// Also display in pure ASCII if non-ASCII characters and "-d u" option specified.
-
-	if (d_u_opt) {
-
-	  unsigned char *p;
-	  int n = 0;
-
-	  for (p = pinfo; *p != '\0'; p++) {
-	    if (*p >= 0x80) n++;
-	  }
-
-	  if (n > 0) {
-	    text_color_set(DW_COLOR_DEBUG);
-	    ax25_safe_print ((char *)pinfo, info_len, 1);
-	    dw_printf ("\n");
-	  }
-	}
-
-/* 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. */
-/* Suppress decoding if "-q d" option used. */
-
-	if ( ( ! q_d_opt ) && ax25_is_aprs(pp)) {
-
-	  decode_aprs_t A;
-
-	  decode_aprs (&A, pp, 0);
-
-	  //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 we have a location.
-
- 	  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;
-	unsigned char fbuf[AX25_MAX_PACKET_LEN];
-
-	flen = ax25_pack(pp, fbuf);
-
-	server_send_rec_packet (chan, pp, fbuf, flen);
-	kissnet_send_rec_packet (chan, fbuf, flen);
-	kiss_send_rec_packet (chan, fbuf, flen);
-
-/* 
- * If it came from DTMF decoder, send it to APRStt gateway.
- * Otherwise, it is a candidate for IGate and digipeater.
- *
- * TODO: It might be useful to have some way to simulate touch tone
- * sequences with BEACON sendto=R... for testing.
- */
-	if (subchan == -1) {
-	  if (tt_config.gateway_enabled && info_len >= 2) {
-	    aprs_tt_sequence (chan, pinfo+1);
-	  }
-	}
-	else { 
-	
-/* Send to Internet server if option is enabled. */
-/* Consider only those with correct CRC. */
-
-	  if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
-
-	    if (digi_config.filter_str[chan][MAX_CHANS] != NULL) {
-
-// TODO1.2: filtering  - maybe it should be ig... so we don't waste time filtering if igate not used.
-
-	    }
-	    else {
-	      igate_send_rec_packet (chan, pp);
-	    }
-	  }
-
-/* Send out a regenerated copy. Applies to all types, not just APRS. */
-
-	  digi_regen (chan, pp);
-
-
-/* 
- *Note that the digipeater function can modify the packet 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!
- * Single bit change appears to be safe from observations so far but be cautious. 
- */
-
-	  if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
-
-	    digipeater (chan, pp);
-	  }
-	}
-
-	ax25_delete (pp);
-	
-} /* end app_process_rec_packet */
-
-
-/* Process control C and window close events. */
-
-#if __WIN32__
-
-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);
-	  ExitProcess (0);
-	}
-	return (TRUE);
-}
-
-
-#else
-
-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);
-}
-
-#endif
-
-
-
-static void usage (char **argv)
-{
-	text_color_set(DW_COLOR_ERROR);
-
-	dw_printf ("\n");
-	dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
-	dw_printf ("\n");
-	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");
-	dw_printf ("    -b n           Bits per audio sample, 8 or 16.\n");
-	dw_printf ("    -B n           Data rate in bits/sec for channel 0.  Standard values are 300, 1200, 9600.\n");
-	dw_printf ("                     If < 600, AFSK tones are set to 1600 & 1800.\n");
-	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 n           Divide audio sample rate by n for channel 0.\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 ("       o             o = output controls such as PTT and DCD.\n");
-	dw_printf ("    -q             Quiet (suppress output) options:\n");
-	dw_printf ("       h             h = Heard line with the audio level.\n");
-	dw_printf ("       d             d = Decoding of APRS packets.\n");
-	dw_printf ("    -t n           Text colors.  1=normal, 0=disabled.\n");
-#if __WIN32__
-#else
-	dw_printf ("    -p             Enable pseudo terminal for KISS protocol.\n");
-#endif
-	dw_printf ("    -x             Send Xmit level calibration tones.\n");
-	dw_printf ("    -U             Print UTF-8 test string and exit.\n");
-	dw_printf ("\n");
-
-#if __WIN32__
-#else
-	dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf.\n");
-#endif
-	exit (EXIT_FAILURE);
-}
-
-
-
-/* end direwolf.c */
+
+
+/*
+ * Open the audio source 
+ *	- soundcard
+ *	- stdin
+ *	- UDP
+ * Files not supported at this time.
+ * Can always "cat" the file and pipe it into stdin.
+ */
+
+	err = audio_open (&audio_config);
+	if (err < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Pointless to continue without audio device.\n");
+	  SLEEP_SEC(5);
+	  exit (1);
+	}
+
+/*
+ * Initialize the AFSK demodulator and HDLC decoder.
+ */
+	multi_modem_init (&audio_config);
+
+/*
+ * Initialize the touch tone decoder & APRStt gateway.
+ */
+	dtmf_init (&audio_config);
+	aprs_tt_init (&tt_config);
+	tt_user_init (&audio_config, &tt_config);
+
+/*
+ * Should there be an option for audio output level?
+ * Note:  This is not the same as a volume control you would see on the screen.
+ * It is the range of the digital sound representation.
+*/
+	gen_tone_init (&audio_config, 100);
+	morse_init (&audio_config, 100);
+
+	assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16);
+	assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2);
+	assert (audio_config.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && audio_config.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC);
+
+/*
+ * Initialize the transmit queue.
+ */
+
+	xmit_init (&audio_config, d_p_opt);
+
+/*
+ * If -x option specified, transmit alternating tones for transmitter
+ * audio level adjustment, up to 1 minute then quit.
+ * TODO:  enhance for more than one channel.
+ */
+
+	if (xmit_calibrate_option) {
+
+	  int max_duration = 60;  /* seconds */
+	  int n = audio_config.achan[0].baud * max_duration;
+	  int chan = 0;
+	
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("\nSending transmit calibration tones.  Press control-C to terminate.\n");
+
+	  ptt_set (OCTYPE_PTT, chan, 1);
+	  while (n-- > 0) {
+
+	    tone_gen_put_bit (chan, n & 1);
+
+	  }
+	  ptt_set (OCTYPE_PTT, chan, 0);
+	  exit (0);
+	}
+
+/*
+ * Initialize the digipeater and IGate functions.
+ */
+	digipeater_init (&audio_config, &digi_config);
+	igate_init (&audio_config, &igate_config, &digi_config, d_i_opt);
+
+/*
+ * Provide the AGW & KISS socket interfaces for use by a client application.
+ */
+	server_init (&audio_config, &misc_config);
+	kissnet_init (&misc_config);
+
+/*
+ * Create a pseudo terminal and KISS TNC emulator.
+ */
+	kiss_init (&misc_config);
+	kiss_frame_init (&audio_config);
+
+/*
+ * Open port for communication with GPS.
+ */
+	dwgps_init (&misc_config, d_g_opt);
+
+	nmea_init (&misc_config);  //  TODO: revisit.
+
+/* 
+ * Create thread for trying to salvage frames with bad FCS.
+ */
+	redecode_init (&audio_config);
+
+/*
+ * Enable beaconing.
+ * Open log file first because "-dttt" (along with -l...) will
+ * log the tracker beacon transmissions with fake channel 999.
+ */
+
+	log_init(misc_config.logdir);
+	beacon_init (&audio_config, &misc_config);
+
+
+/*
+ * Get sound samples and decode them.
+ * Use hot attribute for all functions called for every audio sample.
+ */
+
+	recv_init (&audio_config);
+	recv_process ();
+
+	exit (EXIT_SUCCESS);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        app_process_rec_frame
+ *
+ * Purpose:     This is called when we receive a frame with a valid 
+ *		FCS and acceptable size.
+ *
+ * Inputs:	chan	- Audio channel number, 0 or 1.
+ *		subchan	- Which modem caught it.  
+ *			  Special case -1 for DTMF decoder.
+ *		slice	- Slicer which caught it.
+ *		pp	- Packet handle.
+ *		alevel	- Audio level, range of 0 - 100.
+ *				(Special case, use negative to skip
+ *				 display of audio level line.
+ *				 Use -2 to indicate DTMF message.)
+ *		retries	- Level of bit correction used.
+ *		spectrum - Display of how well multiple decoders did.
+ *
+ *
+ * Description:	Print decoded packet.
+ *		Optionally send to another application.
+ *
+ *--------------------------------------------------------------------*/
+
+// TODO:  Use only one printf per line so output doesn't get jumbled up with stuff from other threads.
+
+void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
+{	
+	
+	char stemp[500];
+	unsigned char *pinfo;
+	int info_len;
+	char heard[AX25_MAX_ADDR_LEN];
+	//int j;
+	int h;
+	char display_retries[32];
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= -1 && subchan < MAX_SUBCHANS);
+	assert (slice >= 0 && slice < MAX_SLICERS);
+	assert (pp != NULL);	// 1.1J+
+     
+	strlcpy (display_retries, "", sizeof(display_retries));
+	if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) {
+	  snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]);
+	}
+
+	ax25_format_addrs (pp, stemp);
+
+	info_len = ax25_get_info (pp, &pinfo);
+
+	/* Print so we can see what is going on. */
+
+	/* Display audio input level. */
+        /* Who are we hearing?   Original station or digipeater. */
+
+	if (ax25_get_num_addr(pp) == 0) {
+	  /* Not AX.25. No station to display below. */
+	  h = -1;
+	  strlcpy (heard, "", sizeof(heard));
+	}
+	else {
+	  h = ax25_get_heard(pp);
+          ax25_get_addr_with_ssid(pp, h, heard);
+	}
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("\n");
+
+	if (( ! q_h_opt ) && alevel.rec >= 0) {    /* suppress if "-q h" option */
+
+	  if (h != -1 && h != AX25_SOURCE) {
+	    dw_printf ("Digipeater ");
+	  }
+
+	  char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE];
+
+	  ax25_alevel_to_text (alevel, alevel_text);
+
+
+	  /* As suggested by KJ4ERJ, if we are receiving from */
+	  /* WIDEn-0, it is quite likely (but not guaranteed), that */
+	  /* we are actually hearing the preceding station in the path. */
+
+	  if (h >= AX25_REPEATER_2 && 
+	        strncmp(heard, "WIDE", 4) == 0 &&
+	        isdigit(heard[4]) &&
+	        heard[5] == '\0') {
+
+	    char probably_really[AX25_MAX_ADDR_LEN];
+
+
+	    ax25_get_addr_with_ssid(pp, h-1, probably_really);
+
+	    dw_printf ("%s (probably %s) audio level = %s  %s  %s\n", heard, probably_really, alevel_text, display_retries, spectrum);
+
+	  }
+	  else if (strcmp(heard, "DTMF") == 0) {
+
+	    dw_printf ("%s audio level = %s  tt\n", heard, alevel_text);
+	  }
+	  else {
+
+	    dw_printf ("%s audio level = %s  %s  %s\n", heard, alevel_text, display_retries, spectrum);
+	  }
+	}
+
+	/* Version 1.2:   Cranking the input level way up produces 199. */
+	/* Keeping it under 100 gives us plenty of headroom to avoid saturation. */
+
+	// TODO:  suppress this message if not using soundcard input.
+	// i.e. we have no control over the situation when using SDR.
+
+	if (alevel.rec > 110) {
+
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Audio input level is too high.  Reduce so most stations are around 50.\n");
+	}
+
+
+// Display non-APRS packets in a different color.
+
+// Display subchannel only when multiple modems configured for channel.
+
+// -1 for APRStt DTMF decoder.
+
+	if (subchan == -1) {
+	  text_color_set(DW_COLOR_REC);
+	  dw_printf ("[%d.dtmf] ", chan);
+	}
+	else {
+	  if (ax25_is_aprs(pp)) {
+	    text_color_set(DW_COLOR_REC);
+	  }
+	  else {
+	    text_color_set(DW_COLOR_DEBUG);
+	  }
+
+	  if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) {
+	    dw_printf ("[%d.%d] ", chan, subchan);
+	  }
+	  else if (audio_config.achan[chan].num_subchan == 1 && audio_config.achan[chan].num_slicers > 1) {
+	    dw_printf ("[%d.%d] ", chan, slice);
+	  }
+	  else if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers > 1) {
+	    dw_printf ("[%d.%d.%d] ", chan, subchan, slice);
+	  }
+	  else {
+	    dw_printf ("[%d] ", chan);
+	  }
+	}
+
+	dw_printf ("%s", stemp);			/* stations followed by : */
+
+	// 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");
+
+// Also display in pure ASCII if non-ASCII characters and "-d u" option specified.
+
+	if (d_u_opt) {
+
+	  unsigned char *p;
+	  int n = 0;
+
+	  for (p = pinfo; *p != '\0'; p++) {
+	    if (*p >= 0x80) n++;
+	  }
+
+	  if (n > 0) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    ax25_safe_print ((char *)pinfo, info_len, 1);
+	    dw_printf ("\n");
+	  }
+	}
+
+/* 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. */
+/* Suppress decoding if "-q d" option used. */
+
+	if ( ( ! q_d_opt ) && ax25_is_aprs(pp)) {
+
+	  decode_aprs_t A;
+
+	  decode_aprs (&A, pp, 0);
+
+	  //Print it all out in human readable format.
+
+	  decode_aprs_print (&A);
+
+	  /*
+	   * Perform validity check on each address.
+	   * This should print an error message if any issues.
+	   */
+	  (void)ax25_check_addresses(pp);
+
+	  // Send to log file.
+
+	  log_write (chan, &A, pp, alevel, retries);
+
+	  // Convert to NMEA waypoint sentence if we have a location.
+
+ 	  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_ft), A.g_course, DW_MPH_TO_KNOTS(A.g_speed_mph), 
+		A.g_comment);
+	  }
+	}
+
+
+/* Send to another application if connected. */
+// TODO1.3:  Put a wrapper around this so we only call one function to send by all methods.
+
+	int flen;
+	unsigned char fbuf[AX25_MAX_PACKET_LEN];
+
+	flen = ax25_pack(pp, fbuf);
+
+	server_send_rec_packet (chan, pp, fbuf, flen);
+	kissnet_send_rec_packet (chan, fbuf, flen);
+	kiss_send_rec_packet (chan, fbuf, flen);
+
+/* 
+ * If it came from DTMF decoder, send it to APRStt gateway.
+ * Otherwise, it is a candidate for IGate and digipeater.
+ *
+ * TODO: It might be useful to have some way to simulate touch tone
+ * sequences with BEACON sendto=R... for testing.
+ */
+	if (subchan == -1) {
+	  if (tt_config.gateway_enabled && info_len >= 2) {
+	    aprs_tt_sequence (chan, (char*)(pinfo+1));
+	  }
+	}
+	else { 
+	
+/* Send to Internet server if option is enabled. */
+/* Consider only those with correct CRC. */
+
+	  if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
+
+	    igate_send_rec_packet (chan, pp);
+	  }
+
+
+/* Send out a regenerated copy. Applies to all types, not just APRS. */
+/* This was an experimental feature never documented in the User Guide. */
+/* Initial feedback was positive but it fell by the wayside. */
+/* Should follow up with testers and either document this or clean out the clutter. */
+
+	  digi_regen (chan, pp);
+
+
+/* 
+ * Note that the digipeater function can modify the packet 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!
+ * Single bit change appears to be safe from observations so far but be cautious. 
+ */
+
+	  if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
+
+	    digipeater (chan, pp);
+	  }
+	}
+
+	ax25_delete (pp);
+	
+} /* end app_process_rec_packet */
+
+
+/* Process control C and window close events. */
+
+#if __WIN32__
+
+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);
+	  ExitProcess (0);
+	}
+	return (TRUE);
+}
+
+
+#else
+
+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);
+}
+
+#endif
+
+
+
+static void usage (char **argv)
+{
+	text_color_set(DW_COLOR_ERROR);
+
+	dw_printf ("\n");
+	dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
+	dw_printf ("\n");
+	dw_printf ("Usage: direwolf [options] [ - | stdin | UDP:nnnn ]\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");
+	dw_printf ("    -b n           Bits per audio sample, 8 or 16.\n");
+	dw_printf ("    -B n           Data rate in bits/sec for channel 0.  Standard values are 300, 1200, 9600.\n");
+	dw_printf ("                     If < 600, AFSK tones are set to 1600 & 1800.\n");
+	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 n           Divide audio sample rate by n for channel 0.\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 ("       g             g = GPS interface.\n");
+	dw_printf ("       t             t = Tracker beacon.\n");
+	dw_printf ("       o             o = output controls such as PTT and DCD.\n");
+	dw_printf ("       i             i = IGate.\n");
+#if USE_HAMLIB
+	dw_printf ("       h             h = hamlib increase verbose level.\n");
+#endif
+	dw_printf ("    -q             Quiet (suppress output) options:\n");
+	dw_printf ("       h             h = Heard line with the audio level.\n");
+	dw_printf ("       d             d = Decoding of APRS packets.\n");
+	dw_printf ("    -t n           Text colors.  1=normal, 0=disabled.\n");
+	dw_printf ("    -a n           Audio statistics interval in seconds.  0 to disable.\n");
+#if __WIN32__
+#else
+	dw_printf ("    -p             Enable pseudo terminal for KISS protocol.\n");
+#endif
+	dw_printf ("    -x             Send Xmit level calibration tones.\n");
+	dw_printf ("    -U             Print UTF-8 test string and exit.\n");
+	dw_printf ("    -S             Print symbol tables and exit.\n");
+	dw_printf ("\n");
+
+	dw_printf ("After any options, there can be a single command line argument for the source of\n");
+	dw_printf ("received audio.  This can overrides the audio input specified in the configuration file.\n");
+	dw_printf ("\n");
+  
+#if __WIN32__
+#else
+	dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf.\n");
+#endif
+	exit (EXIT_FAILURE);
+}
+
+
+
+/* end direwolf.c */
diff --git a/direwolf.conf b/direwolf.conf
deleted file mode 100644
index b564b3c..0000000
--- a/direwolf.conf
+++ /dev/null
@@ -1,458 +0,0 @@
-#############################################################
-#                                                           #
-#               Configuration file for Dire Wolf            #
-#                                                           #
-#                   Linux version                           #
-#                                                           #
-#############################################################
-#
-# Consult the User Guide for more details on configuration options.
-#
-#
-# These are the most likely settings you might change:
-#
-#	(1)   	MYCALL 	-  call sign and SSID for your station.
-#
-#			Look for lines starting with MYCALL and 
-#			change NOCALL to your own.
-#
-#	(2)	PBEACON	-  enable position beaconing.
-#
-#			Look for lines starting with PBEACON and 
-#			modify for your call, location, etc.
-#
-#	(3)	DIGIPEATER  -  configure digipeating rules.
-#
-#			Look for lines starting with DIGIPEATER.
-#			Most people will probably use the given example.
-#			Just remove the "#" from the start of the line
-#			to enable it.
-#
-#	(4)	IGSERVER, IGLOGIN  - IGate server and login
-#
-#			Configure an IGate client to relay messages between 
-#			radio and internet servers.
-#
-#
-# The default location is "direwolf.conf" in the current working directory.
-# On Linux, the user's home directory will also be searched.
-# An alternate configuration file location can be specified with the "-c" command line option.  
-#
-# As you probably guessed by now, # indicates a comment line.
-#
-# Remove the # at the beginning of a line if you want to use a sample
-# configuration that is currently commented out.
-#
-# Commands are a keyword followed by parameters.
-#
-# Command key words are case insensitive.  i.e. upper and lower case are equivalent.
-#
-# Command parameters are generally case sensitive.  i.e. upper and lower case are different.
-#
-
-
-#############################################################
-#                                                           #
-#               FIRST AUDIO DEVICE PROPERTIES               #
-#               (Channel 0 + 1 if in stereo)                #
-#                                                           #
-#############################################################
-
-#
-# Many people will simply use the default sound device.
-# Some might want to use an alternative device by chosing it here.
-#
-# Linux ALSA is complicated.  See User Guide for discussion.
-# To use something other than the default, generally use plughw
-# and a card number reported by "arecord -l" command.  Example:
-
-# ADEVICE  plughw:1,0
-
-# Starting with version 1.0, you can also use "-" or "stdin" to 
-# pipe stdout from some other application such as a software defined
-# radio.  You can also specify "UDP:" and an optional port for input.
-# Something different must be specified for output.
-
-# ADEVICE - plughw:1,0
-# ADEVICE UDP:7355 default
-
-
-
-#
-# Number of audio channels for this souncard:  1 or 2.
-#
-
-ACHANNELS 1
-#ACHANNELS 2
-
-
-#############################################################
-#                                                           #
-#               SECOND AUDIO DEVICE PROPERTIES              #
-#               (Channel 2 + 3 if in stereo)                #
-#                                                           #
-#############################################################
-
-#ADEVICE1  ...
-
-
-#############################################################
-#                                                           #
-#               THIRD AUDIO DEVICE PROPERTIES               #
-#               (Channel 4 + 5 if in stereo)                #
-#                                                           #
-#############################################################
-
-#ADEVICE2  ...
-
-
-#############################################################
-#                                                           #
-#               CHANNEL 0 PROPERTIES                        #
-#                                                           #
-#############################################################
-
-CHANNEL 0
-
-#
-# The following MYCALL, MODEM, PTT, etc. configuration items
-# apply to the most recent CHANNEL.
-#
-
-#
-# Station identifier for this channel.
-# Multiple channels can have the same or different names.
-#
-# It can be up to 6 letters and digits with an optional ssid.
-# The APRS specification requires that it be upper case.
-#
-# Example (don't use this unless you are me):  MYCALL	WB2OSZ-5
-#
-
-MYCALL N0CALL
-
-#
-# Pick a suitable modem speed based on your situation.
-#	1200 	Most common for VHF/UHF.  Default if not specified.
-#	300	Low speed for HF SSB.
-#	9600	High speed - Can't use Microphone and Speaker connections.
-#
-# In the simplest form, just specify the speed. 
-# 
-
-MODEM 1200
-#MODEM 300
-#MODEM 9600
-
-#
-# These are the defaults should be fine for most cases.  In special situations, 
-# you might want to specify different AFSK tones or the baseband mode which does
-# not use AFSK.
-#
-#MODEM 1200 1200:2200
-#MODEM 300  1600:1800
-#MODEM 9600 0:0
-#
-#
-# On HF SSB, you might want to use multiple demodulators on slightly different
-# frequencies to compensate for stations off frequency.  Here we have 7 different
-# demodulators at 30 Hz intervals.  This takes a lot of CPU power so you will 
-# probably need to reduce the audio sampling rate with the /n option.
-
-#MODEM 300 1600:1800 7 at 30 /4
-
-
-#
-# Uncomment line below to enable the DTMF decoder for this channel.
-#
-
-#DTMF
-
-# 
-# If not using a VOX circuit, the transmitter Push to Talk (PTT) 
-# control is usually wired to a serial port with a suitable interface circuit.  
-# DON'T connect it directly!
-#
-# For the PTT command, specify the device and either RTS or DTR.
-# RTS or DTR may be preceded by "-" to invert the signal.
-# Both can be used for interfaces that want them driven with opposite polarity.
-#
-# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on.
-#
-
-#PTT COM1 RTS
-#PTT COM1 RTS -DTR
-#PTT /dev/ttyUSB0 RTS
-
-#
-# On Linux, you can also use general purpose I/O pins if
-# your system is configured for user access to them. 
-# This would apply mostly to microprocessor boards, not a regular PC.
-# See separate Raspberry Pi document for more details.
-# The number may be preceded by "-" to invert the signal.
-#
-
-#PTT GPIO 25
-
-# The Data Carrier Detect (DCD) signal can be sent to the same places
-# as the PTT signal.  This could be used to light up an LED like a normal TNC.
-
-#DCD COM1 -DTR
-#DCD GPIO 24
-
-
-#############################################################
-#                                                           #
-#               CHANNEL 1 PROPERTIES                        #
-#                                                           #
-#############################################################
-
-#CHANNEL 1
-
-#
-# Specify MYCALL, MODEM, PTT, etc. configuration items for 
-# CHANNEL 1.   Repeat for any other channels.
-
-
-#############################################################
-#                                                           #
-#               TEXT TO SPEECH COMMAND FILE                 #
-#                                                           #
-#############################################################
-
-#SPEECH dwespeak.sh
-
-
-#############################################################
-#                                                           #
-#               VIRTUAL TNC SERVER PROPERTIES               #
-#                                                           #
-#############################################################
-
-#
-# Dire Wolf acts as a virtual TNC and can communicate with
-# client applications by different protocols:
-#
-#	- the "AGW TCPIP Socket Interface" - default port 8000
-#	- KISS protocol over TCP socket - default port 8001
-#	- KISS TNC via pseudo terminal   (-p command line option)
-#
-
-AGWPORT 8000
-KISSPORT 8001
-
-#
-# It is sometimes possible to recover frames with a bad FCS.
-# This applies to all channels.  
-#
-#	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 0
-
-#	
-#############################################################
-#                                                           #
-#               BEACONING PROPERTIES                        #
-#                                                           #
-#############################################################
-
-
-#
-# Beaconing is configured with these two commands:
-#
-#	PBEACON		- for a position report (usually yourself)
-#	OBEACON		- for an object report (usually some other entity)
-#
-# Each has a series of keywords and values for options.  
-# See User Guide for details.
-#
-# Example:
-#
-# This results in a broadcast once every 10 minutes.
-# Every half hour, it can travel via two digipeater hops.
-# The others are kept local.
-#
-
-#PBEACON delay=1  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=11 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=21 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=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 
-
-
-#
-# When the destination field is set to "SPEECH" the information part is
-# converted to speech rather than transmitted as a data frame.
-#
-
-#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm."
-
-
-#
-# Modify for your particular situation before removing 
-# the # comment character from the beginning of appropriate lines above.
-# 
-
-
-#############################################################
-#                                                           #
-#               DIGIPEATER PROPERTIES                       #
-#                                                           #
-#############################################################
-
-#
-# For most common situations, use something like this by removing
-# 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.
- 
-# Filtering can be used to limit was is digipeated.
-# For example, only weather weather reports, received on channel 0,
-# will be retransmitted on channel 1.
-#
-
-#FILTER 0 1 t/wn 
-
-
-#############################################################
-#                                                           #
-#               INTERNET GATEWAY                            #
-#                                                           #
-#############################################################
-
-# First you need to specify the name of a Tier 2 server.  
-# The current preferred way is to use one of these regional rotate addresses:
-
-#	noam.aprs2.net 		- for North America
-#	soam.aprs2.net		- for South America
-#	euro.aprs2.net		- for Europe and Africa
-#	asia.aprs2.net 		- for Asia
-#	aunz.aprs2.net		- for Oceania 
-
-#IGSERVER noam.aprs2.net
-
-# You also need to specify your login name and passcode. 
-# Contact the author if you can't figure out how to generate the passcode.
- 
-#IGLOGIN WB2OSZ-5 123456
-
-# That's all you need for a receive only IGate which relays
-# messages from the local radio channel to the global servers.
-
-# Some might want to send an IGate client position directly to a server
-# without sending it over the air and relying on someone else to 
-# forward it to an IGate server.  This is done by using sendto=IG rather
-# than a radio channel number. Overlay R for receive only, T for two way.
-
-#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W 
-#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W 
-
-
-# To relay messages from the Internet to radio, you need to add
-# one more option with the transmit channel number and a VIA path.
-
-#IGTXVIA 0 WIDE1-1
-
-# You might want to apply a filter for what packets will be obtained from the server.
-# Read about filters here:  http://www.aprs-is.net/javaprsfilter.aspx
-# Example, positions and objects within 50 km of my location:
-
-#IGFILTER m/50 
-
-# That is known as a server-side filter.  It is processed by the IGate server.
-# You can also apply local filtering to limit what will be transmitted on the 
-# RF side.  For example, transmit only "messages" on channel 0 and weather 
-# reports on channel 1. 
-
-#FILTER IG 0 t/m
-#FILTER IG 1 t/wn
-
-# 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.
-
-IGTXLIMIT 6 10
-
-
-#############################################################
-#                                                           #
-#               APRStt GATEWAY                              #
-#                                                           #
-#############################################################
-
-#
-# Dire Wolf can receive DTMF (commonly known as Touch Tone)
-# messages and convert them to packet objects.
-#
-# See separate "APRStt-Implementation-Notes" document for details.
-#
-
-#
-# Sample gateway configuration based on:
-#
-#	http://www.aprs.org/aprstt/aprstt-coding24.txt
-#	http://www.aprs.org/aprs-jamboree-2013.html
-#
-
-# Define specific points.
-
-TTPOINT  B01  37^55.37N  81^7.86W  			
-TTPOINT  B7495088  42.605237  -71.34456		
-TTPOINT  B934  42.605237  -71.34456			
-
-TTPOINT B901  42.661279  -71.364452 
-TTPOINT B902  42.660411  -71.364419 
-TTPOINT B903  42.659046  -71.364452 
-TTPOINT B904  42.657578  -71.364602 
-
-
-# For location at given bearing and distance from starting point.
-
-TTVECTOR  B5bbbddd  37^55.37N  81^7.86W  0.01  mi
-
-# For location specified by x, y coordinates.
-
-TTGRID   Byyyxxx    37^50.00N  81^00.00W  37^59.99N  81^09.99W   
-
-# UTM location for Lowell-Dracut-Tyngsborough State Forest.
-
-TTUTM  B6xxxyyy  19T  10  300000  4720000
-
-
-
-# Location for the corral.
-
-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
-#	Others				= dog
-
-TTMACRO  xx1yy  B9xx*AB166*AA2B4C5B3B0A1yy
-TTMACRO  xx2yy  B9xx*AB170*AA3C4C7C3B0A2yy
-TTMACRO  xxyyy  B9xx*AB180*AA3A6C4A0Ayyy
-
-TTMACRO  z  Cz
-
-# Receive on channel 0, Transmit object reports on channel 1 with optional via path.
-
-#TTOBJ 0 1 WIDE1-1
-
-# Advertise gateway position with beacon.
-
-# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway"  
-
-
diff --git a/direwolf.h b/direwolf.h
index 6bfb8d3..958e093 100644
--- a/direwolf.h
+++ b/direwolf.h
@@ -1,161 +1,229 @@
-
-#ifndef DIREWOLF_H
-#define DIREWOLF_H 1
-
-
-
-/*
- * Previously, we could handle only a single audio device.
- * This meant we could have only two radio channels.
- * In version 1.2, we relax this restriction and allow more audio devices.
- * Three is probably adequate for standard version.
- * Larger reasonable numbers should also be fine.
- */
-
-#define MAX_ADEVS 3			
-
-	
-/*
- * Maximum number of radio channels.
- * Note that there could be gaps.
- * Suppose audio device 0 was in mono mode and audio device 1 was stereo.
- * The channels available would be:
- *
- *	ADevice 0:	channel 0
- *	ADevice 1:	left = 2, right = 3
- *
- * TODO1.2:  Look for any places that have
- *		for (ch=0; ch<MAX_CHANS; ch++) ...
- * and make sure they handle undefined channels correctly.
- */
-
-#define MAX_CHANS ((MAX_ADEVS) * 2)
-
-/*
- * Get audio device number for given channel.
- * and first channel for given device.
- */
-
-#define ACHAN2ADEV(n) ((n)>>1)
-#define ADEVFIRSTCHAN(n) ((n) * 2)
-
-/*
- * Maximum number of modems per channel.
- * I called them "subchannels" (in the code) because 
- * it is short and unambiguous.
- * Nothing magic about the number.  Could be larger
- * but CPU demands might be overwhelming.
- */
-
-#define MAX_SUBCHANS 9
-
-
-#if __WIN32__
-#include <windows.h>
-#define SLEEP_SEC(n) Sleep((n)*1000)
-#define SLEEP_MS(n) Sleep(n)
-#else
-#define SLEEP_SEC(n) sleep(n)
-#define SLEEP_MS(n) usleep((n)*1000)
-#endif
-
-
-#if __WIN32__
-#define PTW32_STATIC_LIB
-#include "pthreads/pthread.h"
-#else
-#include <pthread.h>
-#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)
-
-
-
-
-#if __WIN32__
-
-typedef CRITICAL_SECTION dw_mutex_t;
-
-#define dw_mutex_init(x) \
-	InitializeCriticalSection (x)
-
-/* This one waits for lock. */
-
-#define dw_mutex_lock(x) \
-	EnterCriticalSection (x) 
-
-/* Returns non-zero if lock was obtained. */
-
-#define dw_mutex_try_lock(x) \
-	TryEnterCriticalSection (x)
-
-#define dw_mutex_unlock(x) \
-	LeaveCriticalSection (x)
-
-
-#else
-
-typedef pthread_mutex_t dw_mutex_t;
-
-#define dw_mutex_init(x) pthread_mutex_init (x, NULL)
-
-/* this one will wait. */
-
-#define dw_mutex_lock(x) \
-	{	\
-	  int err; \
-	  err = pthread_mutex_lock (x); \
-	  if (err != 0) { \
-	    text_color_set(DW_COLOR_ERROR); \
-	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_lock returned %d", __FILE__, __LINE__, err); \
-	    exit (1); \
-	  } \
-	}
-
-/* This one returns true if lock successful, false if not. */
-/* pthread_mutex_trylock returns 0 for success. */
-
-#define dw_mutex_try_lock(x) \
-	({	\
-	  int err; \
-	  err = pthread_mutex_trylock (x); \
-	  if (err != 0 && err != EBUSY) { \
-	    text_color_set(DW_COLOR_ERROR); \
-	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_trylock returned %d", __FILE__, __LINE__, err); \
-	    exit (1); \
-	  } ; \
-	  ! err; \
-	})
-
-#define dw_mutex_unlock(x) \
-	{	\
-	  int err; \
-	  err = pthread_mutex_unlock (x); \
-	  if (err != 0) { \
-	    text_color_set(DW_COLOR_ERROR); \
-	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_unlock returned %d", __FILE__, __LINE__, err); \
-	    exit (1); \
-	  } \
-	}
-
-
-#endif
-
-
-
-#endif   /* ifndef DIREWOLF_H */
\ No newline at end of file
+
+#ifndef DIREWOLF_H
+#define DIREWOLF_H 1
+
+
+/*
+ * Previously, we could handle only a single audio device.
+ * This meant we could have only two radio channels.
+ * In version 1.2, we relax this restriction and allow more audio devices.
+ * Three is probably adequate for standard version.
+ * Larger reasonable numbers should also be fine.
+ */
+
+#define MAX_ADEVS 3			
+
+	
+/*
+ * Maximum number of radio channels.
+ * Note that there could be gaps.
+ * Suppose audio device 0 was in mono mode and audio device 1 was stereo.
+ * The channels available would be:
+ *
+ *	ADevice 0:	channel 0
+ *	ADevice 1:	left = 2, right = 3
+ *
+ * TODO1.2:  Look for any places that have
+ *		for (ch=0; ch<MAX_CHANS; ch++) ...
+ * and make sure they handle undefined channels correctly.
+ */
+
+#define MAX_CHANS ((MAX_ADEVS) * 2)
+
+/*
+ * Maximum number of rigs.
+ */
+
+#ifdef USE_HAMLIB
+#define MAX_RIGS MAX_CHANS
+#endif
+
+/*
+ * Get audio device number for given channel.
+ * and first channel for given device.
+ */
+
+#define ACHAN2ADEV(n) ((n)>>1)
+#define ADEVFIRSTCHAN(n) ((n) * 2)
+
+/*
+ * Maximum number of modems per channel.
+ * I called them "subchannels" (in the code) because 
+ * it is short and unambiguous.
+ * Nothing magic about the number.  Could be larger
+ * but CPU demands might be overwhelming.
+ */
+
+#define MAX_SUBCHANS 9
+
+/*
+ * Each one of these can have multiple slicers, at
+ * different levels, to compensate for different
+ * amplitudes of the AFSK tones.
+ * Intially used same number as subchannels but
+ * we could probably trim this down a little
+ * without impacting performance.
+ */
+
+#define MAX_SLICERS 9
+
+
+#if __WIN32__
+#include <windows.h>
+#define SLEEP_SEC(n) Sleep((n)*1000)
+#define SLEEP_MS(n) Sleep(n)
+#else
+#define SLEEP_SEC(n) sleep(n)
+#define SLEEP_MS(n) usleep((n)*1000)
+#endif
+
+
+#if __WIN32__
+#define PTW32_STATIC_LIB
+//#include "pthreads/pthread.h"
+#define gmtime_r( _clock, _result ) \
+        ( *(_result) = *gmtime( (_clock) ), \
+          (_result) )
+#else
+#include <pthread.h>
+#endif
+
+
+/* Not sure where to put these. */
+
+/* Prefix with DW_ because /usr/include/gps.h uses a couple of these names. */
+
+#ifndef G_UNKNOWN
+#include "latlong.h"
+#endif
+
+
+#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)
+
+
+
+
+#if __WIN32__
+
+typedef CRITICAL_SECTION dw_mutex_t;
+
+#define dw_mutex_init(x) \
+	InitializeCriticalSection (x)
+
+/* This one waits for lock. */
+
+#define dw_mutex_lock(x) \
+	EnterCriticalSection (x) 
+
+/* Returns non-zero if lock was obtained. */
+
+#define dw_mutex_try_lock(x) \
+	TryEnterCriticalSection (x)
+
+#define dw_mutex_unlock(x) \
+	LeaveCriticalSection (x)
+
+
+#else
+
+typedef pthread_mutex_t dw_mutex_t;
+
+#define dw_mutex_init(x) pthread_mutex_init (x, NULL)
+
+/* this one will wait. */
+
+#define dw_mutex_lock(x) \
+	{	\
+	  int err; \
+	  err = pthread_mutex_lock (x); \
+	  if (err != 0) { \
+	    text_color_set(DW_COLOR_ERROR); \
+	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_lock returned %d", __FILE__, __LINE__, err); \
+	    exit (1); \
+	  } \
+	}
+
+/* This one returns true if lock successful, false if not. */
+/* pthread_mutex_trylock returns 0 for success. */
+
+#define dw_mutex_try_lock(x) \
+	({	\
+	  int err; \
+	  err = pthread_mutex_trylock (x); \
+	  if (err != 0 && err != EBUSY) { \
+	    text_color_set(DW_COLOR_ERROR); \
+	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_trylock returned %d", __FILE__, __LINE__, err); \
+	    exit (1); \
+	  } ; \
+	  ! err; \
+	})
+
+#define dw_mutex_unlock(x) \
+	{	\
+	  int err; \
+	  err = pthread_mutex_unlock (x); \
+	  if (err != 0) { \
+	    text_color_set(DW_COLOR_ERROR); \
+	    dw_printf ("INTERNAL ERROR %s %d pthread_mutex_unlock returned %d", __FILE__, __LINE__, err); \
+	    exit (1); \
+	  } \
+	}
+
+#endif
+
+
+
+/* Platform differences for string functions. */
+
+
+
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+char *strtok_r(char *str, const char *delim, char **saveptr);
+#endif
+
+//#if __WIN32__
+char *strcasestr(const char *S, const char *FIND);
+//#endif
+
+
+#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
+
+// strlcpy and strlcat should be in string.h and the C library.
+
+#else   // Use our own copy
+
+
+#define DEBUG_STRL 1
+
+#if DEBUG_STRL
+
+#define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz,__FILE__,__func__,__LINE__)
+#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz,__FILE__,__func__,__LINE__)
+
+size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line);
+size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line);
+
+#else
+
+#define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz)
+#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz)
+
+size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz);
+size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz);
+
+#endif  /* DEBUG_STRL */
+
+#endif	/* BSD or Apple */
+
+
+#endif   /* ifndef DIREWOLF_H */
diff --git a/direwolf.spec b/direwolf.spec
old mode 100755
new mode 100644
diff --git a/direwolf.txt b/direwolf.txt
index c9f3313..c857739 100644
--- a/direwolf.txt
+++ b/direwolf.txt
@@ -1,532 +1,532 @@
-C#############################################################
-C#                                                           #
-C#               Configuration file for Dire Wolf            #
-C#                                                           #
-L#                   Linux version                           #
-W#                   Windows version                         #
-C#                                                           #
-C#############################################################
-R
-R
-R	The sample config file was getting pretty messy
-R	with the Windows and Linux differences.
-R	It would be a maintenance burden to keep most of
-R	two different versions in sync.
-R	This common source is now used to generate the 
-R	two different variations while having only a single
-R	copy of the common parts.
-R
-R	The first column contains one of the following:
-R
-R		R	remark which is discarded.
-R		C	common to both versions.
-R		W	Windows version only.
-R		L	Linux version only.
-R
-C#
-C# Consult the User Guide for more details on configuration options.
-C#
-C#
-C# These are the most likely settings you might change:
-C#
-C#	(1)   	MYCALL 	-  call sign and SSID for your station.
-C#
-C#			Look for lines starting with MYCALL and 
-C#			change NOCALL to your own.
-C#
-C#	(2)	PBEACON	-  enable position beaconing.
-C#
-C#			Look for lines starting with PBEACON and 
-C#			modify for your call, location, etc.
-C#
-C#	(3)	DIGIPEATER  -  configure digipeating rules.
-C#
-C#			Look for lines starting with DIGIPEATER.
-C#			Most people will probably use the given example.
-C#			Just remove the "#" from the start of the line
-C#			to enable it.
-C#
-C#	(4)	IGSERVER, IGLOGIN  - IGate server and login
-C#
-C#			Configure an IGate client to relay messages between 
-C#			radio and internet servers.
-C#
-C#
-C# The default location is "direwolf.conf" in the current working directory.
-L# On Linux, the user's home directory will also be searched.
-C# An alternate configuration file location can be specified with the "-c" command line option.  
-C#
-C# As you probably guessed by now, # indicates a comment line.
-C#
-C# Remove the # at the beginning of a line if you want to use a sample
-C# configuration that is currently commented out.
-C#
-C# Commands are a keyword followed by parameters.
-C#
-C# Command key words are case insensitive.  i.e. upper and lower case are equivalent.
-C#
-C# Command parameters are generally case sensitive.  i.e. upper and lower case are different.
-C#
-C
-C
-C#############################################################
-C#                                                           #
-C#               FIRST AUDIO DEVICE PROPERTIES               #
-C#               (Channel 0 + 1 if in stereo)                #
-C#                                                           #
-C#############################################################
-C
-C#
-C# Many people will simply use the default sound device.
-C# Some might want to use an alternative device by chosing it here.
-C#
-W# When the Windows version starts up, it displays something like 
-W# this with the available sound devices and capabilities:
-W#
-W#	Available audio input devices for receive (*=selected):
-W#	   *  0: Microphone (C-Media USB Headpho   (channel 2)
-W#	      1: Microphone (Bluetooth SCO Audio
-W#	      2: Microphone (Bluetooth AV Audio)
-W#	 *    3: Microphone (Realtek High Defini   (channels 0 & 1)
-W#	Available audio output devices for transmit (*=selected):
-W#	   *  0: Speakers (C-Media USB Headphone   (channel 2)
-W#	      1: Speakers (Bluetooth SCO Audio)
-W#	      2: Realtek Digital Output(Optical)
-W#	      3: Speakers (Bluetooth AV Audio)
-W#	 *    4: Speakers (Realtek High Definiti   (channels 0 & 1)
-W#	      5: Realtek Digital Output (Realtek
-W#	
-W# Example: To use the microphone and speaker connections on the 
-W# system board, either of these forms can be used:
-W
-W#ADEVICE High
-W#ADEVICE  3 4 
-W
-W
-W# Example: To use the USB Audio, use a command like this with
-W# the input and output device numbers.  (Remove the # comment character.)
-W#ADEVICE USB
-W
-W# The position in the list can change when devices (e.g. USB) are added and removed.
-W# You can also specify devices by using part of the name.
-W# Here is an example of specifying the USB Audio device.
-W# This is case-sensitive.  Upper and lower case are not treated the same.
-W
-W#ADEVICE USB
-W
-W
-L# Linux ALSA is complicated.  See User Guide for discussion.
-L# To use something other than the default, generally use plughw
-L# and a card number reported by "arecord -l" command.  Example:
-L
-L# ADEVICE  plughw:1,0
-L
-L# Starting with version 1.0, you can also use "-" or "stdin" to 
-L# pipe stdout from some other application such as a software defined
-L# radio.  You can also specify "UDP:" and an optional port for input.
-L# Something different must be specified for output.
-L
-W# ADEVICE - 0
-W# ADEVICE UDP:7355 0
-L# ADEVICE - plughw:1,0
-L# ADEVICE UDP:7355 default
-L
-L
-C
-C#
-C# Number of audio channels for this souncard:  1 or 2.
-C#
-C
-CACHANNELS 1
-C#ACHANNELS 2
-C
-C
-C#############################################################
-C#                                                           #
-C#               SECOND AUDIO DEVICE PROPERTIES              #
-C#               (Channel 2 + 3 if in stereo)                #
-C#                                                           #
-C#############################################################
-C
-C#ADEVICE1  ...
-C
-C
-C#############################################################
-C#                                                           #
-C#               THIRD AUDIO DEVICE PROPERTIES               #
-C#               (Channel 4 + 5 if in stereo)                #
-C#                                                           #
-C#############################################################
-C
-C#ADEVICE2  ...
-C
-C
-C#############################################################
-C#                                                           #
-C#               CHANNEL 0 PROPERTIES                        #
-C#                                                           #
-C#############################################################
-C
-CCHANNEL 0
-C
-C#
-C# The following MYCALL, MODEM, PTT, etc. configuration items
-C# apply to the most recent CHANNEL.
-C#
-C
-C#
-C# Station identifier for this channel.
-C# Multiple channels can have the same or different names.
-C#
-C# It can be up to 6 letters and digits with an optional ssid.
-C# The APRS specification requires that it be upper case.
-C#
-C# Example (don't use this unless you are me):  MYCALL	WB2OSZ-5
-C#
-C
-CMYCALL N0CALL
-C
-C#
-C# Pick a suitable modem speed based on your situation.
-C#	1200 	Most common for VHF/UHF.  Default if not specified.
-C#	300	Low speed for HF SSB.
-C#	9600	High speed - Can't use Microphone and Speaker connections.
-C#
-C# In the simplest form, just specify the speed. 
-C# 
-C
-CMODEM 1200
-C#MODEM 300
-C#MODEM 9600
-C
-C#
-C# These are the defaults should be fine for most cases.  In special situations, 
-C# you might want to specify different AFSK tones or the baseband mode which does
-C# not use AFSK.
-C#
-C#MODEM 1200 1200:2200
-C#MODEM 300  1600:1800
-C#MODEM 9600 0:0
-C#
-C#
-C# On HF SSB, you might want to use multiple demodulators on slightly different
-C# frequencies to compensate for stations off frequency.  Here we have 7 different
-C# demodulators at 30 Hz intervals.  This takes a lot of CPU power so you will 
-C# probably need to reduce the audio sampling rate with the /n option.
-C
-C#MODEM 300 1600:1800 7 at 30 /4
-C
-C
-C#
-C# Uncomment line below to enable the DTMF decoder for this channel.
-C#
-C
-C#DTMF
-C
-C# 
-C# If not using a VOX circuit, the transmitter Push to Talk (PTT) 
-C# control is usually wired to a serial port with a suitable interface circuit.  
-C# DON'T connect it directly!
-C#
-C# For the PTT command, specify the device and either RTS or DTR.
-C# RTS or DTR may be preceded by "-" to invert the signal.
-C# Both can be used for interfaces that want them driven with opposite polarity.
-C#
-L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on.
-L#
-C
-C#PTT COM1 RTS
-C#PTT COM1 RTS -DTR
-L#PTT /dev/ttyUSB0 RTS
-C
-L#
-L# On Linux, you can also use general purpose I/O pins if
-L# your system is configured for user access to them. 
-L# This would apply mostly to microprocessor boards, not a regular PC.
-L# See separate Raspberry Pi document for more details.
-L# The number may be preceded by "-" to invert the signal.
-L#
-L
-L#PTT GPIO 25
-L
-C# The Data Carrier Detect (DCD) signal can be sent to the same places
-C# as the PTT signal.  This could be used to light up an LED like a normal TNC.
-C
-C#DCD COM1 -DTR
-L#DCD GPIO 24
-C
-C
-C#############################################################
-C#                                                           #
-C#               CHANNEL 1 PROPERTIES                        #
-C#                                                           #
-C#############################################################
-C
-C#CHANNEL 1
-C
-C#
-C# Specify MYCALL, MODEM, PTT, etc. configuration items for 
-C# CHANNEL 1.   Repeat for any other channels.
-C
-C
-C#############################################################
-C#                                                           #
-C#               TEXT TO SPEECH COMMAND FILE                 #
-C#                                                           #
-C#############################################################
-C
-W#SPEECH dwespeak.bat
-L#SPEECH dwespeak.sh
-C
-C
-C#############################################################
-C#                                                           #
-C#               VIRTUAL TNC SERVER PROPERTIES               #
-C#                                                           #
-C#############################################################
-C
-C#
-C# Dire Wolf acts as a virtual TNC and can communicate with
-C# client applications by different protocols:
-C#
-C#	- the "AGW TCPIP Socket Interface" - default port 8000
-C#	- KISS protocol over TCP socket - default port 8001
-W#	- KISS TNC via serial port
-L#	- KISS TNC via pseudo terminal   (-p command line option)
-C#
-C
-CAGWPORT 8000
-CKISSPORT 8001
-C
-W#
-W# Some applications are designed to operate with only a physical
-W# TNC attached to a serial port.  For these, we provide a virtual serial
-W# port that appears to be connected to a TNC.
-W#
-W# Take a look at the User Guide for instructions to set up
-W# two virtual serial ports named COM3 and COM4 connected by
-W# a null modem.
-W#
-W# Using the  configuration described, Dire Wolf will connect to 
-W# COM3 and the client application will use COM4.
-W#
-W# Uncomment following line to use this feature.
-W
-W#NULLMODEM COM3
-W
-W
-C#
-C# It is sometimes possible to recover frames with a bad FCS.
-C# This applies to all channels.  
-C#
-C#	0  [NONE] - Don't try to repair.
-C#	1  [SINGLE] - Attempt to fix single bit error.  (default)
-C#	2  [DOUBLE] - Also attempt to fix two adjacent bits.
-C#	... see User Guide for more values and in-depth discussion.
-C#
-C
-C#FIX_BITS 0
-C
-C#	
-C#############################################################
-C#                                                           #
-C#               BEACONING PROPERTIES                        #
-C#                                                           #
-C#############################################################
-C
-C
-C#
-C# Beaconing is configured with these two commands:
-C#
-C#	PBEACON		- for a position report (usually yourself)
-C#	OBEACON		- for an object report (usually some other entity)
-C#
-C# Each has a series of keywords and values for options.  
-C# See User Guide for details.
-C#
-C# Example:
-C#
-C# This results in a broadcast once every 10 minutes.
-C# Every half hour, it can travel via two digipeater hops.
-C# The others are kept local.
-C#
-C
-C#PBEACON delay=1  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 
-C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
-C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
-C
-C
-C# With UTM coordinates instead of latitude and longitude.
-C
-C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 
-C
-C
-C#
-C# When the destination field is set to "SPEECH" the information part is
-C# converted to speech rather than transmitted as a data frame.
-C#
-C
-C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm."
-C
-C
-C#
-C# Modify for your particular situation before removing 
-C# the # comment character from the beginning of appropriate lines above.
-C# 
-C
-C
-C#############################################################
-C#                                                           #
-C#               DIGIPEATER PROPERTIES                       #
-C#                                                           #
-C#############################################################
-C
-C#
-C# For most common situations, use something like this by removing
-C# the "#" from the beginning of the line below.  
-C#
-C
-C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE 
-C
-C# See User Guide for more explanation of what this means and how
-C# it can be customized for your particular needs.
-C 
-C# Filtering can be used to limit was is digipeated.
-C# For example, only weather weather reports, received on channel 0,
-C# will be retransmitted on channel 1.
-C#
-C
-C#FILTER 0 1 t/wn 
-C
-C
-C#############################################################
-C#                                                           #
-C#               INTERNET GATEWAY                            #
-C#                                                           #
-C#############################################################
-C
-C# First you need to specify the name of a Tier 2 server.  
-C# The current preferred way is to use one of these regional rotate addresses:
-C
-C#	noam.aprs2.net 		- for North America
-C#	soam.aprs2.net		- for South America
-C#	euro.aprs2.net		- for Europe and Africa
-C#	asia.aprs2.net 		- for Asia
-C#	aunz.aprs2.net		- for Oceania 
-C
-C#IGSERVER noam.aprs2.net
-C
-C# You also need to specify your login name and passcode. 
-C# Contact the author if you can't figure out how to generate the passcode.
-C 
-C#IGLOGIN WB2OSZ-5 123456
-C
-C# That's all you need for a receive only IGate which relays
-C# messages from the local radio channel to the global servers.
-C
-C# Some might want to send an IGate client position directly to a server
-C# without sending it over the air and relying on someone else to 
-C# forward it to an IGate server.  This is done by using sendto=IG rather
-C# than a radio channel number. Overlay R for receive only, T for two way.
-C
-C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W 
-C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W 
-C
-C
-C# To relay messages from the Internet to radio, you need to add
-C# one more option with the transmit channel number and a VIA path.
-C
-C#IGTXVIA 0 WIDE1-1
-C
-C# You might want to apply a filter for what packets will be obtained from the server.
-C# Read about filters here:  http://www.aprs-is.net/javaprsfilter.aspx
-C# Example, positions and objects within 50 km of my location:
-C
-C#IGFILTER m/50 
-C
-C# That is known as a server-side filter.  It is processed by the IGate server.
-C# You can also apply local filtering to limit what will be transmitted on the 
-C# RF side.  For example, transmit only "messages" on channel 0 and weather 
-C# reports on channel 1. 
-C
-C#FILTER IG 0 t/m
-C#FILTER IG 1 t/wn
-C
-C# Finally, we don't want to flood the radio channel.  
-C# The IGate function will limit the number of packets transmitted 
-C# during 1 minute and 5 minute intervals.   If a limit would 
-C# be exceeded, the packet is dropped and message is displayed in red.
-C
-CIGTXLIMIT 6 10
-C
-C
-C#############################################################
-C#                                                           #
-C#               APRStt GATEWAY                              #
-C#                                                           #
-C#############################################################
-C
-C#
-C# Dire Wolf can receive DTMF (commonly known as Touch Tone)
-C# messages and convert them to packet objects.
-C#
-C# See separate "APRStt-Implementation-Notes" document for details.
-C#
-C
-C#
-C# Sample gateway configuration based on:
-C#
-C#	http://www.aprs.org/aprstt/aprstt-coding24.txt
-C#	http://www.aprs.org/aprs-jamboree-2013.html
-C#
-C
-C# Define specific points.
-C
-CTTPOINT  B01  37^55.37N  81^7.86W  			
-CTTPOINT  B7495088  42.605237  -71.34456		
-CTTPOINT  B934  42.605237  -71.34456			
-C
-CTTPOINT B901  42.661279  -71.364452 
-CTTPOINT B902  42.660411  -71.364419 
-CTTPOINT B903  42.659046  -71.364452 
-CTTPOINT B904  42.657578  -71.364602 
-C
-C
-C# For location at given bearing and distance from starting point.
-C
-CTTVECTOR  B5bbbddd  37^55.37N  81^7.86W  0.01  mi
-C
-C# For location specified by x, y coordinates.
-C
-CTTGRID   Byyyxxx    37^50.00N  81^00.00W  37^59.99N  81^09.99W   
-C
-C# UTM location for Lowell-Dracut-Tyngsborough State Forest.
-C
-CTTUTM  B6xxxyyy  19T  10  300000  4720000
-C
-C
-C
-C# Location for the corral.
-C
-CTTCORRAL   37^55.50N  81^7.00W  0^0.02N
-C
-C# Compact messages - Fixed locations xx and object yyy where 
-C#   	Object numbers 100 - 199	= bicycle	
-C#	Object numbers 200 - 299	= fire truck
-C#	Others				= dog
-C
-CTTMACRO  xx1yy  B9xx*AB166*AA2B4C5B3B0A1yy
-CTTMACRO  xx2yy  B9xx*AB170*AA3C4C7C3B0A2yy
-CTTMACRO  xxyyy  B9xx*AB180*AA3A6C4A0Ayyy
-C
-CTTMACRO  z  Cz
-C
-C# Receive on channel 0, Transmit object reports on channel 1 with optional via path.
-C
-C#TTOBJ 0 1 WIDE1-1
-C
-C# Advertise gateway position with beacon.
-C
-C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway"  
-C
-C
+C#############################################################
+C#                                                           #
+C#               Configuration file for Dire Wolf            #
+C#                                                           #
+L#                   Linux version                           #
+W#                   Windows version                         #
+C#                                                           #
+C#############################################################
+R
+R
+R	The sample config file was getting pretty messy
+R	with the Windows and Linux differences.
+R	It would be a maintenance burden to keep most of
+R	two different versions in sync.
+R	This common source is now used to generate the 
+R	two different variations while having only a single
+R	copy of the common parts.
+R
+R	The first column contains one of the following:
+R
+R		R	remark which is discarded.
+R		C	common to both versions.
+R		W	Windows version only.
+R		L	Linux version only.
+R
+C#
+C# Consult the User Guide for more details on configuration options.
+C#
+C#
+C# These are the most likely settings you might change:
+C#
+C#	(1)   	MYCALL 	-  call sign and SSID for your station.
+C#
+C#			Look for lines starting with MYCALL and 
+C#			change NOCALL to your own.
+C#
+C#	(2)	PBEACON	-  enable position beaconing.
+C#
+C#			Look for lines starting with PBEACON and 
+C#			modify for your call, location, etc.
+C#
+C#	(3)	DIGIPEATER  -  configure digipeating rules.
+C#
+C#			Look for lines starting with DIGIPEATER.
+C#			Most people will probably use the given example.
+C#			Just remove the "#" from the start of the line
+C#			to enable it.
+C#
+C#	(4)	IGSERVER, IGLOGIN  - IGate server and login
+C#
+C#			Configure an IGate client to relay messages between 
+C#			radio and internet servers.
+C#
+C#
+C# The default location is "direwolf.conf" in the current working directory.
+L# On Linux, the user's home directory will also be searched.
+C# An alternate configuration file location can be specified with the "-c" command line option.  
+C#
+C# As you probably guessed by now, # indicates a comment line.
+C#
+C# Remove the # at the beginning of a line if you want to use a sample
+C# configuration that is currently commented out.
+C#
+C# Commands are a keyword followed by parameters.
+C#
+C# Command key words are case insensitive.  i.e. upper and lower case are equivalent.
+C#
+C# Command parameters are generally case sensitive.  i.e. upper and lower case are different.
+C#
+C
+C
+C#############################################################
+C#                                                           #
+C#               FIRST AUDIO DEVICE PROPERTIES               #
+C#               (Channel 0 + 1 if in stereo)                #
+C#                                                           #
+C#############################################################
+C
+C#
+C# Many people will simply use the default sound device.
+C# Some might want to use an alternative device by chosing it here.
+C#
+W# When the Windows version starts up, it displays something like 
+W# this with the available sound devices and capabilities:
+W#
+W#	Available audio input devices for receive (*=selected):
+W#	   *  0: Microphone (C-Media USB Headpho   (channel 2)
+W#	      1: Microphone (Bluetooth SCO Audio
+W#	      2: Microphone (Bluetooth AV Audio)
+W#	 *    3: Microphone (Realtek High Defini   (channels 0 & 1)
+W#	Available audio output devices for transmit (*=selected):
+W#	   *  0: Speakers (C-Media USB Headphone   (channel 2)
+W#	      1: Speakers (Bluetooth SCO Audio)
+W#	      2: Realtek Digital Output(Optical)
+W#	      3: Speakers (Bluetooth AV Audio)
+W#	 *    4: Speakers (Realtek High Definiti   (channels 0 & 1)
+W#	      5: Realtek Digital Output (Realtek
+W#	
+W# Example: To use the microphone and speaker connections on the 
+W# system board, either of these forms can be used:
+W
+W#ADEVICE High
+W#ADEVICE  3 4 
+W
+W
+W# Example: To use the USB Audio, use a command like this with
+W# the input and output device numbers.  (Remove the # comment character.)
+W#ADEVICE USB
+W
+W# The position in the list can change when devices (e.g. USB) are added and removed.
+W# You can also specify devices by using part of the name.
+W# Here is an example of specifying the USB Audio device.
+W# This is case-sensitive.  Upper and lower case are not treated the same.
+W
+W#ADEVICE USB
+W
+W
+L# Linux ALSA is complicated.  See User Guide for discussion.
+L# To use something other than the default, generally use plughw
+L# and a card number reported by "arecord -l" command.  Example:
+L
+L# ADEVICE  plughw:1,0
+L
+L# Starting with version 1.0, you can also use "-" or "stdin" to 
+L# pipe stdout from some other application such as a software defined
+L# radio.  You can also specify "UDP:" and an optional port for input.
+L# Something different must be specified for output.
+L
+W# ADEVICE - 0
+W# ADEVICE UDP:7355 0
+L# ADEVICE - plughw:1,0
+L# ADEVICE UDP:7355 default
+L
+L
+C
+C#
+C# Number of audio channels for this souncard:  1 or 2.
+C#
+C
+CACHANNELS 1
+C#ACHANNELS 2
+C
+C
+C#############################################################
+C#                                                           #
+C#               SECOND AUDIO DEVICE PROPERTIES              #
+C#               (Channel 2 + 3 if in stereo)                #
+C#                                                           #
+C#############################################################
+C
+C#ADEVICE1  ...
+C
+C
+C#############################################################
+C#                                                           #
+C#               THIRD AUDIO DEVICE PROPERTIES               #
+C#               (Channel 4 + 5 if in stereo)                #
+C#                                                           #
+C#############################################################
+C
+C#ADEVICE2  ...
+C
+C
+C#############################################################
+C#                                                           #
+C#               CHANNEL 0 PROPERTIES                        #
+C#                                                           #
+C#############################################################
+C
+CCHANNEL 0
+C
+C#
+C# The following MYCALL, MODEM, PTT, etc. configuration items
+C# apply to the most recent CHANNEL.
+C#
+C
+C#
+C# Station identifier for this channel.
+C# Multiple channels can have the same or different names.
+C#
+C# It can be up to 6 letters and digits with an optional ssid.
+C# The APRS specification requires that it be upper case.
+C#
+C# Example (don't use this unless you are me):  MYCALL	WB2OSZ-5
+C#
+C
+CMYCALL N0CALL
+C
+C#
+C# Pick a suitable modem speed based on your situation.
+C#	1200 	Most common for VHF/UHF.  Default if not specified.
+C#	300	Low speed for HF SSB.
+C#	9600	High speed - Can't use Microphone and Speaker connections.
+C#
+C# In the simplest form, just specify the speed. 
+C# 
+C
+CMODEM 1200
+C#MODEM 300
+C#MODEM 9600
+C
+C#
+C# These are the defaults should be fine for most cases.  In special situations, 
+C# you might want to specify different AFSK tones or the baseband mode which does
+C# not use AFSK.
+C#
+C#MODEM 1200 1200:2200
+C#MODEM 300  1600:1800
+C#MODEM 9600 0:0
+C#
+C#
+C# On HF SSB, you might want to use multiple demodulators on slightly different
+C# frequencies to compensate for stations off frequency.  Here we have 7 different
+C# demodulators at 30 Hz intervals.  This takes a lot of CPU power so you will 
+C# probably need to reduce the audio sampling rate with the /n option.
+C
+C#MODEM 300 1600:1800 7 at 30 /4
+C
+C
+C#
+C# Uncomment line below to enable the DTMF decoder for this channel.
+C#
+C
+C#DTMF
+C
+C# 
+C# If not using a VOX circuit, the transmitter Push to Talk (PTT) 
+C# control is usually wired to a serial port with a suitable interface circuit.  
+C# DON'T connect it directly!
+C#
+C# For the PTT command, specify the device and either RTS or DTR.
+C# RTS or DTR may be preceded by "-" to invert the signal.
+C# Both can be used for interfaces that want them driven with opposite polarity.
+C#
+L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on.
+L#
+C
+C#PTT COM1 RTS
+C#PTT COM1 RTS -DTR
+L#PTT /dev/ttyUSB0 RTS
+C
+L#
+L# On Linux, you can also use general purpose I/O pins if
+L# your system is configured for user access to them. 
+L# This would apply mostly to microprocessor boards, not a regular PC.
+L# See separate Raspberry Pi document for more details.
+L# The number may be preceded by "-" to invert the signal.
+L#
+L
+L#PTT GPIO 25
+L
+C# The Data Carrier Detect (DCD) signal can be sent to the same places
+C# as the PTT signal.  This could be used to light up an LED like a normal TNC.
+C
+C#DCD COM1 -DTR
+L#DCD GPIO 24
+C
+C
+C#############################################################
+C#                                                           #
+C#               CHANNEL 1 PROPERTIES                        #
+C#                                                           #
+C#############################################################
+C
+C#CHANNEL 1
+C
+C#
+C# Specify MYCALL, MODEM, PTT, etc. configuration items for 
+C# CHANNEL 1.   Repeat for any other channels.
+C
+C
+C#############################################################
+C#                                                           #
+C#               TEXT TO SPEECH COMMAND FILE                 #
+C#                                                           #
+C#############################################################
+C
+W#SPEECH dwespeak.bat
+L#SPEECH dwespeak.sh
+C
+C
+C#############################################################
+C#                                                           #
+C#               VIRTUAL TNC SERVER PROPERTIES               #
+C#                                                           #
+C#############################################################
+C
+C#
+C# Dire Wolf acts as a virtual TNC and can communicate with
+C# client applications by different protocols:
+C#
+C#	- the "AGW TCPIP Socket Interface" - default port 8000
+C#	- KISS protocol over TCP socket - default port 8001
+W#	- KISS TNC via serial port
+L#	- KISS TNC via pseudo terminal   (-p command line option)
+C#
+C
+CAGWPORT 8000
+CKISSPORT 8001
+C
+W#
+W# Some applications are designed to operate with only a physical
+W# TNC attached to a serial port.  For these, we provide a virtual serial
+W# port that appears to be connected to a TNC.
+W#
+W# Take a look at the User Guide for instructions to set up
+W# two virtual serial ports named COM3 and COM4 connected by
+W# a null modem.
+W#
+W# Using the  configuration described, Dire Wolf will connect to 
+W# COM3 and the client application will use COM4.
+W#
+W# Uncomment following line to use this feature.
+W
+W#NULLMODEM COM3
+W
+W
+C#
+C# It is sometimes possible to recover frames with a bad FCS.
+C# This applies to all channels.  
+C#
+C#	0  [NONE] - Don't try to repair.
+C#	1  [SINGLE] - Attempt to fix single bit error.  (default)
+C#	2  [DOUBLE] - Also attempt to fix two adjacent bits.
+C#	... see User Guide for more values and in-depth discussion.
+C#
+C
+C#FIX_BITS 0
+C
+C#	
+C#############################################################
+C#                                                           #
+C#               BEACONING PROPERTIES                        #
+C#                                                           #
+C#############################################################
+C
+C
+C#
+C# Beaconing is configured with these two commands:
+C#
+C#	PBEACON		- for a position report (usually yourself)
+C#	OBEACON		- for an object report (usually some other entity)
+C#
+C# Each has a series of keywords and values for options.  
+C# See User Guide for details.
+C#
+C# Example:
+C#
+C# This results in a broadcast once every 10 minutes.
+C# Every half hour, it can travel via two digipeater hops.
+C# The others are kept local.
+C#
+C
+C#PBEACON delay=1  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 
+C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
+C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
+C
+C
+C# With UTM coordinates instead of latitude and longitude.
+C
+C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 
+C
+C
+C#
+C# When the destination field is set to "SPEECH" the information part is
+C# converted to speech rather than transmitted as a data frame.
+C#
+C
+C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm."
+C
+C
+C#
+C# Modify for your particular situation before removing 
+C# the # comment character from the beginning of appropriate lines above.
+C# 
+C
+C
+C#############################################################
+C#                                                           #
+C#               DIGIPEATER PROPERTIES                       #
+C#                                                           #
+C#############################################################
+C
+C#
+C# For most common situations, use something like this by removing
+C# the "#" from the beginning of the line below.  
+C#
+C
+C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE 
+C
+C# See User Guide for more explanation of what this means and how
+C# it can be customized for your particular needs.
+C 
+C# Filtering can be used to limit was is digipeated.
+C# For example, only weather weather reports, received on channel 0,
+C# will be retransmitted on channel 1.
+C#
+C
+C#FILTER 0 1 t/wn 
+C
+C
+C#############################################################
+C#                                                           #
+C#               INTERNET GATEWAY                            #
+C#                                                           #
+C#############################################################
+C
+C# First you need to specify the name of a Tier 2 server.  
+C# The current preferred way is to use one of these regional rotate addresses:
+C
+C#	noam.aprs2.net 		- for North America
+C#	soam.aprs2.net		- for South America
+C#	euro.aprs2.net		- for Europe and Africa
+C#	asia.aprs2.net 		- for Asia
+C#	aunz.aprs2.net		- for Oceania 
+C
+C#IGSERVER noam.aprs2.net
+C
+C# You also need to specify your login name and passcode. 
+C# Contact the author if you can't figure out how to generate the passcode.
+C 
+C#IGLOGIN WB2OSZ-5 123456
+C
+C# That's all you need for a receive only IGate which relays
+C# messages from the local radio channel to the global servers.
+C
+C# Some might want to send an IGate client position directly to a server
+C# without sending it over the air and relying on someone else to 
+C# forward it to an IGate server.  This is done by using sendto=IG rather
+C# than a radio channel number. Overlay R for receive only, T for two way.
+C
+C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W 
+C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W 
+C
+C
+C# To relay messages from the Internet to radio, you need to add
+C# one more option with the transmit channel number and a VIA path.
+C
+C#IGTXVIA 0 WIDE1-1
+C
+C# You might want to apply a filter for what packets will be obtained from the server.
+C# Read about filters here:  http://www.aprs-is.net/javaprsfilter.aspx
+C# Example, positions and objects within 50 km of my location:
+C
+C#IGFILTER m/50 
+C
+C# That is known as a server-side filter.  It is processed by the IGate server.
+C# You can also apply local filtering to limit what will be transmitted on the 
+C# RF side.  For example, transmit only "messages" on channel 0 and weather 
+C# reports on channel 1. 
+C
+C#FILTER IG 0 t/m
+C#FILTER IG 1 t/wn
+C
+C# Finally, we don't want to flood the radio channel.  
+C# The IGate function will limit the number of packets transmitted 
+C# during 1 minute and 5 minute intervals.   If a limit would 
+C# be exceeded, the packet is dropped and message is displayed in red.
+C
+CIGTXLIMIT 6 10
+C
+C
+C#############################################################
+C#                                                           #
+C#               APRStt GATEWAY                              #
+C#                                                           #
+C#############################################################
+C
+C#
+C# Dire Wolf can receive DTMF (commonly known as Touch Tone)
+C# messages and convert them to packet objects.
+C#
+C# See separate "APRStt-Implementation-Notes" document for details.
+C#
+C
+C#
+C# Sample gateway configuration based on:
+C#
+C#	http://www.aprs.org/aprstt/aprstt-coding24.txt
+C#	http://www.aprs.org/aprs-jamboree-2013.html
+C#
+C
+C# Define specific points.
+C
+CTTPOINT  B01  37^55.37N  81^7.86W  			
+CTTPOINT  B7495088  42.605237  -71.34456		
+CTTPOINT  B934  42.605237  -71.34456			
+C
+CTTPOINT B901  42.661279  -71.364452 
+CTTPOINT B902  42.660411  -71.364419 
+CTTPOINT B903  42.659046  -71.364452 
+CTTPOINT B904  42.657578  -71.364602 
+C
+C
+C# For location at given bearing and distance from starting point.
+C
+CTTVECTOR  B5bbbddd  37^55.37N  81^7.86W  0.01  mi
+C
+C# For location specified by x, y coordinates.
+C
+CTTGRID   Byyyxxx    37^50.00N  81^00.00W  37^59.99N  81^09.99W   
+C
+C# UTM location for Lowell-Dracut-Tyngsborough State Forest.
+C
+CTTUTM  B6xxxyyy  19T  10  300000  4720000
+C
+C
+C
+C# Location for the corral.
+C
+CTTCORRAL   37^55.50N  81^7.00W  0^0.02N
+C
+C# Compact messages - Fixed locations xx and object yyy where 
+C#   	Object numbers 100 - 199	= bicycle	
+C#	Object numbers 200 - 299	= fire truck
+C#	Others				= dog
+C
+CTTMACRO  xx1yy  B9xx*AB166*AA2B4C5B3B0A1yy
+CTTMACRO  xx2yy  B9xx*AB170*AA3C4C7C3B0A2yy
+CTTMACRO  xxyyy  B9xx*AB180*AA3A6C4A0Ayyy
+C
+CTTMACRO  z  Cz
+C
+C# Receive on channel 0, Transmit object reports on channel 1 with optional via path.
+C
+C#TTOBJ 0 1 WIDE1-1
+C
+C# Advertise gateway position with beacon.
+C
+C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway"  
+C
+C
diff --git a/dlq.c b/dlq.c
index 5c4e334..06157c8 100644
--- a/dlq.c
+++ b/dlq.c
@@ -1,630 +1,646 @@
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      dlq.c
- *
- * Purpose:   	Received frame queue.
- *
- * Description: In previous versions, the main thread read from the
- *		audio device and performed the receive demodulation/decoding.
- *		In version 1.2 we now have a seprate receive thread
- *		for each audio device.  This queue is used to collect
- *		received frames from all channels and process them
- *		serially.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "dlq.h"
-#include "dedupe.h"
-
-
-/* The queue is a linked list of these. */
-
-struct dlq_item_s {
-
-	struct dlq_item_s *nextp;	/* Next item in queue. */
-
-	dlq_type_t type;		/* Type of item. */
-					/* Only received frames at this time. */
-
-	int chan;			/* Radio channel of origin. */
-
-	int subchan;			/* Winning "subchannel" when using multiple */
-					/* decoders on one channel.  */
-					/* Special case, -1 means DTMF decoder. */
-					/* Maybe we should have a different type in this case? */
-
-	packet_t pp;			/* Pointer to frame structure. */
-
-	alevel_t alevel;			/* Audio level. */
-
-	retry_t retries;		/* Effort expended to get a valid CRC. */
-
-	char spectrum[MAX_SUBCHANS+1];	/* "Spectrum" display for multi-decoders. */
-
-};
-
-
-static struct dlq_item_s *queue_head = NULL;	/* Head of linked list for queue. */
-
-#if __WIN32__
-
-// TODO1.2: use dw_mutex_t
-
-static CRITICAL_SECTION dlq_cs;			/* Critical section for updating queues. */
-
-static HANDLE wake_up_event;			/* Notify received packet processing thread when queue not empty. */
-
-#else
-
-static pthread_mutex_t dlq_mutex;		/* Critical section for updating queues. */
-
-static pthread_cond_t wake_up_cond;		/* Notify received packet processing thread when queue not empty. */
-
-static pthread_mutex_t wake_up_mutex;		/* Required by cond_wait. */
-
-static int recv_thread_is_waiting = 0;
-
-#endif
-
-static int dlq_is_empty (void);
-
-static int was_init = 0;			/* was initialization performed? */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dlq_init
- *
- * Purpose:     Initialize the queue.
- *
- * Inputs:	None.
- *
- * Outputs:	
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *--------------------------------------------------------------------*/
-
-
-void dlq_init (void)
-{
-	int c, p;
-	int err;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_init ( )\n");
-#endif
-
-	queue_head = NULL;
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_init: pthread_mutex_init...\n");
-#endif
-
-#if __WIN32__
-	InitializeCriticalSection (&dlq_cs);
-#else
-	err = pthread_mutex_init (&wake_up_mutex, NULL);
-	err = pthread_mutex_init (&dlq_mutex, NULL);
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_init: pthread_mutex_init err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-#endif
-
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_init: pthread_cond_init...\n");
-#endif
-
-#if __WIN32__
-
-	wake_up_event = CreateEvent (NULL, 0, 0, NULL);
-
-	if (wake_up_event == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_init: pthread_cond_init: can't create receive wake up event");
-	  exit (1);
-	}
-
-#else
-	err = pthread_cond_init (&wake_up_cond, NULL);
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_init: pthread_cond_init returns %d\n", err);
-#endif
-
-
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_init: pthread_cond_init err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-
-	recv_thread_is_waiting = 0;
-#endif
-
-	was_init = 1;
-
-} /* end dlq_init */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dlq_append
- *
- * Purpose:     Add a packet to the end of the specified receive queue.
- *
- * Inputs:	type	- One of the following:
- *
- *				DLQ_REC_FRAME - Frame received from radio.
- *
- *		chan	- Channel, 0 is first.
- *
- *		subchan	- Which modem caught it.  
- *			  Special case -1 for APRStt gateway.
- *
- *		pp	- Address of packet object.
- *				Caller should NOT make any references to
- *				it after this point because it could
- *				be deleted at any time.
- *
- *		alevel	- Audio level, range of 0 - 100.
- *				(Special case, use negative to skip
- *				 display of audio level line.
- *				 Use -2 to indicate DTMF message.)
- *
- *		retries	- Level of bit correction used.
- *
- *		spectrum - Display of how well multiple decoders did.
- *
- *
- * Outputs:	Information is appended to queue.
- *
- * Description:	Add item to end of linked list.
- *		Signal the receive processing thread if the queue was formerly empty.
- *
- * IMPORTANT!	Don't make an further references to the packet object after
- *		giving it to dlq_append.
- *
- *--------------------------------------------------------------------*/
-
-void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
-{
-
-	struct dlq_item_s *pnew;
-	struct dlq_item_s *plast;
-	int err;
-	int queue_length = 0;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_append (type=%d, chan=%d, pp=%p, ...)\n", type, chan, pp);
-#endif
-
-	if ( ! was_init) {
-	  dlq_init ();
-	}
-
-#if AX25MEMDEBUG
-
-	if (ax25memdebug_get()) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("dlq_append (type=%d, chan=%d.%d, seq=%d, ...)\n", type, chan, subchan, ax25memdebug_seq(pp));
-	}
-#endif
-
-/* Allocate a new queue item. */
-
-	pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
-
-	pnew->nextp = NULL;
-	pnew->type = type;
-	pnew->chan = chan;
-	pnew->subchan = subchan;
-	pnew->pp = pp;
-	pnew->alevel = alevel;
-	pnew->retries = retries;
-	if (spectrum == NULL) 
-	  strcpy(pnew->spectrum, "");
-	else
-	  strcpy(pnew->spectrum, spectrum);
-
-#if DEBUG1
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_append: enter critical section\n");
-#endif
-#if __WIN32__
-	EnterCriticalSection (&dlq_cs);
-#else
-	err = pthread_mutex_lock (&dlq_mutex);
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_append: pthread_mutex_lock err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-#endif
-
-	if (queue_head == NULL) {
-	  queue_head = pnew;
-	  queue_length = 1;
-	}
-	else {
-	  queue_length = 2;	/* head + new one */
-	  plast = queue_head;
-	  while (plast->nextp != NULL) {
-	    plast = plast->nextp;
-	    queue_length++;
-	  }
-	  plast->nextp = pnew;
-	}
-
-
-#if __WIN32__ 
-	LeaveCriticalSection (&dlq_cs);
-#else
-	err = pthread_mutex_unlock (&dlq_mutex);
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_append: pthread_mutex_unlock err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-#endif
-#if DEBUG1
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_append: left critical section\n");
-	dw_printf ("dlq_append (): about to wake up recv processing thread.\n");
-#endif
-
-
-/*
- * Bug:  June 2015, version 1.2
- *
- * It has long been known that we will eventually block trying to write to a 
- * pseudo terminal if nothing is reading from the other end.  There is even 
- * a warning at start up time:
- *
- *	Virtual KISS TNC is available on /dev/pts/2
- *	WARNING - Dire Wolf will hang eventually if nothing is reading from it.
- *	Created symlink /tmp/kisstnc -> /dev/pts/2
- *
- * In earlier versions, where the audio input and demodulation was in the main 
- * thread, that would stop and it was pretty obvious something was wrong.
- * In version 1.2, the audio in / demodulating was moved to a device specific 
- * thread.  Packet objects are appended to this queue.
- *
- * The main thread should wake up and process them which includes printing and
- * forwarding to clients over multiple protocols and transport methods.
- * Just before the 1.2 release someone reported a memory leak which only showed
- * up after about 20 hours.  It happened to be on a Cubie Board 2, which shouldn't
- * make a difference unless there was some operating system difference.
- * (cubieez 2.0 is based on Debian wheezy, just like Raspian.)
- *
- * The debug output revealed:
- *
- *	It was using AX.25 for Linux (not APRS).
- *	The pseudo terminal KISS interface was being used.
- *	Transmitting was continuing fine.  (So something must be writing to the other end.)
- *	Frames were being received and appended to this queue.
- *	They were not coming out of the queue.
- *
- * My theory is that writing to the the pseudo terminal is blocking so the 
- * main thread is stopped.   It's not taking anything from this queue and we detect
- * it as a memory leak.  
- *
- * Add a new check here and complain if the queue is growing too large.
- * That will get us a step closer to the root cause.  
- * This has been documented in the User Guide and the CHANGES.txt file which is
- * a minimal version of Release Notes.
- * The proper fix will be somehow avoiding or detecting the pseudo terminal filling up
- * and blocking on a write.
- */
-
-	if (queue_length > 10) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Received frame queue is out of control. Length=%d.\n", queue_length);
-	  dw_printf ("Reader thread is probably frozen.\n");
-	  dw_printf ("This can be caused by using a pseudo terminal (direwolf -p) where another\n");
-	  dw_printf ("application is not reading the frames from the other side.\n");
-	}
-
-
-
-#if __WIN32__
-	SetEvent (wake_up_event);
-#else
-	if (recv_thread_is_waiting) {
-
-	  err = pthread_mutex_lock (&wake_up_mutex);
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("dlq_append: pthread_mutex_lock wu err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  err = pthread_cond_signal (&wake_up_cond);
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("dlq_append: pthread_cond_signal err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  err = pthread_mutex_unlock (&wake_up_mutex);
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("dlq_append: pthread_mutex_unlock wu err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-	}
-#endif
-
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dlq_wait_while_empty
- *
- * Purpose:     Sleep while the received data queue is empty rather than
- *		polling periodically.
- *
- * Inputs:	None.
- *		
- *--------------------------------------------------------------------*/
-
-
-void dlq_wait_while_empty (void)
-{
-	int err;
-
-
-#if DEBUG1
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_wait_while_empty () \n");
-#endif
-
-	if ( ! was_init) {
-	  dlq_init ();
-	}
-
-
-	if (queue_head == NULL) {
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("dlq_wait_while_empty (): prepare to SLEEP - about to call cond wait\n");
-#endif
-
-
-#if __WIN32__
-	  WaitForSingleObject (wake_up_event, INFINITE);
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("dlq_wait_while_empty (): returned from wait\n");
-#endif
-
-#else
-	  err = pthread_mutex_lock (&wake_up_mutex);
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("dlq_wait_while_empty: pthread_mutex_lock wu err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  recv_thread_is_waiting = 1;
-	  err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex);
-	  recv_thread_is_waiting = 0;
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("dlq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err);
-#endif
-
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("dlq_wait_while_empty: pthread_cond_wait err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  err = pthread_mutex_unlock (&wake_up_mutex);
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("dlq_wait_while_empty: pthread_mutex_unlock wu err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-#endif
-	}
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_wait_while_empty () returns\n");
-#endif
-
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dlq_remove
- *
- * Purpose:     Remove an item from the head of the queue.
- *
- * Inputs:	None.
- *
- * Outputs:	type		- type of queue entry.
- *
- *		chan		- channel of received frame.
- *		subchan		- which modem caught it.
- *
- *		pp		- pointer to packet object when type is DLQ_REC_FRAME.
- *				   Caller should destroy it with ax25_delete when finished with it.
- *
- * Returns:	1 for success.
- *		0 if queue is empty.  
- *
- *--------------------------------------------------------------------*/
-
-int dlq_remove (dlq_type_t *type, int *chan, int *subchan, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum)
-{
-
-	struct dlq_item_s *phead;
-	int result;
-	int err;
-
-#if DEBUG1
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_remove() enter critical section\n");
-#endif
-
-	if ( ! was_init) {
-	  dlq_init ();
-	}
-
-#if __WIN32__
-	EnterCriticalSection (&dlq_cs);
-#else
-	err = pthread_mutex_lock (&dlq_mutex);
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_remove: pthread_mutex_lock err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-#endif
-
-	if (queue_head == NULL) {
-
-	  *type = -1;
-	  *chan = -1;
-	  *subchan = -1;
-	  *pp = NULL;
-
-	  memset (alevel, 0xff, sizeof(*alevel));
-
-	  *retries = -1;
-	  strcpy(spectrum,"");
-	  result = 0;
-	}
-	else {
-
-	  phead = queue_head;
-	  queue_head = queue_head->nextp;
-
-	  *type = phead->type;
-	  *chan = phead->chan;
-	  *subchan = phead->subchan;
-	  *pp = phead->pp;
-	  *alevel = phead->alevel;
-	  *retries = phead->retries;
-	  strcpy (spectrum, phead->spectrum);
-	  result = 1;
-	}
-	 
-#if __WIN32__
-	LeaveCriticalSection (&dlq_cs);
-#else
-	err = pthread_mutex_unlock (&dlq_mutex);
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("dlq_remove: pthread_mutex_unlock err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-#endif
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dlq_remove()  returns type=%d, chan=%d\n", (int)(*type), *chan);
-#endif
-
-#if AX25MEMDEBUG
-
-	if (ax25memdebug_get() && result) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", *type, *chan, *subchan, ax25memdebug_seq(*pp));
-	}
-#endif
-	if (result) {
-	  free (phead);
-	}
-
-	return (result);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dlq_is_empty
- *
- * Purpose:     Test whether queue is empty.
- *
- * Inputs:	None 
- *
- * Returns:	True if nothing in the queue.	
- *
- *--------------------------------------------------------------------*/
-
-#if 0
-static int dlq_is_empty (void)
-{
-	if (queue_head == NULL) {
-	  return (1);
-	}
-	return (0);
-
-} /* end dlq_is_empty */
-#endif
-
-/* end dlq.c */
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dlq.c
+ *
+ * Purpose:   	Received frame queue.
+ *
+ * Description: In previous versions, the main thread read from the
+ *		audio device and performed the receive demodulation/decoding.
+ *		In version 1.2 we now have a seprate receive thread
+ *		for each audio device.  This queue is used to collect
+ *		received frames from all channels and process them
+ *		serially.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "dlq.h"
+#include "dedupe.h"
+
+
+/* The queue is a linked list of these. */
+
+struct dlq_item_s {
+
+	struct dlq_item_s *nextp;	/* Next item in queue. */
+
+	dlq_type_t type;		/* Type of item. */
+					/* Only received frames at this time. */
+
+	int chan;			/* Radio channel of origin. */
+
+	int subchan;			/* Winning "subchannel" when using multiple */
+					/* decoders on one channel.  */
+					/* Special case, -1 means DTMF decoder. */
+					/* Maybe we should have a different type in this case? */
+
+	int slice;			/* Winning slicer. */
+
+	packet_t pp;			/* Pointer to frame structure. */
+
+	alevel_t alevel;		/* Audio level. */
+
+	retry_t retries;		/* Effort expended to get a valid CRC. */
+
+	char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];	/* "Spectrum" display for multi-decoders. */
+};
+
+
+static struct dlq_item_s *queue_head = NULL;	/* Head of linked list for queue. */
+
+#if __WIN32__
+
+// TODO1.2: use dw_mutex_t
+
+static CRITICAL_SECTION dlq_cs;			/* Critical section for updating queues. */
+
+static HANDLE wake_up_event;			/* Notify received packet processing thread when queue not empty. */
+
+#else
+
+static pthread_mutex_t dlq_mutex;		/* Critical section for updating queues. */
+
+static pthread_cond_t wake_up_cond;		/* Notify received packet processing thread when queue not empty. */
+
+static pthread_mutex_t wake_up_mutex;		/* Required by cond_wait. */
+
+static int recv_thread_is_waiting = 0;
+
+#endif
+
+static int dlq_is_empty (void);
+
+static int was_init = 0;			/* was initialization performed? */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dlq_init
+ *
+ * Purpose:     Initialize the queue.
+ *
+ * Inputs:	None.
+ *
+ * Outputs:	
+ *
+ * Description:	Initialize the queue to be empty and set up other
+ *		mechanisms for sharing it between different threads.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void dlq_init (void)
+{
+	int c, p;
+	int err;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_init ( )\n");
+#endif
+
+	queue_head = NULL;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_init: pthread_mutex_init...\n");
+#endif
+
+#if __WIN32__
+	InitializeCriticalSection (&dlq_cs);
+#else
+	err = pthread_mutex_init (&wake_up_mutex, NULL);
+	err = pthread_mutex_init (&dlq_mutex, NULL);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_init: pthread_mutex_init err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+#endif
+
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_init: pthread_cond_init...\n");
+#endif
+
+#if __WIN32__
+
+	wake_up_event = CreateEvent (NULL, 0, 0, NULL);
+
+	if (wake_up_event == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_init: pthread_cond_init: can't create receive wake up event");
+	  exit (1);
+	}
+
+#else
+	err = pthread_cond_init (&wake_up_cond, NULL);
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_init: pthread_cond_init returns %d\n", err);
+#endif
+
+
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_init: pthread_cond_init err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+
+	recv_thread_is_waiting = 0;
+#endif
+
+	was_init = 1;
+
+} /* end dlq_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dlq_append
+ *
+ * Purpose:     Add a packet to the end of the specified receive queue.
+ *
+ * Inputs:	type	- One of the following:
+ *
+ *				DLQ_REC_FRAME - Frame received from radio.
+ *
+ *		chan	- Channel, 0 is first.
+ *
+ *		subchan	- Which modem caught it.  
+ *			  Special case -1 for APRStt gateway.
+ *
+ *		slice	- Which slice we picked.
+ *
+ *		pp	- Address of packet object.
+ *				Caller should NOT make any references to
+ *				it after this point because it could
+ *				be deleted at any time.
+ *
+ *		alevel	- Audio level, range of 0 - 100.
+ *				(Special case, use negative to skip
+ *				 display of audio level line.
+ *				 Use -2 to indicate DTMF message.)
+ *
+ *		retries	- Level of bit correction used.
+ *
+ *		spectrum - Display of how well multiple decoders did.
+ *
+ *
+ * Outputs:	Information is appended to queue.
+ *
+ * Description:	Add item to end of linked list.
+ *		Signal the receive processing thread if the queue was formerly empty.
+ *
+ * IMPORTANT!	Don't make an further references to the packet object after
+ *		giving it to dlq_append.
+ *
+ *--------------------------------------------------------------------*/
+
+void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
+{
+
+	struct dlq_item_s *pnew;
+	struct dlq_item_s *plast;
+	int err;
+	int queue_length = 0;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_append (type=%d, chan=%d, pp=%p, ...)\n", type, chan, pp);
+#endif
+
+	if ( ! was_init) {
+	  dlq_init ();
+	}
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+	if (pp == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("INTERNAL ERROR:  dlq_append NULL packet pointer. Please report this!\n");
+	  return;
+	}
+
+#if AX25MEMDEBUG
+
+	if (ax25memdebug_get()) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dlq_append (type=%d, chan=%d.%d, seq=%d, ...)\n", type, chan, subchan, ax25memdebug_seq(pp));
+	}
+#endif
+
+/* Allocate a new queue item. */
+
+	pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+
+	pnew->nextp = NULL;
+	pnew->type = type;
+	pnew->chan = chan;
+	pnew->slice = slice;
+	pnew->subchan = subchan;
+	pnew->pp = pp;
+	pnew->alevel = alevel;
+	pnew->retries = retries;
+	if (spectrum == NULL) 
+	  strlcpy(pnew->spectrum, "", sizeof(pnew->spectrum));
+	else
+	  strlcpy(pnew->spectrum, spectrum, sizeof(pnew->spectrum));
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_append: enter critical section\n");
+#endif
+#if __WIN32__
+	EnterCriticalSection (&dlq_cs);
+#else
+	err = pthread_mutex_lock (&dlq_mutex);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_append: pthread_mutex_lock err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+#endif
+
+	if (queue_head == NULL) {
+	  queue_head = pnew;
+	  queue_length = 1;
+	}
+	else {
+	  queue_length = 2;	/* head + new one */
+	  plast = queue_head;
+	  while (plast->nextp != NULL) {
+	    plast = plast->nextp;
+	    queue_length++;
+	  }
+	  plast->nextp = pnew;
+	}
+
+
+#if __WIN32__ 
+	LeaveCriticalSection (&dlq_cs);
+#else
+	err = pthread_mutex_unlock (&dlq_mutex);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_append: pthread_mutex_unlock err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+#endif
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_append: left critical section\n");
+	dw_printf ("dlq_append (): about to wake up recv processing thread.\n");
+#endif
+
+
+/*
+ * Bug:  June 2015, version 1.2
+ *
+ * It has long been known that we will eventually block trying to write to a 
+ * pseudo terminal if nothing is reading from the other end.  There is even 
+ * a warning at start up time:
+ *
+ *	Virtual KISS TNC is available on /dev/pts/2
+ *	WARNING - Dire Wolf will hang eventually if nothing is reading from it.
+ *	Created symlink /tmp/kisstnc -> /dev/pts/2
+ *
+ * In earlier versions, where the audio input and demodulation was in the main 
+ * thread, that would stop and it was pretty obvious something was wrong.
+ * In version 1.2, the audio in / demodulating was moved to a device specific 
+ * thread.  Packet objects are appended to this queue.
+ *
+ * The main thread should wake up and process them which includes printing and
+ * forwarding to clients over multiple protocols and transport methods.
+ * Just before the 1.2 release someone reported a memory leak which only showed
+ * up after about 20 hours.  It happened to be on a Cubie Board 2, which shouldn't
+ * make a difference unless there was some operating system difference.
+ * (cubieez 2.0 is based on Debian wheezy, just like Raspian.)
+ *
+ * The debug output revealed:
+ *
+ *	It was using AX.25 for Linux (not APRS).
+ *	The pseudo terminal KISS interface was being used.
+ *	Transmitting was continuing fine.  (So something must be writing to the other end.)
+ *	Frames were being received and appended to this queue.
+ *	They were not coming out of the queue.
+ *
+ * My theory is that writing to the the pseudo terminal is blocking so the 
+ * main thread is stopped.   It's not taking anything from this queue and we detect
+ * it as a memory leak.  
+ *
+ * Add a new check here and complain if the queue is growing too large.
+ * That will get us a step closer to the root cause.  
+ * This has been documented in the User Guide and the CHANGES.txt file which is
+ * a minimal version of Release Notes.
+ * The proper fix will be somehow avoiding or detecting the pseudo terminal filling up
+ * and blocking on a write.
+ */
+
+	if (queue_length > 10) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Received frame queue is out of control. Length=%d.\n", queue_length);
+	  dw_printf ("Reader thread is probably frozen.\n");
+	  dw_printf ("This can be caused by using a pseudo terminal (direwolf -p) where another\n");
+	  dw_printf ("application is not reading the frames from the other side.\n");
+	}
+
+
+
+#if __WIN32__
+	SetEvent (wake_up_event);
+#else
+	if (recv_thread_is_waiting) {
+
+	  err = pthread_mutex_lock (&wake_up_mutex);
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("dlq_append: pthread_mutex_lock wu err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  err = pthread_cond_signal (&wake_up_cond);
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("dlq_append: pthread_cond_signal err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  err = pthread_mutex_unlock (&wake_up_mutex);
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("dlq_append: pthread_mutex_unlock wu err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+	}
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dlq_wait_while_empty
+ *
+ * Purpose:     Sleep while the received data queue is empty rather than
+ *		polling periodically.
+ *
+ * Inputs:	None.
+ *		
+ *--------------------------------------------------------------------*/
+
+
+void dlq_wait_while_empty (void)
+{
+	int err;
+
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_wait_while_empty () \n");
+#endif
+
+	if ( ! was_init) {
+	  dlq_init ();
+	}
+
+
+	if (queue_head == NULL) {
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dlq_wait_while_empty (): prepare to SLEEP - about to call cond wait\n");
+#endif
+
+
+#if __WIN32__
+	  WaitForSingleObject (wake_up_event, INFINITE);
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dlq_wait_while_empty (): returned from wait\n");
+#endif
+
+#else
+	  err = pthread_mutex_lock (&wake_up_mutex);
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("dlq_wait_while_empty: pthread_mutex_lock wu err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  recv_thread_is_waiting = 1;
+	  err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex);
+	  recv_thread_is_waiting = 0;
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dlq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err);
+#endif
+
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("dlq_wait_while_empty: pthread_cond_wait err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  err = pthread_mutex_unlock (&wake_up_mutex);
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("dlq_wait_while_empty: pthread_mutex_unlock wu err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+#endif
+	}
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_wait_while_empty () returns\n");
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dlq_remove
+ *
+ * Purpose:     Remove an item from the head of the queue.
+ *
+ * Inputs:	None.
+ *
+ * Outputs:	type		- type of queue entry.
+ *
+ *		chan		- channel of received frame.
+ *		subchan		- which demodulator caught it.
+ *		slice		- which slicer caught it.
+ *
+ *		pp		- pointer to packet object when type is DLQ_REC_FRAME.
+ *				   Caller should destroy it with ax25_delete when finished with it.
+ *
+ * Returns:	1 for success.
+ *		0 if queue is empty.  
+ *
+ *--------------------------------------------------------------------*/
+
+
+int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize)
+{
+
+	struct dlq_item_s *phead;
+	int result;
+	int err;
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_remove() enter critical section\n");
+#endif
+
+	if ( ! was_init) {
+	  dlq_init ();
+	}
+
+#if __WIN32__
+	EnterCriticalSection (&dlq_cs);
+#else
+	err = pthread_mutex_lock (&dlq_mutex);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_remove: pthread_mutex_lock err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+#endif
+
+	if (queue_head == NULL) {
+
+	  *type = -1;
+	  *chan = -1;
+	  *subchan = -1;
+	  *slice = -1;
+	  *pp = NULL;
+
+	  memset (alevel, 0xff, sizeof(*alevel));
+
+	  *retries = -1;
+	  strlcpy(spectrum, "", spectrumsize);
+	  result = 0;
+	}
+	else {
+
+	  phead = queue_head;
+	  queue_head = queue_head->nextp;
+
+	  *type = phead->type;
+	  *chan = phead->chan;
+	  *subchan = phead->subchan;
+	  *slice = phead->slice;
+	  *pp = phead->pp;
+	  *alevel = phead->alevel;
+	  *retries = phead->retries;
+	  strlcpy (spectrum, phead->spectrum, spectrumsize);
+	  result = 1;
+	}
+	 
+#if __WIN32__
+	LeaveCriticalSection (&dlq_cs);
+#else
+	err = pthread_mutex_unlock (&dlq_mutex);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("dlq_remove: pthread_mutex_unlock err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+#endif
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dlq_remove()  returns type=%d, chan=%d\n", (int)(*type), *chan);
+#endif
+
+#if AX25MEMDEBUG
+
+	if (ax25memdebug_get() && result) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", *type, *chan, *subchan, ax25memdebug_seq(*pp));
+	}
+#endif
+	if (result) {
+	  free (phead);
+	}
+
+	return (result);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dlq_is_empty
+ *
+ * Purpose:     Test whether queue is empty.
+ *
+ * Inputs:	None 
+ *
+ * Returns:	True if nothing in the queue.	
+ *
+ *--------------------------------------------------------------------*/
+
+#if 0
+static int dlq_is_empty (void)
+{
+	if (queue_head == NULL) {
+	  return (1);
+	}
+	return (0);
+
+} /* end dlq_is_empty */
+#endif
+
+/* end dlq.c */
diff --git a/dlq.h b/dlq.h
index d599e59..acfbce3 100644
--- a/dlq.h
+++ b/dlq.h
@@ -1,30 +1,29 @@
-
-/*------------------------------------------------------------------
- *
- * Module:      dlq.h
- *
- *---------------------------------------------------------------*/
-
-#ifndef DLQ_H
-#define DLQ_H 1
-
-#include "ax25_pad.h"
-#include "audio.h"
-
-
-void dlq_init (void);
-
-/* Types of things that can be in queue. */
-
-typedef enum dlq_type_e {DLQ_REC_FRAME} dlq_type_t; 
-
-void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum);
-
-void dlq_wait_while_empty (void);
-
-int dlq_remove (dlq_type_t *type, int *chan, int *subchan, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum); 
-
-
-#endif
-
-/* end dlq.h */
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dlq.h
+ *
+ *---------------------------------------------------------------*/
+
+#ifndef DLQ_H
+#define DLQ_H 1
+
+#include "ax25_pad.h"
+#include "audio.h"
+
+
+void dlq_init (void);
+
+/* Types of things that can be in queue. */
+
+typedef enum dlq_type_e {DLQ_REC_FRAME} dlq_type_t; 
+
+void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum);
+
+void dlq_wait_while_empty (void);
+
+int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize); 
+
+#endif
+
+/* end dlq.h */
diff --git a/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf b/doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
old mode 100755
new mode 100644
similarity index 100%
rename from A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
rename to doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
diff --git a/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf b/doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
old mode 100755
new mode 100644
similarity index 100%
rename from A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
rename to doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
diff --git a/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf b/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf
new file mode 100644
index 0000000..5fecd4c
Binary files /dev/null and b/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf differ
diff --git a/doc/APRS-Telemetry-Toolkit.pdf b/doc/APRS-Telemetry-Toolkit.pdf
new file mode 100644
index 0000000..68568bf
Binary files /dev/null and b/doc/APRS-Telemetry-Toolkit.pdf differ
diff --git a/doc/APRStt-Implementation-Notes.pdf b/doc/APRStt-Implementation-Notes.pdf
new file mode 100644
index 0000000..7fae3ae
Binary files /dev/null and b/doc/APRStt-Implementation-Notes.pdf differ
diff --git a/doc/APRStt-Listening-Example.pdf b/doc/APRStt-Listening-Example.pdf
new file mode 100644
index 0000000..84e07c4
Binary files /dev/null and b/doc/APRStt-Listening-Example.pdf differ
diff --git a/doc/APRStt-interface-for-SARTrack.pdf b/doc/APRStt-interface-for-SARTrack.pdf
new file mode 100644
index 0000000..cdee1ae
Binary files /dev/null and b/doc/APRStt-interface-for-SARTrack.pdf differ
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..67527f2
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,81 @@
+# Documentation for Dire Wolf #
+
+
+## Essential Reading ##
+ 
+- [User Guide](User-Guide.pdf)
+
+	This is your primary source of information about installation, operation, and configuration.
+
+- [Raspberry Pi APRS](Raspberry-Pi-APRS.pdf)
+
+	The Raspberry Pi has some special considerations that
+	make it different from other generic Linux systems.
+	Start here if using the Raspberry Pi, Beaglebone Black, cubieboard2, or similar single board computers.
+
+	
+## Application Notes ##
+
+These dive into more detail for specialized topics or typical usage scenarios.
+
+
+- [APRStt Implementation Notes](APRStt-Implementation-Notes.pdf)
+
+	Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones.  APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network.
+	This document explains how the APRStt concept was implemented in the Dire Wolf application.  
+
+- [APRStt Interface for SARTrack](APRStt-interface-for-SARTrack.pdf)
+
+	This example illustrates how APRStt can be integrated with other applications such as SARTrack, APRSISCE/32, YAAC, or Xastir.  
+
+- [APRStt Listening Example](APRStt-Listening-Example.pdf)
+
+	WB4APR described a useful application for the [QIKCOM-2 Satallite Transponder](http://www.tapr.org/pipermail/aprssig/2015-November/045035.html). 
+
+    Don’t have your own QIKCOM-2 Satellite Transponder?  No Problem.  You can do the same thing with an ordinary computer and the APRStt gateway built into Dire Wolf.   Here’s how.
+
+- [Raspberry Pi SDR IGate](Raspberry-Pi-SDR-IGate.pdf)
+
+	It's easy to build a receive-only APRS Internet Gateway (IGate) with only a Raspberry Pi and a software defined radio (RTL-SDR) dongle.  Here’s how.
+
+- [APRS Telemetry Toolkit](APRS-Telemetry-Toolkit.pdf)
+
+	Describes scripts and methods to generate telemetry.
+	Includes a complete example of attaching an analog to 
+	digital converter to a Raspberry Pi and transmitting 
+	a measured voltage.
+
+## Miscellaneous ##
+
+
+- [A Better APRS Packet Demodulator, part 1, 1200 baud](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf)
+
+	Sometimes it's a little mystifying why an
+APRS / AX.25 Packet TNC will decode some signals
+and not others.  A weak signal,  buried in static,
+might be fine while a nice strong clean sounding
+signal is not decoded.  Here we will take a brief
+look at what could cause this perplexing situation
+and a couple things that can be done about it.	
+
+
+
+- [A Better APRS Packet Demodulator, part 2, 9600 baud](A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf)
+
+	In the first part of this series we discussed 1200 baud audio frequency shift keying (AFSK).  The mismatch 
+	between FM 	transmitter pre-emphasis and the 
+	receiver de-emphasis will 
+	cause the amplitudes of the two tones to be different.
+	This makes it more difficult to demodulate them accurately.
+	9600 baud operation is an entirely different animal.  ...
+
+- [WA8LMF TNC Test CD Results a.k.a. Battle of the TNCs](WA8LMF-TNC-Test-CD-Results.pdf)
+
+	How can we compare how well the TNCs perform under real world conditions?
+	The de facto standard of measurement is the number of packets decoded from [WA8LMF’s TNC Test CD](http://wa8lmf.net/TNCtest/index.htm).
+	Many have published the number of packets they have been able to decode from this test. Here they are, all gathered in one place, for your reading pleasure.
+
+- [A Closer Look at the WA8LMF TNC Test CD](A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf)
+
+    Here, we take a closer look at some of the frames on the TNC Test CD in hopes of gaining some insights into why some are easily decoded and others are more difficult.
+    There are a lot of ugly signals out there.   Many can be improved by decreasing the transmit volume.   Others are just plain weird and you have to wonder how they are being generated.
\ No newline at end of file
diff --git a/doc/Raspberry-Pi-APRS-Tracker.pdf b/doc/Raspberry-Pi-APRS-Tracker.pdf
new file mode 100644
index 0000000..0d61dab
Binary files /dev/null and b/doc/Raspberry-Pi-APRS-Tracker.pdf differ
diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf
new file mode 100644
index 0000000..dc6120f
Binary files /dev/null and b/doc/Raspberry-Pi-APRS.pdf differ
diff --git a/doc/Raspberry-Pi-SDR-IGate.pdf b/doc/Raspberry-Pi-SDR-IGate.pdf
new file mode 100644
index 0000000..68037bd
Binary files /dev/null and b/doc/Raspberry-Pi-SDR-IGate.pdf differ
diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf
new file mode 100644
index 0000000..2015e9a
Binary files /dev/null and b/doc/User-Guide.pdf differ
diff --git a/doc/WA8LMF-TNC-Test-CD-Results.pdf b/doc/WA8LMF-TNC-Test-CD-Results.pdf
new file mode 100644
index 0000000..cf7c523
Binary files /dev/null and b/doc/WA8LMF-TNC-Test-CD-Results.pdf differ
diff --git a/dsp.c b/dsp.c
index 5202c58..f2a4343 100644
--- a/dsp.c
+++ b/dsp.c
@@ -1,253 +1,253 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Name:        dsp.c
- *
- * Purpose:     Generate the filters used by the demodulators.
- *
- *----------------------------------------------------------------*/
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <unistd.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "audio.h"
-#include "fsk_demod_state.h"
-#include "fsk_gen_filter.h"
-#include "textcolor.h"
-#include "dsp.h"
-
-
-//#include "fsk_demod_agc.h"	/* for M_FILTER_SIZE, etc. */
-
-#define MIN(a,b) ((a)<(b)?(a):(b))
-#define MAX(a,b) ((a)>(b)?(a):(b))
-
-
-// Don't remove this.  It serves as a reminder that an experiment is underway.
-
-#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE)
-#define DEBUG1 1
-#endif
-
-
-/*------------------------------------------------------------------
- *
- * Name:        window
- *
- * Purpose:     Filter window shape functions.
- *
- * Inputs:   	type	- BP_WINDOW_HAMMING, etc.
- *		size	- Number of filter taps.
- *		j	- Index in range of 0 to size-1.
- *
- * Returns:     Multiplier for the window shape.
- *		
- *----------------------------------------------------------------*/
-
-float window (bp_window_t type, int size, int j)
-{
-	float center;
-	float w;
-
-	center = 0.5 * (size - 1);
-
-	switch (type) {
-
-	  case BP_WINDOW_COSINE:
-	    w = cos((j - center) / size * M_PI);
-	    //w = sin(j * M_PI / (size - 1));
-	    break;
-
-	  case BP_WINDOW_HAMMING:
-	    w = 0.53836 - 0.46164 * cos((j * 2 * M_PI) / (size - 1));
-	    break;
-
-	  case BP_WINDOW_BLACKMAN:
-	    w =  0.42659 - 0.49656 * cos((j * 2 * M_PI) / (size - 1)) 
-			 + 0.076849 * cos((j * 4 * M_PI) / (size - 1));
-	    break;
-
-	  case BP_WINDOW_FLATTOP:
-	    w =  1.0    - 1.93  * cos((j * 2 * M_PI) / (size - 1)) 
-			+ 1.29  * cos((j * 4 * M_PI) / (size - 1))
-			- 0.388 * cos((j * 6 * M_PI) / (size - 1))
-			+ 0.028 * cos((j * 8 * M_PI) / (size - 1));
-	    break;
-
-	  case BP_WINDOW_TRUNCATED:
-	    default:
-	    w = 1.0;
-	    break;
-	}
-	return (w);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        gen_lowpass
- *
- * Purpose:     Generate low pass filter kernel.
- *
- * Inputs:   	fc		- Cutoff frequency as fraction of sampling frequency.
- *		filter_size	- Number of filter taps.
- *		wtype		- Window type, BP_WINDOW_HAMMING, etc.
- *
- * Outputs:     lp_filter
- *		
- *----------------------------------------------------------------*/
-
- 
-void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype)
-{
-	int j;
-	float G;
-
-
-#if DEBUG1
-	text_color_set(DW_COLOR_DEBUG);
-
-	dw_printf ("Lowpass, size=%d, fc=%.2f\n", filter_size, fc);
-	dw_printf ("   j     shape   sinc   final\n");
-#endif
-
-	assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
-
-        for (j=0; j<filter_size; j++) {
-	  float center;
-	  float sinc;
-	  float shape;
-
-	  center = 0.5 * (filter_size - 1);
-
-	  if (j - center == 0) {
-	    sinc = 2 * fc;
-	  }
-	  else {
-	    sinc = sin(2 * M_PI * fc * (j-center)) / (M_PI*(j-center));
-	  }
-
-	  shape = window (wtype, filter_size, j);
-	  lp_filter[j] = sinc * shape;
-
-#if DEBUG1
-	  dw_printf ("%6d  %6.2f  %6.3f  %6.3f\n", j, shape, sinc, lp_filter[j] ) ;
-#endif
-        }
-
-/*
- * Normalize lowpass for unity gain at DC.
- */
-	G = 0;
-        for (j=0; j<filter_size; j++) {
-	  G += lp_filter[j];
-	}
-        for (j=0; j<filter_size; j++) {
-	  lp_filter[j] = lp_filter[j] / G;
-	}
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        gen_bandpass
- *
- * Purpose:     Generate band pass filter kernel.
- *
- * Inputs:   	f1		- Lower cutoff frequency as fraction of sampling frequency.
- *		f2		- Upper cutoff frequency...
- *		filter_size	- Number of filter taps.
- *		wtype		- Window type, BP_WINDOW_HAMMING, etc.
- *
- * Outputs:     bp_filter
- *
- * Reference:	http://www.labbookpages.co.uk/audio/firWindowing.html	
- *
- *		Does it need to be an odd length?
- *	
- *----------------------------------------------------------------*/
-
-
-void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
-{
-	int j;
-	float w;
-	float G;
-	float center = 0.5 * (filter_size - 1);
-
-
-#if DEBUG1
-	text_color_set(DW_COLOR_DEBUG);
-
-	dw_printf ("Bandpass, size=%d\n", filter_size);
-	dw_printf ("   j     shape   sinc   final\n");
-#endif
-
-	assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
-
-        for (j=0; j<filter_size; j++) {
-	  float sinc;
-	  float shape;
-
-	  if (j - center == 0) {
-	    sinc = 2 * (f2 - f1);
-	  }
-	  else {
-	    sinc = sin(2 * M_PI * f2 * (j-center)) / (M_PI*(j-center))
-		 - sin(2 * M_PI * f1 * (j-center)) / (M_PI*(j-center));
-	  }
-
-	  shape = window (wtype, filter_size, j);
-	  bp_filter[j] = sinc * shape;
-
-#if DEBUG1
-	  dw_printf ("%6d  %6.2f  %6.3f  %6.3f\n", j, shape, sinc, bp_filter[j] ) ;
-#endif
-        }
-
-/*
- * Normalize bandpass for unity gain in middle of passband.
- * 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
- */
-	w = 2 * M_PI * (f1 + f2) / 2;
-	G = 0;
-        for (j=0; j<filter_size; j++) {
-	  G += 2 * bp_filter[j] * cos((j-center)*w);  // is this correct?
-	}
-
-#if DEBUG1
-	dw_printf ("Before normalizing, G=%.3f\n", G);
-#endif
-        for (j=0; j<filter_size; j++) {
-	  bp_filter[j] = bp_filter[j] / G;
-	}
-}
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        dsp.c
+ *
+ * Purpose:     Generate the filters used by the demodulators.
+ *
+ *----------------------------------------------------------------*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "audio.h"
+#include "fsk_demod_state.h"
+#include "fsk_gen_filter.h"
+#include "textcolor.h"
+#include "dsp.h"
+
+
+//#include "fsk_demod_agc.h"	/* for M_FILTER_SIZE, etc. */
+
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+
+// Don't remove this.  It serves as a reminder that an experiment is underway.
+
+#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE)
+#define DEBUG1 1
+#endif
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        window
+ *
+ * Purpose:     Filter window shape functions.
+ *
+ * Inputs:   	type	- BP_WINDOW_HAMMING, etc.
+ *		size	- Number of filter taps.
+ *		j	- Index in range of 0 to size-1.
+ *
+ * Returns:     Multiplier for the window shape.
+ *		
+ *----------------------------------------------------------------*/
+
+float window (bp_window_t type, int size, int j)
+{
+	float center;
+	float w;
+
+	center = 0.5 * (size - 1);
+
+	switch (type) {
+
+	  case BP_WINDOW_COSINE:
+	    w = cos((j - center) / size * M_PI);
+	    //w = sin(j * M_PI / (size - 1));
+	    break;
+
+	  case BP_WINDOW_HAMMING:
+	    w = 0.53836 - 0.46164 * cos((j * 2 * M_PI) / (size - 1));
+	    break;
+
+	  case BP_WINDOW_BLACKMAN:
+	    w =  0.42659 - 0.49656 * cos((j * 2 * M_PI) / (size - 1)) 
+			 + 0.076849 * cos((j * 4 * M_PI) / (size - 1));
+	    break;
+
+	  case BP_WINDOW_FLATTOP:
+	    w =  1.0    - 1.93  * cos((j * 2 * M_PI) / (size - 1)) 
+			+ 1.29  * cos((j * 4 * M_PI) / (size - 1))
+			- 0.388 * cos((j * 6 * M_PI) / (size - 1))
+			+ 0.028 * cos((j * 8 * M_PI) / (size - 1));
+	    break;
+
+	  case BP_WINDOW_TRUNCATED:
+	    default:
+	    w = 1.0;
+	    break;
+	}
+	return (w);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        gen_lowpass
+ *
+ * Purpose:     Generate low pass filter kernel.
+ *
+ * Inputs:   	fc		- Cutoff frequency as fraction of sampling frequency.
+ *		filter_size	- Number of filter taps.
+ *		wtype		- Window type, BP_WINDOW_HAMMING, etc.
+ *
+ * Outputs:     lp_filter
+ *		
+ *----------------------------------------------------------------*/
+
+ 
+void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype)
+{
+	int j;
+	float G;
+
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("Lowpass, size=%d, fc=%.2f\n", filter_size, fc);
+	dw_printf ("   j     shape   sinc   final\n");
+#endif
+
+	assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
+
+        for (j=0; j<filter_size; j++) {
+	  float center;
+	  float sinc;
+	  float shape;
+
+	  center = 0.5 * (filter_size - 1);
+
+	  if (j - center == 0) {
+	    sinc = 2 * fc;
+	  }
+	  else {
+	    sinc = sin(2 * M_PI * fc * (j-center)) / (M_PI*(j-center));
+	  }
+
+	  shape = window (wtype, filter_size, j);
+	  lp_filter[j] = sinc * shape;
+
+#if DEBUG1
+	  dw_printf ("%6d  %6.2f  %6.3f  %6.3f\n", j, shape, sinc, lp_filter[j] ) ;
+#endif
+        }
+
+/*
+ * Normalize lowpass for unity gain at DC.
+ */
+	G = 0;
+        for (j=0; j<filter_size; j++) {
+	  G += lp_filter[j];
+	}
+        for (j=0; j<filter_size; j++) {
+	  lp_filter[j] = lp_filter[j] / G;
+	}
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        gen_bandpass
+ *
+ * Purpose:     Generate band pass filter kernel.
+ *
+ * Inputs:   	f1		- Lower cutoff frequency as fraction of sampling frequency.
+ *		f2		- Upper cutoff frequency...
+ *		filter_size	- Number of filter taps.
+ *		wtype		- Window type, BP_WINDOW_HAMMING, etc.
+ *
+ * Outputs:     bp_filter
+ *
+ * Reference:	http://www.labbookpages.co.uk/audio/firWindowing.html	
+ *
+ *		Does it need to be an odd length?
+ *	
+ *----------------------------------------------------------------*/
+
+
+void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
+{
+	int j;
+	float w;
+	float G;
+	float center = 0.5 * (filter_size - 1);
+
+
+#if DEBUG1
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("Bandpass, size=%d\n", filter_size);
+	dw_printf ("   j     shape   sinc   final\n");
+#endif
+
+	assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
+
+        for (j=0; j<filter_size; j++) {
+	  float sinc;
+	  float shape;
+
+	  if (j - center == 0) {
+	    sinc = 2 * (f2 - f1);
+	  }
+	  else {
+	    sinc = sin(2 * M_PI * f2 * (j-center)) / (M_PI*(j-center))
+		 - sin(2 * M_PI * f1 * (j-center)) / (M_PI*(j-center));
+	  }
+
+	  shape = window (wtype, filter_size, j);
+	  bp_filter[j] = sinc * shape;
+
+#if DEBUG1
+	  dw_printf ("%6d  %6.2f  %6.3f  %6.3f\n", j, shape, sinc, bp_filter[j] ) ;
+#endif
+        }
+
+/*
+ * Normalize bandpass for unity gain in middle of passband.
+ * 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
+ */
+	w = 2 * M_PI * (f1 + f2) / 2;
+	G = 0;
+        for (j=0; j<filter_size; j++) {
+	  G += 2 * bp_filter[j] * cos((j-center)*w);  // is this correct?
+	}
+
+#if DEBUG1
+	dw_printf ("Before normalizing, G=%.3f\n", G);
+#endif
+        for (j=0; j<filter_size; j++) {
+	  bp_filter[j] = bp_filter[j] / G;
+	}
+}
+
 /* end dsp.c */
\ No newline at end of file
diff --git a/dsp.h b/dsp.h
index 1150714..1f5aaa5 100644
--- a/dsp.h
+++ b/dsp.h
@@ -1,10 +1,10 @@
-
-/* dsp.h */
-
-// TODO:  put prefixes on these names.
-
-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);
-
+
+/* dsp.h */
+
+// TODO:  put prefixes on these names.
+
+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);
+
 void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype);
\ No newline at end of file
diff --git a/dtime_now.c b/dtime_now.c
index df2502d..c78fba8 100644
--- a/dtime_now.c
+++ b/dtime_now.c
@@ -1,52 +1,61 @@
-
-
-#include "textcolor.h"
-#include "dtime_now.h"
-
-
-/* Current time in seconds but more resolution than time(). */
-
-/* We don't care what date a 0 value represents because we */
-/* only use this to calculate elapsed real time. */
-
-
-
-#include <time.h>
-
-#if __WIN32__
-#include <windows.h>
-#endif
-
-
-
-double dtime_now (void)
-{
-	double result;
-
-#if __WIN32__
-	/* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */
-
-	FILETIME ft;
-	
-	GetSystemTimeAsFileTime (&ft);
-
-	result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + 
-			(double)ft.dwLowDateTime ) / 10000000.) - 11644473600.);
-#else
-	/* tv_sec is seconds from Jan 1, 1970. */
-
-	struct timespec ts;
-
-	clock_gettime (CLOCK_REALTIME, &ts);
-
-	result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001);
-	
-#endif
-
-#if DEBUG	
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("dtime_now() returns %.3f\n", result );
-#endif
-
-	return (result);
-}
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "dtime_now.h"
+
+
+/* Current time in seconds but more resolution than time(). */
+
+/* We don't care what date a 0 value represents because we */
+/* only use this to calculate elapsed real time. */
+
+
+
+#include <time.h>
+
+#ifdef __APPLE__
+#include <sys/time.h>
+#endif
+
+
+
+
+
+double dtime_now (void)
+{
+	double result;
+
+#if __WIN32__
+	/* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */
+
+	FILETIME ft;
+	
+	GetSystemTimeAsFileTime (&ft);
+
+	result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + 
+			(double)ft.dwLowDateTime ) / 10000000.) - 11644473600.);
+#else
+	/* tv_sec is seconds from Jan 1, 1970. */
+
+	struct timespec ts;
+
+#ifdef __APPLE__
+	struct timeval tp;
+	gettimeofday(&tp, NULL);
+	ts.tv_nsec = tp.tv_usec * 1000;
+	ts.tv_sec  = tp.tv_sec;
+#else
+	clock_gettime (CLOCK_REALTIME, &ts);
+#endif
+
+	result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001);
+	
+#endif
+
+#if DEBUG	
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("dtime_now() returns %.3f\n", result );
+#endif
+
+	return (result);
+}
diff --git a/dtime_now.h b/dtime_now.h
index e3001d4..5d7c39a 100644
--- a/dtime_now.h
+++ b/dtime_now.h
@@ -1,3 +1,3 @@
-
-
+
+
 extern double dtime_now (void);
\ No newline at end of file
diff --git a/dtmf.c b/dtmf.c
index d563893..3479244 100644
--- a/dtmf.c
+++ b/dtmf.c
@@ -1,458 +1,464 @@
-
-//#define DEBUG 1
-
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-/*------------------------------------------------------------------
- *
- * Module:      dtmf.c
- *
- * Purpose:   	Decoder for DTMF, commonly known as "touch tones."
- *		
- * Description: This uses the Goertzel Algorithm for tone detection.
- *
- * References:	http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm
- * 		http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "dtmf.h"
-
-
-
-#if DTMF_TEST
-#define TIMEOUT_SEC 1	/* short for unit test below. */
-#define DEBUG 1
-#else
-#define TIMEOUT_SEC 5	/* for normal operation. */
-#endif
-
-
-#define NUM_TONES 8
-static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 };
-
-/*
- * Current state of the DTMF decoding. 
- */
-
-static struct dd_s {	 /* Separate for each audio channel. */
-
-	int sample_rate;	/* Samples per sec.  Typ. 44100, 8000, etc. */
-	int block_size;		/* Number of samples to process in one block. */
-	float coef[NUM_TONES];	
-
-	int n;			/* Samples processed in this block. */
-	float Q1[NUM_TONES];
-	float Q2[NUM_TONES];
-	char prev_dec;	
-	char debounced;
-	char prev_debounced;
-	int timeout;
-
-} dd[MAX_CHANS];		
-
-
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        dtmf_init
- *
- * Purpose:     Initialize the DTMF decoder.
- *		This should be called once at application start up time.
- *
- * Inputs:      p_audio_config - Configuration for audio interfaces.
- *
- *			All we care about is:
- *
- *				samples_per_sec - Audio sample frequency, typically 
- *				  		44100, 22050, 8000, etc.
- *
- *			This is a associated with the soundcard.
- *			In version 1.2, we can have multiple soundcards
- *			with potentially different sample rates.
- *
- * Returns:     None.
- *
- *----------------------------------------------------------------*/
-
-
-void dtmf_init (struct audio_s *p_audio_config)
-{
-	int j;		/* Loop over all tones frequencies. */
-	int c;		/* Loop over all audio channels. */
-	
-
-/*
- * Pick a suitable processing block size.
- * Larger = narrower bandwidth, slower response.
- */
-
-	for (c=0; c<MAX_CHANS; c++) {
-	  struct dd_s *D = &(dd[c]);
-	  int a = ACHAN2ADEV(c);
-	  if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) {
-
-#if DEBUG
-	    dw_printf ("channel %d:\n", c);
-#endif
-
-	    D->sample_rate = p_audio_config->adev[a].samples_per_sec;
-	    D->block_size = (205 * D->sample_rate) / 8000;
-	
-
-#if DEBUG
-	    dw_printf ("    freq      k     coef    \n");
-#endif
-	    for (j=0; j<NUM_TONES; j++) {
-	      float k; 
-
-
-// Why do some insist on rounding k to the nearest integer?
-// That would move the filter center frequency away from ideal.
-// What is to be gained?
-// More consistent results for all the tones when k is not rounded off.
-
-	      k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate);
-
-	      D->coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D->block_size));
-
-	      assert (D->coef[j] > 0 && D->coef[j] < 2.0);
-#if DEBUG
-	      dw_printf ("%8d   %5.1f   %8.5f  \n", dtmf_tones[j], k, D->coef[j]);
-#endif
-	    }
-	  }
-	}
-
-	for (c=0; c<MAX_CHANS; c++) {
-	  struct dd_s *D = &(dd[c]); 
-	  D->n = 0;
-	  for (j=0; j<NUM_TONES; j++) {
-	    D->Q1[j] = 0;
-	    D->Q2[j] = 0;
-	  }
-	  D->prev_dec = ' ';
-	  D->debounced = ' ';
-	  D->prev_debounced = ' ';
-	  D->timeout = 0;
-	}
-
-}
-
-/*------------------------------------------------------------------
- *
- * Name:        dtmf_sample
- *
- * Purpose:     Process one audio sample from the sound input source.
- *
- * Inputs:	c	- Audio channel number.
- *			  This can process multiple channels in parallel.
- *		input	- Audio sample.
- *
- * Returns:     0123456789ABCD*# for a button push.
- *		. for nothing happening during sample interval.
- *		$ after several seconds of inactivity.
- *		space between sample intervals.
- *		
- *
- *----------------------------------------------------------------*/
-				
-__attribute__((hot))
-char dtmf_sample (int c, float input)
-{
-	int i;
-	float Q0;
-	float output[NUM_TONES];
-	char decoded;
-	char ret;
-	struct dd_s *D;
-	static const char rc2char[16] = { 	'1', '2', '3', 'A',
-						'4', '5', '6', 'B',
-						'7', '8', '9', 'C',
-						'*', '0', '#', 'D' };
-
-	D = &(dd[c]);
-
-	for (i=0; i<NUM_TONES; i++) {
-	  Q0 = input + D->Q1[i] * D->coef[i] - D->Q2[i];
-	  D->Q2[i] = D->Q1[i];
-	  D->Q1[i] = Q0;
-	}
-
-/*
- * Is it time to process the block?
- */
-	D->n++;
-	if (D->n == D->block_size) {
-	  int row, col;
-
-	  for (i=0; i<NUM_TONES; i++) {
-	    output[i] = sqrt(D->Q1[i] * D->Q1[i] + D->Q2[i] * D->Q2[i] - D->Q1[i] * D->Q2[i] * D->coef[i]);
-	    D->Q1[i] = 0;
-	    D->Q2[i] = 0;
-	  }
-	  D->n = 0;
-
-
-/*
- * The input signal can vary over a couple orders of
- * magnitude so we can't set some absolute threshold.
- *
- * See if one tone is stronger than the sum of the 
- * others in the same group multiplied by some factor.
- *
- * For perfect synthetic signals this needs to be in
- * the range of about 1.33 (very senstive) to 2.15 (very fussy).
- *
- * Too low will cause false triggers on random noise.
- * Too high will won't decode less than perfect signals.
- *
- * Use the mid point 1.74 as our initial guess.
- * It might need some fine tuning for imperfect real world signals.
- */
-
-
-#define THRESHOLD 1.74
-
-	  if      (output[0] > THRESHOLD * (            output[1] + output[2] + output[3])) row = 0;
-	  else if (output[1] > THRESHOLD * (output[0]             + output[2] + output[3])) row = 1;
-	  else if (output[2] > THRESHOLD * (output[0] + output[1]             + output[3])) row = 2;
-	  else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2]            )) row = 3;
-	  else row = -1;
-
-	  if      (output[4] > THRESHOLD * (            output[5] + output[6] + output[7])) col = 0;
-	  else if (output[5] > THRESHOLD * (output[4]             + output[6] + output[7])) col = 1;
-	  else if (output[6] > THRESHOLD * (output[4] + output[5]             + output[7])) col = 2;
-	  else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6]            )) col = 3;
-	  else col = -1;
-
-	  for (i=0; i<NUM_TONES; i++) {
-#if DEBUG
-	    dw_printf ("%5.0f ", output[i]);
-#endif
-	  }
-	  if (row >= 0 && col >= 0) {
-	    decoded = rc2char[row*4+col];
-	  }
-	  else {
-	    decoded = ' ';
-	  }
-
-// Consider valid only if we get same twice in a row.
-
-	  if (decoded == D->prev_dec) {
-	    D->debounced = decoded;
-	    /* Reset timeout timer. */
-	    if (decoded != ' ') {
-	      D->timeout = ((TIMEOUT_SEC) * D->sample_rate) / D->block_size;
-	    }
-	  }
-	  D->prev_dec = decoded;
-
-// Return only new button pushes.
-// Also report timeout after period of inactivity.
-
-	  ret = '.';
-	  if (D->debounced != D->prev_debounced) {
-	    if (D->debounced != ' ') {
-	      ret = D->debounced;
-	    }
-	  }
-	  if (ret == '.') {
-	    if (D->timeout > 0) {
-	      D->timeout--;
-	      if (D->timeout == 0) {
-	        ret = '$';
-              }
-            }
-	  }
-	  D->prev_debounced = D->debounced;
-
-#if DEBUG
-	  dw_printf ("     dec=%c, deb=%c, ret=%c, to=%d \n", 
-			decoded, D->debounced, ret, D->timeout);
-#endif
-	  return (ret);
-	}
-
- 	return (' ');
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Unit test for functions above.
- *
- * Usage:	rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe
- *
- *----------------------------------------------------------------*/
-
-
-#if DTMF_TEST
-
-push_button (char button, int ms)
-{
-	static float phasea = 0;
-	static float phaseb = 0;
-	float fa, fb;
-	int i;
-	float input;
-	char x;
-	static char result[100];
-	static int result_len = 0;
-	int c = 0;			// fake channel number.
-
-
-	switch (button) {
-	  case '1':  fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
-	  case '2':  fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
-	  case '3':  fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
-	  case 'A':  fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
-	  case '4':  fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
-	  case '5':  fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
-	  case '6':  fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
-	  case 'B':  fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
-	  case '7':  fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
-	  case '8':  fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
-	  case '9':  fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
-	  case 'C':  fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
-	  case '*':  fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
-	  case '0':  fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
-	  case '#':  fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
-	  case 'D':  fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
-	  case '?':
-
-// TODO: why are timeouts failing.  Do we care?
-
-	    if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
-	      dw_printf ("\nSuccess!\n");
-	    }
-	    else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
-	      dw_printf ("\n * Time-out failed, otherwise OK *\n");
-	      dw_printf ("\"%s\"\n", result);
-	    }
-	    else {
-	      dw_printf ("\n *** TEST FAILED ***\n");
-	      dw_printf ("\"%s\"\n", result);
-	    }
-	    break;
-
-	  default:  fa = 0; fb = 0;
-	}
-
-	for (i = 0; i < (ms*dd[c].sample_rate)/1000; i++) {
-
-	  input = sin(phasea) + sin(phaseb);
-	  phasea += 2 * M_PI * fa / dd[c].sample_rate;
-	  phaseb += 2 * M_PI * fb / dd[c].sample_rate;
-
-	  /* Make sure it is insensitive to signal amplitude. */
-
-	  x = dtmf_sample (0, input);
-	  //x = dtmf_sample (0, input * 1000);
-	  //x = dtmf_sample (0, input * 0.001);
-
-	  if (x != ' ' && x != '.') {
-	    result[result_len] = x;
-	    result_len++;
-	    result[result_len] = '\0';
-	  }
-	}
-}
-
-static struct audio_s my_audio_config;
-
-
-main ()
-{
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-	my_audio_config.adev[0].defined = 1;
-	my_audio_config.adev[0].samples_per_sec = 44100;
-	my_audio_config.achan[0].valid = 1;
-	my_audio_config.achan[0].dtmf_decode = DTMF_DECODE_ON;
-
-	dtmf_init(&my_audio_config);	
-
-	dw_printf ("\nFirst, check all button tone pairs. \n\n");
-	/* Max auto dialing rate is 10 per second. */
-
-	push_button ('1', 50); push_button (' ', 50);
-	push_button ('2', 50); push_button (' ', 50);
-	push_button ('3', 50); push_button (' ', 50);
-	push_button ('A', 50); push_button (' ', 50);
-
-	push_button ('4', 50); push_button (' ', 50);
-	push_button ('5', 50); push_button (' ', 50);
-	push_button ('6', 50); push_button (' ', 50);
-	push_button ('B', 50); push_button (' ', 50);
-
-	push_button ('7', 50); push_button (' ', 50);
-	push_button ('8', 50); push_button (' ', 50);
-	push_button ('9', 50); push_button (' ', 50);
-	push_button ('C', 50); push_button (' ', 50);
-
-	push_button ('*', 50); push_button (' ', 50);
-	push_button ('0', 50); push_button (' ', 50);
-	push_button ('#', 50); push_button (' ', 50);
-	push_button ('D', 50); push_button (' ', 50);
-
-	dw_printf ("\nShould reject very short pulses.\n\n");
-	
-	push_button ('1', 20); push_button (' ', 50);
-	push_button ('1', 20); push_button (' ', 50);
-	push_button ('1', 20); push_button (' ', 50);
-	push_button ('1', 20); push_button (' ', 50);
-	push_button ('1', 20); push_button (' ', 50);
-
-	dw_printf ("\nTest timeout after inactivity.\n\n");
-	/* For this test we use 1 second. */
-	/* In practice, it will probably more like 5. */
-
-	push_button ('1', 250); push_button (' ', 500);
-	push_button ('2', 250); push_button (' ', 500);
-	push_button ('3', 250); push_button (' ', 1200);
-
-	push_button ('7', 250); push_button (' ', 500);
-	push_button ('8', 250); push_button (' ', 500);
-	push_button ('9', 250); push_button (' ', 1200);
-
-	/* Check for expected results. */
-
-	push_button ('?', 0);
-
-}  /* end main */
-
-#endif
-
-/* end dtmf.c */
-
+
+//#define DEBUG 1
+
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dtmf.c
+ *
+ * Purpose:   	Decoder for DTMF, commonly known as "touch tones."
+ *		
+ * Description: This uses the Goertzel Algorithm for tone detection.
+ *
+ * References:	http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm
+ * 		http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "dtmf.h"
+#include "hdlc_rec.h"	// for dcd_change
+
+
+
+#if DTMF_TEST
+#define TIMEOUT_SEC 1	/* short for unit test below. */
+#define DEBUG 1
+#else
+#define TIMEOUT_SEC 5	/* for normal operation. */
+#endif
+
+
+#define NUM_TONES 8
+static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 };
+
+/*
+ * Current state of the DTMF decoding. 
+ */
+
+static struct dd_s {	 /* Separate for each audio channel. */
+
+	int sample_rate;	/* Samples per sec.  Typ. 44100, 8000, etc. */
+	int block_size;		/* Number of samples to process in one block. */
+	float coef[NUM_TONES];	
+
+	int n;			/* Samples processed in this block. */
+	float Q1[NUM_TONES];
+	float Q2[NUM_TONES];
+	char prev_dec;	
+	char debounced;
+	char prev_debounced;
+	int timeout;
+
+} dd[MAX_CHANS];		
+
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        dtmf_init
+ *
+ * Purpose:     Initialize the DTMF decoder.
+ *		This should be called once at application start up time.
+ *
+ * Inputs:      p_audio_config - Configuration for audio interfaces.
+ *
+ *			All we care about is:
+ *
+ *				samples_per_sec - Audio sample frequency, typically 
+ *				  		44100, 22050, 8000, etc.
+ *
+ *			This is a associated with the soundcard.
+ *			In version 1.2, we can have multiple soundcards
+ *			with potentially different sample rates.
+ *
+ * Returns:     None.
+ *
+ *----------------------------------------------------------------*/
+
+
+void dtmf_init (struct audio_s *p_audio_config)
+{
+	int j;		/* Loop over all tones frequencies. */
+	int c;		/* Loop over all audio channels. */
+	
+
+/*
+ * Pick a suitable processing block size.
+ * Larger = narrower bandwidth, slower response.
+ */
+
+	for (c=0; c<MAX_CHANS; c++) {
+	  struct dd_s *D = &(dd[c]);
+	  int a = ACHAN2ADEV(c);
+	  if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) {
+
+#if DEBUG
+	    dw_printf ("channel %d:\n", c);
+#endif
+
+	    D->sample_rate = p_audio_config->adev[a].samples_per_sec;
+	    D->block_size = (205 * D->sample_rate) / 8000;
+	
+
+#if DEBUG
+	    dw_printf ("    freq      k     coef    \n");
+#endif
+	    for (j=0; j<NUM_TONES; j++) {
+	      float k; 
+
+
+// Why do some insist on rounding k to the nearest integer?
+// That would move the filter center frequency away from ideal.
+// What is to be gained?
+// More consistent results for all the tones when k is not rounded off.
+
+	      k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate);
+
+	      D->coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D->block_size));
+
+	      assert (D->coef[j] > 0 && D->coef[j] < 2.0);
+#if DEBUG
+	      dw_printf ("%8d   %5.1f   %8.5f  \n", dtmf_tones[j], k, D->coef[j]);
+#endif
+	    }
+	  }
+	}
+
+	for (c=0; c<MAX_CHANS; c++) {
+	  struct dd_s *D = &(dd[c]); 
+	  D->n = 0;
+	  for (j=0; j<NUM_TONES; j++) {
+	    D->Q1[j] = 0;
+	    D->Q2[j] = 0;
+	  }
+	  D->prev_dec = ' ';
+	  D->debounced = ' ';
+	  D->prev_debounced = ' ';
+	  D->timeout = 0;
+	}
+
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name:        dtmf_sample
+ *
+ * Purpose:     Process one audio sample from the sound input source.
+ *
+ * Inputs:	c	- Audio channel number.
+ *			  This can process multiple channels in parallel.
+ *		input	- Audio sample.
+ *
+ * Returns:     0123456789ABCD*# for a button push.
+ *		. for nothing happening during sample interval.
+ *		$ after several seconds of inactivity.
+ *		space between sample intervals.
+ *		
+ *
+ *----------------------------------------------------------------*/
+				
+__attribute__((hot))
+char dtmf_sample (int c, float input)
+{
+	int i;
+	float Q0;
+	float output[NUM_TONES];
+	char decoded;
+	char ret;
+	struct dd_s *D;
+	static const char rc2char[16] = { 	'1', '2', '3', 'A',
+						'4', '5', '6', 'B',
+						'7', '8', '9', 'C',
+						'*', '0', '#', 'D' };
+
+	D = &(dd[c]);
+
+	for (i=0; i<NUM_TONES; i++) {
+	  Q0 = input + D->Q1[i] * D->coef[i] - D->Q2[i];
+	  D->Q2[i] = D->Q1[i];
+	  D->Q1[i] = Q0;
+	}
+
+/*
+ * Is it time to process the block?
+ */
+	D->n++;
+	if (D->n == D->block_size) {
+	  int row, col;
+
+	  for (i=0; i<NUM_TONES; i++) {
+	    output[i] = sqrt(D->Q1[i] * D->Q1[i] + D->Q2[i] * D->Q2[i] - D->Q1[i] * D->Q2[i] * D->coef[i]);
+	    D->Q1[i] = 0;
+	    D->Q2[i] = 0;
+	  }
+	  D->n = 0;
+
+
+/*
+ * The input signal can vary over a couple orders of
+ * magnitude so we can't set some absolute threshold.
+ *
+ * See if one tone is stronger than the sum of the 
+ * others in the same group multiplied by some factor.
+ *
+ * For perfect synthetic signals this needs to be in
+ * the range of about 1.33 (very senstive) to 2.15 (very fussy).
+ *
+ * Too low will cause false triggers on random noise.
+ * Too high will won't decode less than perfect signals.
+ *
+ * Use the mid point 1.74 as our initial guess.
+ * It might need some fine tuning for imperfect real world signals.
+ */
+
+
+#define THRESHOLD 1.74
+
+	  if      (output[0] > THRESHOLD * (            output[1] + output[2] + output[3])) row = 0;
+	  else if (output[1] > THRESHOLD * (output[0]             + output[2] + output[3])) row = 1;
+	  else if (output[2] > THRESHOLD * (output[0] + output[1]             + output[3])) row = 2;
+	  else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2]            )) row = 3;
+	  else row = -1;
+
+	  if      (output[4] > THRESHOLD * (            output[5] + output[6] + output[7])) col = 0;
+	  else if (output[5] > THRESHOLD * (output[4]             + output[6] + output[7])) col = 1;
+	  else if (output[6] > THRESHOLD * (output[4] + output[5]             + output[7])) col = 2;
+	  else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6]            )) col = 3;
+	  else col = -1;
+
+	  for (i=0; i<NUM_TONES; i++) {
+#if DEBUG
+	    dw_printf ("%5.0f ", output[i]);
+#endif
+	  }
+	  if (row >= 0 && col >= 0) {
+	    decoded = rc2char[row*4+col];
+	  }
+	  else {
+	    decoded = ' ';
+	  }
+
+// Consider valid only if we get same twice in a row.
+
+	  if (decoded == D->prev_dec) {
+	    D->debounced = decoded;
+
+	    // Update Data Carrier Detect Indicator.
+
+	    dcd_change (c, MAX_SUBCHANS, 0, decoded != ' ');
+
+	    /* Reset timeout timer. */
+	    if (decoded != ' ') {
+	      D->timeout = ((TIMEOUT_SEC) * D->sample_rate) / D->block_size;
+	    }
+	  }
+	  D->prev_dec = decoded;
+
+// Return only new button pushes.
+// Also report timeout after period of inactivity.
+
+	  ret = '.';
+	  if (D->debounced != D->prev_debounced) {
+	    if (D->debounced != ' ') {
+	      ret = D->debounced;
+	    }
+	  }
+	  if (ret == '.') {
+	    if (D->timeout > 0) {
+	      D->timeout--;
+	      if (D->timeout == 0) {
+	        ret = '$';
+              }
+            }
+	  }
+	  D->prev_debounced = D->debounced;
+
+#if DEBUG
+	  dw_printf ("     dec=%c, deb=%c, ret=%c, to=%d \n", 
+			decoded, D->debounced, ret, D->timeout);
+#endif
+	  return (ret);
+	}
+
+ 	return (' ');
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Unit test for functions above.
+ *
+ * Usage:	rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe
+ *
+ *----------------------------------------------------------------*/
+
+
+#if DTMF_TEST
+
+push_button (char button, int ms)
+{
+	static float phasea = 0;
+	static float phaseb = 0;
+	float fa, fb;
+	int i;
+	float input;
+	char x;
+	static char result[100];
+	static int result_len = 0;
+	int c = 0;			// fake channel number.
+
+
+	switch (button) {
+	  case '1':  fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
+	  case '2':  fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
+	  case '3':  fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
+	  case 'A':  fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
+	  case '4':  fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
+	  case '5':  fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
+	  case '6':  fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
+	  case 'B':  fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
+	  case '7':  fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
+	  case '8':  fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
+	  case '9':  fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
+	  case 'C':  fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
+	  case '*':  fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
+	  case '0':  fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
+	  case '#':  fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
+	  case 'D':  fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
+	  case '?':
+
+// TODO: why are timeouts failing.  Do we care?
+
+	    if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
+	      dw_printf ("\nSuccess!\n");
+	    }
+	    else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
+	      dw_printf ("\n * Time-out failed, otherwise OK *\n");
+	      dw_printf ("\"%s\"\n", result);
+	    }
+	    else {
+	      dw_printf ("\n *** TEST FAILED ***\n");
+	      dw_printf ("\"%s\"\n", result);
+	    }
+	    break;
+
+	  default:  fa = 0; fb = 0;
+	}
+
+	for (i = 0; i < (ms*dd[c].sample_rate)/1000; i++) {
+
+	  input = sin(phasea) + sin(phaseb);
+	  phasea += 2 * M_PI * fa / dd[c].sample_rate;
+	  phaseb += 2 * M_PI * fb / dd[c].sample_rate;
+
+	  /* Make sure it is insensitive to signal amplitude. */
+
+	  x = dtmf_sample (0, input);
+	  //x = dtmf_sample (0, input * 1000);
+	  //x = dtmf_sample (0, input * 0.001);
+
+	  if (x != ' ' && x != '.') {
+	    result[result_len] = x;
+	    result_len++;
+	    result[result_len] = '\0';
+	  }
+	}
+}
+
+static struct audio_s my_audio_config;
+
+
+main ()
+{
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+	my_audio_config.adev[0].defined = 1;
+	my_audio_config.adev[0].samples_per_sec = 44100;
+	my_audio_config.achan[0].valid = 1;
+	my_audio_config.achan[0].dtmf_decode = DTMF_DECODE_ON;
+
+	dtmf_init(&my_audio_config);	
+
+	dw_printf ("\nFirst, check all button tone pairs. \n\n");
+	/* Max auto dialing rate is 10 per second. */
+
+	push_button ('1', 50); push_button (' ', 50);
+	push_button ('2', 50); push_button (' ', 50);
+	push_button ('3', 50); push_button (' ', 50);
+	push_button ('A', 50); push_button (' ', 50);
+
+	push_button ('4', 50); push_button (' ', 50);
+	push_button ('5', 50); push_button (' ', 50);
+	push_button ('6', 50); push_button (' ', 50);
+	push_button ('B', 50); push_button (' ', 50);
+
+	push_button ('7', 50); push_button (' ', 50);
+	push_button ('8', 50); push_button (' ', 50);
+	push_button ('9', 50); push_button (' ', 50);
+	push_button ('C', 50); push_button (' ', 50);
+
+	push_button ('*', 50); push_button (' ', 50);
+	push_button ('0', 50); push_button (' ', 50);
+	push_button ('#', 50); push_button (' ', 50);
+	push_button ('D', 50); push_button (' ', 50);
+
+	dw_printf ("\nShould reject very short pulses.\n\n");
+	
+	push_button ('1', 20); push_button (' ', 50);
+	push_button ('1', 20); push_button (' ', 50);
+	push_button ('1', 20); push_button (' ', 50);
+	push_button ('1', 20); push_button (' ', 50);
+	push_button ('1', 20); push_button (' ', 50);
+
+	dw_printf ("\nTest timeout after inactivity.\n\n");
+	/* For this test we use 1 second. */
+	/* In practice, it will probably more like 5. */
+
+	push_button ('1', 250); push_button (' ', 500);
+	push_button ('2', 250); push_button (' ', 500);
+	push_button ('3', 250); push_button (' ', 1200);
+
+	push_button ('7', 250); push_button (' ', 500);
+	push_button ('8', 250); push_button (' ', 500);
+	push_button ('9', 250); push_button (' ', 1200);
+
+	/* Check for expected results. */
+
+	push_button ('?', 0);
+
+}  /* end main */
+
+#endif
+
+/* end dtmf.c */
+
diff --git a/dtmf.h b/dtmf.h
index edba698..dea09df 100644
--- a/dtmf.h
+++ b/dtmf.h
@@ -1,12 +1,12 @@
-/* dtmf.h */
-
-
-#include "audio.h"
-
-void dtmf_init (struct audio_s *p_audio_config);
-
-char dtmf_sample (int c, float input);
-
-
-/* end dtmf.h */
-
+/* dtmf.h */
+
+
+#include "audio.h"
+
+void dtmf_init (struct audio_s *p_audio_config);
+
+char dtmf_sample (int c, float input);
+
+
+/* end dtmf.h */
+
diff --git a/dw-icon.ico b/dw-icon.ico
old mode 100755
new mode 100644
diff --git a/dw-icon.png b/dw-icon.png
old mode 100755
new mode 100644
diff --git a/dw-icon.rc b/dw-icon.rc
old mode 100755
new mode 100644
diff --git a/dw-start.sh b/dw-start.sh
old mode 100755
new mode 100644
index b4829b4..ac86396
--- a/dw-start.sh
+++ b/dw-start.sh
@@ -14,21 +14,30 @@
 # This script has some specifics the Raspberry Pi.
 # Some adjustments might be needed for other Linux variations.
 #
+
+#
+# When running from cron, we have a very minimal environment
+# including PATH=/usr/bin:/bin.
+#
+
+export PATH=/usr/local/bin:$PATH
+
 # First wait a little while in case we just rebooted
 # and the desktop hasn't started up yet.
 #
 
 sleep 30
+LOGFILE=/tmp/dw-start.log
 
 #
 # Nothing to do if it is already running.
 #
 
-a=`ps -ef | grep direwolf | grep -v grep`
+a=`pgrep direwolf`
 if [ "$a" != "" ] 
 then
   #date >> /tmp/dw-start.log
-  #echo "Already running." >> /tmp/dw-start.log
+  #echo "Already running." >> $LOGFILE
   exit
 fi
 
@@ -52,26 +61,40 @@ then
   export DISPLAY="$d"
 fi
 
-echo "DISPLAY=$DISPLAY" >> /tmp/dw-start.log
+echo "DISPLAY=$DISPLAY" >> $LOGFILE
+
+echo "Start up application." >> $LOGFILE
 
-echo "Start up application." >> /tmp/dw-start.log
+#
+# For normal operation as TNC, digipeater, IGate, etc.
+# Print audio statistics each 100 seconds for troubleshooting.
+#
+
+DWCMD="direwolf -a 100"
+
+# Alternative for running with SDR receiver.
+# Piping one application into another makes it a little more complicated.
+# We need to use bash for the | to be recognized. 
+
+#DWCMD="bash -c 'rtl_fm -f 144.39M - | direwolf -c sdr.conf -r 24000 -D 1 -'"
 
 # 
 # Adjust for your particular situation:  gnome-terminal, xterm, etc.
 #
 
+
 if [ -x /usr/bin/lxterminal ]
 then
-  /usr/bin/lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf" &
+  /usr/bin/lxterminal -t "Dire Wolf" -e "$DWCMD" &
 elif [ -x /usr/bin/xterm ] 
 then
-  /usr/bin/xterm -bg white -fg black -e /usr/local/bin/direwolf &
+  /usr/bin/xterm -bg white -fg black -e "$DWCMD" &
 elif [ -x /usr/bin/x-terminal-emulator ]
 then
-  /usr/bin/x-terminal-emulator -e  /usr/local/bin/direwolf &
+  /usr/bin/x-terminal-emulator -e "$DWCMD" &
 else
   echo "Did not find an X terminal emulator."
 fi
 
-echo "-----------------------" >> /tmp/dw-start.log
+echo "-----------------------" >> $LOGFILE
 
diff --git a/dwespeak.bat b/dwespeak.bat
old mode 100755
new mode 100644
index c045c9c..8ea2150
--- a/dwespeak.bat
+++ b/dwespeak.bat
@@ -1,8 +1,8 @@
-echo off 
-
-set chan=%1
-set msg=%2
-
-sleep 1
-
+echo off 
+
+set chan=%1
+set msg=%2
+
+sleep 1
+
 "C:\Program Files (x86)\eSpeak\command_line\espeak.exe" -v en-sc %msg%
\ No newline at end of file
diff --git a/dwespeak.sh b/dwespeak.sh
old mode 100755
new mode 100644
diff --git a/dwgps.c b/dwgps.c
index ceb2f07..f8cc37e 100644
--- a/dwgps.c
+++ b/dwgps.c
@@ -1,419 +1,268 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      dwgps.c
- *
- * Purpose:   	Interface to location data, i.e. GPS receiver.
- *		
- * Description:	Tracker beacons need to know the current location.
- *		At this time, I can't think of any other reason why
- *		we would need this information.
- *
- *		For Linux, we use gpsd and libgps.
- *		This has the extra benefit that the system clock can
- *		be set from the GPS signal.
- *
- *		Not yet implemented for Windows.  Not sure how yet.
- *		The Windows location API is new in Windows 7.
- *		At the end of 2013, about 1/3 of Windows users are
- *		still using XP so that still needs to be supported.	
- *
- * Reference:	
- *
- *---------------------------------------------------------------*/
-
-#if TEST
-#define ENABLE_GPS 1
-#endif
-
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/time.h>
-
-#if __WIN32__
-#include <windows.h>
-#else
-#if ENABLE_GPS
-#include <gps.h>
-
-#if GPSD_API_MAJOR_VERSION != 5
-#error libgps API version might be incompatible.
-#endif
-
-#endif
-#endif
-
-#include "direwolf.h"
-#include "textcolor.h"
-#include "dwgps.h"
-
-
-/* Was init successful? */
-
-static enum { INIT_NOT_YET, INIT_SUCCESS, INIT_FAILED } init_status = INIT_NOT_YET;
-
-#if __WIN32__
-#include <windows.h>
-#else
-#if ENABLE_GPS
-
-static struct gps_data_t gpsdata;
-
-#endif
-#endif
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dwgps_init
- *
- * Purpose:    	Intialize the GPS interface.
- *
- * Inputs:	none.
- *		
- * Returns:	0 = success
- *		-1 = failure
- *
- * Description:	For Linux, this maps into gps_open.
- *		Not yet implemented for Windows.
- *
- *--------------------------------------------------------------------*/
-
-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;
-	return (-1);
-
-#elif ENABLE_GPS
-
-	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 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");
-	  }
-	  else {
-	    dw_printf ("%s\n", gps_errstr(errno));
-	  }
-	  init_status = INIT_FAILED;
-	  return (-1);
-	}
-	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");
-	init_status = INIT_FAILED;
-	return (-1);
-
-#endif
-
-}  /* end dwgps_init */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dwgps_read
- *
- * Purpose:    	Obtain current location from GPS receiver.
- *
- * Outputs:	*plat		- Latitude.
- *		*plon		- Longitude.
- *		*pspeed		- Speed, knots.
- *		*pcourse	- Course over ground, degrees.
- *		*palt		- Altitude, meters.
- *
- * Returns:	-1 = error
- *		0 = location currently not available (no fix)
- *		2 = 2D fix, lat/lon, speed, and course are set.
- *		3 - 3D fix, altitude is also set.
- *
- *--------------------------------------------------------------------*/
-
-int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float *palt)
-{
-#if __WIN32__
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Internal error, dwgps_read, shouldn't be here.\n");
-	return (-1);
-
-#elif ENABLE_GPS
-
-	int err;
-
-	if (init_status != INIT_SUCCESS) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error, dwgps_read without successful init.\n");
-	  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;
-	     *plon = gpsdata.fix.longitude;
-	     *pcourse = gpsdata.fix.track;		
-	     *pspeed = MPS_TO_KNOTS * gpsdata.fix.speed; /* libgps uses meters/sec */
-
-	     if (gpsdata.fix.mode >= MODE_3D) {
-	       *palt = gpsdata.fix.altitude;
-	       return (3);
-	     }
-	     return (2);
-	   }
-
-	   /* No fix.  Probably temporary condition. */
-	   return (0);
-	}
-	else if (err == 0) {
-
-	   /* No data available at the present time. */
-	   return (0);
-	}
-	else {
-
-	  /* More serious error. */
-	  return (-1);
-	}
-#else 
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Internal error, dwgps_read, shouldn't be here.\n");
-	return (-1);
-#endif
-
-} /* end dwgps_read */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dwgps_term
- *
- * Purpose:    	Shut down GPS interface before exiting from application.
- *
- * Inputs:	none.
- *
- * Returns:	none.
- *
- *--------------------------------------------------------------------*/
-
-void dwgps_term (void) {
-
-#if __WIN32__
-	
-#elif ENABLE_GPS
-
-	if (init_status == INIT_SUCCESS) {
-
-#ifndef USE_GPS_SHM
-	  gps_stream(&gpsdata, WATCH_DISABLE, NULL);
-#endif
-	  gps_close (&gpsdata);
-	}
-#else 
-
-#endif
-
-} /* end dwgps_term */
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:    	Simple unit test for other functions in this file.
- *
- * Description: Compile with -DTEST option.
- *
- *			gcc -DTEST dwgps.c textcolor.c -lgps
- *			./a.out
- *
- *--------------------------------------------------------------------*/
-
-#if TEST
-
-int main (int argc, char *argv[])
-{
-
-#if __WIN32__
-
-	printf ("Not in win32 version yet.\n");
-
-#elif ENABLE_GPS
-	int err;
-	int fix;
-	double lat;
-	double lon;
-	float speed;
-	float course;
-	float alt;
-
-	err = dwgps_init ();
-
-	if (err != 0) exit(1);
-
-	while (1) {
-	  fix = dwgps_read (&lat, &lon, &speed, &course, &alt)
;
-	  switch (fix) {
-	    case 3:
-	    case 2:
-	      dw_printf ("%.6f  %.6f", lat, lon);
-	      dw_printf ("  %.1f knots  %.0f degrees", speed, course);
-	      if (fix==3) dw_printf ("  altitude = %.1f meters", alt);
-	      dw_printf ("\n");
-	      break;
-	    case 0:
-	      dw_printf ("location currently not available.\n");
-	      break;
-	    default:
-	      dw_printf ("ERROR getting GPS information.\n");
-	  }
-	  sleep (3);
-	}
-
-
-#else 
-
-	printf ("Test: Shouldn't be here.\n");
-#endif
-
-} /* end main */
-
-
-#endif
-
-
-
-/* end dwgps.c */
-
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dwgps.c
+ *
+ * Purpose:   	Interface for obtaining location from GPS.
+ *		
+ * Description:	This is a wrapper for two different implementations:
+ *
+ *		(1) Read NMEA sentences from a serial port (or USB
+ *		    that looks line one).  Available for all platforms.
+ *
+ *		(2) Read from gpsd.  Not available for Windows.
+ *		    Including this is optional because it depends
+ *		    on another external software component.
+ *
+ *
+ * API:		dwgps_init	Connect to data stream at start up time.
+ *
+ *		dwgps_read	Return most recent location to application.
+ *
+ *		dwgps_print	Print contents of structure for debugging.
+ *
+ *		dwgps_term	Shutdown on exit.
+ *
+ *
+ * from below:	dwgps_set_data	Called from other two implementations to
+ *				save data until it is needed.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "dwgps.h"
+#include "dwgpsnmea.h"
+#include "dwgpsd.h"
+
+
+static int s_dwgps_debug = 0;		/* Enable debug output. */
+					/* >= 2 show updates from GPS. */
+					/* >= 1 show results from dwgps_read. */
+
+/*
+ * The GPS reader threads deposit current data here when it becomes available.
+ * dwgps_read returns it to the requesting application.
+ *
+ * A critical region to avoid inconsistency between fields.
+ */
+
+static dwgps_info_t s_dwgps_info = {
+	.timestamp = 0,
+	.fix = DWFIX_NOT_INIT,			/* to detect read without init. */
+	.dlat = G_UNKNOWN,
+	.dlon = G_UNKNOWN,
+	.speed_knots = G_UNKNOWN,
+	.track = G_UNKNOWN,
+	.altitude = G_UNKNOWN
+};
+
+static dw_mutex_t s_gps_mutex;
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgps_init
+ *
+ * Purpose:    	Intialize the GPS interface.
+ *
+ * Inputs:	pconfig		Configuration settings.  This might include
+ *				serial port name for direct connect and host
+ *				name or address for network connection.
+ *
+ *		debug	- If >= 1, print results when dwgps_read is called.
+ *				(In this file.)
+ *
+ *			  If >= 2, location updates are also printed.
+ *				(In other two related files.)
+ *
+ * Returns:	none
+ *
+ * Description:	Call corresponding functions for implementations.
+ * 		Normally we would expect someone to use either GPSNMEA or
+ *		GPSD but there is nothing to prevent use of both at the
+ *		same time.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void dwgps_init (struct misc_config_s *pconfig, int debug)
+{
+
+	s_dwgps_debug = debug;
+
+	dw_mutex_init (&s_gps_mutex);
+
+	dwgpsnmea_init (pconfig, debug);
+
+#if ENABLE_GPSD
+
+	dwgpsd_init (pconfig, debug);
+
+#endif
+
+	SLEEP_MS(500);		/* So receive thread(s) can clear the */
+				/* not init status before it gets checked. */
+
+} /* end dwgps_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgps_clear
+ *
+ * Purpose:    	Clear the gps info structure.
+ *
+ *--------------------------------------------------------------------*/
+
+void dwgps_clear (dwgps_info_t *gpsinfo)
+{
+	gpsinfo->timestamp = 0;
+	gpsinfo->fix = DWFIX_NOT_SEEN;
+	gpsinfo->dlat = G_UNKNOWN;
+	gpsinfo->dlon = G_UNKNOWN;
+	gpsinfo->speed_knots = G_UNKNOWN;
+	gpsinfo->track = G_UNKNOWN;
+	gpsinfo->altitude = G_UNKNOWN;
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgps_read
+ *
+ * Purpose:     Return most recent location data available.
+ *
+ * Outputs:	gpsinfo		- Structure with latitude, longitude, etc.
+ *
+ * Returns:	Position fix quality.  Same as in structure.
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+dwfix_t dwgps_read (dwgps_info_t *gpsinfo)
+{
+
+	dw_mutex_lock (&s_gps_mutex);
+
+	memcpy (gpsinfo, &s_dwgps_info, sizeof(*gpsinfo));
+
+	dw_mutex_unlock (&s_gps_mutex);
+
+	if (s_dwgps_debug >= 1) {
+	  text_color_set (DW_COLOR_DEBUG);
+	  dwgps_print ("gps_read: ", gpsinfo);
+	}
+
+	// TODO: Should we check timestamp and complain if very stale?
+	// or should we leave that up to the caller?
+
+	return (s_dwgps_info.fix);
+} 
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgps_print
+ *
+ * Purpose:     Print gps information for debugging.
+ *
+ * Inputs:	msg		- Message for prefix on line.
+ *		gpsinfo		- Structure with latitude, longitude, etc.
+ *
+ * Description:	Caller is responsible for setting text color.
+ *
+ *--------------------------------------------------------------------*/
+
+void dwgps_print (char *msg, dwgps_info_t *gpsinfo)
+{
+
+	dw_printf ("%stime=%d fix=%d lat=%.6f lon=%.6f trk=%.0f spd=%.1f alt=%.0f\n",
+			msg,
+			(int)gpsinfo->timestamp, (int)gpsinfo->fix,
+			gpsinfo->dlat, gpsinfo->dlon,
+			gpsinfo->track, gpsinfo->speed_knots,
+			gpsinfo->altitude);
+
+}  /* end dwgps_set_data */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgps_term
+ *
+ * Purpose:    	Shut down GPS interface before exiting from application.
+ *
+ * Inputs:	none.
+ *
+ * Returns:	none.
+ *
+ *--------------------------------------------------------------------*/
+
+void dwgps_term (void) {
+
+	dwgpsnmea_term ();
+
+#if ENABLE_GPSD
+	dwgpsd_term ();
+#endif
+
+} /* end dwgps_term */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgps_set_data
+ *
+ * Purpose:     Called by the GPS interfaces when new data is available.
+ *
+ * Inputs:	gpsinfo		- Structure with latitude, longitude, etc.
+ *
+ *--------------------------------------------------------------------*/
+
+void dwgps_set_data (dwgps_info_t *gpsinfo)
+{
+
+	/* Debug print is handled by the two callers so */
+	/* we can distinguish the source. */
+
+	dw_mutex_lock (&s_gps_mutex);
+
+	memcpy (&s_dwgps_info, gpsinfo, sizeof(s_dwgps_info));
+
+	dw_mutex_unlock (&s_gps_mutex);
+
+}  /* end dwgps_set_data */
+
+
+/* end dwgps.c */
+
+
+
diff --git a/dwgps.h b/dwgps.h
index ffb563d..78f821f 100644
--- a/dwgps.h
+++ b/dwgps.h
@@ -1,15 +1,61 @@
-
-/* dwgps.h */
-
-
-int dwgps_init (void);
-
-int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float *palt);
-
-void dwgps_term (void);
-
-
-/* end dwgps.h */
-
-
-
+
+/* dwgps.h */
+
+#ifndef DWGPS_H
+#define DWGPS_H 1
+
+
+#include <time.h>
+#include "config.h"	/* for struct misc_config_s */
+
+
+/*
+ * Values for fix, equivalent to values from libgps.
+ *	-2 = not initialized.
+ *	-1 = error communicating with GPS receiver.
+ *	0 = nothing heard yet.
+ *	1 = had signal but lost it.
+ *	2 = 2D.
+ *	3 = 3D.
+ *
+ * Undefined float & double values are set to G_UNKNOWN.
+ *
+ */
+
+enum dwfix_e { DWFIX_NOT_INIT= -2, DWFIX_ERROR= -1, DWFIX_NOT_SEEN=0, DWFIX_NO_FIX=1, DWFIX_2D=2, DWFIX_3D=3 };
+
+typedef enum dwfix_e dwfix_t;
+
+typedef struct dwgps_info_s {
+	time_t timestamp;	/* When last updated.  System time. */
+	dwfix_t fix;		/* Quality of position fix. */
+	double dlat;		/* Latitude.  Valid if fix >= 2. */
+	double dlon;		/* Longitude. Valid if fix >= 2. */
+	float speed_knots;	/* libgps uses meters/sec but we use GPS usual knots. */
+	float track;		/* What is difference between track and course? */
+	float altitude;		/* meters above mean sea level. Valid if fix == 3. */
+} dwgps_info_t;
+
+
+
+
+
+void dwgps_init (struct misc_config_s *pconfig, int debug);
+
+void dwgps_clear (dwgps_info_t *gpsinfo);
+
+dwfix_t dwgps_read (dwgps_info_t *gpsinfo);
+
+void dwgps_print (char *msg, dwgps_info_t *gpsinfo);
+
+void dwgps_term (void);
+
+void dwgps_set_data (dwgps_info_t *gpsinfo);
+
+
+#endif /* DWGPS_H 1 */
+
+/* end dwgps.h */
+
+
+
diff --git a/dwgpsd.c b/dwgpsd.c
new file mode 100644
index 0000000..37688ff
--- /dev/null
+++ b/dwgpsd.c
@@ -0,0 +1,440 @@
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dwgps.c
+ *
+ * Purpose:   	Interface to location data, i.e. GPS receiver.
+ *		
+ * Description:	For Linux, we would normally want to use gpsd and libgps.
+ *		This allows multiple applications to access the GPS data,
+ *		without fighting over the same serial port, and has the 
+ *		extra benefit that the system clock can be set from the GPS signal.
+ *
+ * Reference:	http://www.catb.org/gpsd/
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+
+#if __WIN32__
+#error Not for Windows
+#endif
+
+#if ENABLE_GPSD
+#include <gps.h>
+
+// Debian bug report:  direwolf (1.2-1) FTBFS with libgps22 as part of the gpsd transition (#803605):
+// dwgps.c claims to only support GPSD_API_MAJOR_VERSION 5, but also builds successfully with
+// GPSD_API_MAJOR_VERSION 6 provided by libgps22 when the attached patch is applied.
+#if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 6
+#error libgps API version might be incompatible.
+#endif
+
+#endif
+
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "dwgps.h"
+#include "dwgpsd.h"
+
+
+
+static int s_debug = 0;		/* Enable debug output. */
+				/* >= 1 show results from dwgps_read. */
+				/* >= 2 show updates from GPS. */
+
+static void * read_gpsd_thread (void *arg);
+
+/*
+ * Information for interface to gpsd daemon. 
+ */
+
+static struct gps_data_t gpsdata;
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgpsd_init
+ *
+ * Purpose:    	Intialize the GPS interface.
+ *
+ * Inputs:	pconfig		Configuration settings.  This includes
+ *				host name or address for network connection.
+ *
+ *		debug	- If >= 1, print results when dwgps_read is called.
+ *				(In different file.)
+ *
+ *			  If >= 2, location updates are also printed.
+ *				(In this file.)
+ *		
+ * Returns:	1 = success
+ *		0 = nothing to do  (no host specified in config)
+ *		-1 = failure
+ *
+ * Description:	- Establish socket connection with gpsd.
+ *		- Start up thread to process incoming data.
+ *		  It reads from the daemon and deposits into
+ *		  shared region via dwgps_put_data.
+ *
+ * 		The application calls dwgps_read to get the most 
+ 8		recent information.			
+ *
+ *--------------------------------------------------------------------*/
+
+/*
+ * Historical notes:
+ *
+ * Originally, I wanted to use the shared memory interface to gpsd
+ * because it is simpler and more efficient.  Just access it when we
+ * actually need the data and we don't have a lot of extra unnecessary
+ * busy work going on.
+ *
+ * The current version of gpsd, supplied with Raspian (Wheezy), is 3.6 from back in 
+ * May 2012, is missing support for the shared memory interface.  
+ * 
+ * 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.
+ *
+ * Update:  January 2016.
+ *
+ *	I'm told that it might work in Raspian, Jessie version.
+ *	Haven't tried it yet.
+ */
+
+
+
+int dwgpsd_init (struct misc_config_s *pconfig, int debug)
+{
+
+#if ENABLE_GPSD
+
+	pthread_t read_gps_tid;
+	int e;
+	int err;
+	int arg = 0;
+	char sport[12];
+	dwgps_info_t info;
+
+	s_debug = debug;
+
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dwgpsd_init()\n");
+	}
+
+/* 
+ * Socket interface to gpsd.
+ */
+
+	if (strlen(pconfig->gpsd_host) == 0) {
+
+	  /* Nothing to do.  Leave initial fix value of errror. */
+	  return (0);
+	}	  
+
+	snprintf (sport, sizeof(sport), "%d", pconfig->gpsd_port);
+	err = gps_open (pconfig->gpsd_host, sport, &gpsdata);
+	if (err != 0) {
+	  dwgps_info_t info;
+
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Unable to connect to GPSD stream at %s:%s.\n", pconfig->gpsd_host, sport);
+	  dw_printf ("%s\n", gps_errstr(errno));
+
+	  return (-1);
+	}
+
+	gps_stream(&gpsdata, WATCH_ENABLE | WATCH_JSON, NULL);
+
+	e = pthread_create (&read_gps_tid, NULL, read_gpsd_thread, (void *)(long)arg);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Could not create GPS reader thread");
+	  return (-1);
+	}
+
+/* success */
+
+	return (1);
+
+
+#else	/* end ENABLE_GPSD */
+
+	// Shouldn't be here. 
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("GPSD interface not enabled in this version.\n");
+	dw_printf ("See documentation on how to rebuild with ENABLE_GPSD.\n");
+
+	return (-1);
+
+#endif
+
+}  /* end dwgps_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        read_gpsd_thread
+ *
+ * Purpose:     Read information from GPSD, as it becomes available, and
+ *		store in memory shared with dwgps_read.
+ *
+ * Inputs:	arg		- not used
+ *
+ *--------------------------------------------------------------------*/
+
+#define TIMEOUT 30
+
+#if ENABLE_GPSD
+
+static void * read_gpsd_thread (void *arg)
+{
+	dwgps_info_t info;
+
+	if (s_debug >= 1) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("starting read_gpsd_thread (%d)\n", (int)(long)arg);
+	}
+
+	dwgps_clear (&info);
+	info.fix = DWFIX_NOT_SEEN;	/* clear not init state. */
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dwgps_print ("GPSD: ", &info);
+	}
+	dwgps_set_data (&info);
+
+	while (1) {
+
+          if ( ! gps_waiting(&gpsdata, TIMEOUT * 1000000)) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("GPSD: Timeout waiting for GPS data.\n");
+	    /* Fall thru to read which should get error and bail out. */
+	  }
+
+	  if (gps_read (&gpsdata) == -1) {
+	    text_color_set(DW_COLOR_ERROR);
+
+	    dw_printf ("------------------------------------------\n");
+	    dw_printf ("GPSD: Lost communication with gpsd server.\n");
+	    dw_printf ("------------------------------------------\n");
+
+	    info.fix = DWFIX_ERROR;
+	    if (s_debug >= 2) {
+	      text_color_set(DW_COLOR_DEBUG);
+	      dwgps_print ("GPSD: ", &info);
+	    }
+	    dwgps_set_data (&info);
+
+	    break;   // Jump out of loop and terminate thread.
+	  }
+
+	  switch (gpsdata.fix.mode) {
+	    default:
+	    case MODE_NOT_SEEN:
+	      if (info.fix >= DWFIX_2D) {
+		text_color_set(DW_COLOR_INFO);
+	        dw_printf ("GPSD: Lost location fix.\n");
+	      }
+	      info.fix = DWFIX_NOT_SEEN;
+	      break;
+
+	    case MODE_NO_FIX:
+	      if (info.fix >= DWFIX_2D) {
+		text_color_set(DW_COLOR_INFO);
+	        dw_printf ("GPSD: Lost location fix.\n");
+	      }
+	      info.fix = DWFIX_NO_FIX;
+	      break;
+
+	    case MODE_2D:
+	      if (info.fix != DWFIX_2D) {
+		text_color_set(DW_COLOR_INFO);
+	        dw_printf ("GPSD: Location fix is now 2D.\n");
+	      }
+	      info.fix = DWFIX_2D;
+	      break;
+
+	    case MODE_3D:
+	      if (info.fix != DWFIX_3D) {
+		text_color_set(DW_COLOR_INFO);
+	        dw_printf ("GPSD: Location fix is now 3D.\n");
+	      }
+	      info.fix = DWFIX_3D;
+	      break;
+	  }
+
+	    /* Data is available. */
+	    // TODO:  what is gpsdata.status?
+
+
+	  if (gpsdata.status >= STATUS_FIX && gpsdata.fix.mode >= MODE_2D) {
+
+	    info.dlat = isnan(gpsdata.fix.latitude) ? G_UNKNOWN : gpsdata.fix.latitude;
+	    info.dlon = isnan(gpsdata.fix.longitude) ? G_UNKNOWN : gpsdata.fix.longitude;
+	    info.track = isnan(gpsdata.fix.track) ? G_UNKNOWN : gpsdata.fix.track;
+	    info.speed_knots = isnan(gpsdata.fix.speed) ? G_UNKNOWN : (MPS_TO_KNOTS * gpsdata.fix.speed);
+
+	    if (gpsdata.fix.mode >= MODE_3D) {
+	      info.altitude = isnan(gpsdata.fix.altitude) ? G_UNKNOWN : gpsdata.fix.altitude;
+	    }
+	  }
+
+	  info.timestamp = time(NULL);
+	  if (s_debug >= 2) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dwgps_print ("GPSD: ", &info);
+	  }
+	  dwgps_set_data (&info);
+	}
+
+	return(0);	// Terminate thread on serious error.
+
+} /* end read_gps_thread */
+
+#endif
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgpsd_term
+ *
+ * Purpose:    	Shut down GPS interface before exiting from application.
+ *
+ * Inputs:	none.
+ *
+ * Returns:	none.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void dwgpsd_term (void) {
+
+#if ENABLE_GPSD
+
+	gps_close (&gpsdata);
+
+#endif
+
+} /* end dwgpsd_term */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:    	Simple unit test for other functions in this file.
+ *
+ * Description: Compile with -DGPSTEST option.
+ *
+ *		 gcc  -DGPSTEST -DENABLE_GPSD dwgpsd.c dwgps.c textcolor.o  latlong.o misc.a -lm -lpthread -lgps
+ *		./a.out
+ *
+ *--------------------------------------------------------------------*/
+
+#if GPSTEST
+
+
+int dwgpsnmea_init (struct misc_config_s *pconfig, int debug)
+{
+	return (0);
+}
+void dwgpsnmea_term (void)
+{
+	return;
+}
+
+
+int main (int argc, char *argv[])
+{
+	struct misc_config_s config;
+	dwgps_info_t info;
+
+
+	memset (&config, 0, sizeof(config));
+	strlcpy (config.gpsd_host, "localhost", sizeof(config.gpsd_host));
+	config.gpsd_port = atoi(DEFAULT_GPSD_PORT);
+
+	dwgps_init (&config, 3);
+
+	while (1) {
+	  dwfix_t fix;
+
+	  fix = dwgps_read (&info)
;
+	  text_color_set (DW_COLOR_INFO);
+	  switch (fix) {
+	    case DWFIX_2D:
+	    case DWFIX_3D:
+	      dw_printf ("%.6f  %.6f", info.dlat, info.dlon);
+	      dw_printf ("  %.1f knots  %.0f degrees", info.speed_knots, info.track);
+	      if (fix==3) dw_printf ("  altitude = %.1f meters", info.altitude);
+	      dw_printf ("\n");
+	      break;
+	    case DWFIX_NOT_SEEN:
+	    case DWFIX_NO_FIX:
+	      dw_printf ("Location currently not available.\n");
+	      break;
+	    case DWFIX_NOT_INIT:
+	      dw_printf ("GPS Init failed.\n");
+	      exit (1);
+	    case DWFIX_ERROR:
+	    default:
+	      dw_printf ("ERROR getting GPS information.\n");
+	      break;
+	  }
+	  SLEEP_SEC (3);
+	}
+
+} /* end main */
+
+
+#endif
+
+
+
+/* end dwgpsd.c */
+
+
+
diff --git a/dwgpsd.h b/dwgpsd.h
new file mode 100644
index 0000000..4c0e0fd
--- /dev/null
+++ b/dwgpsd.h
@@ -0,0 +1,22 @@
+
+/* dwgpsd.h   -   For communicating with daemon */
+
+
+
+#ifndef DWGPSD_H
+#define DWGPSD_H 1
+
+#include "config.h"
+
+
+int dwgpsd_init (struct misc_config_s *pconfig, int debug);
+
+void dwgpsd_term (void);
+
+#endif
+
+
+/* end dwgpsd.h */
+
+
+
diff --git a/dwgpsnmea.c b/dwgpsnmea.c
new file mode 100644
index 0000000..0de0512
--- /dev/null
+++ b/dwgpsnmea.c
@@ -0,0 +1,800 @@
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      dwgpsnmea.c
+ *
+ * Purpose:   	process NMEA sentences from a GPS receiver.
+ *		
+ * Description:	This version is available for all operating systems.
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "dwgps.h"
+#include "dwgpsnmea.h"
+#include "serial_port.h"
+
+
+static int s_debug = 0;		/* Enable debug output. */
+				/* See dwgpsnmea_init description for values. */
+					
+
+
+
+#if __WIN32__
+static unsigned __stdcall read_gpsnmea_thread (void *arg);
+#else
+static void * read_gpsnmea_thread (void *arg);
+#endif
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgpsnmea_init
+ *
+ * Purpose:    	Open serial port for the GPS receiver.
+ *
+ * Inputs:	pconfig		Configuration settings.  This includes
+ *				serial port name for direct connect.
+ *
+ *		debug	- If >= 1, print results when dwgps_read is called.
+ *				(In different file.)
+ *
+ *			  If >= 2, location updates are also printed.
+ *				(In this file.)
+ *				Why not do it in dwgps_set_data() ?
+ *				Here, we can prefix it with GPSNMEA to
+ *				distinguish it from GPSD.
+ *
+ *			  If >= 3, Also the NMEA sentences.
+ *				(In this file.)
+ *		
+ * Returns:	1 = success
+ *		0 = nothing to do  (no serial port specified in config)
+ *		-1 = failure
+ *
+ * Description:	When talking directly to GPS receiver  (any operating system):
+ *
+ *			- Open the appropriate serial port.
+ *			- Start up thread to process incoming data.
+ *			  It reads from the serial port and deposits into
+ *			  dwgps_info, above.
+ *
+ * 		The application calls dwgps_read to get the most recent information.			
+ *
+ *--------------------------------------------------------------------*/
+
+/* Make this static and available to all functions so term function can access it. */
+
+static MYFDTYPE s_gpsnmea_port_fd = MYFDERROR;   /* Handle for serial port. */
+
+
+int dwgpsnmea_init (struct misc_config_s *pconfig, int debug)
+{
+	//dwgps_info_t info;
+#if __WIN32__
+	HANDLE read_gps_th;
+#else
+	pthread_t read_gps_tid;
+	int e;
+#endif
+
+	s_debug = debug;
+
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("dwgpsnmea_init()\n");
+	}
+
+	if (strlen(pconfig->gpsnmea_port) == 0) {
+
+	  /* Nothing to do.  Leave initial fix value for not init. */
+	  return (0);
+	}
+
+/*
+ * Open serial port connection.
+ * 4800 baud is standard for GPS.
+ * Should add an option to allow changing someday.
+ */
+
+	s_gpsnmea_port_fd = serial_port_open (pconfig->gpsnmea_port, 4800);
+
+	if (s_gpsnmea_port_fd != MYFDERROR) {
+#if __WIN32__
+	  read_gps_th = (HANDLE)_beginthreadex (NULL, 0, read_gpsnmea_thread, (void*)(long)s_gpsnmea_port_fd, 0, NULL);
+	  if (read_gps_th == NULL) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Could not create GPS NMEA listening thread.\n");
+	    return (-1);
+	  }
+#else
+	  int e;
+	  e = pthread_create (&read_gps_tid, NULL, read_gpsnmea_thread, (void*)(long)s_gpsnmea_port_fd);
+	  if (e != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    perror("Could not create GPS NMEA listening thread.");
+	    return (-1);
+	  }
+#endif
+	}
+	else {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not open serial port %s for GPS receiver.\n", pconfig->gpsnmea_port);
+	  return (-1);
+	}
+
+/* success */
+
+	return (1);
+
+}  /* end dwgpsnmea_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        read_gpsnmea_thread
+ *
+ * Purpose:     Read information from GPS, as it becomes available, and
+ *		store it for later retrieval by dwgps_read.
+ *
+ * Inputs:	fd	- File descriptor for serial port.
+ *
+ * Description:	This version reads from serial port and parses the 
+ *		NMEA sentences.
+ *
+ *--------------------------------------------------------------------*/
+
+#define TIMEOUT 5
+
+
+#if __WIN32__
+static unsigned __stdcall read_gpsnmea_thread (void *arg)
+#else
+static void * read_gpsnmea_thread (void *arg)
+#endif
+{
+	MYFDTYPE fd = (MYFDTYPE)(long)arg;
+
+// Maximum length of message from GPS receiver is 82 according to some people.  
+// Make buffer considerably larger to be safe.
+
+#define NMEA_MAX_LEN 160	
+
+	char gps_msg[NMEA_MAX_LEN];
+	int gps_msg_len = 0;
+	dwgps_info_t info;
+
+
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("read_gpsnmea_thread (%d)\n", (int)(long)arg);
+	}
+
+	dwgps_clear (&info);
+	info.fix = DWFIX_NOT_SEEN;	/* clear not init state. */
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dwgps_print ("GPSNMEA: ", &info);
+	}
+	dwgps_set_data (&info);
+
+
+	while (1) {
+	  int ch;
+
+	  ch = serial_port_get1(fd);
+
+	  if (ch < 0) {
+
+	    /* This might happen if a USB  device is unplugged. */
+	    /* I can't imagine anything that would cause it with */
+	    /* a normal serial port. */
+
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("----------------------------------------------\n");
+	    dw_printf ("GPSNMEA: Lost communication with GPS receiver.\n");
+	    dw_printf ("----------------------------------------------\n");
+
+	    info.fix = DWFIX_ERROR;
+	    if (s_debug >= 2) {
+	      text_color_set(DW_COLOR_DEBUG);
+	      dwgps_print ("GPSNMEA: ", &info);
+	    }
+	    dwgps_set_data (&info);
+
+	    // TODO: doesn't exist yet - serial_port_close(fd);
+	    s_gpsnmea_port_fd = MYFDERROR;
+
+	    break;	/* terminate thread. */
+	  }
+
+	  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] == '$') {
+
+	      dwfix_t f;
+
+	      if (s_debug >= 3) {
+	        text_color_set(DW_COLOR_DEBUG);
+	        dw_printf ("%s\n", gps_msg);
+	      }
+
+/* Process sentence. */
+
+	      if (strncmp(gps_msg, "$GPRMC", 6) == 0) {
+
+		f = dwgpsnmea_gprmc (gps_msg, 0, &info.dlat, &info.dlon, &info.speed_knots, &info.track);
+
+	        if (f == DWFIX_ERROR) {
+
+		  /* Parse error.  Shouldn't happen.  Better luck next time. */
+	            text_color_set(DW_COLOR_INFO);
+	            dw_printf ("GPSNMEA: Error parsing $GPRMC sentence.\n");
+	            dw_printf ("%s\n", gps_msg);
+	        }
+	        else if (f == DWFIX_2D) {
+
+	          if (info.fix != DWFIX_2D && info.fix != DWFIX_3D) {
+
+		    text_color_set(DW_COLOR_INFO);
+	            dw_printf ("GPSNMEA: Location fix is now available.\n");
+
+		    info.fix = DWFIX_2D;   // Don't know if 2D or 3D.  Take minimum.
+	          }
+	  	  info.timestamp = time(NULL);
+	          if (s_debug >= 2) {
+	            text_color_set(DW_COLOR_DEBUG);
+	            dwgps_print ("GPSNMEA: ", &info);
+	          }
+	          dwgps_set_data (&info);
+	        }
+	        else {
+
+	          if (info.fix == DWFIX_2D || info.fix == DWFIX_3D) {
+
+		    text_color_set(DW_COLOR_INFO);
+	            dw_printf ("GPSNMEA: Lost location fix.\n");
+	          }
+	          info.fix = f;		/* lost it. */
+	  	  info.timestamp = time(NULL);
+	          if (s_debug >= 2) {
+	            text_color_set(DW_COLOR_DEBUG);
+	            dwgps_print ("GPSNMEA: ", &info);
+	          }
+	          dwgps_set_data (&info);
+	        }
+
+	      }
+	      else if (strncmp(gps_msg, "$GPGGA", 6) == 0) {
+		int nsat;
+
+		f = dwgpsnmea_gpgga (gps_msg, 0, &info.dlat, &info.dlon, &info.altitude, &nsat);
+
+	        /* Only switch between 2D & 3D.  */
+	        /* Let GPRMC handle other changes in fix state and data transfer. */
+
+	        if (f == DWFIX_ERROR) {
+
+		    /* Parse error.  Shouldn't happen.  Better luck next time. */
+	            text_color_set(DW_COLOR_INFO);
+	            dw_printf ("GPSNMEA: Error parsing $GPGGA sentence.\n");
+	            dw_printf ("%s\n", gps_msg);
+	        }
+	        else if ((f == DWFIX_3D && info.fix == DWFIX_2D) ||
+	                 (f == DWFIX_2D && info.fix == DWFIX_3D)) {
+		  text_color_set(DW_COLOR_INFO);
+	          dw_printf ("GPSNMEA: Location fix is now %dD.\n", (int)f);
+	          info.fix = f;
+	        }	
+	      }
+	    }
+
+	    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) */
+
+#if __WIN32__
+	return (0);
+#else
+	return (NULL);	
+#endif
+
+} /* end read_gpsnmea_thread */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	remove_checksum
+ *
+ * Purpose:	Validate checksum and remove before further processing.
+ *
+ * Inputs:	sentence
+ *		quiet		suppress printing of error messages.
+ *
+ * Outputs:	sentence	modified in place.
+ *
+ * Returns:	0 = good checksum.
+ *		-1 = error.  missing or wrong.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static int remove_checksum (char *sent, int quiet)
+{
+        char *p;
+        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) {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_INFO);
+            dw_printf("Missing GPS checksum.\n");
+	  }
+          return (-1);
+        }
+        if (cs != strtoul(p+1, NULL, 16)) {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("GPS checksum error. Expected %02x but found %s.\n", cs, p+1);
+	  }
+          return (-1);
+        }
+        *p = '\0';      // Remove the checksum.
+	return (0);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgpsnmea_gprmc
+ *
+ * Purpose:    	Parse $GPRMC sentence and extract interesing parts.
+ *
+ * Inputs:	sentence	NMEA sentence.
+ *
+ *		quiet		suppress printing of error messages.
+ *
+ * Outputs:	odlat		latitude
+ *		odlon		longitude
+ *		oknots		speed
+ *		ocourse		direction of travel.
+ *		
+ *					Left undefined if not valid.
+ *
+ * Returns:	DWFIX_ERROR	Parse error.
+ *		DWFIX_NO_FIX	GPS is there but Position unknown.  Could be temporary.
+ *		DWFIX_2D	Valid position.   We don't know if it is really 2D or 3D.
+ *
+ * Examples:	$GPRMC,001431.00,V,,,,,,,121015,,,N*7C
+ *		$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
+ *
+ *--------------------------------------------------------------------*/
+
+dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon, float *oknots, float *ocourse)
+{
+	char stemp[NMEA_MAX_LEN];	/* Make copy because parsing is destructive. */
+
+	char *next;
+
+	char *ptype;			/* Should be $GPRMC */
+	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 */
+
+	strlcpy (stemp, sentence, sizeof(stemp));
+
+	if (remove_checksum (stemp, quiet) < 0) {
+	  return (DWFIX_ERROR);
+	}
+
+	next = stemp;
+	ptype = strsep(&next, ",");
+	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, ",");	
+
+	/* Suppress the 'set but not used' warnings. */
+	/* Alternatively, we might use __attribute__((unused)) */
+
+	(void)(ptype);
+	(void)(ptime);
+	(void)(pdate);
+
+	if (pstatus != NULL && strlen(pstatus) == 1) {
+	  if (*pstatus != 'A') {
+	    return (DWFIX_NO_FIX);		/* Not "Active." Don't parse. */
+	  }
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("No status in GPRMC sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+
+	if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) {
+	  *odlat = latitude_from_nmea(plat, pns);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get latitude from GPRMC sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+
+	if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) {
+	  *odlon = longitude_from_nmea(plon, pew);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get longitude from GPRMC sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+
+	if (pknots != NULL && strlen(pknots) > 0) {
+	  *oknots = atof(pknots);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get speed from GPRMC sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+
+	if (pcourse != NULL) {
+	  if (strlen(pcourse) > 0) {
+	    *ocourse = atof(pcourse);
+	  }
+	  else {
+	    /* When stationary, this field might be empty. */
+	    *ocourse = G_UNKNOWN;
+	  }
+	}
+	else {
+	  if ( ! quiet) {
+
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get course from GPRMC sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+	//text_color_set (DW_COLOR_INFO);
+        //dw_printf("%.6f %.6f %.1f %.0f\n", *odlat, *odlon, *oknots, *ocourse);
+
+	return (DWFIX_2D);
+
+} /* end dwgpsnmea_gprmc */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgpsnmea_gpgga
+ *
+ * Purpose:    	Parse $GPGGA sentence and extract interesing parts.
+ *
+ * Inputs:	sentence	NMEA sentence.
+ *
+ *		quiet		suppress printing of error messages.
+ *
+ * Outputs:	odlat		latitude
+ *		odlon		longitude
+ *		oalt		altitude in meters
+ *		onsat		number of satellites.
+ *		
+ *					Left undefined if not valid.
+ *
+ * Returns:	DWFIX_ERROR	Parse error.
+ *		DWFIX_NO_FIX	GPS is there but Position unknown.  Could be temporary.
+ *		DWFIX_2D	Valid position.   We don't know if it is really 2D or 3D.
+ *				Take more cautious value so we don't try using altitude.
+ *
+ * Examples:	$GPGGA,001429.00,,,,,0,00,99.99,,,,,,*68
+ *		$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
+ *
+ *--------------------------------------------------------------------*/
+
+
+// TODO: in progress...
+
+dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat)
+{
+	char stemp[NMEA_MAX_LEN];	/* Make copy because parsing is destructive. */
+
+	char *next;
+
+	char *ptype;			/* Should be $GPGGA */
+	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. */
+
+
+	strlcpy (stemp, sentence, sizeof(stemp));
+
+	if (remove_checksum (stemp, quiet) < 0) {
+	  return (DWFIX_ERROR);
+	}
+
+	next = stemp;
+	ptype = strsep(&next, ",");
+	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, ",");
+
+	/* Suppress the 'set but not used' warnings. */
+	/* Alternatively, we might use __attribute__((unused)) */
+
+	(void)(ptype);
+	(void)(ptime);
+	(void)(pnum_sat);
+	(void)(phdop);
+	(void)(palt_u);
+	(void)(pheight);
+	(void)(pheight_u);
+	(void)(psince);
+	(void)(pdsta);
+
+	if (pfix != NULL && strlen(pfix) == 1) {
+	  if (*pfix == '0') {
+	    return (DWFIX_NO_FIX);		/* No Fix. Don't parse the rest. */
+	  }
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("No fix in GPGGA sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+
+	if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) {
+	  *odlat = latitude_from_nmea(plat, pns);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get latitude from GPGGA sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+
+	if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) {
+	  *odlon = longitude_from_nmea(plon, pew);
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get longitude from GPGGA sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+	// TODO: num sat...
+
+/* 
+ * We can distinguish between 2D & 3D fix by presence 
+ * of altitude or an empty field.
+ */
+
+	if (paltitude != NULL) {
+
+	  if (strlen(paltitude) > 0) {
+	    *oalt = atof(paltitude);
+	    return (DWFIX_3D);
+	  }
+	  else {
+	    return (DWFIX_2D);
+	  }
+	}
+	else {
+	  if ( ! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf("Can't get altitude from GPGGA sentence.\n");
+	  }
+	  return (DWFIX_ERROR);
+	}
+
+} /* end dwgpsnmea_gpgga */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dwgpsnmea_term
+ *
+ * Purpose:    	Shut down GPS interface before exiting from application.
+ *
+ * Inputs:	none.
+ *
+ * Returns:	none.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void dwgpsnmea_term (void) {
+
+	// Should probably kill reader thread before closing device to avoid
+	// message about read error.
+
+	// serial_port_close (s_gpsnmea_port_fd); 
+
+} /* end dwgps_term */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:    	Simple unit test for other functions in this file.
+ *
+ * Description: Compile with -DGPSTEST option.
+ *
+ *		Windows:
+ *			gcc  -DGPSTEST -Iregex dwgpsnmea.c dwgps.c textcolor.o serial_port.o latlong.o misc.a
+ *			a.exe
+ *
+ *		Linux:
+ *			 gcc -DGPSTEST dwgpsnmea.c dwgps.c textcolor.o serial_port.o latlong.o misc.a -lm -lpthread
+ *			./a.out
+ *
+ *--------------------------------------------------------------------*/
+
+#if GPSTEST
+
+int main (int argc, char *argv[])
+{
+
+	struct misc_config_s config;
+	dwgps_info_t info;
+
+
+	memset (&config, 0, sizeof(config));
+	strlcpy (config.gpsnmea_port, "COM22", sizeof(config.gpsnmea_port));
+
+	dwgps_init (&config, 3);
+
+	while (1) {
+	  dwfix_t fix;
+
+	  fix = dwgps_read (&info)
;
+	  text_color_set (DW_COLOR_INFO);
+	  switch (fix) {
+	    case DWFIX_2D:
+	    case DWFIX_3D:
+	      dw_printf ("%.6f  %.6f", info.dlat, info.dlon);
+	      dw_printf ("  %.1f knots  %.0f degrees", info.speed_knots, info.track);
+	      if (fix==3) dw_printf ("  altitude = %.1f meters", info.altitude);
+	      dw_printf ("\n");
+	      break;
+	    case DWFIX_NOT_SEEN:
+	    case DWFIX_NO_FIX:
+	      dw_printf ("Location currently not available.\n");
+	      break;
+	    case DWFIX_NOT_INIT:
+	      dw_printf ("GPS Init failed.\n");
+	      exit (1);
+	    case DWFIX_ERROR:
+	    default:
+	      dw_printf ("ERROR getting GPS information.\n");
+	      break;
+	  }
+	  SLEEP_SEC (3);
+	}
+
+} /* end main */
+
+
+#endif
+
+
+
+/* end dwgpsnmea.c */
+
+
+
diff --git a/dwgpsnmea.h b/dwgpsnmea.h
new file mode 100644
index 0000000..a126a50
--- /dev/null
+++ b/dwgpsnmea.h
@@ -0,0 +1,28 @@
+
+/* dwgpsnmea.h   -   For NMEA sentences over serial port */
+
+
+
+#ifndef DWGPSNMEA_H
+#define DWGPSNMEA_H 1
+
+#include "dwgps.h"		/* for dwfix_t */
+#include "config.h"
+
+
+int dwgpsnmea_init (struct misc_config_s *pconfig, int debug);
+
+void dwgpsnmea_term (void);
+
+
+dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon, float *oknots, float *ocourse);
+
+dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat);
+
+#endif
+
+
+/* end dwgpsnmea.h */
+
+
+
diff --git a/encode_aprs.c b/encode_aprs.c
index 307c11a..67476f4 100644
--- a/encode_aprs.c
+++ b/encode_aprs.c
@@ -1,820 +1,893 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-/*------------------------------------------------------------------
- *
- * Module:      encode_aprs.c
- *
- * Purpose:   	Construct APRS packets from components.
- *		
- * Description: 
- *
- * References:	APRS Protocol Reference.
- *
- *		Frequency spec.
- *		http://www.aprs.org/info/freqspec.txt
- *
- *---------------------------------------------------------------*/
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <time.h>
-#include <math.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "encode_aprs.h"
-#include "latlong.h"
-#include "textcolor.h"
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        set_norm_position
- *
- * Purpose:     Fill in the human-readable latitude, longitude, 
- * 		symbol part which is common to multiple data formats.
- *
- * Inputs: 	symtab	- Symbol table id or overlay.
- *		symbol	- Symbol id.
- *    		dlat	- Latitude.
- *		dlong	- Longitude.
- *
- * Outputs:	presult	- Stored here.  
- *
- * Returns:     Number of characters in result.
- *
- *----------------------------------------------------------------*/
-
-/* Position & symbol fields common to several message formats. */
-
-typedef struct position_s {
-	  char lat[8];
-	  char sym_table_id;		/* / \ 0-9 A-Z */
-	  char lon[9];
-	  char symbol_code;
-	} position_t;
-
-
-static int set_norm_position (char symtab, char symbol, double dlat, double dlong, position_t *presult)
-{
-
-	latitude_to_str (dlat, 0, presult->lat);
-
-	if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n");
-	}
-	presult->sym_table_id = symtab;
-
-	longitude_to_str (dlong, 0, presult->lon);
-
-	if (symbol < '!' || symbol > '~') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Symbol code is not in range of ! to ~\n");
-	}
-	presult->symbol_code = symbol;
-
-	return (sizeof(position_t));
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        set_comp_position
- *
- * Purpose:     Fill in the compressed latitude, longitude, 
- *		symbol part which is common to multiple data formats.
- *
- * Inputs: 	symtab	- Symbol table id or overlay.
- *		symbol	- Symbol id.
- *    		dlat	- Latitude.
- *		dlong	- Longitude.
- *
- * 	 	power	- Watts.
- *		height	- Feet.
- *		gain	- dBi.
- *
- * 		course	- Degress, 1 - 360.  0 means none or unknown.
- *		speed	- knots.
- *
- *
- * Outputs:	presult	- Stored here.  
- *
- * Returns:     Number of characters in result.
- *
- * Description:	The cst field can have only one of 
- *
- *		course/speed	- takes priority (this implementation)
- *		radio range	- calculated from PHG
- *		altitude	- not implemented yet.
- *
- *----------------------------------------------------------------*/
-
-/* Compressed position & symbol fields common to several message formats. */
-
-typedef struct compressed_position_s {
-	  char sym_table_id;		/* / \ a-j A-Z */
-					/* "The presence of the leading Symbol Table Identifier */
-					/* instead of a digit indicates that this is a compressed */
-					/* Position Report and not a normal lat/long report." */
-
-	  char y[4];			/* Compressed Latitude. */
-	  char x[4];			/* Compressed Longitude. */
-	  char symbol_code;
-	  char c;			/* Course/speed or radio range or altitude. */
-	  char s;
-	  char t	;		/* Compression type. */
-	} compressed_position_t;
-
-
-static int set_comp_position (char symtab, char symbol, double dlat, double dlong, 
-		int power, int height, int gain, 
-		int course, int speed,
-		compressed_position_t *presult)
-{
-
-	if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n");
-	}
-
-/*
- * In compressed format, the characters a-j are used for a numeric overlay.
- * This allows the receiver to distinguish between compressed and normal formats.
- */
-	if (isdigit(symtab)) {
-	  symtab = symtab - '0' + 'a';
-	}
-	presult->sym_table_id = symtab;
-
-	latitude_to_comp_str (dlat, presult->y);
-	longitude_to_comp_str (dlong, presult->x);
-
-	if (symbol < '!' || symbol > '~') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Symbol code is not in range of ! to ~\n");
-	}
-	presult->symbol_code = symbol;
-
-/*
- * The cst field is complicated.
- *
- * When c is ' ', the cst field is not used.
- *
- * When the t byte has a certain pattern, c & s represent altitude.
- *
- * Otherwise, c & s can be either course/speed or radio range.
- *
- * When c is in range of '!' to 'z', 
- *
- * 	('!' - 33) * 4 = 0 degrees.
- *	...
- *	('z' - 33) * 4 = 356 degrees.
- *
- * In this case, s represents speed ...
- *
- * When c is '{', s is range ...
- */
-
-	if (course || speed) {
-	  int c;
-	  int s;
-
-	  c = (course + 1) / 4;
-	  if (c < 0) c += 90;
-	  if (c >= 90) c -= 90;
-	  presult->c = c + '!';
-
-	  s = (int)round(log(speed+1.0) / log(1.08));
-	  presult->s = s + '!';
-	   
-	  presult->t = 0x26 + '!';	/* current, other tracker. */
-	}
-	else if (power || height || gain) {
-	  int s;
-	  float range;
-
-	  presult->c = '{';		/* radio range. */
-
-	  if (power == 0) power = 10;
-	  if (height == 0) height = 20;
-	  if (gain == 0) gain = 3;
-
-	  // from protocol reference page 29.
-	  range = sqrt(2.0*height * sqrt((power/10.0) * (gain/2.0)));
-
-	  s = (int)round(log(range/2.) / log(1.08));
-	  if (s < 0) s = 0;
-	  if (s > 93) s = 93;
-
-	  presult->s = s + '!';
-   
-	  presult->t = 0x26 + '!';	/* current, other tracker. */  
-	}
-	else {
-	  presult->c = ' ';		/* cst field not used. */
-	  presult->s = ' ';
-	  presult->t = '!';		/* avoid space. */  
-	}
-	return (sizeof(compressed_position_t));
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        phg_data_extension
- *
- * Purpose:     Fill in parts of the power/height/gain data extension.
- *
- * Inputs: 	power	- Watts.
- *		height	- Feet.
- *		gain	- dB.  Not clear if it is dBi or dBd.
- *		dir	- Directivity: N, NE, etc., omni.
- *
- * Outputs:	presult	- Stored here.  
- *
- * Returns:     Number of characters in result.
- *
- *----------------------------------------------------------------*/
-
-
-typedef struct phg_s {
-	  char P;
-	  char H;
-	  char G;
-	  char p;
-	  char h;
-	  char g;
-	  char d;
-	} phg_t;
-
-
-static int phg_data_extension (int power, int height, int gain, char *dir, char *presult)
-{
-	phg_t *r = (phg_t*)presult;
-	int x;
-
-	r->P = 'P';
-	r->H = 'H';
-	r->G = 'G';
-		
-	x = (int)round(sqrt((float)power)) + '0';
-	if (x < '0') x = '0';
-	else if (x > '9') x = '9';
-	r->p = x;
-
-	x = (int)round(log2(height/10.0)) + '0';
-	if (x < '0') x = '0';
-	/* Result can go beyond '9'. */
-	r->h = x;
-
-	x = gain + '0';
-	if (x < '0') x = '0';
-	else if (x > '9') x = '0';
-	r->g = x;
-
-	r->d = '0';
-	if (dir != NULL) {
-	  if (strcasecmp(dir,"NE") == 0) r->d = '1';
-	  if (strcasecmp(dir,"E") == 0) r->d = '2';
-	  if (strcasecmp(dir,"SE") == 0) r->d = '3';
-	  if (strcasecmp(dir,"S") == 0) r->d = '4';
-	  if (strcasecmp(dir,"SW") == 0) r->d = '5';
-	  if (strcasecmp(dir,"W") == 0) r->d = '6';
-	  if (strcasecmp(dir,"NW") == 0) r->d = '7';
-	  if (strcasecmp(dir,"N") == 0) r->d = '8';
-	}
-	return (sizeof(phg_t));
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        cse_spd_data_extension
- *
- * Purpose:     Fill in parts of the course & speed data extension.
- *
- * Inputs: 	course	- Degress, 1 - 360.
- *		speed	- knots.
- *
- * Outputs:	presult	- Stored here.  
- *
- * Returns:     Number of characters in result.
- *
- *----------------------------------------------------------------*/
-
-
-typedef struct cs_s {
-	  char cse[3];
-	  char slash;
-	  char spd[3];
-	} cs_t;
-
-
-static int cse_spd_data_extension (int course, int speed, char *presult)
-{
-	cs_t *r = (cs_t*)presult;
-	char stemp[8];
-	int x;
-
-	x = course;
-	if (x < 0) x = 0;
-	if (x > 360) x = 360;
-	sprintf (stemp, "%03d", x);
-	memcpy (r->cse, stemp, 3);
-
-	r->slash = '/';
-
-	x = speed;
-	if (x < 0) x = 0;
-	if (x > 999) x = 999;
-	sprintf (stemp, "%03d", x);
-	memcpy (r->spd, stemp, 3);
-
-	return (sizeof(cs_t));
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        frequency_spec
- *
- * Purpose:     Put frequency specification in beginning of comment field.
- *
- * Inputs: 	freq	- MHz.
- *		tone	- Hz.
- *		offset	- MHz.
- *
- * Outputs:	presult	- Stored here.  
- *
- * Returns:     Number of characters in result.
- *
- * Description:	There are several valid variations.
- *
- *		The frequency could be missing here if it is in the 
- *		object name.  In this case we could have tone & offset.
- *
- *		Offset must always be preceded by tone.
- *
- *----------------------------------------------------------------*/
-
-
-typedef struct freq_s {
-	  char f[7];		/* format 999.999 */
-	  char mhz[3];
-	  char space;
-	} freq_t;
-
-typedef struct to_s {
-	  char T;
-	  char ttt[3];		/* format 999 (drop fraction) or 'off'. */
-	  char space1;
-	  char oooo[4];		/* leading sign, 3 digits, tens of KHz. */
-	  char space2;
-	} to_t;
-
-
-static int frequency_spec (float freq, float tone, float offset, char *presult)
-{
-	int result_len = 0;
-	
-	if (freq != 0) {
-	  freq_t *f = (freq_t*)presult;
-	  char stemp[12];
-
-	  /* Should use letters for > 999.999. */
-	  sprintf (stemp, "%07.3f", freq);
-	  memcpy (f->f, stemp, 7);
-	  memcpy (f->mhz, "MHz", 3);
-	  f->space = ' ';
-	  result_len = sizeof (freq_t);
-	}
-	
-	if (tone != 0 || offset != 0) {
-	  to_t *to = (to_t*)(presult + result_len);
-	  char stemp[12];
-
-	  to->T = 'T';
-	  if (tone == 0) {
-	    memcpy(to->ttt, "off", 3);
-	  }
-	  else {
-	    sprintf (stemp, "%03d", (int)tone);
-	    memcpy (to->ttt, stemp, 3);
-	  }
-	  to->space1 = ' ';
-	  sprintf (stemp, "%+04d", (int)round(offset * 100));
-	  memcpy (to->oooo, stemp, 4);
-	  to->space2 = ' ';
-
-	  result_len += sizeof (to_t);
-	}
-
-	return (result_len);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        encode_position
- *
- * Purpose:     Construct info part for position report format.
- *
- * 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.
- *
- * 	 	power	- Watts.
- *		height	- Feet.
- *		gain	- dB.  Not clear if it is dBi or dBd.
- *		dir	- Directivity: N, NE, etc., omni.
- *
- * 		course	- Degress, 1 - 360.  0 means none or unknown.
- *		speed	- knots.
- *
- * 	 	freq	- MHz.
- *		tone	- Hz.
- *		offset	- MHz.
- *
- *		comment	- Additional comment text.
- *
- *
- * Outputs:	presult	- Stored here.  Should be at least ??? bytes.
- *
- * Returns:     Number of characters in result.
- *
- * Description:	There can be a single optional "data extension"
- *		following the position so there is a choice
- *		between:
- *			Power/height/gain/directivity or
- *			Course/speed.
- *
- *		Afer that, 
- *
- *----------------------------------------------------------------*/
-
-typedef struct aprs_ll_pos_s {
-	  char dti;			/* ! or = */
-	  position_t pos;
-	  				/* Comment up to 43 characters. */
-					/* Start of comment could be data extension(s). */
-} aprs_ll_pos_t;
-
-
-typedef struct aprs_compressed_pos_s {
-	  char dti;			/* ! or = */
-	  compressed_position_t cpos;
-	  				/* Comment up to 40 characters. */
-					/* No data extension allowed for compressed location. */
-} aprs_compressed_pos_t;
-
-
-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,
-		float freq, float tone, float offset,
-		char *comment,
-		char *presult)
-{
-	int result_len = 0;
-
-	if (compressed) {
-	  aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult;
-
-	  p->dti = messaging ? '=' : '!';
-	  set_comp_position (symtab, symbol, lat, lon, 
-		power, height, gain, 
-		course, speed,
-		&(p->cpos));
-	  result_len = 1 + sizeof (p->cpos);
-	}
-	else {
-	  aprs_ll_pos_t *p = (aprs_ll_pos_t *)presult;
-
-	  p->dti = messaging ? '=' : '!';
-	  set_norm_position (symtab, symbol, lat, lon, &(p->pos));
-	  result_len = 1 + sizeof (p->pos);
-
-/* Optional data extension. (singular) */
-/* Can't have both course/speed and PHG.  Former gets priority. */
-
-	  if (course || speed) {
-	    result_len += cse_spd_data_extension (course, speed, presult + result_len);
-	  }
-	  else if (power || height || gain) {
- 	    result_len += phg_data_extension (power, height, gain, dir, presult + result_len);
-	  }
-	}
-
-/* Optional frequency spec. */
-
-	if (freq != 0 || tone != 0 || offset != 0) {
-	  result_len += frequency_spec (freq, tone, offset, presult + result_len);
-	}
-
-	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) {
-	  strcat (presult, comment);
-	  result_len += strlen(comment);
-	}
-
-	return (result_len);
-
-} /* end encode_position */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        encode_object
- *
- * Purpose:     Construct info part for object report format.
- *
- * Inputs:      name	- Name, up to 9 characters.
- *		compressed - Send in compressed form?
- *		thyme	- Time stamp or 0 for none.
- *		lat	- Latitude.
- *		lon	- Longitude.
- *		symtab	- Symbol table id or overlay.
- *		symbol	- Symbol id.
- *
- * 	 	power	- Watts.
- *		height	- Feet.
- *		gain	- dB.  Not clear if it is dBi or dBd.
- *		dir	- Direction: N, NE, etc., omni.
- *
- * 		course	- Degress, 1 - 360.  0 means none or unknown.
- *		speed	- knots.
- *
- * 	 	freq	- MHz.
- *		tone	- Hz.
- *		offset	- MHz.
- *
- *		comment	- Additional comment text.
- *
- * Outputs:	presult	- Stored here.  Should be at least ??? bytes.
- *
- * Returns:     Number of characters in result.
- *
- * Description:	
- *
- *----------------------------------------------------------------*/
-
-typedef struct aprs_object_s {
-	  struct {
-	    char dti;			/* ; */					
-	    char name[9];								
-	    char live_killed;		/* * for live or _ for killed */	
-	    char time_stamp[7];	  
-	  } o;
-	  union {
-	    position_t pos;		/* Up to 43 char comment.  First 7 bytes could be data extension. */
-	    compressed_position_t cpos;	/* Up to 40 char comment.  No PHG data extension in this case. */
-	  } u;    
-	} aprs_object_t;
-
-int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, 
-		char symtab, char symbol, 
-		int power, int height, int gain, char *dir,
-		int course, int speed,
-		float freq, float tone, float offset, char *comment,
-		char *presult)
-{
-	aprs_object_t *p = (aprs_object_t *) presult;
-	int result_len = 0;
-	int n;
-
-
-	p->o.dti = ';';
-
-	memset (p->o.name, ' ', sizeof(p->o.name));
-	n = strlen(name);
-	if (n > sizeof(p->o.name)) n = sizeof(p->o.name);
-	memcpy (p->o.name, name, n);
-
-	p->o.live_killed = '*';
-
-	if (thyme != 0) {
-	  struct tm tm;
-
-#define XMIT_UTC 1
-#if XMIT_UTC
-	  gmtime_r (&thyme, &tm);
-#else
-	  /* Using local time, for this application, would make more sense to me. */
-	  /* On Windows, localtime_r produces UTC. */
-	  /* How do we set the time zone?  Google for mingw time zone. */
-
-	  localtime_r (thyme, &tm);
-#endif
-	  sprintf (p->o.time_stamp, "%02d%02d%02d", tm.tm_mday, tm.tm_hour, tm.tm_min);
-#if XMIT_UTC
-	  p->o.time_stamp[6] = 'z';
-#else
-	  p->o.time_stamp[6] = '/';
-#endif
-	}
-	else {
-	  memcpy (p->o.time_stamp, "111111z", sizeof(p->o.time_stamp));
-	}
-
-	if (compressed) {
-	  set_comp_position (symtab, symbol, lat, lon, 
-		power, height, gain, 
-		course, speed,
-		&(p->u.cpos));
-	  result_len = sizeof(p->o) + sizeof (p->u.cpos);
-	}
-	else {
-	  set_norm_position (symtab, symbol, lat, lon, &(p->u.pos));
-	  result_len = sizeof(p->o) + sizeof (p->u.pos);
-
-/* Optional data extension. (singular) */
-/* Can't have both course/speed and PHG.  Former gets priority. */
-
-	  if (course || speed) {
-	    result_len += cse_spd_data_extension (course, speed, presult + result_len);
-	  }
-	  else if (power || height || gain) {
- 	    result_len += phg_data_extension (power, height, gain, dir, presult + result_len);
-	  }
-	}
-
-/* Optional frequency spec. */
-
-	if (freq != 0 || tone != 0 || offset != 0) {
-	  result_len += frequency_spec (freq, tone, offset, presult + result_len);
-	}
-
-	presult[result_len] = '\0';
-
-/* Finally, comment text. */
-	
-	if (comment != NULL) {
-	  strcat (presult, comment);
-	  result_len += strlen(comment);
-	}
-
-	return (result_len);
-
-} /* end encode_object */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Quick test for some functions in this file.
- *
- * Description:	Just a smattering, not an organized test.
- *
- * 		$ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c textcolor.c ; ./a.exe
- *
- *----------------------------------------------------------------*/
-
-
-#if EN_MAIN
-
-
-int main (int argc, char *argv[])
-{
-	char result[100];
-
-
-
-/***********  Position  ***********/
-
-	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!  line %d\n", __LINE__);
-
-/* with PHG. */
-
-	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!  line %d\n", __LINE__);
-
-/* with freq. */
-
-	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!  line %d\n", __LINE__);
-
-/* with course/speed, freq, and comment! */
-
-	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!  line %d\n", __LINE__);
-
-/* Course speed, no tone, + offset */
-
-	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!  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), 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!  line %d\n", __LINE__);
-
-
-/* with PHG. In this case it is converted to precomputed radio range.  TODO: check on this.  Is 27.4 correct? */
-
-	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, "!D8yKC<Hn[&{CG") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
-
-/* with course/speed, freq, and comment!  TODO:  check on this 55 knots should be 63 MPH.  we get 62. */
-
-	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, "!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
-
-// 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, 0, 0, 0, 0, 0, NULL, result);
-	dw_printf ("%s\n", result);
-	if (strcmp(result, ";WB1GOF-C *111111z4234.61ND07126.47W&") != 0) dw_printf ("ERROR!  line %d\n", __LINE__);
-
-// TODO: need more tests.
-
-	return(0);
-
-}  /* end main */
-
-#endif		/* unit test */
-
-
-/* end encode_aprs.c */
-
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    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
+//    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/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * Module:      encode_aprs.c
+ *
+ * Purpose:   	Construct APRS packets from components.
+ *		
+ * Description: 
+ *
+ * References:	APRS Protocol Reference.
+ *
+ *		Frequency spec.
+ *		http://www.aprs.org/info/freqspec.txt
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <math.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "encode_aprs.h"
+#include "latlong.h"
+#include "textcolor.h"
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        set_norm_position
+ *
+ * Purpose:     Fill in the human-readable latitude, longitude, 
+ * 		symbol part which is common to multiple data formats.
+ *
+ * Inputs: 	symtab	- Symbol table id or overlay.
+ *		symbol	- Symbol id.
+ *    		dlat	- Latitude.
+ *		dlong	- Longitude.
+ *		ambiguity - Blank out least significant digits.
+ *
+ * Outputs:	presult	- Stored here.  
+ *
+ * Returns:     Number of characters in result.
+ *
+ *----------------------------------------------------------------*/
+
+/* Position & symbol fields common to several message formats. */
+
+typedef struct position_s {
+	  char lat[8];
+	  char sym_table_id;		/* / \ 0-9 A-Z */
+	  char lon[9];
+	  char symbol_code;
+	} position_t;
+
+
+static int set_norm_position (char symtab, char symbol, double dlat, double dlong, int ambiguity, position_t *presult)
+{
+
+	latitude_to_str (dlat, ambiguity, presult->lat);
+
+	if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n");
+	}
+	presult->sym_table_id = symtab;
+
+	longitude_to_str (dlong, ambiguity, presult->lon);
+
+	if (symbol < '!' || symbol > '~') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Symbol code is not in range of ! to ~\n");
+	}
+	presult->symbol_code = symbol;
+
+	return (sizeof(position_t));
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        set_comp_position
+ *
+ * Purpose:     Fill in the compressed latitude, longitude, 
+ *		symbol part which is common to multiple data formats.
+ *
+ * Inputs: 	symtab	- Symbol table id or overlay.
+ *		symbol	- Symbol id.
+ *    		dlat	- Latitude.
+ *		dlong	- Longitude.
+ *
+ * 	 	power	- Watts.
+ *		height	- Feet.
+ *		gain	- dBi.
+ *
+ * 		course	- Degress, 0 - 360 (360 equiv. to 0).  
+ *			  Use G_UNKNOWN for none or unknown.
+ *		speed	- knots.
+ *
+ *
+ * Outputs:	presult	- Stored here.  
+ *
+ * Returns:     Number of characters in result.
+ *
+ * Description:	The cst field can have only one of 
+ *
+ *		course/speed	- takes priority (this implementation)
+ *		radio range	- calculated from PHG
+ *		altitude	- not implemented yet.
+ *
+ *		Some conversion must be performed for course from
+ *		the API definition to what is sent over the air.
+ *
+ *----------------------------------------------------------------*/
+
+/* Compressed position & symbol fields common to several message formats. */
+
+typedef struct compressed_position_s {
+	  char sym_table_id;		/* / \ a-j A-Z */
+					/* "The presence of the leading Symbol Table Identifier */
+					/* instead of a digit indicates that this is a compressed */
+					/* Position Report and not a normal lat/long report." */
+
+	  char y[4];			/* Compressed Latitude. */
+	  char x[4];			/* Compressed Longitude. */
+	  char symbol_code;
+	  char c;			/* Course/speed or radio range or altitude. */
+	  char s;
+	  char t	;		/* Compression type. */
+	} compressed_position_t;
+
+
+static int set_comp_position (char symtab, char symbol, double dlat, double dlong, 
+		int power, int height, int gain, 
+		int course, int speed,
+		compressed_position_t *presult)
+{
+
+	if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n");
+	}
+
+/*
+ * In compressed format, the characters a-j are used for a numeric overlay.
+ * This allows the receiver to distinguish between compressed and normal formats.
+ */
+	if (isdigit(symtab)) {
+	  symtab = symtab - '0' + 'a';
+	}
+	presult->sym_table_id = symtab;
+
+	latitude_to_comp_str (dlat, presult->y);
+	longitude_to_comp_str (dlong, presult->x);
+
+	if (symbol < '!' || symbol > '~') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Symbol code is not in range of ! to ~\n");
+	}
+	presult->symbol_code = symbol;
+
+/*
+ * The cst field is complicated.
+ *
+ * When c is ' ', the cst field is not used.
+ *
+ * When the t byte has a certain pattern, c & s represent altitude.
+ *
+ * Otherwise, c & s can be either course/speed or radio range.
+ *
+ * When c is in range of '!' to 'z', 
+ *
+ * 	('!' - 33) * 4 = 0 degrees.
+ *	...
+ *	('z' - 33) * 4 = 356 degrees.
+ *
+ * In this case, s represents speed ...
+ *
+ * When c is '{', s is range ...
+ */
+
+	if (speed > 0) {
+	  int c;
+	  int s;
+	  
+	  if (course != G_UNKNOWN) {
+	    c = (course + 2) / 4;
+	    if (c < 0) c += 90;
+	    if (c >= 90) c -= 90;
+	  }
+	  else {
+	    c = 0;
+	  }
+	  presult->c = c + '!';
+
+	  s = (int)round(log(speed+1.0) / log(1.08));
+	  presult->s = s + '!';
+	   
+	  presult->t = 0x26 + '!';	/* current, other tracker. */
+	}
+	else if (power || height || gain) {
+	  int s;
+	  float range;
+
+	  presult->c = '{';		/* radio range. */
+
+	  if (power == 0) power = 10;
+	  if (height == 0) height = 20;
+	  if (gain == 0) gain = 3;
+
+	  // from protocol reference page 29.
+	  range = sqrt(2.0*height * sqrt((power/10.0) * (gain/2.0)));
+
+	  s = (int)round(log(range/2.) / log(1.08));
+	  if (s < 0) s = 0;
+	  if (s > 93) s = 93;
+
+	  presult->s = s + '!';
+   
+	  presult->t = 0x26 + '!';	/* current, other tracker. */  
+	}
+	else {
+	  presult->c = ' ';		/* cst field not used. */
+	  presult->s = ' ';
+	  presult->t = '!';		/* avoid space. */  
+	}
+	return (sizeof(compressed_position_t));
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        phg_data_extension
+ *
+ * Purpose:     Fill in parts of the power/height/gain data extension.
+ *
+ * Inputs: 	power	- Watts.
+ *		height	- Feet.
+ *		gain	- dB.  Protocol spec doesn't mention whether it is dBi or dBd.
+ *				This says dBi:
+ *				http://www.tapr.org/pipermail/aprssig/2008-September/027034.html
+
+ *		dir	- Directivity: N, NE, etc., omni.
+ *
+ * Outputs:	presult	- Stored here.  
+ *
+ * Returns:     Number of characters in result.
+ *
+ *----------------------------------------------------------------*/
+
+// TODO (bug):  Doesn't check for G_UNKNOWN.
+// could have a case where some, but not all, values were specified.
+// Callers originally checked for any not zero. 
+// now they check for any > 0.
+
+
+typedef struct phg_s {
+	  char P;
+	  char H;
+	  char G;
+	  char p;
+	  char h;
+	  char g;
+	  char d;
+	} phg_t;
+
+
+static int phg_data_extension (int power, int height, int gain, char *dir, char *presult)
+{
+	phg_t *r = (phg_t*)presult;
+	int x;
+
+	r->P = 'P';
+	r->H = 'H';
+	r->G = 'G';
+		
+	x = (int)round(sqrt((float)power)) + '0';
+	if (x < '0') x = '0';
+	else if (x > '9') x = '9';
+	r->p = x;
+
+	x = (int)round(log2(height/10.0)) + '0';
+	if (x < '0') x = '0';
+	/* Result can go beyond '9'. */
+	r->h = x;
+
+	x = gain + '0';
+	if (x < '0') x = '0';
+	else if (x > '9') x = '0';
+	r->g = x;
+
+	r->d = '0';
+	if (dir != NULL) {
+	  if (strcasecmp(dir,"NE") == 0) r->d = '1';
+	  if (strcasecmp(dir,"E") == 0) r->d = '2';
+	  if (strcasecmp(dir,"SE") == 0) r->d = '3';
+	  if (strcasecmp(dir,"S") == 0) r->d = '4';
+	  if (strcasecmp(dir,"SW") == 0) r->d = '5';
+	  if (strcasecmp(dir,"W") == 0) r->d = '6';
+	  if (strcasecmp(dir,"NW") == 0) r->d = '7';
+	  if (strcasecmp(dir,"N") == 0) r->d = '8';
+	}
+	return (sizeof(phg_t));
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        cse_spd_data_extension
+ *
+ * Purpose:     Fill in parts of the course & speed data extension.
+ *
+ * Inputs: 	course	- Degress, 0 - 360 (360 equiv. to 0).
+ *			  Use G_UNKNOWN for none or unknown.
+ *
+ *		speed	- knots.
+ *
+ * Outputs:	presult	- Stored here.  
+ *
+ * Returns:     Number of characters in result.
+ *
+ * Description: Over the air we use:
+ *			0 	for unknown or not relevant.
+ *			1 - 360	for valid course.  (360 for north)
+ *
+ *----------------------------------------------------------------*/
+
+
+typedef struct cs_s {
+	  char cse[3];
+	  char slash;
+	  char spd[3];
+	} cs_t;
+
+
+static int cse_spd_data_extension (int course, int speed, char *presult)
+{
+	cs_t *r = (cs_t*)presult;
+	char stemp[8];
+	int x;
+
+	if (course != G_UNKNOWN) {
+	  x = course;
+	  while (x < 1) x += 360;
+	  while (x > 360) x -= 360;
+	  // Should now be in range of 1 - 360. */
+	  // Original value of 0 for north is transmitted as 360. */
+	}
+	else {
+	  x = 0;
+	}
+	snprintf (stemp, sizeof(stemp), "%03d", x);
+	memcpy (r->cse, stemp, 3);
+
+	r->slash = '/';
+
+	x = speed;
+	if (x < 0) x = 0;		// would include G_UNKNOWN
+	if (x > 999) x = 999;
+	snprintf (stemp, sizeof(stemp), "%03d", x);
+	memcpy (r->spd, stemp, 3);
+
+	return (sizeof(cs_t));
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        frequency_spec
+ *
+ * Purpose:     Put frequency specification in beginning of comment field.
+ *
+ * Inputs: 	freq	- MHz.
+ *		tone	- Hz.
+ *		offset	- MHz.
+ *
+ * Outputs:	presult	- Stored here.  
+ *
+ * Returns:     Number of characters in result.
+ *
+ * Description:	There are several valid variations.
+ *
+ *		The frequency could be missing here if it is in the 
+ *		object name.  In this case we could have tone & offset.
+ *
+ *		Offset must always be preceded by tone.
+ *
+ *		Resulting formats are all fixed width and have a trailing space:
+ *
+ *			"999.999MHz "
+ *			"T999 "
+ *			"+999 "			(10 kHz units)
+ *
+ * Reference:	http://www.aprs.org/info/freqspec.txt
+ *
+ *----------------------------------------------------------------*/
+
+
+static int frequency_spec (float freq, float tone, float offset, char *presult)
+{
+	int result_size = 24;		// TODO: add as parameter.
+
+	*presult = '\0';
+	
+	if (freq > 0) {
+	  char stemp[16];
+
+	  /* TODO: Should use letters for > 999.999. */
+	  /* For now, just be sure we have proper field width. */
+
+	  if (freq > 999.999) freq = 999.999;
+
+	  snprintf (stemp, sizeof(stemp), "%07.3fMHz ", freq);
+
+	  strlcpy (presult, stemp, result_size);
+	}
+
+	if (tone != G_UNKNOWN) {
+	  char stemp[12];
+
+	  if (tone == 0) {
+	    strlcpy (stemp, "Toff ", sizeof (stemp));
+	  }
+	  else {
+	    snprintf (stemp, sizeof(stemp), "T%03d ", (int)tone);
+	  }
+
+	  strlcat (presult, stemp, result_size);
+	}
+
+	if (offset != G_UNKNOWN) {
+	  char stemp[12];
+
+	  snprintf (stemp, sizeof(stemp), "%+04d ", (int)round(offset * 100));
+	  strlcat (presult, stemp, result_size);
+	}
+
+	return (strlen(presult));
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        encode_position
+ *
+ * Purpose:     Construct info part for position report format.
+ *
+ * Inputs:      messaging - This determines whether the data type indicator 
+ *			   is set to '!' (false) or '=' (true).
+ *		compressed - Send in compressed form?
+ *		lat	- Latitude.
+ *		lon	- Longitude.
+ *		ambiguity - Number of digits to omit from location.
+ *		alt_ft	- Altitude in feet.
+ *		symtab	- Symbol table id or overlay.
+ *		symbol	- Symbol id.
+ *
+ * 	 	power	- Watts.
+ *		height	- Feet.
+ *		gain	- dB.  Not clear if it is dBi or dBd.
+ *		dir	- Directivity: N, NE, etc., omni.
+ *
+ *		course	- Degress, 0 - 360 (360 equiv. to 0).
+ *			  Use G_UNKNOWN for none or unknown.
+ *		speed	- knots.		// TODO:  should distinguish unknown(not revevant) vs. known zero.
+ *
+ * 	 	freq	- MHz.
+ *		tone	- Hz.
+ *		offset	- MHz.
+ *
+ *		comment	- Additional comment text.
+ *
+ *		result_size - Ammount of space for result, provideed by 
+ *				caller, to avoid buffer overflow.
+ *
+ * Outputs:	presult	- Stored here.  Should be at least ??? bytes.
+ *				Could get into hundreds of characters
+ *				because it includes the comment.
+ *
+ * Returns:     Number of characters in result.
+ *
+ * Description:	There can be a single optional "data extension"
+ *		following the position so there is a choice
+ *		between:
+ *			Power/height/gain/directivity or
+ *			Course/speed.
+ *
+ *		Afer that, 
+ *
+ *----------------------------------------------------------------*/
+
+
+typedef struct aprs_ll_pos_s {
+	  char dti;			/* ! or = */
+	  position_t pos;
+	  				/* Comment up to 43 characters. */
+					/* Start of comment could be data extension(s). */
+} aprs_ll_pos_t;
+
+
+typedef struct aprs_compressed_pos_s {
+	  char dti;			/* ! or = */
+	  compressed_position_t cpos;
+	  				/* Comment up to 40 characters. */
+					/* No data extension allowed for compressed location. */
+} aprs_compressed_pos_t;
+
+
+int encode_position (int messaging, int compressed, double lat, double lon, int ambiguity, int alt_ft, 
+		char symtab, char symbol, 
+		int power, int height, int gain, char *dir,
+		int course, int speed,
+		float freq, float tone, float offset,
+		char *comment,
+		char *presult, size_t result_size)
+{
+	int result_len = 0;
+
+	if (compressed) {
+	  aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult;
+
+	  p->dti = messaging ? '=' : '!';
+	  set_comp_position (symtab, symbol, lat, lon, 
+		power, height, gain, 
+		course, speed,
+		&(p->cpos));
+	  result_len = 1 + sizeof (p->cpos);
+	}
+	else {
+	  aprs_ll_pos_t *p = (aprs_ll_pos_t *)presult;
+
+	  p->dti = messaging ? '=' : '!';
+	  set_norm_position (symtab, symbol, lat, lon, ambiguity, &(p->pos));
+	  result_len = 1 + sizeof (p->pos);
+
+/* Optional data extension. (singular) */
+/* Can't have both course/speed and PHG.  Former gets priority. */
+
+	  if (course != G_UNKNOWN || speed > 0) {
+	    result_len += cse_spd_data_extension (course, speed, presult + result_len);
+	  }
+	  else if (power > 0 || height > 0 || gain > 0) {
+ 	    result_len += phg_data_extension (power, height, gain, dir, presult + result_len);
+	  }
+	}
+
+/* Optional frequency spec. */
+
+	if (freq != 0 || tone != 0 || offset != 0) {
+	  result_len += frequency_spec (freq, tone, offset, presult + result_len);
+	}
+
+	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;
+	  snprintf (salt, sizeof(salt), "/A=%06d", alt_ft);
+	  strlcat (presult, salt, result_size);
+	  result_len += strlen(salt);
+	}
+
+/* Finally, comment text. */
+	
+	if (comment != NULL) {
+	  strlcat (presult, comment, result_size);
+	  result_len += strlen(comment);
+	}
+
+	if (result_len >= result_size) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("encode_position result of %d characters won't fit into space provided.\n", result_len);
+	}
+
+	return (result_len);
+
+} /* end encode_position */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        encode_object
+ *
+ * Purpose:     Construct info part for object report format.
+ *
+ * Inputs:      name	- Name, up to 9 characters.
+ *		compressed - Send in compressed form?
+ *		thyme	- Time stamp or 0 for none.
+ *		lat	- Latitude.
+ *		lon	- Longitude.
+ *		ambiguity - Number of digits to omit from location.
+ *		symtab	- Symbol table id or overlay.
+ *		symbol	- Symbol id.
+ *
+ * 	 	power	- Watts.
+ *		height	- Feet.
+ *		gain	- dB.  Not clear if it is dBi or dBd.
+ *		dir	- Direction: N, NE, etc., omni.
+ *
+ *		course	- Degress, 0 - 360 (360 equiv. to 0).
+ *			  Use G_UNKNOWN for none or unknown.
+ *		speed	- knots.
+ *
+ * 	 	freq	- MHz.
+ *		tone	- Hz.
+ *		offset	- MHz.
+ *
+ *		comment	- Additional comment text.
+ *
+ *		result_size - Ammount of space for result, provideed by 
+ *				caller, to avoid buffer overflow.
+ *
+ * Outputs:	presult	- Stored here.  Should be at least ??? bytes.
+ *				36 for fixed part,
+ *				7 for optional extended data,
+ *				~20 for freq, etc.,
+ *				comment could be very long...
+ *
+ * Returns:     Number of characters in result.
+ *
+ * Description:	
+ *
+ *----------------------------------------------------------------*/
+
+typedef struct aprs_object_s {
+	  struct {
+	    char dti;			/* ; */					
+	    char name[9];								
+	    char live_killed;		/* * for live or _ for killed */	
+	    char time_stamp[7];	  
+	  } o;
+	  union {
+	    position_t pos;		/* Up to 43 char comment.  First 7 bytes could be data extension. */
+	    compressed_position_t cpos;	/* Up to 40 char comment.  No PHG data extension in this case. */
+	  } u;    
+	} aprs_object_t;
+
+int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, int ambiguity,
+		char symtab, char symbol, 
+		int power, int height, int gain, char *dir,
+		int course, int speed,
+		float freq, float tone, float offset, char *comment,
+		char *presult, size_t result_size)
+{
+	aprs_object_t *p = (aprs_object_t *) presult;
+	int result_len = 0;
+	int n;
+
+
+	p->o.dti = ';';
+
+	memset (p->o.name, ' ', sizeof(p->o.name));
+	n = strlen(name);
+	if (n > sizeof(p->o.name)) n = sizeof(p->o.name);
+	memcpy (p->o.name, name, n);
+
+	p->o.live_killed = '*';
+
+	if (thyme != 0) {
+	  struct tm tm;
+
+#define XMIT_UTC 1
+#if XMIT_UTC
+	  gmtime_r (&thyme, &tm);
+#else
+	  /* Using local time, for this application, would make more sense to me. */
+	  /* On Windows, localtime_r produces UTC. */
+	  /* How do we set the time zone?  Google for mingw time zone. */
+
+	  localtime_r (thyme, &tm);
+#endif
+	  snprintf (p->o.time_stamp, sizeof(p->o.time_stamp), "%02d%02d%02d", tm.tm_mday, tm.tm_hour, tm.tm_min);
+#if XMIT_UTC
+	  p->o.time_stamp[6] = 'z';
+#else
+	  p->o.time_stamp[6] = '/';
+#endif
+	}
+	else {
+	  memcpy (p->o.time_stamp, "111111z", sizeof(p->o.time_stamp));
+	}
+
+	if (compressed) {
+	  set_comp_position (symtab, symbol, lat, lon, 
+		power, height, gain, 
+		course, speed,
+		&(p->u.cpos));
+	  result_len = sizeof(p->o) + sizeof (p->u.cpos);
+	}
+	else {
+	  set_norm_position (symtab, symbol, lat, lon, ambiguity, &(p->u.pos));
+	  result_len = sizeof(p->o) + sizeof (p->u.pos);
+
+/* Optional data extension. (singular) */
+/* Can't have both course/speed and PHG.  Former gets priority. */
+
+	  if (course != G_UNKNOWN || speed > 0) {
+	    result_len += cse_spd_data_extension (course, speed, presult + result_len);
+	  }
+	  else if (power > 0 || height > 0 || gain > 0) {
+ 	    result_len += phg_data_extension (power, height, gain, dir, presult + result_len);
+	  }
+	}
+
+/* Optional frequency spec. */
+
+	if (freq != 0 || tone != 0 || offset != 0) {
+	  result_len += frequency_spec (freq, tone, offset, presult + result_len);
+	}
+
+	presult[result_len] = '\0';
+
+/* Finally, comment text. */
+	
+	if (comment != NULL) {
+	  strlcat (presult, comment, result_size);
+	  result_len += strlen(comment);
+	}
+
+	if (result_len >= result_size) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("encode_object result of %d characters won't fit into space provided.\n", result_len);
+	}
+
+	return (result_len);
+
+} /* end encode_object */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Quick test for some functions in this file.
+ *
+ * Description:	Just a smattering, not an organized test.
+ *
+ * 		$ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c textcolor.c ; ./a.exe
+ *
+ *----------------------------------------------------------------*/
+
+
+#if EN_MAIN
+
+
+int main (int argc, char *argv[])
+{
+	char result[100];
+	int errors = 0;
+
+
+/***********  Position  ***********/
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+/* with PHG. */
+// TODO:  Need to test specifying some but not all.
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		50, 100, 6, "N", G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+/* with freq & tone.  minus offset, no offset, explict simplex. */
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, -0.6, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, G_UNKNOWN, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 ") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, 0, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 +000 ") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+/* with course/speed, freq, and comment! */
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+/* Course speed, no tone, + offset */
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 River flooding") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+/* Course speed, no tone, + offset + altitude */
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&',
+		0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 /A=012345River flooding") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+	encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&',
+		0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(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__); errors++; }
+
+// TODO: try boundary conditions of course = 0, 359, 360
+
+/*********** Compressed position. ***********/
+
+	encode_position (0, 1, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!D8yKC<Hn[&  !") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+
+/* with PHG. In this case it is converted to precomputed radio range.  TODO: check on this.  Is 27.4 correct? */
+
+	encode_position (0, 1, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		50, 100, 6, "N", G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!D8yKC<Hn[&{CG") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+/* with course/speed, freq, and comment!  Roundoff. 55 knots should be 63 MPH.  we get 62. */
+
+	encode_position (0, 1, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
+		0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, "!D8yKC<Hn[&NUG146.955MHz T074 -060 River flooding") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+// TODO:  test alt; cs+alt
+
+
+/*********** Object. ***********/
+
+	encode_object ("WB1GOF-C", 0, 0, 42+34.61/60, -(71+26.47/60), 0, 'D', '&',
+		0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+	dw_printf ("%s\n", result);
+	if (strcmp(result, ";WB1GOF-C *111111z4234.61ND07126.47W&") != 0) { dw_printf ("ERROR!  line %d\n", __LINE__); errors++; }
+
+// TODO: need more tests.
+
+	if (errors > 0) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Encode APRS test FAILED with %d errors.\n", errors);
+	  exit (EXIT_FAILURE);
+	}
+
+	text_color_set (DW_COLOR_REC);
+	dw_printf ("Encode APRS test PASSED with no errors.\n");
+	exit (EXIT_SUCCESS);
+
+}  /* end main */
+
+#endif		/* unit test */
+
+
+/* end encode_aprs.c */
+
diff --git a/encode_aprs.h b/encode_aprs.h
index 198cecf..b100e11 100644
--- a/encode_aprs.h
+++ b/encode_aprs.h
@@ -1,16 +1,16 @@
-
-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,
-		float freq, float tone, float offset,
-		char *comment,
-		char *presult);
-
-int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, 
-		char symtab, char symbol, 
-		int power, int height, int gain, char *dir,
-		int course, int speed,
-		float freq, float tone, float offset, char *comment,
-		char *presult);
-
+
+int encode_position (int messaging, int compressed, double lat, double lon, int ambiguity, int alt_ft,
+		char symtab, char symbol, 
+		int power, int height, int gain, char *dir,
+		int course, int speed_knots,
+		float freq, float tone, float offset,
+		char *comment,
+		char *presult, size_t result_size);
+
+int encode_object (char *name, int compressed, time_t thyme, double lat, double lon, int ambiguity,
+		char symtab, char symbol, 
+		int power, int height, int gain, char *dir,
+		int course, int speed_knots,
+		float freq, float tone, float offset, char *comment,
+		char *presult, size_t result_size);
+
diff --git a/fcs_calc.c b/fcs_calc.c
index d2e9cfe..eff88d3 100644
--- a/fcs_calc.c
+++ b/fcs_calc.c
@@ -1,108 +1,108 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011  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 <stdio.h>
-
-/*
- * Calculate the FCS for an AX.25 frame.
- */
-
-#include "fcs_calc.h"
-
-
-static const unsigned short ccitt_table[256] = {
-
-// from http://www.ietf.org/rfc/rfc1549.txt
-
-   0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
-   0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
-   0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
-   0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
-   0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
-   0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
-   0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
-   0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
-   0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
-   0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
-   0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
-   0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
-   0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
-   0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
-   0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
-   0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
-   0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
-   0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
-   0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
-   0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
-   0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
-   0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
-   0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
-   0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
-   0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
-   0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
-   0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
-   0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
-   0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
-   0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
-   0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
-   0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
-
-};
-
-
-/* 
- * Use this for an AX.25 frame. 
- */
-
-unsigned short fcs_calc (unsigned char *data, int len)
-{
-	unsigned short crc = 0xffff;
-	int j;
-
-	for (j=0; j<len; j++) {
-
-  	  crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
-	}
-
-	return ( crc ^ 0xffff );
-}
-
-
-/* 
- * This can be used when we want to calculate a single CRC over disjoint data.
- *
- * 	crc = crc16 (region1, sizeof(region1), 0xffff);
- *	crc = crc16 (region2, sizeof(region2), crc);
- *	crc = crc16 (region3, sizeof(region3), crc);
- */
-
-unsigned short crc16 (unsigned char *data, int len, unsigned short seed)
-{
-	unsigned short crc = seed;
-	int j;
-
-	for (j=0; j<len; j++) {
-
-  	  crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
-	}
-
-	return ( crc ^ 0xffff );
-}
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011  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 <stdio.h>
+
+/*
+ * Calculate the FCS for an AX.25 frame.
+ */
+
+#include "fcs_calc.h"
+
+
+static const unsigned short ccitt_table[256] = {
+
+// from http://www.ietf.org/rfc/rfc1549.txt
+
+   0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+   0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+   0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+   0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+   0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+   0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+   0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+   0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+   0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+   0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+   0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+   0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+   0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+   0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+   0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+   0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+   0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+   0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+   0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+   0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+   0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+   0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+   0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+   0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+   0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+   0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+   0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+   0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+   0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+   0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+   0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+   0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+
+};
+
+
+/* 
+ * Use this for an AX.25 frame. 
+ */
+
+unsigned short fcs_calc (unsigned char *data, int len)
+{
+	unsigned short crc = 0xffff;
+	int j;
+
+	for (j=0; j<len; j++) {
+
+  	  crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
+	}
+
+	return ( crc ^ 0xffff );
+}
+
+
+/* 
+ * This can be used when we want to calculate a single CRC over disjoint data.
+ *
+ * 	crc = crc16 (region1, sizeof(region1), 0xffff);
+ *	crc = crc16 (region2, sizeof(region2), crc);
+ *	crc = crc16 (region3, sizeof(region3), crc);
+ */
+
+unsigned short crc16 (unsigned char *data, int len, unsigned short seed)
+{
+	unsigned short crc = seed;
+	int j;
+
+	for (j=0; j<len; j++) {
+
+  	  crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
+	}
+
+	return ( crc ^ 0xffff );
+}
+
diff --git a/fcs_calc.h b/fcs_calc.h
index fa0c34b..2e2b0ef 100644
--- a/fcs_calc.h
+++ b/fcs_calc.h
@@ -1,11 +1,11 @@
-
-/* fcs_calc.h */
-
-
-unsigned short fcs_calc (unsigned char *data, int len);
-
-unsigned short crc16 (unsigned char *data, int len, unsigned short seed);
-
-/* end fcs_calc.h */
-
-
+
+/* fcs_calc.h */
+
+
+unsigned short fcs_calc (unsigned char *data, int len);
+
+unsigned short crc16 (unsigned char *data, int len, unsigned short seed);
+
+/* end fcs_calc.h */
+
+
diff --git a/fsk_demod_agc.h b/fsk_demod_agc.h
index c4353c9..95c8079 100644
--- a/fsk_demod_agc.h
+++ b/fsk_demod_agc.h
@@ -1,2 +1,2 @@
-#define TUNE_MS_FILTER_SIZE 140 
-#define TUNE_PRE_BAUD    1.080 
+#define TUNE_MS_FILTER_SIZE 140 
+#define TUNE_PRE_BAUD    1.080 
diff --git a/fsk_demod_state.h b/fsk_demod_state.h
index a6447ab..74a723c 100644
--- a/fsk_demod_state.h
+++ b/fsk_demod_state.h
@@ -1,260 +1,269 @@
-/* fsk_demod_state.h */
-
-#ifndef FSK_DEMOD_STATE_H
-
-#include "rpack.h"
-
-
-/*
- * Demodulator state.
- * Different copy is required for each channel & subchannel being processed concurrently.
- */
-
-// TODO1.2:  change prefix from BP_ to DSP_
-
-typedef enum bp_window_e { BP_WINDOW_TRUNCATED, 
-				BP_WINDOW_COSINE, 
-				BP_WINDOW_HAMMING,
-				BP_WINDOW_BLACKMAN,
-				BP_WINDOW_FLATTOP } bp_window_t;
-
-
-struct demodulator_state_s
-{
-/*
- * These are set once during initialization.
- */
-
-	char profile;			// 'A', 'B', etc.	Upper case.
-					// Only needed to see if we are using 'F' to take fast path.
-
-#define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
-
-	int pll_step_per_sample;	// PLL is advanced by this much each audio sample.
-					// Data is sampled when it overflows.
-
-
-	int ms_filter_size;		/* Size of mark & space filters, in audio samples. */
-					/* Started off as a guess of one bit length */
-					/* but somewhat longer turned out to be better. */
-					/* Currently using same size for any prefilter. */
-
-#define MAX_FILTER_SIZE 320		/* 304 is needed for profile C, 300 baud & 44100. */
-
-/*
- * Filter length for Mark & Space in bit times.
- * e.g.  1 means 1/1200 second for 1200 baud.
- */
-	float ms_filter_len_bits;
-
-/* 
- * Window type for the various filters.
- */
-	
-	bp_window_t pre_window;
-	bp_window_t ms_window;
-	bp_window_t lp_window;
-
-
-/*
- * Alternate Low pass filters.
- * First is arbitrary number for quick IIR.
- * Second is frequency as ratio to baud rate for FIR.
- */
-	int lpf_use_fir;		/* 0 for IIR, 1 for FIR. */
-
-	float lpf_iir;			/* Only if using IIR. */
-
-	float lpf_baud;			/* Cutoff frequency as fraction of baud. */
-					/* Intuitively we'd expect this to be somewhere */
-					/* in the range of 0.5 to 1. */
-					/* In practice, it turned out a little larger */
-					/* for profiles B, C, D. */
-
-	float lp_filter_len_bits;  	/* Length in number of bit times. */
-
-	int lp_filter_size;		/* Size of Low Pass filter, in audio samples. */
-					/* Previously it was always the same as the M/S */
-					/* filters but in version 1.2 it's now independent. */
-
-/*
- * Automatic gain control.  Fast attack and slow decay factors.
- */
-	float agc_fast_attack;
-	float agc_slow_decay;
-
-/*
- * Use a longer term view for reporting signal levels.
- */
-	float quick_attack;
-	float sluggish_decay;
-
-/*
- * Hysteresis before final demodulator 0 / 1 decision.		
- */
-	float hysteresis;
-	int num_slicers;		/* >1 for multiple slicers. */
-
-/* 
- * Phase Locked Loop (PLL) inertia.
- * Larger number means less influence by signal transitions.
- */
-	float pll_locked_inertia;
-	float pll_searching_inertia;
-			
-
-/*
- * Optional band pass pre-filter before mark/space detector.
- */
-	int use_prefilter;	/* True to enable it. */
-
-	float prefilter_baud;	/* Cutoff frequencies, as fraction of */
-				/* baud rate, beyond tones used.  */
-				/* Example, if we used 1600/1800 tones at */
-				/* 300 baud, and this was 0.5, the cutoff */
-				/* frequencies would be: */
-				/* lower = min(1600,1800) - 0.5 * 300 = 1450 */
-				/* upper = max(1600,1800) + 0.5 * 300 = 1950 */
-
-	float pre_filter_len_bits;  /* Length in number of bit times. */
-
-	int pre_filter_size;	/* Size of pre filter, in audio samples. */									
-
-	float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-/*
- * Kernel for the mark and space detection filters.
- */
-					
-	float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-	float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-	float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-	float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-/*
- * The rest are continuously updated.
- */
-
-/*
- * Most recent raw audio samples, before/after prefiltering.
- */
-	float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-/*
- * Use half of the AGC code to get a measure of input audio amplitude.
- * These use "quick" attack and "sluggish" decay while the 
- * AGC uses "fast" attack and "slow" decay.
- */
-
-	float alevel_rec_peak;
-	float alevel_rec_valley;
-	float alevel_mark_peak;
-	float alevel_space_peak;
-
-/*
- * Input to the mark/space detector.
- * Could be prefiltered or raw audio.
- */
-	float ms_in_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-/*
- * Outputs from the mark and space amplitude detection, 
- * used as inputs to the FIR lowpass filters.
- * Kernel for the lowpass filters.
- */
-
-	float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-	float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-	float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-
-	float m_peak, s_peak;
-	float m_valley, s_valley;
-	float m_amp_prev, s_amp_prev;
-
-/*
- * For the PLL and data bit timing.
- * starting in version 1.2 we can have multiple slicers for one demodulator.
- * Each slicer has its own PLL and HDLC decoder.
- */
-
-#if 1
-	struct {
-
-		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.
-
-		int prev_demod_data;			// Previous data bit detected.
-							// Used to look for transitions.
-
-		/* This is used only for "9600" baud data. */
-
-		int lfsr;				// Descrambler shift register.
-
-	} slicer [MAX_SUBCHANS];
-
-#else
-	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.
-
-	int prev_demod_data;			// Previous data bit detected.
-						// Used to look for transitions.
-#endif
-
-
-
-/* 
- * Special for Rino decoder only.
- * One for each possible signal polarity.
- */
-
-#if 0
-
-	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
+/* fsk_demod_state.h */
+
+#ifndef FSK_DEMOD_STATE_H
+
+#include "rpack.h"
+
+
+/*
+ * Demodulator state.
+ * Different copy is required for each channel & subchannel being processed concurrently.
+ */
+
+// TODO1.2:  change prefix from BP_ to DSP_
+
+typedef enum bp_window_e { BP_WINDOW_TRUNCATED, 
+				BP_WINDOW_COSINE, 
+				BP_WINDOW_HAMMING,
+				BP_WINDOW_BLACKMAN,
+				BP_WINDOW_FLATTOP } bp_window_t;
+
+
+struct demodulator_state_s
+{
+/*
+ * These are set once during initialization.
+ */
+
+	char profile;			// 'A', 'B', etc.	Upper case.
+					// Only needed to see if we are using 'F' to take fast path.
+
+#define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
+
+	int pll_step_per_sample;	// PLL is advanced by this much each audio sample.
+					// Data is sampled when it overflows.
+
+
+	int ms_filter_size;		/* Size of mark & space filters, in audio samples. */
+					/* Started off as a guess of one bit length */
+					/* but somewhat longer turned out to be better. */
+					/* Currently using same size for any prefilter. */
+
+#define MAX_FILTER_SIZE 320		/* 304 is needed for profile C, 300 baud & 44100. */
+
+/*
+ * Filter length for Mark & Space in bit times.
+ * e.g.  1 means 1/1200 second for 1200 baud.
+ */
+	float ms_filter_len_bits;
+
+/* 
+ * Window type for the various filters.
+ */
+	
+	bp_window_t pre_window;
+	bp_window_t ms_window;
+	bp_window_t lp_window;
+
+
+/*
+ * Alternate Low pass filters.
+ * First is arbitrary number for quick IIR.
+ * Second is frequency as ratio to baud rate for FIR.
+ */
+	int lpf_use_fir;		/* 0 for IIR, 1 for FIR. */
+
+	float lpf_iir;			/* Only if using IIR. */
+
+	float lpf_baud;			/* Cutoff frequency as fraction of baud. */
+					/* Intuitively we'd expect this to be somewhere */
+					/* in the range of 0.5 to 1. */
+					/* In practice, it turned out a little larger */
+					/* for profiles B, C, D. */
+
+	float lp_filter_len_bits;  	/* Length in number of bit times. */
+
+	int lp_filter_size;		/* Size of Low Pass filter, in audio samples. */
+					/* Previously it was always the same as the M/S */
+					/* filters but in version 1.2 it's now independent. */
+
+/*
+ * Automatic gain control.  Fast attack and slow decay factors.
+ */
+	float agc_fast_attack;
+	float agc_slow_decay;
+
+/*
+ * Use a longer term view for reporting signal levels.
+ */
+	float quick_attack;
+	float sluggish_decay;
+
+/*
+ * Hysteresis before final demodulator 0 / 1 decision.		
+ */
+	float hysteresis;
+	int num_slicers;		/* >1 for multiple slicers. */
+
+/* 
+ * Phase Locked Loop (PLL) inertia.
+ * Larger number means less influence by signal transitions.
+ */
+	float pll_locked_inertia;
+	float pll_searching_inertia;
+			
+
+/*
+ * Optional band pass pre-filter before mark/space detector.
+ */
+	int use_prefilter;	/* True to enable it. */
+
+	float prefilter_baud;	/* Cutoff frequencies, as fraction of */
+				/* baud rate, beyond tones used.  */
+				/* Example, if we used 1600/1800 tones at */
+				/* 300 baud, and this was 0.5, the cutoff */
+				/* frequencies would be: */
+				/* lower = min(1600,1800) - 0.5 * 300 = 1450 */
+				/* upper = max(1600,1800) + 0.5 * 300 = 1950 */
+
+	float pre_filter_len_bits;  /* Length in number of bit times. */
+
+	int pre_filter_size;	/* Size of pre filter, in audio samples. */									
+
+	float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+/*
+ * Kernel for the mark and space detection filters.
+ */
+					
+	float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+	float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+	float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+	float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+/*
+ * The rest are continuously updated.
+ */
+
+/*
+ * Most recent raw audio samples, before/after prefiltering.
+ */
+	float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+/*
+ * Use half of the AGC code to get a measure of input audio amplitude.
+ * These use "quick" attack and "sluggish" decay while the 
+ * AGC uses "fast" attack and "slow" decay.
+ */
+
+	float alevel_rec_peak;
+	float alevel_rec_valley;
+	float alevel_mark_peak;
+	float alevel_space_peak;
+
+/*
+ * Input to the mark/space detector.
+ * Could be prefiltered or raw audio.
+ */
+	float ms_in_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+/*
+ * Outputs from the mark and space amplitude detection, 
+ * used as inputs to the FIR lowpass filters.
+ * Kernel for the lowpass filters.
+ */
+
+	float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+	float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+	float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+
+	float m_peak, s_peak;
+	float m_valley, s_valley;
+	float m_amp_prev, s_amp_prev;
+
+/*
+ * For the PLL and data bit timing.
+ * starting in version 1.2 we can have multiple slicers for one demodulator.
+ * Each slicer has its own PLL and HDLC decoder.
+ */
+
+/*
+ * Version 1.3: Clean up subchan vs. slicer.
+ *
+ * Originally some number of CHANNELS (originally 2, later 6)
+ * which can have multiple parallel demodulators called SUB-CHANNELS.
+ * This was originally for staggered frequencies for HF SSB.
+ * It can also be used for multiple demodulators with the same
+ * frequency but other differing parameters.
+ * Each subchannel has its own demodulator and HDLC decoder.
+ *
+ * In version 1.2 we added multiple SLICERS.
+ * The data structure, here, has multiple slicers per
+ * demodulator (subchannel).  Due to fuzzy thinking or
+ * expediency, the multiple slicers got mapped into subchannels.
+ * This means we can't use both multiple decoders and
+ * multiple slicers at the same time.
+ *
+ * Clean this up in 1.3 and keep the concepts separate.
+ * This means adding a third variable many places
+ * we are passing around the origin.
+ *
+ */
+	struct {
+
+		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.
+
+		int prev_demod_data;			// Previous data bit detected.
+							// Used to look for transitions.
+
+		/* This is used only for "9600" baud data. */
+
+		int lfsr;				// Descrambler shift register.
+
+	} slicer [MAX_SLICERS];				// Actual number in use is num_slicers.
+							// Should be in range 1 .. MAX_SLICERS,
+
+/* 
+ * Special for Rino decoder only.
+ * One for each possible signal polarity.
+ * The project showed promise but fell by the wayside.
+ */
+
+#if 0
+
+	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
 #endif
\ No newline at end of file
diff --git a/fsk_filters.h b/fsk_filters.h
index 0786462..81c4e9a 100644
--- a/fsk_filters.h
+++ b/fsk_filters.h
@@ -1,7 +1,7 @@
-/* 1200 bits/sec with Audio sample rate = 11025 */
-/* Mark freq = 1200, Space freq = 2200 */
-
-static const signed short m_sin_table[9] = { 0 , 7347 , 11257 , 9899 , 3909 , -3909 , -9899 , -11257 , -7347  };
-static const signed short m_cos_table[9] = { 11431 , 8756 , 1984 , -5715 , -10741 , -10741 , -5715 , 1984 , 8756  };
-static const signed short s_sin_table[9] = { 0 , 10950 , 6281 , -7347 , -10496 , 1327 , 11257 , 5130 , -8314  };
-static const signed short s_cos_table[9] = { 11431 , 3278 , -9550 , -8756 , 4527 , 11353 , 1984 , -10215 , -7844  };
+/* 1200 bits/sec with Audio sample rate = 11025 */
+/* Mark freq = 1200, Space freq = 2200 */
+
+static const signed short m_sin_table[9] = { 0 , 7347 , 11257 , 9899 , 3909 , -3909 , -9899 , -11257 , -7347  };
+static const signed short m_cos_table[9] = { 11431 , 8756 , 1984 , -5715 , -10741 , -10741 , -5715 , 1984 , 8756  };
+static const signed short s_sin_table[9] = { 0 , 10950 , 6281 , -7347 , -10496 , 1327 , 11257 , 5130 , -8314  };
+static const signed short s_cos_table[9] = { 11431 , 3278 , -9550 , -8756 , 4527 , 11353 , 1984 , -10215 , -7844  };
diff --git a/fsk_gen_filter.h b/fsk_gen_filter.h
index 608c69b..e7e8fa6 100644
--- a/fsk_gen_filter.h
+++ b/fsk_gen_filter.h
@@ -1,15 +1,15 @@
-
-
-#ifndef FSK_GEN_FILTER_H
-#define FSK_GEN_FILTER_H 1
-
-#include "audio.h"
-#include "fsk_demod_state.h"
-
-void fsk_gen_filter (int samples_per_sec, 
-			int baud, 
-			int mark_freq, int space_freq, 
-			char profile,
-			struct demodulator_state_s *D);
-
+
+
+#ifndef FSK_GEN_FILTER_H
+#define FSK_GEN_FILTER_H 1
+
+#include "audio.h"
+#include "fsk_demod_state.h"
+
+void fsk_gen_filter (int samples_per_sec, 
+			int baud, 
+			int mark_freq, int space_freq, 
+			char profile,
+			struct demodulator_state_s *D);
+
 #endif
\ No newline at end of file
diff --git a/gen_packets.c b/gen_packets.c
index 8fbef7d..78ad964 100644
--- a/gen_packets.c
+++ b/gen_packets.c
@@ -1,760 +1,805 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Name:	gen_packets.c
- *
- * Purpose:	Test program for generating AX.25 frames.
- *
- * Description:	Given messages are converted to audio and written 
- *		to a .WAV type audio file.
- *
- * Bugs:	Most options are implemented for only one audio channel.
- *
- * Examples:	Different speeds:
- *
- *			gen_packets -o z1.wav
- *			atest z1.wav
- *
- *			gen_packets -B 300 -o z3.wav
- *			atest -B 300 z3.wav
- *
- *			gen_packets -B 9600 -o z9.wav
- *			atest -B 300 z9.wav
- *
- *		User-defined content:
- *
- *			echo "WB2OSZ>APDW12:This is a test" | gen_packets -o z.wav -
- *			atest z.wav
- *
- *			echo "WB2OSZ>APDW12:Test line 1" >  z.txt
- *			echo "WB2OSZ>APDW12:Test line 2" >> z.txt
- *			echo "WB2OSZ>APDW12:Test line 3" >> z.txt
- *			gen_packets -o z.wav z.txt
- *			atest z.wav
- *
- *		With artificial noise added:
- *
- *			gen_packets -n 100 -o z2.wav
- *			atest z2.wav
- *
- *		
- *------------------------------------------------------------------*/
-
-
-
-
-#include <stdio.h>     
-#include <stdlib.h>    
-#include <getopt.h>
-#include <string.h>
-#include <assert.h>
-
-#include "audio.h"
-#include "ax25_pad.h"
-#include "hdlc_send.h"
-#include "gen_tone.h"
-#include "textcolor.h"
-
-
-static void usage (char **argv);
-static int audio_file_open (char *fname, struct audio_s *pa);
-static int audio_file_close (void);
-
-static int g_add_noise = 0;
-static float g_noise_level = 0;
-
-static struct audio_s modem;
-
-
-static void send_packet (char *str)
-{
-    	packet_t pp;
-    	unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
-    	int flen;
-	int c;
-
-	pp = ax25_from_text (str, 1);
-	flen = ax25_pack (pp, fbuf);
-	for (c=0; c<modem.adev[0].num_channels; c++)
-	{
-	   hdlc_send_flags (c, 8, 0);
-	   hdlc_send_frame (c, fbuf, flen);
-	   hdlc_send_flags (c, 2, 1);
-	}
-	ax25_delete (pp);
-}
-
-
-
-int main(int argc, char **argv)
-{
-	int c;
-	int digit_optind = 0;
-	int err;
-	int packet_count = 0;
-	int i;
-	int chan;
-
-/*
- * Set up default values for the modem.
- */
-
-	memset (&modem, 0, sizeof(modem));
-
-	modem.adev[0].defined = 1;
-        modem.adev[0].num_channels = DEFAULT_NUM_CHANNELS;              /* -2 stereo */
-        modem.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;        /* -r option */
-        modem.adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;        /* -8 for 8 instead of 16 bits */
-        
-	for (chan = 0; chan < MAX_CHANS; chan++) {
-	  modem.achan[chan].modem_type = MODEM_AFSK;			/* change with -g */
-	  modem.achan[chan].mark_freq = DEFAULT_MARK_FREQ;              /* -m option */
-          modem.achan[chan].space_freq = DEFAULT_SPACE_FREQ;            /* -s option */
-          modem.achan[chan].baud = DEFAULT_BAUD;                        /* -b option */
-	}
-
-	modem.achan[0].valid = 1;
-
-
-/*
- * Set up other default values.
- */
-	int amplitude = 50;		/* -a option */
-					/* 100% is actually half of the digital signal range so */
-					/* we have some headroom for adding noise, etc. */
-
-	int leading_zeros = 12;		/* -z option TODO: not implemented, should replace with txdelay frames. */
-	char output_file[256];		/* -o option */
-	FILE *input_fp = NULL;		/* File or NULL for built-in message */
-
-	strcpy (output_file, "");
-
-/*
- * Parse the command line options.
- */
-
-	while (1) {
-          int this_option_optind = optind ? optind : 1;
-          int option_index = 0;
-          static struct option long_options[] = {
-            {"future1", 1, 0, 0},
-            {"future2", 0, 0, 0},
-            {"future3", 1, 0, 'c'},
-            {0, 0, 0, 0}
-          };
-
-	  /* ':' following option character means arg is required. */
-
-          c = getopt_long(argc, argv, "gm:s:a:b:B:r:n:o:z:82",
-                        long_options, &option_index);
-          if (c == -1)
-            break;
-
-          switch (c) {
-
-            case 0:				/* possible future use */
-
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf("option %s", long_options[option_index].name);
-              if (optarg) {
-                dw_printf(" with arg %s", optarg);
-              }
-              dw_printf("\n");
-              break;
-
-            case 'b':				/* -b for data Bit rate */
-
-              modem.achan[0].baud = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
-              if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) {
-                text_color_set(DW_COLOR_ERROR); 
-                dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
-                exit (EXIT_FAILURE);
-              }
-              break;
-
-            case 'B':				/* -B for data Bit rate */
-						/*    300 implies 1600/1800 AFSK. */
-						/*    1200 implies 1200/2200 AFSK. */
-						/*    9600 implies scrambled. */
-
-              modem.achan[0].baud = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
-              if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) {
-                text_color_set(DW_COLOR_ERROR); 
-                dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
-                exit (EXIT_FAILURE);
-              }
-
-	      switch (modem.achan[0].baud) {
-	        case 300:
-                  modem.achan[0].mark_freq = 1600;
-                  modem.achan[0].space_freq = 1800;
-	          break;
-	        case 1200:
-                  modem.achan[0].mark_freq = 1200;
-                  modem.achan[0].space_freq = 2200;
-	          break;
-	        case 9600:
-                  modem.achan[0].modem_type = MODEM_SCRAMBLE;
-                  text_color_set(DW_COLOR_INFO); 
-                  dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
-	          break;
-	      }
-              break;
-
-            case 'g':				/* -g for g3ruh scrambling */
-
-              modem.achan[0].modem_type = MODEM_SCRAMBLE;
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
-              break;
-
-            case 'm':				/* -m for Mark freq */
-
-              modem.achan[0].mark_freq = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Mark frequency set to %d Hz.\n", modem.achan[0].mark_freq);
-              if (modem.achan[0].mark_freq < 300 || modem.achan[0].mark_freq > 3000) {
-                text_color_set(DW_COLOR_ERROR); 
-	        dw_printf ("Use a more reasonable value in range of 300 - 3000.\n");
-                exit (EXIT_FAILURE);
-              }
-              break;
-
-            case 's':				/* -s for Space freq */
-
-              modem.achan[0].space_freq = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Space frequency set to %d Hz.\n", modem.achan[0].space_freq);
-              if (modem.achan[0].space_freq < 300 || modem.achan[0].space_freq > 3000) {
-                text_color_set(DW_COLOR_ERROR); 
-	        dw_printf ("Use a more reasonable value in range of 300 - 3000.\n");
-                exit (EXIT_FAILURE);
-              }
-              break;
-
-            case 'n':				/* -n number of packets with increasing noise. */
-
-	      packet_count = atoi(optarg);
-
-	      g_add_noise = 1;
-
-              break;
-
-            case 'a':				/* -a for amplitude */
-
-              amplitude = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Amplitude set to %d%%.\n", amplitude);
-              if (amplitude < 0 || amplitude > 200) {
-                text_color_set(DW_COLOR_ERROR); 
-	        dw_printf ("Amplitude must be in range of 0 to 200.\n");
-                exit (EXIT_FAILURE);
-              }
-              break;
-
-            case 'r':				/* -r for audio sample Rate */
-
-              modem.adev[0].samples_per_sec = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Audio sample rate set to %d samples / second.\n", modem.adev[0].samples_per_sec);
-              if (modem.adev[0].samples_per_sec < MIN_SAMPLES_PER_SEC || modem.adev[0].samples_per_sec > MAX_SAMPLES_PER_SEC) {
-                text_color_set(DW_COLOR_ERROR); 
-	        dw_printf ("Use a more reasonable audio sample rate in range of %d - %d.\n",
-						MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC);
-                exit (EXIT_FAILURE);
-              }
-              break;
-
-            case 'z':				/* -z leading zeros before frame flag */
-
-              leading_zeros = atoi(optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Send %d zero bits before frame flag.\n", leading_zeros);
-
-	      /* The demodulator needs a few for the clock recovery PLL. */
-	      /* We don't want to be here all day either. */
-              /* We can't translate to time yet because the data bit rate */
-              /* could be changed later. */
-
-              if (leading_zeros < 8 || leading_zeros > 12000) {
-                text_color_set(DW_COLOR_ERROR); 
-	        dw_printf ("Use a more reasonable value.\n");
-                exit (EXIT_FAILURE);
-              }
-              break;
-
-            case '8':				/* -8 for 8 bit samples */
-
-              modem.adev[0].bits_per_sample = 8;
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf("8 bits per audio sample rather than 16.\n");
-              break;
-
-            case '2':				/* -2 for 2 channels of sound */
-  
-              modem.adev[0].num_channels = 2;
-	      modem.achan[1].valid = 1;
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf("2 channels of sound rather than 1.\n");
-              break;
-
-            case 'o':				/* -o for Output file */
-
-              strcpy (output_file, optarg);
-              text_color_set(DW_COLOR_INFO); 
-              dw_printf ("Output file set to %s\n", output_file);
-              break;
-
-            case '?':
-
-              /* Unknown option message was already printed. */
-              usage (argv);
-              break;
-
-            default:
-
-              /* Should not be here. */
-              text_color_set(DW_COLOR_ERROR); 
-              dw_printf("?? getopt returned character code 0%o ??\n", c);
-              usage (argv);
-          }
-	}
-
-
-/*
- * Open the output file.
- */
-
-        if (strlen(output_file) == 0) {
-          text_color_set(DW_COLOR_ERROR); 
-          dw_printf ("ERROR: The -o ouput file option must be specified.\n");
-          usage (argv);
-          exit (1);
-        }
-
-	err = audio_file_open (output_file, &modem);
-
-
-        if (err < 0) {
-          text_color_set(DW_COLOR_ERROR); 
-          dw_printf ("ERROR - Can't open output file.\n");
-          exit (1);
-        }
-
-
-	gen_tone_init (&modem, amplitude/2);
-
-        assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16);
-        assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2);
-        assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC);
-
-/*
- * Get user packets(s) from file or stdin if specified.
- * "-n" option is ignored in this case.
- */
-
-	if (optind < argc) {
-
-	  char str[400];
-
-          // dw_printf("non-option ARGV-elements: ");
-          // while (optind < argc)
-          // dw_printf("%s ", argv[optind++]);
-          //dw_printf("\n");
-
-          if (optind < argc - 1) {
-            text_color_set(DW_COLOR_ERROR); 
-	    dw_printf ("Warning: File(s) beyond the first are ignored.\n");
-          }
-
-          if (strcmp(argv[optind], "-") == 0) {
-            text_color_set(DW_COLOR_INFO); 
-            dw_printf ("Reading from stdin ...\n");
-            input_fp = stdin;
-          }
-          else {
-            input_fp = fopen(argv[optind], "r");
-            if (input_fp == NULL) {
-              text_color_set(DW_COLOR_ERROR); 
- 	      dw_printf ("Can't open %s for read.\n", argv[optind]);
-              exit (EXIT_FAILURE);
-            }
-            text_color_set(DW_COLOR_INFO); 
-            dw_printf ("Reading from %s ...\n", argv[optind]);    
-          }
-
-          while (fgets (str, sizeof(str), input_fp) != NULL) {
-            text_color_set(DW_COLOR_REC); 
-            dw_printf ("%s", str);
-	    send_packet (str);
-	  }
-
-          if (input_fp != stdin) {
-            fclose (input_fp);
-          }
-
-	  audio_file_close();
-    	  return EXIT_SUCCESS;
-	}
-
-/* 
- * Otherwise, use the built in packets.
- */
-      	text_color_set(DW_COLOR_INFO); 
-      	dw_printf ("built in message...\n");
-	
-
-	if (packet_count > 0)  {
-
-/*
- * Generate packets with increasing noise level.
- * Would probably be better to record real noise from a radio but
- * for now just use a random number generator.
- */
-	  for (i = 1; i <= packet_count; i++) {
-
-	    char stemp[80];
-	
-	    if (modem.achan[0].modem_type == MODEM_SCRAMBLE) {
-	      g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count);
-	    }
-	    else {
-		/* About 2/3 should be decoded properly. */
-	      g_noise_level = amplitude *.0023 * ((float)i / packet_count);
-	    }
-
-	    sprintf (stemp, "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  %04d of %04d", i, packet_count);
-
-	    send_packet (stemp);
-
-	  }
-	}
-	else {
-
-/*
- * Builtin default 4 packets.
- */
-
-	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  1 of 4");
-	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  2 of 4");
-	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  3 of 4");
-	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  4 of 4");
-	}
-
-	audio_file_close();
-
-    	return EXIT_SUCCESS;
-}
-
-
-static void usage (char **argv)
-{
-
-	text_color_set(DW_COLOR_ERROR); 
-	dw_printf ("\n");
-	dw_printf ("Usage: gen_packets [options] [file]\n");
-	dw_printf ("Options:\n");
-	dw_printf ("  -a <number>   Signal amplitude in range of 0 - 200%%.  Default 50.\n");
-	dw_printf ("  -b <number>   Bits / second for data.  Default is %d.\n", DEFAULT_BAUD);
-	dw_printf ("  -B <number>   Bits / second for data.  Proper modem selected for 300, 1200, 9600.\n");
-	dw_printf ("  -g            Scrambled baseband rather than AFSK.\n");
-	dw_printf ("  -m <number>   Mark frequency.  Default is %d.\n", DEFAULT_MARK_FREQ);
-	dw_printf ("  -s <number>   Space frequency.  Default is %d.\n", DEFAULT_SPACE_FREQ);
-	dw_printf ("  -r <number>   Audio sample Rate.  Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);
-	dw_printf ("  -n <number>   Generate specified number of frames with increasing noise.\n");
-	dw_printf ("  -o <file>     Send output to .wav file.\n");
-//	dw_printf ("  -8            8 bit audio rather than 16.\n");
-//	dw_printf ("  -2            2 channels of audio rather than 1.\n");
-//	dw_printf ("  -z <number>   Number of leading zero bits before frame.\n");
-//	dw_printf ("                  Default is 12 which is .01 seconds at 1200 bits/sec.\n");
-
-	dw_printf ("\n");
-	dw_printf ("An optional file may be specified to provide messages other than\n");
-	dw_printf ("the default built-in message. The format should correspond to\n");
-	dw_printf ("the standard packet monitoring representation such as,\n\n");
-	dw_printf ("    WB2OSZ-1>APDW12,WIDE2-2:!4237.14NS07120.83W#\n");
-	dw_printf ("\n");
-	dw_printf ("Example:  gen_packets -o x.wav \n");
-	dw_printf ("\n");
-        dw_printf ("    With all defaults, a built-in test message is generated\n");
-	dw_printf ("    with standard Bell 202 tones used for packet radio on ordinary\n");
-	dw_printf ("    VHF FM transceivers.\n");
-	dw_printf ("\n");
-	dw_printf ("Example:  gen_packets -o x.wav -g -b 9600\n");
-	dw_printf ("Shortcut: gen_packets -o x.wav -B 9600\n");
-	dw_printf ("\n");
-        dw_printf ("    9600 baud mode.\n");
-	dw_printf ("\n");
-	dw_printf ("Example:  gen_packets -o x.wav -m 1600 -s 1800 -b 300\n");
-	dw_printf ("Shortcut: gen_packets -o x.wav -B 300\n");
-	dw_printf ("\n");
-        dw_printf ("    200 Hz shift, 300 baud, suitable for HF SSB transceiver.\n");
-	dw_printf ("\n");
-	dw_printf ("Example:  echo -n \"WB2OSZ>WORLD:Hello, world!\" | gen_packets -a 25 -o x.wav -\n");
-	dw_printf ("\n");
-        dw_printf ("    Read message from stdin and put quarter volume sound into the file x.wav.\n");
-
-	exit (EXIT_FAILURE);
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_file_open
- *
- * Purpose:     Open a .WAV format file for output.
- *
- * Inputs:      fname		- Name of .WAV file to create.
- *
- *		pa		- Address of structure of type audio_s.
- *				
- *				The fields that we care about are:
- *					num_channels
- *					samples_per_sec
- *					bits_per_sample
- *				If zero, reasonable defaults will be provided.
- *         
- * Returns:     0 for success, -1 for failure.
- *		
- *----------------------------------------------------------------*/
-
-struct wav_header {             /* .WAV file header. */
-        char riff[4];           /* "RIFF" */
-        int filesize;          /* file length - 8 */
-        char wave[4];           /* "WAVE" */
-        char fmt[4];            /* "fmt " */
-        int fmtsize;           /* 16. */
-        short wformattag;       /* 1 for PCM. */
-        short nchannels;        /* 1 for mono, 2 for stereo. */
-        int nsamplespersec;    /* sampling freq, Hz. */
-        int navgbytespersec;   /* = nblockalign * nsamplespersec. */
-        short nblockalign;      /* = wbitspersample / 8 * nchannels. */
-        short wbitspersample;   /* 16 or 8. */
-        char data[4];           /* "data" */
-        int datasize;          /* number of bytes following. */
-} ;
-
-				/* 8 bit samples are unsigned bytes */
-				/* in range of 0 .. 255. */
- 
-				/* 16 bit samples are signed short */
-				/* in range of -32768 .. +32767. */
-
-static FILE *out_fp = NULL;
-
-static struct wav_header header;
-
-static int byte_count;			/* Number of data bytes written to file. */
-					/* Will be written to header when file is closed. */
-
-
-static int audio_file_open (char *fname, struct audio_s *pa)
-{
-	int n;
-
-/*
- * Fill in defaults for any missing values.
- */
-	if (pa -> adev[0].num_channels == 0)
-	  pa -> adev[0].num_channels = DEFAULT_NUM_CHANNELS;
-
-	if (pa -> adev[0].samples_per_sec == 0)
-	  pa -> adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
-
-	if (pa -> adev[0].bits_per_sample == 0)
-	  pa -> adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
-
-
-/*
- * Write the file header.  Don't know length yet.
- */
-        out_fp = fopen (fname, "wb");	
-	
-        if (out_fp == NULL) {
-           text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for write: %s\n", fname);
-	   perror ("");
-           return (-1);
-        }
-
-	memset (&header, 0, sizeof(header));
-
-        memcpy (header.riff, "RIFF", (size_t)4);
-        header.filesize = 0; 
-        memcpy (header.wave, "WAVE", (size_t)4);
-        memcpy (header.fmt, "fmt ", (size_t)4);
-        header.fmtsize = 16;			// Always 16.
-        header.wformattag = 1;     		// 1 for PCM.
-
-        header.nchannels = pa -> adev[0].num_channels;   		
-        header.nsamplespersec = pa -> adev[0].samples_per_sec;    
-        header.wbitspersample = pa -> adev[0].bits_per_sample;  
-		
-        header.nblockalign = header.wbitspersample / 8 * header.nchannels;     
-        header.navgbytespersec = header.nblockalign * header.nsamplespersec;   
-        memcpy (header.data, "data", (size_t)4);
-        header.datasize = 0;        
-
-	assert (header.nchannels == 1 || header.nchannels == 2);
-
-        n = fwrite (&header, sizeof(header), (size_t)1, out_fp);
-
-	if (n != 1) {
-          text_color_set(DW_COLOR_ERROR); 
-	  dw_printf ("Couldn't write header to: %s\n", fname);
-	  perror ("");
-	  fclose (out_fp);
-	  out_fp = NULL;
-          return (-1);
-        }
-
-
-/*
- * Number of bytes written will be filled in later.
- */
-        byte_count = 0;
-	
-	return (0);
-
-} /* end audio_open */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_put
- *
- * Purpose:     Send one byte to the audio output file.
- *
- * Inputs:	c	- One byte in range of 0 - 255.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- * Description:	The caller must deal with the details of mono/stereo
- *		and number of bytes per sample.
- *
- *----------------------------------------------------------------*/
-
-
-int audio_put (int a, int c)
-{
-	static short sample16;
-	int s;
-
-	if (g_add_noise) {
-
-	  if ((byte_count & 1) == 0) {
-	    sample16 = c & 0xff;		/* save lower byte. */
-	    byte_count++;
-	    return c;
-	  }
-	  else {
-	    float r;
-
-	    sample16 |= (c << 8) & 0xff00;	/* insert upper byte. */
-	    byte_count++;
-	    s = sample16;  // sign extend.
-
-/* Add random noise to the signal. */
-/* r should be in range of -1 .. +1. */
-	    
-	    r = (rand() - RAND_MAX/2.0) / (RAND_MAX/2.0);
-
-	    s += 5 * r * g_noise_level * 32767;
-
-	    if (s > 32767) s = 32767;
-	    if (s < -32767) s = -32767;
-
-	    putc(s & 0xff, out_fp);  
-	    return (putc((s >> 8) & 0xff, out_fp));
-	  }
-	}
-	else {
-	  byte_count++;
-	  return (putc(c, out_fp));
-	}
-
-} /* end audio_put */
-
-
-int audio_flush (int a)
-{
-	return 0;
-}
-
-/*------------------------------------------------------------------
- *
- * Name:        audio_file_close
- *
- * Purpose:     Close the audio output file.
- *
- * Returns:     Normally non-negative.
- *              -1 for any type of error.
- *
- *
- * Description:	Must go back to beginning of file and fill in the
- *		size of the data.
- *
- *----------------------------------------------------------------*/
-
-static int audio_file_close (void)
-{
-	int n;
-
-        //text_color_set(DW_COLOR_DEBUG); 
-	//dw_printf ("audio_close()\n");
-
-/*
- * Go back and fix up lengths in header.
- */
-        header.filesize = byte_count + sizeof(header) - 8;           
-        header.datasize = byte_count;        
-
-	if (out_fp == NULL) {
-	  return (-1);
- 	}
-
-        fflush (out_fp);
-
-        fseek (out_fp, 0L, SEEK_SET);         
-        n = fwrite (&header, sizeof(header), (size_t)1, out_fp);
-
-	if (n != 1) {
-          text_color_set(DW_COLOR_ERROR); 
-	  dw_printf ("Couldn't write header to audio file.\n");
-	  perror ("");		// TODO: remove perror.
-	  fclose (out_fp);
-	  out_fp = NULL;
-          return (-1);
-        }
-
-        fclose (out_fp);
-        out_fp = NULL;
-
-	return (0);
-
-} /* end audio_close */
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:	gen_packets.c
+ *
+ * Purpose:	Test program for generating AX.25 frames.
+ *
+ * Description:	Given messages are converted to audio and written 
+ *		to a .WAV type audio file.
+ *
+ * Bugs:	Most options are implemented for only one audio channel.
+ *
+ * Examples:	Different speeds:
+ *
+ *			gen_packets -o z1.wav
+ *			atest z1.wav
+ *
+ *			gen_packets -B 300 -o z3.wav
+ *			atest -B 300 z3.wav
+ *
+ *			gen_packets -B 9600 -o z9.wav
+ *			atest -B 300 z9.wav
+ *
+ *		User-defined content:
+ *
+ *			echo "WB2OSZ>APDW12:This is a test" | gen_packets -o z.wav -
+ *			atest z.wav
+ *
+ *			echo "WB2OSZ>APDW12:Test line 1" >  z.txt
+ *			echo "WB2OSZ>APDW12:Test line 2" >> z.txt
+ *			echo "WB2OSZ>APDW12:Test line 3" >> z.txt
+ *			gen_packets -o z.wav z.txt
+ *			atest z.wav
+ *
+ *		With artificial noise added:
+ *
+ *			gen_packets -n 100 -o z2.wav
+ *			atest z2.wav
+ *
+ *		
+ *------------------------------------------------------------------*/
+
+
+
+
+#include <stdio.h>     
+#include <stdlib.h>    
+#include <getopt.h>
+#include <string.h>
+#include <assert.h>
+
+#include "audio.h"
+#include "ax25_pad.h"
+#include "hdlc_send.h"
+#include "gen_tone.h"
+#include "textcolor.h"
+#include "morse.h"
+
+
+static void usage (char **argv);
+static int audio_file_open (char *fname, struct audio_s *pa);
+static int audio_file_close (void);
+
+static int g_add_noise = 0;
+static float g_noise_level = 0;
+static int g_morse_wpm = 0;		/* Send morse code at this speed. */
+
+
+static struct audio_s modem;
+
+
+static void send_packet (char *str)
+{
+    	packet_t pp;
+    	unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
+    	int flen;
+	int c;
+
+
+	if (g_morse_wpm > 0) {
+
+	  morse_send (0, str, g_morse_wpm, 100, 100);
+	}
+	else {
+	  pp = ax25_from_text (str, 1);
+	  flen = ax25_pack (pp, fbuf);
+	  for (c=0; c<modem.adev[0].num_channels; c++)
+	  {
+	    hdlc_send_flags (c, 8, 0);
+	    hdlc_send_frame (c, fbuf, flen);
+	    hdlc_send_flags (c, 2, 1);
+	  }
+	  ax25_delete (pp);
+	}
+}
+
+
+
+int main(int argc, char **argv)
+{
+	int c;
+	//int digit_optind = 0;
+	int err;
+	int packet_count = 0;
+	int i;
+	int chan;
+
+/*
+ * Set up default values for the modem.
+ */
+
+	memset (&modem, 0, sizeof(modem));
+
+	modem.adev[0].defined = 1;
+        modem.adev[0].num_channels = DEFAULT_NUM_CHANNELS;              /* -2 stereo */
+        modem.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;        /* -r option */
+        modem.adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;        /* -8 for 8 instead of 16 bits */
+        
+	for (chan = 0; chan < MAX_CHANS; chan++) {
+	  modem.achan[chan].modem_type = MODEM_AFSK;			/* change with -g */
+	  modem.achan[chan].mark_freq = DEFAULT_MARK_FREQ;              /* -m option */
+          modem.achan[chan].space_freq = DEFAULT_SPACE_FREQ;            /* -s option */
+          modem.achan[chan].baud = DEFAULT_BAUD;                        /* -b option */
+	}
+
+	modem.achan[0].valid = 1;
+
+
+/*
+ * Set up other default values.
+ */
+	int amplitude = 50;		/* -a option */
+					/* 100% is actually half of the digital signal range so */
+					/* we have some headroom for adding noise, etc. */
+
+	int leading_zeros = 12;		/* -z option TODO: not implemented, should replace with txdelay frames. */
+	char output_file[256];		/* -o option */
+	FILE *input_fp = NULL;		/* File or NULL for built-in message */
+
+	strlcpy (output_file, "", sizeof(output_file));
+
+/*
+ * Parse the command line options.
+ */
+
+	while (1) {
+          //int this_option_optind = optind ? optind : 1;
+          int option_index = 0;
+          static struct option long_options[] = {
+            {"future1", 1, 0, 0},
+            {"future2", 0, 0, 0},
+            {"future3", 1, 0, 'c'},
+            {0, 0, 0, 0}
+          };
+
+	  /* ':' following option character means arg is required. */
+
+          c = getopt_long(argc, argv, "gm:s:a:b:B:r:n:o:z:82M:",
+                        long_options, &option_index);
+          if (c == -1)
+            break;
+
+          switch (c) {
+
+            case 0:				/* possible future use */
+
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf("option %s", long_options[option_index].name);
+              if (optarg) {
+                dw_printf(" with arg %s", optarg);
+              }
+              dw_printf("\n");
+              break;
+
+            case 'b':				/* -b for data Bit rate */
+
+              modem.achan[0].baud = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
+              if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) {
+                text_color_set(DW_COLOR_ERROR); 
+                dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case 'B':				/* -B for data Bit rate */
+						/*    300 implies 1600/1800 AFSK. */
+						/*    1200 implies 1200/2200 AFSK. */
+						/*    9600 implies scrambled. */
+
+              modem.achan[0].baud = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
+              if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) {
+                text_color_set(DW_COLOR_ERROR); 
+                dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
+                exit (EXIT_FAILURE);
+              }
+
+	      switch (modem.achan[0].baud) {
+	        case 300:
+                  modem.achan[0].mark_freq = 1600;
+                  modem.achan[0].space_freq = 1800;
+	          break;
+	        case 1200:
+                  modem.achan[0].mark_freq = 1200;
+                  modem.achan[0].space_freq = 2200;
+	          break;
+	        case 9600:
+                  modem.achan[0].modem_type = MODEM_SCRAMBLE;
+                  text_color_set(DW_COLOR_INFO); 
+                  dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
+	          break;
+	      }
+              break;
+
+            case 'g':				/* -g for g3ruh scrambling */
+
+              modem.achan[0].modem_type = MODEM_SCRAMBLE;
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
+              break;
+
+            case 'm':				/* -m for Mark freq */
+
+              modem.achan[0].mark_freq = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Mark frequency set to %d Hz.\n", modem.achan[0].mark_freq);
+              if (modem.achan[0].mark_freq < 300 || modem.achan[0].mark_freq > 3000) {
+                text_color_set(DW_COLOR_ERROR); 
+	        dw_printf ("Use a more reasonable value in range of 300 - 3000.\n");
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case 's':				/* -s for Space freq */
+
+              modem.achan[0].space_freq = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Space frequency set to %d Hz.\n", modem.achan[0].space_freq);
+              if (modem.achan[0].space_freq < 300 || modem.achan[0].space_freq > 3000) {
+                text_color_set(DW_COLOR_ERROR); 
+	        dw_printf ("Use a more reasonable value in range of 300 - 3000.\n");
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case 'n':				/* -n number of packets with increasing noise. */
+
+	      packet_count = atoi(optarg);
+
+	      g_add_noise = 1;
+
+              break;
+
+            case 'a':				/* -a for amplitude */
+
+              amplitude = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Amplitude set to %d%%.\n", amplitude);
+              if (amplitude < 0 || amplitude > 200) {
+                text_color_set(DW_COLOR_ERROR); 
+	        dw_printf ("Amplitude must be in range of 0 to 200.\n");
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case 'r':				/* -r for audio sample Rate */
+
+              modem.adev[0].samples_per_sec = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Audio sample rate set to %d samples / second.\n", modem.adev[0].samples_per_sec);
+              if (modem.adev[0].samples_per_sec < MIN_SAMPLES_PER_SEC || modem.adev[0].samples_per_sec > MAX_SAMPLES_PER_SEC) {
+                text_color_set(DW_COLOR_ERROR); 
+	        dw_printf ("Use a more reasonable audio sample rate in range of %d - %d.\n",
+						MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC);
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case 'z':				/* -z leading zeros before frame flag */
+
+              leading_zeros = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Send %d zero bits before frame flag.\n", leading_zeros);
+
+	      /* The demodulator needs a few for the clock recovery PLL. */
+	      /* We don't want to be here all day either. */
+              /* We can't translate to time yet because the data bit rate */
+              /* could be changed later. */
+
+              if (leading_zeros < 8 || leading_zeros > 12000) {
+                text_color_set(DW_COLOR_ERROR); 
+	        dw_printf ("Use a more reasonable value.\n");
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case '8':				/* -8 for 8 bit samples */
+
+              modem.adev[0].bits_per_sample = 8;
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf("8 bits per audio sample rather than 16.\n");
+              break;
+
+            case '2':				/* -2 for 2 channels of sound */
+  
+              modem.adev[0].num_channels = 2;
+	      modem.achan[1].valid = 1;
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf("2 channels of sound rather than 1.\n");
+              break;
+
+            case 'o':				/* -o for Output file */
+
+              strlcpy (output_file, optarg, sizeof(output_file));
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Output file set to %s\n", output_file);
+              break;
+
+            case 'M':				/* -M for morse code speed */
+
+//TODO: document this.
+
+              g_morse_wpm = atoi(optarg);
+              text_color_set(DW_COLOR_INFO); 
+              dw_printf ("Morse code speed set to %d WPM.\n", g_morse_wpm);
+              if (g_morse_wpm < 5 || g_morse_wpm > 50) {
+                text_color_set(DW_COLOR_ERROR); 
+	        dw_printf ("Morse code speed must be in range of 5 to 50 WPM.\n");
+                exit (EXIT_FAILURE);
+              }
+              break;
+
+            case '?':
+
+              /* Unknown option message was already printed. */
+              usage (argv);
+              break;
+
+            default:
+
+              /* Should not be here. */
+              text_color_set(DW_COLOR_ERROR); 
+              dw_printf("?? getopt returned character code 0%o ??\n", c);
+              usage (argv);
+          }
+	}
+
+
+/*
+ * Open the output file.
+ */
+
+        if (strlen(output_file) == 0) {
+          text_color_set(DW_COLOR_ERROR); 
+          dw_printf ("ERROR: The -o ouput file option must be specified.\n");
+          usage (argv);
+          exit (1);
+        }
+
+	err = audio_file_open (output_file, &modem);
+
+
+        if (err < 0) {
+          text_color_set(DW_COLOR_ERROR); 
+          dw_printf ("ERROR - Can't open output file.\n");
+          exit (1);
+        }
+
+
+
+
+	gen_tone_init (&modem, amplitude/2);
+	morse_init (&modem, amplitude/2);
+
+
+        assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16);
+        assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2);
+        assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC);
+
+/*
+ * Get user packets(s) from file or stdin if specified.
+ * "-n" option is ignored in this case.
+ */
+
+	if (optind < argc) {
+
+	  char str[400];
+
+          // dw_printf("non-option ARGV-elements: ");
+          // while (optind < argc)
+          // dw_printf("%s ", argv[optind++]);
+          //dw_printf("\n");
+
+          if (optind < argc - 1) {
+            text_color_set(DW_COLOR_ERROR); 
+	    dw_printf ("Warning: File(s) beyond the first are ignored.\n");
+          }
+
+          if (strcmp(argv[optind], "-") == 0) {
+            text_color_set(DW_COLOR_INFO); 
+            dw_printf ("Reading from stdin ...\n");
+            input_fp = stdin;
+          }
+          else {
+            input_fp = fopen(argv[optind], "r");
+            if (input_fp == NULL) {
+              text_color_set(DW_COLOR_ERROR); 
+ 	      dw_printf ("Can't open %s for read.\n", argv[optind]);
+              exit (EXIT_FAILURE);
+            }
+            text_color_set(DW_COLOR_INFO); 
+            dw_printf ("Reading from %s ...\n", argv[optind]);    
+          }
+
+          while (fgets (str, sizeof(str), input_fp) != NULL) {
+            text_color_set(DW_COLOR_REC); 
+            dw_printf ("%s", str);
+	    send_packet (str);
+	  }
+
+          if (input_fp != stdin) {
+            fclose (input_fp);
+          }
+
+	  audio_file_close();
+    	  return EXIT_SUCCESS;
+	}
+
+/* 
+ * Otherwise, use the built in packets.
+ */
+      	text_color_set(DW_COLOR_INFO); 
+      	dw_printf ("built in message...\n");
+	
+
+	if (packet_count > 0)  {
+
+/*
+ * Generate packets with increasing noise level.
+ * Would probably be better to record real noise from a radio but
+ * for now just use a random number generator.
+ */
+	  for (i = 1; i <= packet_count; i++) {
+
+	    char stemp[80];
+	
+	    if (modem.achan[0].modem_type == MODEM_SCRAMBLE) {
+	      g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count);
+	    }
+	    else if (modem.achan[0].baud < 600) {
+		/* About 2/3 should be decoded properly. */
+	      g_noise_level = amplitude *.0048 * ((float)i / packet_count);
+	    }
+	    else {
+		/* About 2/3 should be decoded properly. */
+	      g_noise_level = amplitude *.0023 * ((float)i / packet_count);
+	    }
+
+	    snprintf (stemp, sizeof(stemp), "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  %04d of %04d", i, packet_count);
+
+	    send_packet (stemp);
+
+	  }
+	}
+	else {
+
+/*
+ * Builtin default 4 packets.
+ */
+
+	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  1 of 4");
+	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  2 of 4");
+	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  3 of 4");
+	  send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog!  4 of 4");
+	}
+
+	audio_file_close();
+
+    	return EXIT_SUCCESS;
+}
+
+
+static void usage (char **argv)
+{
+
+	text_color_set(DW_COLOR_ERROR); 
+	dw_printf ("\n");
+	dw_printf ("Usage: gen_packets [options] [file]\n");
+	dw_printf ("Options:\n");
+	dw_printf ("  -a <number>   Signal amplitude in range of 0 - 200%%.  Default 50.\n");
+	dw_printf ("  -b <number>   Bits / second for data.  Default is %d.\n", DEFAULT_BAUD);
+	dw_printf ("  -B <number>   Bits / second for data.  Proper modem selected for 300, 1200, 9600.\n");
+	dw_printf ("  -g            Scrambled baseband rather than AFSK.\n");
+	dw_printf ("  -m <number>   Mark frequency.  Default is %d.\n", DEFAULT_MARK_FREQ);
+	dw_printf ("  -s <number>   Space frequency.  Default is %d.\n", DEFAULT_SPACE_FREQ);
+	dw_printf ("  -r <number>   Audio sample Rate.  Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);
+	dw_printf ("  -n <number>   Generate specified number of frames with increasing noise.\n");
+	dw_printf ("  -o <file>     Send output to .wav file.\n");
+//	dw_printf ("  -8            8 bit audio rather than 16.\n");
+//	dw_printf ("  -2            2 channels of audio rather than 1.\n");
+//	dw_printf ("  -z <number>   Number of leading zero bits before frame.\n");
+//	dw_printf ("                  Default is 12 which is .01 seconds at 1200 bits/sec.\n");
+
+	dw_printf ("\n");
+	dw_printf ("An optional file may be specified to provide messages other than\n");
+	dw_printf ("the default built-in message. The format should correspond to\n");
+	dw_printf ("the standard packet monitoring representation such as,\n\n");
+	dw_printf ("    WB2OSZ-1>APDW12,WIDE2-2:!4237.14NS07120.83W#\n");
+	dw_printf ("\n");
+	dw_printf ("Example:  gen_packets -o x.wav \n");
+	dw_printf ("\n");
+        dw_printf ("    With all defaults, a built-in test message is generated\n");
+	dw_printf ("    with standard Bell 202 tones used for packet radio on ordinary\n");
+	dw_printf ("    VHF FM transceivers.\n");
+	dw_printf ("\n");
+	dw_printf ("Example:  gen_packets -o x.wav -g -b 9600\n");
+	dw_printf ("Shortcut: gen_packets -o x.wav -B 9600\n");
+	dw_printf ("\n");
+        dw_printf ("    9600 baud mode.\n");
+	dw_printf ("\n");
+	dw_printf ("Example:  gen_packets -o x.wav -m 1600 -s 1800 -b 300\n");
+	dw_printf ("Shortcut: gen_packets -o x.wav -B 300\n");
+	dw_printf ("\n");
+        dw_printf ("    200 Hz shift, 300 baud, suitable for HF SSB transceiver.\n");
+	dw_printf ("\n");
+	dw_printf ("Example:  echo -n \"WB2OSZ>WORLD:Hello, world!\" | gen_packets -a 25 -o x.wav -\n");
+	dw_printf ("\n");
+        dw_printf ("    Read message from stdin and put quarter volume sound into the file x.wav.\n");
+
+	exit (EXIT_FAILURE);
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_file_open
+ *
+ * Purpose:     Open a .WAV format file for output.
+ *
+ * Inputs:      fname		- Name of .WAV file to create.
+ *
+ *		pa		- Address of structure of type audio_s.
+ *				
+ *				The fields that we care about are:
+ *					num_channels
+ *					samples_per_sec
+ *					bits_per_sample
+ *				If zero, reasonable defaults will be provided.
+ *         
+ * Returns:     0 for success, -1 for failure.
+ *		
+ *----------------------------------------------------------------*/
+
+struct wav_header {             /* .WAV file header. */
+        char riff[4];           /* "RIFF" */
+        int filesize;          /* file length - 8 */
+        char wave[4];           /* "WAVE" */
+        char fmt[4];            /* "fmt " */
+        int fmtsize;           /* 16. */
+        short wformattag;       /* 1 for PCM. */
+        short nchannels;        /* 1 for mono, 2 for stereo. */
+        int nsamplespersec;    /* sampling freq, Hz. */
+        int navgbytespersec;   /* = nblockalign * nsamplespersec. */
+        short nblockalign;      /* = wbitspersample / 8 * nchannels. */
+        short wbitspersample;   /* 16 or 8. */
+        char data[4];           /* "data" */
+        int datasize;          /* number of bytes following. */
+} ;
+
+				/* 8 bit samples are unsigned bytes */
+				/* in range of 0 .. 255. */
+ 
+				/* 16 bit samples are signed short */
+				/* in range of -32768 .. +32767. */
+
+static FILE *out_fp = NULL;
+
+static struct wav_header header;
+
+static int byte_count;			/* Number of data bytes written to file. */
+					/* Will be written to header when file is closed. */
+
+
+static int audio_file_open (char *fname, struct audio_s *pa)
+{
+	int n;
+
+/*
+ * Fill in defaults for any missing values.
+ */
+	if (pa -> adev[0].num_channels == 0)
+	  pa -> adev[0].num_channels = DEFAULT_NUM_CHANNELS;
+
+	if (pa -> adev[0].samples_per_sec == 0)
+	  pa -> adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
+
+	if (pa -> adev[0].bits_per_sample == 0)
+	  pa -> adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
+
+
+/*
+ * Write the file header.  Don't know length yet.
+ */
+        out_fp = fopen (fname, "wb");	
+	
+        if (out_fp == NULL) {
+           text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for write: %s\n", fname);
+	   perror ("");
+           return (-1);
+        }
+
+	memset (&header, 0, sizeof(header));
+
+        memcpy (header.riff, "RIFF", (size_t)4);
+        header.filesize = 0; 
+        memcpy (header.wave, "WAVE", (size_t)4);
+        memcpy (header.fmt, "fmt ", (size_t)4);
+        header.fmtsize = 16;			// Always 16.
+        header.wformattag = 1;     		// 1 for PCM.
+
+        header.nchannels = pa -> adev[0].num_channels;   		
+        header.nsamplespersec = pa -> adev[0].samples_per_sec;    
+        header.wbitspersample = pa -> adev[0].bits_per_sample;  
+		
+        header.nblockalign = header.wbitspersample / 8 * header.nchannels;     
+        header.navgbytespersec = header.nblockalign * header.nsamplespersec;   
+        memcpy (header.data, "data", (size_t)4);
+        header.datasize = 0;        
+
+	assert (header.nchannels == 1 || header.nchannels == 2);
+
+        n = fwrite (&header, sizeof(header), (size_t)1, out_fp);
+
+	if (n != 1) {
+          text_color_set(DW_COLOR_ERROR); 
+	  dw_printf ("Couldn't write header to: %s\n", fname);
+	  perror ("");
+	  fclose (out_fp);
+	  out_fp = NULL;
+          return (-1);
+        }
+
+
+/*
+ * Number of bytes written will be filled in later.
+ */
+        byte_count = 0;
+	
+	return (0);
+
+} /* end audio_open */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_put
+ *
+ * Purpose:     Send one byte to the audio output file.
+ *
+ * Inputs:	c	- One byte in range of 0 - 255.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ * Description:	The caller must deal with the details of mono/stereo
+ *		and number of bytes per sample.
+ *
+ *----------------------------------------------------------------*/
+
+#define MY_RAND_MAX 0x7fffffff
+
+static int seed = 1;
+
+static int my_rand (void) {
+	seed = ((seed * 1103515245) + 12345) & MY_RAND_MAX;
+	return (seed);
+}
+
+int audio_put (int a, int c)
+{
+	static short sample16;
+	int s;
+
+	if (g_add_noise) {
+
+	  if ((byte_count & 1) == 0) {
+	    sample16 = c & 0xff;		/* save lower byte. */
+	    byte_count++;
+	    return c;
+	  }
+	  else {
+	    float r;
+
+	    sample16 |= (c << 8) & 0xff00;	/* insert upper byte. */
+	    byte_count++;
+	    s = sample16;  // sign extend.
+
+/* Add random noise to the signal. */
+/* r should be in range of -1 .. +1. */
+
+/* Use own function instead of rand() from the C library. */
+/* Windows and Linux have different results, messing up my self test procedure. */
+/* No idea what Mac OSX and BSD might do. */
+ 
+
+	    r = (my_rand() - MY_RAND_MAX/2.0) / (MY_RAND_MAX/2.0);
+
+	    s += 5 * r * g_noise_level * 32767;
+
+	    if (s > 32767) s = 32767;
+	    if (s < -32767) s = -32767;
+
+	    putc(s & 0xff, out_fp);  
+	    return (putc((s >> 8) & 0xff, out_fp));
+	  }
+	}
+	else {
+	  byte_count++;
+	  return (putc(c, out_fp));
+	}
+
+} /* end audio_put */
+
+
+int audio_flush (int a)
+{
+	return 0;
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name:        audio_file_close
+ *
+ * Purpose:     Close the audio output file.
+ *
+ * Returns:     Normally non-negative.
+ *              -1 for any type of error.
+ *
+ *
+ * Description:	Must go back to beginning of file and fill in the
+ *		size of the data.
+ *
+ *----------------------------------------------------------------*/
+
+static int audio_file_close (void)
+{
+	int n;
+
+        //text_color_set(DW_COLOR_DEBUG); 
+	//dw_printf ("audio_close()\n");
+
+/*
+ * Go back and fix up lengths in header.
+ */
+        header.filesize = byte_count + sizeof(header) - 8;           
+        header.datasize = byte_count;        
+
+	if (out_fp == NULL) {
+	  return (-1);
+ 	}
+
+        fflush (out_fp);
+
+        fseek (out_fp, 0L, SEEK_SET);         
+        n = fwrite (&header, sizeof(header), (size_t)1, out_fp);
+
+	if (n != 1) {
+          text_color_set(DW_COLOR_ERROR); 
+	  dw_printf ("Couldn't write header to audio file.\n");
+	  perror ("");		// TODO: remove perror.
+	  fclose (out_fp);
+	  out_fp = NULL;
+          return (-1);
+        }
+
+        fclose (out_fp);
+        out_fp = NULL;
+
+	return (0);
+
+} /* end audio_close */
+
diff --git a/gen_tone.c b/gen_tone.c
index fe34472..63f36f9 100644
--- a/gen_tone.c
+++ b/gen_tone.c
@@ -1,486 +1,485 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      gen_tone.c
- *
- * Purpose:     Convert bits to AFSK for writing to .WAV sound file 
- *		or a sound device.
- *
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <math.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "audio.h"
-#include "gen_tone.h"
-#include "textcolor.h"
-
-#include "fsk_demod_state.h"	/* for MAX_FILTER_SIZE which might be overly generous for here. */
-				/* but safe if we use same size as for receive. */
-#include "dsp.h"
-
-
-// Properties of the digitized sound stream & modem.
-
-static struct audio_s *save_audio_config_p;
-
-/*
- * 8 bit samples are unsigned bytes in range of 0 .. 255.
- *
- * 16 bit samples are signed short in range of -32768 .. +32767.
- */
-
-
-/* Constants after initialization. */
-
-#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
-
-static int ticks_per_sample[MAX_CHANS];	/* Same for both channels of same soundcard */
-					/* because they have same sample rate */
-					/* but less confusing to have for each channel. */
-
-static int ticks_per_bit[MAX_CHANS];
-static int f1_change_per_sample[MAX_CHANS];
-static int f2_change_per_sample[MAX_CHANS];
-
-static short sine_table[256];
-
-
-/* Accumulators. */
-
-static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation.
-					    // Upper bits are used as index into sine table.
-
-static int bit_len_acc[MAX_CHANS];	// To accumulate fractional samples per bit.
-
-static int lfsr[MAX_CHANS];		// Shift register for scrambler.
-
-
-/*
- * The K9NG/G3RUH output originally took a very simple and lazy approach.
- * We simply generated a square wave with + or - the desired amplitude.
- * This has a couple undesirable properties.
- *
- *	- Transmitting a square wave would splatter into adjacent
- *	   channels of the transmitter doesn't limit the bandwidth.
- *
- *	- The usual sample rate of 44100 is not a multiple of the 
- *	   baud rate so jitter would be added to the zero crossings.
- *
- * Starting in version 1.2, we try to overcome these issues by using
- * a higher sample rate, low pass filtering, and down sampling.
- *
- * What sort of low pass filter would be appropriate?  Intuitively,
- * we would expect a cutoff frequency somewhere between baud/2 and baud.
- * The current values were found with a small amount of trial and 
- * error for best results.  Future improvement is certainly possible.
- */
-
-/* 
- * For low pass filtering of 9600 baud data. 
- */
-
-/* Add sample to buffer and shift the rest down. */
-// TODO:  Can we have one copy of these in dsp.h?
-
-static inline void push_sample (float val, float *buff, int size)
-{
-	memmove(buff+1,buff,(size-1)*sizeof(float));
-	buff[0] = val; 
-}
-
-
-/* FIR filter kernel. */
-
-static inline float convolve (const float *data, const float *filter, int filter_size)
-{
-	  float sum = 0;
-	  int j;
-
-	  for (j=0; j<filter_size; j++) {
-	    sum += filter[j] * data[j];
-	  }
-	  return (sum);
-}
-
-static int lp_filter_size[MAX_CHANS];
-static float raw[MAX_CHANS][MAX_FILTER_SIZE] __attribute__((aligned(16)));
-static float lp_filter[MAX_CHANS][MAX_FILTER_SIZE] __attribute__((aligned(16)));
-static int resample[MAX_CHANS];
-
-#define UPSAMPLE 2
-
-
-/*------------------------------------------------------------------
- *
- * Name:        gen_tone_init
- *
- * Purpose:     Initialize for AFSK tone generation which might
- *		be used for RTTY or amateur packet radio.
- *
- * Inputs:      audio_config_p		- Pointer to modem parameter structure, modem_s.
- *
- *				The fields we care about are:
- *
- *					samples_per_sec
- *					baud
- *					mark_freq
- *					space_freq
- *					samples_per_sec
- *
- *		amp		- Signal amplitude on scale of 0 .. 100.
- *
- * Returns:     0 for success.
- *              -1 for failure.
- *
- * Description:	 Calculate various constants for use by the direct digital synthesis
- * 		audio tone generation.
- *
- *----------------------------------------------------------------*/
-
-static int amp16bit;	/* for 9600 baud */
-
-
-int gen_tone_init (struct audio_s *audio_config_p, int amp)  
-{
-	int j;
-	int chan = 0;
-	
-/* 
- * Save away modem parameters for later use. 
- */
-
-	save_audio_config_p = audio_config_p;
-	
-
-	amp16bit = (32767 * amp) / 100;
-
-
-	for (chan = 0; chan < MAX_CHANS; chan++) {
-
-	  if (audio_config_p->achan[chan].valid) {
-
-	    int a = ACHAN2ADEV(chan);
-
-	    ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
-
-	    ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5);
-
-	    f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
-
-	    f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
-
-	    tone_phase[chan] = 0;
-				
-	    bit_len_acc[chan] = 0;
-
-	    lfsr[chan] = 0;
-	  }
-	}
-
-        for (j=0; j<256; j++) {
-	  double a;
-	  int s;
-
-	  a = ((double)(j) / 256.0) * (2 * M_PI);
-	  s = (int) (sin(a) * 32767 * amp / 100.0);
-
-	  /* 16 bit sound sample is in range of -32768 .. +32767. */
-	  
-	  assert (s >= -32768 && s <= 32767);
-	
-	  sine_table[j] = s;
-        }
-
-
-/*
- * Low pass filter for 9600 baud. 
- */
-
-	for (chan = 0; chan < MAX_CHANS; chan++) {
-
-	  if (audio_config_p->achan[chan].valid && 
-		(audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE 
-		  ||  audio_config_p->achan[chan].modem_type == MODEM_BASEBAND)) {
-
-	    int a = ACHAN2ADEV(chan);
-	    int samples_per_sec;		/* Might be scaled up! */
-	    int baud;
-
-	    /* These numbers were by trial and error.  Need more investigation here. */
-
-	    float filter_len_bits =  88 * 9600.0 / (44100.0 * 2.0);
-						/* Filter length in number of data bits. */	
-	
-	    float lpf_baud = 0.8;		/* Lowpass cutoff freq as fraction of baud rate */
-
-	    float fc;				/* Cutoff frequency as fraction of sampling frequency. */
-
-
-	    samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE;		
-	    baud = audio_config_p->achan[chan].baud;
-
-	    ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)samples_per_sec ) + 0.5);
-	    ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)baud ) + 0.5);
-
-	    lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5);
-
-	    if (lp_filter_size[chan] < 10 || lp_filter_size[chan] > MAX_FILTER_SIZE) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("gen_tone_init: INTERNAL ERROR, chan %d, lp_filter_size %d\n", chan, lp_filter_size[chan]);
-	      lp_filter_size[chan] = MAX_FILTER_SIZE / 2;
-	    }
-
-	    fc = (float)baud * lpf_baud / (float)samples_per_sec;
-
-	    //text_color_set(DW_COLOR_DEBUG);
-	    //dw_printf ("gen_tone_init: chan %d, call gen_lowpass(fc=%.2f, , size=%d, )\n", chan, fc, lp_filter_size[chan]);
-
-	    gen_lowpass (fc, lp_filter[chan], lp_filter_size[chan], BP_WINDOW_HAMMING);
-
-	  }
-	}
-
-	return (0);
-
- } /* end gen_tone_init */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        gen_tone_put_bit
- *
- * Purpose:     Generate tone of proper duration for one data bit.
- *
- * Inputs:      chan	- Audio channel, 0 = first.
- *
- *		dat	- 0 for f1, 1 for f2.
- *
- * 			  	-1 inserts half bit to test data	
- *				recovery PLL.
- *
- * Assumption:  fp is open to a file for write.
- *
- *--------------------------------------------------------------------*/
-
-static void put_sample (int chan, int a, int sam);
-
-void tone_gen_put_bit (int chan, int dat)
-{
-	int a = ACHAN2ADEV(chan);	/* device for channel. */
-
-
-	assert (save_audio_config_p->achan[chan].valid);
-
-
-        if (dat < 0) { 
-	  /* Hack to test receive PLL recovery. */
-	  bit_len_acc[chan] -= ticks_per_bit[chan]; 
-	  dat = 0; 
-	} 
-
-	if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) {
-	  int x;
-
-	  x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1;
-	  lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
-	  dat = x;
-	}
-	  
-	do {
-
-	  if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) {
-	    int sam;
-
-	    tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan];
-            sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
-	    put_sample (chan, a, sam);
-	  }
-  	  else {
-	    
-	    float fsam = dat ? amp16bit : (-amp16bit);
-
-	    /* version 1.2 - added a low pass filter instead of square wave out. */
-
-	    push_sample (fsam, raw[chan], lp_filter_size[chan]);
-	    
-	    resample[chan]++;
-	    if (resample[chan] >= UPSAMPLE) {
-	      int sam; 
-
-	      sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]);
-	      resample[chan] = 0;
-	      put_sample (chan, a, sam);
-	    }
-	  }
-
-	  /* Enough for the bit time? */
-
-	  bit_len_acc[chan] += ticks_per_sample[chan];
-
-        } while (bit_len_acc[chan] < ticks_per_bit[chan]);
-
-	bit_len_acc[chan] -= ticks_per_bit[chan];
-}
-
-
-static void put_sample (int chan, int a, int sam) {
-
-        /* Ship out an audio sample. */
-
-	assert (save_audio_config_p->adev[a].num_channels == 1 || save_audio_config_p->adev[a].num_channels == 2);
-
-	/* Generalize to allow 8 bits someday? */
-
-	assert (save_audio_config_p->adev[a].bits_per_sample == 16);
-
-	if (sam < -32767) sam = -32767;
-	else if (sam > 32767) sam = 32767;
-
-	if (save_audio_config_p->adev[a].num_channels == 1) {
-
-	  /* Mono */
-
-          audio_put (a, sam & 0xff);
-          audio_put (a, (sam >> 8) & 0xff);
- 	}
-	else {
-
-	  if (chan == ADEVFIRSTCHAN(a)) {
-	  
-	    /* Stereo, left channel. */
-
-            audio_put (a, sam & 0xff);
-            audio_put (a, (sam >> 8) & 0xff);
- 
-            audio_put (a, 0);		
-            audio_put (a, 0);
-	  }
-	  else { 
-
-	    /* Stereo, right channel. */
-	  
-            audio_put (a, 0);		
-            audio_put (a, 0);
-
-            audio_put (a, sam & 0xff);
-            audio_put (a, (sam >> 8) & 0xff);
-	  }
-	}
-}
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Quick test program for above.
- *
- * Description: Compile like this for unit test:
- *
- *		gcc -Wall -DMAIN -o gen_tone_test gen_tone.c audio.c textcolor.c
- *
- *		gcc -Wall -DMAIN -o gen_tone_test.exe gen_tone.c audio_win.c textcolor.c -lwinmm
- *
- *--------------------------------------------------------------------*/
-
-
-#if MAIN
-
-
-int main ()
-{
-	int n;
-	int chan1 = 0;
-	int chan2 = 1;
-	int r;
-	struct audio_s my_audio_config;
-
-
-/* to sound card */
-/* one channel.  2 times:  one second of each tone. */
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-	strcpy (my_audio_config.adev[0].adevice_in, DEFAULT_ADEVICE);
-	strcpy (my_audio_config.adev[0].adevice_out, DEFAULT_ADEVICE);
-
-	audio_open (&my_audio_config);
-	gen_tone_init (&my_audio_config, 100);
-
-	for (r=0; r<2; r++) {
-
-	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
- 	    tone_gen_put_bit ( chan1, 1 );
-	  }
-
-	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
- 	    tone_gen_put_bit ( chan1, 0 );
-	  }
-	}
-
-	audio_close();
-
-/* Now try stereo. */
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-	strcpy (my_audio_config.adev[0].adevice_in, DEFAULT_ADEVICE);
-	strcpy (my_audio_config.adev[0].adevice_out, DEFAULT_ADEVICE);
-	my_audio_config.adev[0].num_channels = 2;
-
-	audio_open (&my_audio_config);
-	gen_tone_init (&my_audio_config, 100);
-
-	for (r=0; r<4; r++) {
-
-	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
- 	    tone_gen_put_bit ( chan1, 1 );
-	  }
-
-	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
- 	    tone_gen_put_bit ( chan1, 0 );
-	  }
-
-	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
- 	    tone_gen_put_bit ( chan2, 1 );
-	  }
-
-	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
- 	    tone_gen_put_bit ( chan2, 0 );
-	  }
-	}
-
-	audio_close();
-
-	return(0);
-}
-
-#endif
-
-
-/* end gen_tone.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      gen_tone.c
+ *
+ * Purpose:     Convert bits to AFSK for writing to .WAV sound file 
+ *		or a sound device.
+ *
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "audio.h"
+#include "gen_tone.h"
+#include "textcolor.h"
+
+#include "fsk_demod_state.h"	/* for MAX_FILTER_SIZE which might be overly generous for here. */
+				/* but safe if we use same size as for receive. */
+#include "dsp.h"
+
+
+// Properties of the digitized sound stream & modem.
+
+static struct audio_s *save_audio_config_p;
+
+/*
+ * 8 bit samples are unsigned bytes in range of 0 .. 255.
+ *
+ * 16 bit samples are signed short in range of -32768 .. +32767.
+ */
+
+
+/* Constants after initialization. */
+
+#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
+
+static int ticks_per_sample[MAX_CHANS];	/* Same for both channels of same soundcard */
+					/* because they have same sample rate */
+					/* but less confusing to have for each channel. */
+
+static int ticks_per_bit[MAX_CHANS];
+static int f1_change_per_sample[MAX_CHANS];
+static int f2_change_per_sample[MAX_CHANS];
+
+static short sine_table[256];
+
+
+/* Accumulators. */
+
+static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation.
+					    // Upper bits are used as index into sine table.
+
+static int bit_len_acc[MAX_CHANS];	// To accumulate fractional samples per bit.
+
+static int lfsr[MAX_CHANS];		// Shift register for scrambler.
+
+
+/*
+ * The K9NG/G3RUH output originally took a very simple and lazy approach.
+ * We simply generated a square wave with + or - the desired amplitude.
+ * This has a couple undesirable properties.
+ *
+ *	- Transmitting a square wave would splatter into adjacent
+ *	   channels of the transmitter doesn't limit the bandwidth.
+ *
+ *	- The usual sample rate of 44100 is not a multiple of the 
+ *	   baud rate so jitter would be added to the zero crossings.
+ *
+ * Starting in version 1.2, we try to overcome these issues by using
+ * a higher sample rate, low pass filtering, and down sampling.
+ *
+ * What sort of low pass filter would be appropriate?  Intuitively,
+ * we would expect a cutoff frequency somewhere between baud/2 and baud.
+ * The current values were found with a small amount of trial and 
+ * error for best results.  Future improvement is certainly possible.
+ */
+
+/* 
+ * For low pass filtering of 9600 baud data. 
+ */
+
+/* Add sample to buffer and shift the rest down. */
+// TODO:  Can we have one copy of these in dsp.h?
+
+static inline void push_sample (float val, float *buff, int size)
+{
+	memmove(buff+1,buff,(size-1)*sizeof(float));
+	buff[0] = val; 
+}
+
+
+/* FIR filter kernel. */
+
+static inline float convolve (const float *data, const float *filter, int filter_size)
+{
+	  float sum = 0;
+	  int j;
+
+	  for (j=0; j<filter_size; j++) {
+	    sum += filter[j] * data[j];
+	  }
+	  return (sum);
+}
+
+static int lp_filter_size[MAX_CHANS];
+static float raw[MAX_CHANS][MAX_FILTER_SIZE] __attribute__((aligned(16)));
+static float lp_filter[MAX_CHANS][MAX_FILTER_SIZE] __attribute__((aligned(16)));
+static int resample[MAX_CHANS];
+
+#define UPSAMPLE 2
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        gen_tone_init
+ *
+ * Purpose:     Initialize for AFSK tone generation which might
+ *		be used for RTTY or amateur packet radio.
+ *
+ * Inputs:      audio_config_p		- Pointer to modem parameter structure, modem_s.
+ *
+ *				The fields we care about are:
+ *
+ *					samples_per_sec
+ *					baud
+ *					mark_freq
+ *					space_freq
+ *					samples_per_sec
+ *
+ *		amp		- Signal amplitude on scale of 0 .. 100.
+ *
+ * Returns:     0 for success.
+ *              -1 for failure.
+ *
+ * Description:	 Calculate various constants for use by the direct digital synthesis
+ * 		audio tone generation.
+ *
+ *----------------------------------------------------------------*/
+
+static int amp16bit;	/* for 9600 baud */
+
+
+int gen_tone_init (struct audio_s *audio_config_p, int amp)  
+{
+	int j;
+	int chan = 0;
+	
+/* 
+ * Save away modem parameters for later use. 
+ */
+
+	save_audio_config_p = audio_config_p;
+	
+
+	amp16bit = (32767 * amp) / 100;
+
+
+	for (chan = 0; chan < MAX_CHANS; chan++) {
+
+	  if (audio_config_p->achan[chan].valid) {
+
+	    int a = ACHAN2ADEV(chan);
+
+	    ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
+
+	    ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5);
+
+	    f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
+
+	    f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
+
+	    tone_phase[chan] = 0;
+				
+	    bit_len_acc[chan] = 0;
+
+	    lfsr[chan] = 0;
+	  }
+	}
+
+        for (j=0; j<256; j++) {
+	  double a;
+	  int s;
+
+	  a = ((double)(j) / 256.0) * (2 * M_PI);
+	  s = (int) (sin(a) * 32767 * amp / 100.0);
+
+	  /* 16 bit sound sample is in range of -32768 .. +32767. */
+	  
+	  assert (s >= -32768 && s <= 32767);
+	
+	  sine_table[j] = s;
+        }
+
+
+/*
+ * Low pass filter for 9600 baud. 
+ */
+
+	for (chan = 0; chan < MAX_CHANS; chan++) {
+
+	  if (audio_config_p->achan[chan].valid && 
+		(audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE 
+		  ||  audio_config_p->achan[chan].modem_type == MODEM_BASEBAND)) {
+
+	    int a = ACHAN2ADEV(chan);
+	    int samples_per_sec;		/* Might be scaled up! */
+	    int baud;
+
+	    /* These numbers were by trial and error.  Need more investigation here. */
+
+	    float filter_len_bits =  88 * 9600.0 / (44100.0 * 2.0);
+						/* Filter length in number of data bits. */	
+	
+	    float lpf_baud = 0.8;		/* Lowpass cutoff freq as fraction of baud rate */
+
+	    float fc;				/* Cutoff frequency as fraction of sampling frequency. */
+
+
+	    samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE;		
+	    baud = audio_config_p->achan[chan].baud;
+
+	    ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)samples_per_sec ) + 0.5);
+	    ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)baud ) + 0.5);
+
+	    lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5);
+
+	    if (lp_filter_size[chan] < 10 || lp_filter_size[chan] > MAX_FILTER_SIZE) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("gen_tone_init: INTERNAL ERROR, chan %d, lp_filter_size %d\n", chan, lp_filter_size[chan]);
+	      lp_filter_size[chan] = MAX_FILTER_SIZE / 2;
+	    }
+
+	    fc = (float)baud * lpf_baud / (float)samples_per_sec;
+
+	    //text_color_set(DW_COLOR_DEBUG);
+	    //dw_printf ("gen_tone_init: chan %d, call gen_lowpass(fc=%.2f, , size=%d, )\n", chan, fc, lp_filter_size[chan]);
+
+	    gen_lowpass (fc, lp_filter[chan], lp_filter_size[chan], BP_WINDOW_HAMMING);
+
+	  }
+	}
+
+	return (0);
+
+ } /* end gen_tone_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        gen_tone_put_bit
+ *
+ * Purpose:     Generate tone of proper duration for one data bit.
+ *
+ * Inputs:      chan	- Audio channel, 0 = first.
+ *
+ *		dat	- 0 for f1, 1 for f2.
+ *
+ * 			  	-1 inserts half bit to test data	
+ *				recovery PLL.
+ *
+ * Assumption:  fp is open to a file for write.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void tone_gen_put_bit (int chan, int dat)
+{
+	int a = ACHAN2ADEV(chan);	/* device for channel. */
+
+
+	assert (save_audio_config_p->achan[chan].valid);
+
+
+        if (dat < 0) { 
+	  /* Hack to test receive PLL recovery. */
+	  bit_len_acc[chan] -= ticks_per_bit[chan]; 
+	  dat = 0; 
+	} 
+
+	if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) {
+	  int x;
+
+	  x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1;
+	  lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
+	  dat = x;
+	}
+	  
+	do {
+
+	  if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) {
+	    int sam;
+
+	    tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan];
+            sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
+	    gen_tone_put_sample (chan, a, sam);
+	  }
+  	  else {
+	    
+	    float fsam = dat ? amp16bit : (-amp16bit);
+
+	    /* version 1.2 - added a low pass filter instead of square wave out. */
+
+	    push_sample (fsam, raw[chan], lp_filter_size[chan]);
+	    
+	    resample[chan]++;
+	    if (resample[chan] >= UPSAMPLE) {
+	      int sam; 
+
+	      sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]);
+	      resample[chan] = 0;
+	      gen_tone_put_sample (chan, a, sam);
+	    }
+	  }
+
+	  /* Enough for the bit time? */
+
+	  bit_len_acc[chan] += ticks_per_sample[chan];
+
+        } while (bit_len_acc[chan] < ticks_per_bit[chan]);
+
+	bit_len_acc[chan] -= ticks_per_bit[chan];
+}
+
+
+void gen_tone_put_sample (int chan, int a, int sam) {
+
+        /* Ship out an audio sample. */
+
+	assert (save_audio_config_p->adev[a].num_channels == 1 || save_audio_config_p->adev[a].num_channels == 2);
+
+	/* Generalize to allow 8 bits someday? */
+
+	assert (save_audio_config_p->adev[a].bits_per_sample == 16);
+
+	if (sam < -32767) sam = -32767;
+	else if (sam > 32767) sam = 32767;
+
+	if (save_audio_config_p->adev[a].num_channels == 1) {
+
+	  /* Mono */
+
+          audio_put (a, sam & 0xff);
+          audio_put (a, (sam >> 8) & 0xff);
+ 	}
+	else {
+
+	  if (chan == ADEVFIRSTCHAN(a)) {
+	  
+	    /* Stereo, left channel. */
+
+            audio_put (a, sam & 0xff);
+            audio_put (a, (sam >> 8) & 0xff);
+ 
+            audio_put (a, 0);		
+            audio_put (a, 0);
+	  }
+	  else { 
+
+	    /* Stereo, right channel. */
+	  
+            audio_put (a, 0);		
+            audio_put (a, 0);
+
+            audio_put (a, sam & 0xff);
+            audio_put (a, (sam >> 8) & 0xff);
+	  }
+	}
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Quick test program for above.
+ *
+ * Description: Compile like this for unit test:
+ *
+ *		gcc -Wall -DMAIN -o gen_tone_test gen_tone.c audio.c textcolor.c
+ *
+ *		gcc -Wall -DMAIN -o gen_tone_test.exe gen_tone.c audio_win.c textcolor.c -lwinmm
+ *
+ *--------------------------------------------------------------------*/
+
+
+#if MAIN
+
+
+int main ()
+{
+	int n;
+	int chan1 = 0;
+	int chan2 = 1;
+	int r;
+	struct audio_s my_audio_config;
+
+
+/* to sound card */
+/* one channel.  2 times:  one second of each tone. */
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+	strlcpy (my_audio_config.adev[0].adevice_in, DEFAULT_ADEVICE, sizeof(my_audio_config.adev[0].adevice_in));
+	strlcpy (my_audio_config.adev[0].adevice_out, DEFAULT_ADEVICE, sizeof(my_audio_config.adev[0].adevice_out));
+
+	audio_open (&my_audio_config);
+	gen_tone_init (&my_audio_config, 100);
+
+	for (r=0; r<2; r++) {
+
+	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
+ 	    tone_gen_put_bit ( chan1, 1 );
+	  }
+
+	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
+ 	    tone_gen_put_bit ( chan1, 0 );
+	  }
+	}
+
+	audio_close();
+
+/* Now try stereo. */
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+	strlcpy (my_audio_config.adev[0].adevice_in, DEFAULT_ADEVICE, sizeof(my_audio_config.adev[0].adevice_in));
+	strlcpy (my_audio_config.adev[0].adevice_out, DEFAULT_ADEVICE, , sizeof(my_audio_config.adev[0].adevice_out));
+	my_audio_config.adev[0].num_channels = 2;
+
+	audio_open (&my_audio_config);
+	gen_tone_init (&my_audio_config, 100);
+
+	for (r=0; r<4; r++) {
+
+	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
+ 	    tone_gen_put_bit ( chan1, 1 );
+	  }
+
+	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
+ 	    tone_gen_put_bit ( chan1, 0 );
+	  }
+
+	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
+ 	    tone_gen_put_bit ( chan2, 1 );
+	  }
+
+	  for (n=0; n<my_audio_config.baud[0] * 2 ; n++) {
+ 	    tone_gen_put_bit ( chan2, 0 );
+	  }
+	}
+
+	audio_close();
+
+	return(0);
+}
+
+#endif
+
+
+/* end gen_tone.c */
diff --git a/gen_tone.h b/gen_tone.h
index b4b3f30..1937c4a 100644
--- a/gen_tone.h
+++ b/gen_tone.h
@@ -1,16 +1,17 @@
-/*
- * gen_tone.h
- */
-
-
-int gen_tone_init (struct audio_s *pp, int amp);
-
-
-//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);
-
-//int gen_tone_open_fd (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, int fd)  ;
-
-//int gen_tone_close (void);
-
-void tone_gen_put_bit (int chan, int dat);
-
+/*
+ * gen_tone.h
+ */
+
+
+int gen_tone_init (struct audio_s *pp, int amp);
+
+
+//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);
+
+//int gen_tone_open_fd (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, int fd)  ;
+
+//int gen_tone_close (void);
+
+void tone_gen_put_bit (int chan, int dat);
+
+void gen_tone_put_sample (int chan, int a, int sam);
\ No newline at end of file
diff --git a/direwolf.txt b/generic.conf
similarity index 91%
copy from direwolf.txt
copy to generic.conf
index c9f3313..223007f 100644
--- a/direwolf.txt
+++ b/generic.conf
@@ -1,532 +1,573 @@
-C#############################################################
-C#                                                           #
-C#               Configuration file for Dire Wolf            #
-C#                                                           #
-L#                   Linux version                           #
-W#                   Windows version                         #
-C#                                                           #
-C#############################################################
-R
-R
-R	The sample config file was getting pretty messy
-R	with the Windows and Linux differences.
-R	It would be a maintenance burden to keep most of
-R	two different versions in sync.
-R	This common source is now used to generate the 
-R	two different variations while having only a single
-R	copy of the common parts.
-R
-R	The first column contains one of the following:
-R
-R		R	remark which is discarded.
-R		C	common to both versions.
-R		W	Windows version only.
-R		L	Linux version only.
-R
-C#
-C# Consult the User Guide for more details on configuration options.
-C#
-C#
-C# These are the most likely settings you might change:
-C#
-C#	(1)   	MYCALL 	-  call sign and SSID for your station.
-C#
-C#			Look for lines starting with MYCALL and 
-C#			change NOCALL to your own.
-C#
-C#	(2)	PBEACON	-  enable position beaconing.
-C#
-C#			Look for lines starting with PBEACON and 
-C#			modify for your call, location, etc.
-C#
-C#	(3)	DIGIPEATER  -  configure digipeating rules.
-C#
-C#			Look for lines starting with DIGIPEATER.
-C#			Most people will probably use the given example.
-C#			Just remove the "#" from the start of the line
-C#			to enable it.
-C#
-C#	(4)	IGSERVER, IGLOGIN  - IGate server and login
-C#
-C#			Configure an IGate client to relay messages between 
-C#			radio and internet servers.
-C#
-C#
-C# The default location is "direwolf.conf" in the current working directory.
-L# On Linux, the user's home directory will also be searched.
-C# An alternate configuration file location can be specified with the "-c" command line option.  
-C#
-C# As you probably guessed by now, # indicates a comment line.
-C#
-C# Remove the # at the beginning of a line if you want to use a sample
-C# configuration that is currently commented out.
-C#
-C# Commands are a keyword followed by parameters.
-C#
-C# Command key words are case insensitive.  i.e. upper and lower case are equivalent.
-C#
-C# Command parameters are generally case sensitive.  i.e. upper and lower case are different.
-C#
-C
-C
-C#############################################################
-C#                                                           #
-C#               FIRST AUDIO DEVICE PROPERTIES               #
-C#               (Channel 0 + 1 if in stereo)                #
-C#                                                           #
-C#############################################################
-C
-C#
-C# Many people will simply use the default sound device.
-C# Some might want to use an alternative device by chosing it here.
-C#
-W# When the Windows version starts up, it displays something like 
-W# this with the available sound devices and capabilities:
-W#
-W#	Available audio input devices for receive (*=selected):
-W#	   *  0: Microphone (C-Media USB Headpho   (channel 2)
-W#	      1: Microphone (Bluetooth SCO Audio
-W#	      2: Microphone (Bluetooth AV Audio)
-W#	 *    3: Microphone (Realtek High Defini   (channels 0 & 1)
-W#	Available audio output devices for transmit (*=selected):
-W#	   *  0: Speakers (C-Media USB Headphone   (channel 2)
-W#	      1: Speakers (Bluetooth SCO Audio)
-W#	      2: Realtek Digital Output(Optical)
-W#	      3: Speakers (Bluetooth AV Audio)
-W#	 *    4: Speakers (Realtek High Definiti   (channels 0 & 1)
-W#	      5: Realtek Digital Output (Realtek
-W#	
-W# Example: To use the microphone and speaker connections on the 
-W# system board, either of these forms can be used:
-W
-W#ADEVICE High
-W#ADEVICE  3 4 
-W
-W
-W# Example: To use the USB Audio, use a command like this with
-W# the input and output device numbers.  (Remove the # comment character.)
-W#ADEVICE USB
-W
-W# The position in the list can change when devices (e.g. USB) are added and removed.
-W# You can also specify devices by using part of the name.
-W# Here is an example of specifying the USB Audio device.
-W# This is case-sensitive.  Upper and lower case are not treated the same.
-W
-W#ADEVICE USB
-W
-W
-L# Linux ALSA is complicated.  See User Guide for discussion.
-L# To use something other than the default, generally use plughw
-L# and a card number reported by "arecord -l" command.  Example:
-L
-L# ADEVICE  plughw:1,0
-L
-L# Starting with version 1.0, you can also use "-" or "stdin" to 
-L# pipe stdout from some other application such as a software defined
-L# radio.  You can also specify "UDP:" and an optional port for input.
-L# Something different must be specified for output.
-L
-W# ADEVICE - 0
-W# ADEVICE UDP:7355 0
-L# ADEVICE - plughw:1,0
-L# ADEVICE UDP:7355 default
-L
-L
-C
-C#
-C# Number of audio channels for this souncard:  1 or 2.
-C#
-C
-CACHANNELS 1
-C#ACHANNELS 2
-C
-C
-C#############################################################
-C#                                                           #
-C#               SECOND AUDIO DEVICE PROPERTIES              #
-C#               (Channel 2 + 3 if in stereo)                #
-C#                                                           #
-C#############################################################
-C
-C#ADEVICE1  ...
-C
-C
-C#############################################################
-C#                                                           #
-C#               THIRD AUDIO DEVICE PROPERTIES               #
-C#               (Channel 4 + 5 if in stereo)                #
-C#                                                           #
-C#############################################################
-C
-C#ADEVICE2  ...
-C
-C
-C#############################################################
-C#                                                           #
-C#               CHANNEL 0 PROPERTIES                        #
-C#                                                           #
-C#############################################################
-C
-CCHANNEL 0
-C
-C#
-C# The following MYCALL, MODEM, PTT, etc. configuration items
-C# apply to the most recent CHANNEL.
-C#
-C
-C#
-C# Station identifier for this channel.
-C# Multiple channels can have the same or different names.
-C#
-C# It can be up to 6 letters and digits with an optional ssid.
-C# The APRS specification requires that it be upper case.
-C#
-C# Example (don't use this unless you are me):  MYCALL	WB2OSZ-5
-C#
-C
-CMYCALL N0CALL
-C
-C#
-C# Pick a suitable modem speed based on your situation.
-C#	1200 	Most common for VHF/UHF.  Default if not specified.
-C#	300	Low speed for HF SSB.
-C#	9600	High speed - Can't use Microphone and Speaker connections.
-C#
-C# In the simplest form, just specify the speed. 
-C# 
-C
-CMODEM 1200
-C#MODEM 300
-C#MODEM 9600
-C
-C#
-C# These are the defaults should be fine for most cases.  In special situations, 
-C# you might want to specify different AFSK tones or the baseband mode which does
-C# not use AFSK.
-C#
-C#MODEM 1200 1200:2200
-C#MODEM 300  1600:1800
-C#MODEM 9600 0:0
-C#
-C#
-C# On HF SSB, you might want to use multiple demodulators on slightly different
-C# frequencies to compensate for stations off frequency.  Here we have 7 different
-C# demodulators at 30 Hz intervals.  This takes a lot of CPU power so you will 
-C# probably need to reduce the audio sampling rate with the /n option.
-C
-C#MODEM 300 1600:1800 7 at 30 /4
-C
-C
-C#
-C# Uncomment line below to enable the DTMF decoder for this channel.
-C#
-C
-C#DTMF
-C
-C# 
-C# If not using a VOX circuit, the transmitter Push to Talk (PTT) 
-C# control is usually wired to a serial port with a suitable interface circuit.  
-C# DON'T connect it directly!
-C#
-C# For the PTT command, specify the device and either RTS or DTR.
-C# RTS or DTR may be preceded by "-" to invert the signal.
-C# Both can be used for interfaces that want them driven with opposite polarity.
-C#
-L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on.
-L#
-C
-C#PTT COM1 RTS
-C#PTT COM1 RTS -DTR
-L#PTT /dev/ttyUSB0 RTS
-C
-L#
-L# On Linux, you can also use general purpose I/O pins if
-L# your system is configured for user access to them. 
-L# This would apply mostly to microprocessor boards, not a regular PC.
-L# See separate Raspberry Pi document for more details.
-L# The number may be preceded by "-" to invert the signal.
-L#
-L
-L#PTT GPIO 25
-L
-C# The Data Carrier Detect (DCD) signal can be sent to the same places
-C# as the PTT signal.  This could be used to light up an LED like a normal TNC.
-C
-C#DCD COM1 -DTR
-L#DCD GPIO 24
-C
-C
-C#############################################################
-C#                                                           #
-C#               CHANNEL 1 PROPERTIES                        #
-C#                                                           #
-C#############################################################
-C
-C#CHANNEL 1
-C
-C#
-C# Specify MYCALL, MODEM, PTT, etc. configuration items for 
-C# CHANNEL 1.   Repeat for any other channels.
-C
-C
-C#############################################################
-C#                                                           #
-C#               TEXT TO SPEECH COMMAND FILE                 #
-C#                                                           #
-C#############################################################
-C
-W#SPEECH dwespeak.bat
-L#SPEECH dwespeak.sh
-C
-C
-C#############################################################
-C#                                                           #
-C#               VIRTUAL TNC SERVER PROPERTIES               #
-C#                                                           #
-C#############################################################
-C
-C#
-C# Dire Wolf acts as a virtual TNC and can communicate with
-C# client applications by different protocols:
-C#
-C#	- the "AGW TCPIP Socket Interface" - default port 8000
-C#	- KISS protocol over TCP socket - default port 8001
-W#	- KISS TNC via serial port
-L#	- KISS TNC via pseudo terminal   (-p command line option)
-C#
-C
-CAGWPORT 8000
-CKISSPORT 8001
-C
-W#
-W# Some applications are designed to operate with only a physical
-W# TNC attached to a serial port.  For these, we provide a virtual serial
-W# port that appears to be connected to a TNC.
-W#
-W# Take a look at the User Guide for instructions to set up
-W# two virtual serial ports named COM3 and COM4 connected by
-W# a null modem.
-W#
-W# Using the  configuration described, Dire Wolf will connect to 
-W# COM3 and the client application will use COM4.
-W#
-W# Uncomment following line to use this feature.
-W
-W#NULLMODEM COM3
-W
-W
-C#
-C# It is sometimes possible to recover frames with a bad FCS.
-C# This applies to all channels.  
-C#
-C#	0  [NONE] - Don't try to repair.
-C#	1  [SINGLE] - Attempt to fix single bit error.  (default)
-C#	2  [DOUBLE] - Also attempt to fix two adjacent bits.
-C#	... see User Guide for more values and in-depth discussion.
-C#
-C
-C#FIX_BITS 0
-C
-C#	
-C#############################################################
-C#                                                           #
-C#               BEACONING PROPERTIES                        #
-C#                                                           #
-C#############################################################
-C
-C
-C#
-C# Beaconing is configured with these two commands:
-C#
-C#	PBEACON		- for a position report (usually yourself)
-C#	OBEACON		- for an object report (usually some other entity)
-C#
-C# Each has a series of keywords and values for options.  
-C# See User Guide for details.
-C#
-C# Example:
-C#
-C# This results in a broadcast once every 10 minutes.
-C# Every half hour, it can travel via two digipeater hops.
-C# The others are kept local.
-C#
-C
-C#PBEACON delay=1  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 
-C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
-C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
-C
-C
-C# With UTM coordinates instead of latitude and longitude.
-C
-C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 
-C
-C
-C#
-C# When the destination field is set to "SPEECH" the information part is
-C# converted to speech rather than transmitted as a data frame.
-C#
-C
-C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm."
-C
-C
-C#
-C# Modify for your particular situation before removing 
-C# the # comment character from the beginning of appropriate lines above.
-C# 
-C
-C
-C#############################################################
-C#                                                           #
-C#               DIGIPEATER PROPERTIES                       #
-C#                                                           #
-C#############################################################
-C
-C#
-C# For most common situations, use something like this by removing
-C# the "#" from the beginning of the line below.  
-C#
-C
-C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE 
-C
-C# See User Guide for more explanation of what this means and how
-C# it can be customized for your particular needs.
-C 
-C# Filtering can be used to limit was is digipeated.
-C# For example, only weather weather reports, received on channel 0,
-C# will be retransmitted on channel 1.
-C#
-C
-C#FILTER 0 1 t/wn 
-C
-C
-C#############################################################
-C#                                                           #
-C#               INTERNET GATEWAY                            #
-C#                                                           #
-C#############################################################
-C
-C# First you need to specify the name of a Tier 2 server.  
-C# The current preferred way is to use one of these regional rotate addresses:
-C
-C#	noam.aprs2.net 		- for North America
-C#	soam.aprs2.net		- for South America
-C#	euro.aprs2.net		- for Europe and Africa
-C#	asia.aprs2.net 		- for Asia
-C#	aunz.aprs2.net		- for Oceania 
-C
-C#IGSERVER noam.aprs2.net
-C
-C# You also need to specify your login name and passcode. 
-C# Contact the author if you can't figure out how to generate the passcode.
-C 
-C#IGLOGIN WB2OSZ-5 123456
-C
-C# That's all you need for a receive only IGate which relays
-C# messages from the local radio channel to the global servers.
-C
-C# Some might want to send an IGate client position directly to a server
-C# without sending it over the air and relying on someone else to 
-C# forward it to an IGate server.  This is done by using sendto=IG rather
-C# than a radio channel number. Overlay R for receive only, T for two way.
-C
-C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W 
-C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W 
-C
-C
-C# To relay messages from the Internet to radio, you need to add
-C# one more option with the transmit channel number and a VIA path.
-C
-C#IGTXVIA 0 WIDE1-1
-C
-C# You might want to apply a filter for what packets will be obtained from the server.
-C# Read about filters here:  http://www.aprs-is.net/javaprsfilter.aspx
-C# Example, positions and objects within 50 km of my location:
-C
-C#IGFILTER m/50 
-C
-C# That is known as a server-side filter.  It is processed by the IGate server.
-C# You can also apply local filtering to limit what will be transmitted on the 
-C# RF side.  For example, transmit only "messages" on channel 0 and weather 
-C# reports on channel 1. 
-C
-C#FILTER IG 0 t/m
-C#FILTER IG 1 t/wn
-C
-C# Finally, we don't want to flood the radio channel.  
-C# The IGate function will limit the number of packets transmitted 
-C# during 1 minute and 5 minute intervals.   If a limit would 
-C# be exceeded, the packet is dropped and message is displayed in red.
-C
-CIGTXLIMIT 6 10
-C
-C
-C#############################################################
-C#                                                           #
-C#               APRStt GATEWAY                              #
-C#                                                           #
-C#############################################################
-C
-C#
-C# Dire Wolf can receive DTMF (commonly known as Touch Tone)
-C# messages and convert them to packet objects.
-C#
-C# See separate "APRStt-Implementation-Notes" document for details.
-C#
-C
-C#
-C# Sample gateway configuration based on:
-C#
-C#	http://www.aprs.org/aprstt/aprstt-coding24.txt
-C#	http://www.aprs.org/aprs-jamboree-2013.html
-C#
-C
-C# Define specific points.
-C
-CTTPOINT  B01  37^55.37N  81^7.86W  			
-CTTPOINT  B7495088  42.605237  -71.34456		
-CTTPOINT  B934  42.605237  -71.34456			
-C
-CTTPOINT B901  42.661279  -71.364452 
-CTTPOINT B902  42.660411  -71.364419 
-CTTPOINT B903  42.659046  -71.364452 
-CTTPOINT B904  42.657578  -71.364602 
-C
-C
-C# For location at given bearing and distance from starting point.
-C
-CTTVECTOR  B5bbbddd  37^55.37N  81^7.86W  0.01  mi
-C
-C# For location specified by x, y coordinates.
-C
-CTTGRID   Byyyxxx    37^50.00N  81^00.00W  37^59.99N  81^09.99W   
-C
-C# UTM location for Lowell-Dracut-Tyngsborough State Forest.
-C
-CTTUTM  B6xxxyyy  19T  10  300000  4720000
-C
-C
-C
-C# Location for the corral.
-C
-CTTCORRAL   37^55.50N  81^7.00W  0^0.02N
-C
-C# Compact messages - Fixed locations xx and object yyy where 
-C#   	Object numbers 100 - 199	= bicycle	
-C#	Object numbers 200 - 299	= fire truck
-C#	Others				= dog
-C
-CTTMACRO  xx1yy  B9xx*AB166*AA2B4C5B3B0A1yy
-CTTMACRO  xx2yy  B9xx*AB170*AA3C4C7C3B0A2yy
-CTTMACRO  xxyyy  B9xx*AB180*AA3A6C4A0Ayyy
-C
-CTTMACRO  z  Cz
-C
-C# Receive on channel 0, Transmit object reports on channel 1 with optional via path.
-C
-C#TTOBJ 0 1 WIDE1-1
-C
-C# Advertise gateway position with beacon.
-C
-C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway"  
-C
-C
+C#############################################################
+C#                                                           #
+C#               Configuration file for Dire Wolf            #
+C#                                                           #
+L#                   Linux version                           #
+W#                   Windows version                         #
+M#                   Macintosh version                       #
+C#                                                           #
+C#############################################################
+R
+R
+R	The sample config file was getting pretty messy
+R	with the Windows and Linux differences.
+R	It would be a maintenance burden to keep most of
+R	two different versions in sync.
+R	This common source is now used to generate the 
+R	two different variations while having only a single
+R	copy of the common parts.
+R
+R	The first column contains one of the following:
+R
+R		R	remark which is discarded.
+R		C	common to both versions.
+R		W	Windows version only.
+R		L	Linux version only.
+R		M	Macintosh version and possibly others (portaudio used).
+R
+C#
+C# Consult the User Guide for more details on configuration options.
+C#
+C#
+C# These are the most likely settings you might change:
+C#
+C#	(1)   	MYCALL 	-  call sign and SSID for your station.
+C#
+C#			Look for lines starting with MYCALL and 
+C#			change NOCALL to your own.
+C#
+C#	(2)	PBEACON	-  enable position beaconing.
+C#
+C#			Look for lines starting with PBEACON and 
+C#			modify for your call, location, etc.
+C#
+C#	(3)	DIGIPEATER  -  configure digipeating rules.
+C#
+C#			Look for lines starting with DIGIPEATER.
+C#			Most people will probably use the given example.
+C#			Just remove the "#" from the start of the line
+C#			to enable it.
+C#
+C#	(4)	IGSERVER, IGLOGIN  - IGate server and login
+C#
+C#			Configure an IGate client to relay messages between 
+C#			radio and internet servers.
+C#
+C#
+C# The default location is "direwolf.conf" in the current working directory.
+L# On Linux, the user's home directory will also be searched.
+C# An alternate configuration file location can be specified with the "-c" command line option.  
+C#
+C# As you probably guessed by now, # indicates a comment line.
+C#
+C# Remove the # at the beginning of a line if you want to use a sample
+C# configuration that is currently commented out.
+C#
+C# Commands are a keyword followed by parameters.
+C#
+C# Command key words are case insensitive.  i.e. upper and lower case are equivalent.
+C#
+C# Command parameters are generally case sensitive.  i.e. upper and lower case are different.
+C#
+C
+C
+C#############################################################
+C#                                                           #
+C#               FIRST AUDIO DEVICE PROPERTIES               #
+C#               (Channel 0 + 1 if in stereo)                #
+C#                                                           #
+C#############################################################
+C
+C#
+C# Many people will simply use the default sound device.
+C# Some might want to use an alternative device by chosing it here.
+C#
+W# When the Windows version starts up, it displays something like 
+W# this with the available sound devices and capabilities:
+W#
+W#	Available audio input devices for receive (*=selected):
+W#	   *  0: Microphone (C-Media USB Headpho   (channel 2)
+W#	      1: Microphone (Bluetooth SCO Audio
+W#	      2: Microphone (Bluetooth AV Audio)
+W#	 *    3: Microphone (Realtek High Defini   (channels 0 & 1)
+W#	Available audio output devices for transmit (*=selected):
+W#	   *  0: Speakers (C-Media USB Headphone   (channel 2)
+W#	      1: Speakers (Bluetooth SCO Audio)
+W#	      2: Realtek Digital Output(Optical)
+W#	      3: Speakers (Bluetooth AV Audio)
+W#	 *    4: Speakers (Realtek High Definiti   (channels 0 & 1)
+W#	      5: Realtek Digital Output (Realtek
+W#	
+W# Example: To use the microphone and speaker connections on the 
+W# system board, either of these forms can be used:
+W
+W#ADEVICE High
+W#ADEVICE  3 4 
+W
+W
+W# Example: To use the USB Audio, use a command like this with
+W# the input and output device numbers.  (Remove the # comment character.)
+W#ADEVICE USB
+W
+W# The position in the list can change when devices (e.g. USB) are added and removed.
+W# You can also specify devices by using part of the name.
+W# Here is an example of specifying the USB Audio device.
+W# This is case-sensitive.  Upper and lower case are not treated the same.
+W
+W#ADEVICE USB
+W
+W
+L# Linux ALSA is complicated.  See User Guide for discussion.
+L# To use something other than the default, generally use plughw
+L# and a card number reported by "arecord -l" command.  Example:
+L
+L# ADEVICE  plughw:1,0
+L
+L# Starting with version 1.0, you can also use "-" or "stdin" to 
+L# pipe stdout from some other application such as a software defined
+L# radio.  You can also specify "UDP:" and an optional port for input.
+L# Something different must be specified for output.
+L
+M# Macintosh Operating System uses portaudio driver for audio
+M# input/output. Default device selection not available. User/OP
+M# must configure the sound input/output option.  Note that
+M# the device names can contain spaces.  In this case, the names
+M# must be enclosed by quotes.
+M#
+M# Examples:
+M#
+M# ADEVICE  "USB Audio Codec:6"  "USB Audio Codec:5"
+M#
+M#
+W# ADEVICE - 0
+W# ADEVICE UDP:7355 0
+L# ADEVICE - plughw:1,0
+L# ADEVICE UDP:7355 default
+M# ADEVICE UDP:7355 default
+M#
+L
+L
+C
+C#
+C# Number of audio channels for this souncard:  1 or 2.
+C#
+C
+CACHANNELS 1
+C#ACHANNELS 2
+C
+C
+C#############################################################
+C#                                                           #
+C#               SECOND AUDIO DEVICE PROPERTIES              #
+C#               (Channel 2 + 3 if in stereo)                #
+C#                                                           #
+C#############################################################
+C
+C#ADEVICE1  ...
+C
+C
+C#############################################################
+C#                                                           #
+C#               THIRD AUDIO DEVICE PROPERTIES               #
+C#               (Channel 4 + 5 if in stereo)                #
+C#                                                           #
+C#############################################################
+C
+C#ADEVICE2  ...
+C
+C
+C#############################################################
+C#                                                           #
+C#               CHANNEL 0 PROPERTIES                        #
+C#                                                           #
+C#############################################################
+C
+CCHANNEL 0
+C
+C#
+C# The following MYCALL, MODEM, PTT, etc. configuration items
+C# apply to the most recent CHANNEL.
+C#
+C
+C#
+C# Station identifier for this channel.
+C# Multiple channels can have the same or different names.
+C#
+C# It can be up to 6 letters and digits with an optional ssid.
+C# The APRS specification requires that it be upper case.
+C#
+C# Example (don't use this unless you are me):  MYCALL	WB2OSZ-5
+C#
+C
+CMYCALL N0CALL
+C
+C#
+C# Pick a suitable modem speed based on your situation.
+C#	1200 	Most common for VHF/UHF.  Default if not specified.
+C#	300	Low speed for HF SSB.
+C#	9600	High speed - Can't use Microphone and Speaker connections.
+C#
+C# In the simplest form, just specify the speed. 
+C# 
+C
+CMODEM 1200
+C#MODEM 300
+C#MODEM 9600
+C
+C#
+C# These are the defaults should be fine for most cases.  In special situations, 
+C# you might want to specify different AFSK tones or the baseband mode which does
+C# not use AFSK.
+C#
+C#MODEM 1200 1200:2200
+C#MODEM 300  1600:1800
+C#MODEM 9600 0:0
+C#
+C#
+C# On HF SSB, you might want to use multiple demodulators on slightly different
+C# frequencies to compensate for stations off frequency.  Here we have 7 different
+C# demodulators at 30 Hz intervals.  This takes a lot of CPU power so you will 
+C# probably need to reduce the audio sampling rate with the /n option.
+C
+C#MODEM 300 1600:1800 7 at 30 /4
+C
+C
+C#
+C# Uncomment line below to enable the DTMF decoder for this channel.
+C#
+C
+C#DTMF
+C
+C# 
+C# If not using a VOX circuit, the transmitter Push to Talk (PTT) 
+C# control is usually wired to a serial port with a suitable interface circuit.  
+C# DON'T connect it directly!
+C#
+C# For the PTT command, specify the device and either RTS or DTR.
+C# RTS or DTR may be preceded by "-" to invert the signal.
+C# Both can be used for interfaces that want them driven with opposite polarity.
+C#
+L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on.
+L#
+C
+C#PTT COM1 RTS
+C#PTT COM1 RTS -DTR
+L#PTT /dev/ttyUSB0 RTS
+C
+L#
+L# On Linux, you can also use general purpose I/O pins if
+L# your system is configured for user access to them. 
+L# This would apply mostly to microprocessor boards, not a regular PC.
+L# See separate Raspberry Pi document for more details.
+L# The number may be preceded by "-" to invert the signal.
+L#
+L
+L#PTT GPIO 25
+L
+C# The Data Carrier Detect (DCD) signal can be sent to the same places
+C# as the PTT signal.  This could be used to light up an LED like a normal TNC.
+C
+C#DCD COM1 -DTR
+L#DCD GPIO 24
+C
+C
+C#############################################################
+C#                                                           #
+C#               CHANNEL 1 PROPERTIES                        #
+C#                                                           #
+C#############################################################
+C
+C#CHANNEL 1
+C
+C#
+C# Specify MYCALL, MODEM, PTT, etc. configuration items for 
+C# CHANNEL 1.   Repeat for any other channels.
+C
+C
+C#############################################################
+C#                                                           #
+C#               TEXT TO SPEECH COMMAND FILE                 #
+C#                                                           #
+C#############################################################
+C
+W#SPEECH dwespeak.bat
+L#SPEECH dwespeak.sh
+C
+C
+C#############################################################
+C#                                                           #
+C#               VIRTUAL TNC SERVER PROPERTIES               #
+C#                                                           #
+C#############################################################
+C
+C#
+C# Dire Wolf acts as a virtual TNC and can communicate with
+C# client applications by different protocols:
+C#
+C#	- the "AGW TCPIP Socket Interface" - default port 8000
+C#	- KISS protocol over TCP socket - default port 8001
+W#	- KISS TNC via serial port
+L#	- KISS TNC via pseudo terminal   (-p command line option)
+C#
+C
+CAGWPORT 8000
+CKISSPORT 8001
+C
+W#
+W# Some applications are designed to operate with only a physical
+W# TNC attached to a serial port.  For these, we provide a virtual serial
+W# port that appears to be connected to a TNC.
+W#
+W# Take a look at the User Guide for instructions to set up
+W# two virtual serial ports named COM3 and COM4 connected by
+W# a null modem.
+W#
+W# Using the  configuration described, Dire Wolf will connect to 
+W# COM3 and the client application will use COM4.
+W#
+W# Uncomment following line to use this feature.
+W
+W#NULLMODEM COM3
+W
+W
+C#
+C# It is sometimes possible to recover frames with a bad FCS.
+C# This applies to all channels.  
+C#
+C#	0  [NONE] - Don't try to repair.
+C#	1  [SINGLE] - Attempt to fix single bit error.  (default)
+C#	2  [DOUBLE] - Also attempt to fix two adjacent bits.
+C#	... see User Guide for more values and in-depth discussion.
+C#
+C
+C#FIX_BITS 0
+C
+C#	
+C#############################################################
+C#                                                           #
+C#               BEACONING PROPERTIES                        #
+C#                                                           #
+C#############################################################
+C
+C
+C#
+C# Beaconing is configured with these two commands:
+C#
+C#	PBEACON		- for a position report (usually yourself)
+C#	OBEACON		- for an object report (usually some other entity)
+C#
+C# Each has a series of keywords and values for options.  
+C# See User Guide for details.
+C#
+C# Example:
+C#
+C# This results in a broadcast once every 10 minutes.
+C# Every half hour, it can travel via two digipeater hops.
+C# The others are kept local.
+C#
+C
+C#PBEACON delay=1  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 
+C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
+C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"  
+C
+C
+C# With UTM coordinates instead of latitude and longitude.
+C
+C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 
+C
+C
+C#
+C# When the destination field is set to "SPEECH" the information part is
+C# converted to speech rather than transmitted as a data frame.
+C#
+C
+C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm."
+C
+C# Similar for Morse code.  If SSID is specified, it is multiplied
+C# by 2 to get speed in words per minute (WPM).
+C
+C#CBEACON dest="MORSE-6" info="de MYCALL"
+C
+C
+C#
+C# Modify for your particular situation before removing 
+C# the # comment character from the beginning of appropriate lines above.
+C# 
+C
+C
+C#############################################################
+C#                                                           #
+C#               DIGIPEATER PROPERTIES                       #
+C#                                                           #
+C#############################################################
+C
+C#
+C# For most common situations, use something like this by removing
+C# the "#" from the beginning of the line below.  
+C#
+C
+C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE 
+C
+C# See User Guide for more explanation of what this means and how
+C# it can be customized for your particular needs.
+C 
+C# Filtering can be used to limit was is digipeated.
+C# For example, only weather weather reports, received on channel 0,
+C# will be retransmitted on channel 1.
+C#
+C
+C#FILTER 0 1 t/wn 
+C
+C
+C#############################################################
+C#                                                           #
+C#               INTERNET GATEWAY                            #
+C#                                                           #
+C#############################################################
+C
+C# First you need to specify the name of a Tier 2 server.  
+C# The current preferred way is to use one of these regional rotate addresses:
+C
+C#	noam.aprs2.net 		- for North America
+C#	soam.aprs2.net		- for South America
+C#	euro.aprs2.net		- for Europe and Africa
+C#	asia.aprs2.net 		- for Asia
+C#	aunz.aprs2.net		- for Oceania 
+C
+C#IGSERVER noam.aprs2.net
+C
+C# You also need to specify your login name and passcode. 
+C# Contact the author if you can't figure out how to generate the passcode.
+C 
+C#IGLOGIN WB2OSZ-5 123456
+C
+C# That's all you need for a receive only IGate which relays
+C# messages from the local radio channel to the global servers.
+C
+C# Some might want to send an IGate client position directly to a server
+C# without sending it over the air and relying on someone else to 
+C# forward it to an IGate server.  This is done by using sendto=IG rather
+C# than a radio channel number. Overlay R for receive only, T for two way.
+C
+C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W 
+C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W 
+C
+C
+C# To relay messages from the Internet to radio, you need to add
+C# one more option with the transmit channel number and a VIA path.
+C
+C#IGTXVIA 0 WIDE1-1
+C
+C# You might want to apply a filter for what packets will be obtained from the server.
+C# Read about filters here:  http://www.aprs-is.net/javaprsfilter.aspx
+C# Example, positions and objects within 50 km of my location:
+C
+C#IGFILTER m/50 
+C
+C# That is known as a server-side filter.  It is processed by the IGate server.
+C# You can also apply local filtering to limit what will be transmitted on the 
+C# RF side.  For example, transmit only "messages" on channel 0 and weather 
+C# reports on channel 1. 
+C
+C#FILTER IG 0 t/m
+C#FILTER IG 1 t/wn
+C
+C# Finally, we don't want to flood the radio channel.  
+C# The IGate function will limit the number of packets transmitted 
+C# during 1 minute and 5 minute intervals.   If a limit would 
+C# be exceeded, the packet is dropped and message is displayed in red.
+C
+CIGTXLIMIT 6 10
+C
+C
+C#############################################################
+C#                                                           #
+C#               APRStt GATEWAY                              #
+C#                                                           #
+C#############################################################
+C
+C#
+C# Dire Wolf can receive DTMF (commonly known as Touch Tone)
+C# messages and convert them to packet objects.
+C#
+C# See separate "APRStt-Implementation-Notes" document for details.
+C#
+C
+C#
+C# Sample gateway configuration based on:
+C#
+C#	http://www.aprs.org/aprstt/aprstt-coding24.txt
+C#	http://www.aprs.org/aprs-jamboree-2013.html
+C#
+C
+C# Define specific points.
+C
+CTTPOINT  B01  37^55.37N  81^7.86W  			
+CTTPOINT  B7495088  42.605237  -71.34456		
+CTTPOINT  B934  42.605237  -71.34456			
+C
+CTTPOINT B901  42.661279  -71.364452 
+CTTPOINT B902  42.660411  -71.364419 
+CTTPOINT B903  42.659046  -71.364452 
+CTTPOINT B904  42.657578  -71.364602 
+C
+C
+C# For location at given bearing and distance from starting point.
+C
+CTTVECTOR  B5bbbddd  37^55.37N  81^7.86W  0.01  mi
+C
+C# For location specified by x, y coordinates.
+C
+CTTGRID   Byyyxxx    37^50.00N  81^00.00W  37^59.99N  81^09.99W   
+C
+C# UTM location for Lowell-Dracut-Tyngsborough State Forest.
+C
+CTTUTM  B6xxxyyy  19T  10  300000  4720000
+C
+C
+C
+C# Location for the corral.
+C
+CTTCORRAL   37^55.50N  81^7.00W  0^0.02N
+C
+C# Compact messages - Fixed locations xx and object yyy where 
+C#   	Object numbers 100 - 199	= bicycle	
+C#	Object numbers 200 - 299	= fire truck
+C#	Others				= dog
+C
+CTTMACRO  xx1yy  B9xx*AB166*AA2B4C5B3B0A1yy
+CTTMACRO  xx2yy  B9xx*AB170*AA3C4C7C3B0A2yy
+CTTMACRO  xxyyy  B9xx*AB180*AA3A6C4A0Ayyy
+C
+CTTMACRO  z  Cz
+C
+C# Receive on channel 0, Transmit object reports on channel 1 with optional via path.
+C# You probably want to put in a transmit delay on the APRStt channel so it
+C# it doesn't start sending a response before the user releases PTT.
+C# This is in 10 ms units so 100 means 1000 ms = 1 second.
+C
+C#TTOBJ 0 1 WIDE1-1
+C#CHANNEL 0
+C#DWAIT 100
+C
+C# Advertise gateway position with beacon.
+C
+C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway"  
+C
+C
+C# Sample speech responses.
+C# Default is Morse code "R" for received OK and "?" for all errors.
+C
+C#TTERR  OK               SPEECH  Message Received.
+C#TTERR  D_MSG            SPEECH  D not implemented.
+C#TTERR  INTERNAL         SPEECH  Internal error.
+C#TTERR  MACRO_NOMATCH    SPEECH  No definition for digit sequence.
+C#TTERR  BAD_CHECKSUM     SPEECH  Bad checksum on call.
+C#TTERR  INVALID_CALL     SPEECH  Invalid callsign.
+C#TTERR  INVALID_OBJNAME  SPEECH  Invalid object name.
+C#TTERR  INVALID_SYMBOL   SPEECH  Invalid symbol.
+C#TTERR  INVALID_LOC      SPEECH  Invalid location.
+C#TTERR  NO_CALL          SPEECH  No call or object name.
+C#TTERR  SATSQ            SPEECH  Satellite square must be 4 digits.
+C#TTERR  SUFFIX_NO_CALL   SPEECH  Send full call before using suffix.
+C
\ No newline at end of file
diff --git a/geotranz/README-FIRST.txt b/geotranz/README-FIRST.txt
old mode 100755
new mode 100644
index f5a010d..602f114
--- a/geotranz/README-FIRST.txt
+++ b/geotranz/README-FIRST.txt
@@ -1,5 +1,5 @@
-
-This directory contains a subset of geotrans 2.4.2 from
-
-https://github.com/smanders/geotranz
-
+
+This directory contains a subset of geotrans 2.4.2 from
+
+https://github.com/smanders/geotranz
+
diff --git a/geotranz/error_string.c b/geotranz/error_string.c
old mode 100755
new mode 100644
index 02cc2c2..d930188
--- a/geotranz/error_string.c
+++ b/geotranz/error_string.c
@@ -1,131 +1,131 @@
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "utm.h"
-#include "mgrs.h"
-#include "usng.h"
-
-#include "error_string.h"
-
-
-// Convert error codes to text.
-// Note that the code is a bit mask so it is possible to have multiple messages.
-// Caller should probably provide space for a couple hundred characters to be safe.
-
-
-static const struct {
-	long mask;
-	char *msg;
-} utm_err [] = {
-	
-	{ UTM_NO_ERROR,           "No errors occurred in function" },
-	{ UTM_LAT_ERROR,          "Latitude outside of valid range (-80.5 to 84.5 degrees)" },
-	{ UTM_LON_ERROR,          "Longitude outside of valid range (-180 to 360 degrees)" },
-	{ UTM_EASTING_ERROR,      "Easting outside of valid range (100,000 to 900,000 meters)" },
-	{ UTM_NORTHING_ERROR,     "Northing outside of valid range (0 to 10,000,000 meters)" },
-	{ UTM_ZONE_ERROR,         "Zone outside of valid range (1 to 60)" },
-	{ UTM_HEMISPHERE_ERROR,   "Invalid hemisphere ('N' or 'S')" },
-	{ UTM_ZONE_OVERRIDE_ERROR,"Zone outside of valid range (1 to 60) and within 1 of 'natural' zone" },
-	{ UTM_A_ERROR,            "Semi-major axis less than or equal to zero" },
- 	{ UTM_INV_F_ERROR,        "Inverse flattening outside of valid range (250 to 350)" },
-	{ 0, NULL } };
-
-
-static const struct {
-	long mask;
-	char *msg;
-} mgrs_err [] = {
-	
-	{ MGRS_NO_ERROR,          "No errors occurred in function" },
-	{ MGRS_LAT_ERROR,         "Latitude outside of valid range (-90 to 90 degrees)" },
-	{ MGRS_LON_ERROR,         "Longitude outside of valid range (-180 to 360 degrees)" },
-	{ MGRS_STRING_ERROR,      "An MGRS string error: string too long, too short, or badly formed" },
-	{ MGRS_PRECISION_ERROR,   "The precision must be between 0 and 5 inclusive." },
-	{ MGRS_A_ERROR,           "Inverse flattening outside of valid range (250 to 350)" },
-	{ MGRS_INV_F_ERROR,       "Invalid hemisphere ('N' or 'S')" },
-	{ MGRS_EASTING_ERROR,     "Easting outside of valid range (100,000 to 900,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
-	{ MGRS_NORTHING_ERROR,    "Northing outside of valid range (0 to 10,000,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
- 	{ MGRS_ZONE_ERROR,        "Zone outside of valid range (1 to 60)" },
- 	{ MGRS_HEMISPHERE_ERROR,  "Invalid hemisphere ('N' or 'S')" },
- 	{ MGRS_LAT_WARNING,       "Latitude warning ???" },
-	{ 0, NULL } };
-
-
-static const struct {
-	long mask;
-	char *msg;
-} usng_err [] = {
-	
-	{ USNG_NO_ERROR,          "No errors occurred in function" },
-	{ USNG_LAT_ERROR,         "Latitude outside of valid range (-90 to 90 degrees)" },
-	{ USNG_LON_ERROR,         "Longitude outside of valid range (-180 to 360 degrees)" },
-	{ USNG_STRING_ERROR,      "A USNG string error: string too long, too short, or badly formed" },
-	{ USNG_PRECISION_ERROR,   "The precision must be between 0 and 5 inclusive." },
-	{ USNG_A_ERROR,           "Inverse flattening outside of valid range (250 to 350)" },
-	{ USNG_INV_F_ERROR,       "Invalid hemisphere ('N' or 'S')" },
-	{ USNG_EASTING_ERROR,     "Easting outside of valid range (100,000 to 900,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
-	{ USNG_NORTHING_ERROR,    "Northing outside of valid range (0 to 10,000,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
- 	{ USNG_ZONE_ERROR,        "Zone outside of valid range (1 to 60)" },
- 	{ USNG_HEMISPHERE_ERROR,  "Invalid hemisphere ('N' or 'S')" },
- 	{ USNG_LAT_WARNING,       "Latitude warning ???" },
-	{ 0, NULL } };
-
-
-
-void utm_error_string (long err, char *str)
-{
-	int n;
-
-	strcpy (str, "");
-
-	for (n = 1; utm_err[n].mask != 0; n++) {
-	  if (err & utm_err[n].mask) {
-	    if (strlen(str) > 0) strcat(str, "\n");
-	    strcat (str, utm_err[n].msg);
-	  }
-	}
-
-	if (strlen(str) == 0) {
-	  strcpy (str, utm_err[0].msg);
-	}
-}
-
-void mgrs_error_string (long err, char *str)
-{
-	int n;
-
-	strcpy (str, "");
-
-	for (n = 1; mgrs_err[n].mask != 0; n++) {
-	  if (err & mgrs_err[n].mask) {
-	    if (strlen(str) > 0) strcat(str, "\n");
-	    strcat (str, mgrs_err[n].msg);
-	  }
-	}
-
-	if (strlen(str) == 0) {
-	  strcpy (str, mgrs_err[0].msg);
-	}
-}
-
-
-void usng_error_string (long err, char *str)
-{
-	int n;
-
-	strcpy (str, "");
-
-	for (n = 1; usng_err[n].mask != 0; n++) {
-	  if (err & usng_err[n].mask) {
-	    if (strlen(str) > 0) strcat(str, "\n");
-	    strcat (str, usng_err[n].msg);
-	  }
-	}
-
-	if (strlen(str) == 0) {
-	  strcpy (str, usng_err[0].msg);
-	}
-}
-
-
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "utm.h"
+#include "mgrs.h"
+#include "usng.h"
+
+#include "error_string.h"
+
+
+// Convert error codes to text.
+// Note that the code is a bit mask so it is possible to have multiple messages.
+// Caller should probably provide space for a couple hundred characters to be safe.
+
+
+static const struct {
+	long mask;
+	char *msg;
+} utm_err [] = {
+	
+	{ UTM_NO_ERROR,           "No errors occurred in function" },
+	{ UTM_LAT_ERROR,          "Latitude outside of valid range (-80.5 to 84.5 degrees)" },
+	{ UTM_LON_ERROR,          "Longitude outside of valid range (-180 to 360 degrees)" },
+	{ UTM_EASTING_ERROR,      "Easting outside of valid range (100,000 to 900,000 meters)" },
+	{ UTM_NORTHING_ERROR,     "Northing outside of valid range (0 to 10,000,000 meters)" },
+	{ UTM_ZONE_ERROR,         "Zone outside of valid range (1 to 60)" },
+	{ UTM_HEMISPHERE_ERROR,   "Invalid hemisphere ('N' or 'S')" },
+	{ UTM_ZONE_OVERRIDE_ERROR,"Zone outside of valid range (1 to 60) and within 1 of 'natural' zone" },
+	{ UTM_A_ERROR,            "Semi-major axis less than or equal to zero" },
+ 	{ UTM_INV_F_ERROR,        "Inverse flattening outside of valid range (250 to 350)" },
+	{ 0, NULL } };
+
+
+static const struct {
+	long mask;
+	char *msg;
+} mgrs_err [] = {
+	
+	{ MGRS_NO_ERROR,          "No errors occurred in function" },
+	{ MGRS_LAT_ERROR,         "Latitude outside of valid range (-90 to 90 degrees)" },
+	{ MGRS_LON_ERROR,         "Longitude outside of valid range (-180 to 360 degrees)" },
+	{ MGRS_STRING_ERROR,      "An MGRS string error: string too long, too short, or badly formed" },
+	{ MGRS_PRECISION_ERROR,   "The precision must be between 0 and 5 inclusive." },
+	{ MGRS_A_ERROR,           "Inverse flattening outside of valid range (250 to 350)" },
+	{ MGRS_INV_F_ERROR,       "Invalid hemisphere ('N' or 'S')" },
+	{ MGRS_EASTING_ERROR,     "Easting outside of valid range (100,000 to 900,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
+	{ MGRS_NORTHING_ERROR,    "Northing outside of valid range (0 to 10,000,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
+ 	{ MGRS_ZONE_ERROR,        "Zone outside of valid range (1 to 60)" },
+ 	{ MGRS_HEMISPHERE_ERROR,  "Invalid hemisphere ('N' or 'S')" },
+ 	{ MGRS_LAT_WARNING,       "Latitude warning ???" },
+	{ 0, NULL } };
+
+
+static const struct {
+	long mask;
+	char *msg;
+} usng_err [] = {
+	
+	{ USNG_NO_ERROR,          "No errors occurred in function" },
+	{ USNG_LAT_ERROR,         "Latitude outside of valid range (-90 to 90 degrees)" },
+	{ USNG_LON_ERROR,         "Longitude outside of valid range (-180 to 360 degrees)" },
+	{ USNG_STRING_ERROR,      "A USNG string error: string too long, too short, or badly formed" },
+	{ USNG_PRECISION_ERROR,   "The precision must be between 0 and 5 inclusive." },
+	{ USNG_A_ERROR,           "Inverse flattening outside of valid range (250 to 350)" },
+	{ USNG_INV_F_ERROR,       "Invalid hemisphere ('N' or 'S')" },
+	{ USNG_EASTING_ERROR,     "Easting outside of valid range (100,000 to 900,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
+	{ USNG_NORTHING_ERROR,    "Northing outside of valid range (0 to 10,000,000 meters for UTM) (0 to 4,000,000 meters for UPS)" },
+ 	{ USNG_ZONE_ERROR,        "Zone outside of valid range (1 to 60)" },
+ 	{ USNG_HEMISPHERE_ERROR,  "Invalid hemisphere ('N' or 'S')" },
+ 	{ USNG_LAT_WARNING,       "Latitude warning ???" },
+	{ 0, NULL } };
+
+
+
+void utm_error_string (long err, char *str)
+{
+	int n;
+
+	strcpy (str, "");
+
+	for (n = 1; utm_err[n].mask != 0; n++) {
+	  if (err & utm_err[n].mask) {
+	    if (strlen(str) > 0) strcat(str, "\n");
+	    strcat (str, utm_err[n].msg);
+	  }
+	}
+
+	if (strlen(str) == 0) {
+	  strcpy (str, utm_err[0].msg);
+	}
+}
+
+void mgrs_error_string (long err, char *str)
+{
+	int n;
+
+	strcpy (str, "");
+
+	for (n = 1; mgrs_err[n].mask != 0; n++) {
+	  if (err & mgrs_err[n].mask) {
+	    if (strlen(str) > 0) strcat(str, "\n");
+	    strcat (str, mgrs_err[n].msg);
+	  }
+	}
+
+	if (strlen(str) == 0) {
+	  strcpy (str, mgrs_err[0].msg);
+	}
+}
+
+
+void usng_error_string (long err, char *str)
+{
+	int n;
+
+	strcpy (str, "");
+
+	for (n = 1; usng_err[n].mask != 0; n++) {
+	  if (err & usng_err[n].mask) {
+	    if (strlen(str) > 0) strcat(str, "\n");
+	    strcat (str, usng_err[n].msg);
+	  }
+	}
+
+	if (strlen(str) == 0) {
+	  strcpy (str, usng_err[0].msg);
+	}
+}
+
+
diff --git a/geotranz/error_string.h b/geotranz/error_string.h
old mode 100755
new mode 100644
index 17b980e..43e591d
--- a/geotranz/error_string.h
+++ b/geotranz/error_string.h
@@ -1,7 +1,7 @@
-
-
-void utm_error_string (long err, char *str);
-
-void mgrs_error_string (long err, char *str);
-
-void usng_error_string (long err, char *str);
+
+
+void utm_error_string (long err, char *str);
+
+void mgrs_error_string (long err, char *str);
+
+void usng_error_string (long err, char *str);
diff --git a/geotranz/mgrs.c b/geotranz/mgrs.c
old mode 100755
new mode 100644
index 06adbd7..9fb8289
--- a/geotranz/mgrs.c
+++ b/geotranz/mgrs.c
@@ -1,1347 +1,1347 @@
-/***************************************************************************/
-/* RSC IDENTIFIER:  MGRS
- *
- * ABSTRACT
- *
- *    This component converts between geodetic coordinates (latitude and 
- *    longitude) and Military Grid Reference System (MGRS) coordinates. 
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found, the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *          MGRS_NO_ERROR          : No errors occurred in function
- *          MGRS_LAT_ERROR         : Latitude outside of valid range 
- *                                    (-90 to 90 degrees)
- *          MGRS_LON_ERROR         : Longitude outside of valid range
- *                                    (-180 to 360 degrees)
- *          MGRS_STR_ERROR         : An MGRS string error: string too long,
- *                                    too short, or badly formed
- *          MGRS_PRECISION_ERROR   : The precision must be between 0 and 5 
- *                                    inclusive.
- *          MGRS_A_ERROR           : Semi-major axis less than or equal to zero
- *          MGRS_INV_F_ERROR       : Inverse flattening outside of valid range
- *									                  (250 to 350)
- *          MGRS_EASTING_ERROR     : Easting outside of valid range
- *                                    (100,000 to 900,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          MGRS_NORTHING_ERROR    : Northing outside of valid range
- *                                    (0 to 10,000,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          MGRS_ZONE_ERROR        : Zone outside of valid range (1 to 60)
- *          MGRS_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
- *
- * REUSE NOTES
- *
- *    MGRS is intended for reuse by any application that does conversions
- *    between geodetic coordinates and MGRS coordinates.
- *
- * REFERENCES
- *
- *    Further information on MGRS can be found in the Reuse Manual.
- *
- *    MGRS originated from : U.S. Army Topographic Engineering Center
- *                           Geospatial Information Division
- *                           7701 Telegraph Road
- *                           Alexandria, VA  22310-3864
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *
- * ENVIRONMENT
- *
- *    MGRS was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC version 2.8.1
- *    2. Windows 95 with MS Visual C++ version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    16-11-94          Original Code
- *    15-09-99          Reengineered upper layers
- *    02-05-03          Corrected latitude band bug in GRID_UTM 
- *    08-20-03          Reengineered lower layers
- */
-
-
-/***************************************************************************/
-/*
- *                               INCLUDES
- */
-#include <ctype.h>
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
-#include "ups.h"
-#include "utm.h"
-#include "mgrs.h"
-
-/*
- *      ctype.h     - Standard C character handling library
- *      math.h      - Standard C math library
- *      stdio.h     - Standard C input/output library
- *      string.h    - Standard C string handling library
- *      ups.h       - Universal Polar Stereographic (UPS) projection
- *      utm.h       - Universal Transverse Mercator (UTM) projection
- *      mgrs.h      - function prototype error checking
- */
-
-
-/***************************************************************************/
-/*
- *                              GLOBAL DECLARATIONS
- */
-#define DEG_TO_RAD       0.017453292519943295 /* PI/180                      */
-#define RAD_TO_DEG       57.29577951308232087 /* 180/PI                      */
-#define LETTER_A               0   /* ARRAY INDEX FOR LETTER A               */
-#define LETTER_B               1   /* ARRAY INDEX FOR LETTER B               */
-#define LETTER_C               2   /* ARRAY INDEX FOR LETTER C               */
-#define LETTER_D               3   /* ARRAY INDEX FOR LETTER D               */
-#define LETTER_E               4   /* ARRAY INDEX FOR LETTER E               */
-#define LETTER_F               5   /* ARRAY INDEX FOR LETTER F               */
-#define LETTER_G               6   /* ARRAY INDEX FOR LETTER G               */
-#define LETTER_H               7   /* ARRAY INDEX FOR LETTER H               */
-#define LETTER_I               8   /* ARRAY INDEX FOR LETTER I               */
-#define LETTER_J               9   /* ARRAY INDEX FOR LETTER J               */
-#define LETTER_K              10   /* ARRAY INDEX FOR LETTER K               */
-#define LETTER_L              11   /* ARRAY INDEX FOR LETTER L               */
-#define LETTER_M              12   /* ARRAY INDEX FOR LETTER M               */
-#define LETTER_N              13   /* ARRAY INDEX FOR LETTER N               */
-#define LETTER_O              14   /* ARRAY INDEX FOR LETTER O               */
-#define LETTER_P              15   /* ARRAY INDEX FOR LETTER P               */
-#define LETTER_Q              16   /* ARRAY INDEX FOR LETTER Q               */
-#define LETTER_R              17   /* ARRAY INDEX FOR LETTER R               */
-#define LETTER_S              18   /* ARRAY INDEX FOR LETTER S               */
-#define LETTER_T              19   /* ARRAY INDEX FOR LETTER T               */
-#define LETTER_U              20   /* ARRAY INDEX FOR LETTER U               */
-#define LETTER_V              21   /* ARRAY INDEX FOR LETTER V               */
-#define LETTER_W              22   /* ARRAY INDEX FOR LETTER W               */
-#define LETTER_X              23   /* ARRAY INDEX FOR LETTER X               */
-#define LETTER_Y              24   /* ARRAY INDEX FOR LETTER Y               */
-#define LETTER_Z              25   /* ARRAY INDEX FOR LETTER Z               */
-#define MGRS_LETTERS            3  /* NUMBER OF LETTERS IN MGRS              */
-#define ONEHT          100000.e0    /* ONE HUNDRED THOUSAND                  */
-#define TWOMIL        2000000.e0    /* TWO MILLION                           */
-#define TRUE                      1  /* CONSTANT VALUE FOR TRUE VALUE  */
-#define FALSE                     0  /* CONSTANT VALUE FOR FALSE VALUE */
-#define PI    3.14159265358979323e0  /* PI                             */
-#define PI_OVER_2  (PI / 2.0e0)
-
-#define MIN_EASTING  100000
-#define MAX_EASTING  900000
-#define MIN_NORTHING 0
-#define MAX_NORTHING 10000000
-#define MAX_PRECISION           5   /* Maximum precision of easting & northing */
-#define MIN_UTM_LAT      ( (-80 * PI) / 180.0 ) /* -80 degrees in radians    */
-#define MAX_UTM_LAT      ( (84 * PI) / 180.0 )  /* 84 degrees in radians     */
-
-#define MIN_EAST_NORTH 0
-#define MAX_EAST_NORTH 4000000
-
-
-/* Ellipsoid parameters, default to WGS 84 */
-double MGRS_a = 6378137.0;    /* Semi-major axis of ellipsoid in meters */
-double MGRS_f = 1 / 298.257223563; /* Flattening of ellipsoid           */
-char   MGRS_Ellipsoid_Code[3] = {'W','E',0};
-
-
-/* 
- *    CLARKE_1866 : Ellipsoid code for CLARKE_1866
- *    CLARKE_1880 : Ellipsoid code for CLARKE_1880
- *    BESSEL_1841 : Ellipsoid code for BESSEL_1841
- *    BESSEL_1841_NAMIBIA : Ellipsoid code for BESSEL 1841 (NAMIBIA)
- */
-const char* CLARKE_1866 = "CC";
-const char* CLARKE_1880 = "CD";
-const char* BESSEL_1841 = "BR";
-const char* BESSEL_1841_NAMIBIA = "BN";
-
-
-typedef struct Latitude_Band_Value
-{
-  long letter;            /* letter representing latitude band  */
-  double min_northing;    /* minimum northing for latitude band */       
-  double north;           /* upper latitude for latitude band   */
-  double south;           /* lower latitude for latitude band   */
-  double northing_offset; /* latitude band northing offset      */
-} Latitude_Band; 
-
-static const Latitude_Band Latitude_Band_Table[20] = 
-  {{LETTER_C, 1100000.0, -72.0, -80.5, 0.0}, 
-  {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0},
-  {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0},
-  {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0},
-  {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0},
-  {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0},
-  {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0},
-  {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0},
-  {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0},
-  {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0},
-  {LETTER_N, 0.0, 8.0, 0.0, 0.0},
-  {LETTER_P, 800000.0, 16.0, 8.0, 0.0},
-  {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0},
-  {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0},
-  {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0},
-  {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0},
-  {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0},
-  {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0},
-  {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0},
-  {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}};
-
- 
-typedef struct UPS_Constant_Value
-{
-  long letter;            /* letter representing latitude band      */
-  long ltr2_low_value;    /* 2nd letter range - low number         */
-  long ltr2_high_value;   /* 2nd letter range - high number          */
-  long ltr3_high_value;   /* 3rd letter range - high number (UPS)   */
-  double false_easting;   /* False easting based on 2nd letter      */
-  double false_northing;  /* False northing based on 3rd letter     */
-} UPS_Constant; 
-
-static const UPS_Constant UPS_Constant_Table[4] = 
-  {{LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000.0, 800000.0},
-  {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000.0, 800000.0},
-  {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000.0, 1300000.0},
-  {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000.0, 1300000.0}};
-
-/***************************************************************************/
-/*
- *                              FUNCTIONS     
- */
-
-long Get_Latitude_Band_Min_Northing(long letter, double* min_northing, double* northing_offset)
-/*
- * The function Get_Latitude_Band_Min_Northing receives a latitude band letter
- * and uses the Latitude_Band_Table to determine the minimum northing and northing offset
- * for that latitude band letter.
- *
- *   letter        : Latitude band letter             (input)
- *   min_northing  : Minimum northing for that letter	(output)
- */
-{ /* Get_Latitude_Band_Min_Northing */
-  long error_code = MGRS_NO_ERROR;
-
-  if ((letter >= LETTER_C) && (letter <= LETTER_H))
-  {
-    *min_northing = Latitude_Band_Table[letter-2].min_northing;
-    *northing_offset = Latitude_Band_Table[letter-2].northing_offset;
-  }
-  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
-  {
-    *min_northing = Latitude_Band_Table[letter-3].min_northing;
-    *northing_offset = Latitude_Band_Table[letter-3].northing_offset;
-  }
-  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
-  {
-    *min_northing = Latitude_Band_Table[letter-4].min_northing;
-    *northing_offset = Latitude_Band_Table[letter-4].northing_offset;
-  }
-  else
-    error_code |= MGRS_STRING_ERROR;
-
-  return error_code;
-} /* Get_Latitude_Band_Min_Northing */
-
-
-long Get_Latitude_Range(long letter, double* north, double* south)
-/*
- * The function Get_Latitude_Range receives a latitude band letter
- * and uses the Latitude_Band_Table to determine the latitude band 
- * boundaries for that latitude band letter.
- *
- *   letter   : Latitude band letter                        (input)
- *   north    : Northern latitude boundary for that letter	(output)
- *   north    : Southern latitude boundary for that letter	(output)
- */
-{ /* Get_Latitude_Range */
-  long error_code = MGRS_NO_ERROR;
-
-  if ((letter >= LETTER_C) && (letter <= LETTER_H))
-  {
-    *north = Latitude_Band_Table[letter-2].north * DEG_TO_RAD;
-    *south = Latitude_Band_Table[letter-2].south * DEG_TO_RAD;
-  }
-  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
-  {
-    *north = Latitude_Band_Table[letter-3].north * DEG_TO_RAD;
-    *south = Latitude_Band_Table[letter-3].south * DEG_TO_RAD;
-  }
-  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
-  {
-    *north = Latitude_Band_Table[letter-4].north * DEG_TO_RAD;
-    *south = Latitude_Band_Table[letter-4].south * DEG_TO_RAD;
-  }
-  else
-    error_code |= MGRS_STRING_ERROR;
-
-  return error_code;
-} /* Get_Latitude_Range */
-
-
-long Get_Latitude_Letter(double latitude, int* letter)
-/*
- * The function Get_Latitude_Letter receives a latitude value
- * and uses the Latitude_Band_Table to determine the latitude band 
- * letter for that latitude.
- *
- *   latitude   : Latitude              (input)
- *   letter     : Latitude band letter  (output)
- */
-{ /* Get_Latitude_Letter */
-  double temp = 0.0;
-  long error_code = MGRS_NO_ERROR;
-  double lat_deg = latitude * RAD_TO_DEG;
-
-  if (lat_deg >= 72 && lat_deg < 84.5)
-    *letter = LETTER_X;
-  else if (lat_deg > -80.5 && lat_deg < 72)
-  {
-    temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12;
-    *letter = Latitude_Band_Table[(int)temp].letter;
-  }
-  else
-    error_code |= MGRS_LAT_ERROR;
-
-  return error_code;
-} /* Get_Latitude_Letter */
-
-
-long Check_Zone(char* MGRS, long* zone_exists)
-/*
- * The function Check_Zone receives an MGRS coordinate string.
- * If a zone is given, TRUE is returned. Otherwise, FALSE
- * is returned.
- *
- *   MGRS           : MGRS coordinate string        (input)
- *   zone_exists    : TRUE if a zone is given, 
- *                    FALSE if a zone is not given  (output)
- */
-{ /* Check_Zone */
-  int i = 0;
-  int j = 0;
-  int num_digits = 0;
-  long error_code = MGRS_NO_ERROR;
-
-  /* skip any leading blanks */  
-  while (MGRS[i] == ' ')
-    i++;  
-  j = i;
-  while (isdigit(MGRS[i]))
-    i++;
-  num_digits = i - j;
-  if (num_digits <= 2)
-    if (num_digits > 0)
-      *zone_exists = TRUE;
-    else
-      *zone_exists = FALSE;
-  else
-    error_code |= MGRS_STRING_ERROR;
-
-  return error_code;
-} /* Check_Zone */
-
-
-long Round_MGRS (double value)
-/*
- * The function Round_MGRS rounds the input value to the 
- * nearest integer, using the standard engineering rule.
- * The rounded integer value is then returned.
- *
- *   value           : Value to be rounded  (input)
- */
-{ /* Round_MGRS */
-  double ivalue;
-  long ival;
-  double fraction = modf (value, &ivalue);
-  ival = (long)(ivalue);
-  if ((fraction > 0.5) || ((fraction == 0.5) && (ival%2 == 1)))
-    ival++;
-  return (ival);
-} /* Round_MGRS */
-
-
-long Make_MGRS_String (char* MGRS, 
-                       long Zone, 
-                       int Letters[MGRS_LETTERS], 
-                       double Easting, 
-                       double Northing,
-                       long Precision)
-/*
- * The function Make_MGRS_String constructs an MGRS string 
- * from its component parts.
- *
- *   MGRS           : MGRS coordinate string          (output)
- *   Zone           : UTM Zone                        (input)
- *   Letters        : MGRS coordinate string letters  (input)
- *   Easting        : Easting value                   (input)
- *   Northing       : Northing value                  (input)
- *   Precision      : Precision level of MGRS string  (input)
- */
-{ /* Make_MGRS_String */
-  long i;
-  long j;
-  double divisor;
-  long east;
-  long north;
-  char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-  long error_code = MGRS_NO_ERROR;
-
-  i = 0;
-  if (Zone)
-    i = sprintf (MGRS+i,"%2.2ld",Zone);
-  else
-    strncpy(MGRS, "  ", 2);  // 2 spaces
-
-  for (j=0;j<3;j++)
-    MGRS[i++] = alphabet[Letters[j]];
-  divisor = pow (10.0, (5 - Precision));
-  Easting = fmod (Easting, 100000.0);
-  if (Easting >= 99999.5)
-    Easting = 99999.0;
-  east = (long)(Easting/divisor);
-  i += sprintf (MGRS+i, "%*.*ld", (int)Precision, (int)Precision, east);
-  Northing = fmod (Northing, 100000.0);
-  if (Northing >= 99999.5)
-    Northing = 99999.0;
-  north = (long)(Northing/divisor);
-  i += sprintf (MGRS+i, "%*.*ld", (int)Precision, (int)Precision, north);
-  return (error_code);
-} /* Make_MGRS_String */
-
-
-long Break_MGRS_String (char* MGRS,
-                        long* Zone,
-                        long Letters[MGRS_LETTERS],
-                        double* Easting,
-                        double* Northing,
-                        long* Precision)
-/*
- * The function Break_MGRS_String breaks down an MGRS  
- * coordinate string into its component parts.
- *
- *   MGRS           : MGRS coordinate string          (input)
- *   Zone           : UTM Zone                        (output)
- *   Letters        : MGRS coordinate string letters  (output)
- *   Easting        : Easting value                   (output)
- *   Northing       : Northing value                  (output)
- *   Precision      : Precision level of MGRS string  (output)
- */
-{ /* Break_MGRS_String */
-  long num_digits;
-  long num_letters;
-  long i = 0;
-  long j = 0;
-  long error_code = MGRS_NO_ERROR;
-
-  while (MGRS[i] == ' ')
-    i++;  /* skip any leading blanks */
-  j = i;
-  while (isdigit(MGRS[i]))
-    i++;
-  num_digits = i - j;
-  if (num_digits <= 2)
-    if (num_digits > 0)
-    {
-      char zone_string[3];
-      /* get zone */
-      strncpy (zone_string, MGRS+j, 2);
-      zone_string[2] = 0;
-      sscanf (zone_string, "%ld", Zone);  
-      if ((*Zone < 1) || (*Zone > 60))
-        error_code |= MGRS_STRING_ERROR;
-    }
-    else
-      *Zone = 0;
-  else
-    error_code |= MGRS_STRING_ERROR;
-  j = i;
-
-  while (isalpha(MGRS[i]))
-    i++;
-  num_letters = i - j;
-  if (num_letters == 3)
-  {
-    /* get letters */
-    Letters[0] = (toupper(MGRS[j]) - (long)'A');
-    if ((Letters[0] == LETTER_I) || (Letters[0] == LETTER_O))
-      error_code |= MGRS_STRING_ERROR;
-    Letters[1] = (toupper(MGRS[j+1]) - (long)'A');
-    if ((Letters[1] == LETTER_I) || (Letters[1] == LETTER_O))
-      error_code |= MGRS_STRING_ERROR;
-    Letters[2] = (toupper(MGRS[j+2]) - (long)'A');
-    if ((Letters[2] == LETTER_I) || (Letters[2] == LETTER_O))
-      error_code |= MGRS_STRING_ERROR;
-  }
-  else
-    error_code |= MGRS_STRING_ERROR;
-  j = i;
-  while (isdigit(MGRS[i]))
-    i++;
-  num_digits = i - j;
-  if ((num_digits <= 10) && (num_digits%2 == 0))
-  {
-    long n;
-    char east_string[6];
-    char north_string[6];
-    long east;
-    long north;
-    double multiplier;
-    /* get easting & northing */
-    n = num_digits/2;
-    *Precision = n;
-    if (n > 0)
-    {
-      strncpy (east_string, MGRS+j, n);
-      east_string[n] = 0;
-      sscanf (east_string, "%ld", &east);
-      strncpy (north_string, MGRS+j+n, n);
-      north_string[n] = 0;
-      sscanf (north_string, "%ld", &north);
-      multiplier = pow (10.0, 5 - n);
-      *Easting = east * multiplier;
-      *Northing = north * multiplier;
-    }
-    else
-    {
-      *Easting = 0.0;
-      *Northing = 0.0;
-    }
-  }
-  else
-    error_code |= MGRS_STRING_ERROR;
-
-  return (error_code);
-} /* Break_MGRS_String */
-
-
-void Get_Grid_Values (long zone, 
-                      long* ltr2_low_value, 
-                      long* ltr2_high_value, 
-                      double *pattern_offset)
-/*
- * The function getGridValues sets the letter range used for 
- * the 2nd letter in the MGRS coordinate string, based on the set 
- * number of the utm zone. It also sets the pattern offset using a
- * value of A for the second letter of the grid square, based on 
- * the grid pattern and set number of the utm zone.
- *
- *    zone            : Zone number             (input)
- *    ltr2_low_value  : 2nd letter low number   (output)
- *    ltr2_high_value : 2nd letter high number  (output)
- *    pattern_offset  : Pattern offset          (output)
- */
-{ /* BEGIN Get_Grid_Values */
-  long set_number;    /* Set number (1-6) based on UTM zone number */
-  long aa_pattern;    /* Pattern based on ellipsoid code */
-
-  set_number = zone % 6;
-
-  if (!set_number)
-    set_number = 6;
-
-  if (!strcmp(MGRS_Ellipsoid_Code,CLARKE_1866) || !strcmp(MGRS_Ellipsoid_Code, CLARKE_1880) || 
-      !strcmp(MGRS_Ellipsoid_Code,BESSEL_1841) || !strcmp(MGRS_Ellipsoid_Code,BESSEL_1841_NAMIBIA))
-    aa_pattern = FALSE;
-  else
-    aa_pattern = TRUE;
-
-  if ((set_number == 1) || (set_number == 4))
-  {
-    *ltr2_low_value = LETTER_A;
-    *ltr2_high_value = LETTER_H;
-  }
-  else if ((set_number == 2) || (set_number == 5))
-  {
-    *ltr2_low_value = LETTER_J;
-    *ltr2_high_value = LETTER_R;
-  }
-  else if ((set_number == 3) || (set_number == 6))
-  {
-    *ltr2_low_value = LETTER_S;
-    *ltr2_high_value = LETTER_Z;
-  }
-
-  /* False northing at A for second letter of grid square */
-  if (aa_pattern)
-  {
-    if ((set_number % 2) ==  0)
-      *pattern_offset = 500000.0;
-    else
-      *pattern_offset = 0.0;
-  }
-  else
-  {
-    if ((set_number % 2) == 0)
-      *pattern_offset =  1500000.0;
-    else
-      *pattern_offset = 1000000.00;
-  }
-} /* END OF Get_Grid_Values */
-
-
-long UTM_To_MGRS (long Zone,
-                  char Hemisphere,
-                  double Longitude, 
-                  double Latitude,
-                  double Easting,
-                  double Northing,
-                  long Precision, 
-                  char *MGRS)
-/*
- * The function UTM_To_MGRS calculates an MGRS coordinate string
- * based on the zone, latitude, easting and northing.
- *
- *    Zone      : Zone number             (input)
- *    Hemisphere: Hemisphere              (input)
- *    Longitude : Longitude in radians    (input)
- *    Latitude  : Latitude in radians     (input)
- *    Easting   : Easting                 (input)
- *    Northing  : Northing                (input)
- *    Precision : Precision               (input)
- *    MGRS      : MGRS coordinate string  (output)
- */
-{ /* BEGIN UTM_To_MGRS */
-  double pattern_offset;      /* Northing offset for 3rd letter               */
-  double grid_easting;        /* Easting used to derive 2nd letter of MGRS   */
-  double grid_northing;       /* Northing used to derive 3rd letter of MGRS  */
-  long ltr2_low_value;        /* 2nd letter range - low number               */
-  long ltr2_high_value;       /* 2nd letter range - high number              */
-  int letters[MGRS_LETTERS];  /* Number location of 3 letters in alphabet    */
-  double divisor;
-  double rounded_easting;
-  long temp_error_code = MGRS_NO_ERROR;
-  long error_code = MGRS_NO_ERROR;
-
-	divisor = pow (10.0, (5 - Precision));
-	rounded_easting = Round_MGRS (Easting/divisor) * divisor;
-
-	/* Special check for rounding to (truncated) eastern edge of zone 31V */
-	if ((Zone == 31) && (((Latitude >= 56.0 * DEG_TO_RAD) && (Latitude < 64.0 * DEG_TO_RAD)) && ((Longitude >= 3.0 * DEG_TO_RAD) || (rounded_easting >= 500000.0))))
-	{ /* Reconvert to UTM zone 32 */
-    Set_UTM_Parameters (MGRS_a, MGRS_f, 32);
-    temp_error_code = Convert_Geodetic_To_UTM (Latitude, Longitude, &Zone, &Hemisphere, &Easting, &Northing);
-    if(temp_error_code)
-    {
-      if(temp_error_code & UTM_LAT_ERROR)
-        error_code |= MGRS_LAT_ERROR;
-      if(temp_error_code & UTM_LON_ERROR)
-        error_code |= MGRS_LON_ERROR;
-      if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-        error_code |= MGRS_ZONE_ERROR;
-      if(temp_error_code & UTM_EASTING_ERROR)
-        error_code |= MGRS_EASTING_ERROR;
-      if(temp_error_code & UTM_NORTHING_ERROR)
-        error_code |= MGRS_NORTHING_ERROR;
-
-      return error_code;
-    }
-    else
-	    /* Round easting value using new easting */
-	    Easting = Round_MGRS (Easting/divisor) * divisor;
-	}
-  else
-	  Easting = rounded_easting;
-
-	/* Round northing values */
-	Northing = Round_MGRS (Northing/divisor) * divisor;
-
-  if( Latitude <= 0.0 && Northing == 1.0e7)
-  {
-    Latitude = 0.0;
-    Northing = 0.0;
-  }
-
-  Get_Grid_Values(Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
-
-  error_code = Get_Latitude_Letter(Latitude, &letters[0]);
-   
-  if (!error_code)
-  {
-    grid_northing = Northing;
-
-    while (grid_northing >= TWOMIL)
-    {
-      grid_northing = grid_northing - TWOMIL; 
-    }
-    grid_northing = grid_northing + pattern_offset;
-    if(grid_northing >= TWOMIL)
-      grid_northing = grid_northing - TWOMIL;
-
-    letters[2] = (long)(grid_northing / ONEHT); 
-    if (letters[2] > LETTER_H)
-      letters[2] = letters[2] + 1;
-
-    if (letters[2] > LETTER_N)
-      letters[2] = letters[2] + 1;
-
-    grid_easting = Easting;
-    if (((letters[0] == LETTER_V) && (Zone == 31)) && (grid_easting == 500000.0))
-      grid_easting = grid_easting - 1.0; /* SUBTRACT 1 METER */
-
-    letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT) -1); 
-    if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N))
-      letters[1] = letters[1] + 1;
-
-    Make_MGRS_String (MGRS, Zone, letters, grid_easting, Northing, Precision);
-  }
-  return error_code;
-} /* END UTM_To_MGRS */
-
-
-long Set_MGRS_Parameters (double a,
-                          double f,
-                          char   *Ellipsoid_Code)
-/*
- * The function SET_MGRS_PARAMETERS receives the ellipsoid parameters and sets
- * the corresponding state variables. If any errors occur, the error code(s)
- * are returned by the function, otherwise MGRS_NO_ERROR is returned.
- *
- *   a                : Semi-major axis of ellipsoid in meters  (input)
- *   f                : Flattening of ellipsoid					        (input)
- *   Ellipsoid_Code   : 2-letter code for ellipsoid             (input)
- */
-{ /* Set_MGRS_Parameters  */
-
-  double inv_f = 1 / f;
-  long Error_Code = MGRS_NO_ERROR;
-
-  if (a <= 0.0)
-  { /* Semi-major axis must be greater than zero */
-    Error_Code |= MGRS_A_ERROR;
-  }
-  if ((inv_f < 250) || (inv_f > 350))
-  { /* Inverse flattening must be between 250 and 350 */
-    Error_Code |= MGRS_INV_F_ERROR;
-  }
-  if (!Error_Code)
-  { /* no errors */
-    MGRS_a = a;
-    MGRS_f = f;
-    strcpy (MGRS_Ellipsoid_Code, Ellipsoid_Code);
-  }
-  return (Error_Code);
-}  /* Set_MGRS_Parameters  */
-
-
-void Get_MGRS_Parameters (double *a,
-                          double *f,
-                          char* Ellipsoid_Code)
-/*
- * The function Get_MGRS_Parameters returns the current ellipsoid
- * parameters.
- *
- *  a                : Semi-major axis of ellipsoid, in meters (output)
- *  f                : Flattening of ellipsoid					       (output)
- *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
- */
-{ /* Get_MGRS_Parameters */
-  *a = MGRS_a;
-  *f = MGRS_f;
-  strcpy (Ellipsoid_Code, MGRS_Ellipsoid_Code);
-  return;
-} /* Get_MGRS_Parameters */
-
-
-long Convert_Geodetic_To_MGRS (double Latitude,
-                               double Longitude,
-                               long Precision,
-                               char* MGRS)
-/*
- * The function Convert_Geodetic_To_MGRS converts Geodetic (latitude and
- * longitude) coordinates to an MGRS coordinate string, according to the 
- * current ellipsoid parameters.  If any errors occur, the error code(s) 
- * are returned by the function, otherwise MGRS_NO_ERROR is returned.
- *
- *    Latitude   : Latitude in radians              (input)
- *    Longitude  : Longitude in radians             (input)
- *    Precision  : Precision level of MGRS string   (input)
- *    MGRS       : MGRS coordinate string           (output)
- *  
- */
-{ /* Convert_Geodetic_To_MGRS */
-  long zone;
-  char hemisphere;
-  double easting;
-  double northing;
-  long temp_error_code = MGRS_NO_ERROR;
-  long error_code = MGRS_NO_ERROR;
-
-  if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2))
-  { /* Latitude out of range */
-    error_code |= MGRS_LAT_ERROR;
-  }
-  if ((Longitude < -PI) || (Longitude > (2*PI)))
-  { /* Longitude out of range */
-    error_code |= MGRS_LON_ERROR;
-  }
-  if ((Precision < 0) || (Precision > MAX_PRECISION))
-    error_code |= MGRS_PRECISION_ERROR;
-  if (!error_code)
-  {
-    if ((Latitude < MIN_UTM_LAT) || (Latitude > MAX_UTM_LAT))
-    {
-      temp_error_code = Set_UPS_Parameters (MGRS_a, MGRS_f);
-      if(!temp_error_code)
-      {
-        temp_error_code = Convert_Geodetic_To_UPS (Latitude, Longitude, &hemisphere, &easting, &northing);
-        if(!temp_error_code)
-        {
-          error_code |= Convert_UPS_To_MGRS (hemisphere, easting, northing, Precision, MGRS);
-        }
-        else
-        {
-          if(temp_error_code & UPS_LAT_ERROR)
-            error_code |= MGRS_LAT_ERROR;
-          if(temp_error_code & UPS_LON_ERROR)
-            error_code |= MGRS_LON_ERROR;
-        }
-      }
-      else
-      {
-        if(temp_error_code & UPS_A_ERROR)
-          error_code |= MGRS_A_ERROR;
-        if(temp_error_code & UPS_INV_F_ERROR)
-          error_code |= MGRS_INV_F_ERROR;
-      }
-    }
-    else
-    {
-      temp_error_code = Set_UTM_Parameters (MGRS_a, MGRS_f, 0);
-      if(!temp_error_code)
-      {
-        temp_error_code = Convert_Geodetic_To_UTM (Latitude, Longitude, &zone, &hemisphere, &easting, &northing);
-        if(!temp_error_code)
-          error_code |= UTM_To_MGRS (zone, hemisphere, Longitude, Latitude, easting, northing, Precision, MGRS);
-        else
-        {
-          if(temp_error_code & UTM_LAT_ERROR)
-            error_code |= MGRS_LAT_ERROR;
-          if(temp_error_code & UTM_LON_ERROR)
-            error_code |= MGRS_LON_ERROR;
-          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-            error_code |= MGRS_ZONE_ERROR;
-          if(temp_error_code & UTM_EASTING_ERROR)
-            error_code |= MGRS_EASTING_ERROR;
-          if(temp_error_code & UTM_NORTHING_ERROR)
-            error_code |= MGRS_NORTHING_ERROR;
-        }
-      }
-      else
-      {
-        if(temp_error_code & UTM_A_ERROR)
-          error_code |= MGRS_A_ERROR;
-        if(temp_error_code & UTM_INV_F_ERROR)
-          error_code |= MGRS_INV_F_ERROR;
-        if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-          error_code |= MGRS_ZONE_ERROR;
-      }
-    }
-  }
-  return (error_code);
-} /* Convert_Geodetic_To_MGRS */
-
-
-long Convert_MGRS_To_Geodetic (char* MGRS, 
-                               double *Latitude, 
-                               double *Longitude)
-/*
- * The function Convert_MGRS_To_Geodetic converts an MGRS coordinate string
- * to Geodetic (latitude and longitude) coordinates 
- * according to the current ellipsoid parameters.  If any errors occur, 
- * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
- * is returned.
- *
- *    MGRS       : MGRS coordinate string           (input)
- *    Latitude   : Latitude in radians              (output)
- *    Longitude  : Longitude in radians             (output)
- *  
- */
-{ /* Convert_MGRS_To_Geodetic */
-  long zone;
-  char hemisphere;
-  double easting;
-  double northing;
-  long zone_exists;
-  long temp_error_code = MGRS_NO_ERROR;
-  long error_code = MGRS_NO_ERROR;
-
-  error_code = Check_Zone(MGRS, &zone_exists);
-  if (!error_code)
-  {
-    if (zone_exists)
-    {
-      error_code |= Convert_MGRS_To_UTM (MGRS, &zone, &hemisphere, &easting, &northing);
-      if(!error_code || (error_code & MGRS_LAT_WARNING))
-      {
-        temp_error_code = Set_UTM_Parameters (MGRS_a, MGRS_f, 0);
-        if(!temp_error_code)
-        {
-          temp_error_code = Convert_UTM_To_Geodetic (zone, hemisphere, easting, northing, Latitude, Longitude);
-          if(temp_error_code)
-          {
-            if((temp_error_code & UTM_ZONE_ERROR) || (temp_error_code & UTM_HEMISPHERE_ERROR))
-              error_code |= MGRS_STRING_ERROR;
-            if(temp_error_code & UTM_EASTING_ERROR)
-              error_code |= MGRS_EASTING_ERROR;
-            if(temp_error_code & UTM_NORTHING_ERROR)
-              error_code |= MGRS_NORTHING_ERROR;
-          }
-        }
-        else
-        {
-          if(temp_error_code & UTM_A_ERROR)
-            error_code |= MGRS_A_ERROR;
-          if(temp_error_code & UTM_INV_F_ERROR)
-            error_code |= MGRS_INV_F_ERROR;
-          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-            error_code |= MGRS_ZONE_ERROR;
-        }
-      }
-    }
-    else
-    {
-      error_code |= Convert_MGRS_To_UPS (MGRS, &hemisphere, &easting, &northing);
-      if(!error_code)
-      {
-        temp_error_code = Set_UPS_Parameters (MGRS_a, MGRS_f);
-        if(!temp_error_code)
-        {
-          temp_error_code = Convert_UPS_To_Geodetic (hemisphere, easting, northing, Latitude, Longitude);
-          if(temp_error_code)
-          {
-            if(temp_error_code & UPS_HEMISPHERE_ERROR)
-              error_code |= MGRS_STRING_ERROR;
-            if(temp_error_code & UPS_EASTING_ERROR)
-              error_code |= MGRS_EASTING_ERROR;
-            if(temp_error_code & UPS_LAT_ERROR)
-              error_code |= MGRS_NORTHING_ERROR;
-          }
-        }
-        else
-        {
-          if(temp_error_code & UPS_A_ERROR)
-            error_code |= MGRS_A_ERROR;
-          if(temp_error_code & UPS_INV_F_ERROR)
-            error_code |= MGRS_INV_F_ERROR;
-        }
-      }
-    }
-  }
-  return (error_code);
-} /* END OF Convert_MGRS_To_Geodetic */
-
-
-long Convert_UTM_To_MGRS (long Zone,
-                          char Hemisphere,
-                          double Easting,
-                          double Northing,
-                          long Precision,
-                          char* MGRS)
-/*
- * The function Convert_UTM_To_MGRS converts UTM (zone, easting, and
- * northing) coordinates to an MGRS coordinate string, according to the 
- * current ellipsoid parameters.  If any errors occur, the error code(s) 
- * are returned by the function, otherwise MGRS_NO_ERROR is returned.
- *
- *    Zone       : UTM zone                         (input)
- *    Hemisphere : North or South hemisphere        (input)
- *    Easting    : Easting (X) in meters            (input)
- *    Northing   : Northing (Y) in meters           (input)
- *    Precision  : Precision level of MGRS string   (input)
- *    MGRS       : MGRS coordinate string           (output)
- */
-{ /* Convert_UTM_To_MGRS */
-  double latitude;           /* Latitude of UTM point */
-  double longitude;          /* Longitude of UTM point */
-  long utm_error_code = MGRS_NO_ERROR;
-  long error_code = MGRS_NO_ERROR;
-
-  if ((Zone < 1) || (Zone > 60))
-    error_code |= MGRS_ZONE_ERROR;
-  if ((Hemisphere != 'S') && (Hemisphere != 'N'))
-    error_code |= MGRS_HEMISPHERE_ERROR;
-  if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
-    error_code |= MGRS_EASTING_ERROR;
-  if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
-    error_code |= MGRS_NORTHING_ERROR;
-  if ((Precision < 0) || (Precision > MAX_PRECISION))
-    error_code |= MGRS_PRECISION_ERROR;
-  if (!error_code)
-  {
-    Set_UTM_Parameters (MGRS_a, MGRS_f, 0);
-    utm_error_code = Convert_UTM_To_Geodetic (Zone, Hemisphere, Easting, Northing, &latitude, &longitude);
-    if(utm_error_code)
-    {
-      if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
-        error_code |= MGRS_STRING_ERROR;
-      if(utm_error_code & UTM_EASTING_ERROR)
-        error_code |= MGRS_EASTING_ERROR;
-      if(utm_error_code & UTM_NORTHING_ERROR)
-        error_code |= MGRS_NORTHING_ERROR;
-    }
-
-	  error_code = UTM_To_MGRS (Zone, Hemisphere, longitude, latitude, Easting, Northing, Precision, MGRS);
-  }
-  return (error_code);
-} /* Convert_UTM_To_MGRS */
-
-
-long Convert_MGRS_To_UTM (char   *MGRS,
-                          long   *Zone,
-                          char   *Hemisphere,
-                          double *Easting,
-                          double *Northing)
-/*
- * The function Convert_MGRS_To_UTM converts an MGRS coordinate string
- * to UTM projection (zone, hemisphere, easting and northing) coordinates 
- * according to the current ellipsoid parameters.  If any errors occur, 
- * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
- * is returned.
- *
- *    MGRS       : MGRS coordinate string           (input)
- *    Zone       : UTM zone                         (output)
- *    Hemisphere : North or South hemisphere        (output)
- *    Easting    : Easting (X) in meters            (output)
- *    Northing   : Northing (Y) in meters           (output)
- */
-{ /* Convert_MGRS_To_UTM */
-  double min_northing;
-  double northing_offset;
-  long ltr2_low_value;
-  long ltr2_high_value;
-  double pattern_offset;
-  double upper_lat_limit;     /* North latitude limits based on 1st letter  */
-  double lower_lat_limit;     /* South latitude limits based on 1st letter  */
-  double grid_easting;        /* Easting for 100,000 meter grid square      */
-  double grid_northing;       /* Northing for 100,000 meter grid square     */
-  long letters[MGRS_LETTERS];
-  long in_precision;
-  double latitude = 0.0;
-  double longitude = 0.0;
-  double divisor = 1.0;
-  long utm_error_code = MGRS_NO_ERROR;
-  long error_code = MGRS_NO_ERROR;
-
-  error_code = Break_MGRS_String (MGRS, Zone, letters, Easting, Northing, &in_precision);
-  if (!*Zone)
-    error_code |= MGRS_STRING_ERROR;
-  else
-  {
-    if (!error_code)
-    {
-      if ((letters[0] == LETTER_X) && ((*Zone == 32) || (*Zone == 34) || (*Zone == 36)))
-        error_code |= MGRS_STRING_ERROR;
-      else
-      {
-        if (letters[0] < LETTER_N)
-          *Hemisphere = 'S';
-        else
-          *Hemisphere = 'N';
-
-        Get_Grid_Values(*Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
-
-        /* Check that the second letter of the MGRS string is within
-         * the range of valid second letter values 
-         * Also check that the third letter is valid */
-        if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || (letters[2] > LETTER_V))
-          error_code |= MGRS_STRING_ERROR;
-
-        if (!error_code)
-        {
-          double row_letter_northing = (double)(letters[2]) * ONEHT;
-          grid_easting = (double)((letters[1]) - ltr2_low_value + 1) * ONEHT;
-          if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_O))
-            grid_easting = grid_easting - ONEHT;
-
-          if (letters[2] > LETTER_O)
-            row_letter_northing = row_letter_northing - ONEHT;
-
-          if (letters[2] > LETTER_I)
-            row_letter_northing = row_letter_northing - ONEHT; 
-
-          if (row_letter_northing >= TWOMIL)
-            row_letter_northing = row_letter_northing - TWOMIL;
-
-          error_code = Get_Latitude_Band_Min_Northing(letters[0], &min_northing, &northing_offset);
-          if (!error_code)
-          {
-            grid_northing = row_letter_northing - pattern_offset;
-            if(grid_northing < 0)
-              grid_northing += TWOMIL;
-            
-            grid_northing += northing_offset;
-
-            if(grid_northing < min_northing)
-              grid_northing += TWOMIL;
-
-            *Easting = grid_easting + *Easting;
-            *Northing = grid_northing + *Northing;
-
-            /* check that point is within Zone Letter bounds */
-            utm_error_code = Set_UTM_Parameters(MGRS_a,MGRS_f,0);
-            if (!utm_error_code)
-            {
-              utm_error_code = Convert_UTM_To_Geodetic(*Zone,*Hemisphere,*Easting,*Northing,&latitude,&longitude);
-              if (!utm_error_code)
-              {
-                divisor = pow (10.0, in_precision);
-                error_code = Get_Latitude_Range(letters[0], &upper_lat_limit, &lower_lat_limit);
-                if (!error_code)
-                {
-                  if (!(((lower_lat_limit - DEG_TO_RAD/divisor) <= latitude) && (latitude <= (upper_lat_limit + DEG_TO_RAD/divisor))))
-                    error_code |= MGRS_LAT_WARNING;
-                }
-              }
-              else
-              {
-                if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
-                  error_code |= MGRS_STRING_ERROR;
-                if(utm_error_code & UTM_EASTING_ERROR)
-                  error_code |= MGRS_EASTING_ERROR;
-                if(utm_error_code & UTM_NORTHING_ERROR)
-                  error_code |= MGRS_NORTHING_ERROR;
-              }
-            }
-            else
-            {
-              if(utm_error_code & UTM_A_ERROR)
-                error_code |= MGRS_A_ERROR;
-              if(utm_error_code & UTM_INV_F_ERROR)
-                error_code |= MGRS_INV_F_ERROR;
-              if(utm_error_code & UTM_ZONE_OVERRIDE_ERROR)
-                error_code |= MGRS_ZONE_ERROR;
-            }
-          }
-        }
-      }
-    }
-  }
-  return (error_code);
-} /* Convert_MGRS_To_UTM */
-
-
-long Convert_UPS_To_MGRS (char   Hemisphere,
-                          double Easting,
-                          double Northing,
-                          long   Precision,
-                          char*  MGRS)
-/*
- *  The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, 
- *  and northing) coordinates to an MGRS coordinate string according to 
- *  the current ellipsoid parameters.  If any errors occur, the error
- *  code(s) are returned by the function, otherwise UPS_NO_ERROR is 
- *  returned.
- *
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
- *    Easting       : Easting/X in meters              (input)
- *    Northing      : Northing/Y in meters             (input)
- *    Precision     : Precision level of MGRS string   (input)
- *    MGRS          : MGRS coordinate string           (output)
- */
-{ /* Convert_UPS_To_MGRS */
-  double false_easting;       /* False easting for 2nd letter                 */
-  double false_northing;      /* False northing for 3rd letter                */
-  double grid_easting;        /* Easting used to derive 2nd letter of MGRS    */
-  double grid_northing;       /* Northing used to derive 3rd letter of MGRS   */
-  long ltr2_low_value;        /* 2nd letter range - low number                */
-  int letters[MGRS_LETTERS];  /* Number location of 3 letters in alphabet     */
-  double divisor;
-  int index = 0;
-  long error_code = MGRS_NO_ERROR;
-
-  if ((Hemisphere != 'N') && (Hemisphere != 'S'))
-    error_code |= MGRS_HEMISPHERE_ERROR;
-  if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH))
-    error_code |= MGRS_EASTING_ERROR;
-  if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH))
-    error_code |= MGRS_NORTHING_ERROR;
-  if ((Precision < 0) || (Precision > MAX_PRECISION))
-    error_code |= MGRS_PRECISION_ERROR;
-  if (!error_code)
-  {
-    divisor = pow (10.0, (5 - Precision));
-    Easting = Round_MGRS (Easting/divisor) * divisor;
-    Northing = Round_MGRS (Northing/divisor) * divisor;
-
-    if (Hemisphere == 'N')
-    {
-      if (Easting >= TWOMIL)
-        letters[0] = LETTER_Z; 
-      else
-        letters[0] = LETTER_Y;
-
-      index = letters[0] - 22;
-      ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
-      false_easting = UPS_Constant_Table[index].false_easting;
-      false_northing = UPS_Constant_Table[index].false_northing;
-    }
-    else
-    {
-      if (Easting >= TWOMIL)
-        letters[0] = LETTER_B;
-      else
-        letters[0] = LETTER_A;
-
-      ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
-      false_easting = UPS_Constant_Table[letters[0]].false_easting;
-      false_northing = UPS_Constant_Table[letters[0]].false_northing;
-    }
-
-    grid_northing = Northing;
-    grid_northing = grid_northing - false_northing;
-    letters[2] = (long)(grid_northing / ONEHT);
-
-    if (letters[2] > LETTER_H)
-      letters[2] = letters[2] + 1;
-
-    if (letters[2] > LETTER_N)
-      letters[2] = letters[2] + 1;
-
-    grid_easting = Easting;
-    grid_easting = grid_easting - false_easting;
-    letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT)); 
-
-    if (Easting < TWOMIL)
-    {
-      if (letters[1] > LETTER_L)
-        letters[1] = letters[1] + 3; 
-
-      if (letters[1] > LETTER_U)
-        letters[1] = letters[1] + 2; 
-    }
-    else
-    {
-      if (letters[1] > LETTER_C)
-        letters[1] = letters[1] + 2; 
-
-      if (letters[1] > LETTER_H)
-        letters[1] = letters[1] + 1;
-      
-      if (letters[1] > LETTER_L)
-        letters[1] = letters[1] + 3; 
-    }
-
-    Make_MGRS_String (MGRS, 0, letters, Easting, Northing, Precision);
-  }
-  return (error_code);
-} /* Convert_UPS_To_MGRS */
-
-
-long Convert_MGRS_To_UPS ( char   *MGRS,
-                           char   *Hemisphere,
-                           double *Easting,
-                           double *Northing)
-/*
- *  The function Convert_MGRS_To_UPS converts an MGRS coordinate string
- *  to UPS (hemisphere, easting, and northing) coordinates, according 
- *  to the current ellipsoid parameters. If any errors occur, the error 
- *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
- *
- *    MGRS          : MGRS coordinate string           (input)
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
- *    Easting       : Easting/X in meters              (output)
- *    Northing      : Northing/Y in meters             (output)
- */
-{ /* Convert_MGRS_To_UPS */
-  long ltr2_high_value;       /* 2nd letter range - high number             */
-  long ltr3_high_value;       /* 3rd letter range - high number (UPS)       */
-  long ltr2_low_value;        /* 2nd letter range - low number              */
-  double false_easting;       /* False easting for 2nd letter               */
-  double false_northing;      /* False northing for 3rd letter              */
-  double grid_easting;        /* easting for 100,000 meter grid square      */
-  double grid_northing;       /* northing for 100,000 meter grid square     */
-  long zone;
-  long letters[MGRS_LETTERS];
-  long in_precision;
-  int index = 0;
-  long error_code = MGRS_NO_ERROR;
-
-  error_code = Break_MGRS_String (MGRS, &zone, letters, Easting, Northing, &in_precision);
-  if (zone)
-    error_code |= MGRS_STRING_ERROR;
-  else
-  {
-    if (!error_code)
-    {
-      if (letters[0] >= LETTER_Y)
-      {
-        *Hemisphere = 'N';
-
-        index = letters[0] - 22;
-        ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
-        ltr2_high_value = UPS_Constant_Table[index].ltr2_high_value;
-        ltr3_high_value = UPS_Constant_Table[index].ltr3_high_value;
-        false_easting = UPS_Constant_Table[index].false_easting;
-        false_northing = UPS_Constant_Table[index].false_northing;
-      }
-      else
-      {
-        *Hemisphere = 'S';
-
-        ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
-        ltr2_high_value = UPS_Constant_Table[letters[0]].ltr2_high_value;
-        ltr3_high_value = UPS_Constant_Table[letters[0]].ltr3_high_value;
-        false_easting = UPS_Constant_Table[letters[0]].false_easting;
-        false_northing = UPS_Constant_Table[letters[0]].false_northing;
-      }
-
-      /* Check that the second letter of the MGRS string is within
-       * the range of valid second letter values 
-       * Also check that the third letter is valid */
-      if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) ||
-          ((letters[1] == LETTER_D) || (letters[1] == LETTER_E) ||
-          (letters[1] == LETTER_M) || (letters[1] == LETTER_N) ||
-          (letters[1] == LETTER_V) || (letters[1] == LETTER_W)) ||
-          (letters[2] > ltr3_high_value))
-          error_code |= MGRS_STRING_ERROR;
-
-      if (!error_code)
-      {
-        grid_northing = (double)letters[2] * ONEHT + false_northing; 
-        if (letters[2] > LETTER_I)
-          grid_northing = grid_northing - ONEHT;
-
-        if (letters[2] > LETTER_O)
-          grid_northing = grid_northing - ONEHT;
-
-        grid_easting = (double)((letters[1]) - ltr2_low_value) * ONEHT + false_easting; 
-        if (ltr2_low_value != LETTER_A)
-        {
-          if (letters[1] > LETTER_L)
-            grid_easting = grid_easting - 300000.0;
-
-          if (letters[1] > LETTER_U)
-            grid_easting = grid_easting - 200000.0;
-        }
-        else
-        {
-          if (letters[1] > LETTER_C)
-            grid_easting = grid_easting - 200000.0;
-
-          if (letters[1] > LETTER_I)
-            grid_easting = grid_easting - ONEHT;
-
-          if (letters[1] > LETTER_L)
-            grid_easting = grid_easting - 300000.0;
-        }
-
-        *Easting = grid_easting + *Easting;
-        *Northing = grid_northing + *Northing;
-      }
-    }
-  }
-  return (error_code);
-} /* Convert_MGRS_To_UPS */
-
-
-
+/***************************************************************************/
+/* RSC IDENTIFIER:  MGRS
+ *
+ * ABSTRACT
+ *
+ *    This component converts between geodetic coordinates (latitude and 
+ *    longitude) and Military Grid Reference System (MGRS) coordinates. 
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found, the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *          MGRS_NO_ERROR          : No errors occurred in function
+ *          MGRS_LAT_ERROR         : Latitude outside of valid range 
+ *                                    (-90 to 90 degrees)
+ *          MGRS_LON_ERROR         : Longitude outside of valid range
+ *                                    (-180 to 360 degrees)
+ *          MGRS_STR_ERROR         : An MGRS string error: string too long,
+ *                                    too short, or badly formed
+ *          MGRS_PRECISION_ERROR   : The precision must be between 0 and 5 
+ *                                    inclusive.
+ *          MGRS_A_ERROR           : Semi-major axis less than or equal to zero
+ *          MGRS_INV_F_ERROR       : Inverse flattening outside of valid range
+ *									                  (250 to 350)
+ *          MGRS_EASTING_ERROR     : Easting outside of valid range
+ *                                    (100,000 to 900,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          MGRS_NORTHING_ERROR    : Northing outside of valid range
+ *                                    (0 to 10,000,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          MGRS_ZONE_ERROR        : Zone outside of valid range (1 to 60)
+ *          MGRS_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
+ *
+ * REUSE NOTES
+ *
+ *    MGRS is intended for reuse by any application that does conversions
+ *    between geodetic coordinates and MGRS coordinates.
+ *
+ * REFERENCES
+ *
+ *    Further information on MGRS can be found in the Reuse Manual.
+ *
+ *    MGRS originated from : U.S. Army Topographic Engineering Center
+ *                           Geospatial Information Division
+ *                           7701 Telegraph Road
+ *                           Alexandria, VA  22310-3864
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    MGRS was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC version 2.8.1
+ *    2. Windows 95 with MS Visual C++ version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    16-11-94          Original Code
+ *    15-09-99          Reengineered upper layers
+ *    02-05-03          Corrected latitude band bug in GRID_UTM 
+ *    08-20-03          Reengineered lower layers
+ */
+
+
+/***************************************************************************/
+/*
+ *                               INCLUDES
+ */
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include "ups.h"
+#include "utm.h"
+#include "mgrs.h"
+
+/*
+ *      ctype.h     - Standard C character handling library
+ *      math.h      - Standard C math library
+ *      stdio.h     - Standard C input/output library
+ *      string.h    - Standard C string handling library
+ *      ups.h       - Universal Polar Stereographic (UPS) projection
+ *      utm.h       - Universal Transverse Mercator (UTM) projection
+ *      mgrs.h      - function prototype error checking
+ */
+
+
+/***************************************************************************/
+/*
+ *                              GLOBAL DECLARATIONS
+ */
+#define DEG_TO_RAD       0.017453292519943295 /* PI/180                      */
+#define RAD_TO_DEG       57.29577951308232087 /* 180/PI                      */
+#define LETTER_A               0   /* ARRAY INDEX FOR LETTER A               */
+#define LETTER_B               1   /* ARRAY INDEX FOR LETTER B               */
+#define LETTER_C               2   /* ARRAY INDEX FOR LETTER C               */
+#define LETTER_D               3   /* ARRAY INDEX FOR LETTER D               */
+#define LETTER_E               4   /* ARRAY INDEX FOR LETTER E               */
+#define LETTER_F               5   /* ARRAY INDEX FOR LETTER F               */
+#define LETTER_G               6   /* ARRAY INDEX FOR LETTER G               */
+#define LETTER_H               7   /* ARRAY INDEX FOR LETTER H               */
+#define LETTER_I               8   /* ARRAY INDEX FOR LETTER I               */
+#define LETTER_J               9   /* ARRAY INDEX FOR LETTER J               */
+#define LETTER_K              10   /* ARRAY INDEX FOR LETTER K               */
+#define LETTER_L              11   /* ARRAY INDEX FOR LETTER L               */
+#define LETTER_M              12   /* ARRAY INDEX FOR LETTER M               */
+#define LETTER_N              13   /* ARRAY INDEX FOR LETTER N               */
+#define LETTER_O              14   /* ARRAY INDEX FOR LETTER O               */
+#define LETTER_P              15   /* ARRAY INDEX FOR LETTER P               */
+#define LETTER_Q              16   /* ARRAY INDEX FOR LETTER Q               */
+#define LETTER_R              17   /* ARRAY INDEX FOR LETTER R               */
+#define LETTER_S              18   /* ARRAY INDEX FOR LETTER S               */
+#define LETTER_T              19   /* ARRAY INDEX FOR LETTER T               */
+#define LETTER_U              20   /* ARRAY INDEX FOR LETTER U               */
+#define LETTER_V              21   /* ARRAY INDEX FOR LETTER V               */
+#define LETTER_W              22   /* ARRAY INDEX FOR LETTER W               */
+#define LETTER_X              23   /* ARRAY INDEX FOR LETTER X               */
+#define LETTER_Y              24   /* ARRAY INDEX FOR LETTER Y               */
+#define LETTER_Z              25   /* ARRAY INDEX FOR LETTER Z               */
+#define MGRS_LETTERS            3  /* NUMBER OF LETTERS IN MGRS              */
+#define ONEHT          100000.e0    /* ONE HUNDRED THOUSAND                  */
+#define TWOMIL        2000000.e0    /* TWO MILLION                           */
+#define TRUE                      1  /* CONSTANT VALUE FOR TRUE VALUE  */
+#define FALSE                     0  /* CONSTANT VALUE FOR FALSE VALUE */
+#define PI    3.14159265358979323e0  /* PI                             */
+#define PI_OVER_2  (PI / 2.0e0)
+
+#define MIN_EASTING  100000
+#define MAX_EASTING  900000
+#define MIN_NORTHING 0
+#define MAX_NORTHING 10000000
+#define MAX_PRECISION           5   /* Maximum precision of easting & northing */
+#define MIN_UTM_LAT      ( (-80 * PI) / 180.0 ) /* -80 degrees in radians    */
+#define MAX_UTM_LAT      ( (84 * PI) / 180.0 )  /* 84 degrees in radians     */
+
+#define MIN_EAST_NORTH 0
+#define MAX_EAST_NORTH 4000000
+
+
+/* Ellipsoid parameters, default to WGS 84 */
+double MGRS_a = 6378137.0;    /* Semi-major axis of ellipsoid in meters */
+double MGRS_f = 1 / 298.257223563; /* Flattening of ellipsoid           */
+char   MGRS_Ellipsoid_Code[3] = {'W','E',0};
+
+
+/* 
+ *    CLARKE_1866 : Ellipsoid code for CLARKE_1866
+ *    CLARKE_1880 : Ellipsoid code for CLARKE_1880
+ *    BESSEL_1841 : Ellipsoid code for BESSEL_1841
+ *    BESSEL_1841_NAMIBIA : Ellipsoid code for BESSEL 1841 (NAMIBIA)
+ */
+const char* CLARKE_1866 = "CC";
+const char* CLARKE_1880 = "CD";
+const char* BESSEL_1841 = "BR";
+const char* BESSEL_1841_NAMIBIA = "BN";
+
+
+typedef struct Latitude_Band_Value
+{
+  long letter;            /* letter representing latitude band  */
+  double min_northing;    /* minimum northing for latitude band */       
+  double north;           /* upper latitude for latitude band   */
+  double south;           /* lower latitude for latitude band   */
+  double northing_offset; /* latitude band northing offset      */
+} Latitude_Band; 
+
+static const Latitude_Band Latitude_Band_Table[20] = 
+  {{LETTER_C, 1100000.0, -72.0, -80.5, 0.0}, 
+  {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0},
+  {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0},
+  {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0},
+  {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0},
+  {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0},
+  {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0},
+  {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0},
+  {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0},
+  {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0},
+  {LETTER_N, 0.0, 8.0, 0.0, 0.0},
+  {LETTER_P, 800000.0, 16.0, 8.0, 0.0},
+  {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0},
+  {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0},
+  {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0},
+  {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0},
+  {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0},
+  {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0},
+  {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0},
+  {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}};
+
+ 
+typedef struct UPS_Constant_Value
+{
+  long letter;            /* letter representing latitude band      */
+  long ltr2_low_value;    /* 2nd letter range - low number         */
+  long ltr2_high_value;   /* 2nd letter range - high number          */
+  long ltr3_high_value;   /* 3rd letter range - high number (UPS)   */
+  double false_easting;   /* False easting based on 2nd letter      */
+  double false_northing;  /* False northing based on 3rd letter     */
+} UPS_Constant; 
+
+static const UPS_Constant UPS_Constant_Table[4] = 
+  {{LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000.0, 800000.0},
+  {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000.0, 800000.0},
+  {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000.0, 1300000.0},
+  {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000.0, 1300000.0}};
+
+/***************************************************************************/
+/*
+ *                              FUNCTIONS     
+ */
+
+long Get_Latitude_Band_Min_Northing(long letter, double* min_northing, double* northing_offset)
+/*
+ * The function Get_Latitude_Band_Min_Northing receives a latitude band letter
+ * and uses the Latitude_Band_Table to determine the minimum northing and northing offset
+ * for that latitude band letter.
+ *
+ *   letter        : Latitude band letter             (input)
+ *   min_northing  : Minimum northing for that letter	(output)
+ */
+{ /* Get_Latitude_Band_Min_Northing */
+  long error_code = MGRS_NO_ERROR;
+
+  if ((letter >= LETTER_C) && (letter <= LETTER_H))
+  {
+    *min_northing = Latitude_Band_Table[letter-2].min_northing;
+    *northing_offset = Latitude_Band_Table[letter-2].northing_offset;
+  }
+  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
+  {
+    *min_northing = Latitude_Band_Table[letter-3].min_northing;
+    *northing_offset = Latitude_Band_Table[letter-3].northing_offset;
+  }
+  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
+  {
+    *min_northing = Latitude_Band_Table[letter-4].min_northing;
+    *northing_offset = Latitude_Band_Table[letter-4].northing_offset;
+  }
+  else
+    error_code |= MGRS_STRING_ERROR;
+
+  return error_code;
+} /* Get_Latitude_Band_Min_Northing */
+
+
+long Get_Latitude_Range(long letter, double* north, double* south)
+/*
+ * The function Get_Latitude_Range receives a latitude band letter
+ * and uses the Latitude_Band_Table to determine the latitude band 
+ * boundaries for that latitude band letter.
+ *
+ *   letter   : Latitude band letter                        (input)
+ *   north    : Northern latitude boundary for that letter	(output)
+ *   north    : Southern latitude boundary for that letter	(output)
+ */
+{ /* Get_Latitude_Range */
+  long error_code = MGRS_NO_ERROR;
+
+  if ((letter >= LETTER_C) && (letter <= LETTER_H))
+  {
+    *north = Latitude_Band_Table[letter-2].north * DEG_TO_RAD;
+    *south = Latitude_Band_Table[letter-2].south * DEG_TO_RAD;
+  }
+  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
+  {
+    *north = Latitude_Band_Table[letter-3].north * DEG_TO_RAD;
+    *south = Latitude_Band_Table[letter-3].south * DEG_TO_RAD;
+  }
+  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
+  {
+    *north = Latitude_Band_Table[letter-4].north * DEG_TO_RAD;
+    *south = Latitude_Band_Table[letter-4].south * DEG_TO_RAD;
+  }
+  else
+    error_code |= MGRS_STRING_ERROR;
+
+  return error_code;
+} /* Get_Latitude_Range */
+
+
+long Get_Latitude_Letter(double latitude, int* letter)
+/*
+ * The function Get_Latitude_Letter receives a latitude value
+ * and uses the Latitude_Band_Table to determine the latitude band 
+ * letter for that latitude.
+ *
+ *   latitude   : Latitude              (input)
+ *   letter     : Latitude band letter  (output)
+ */
+{ /* Get_Latitude_Letter */
+  double temp = 0.0;
+  long error_code = MGRS_NO_ERROR;
+  double lat_deg = latitude * RAD_TO_DEG;
+
+  if (lat_deg >= 72 && lat_deg < 84.5)
+    *letter = LETTER_X;
+  else if (lat_deg > -80.5 && lat_deg < 72)
+  {
+    temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12;
+    *letter = Latitude_Band_Table[(int)temp].letter;
+  }
+  else
+    error_code |= MGRS_LAT_ERROR;
+
+  return error_code;
+} /* Get_Latitude_Letter */
+
+
+long Check_Zone(char* MGRS, long* zone_exists)
+/*
+ * The function Check_Zone receives an MGRS coordinate string.
+ * If a zone is given, TRUE is returned. Otherwise, FALSE
+ * is returned.
+ *
+ *   MGRS           : MGRS coordinate string        (input)
+ *   zone_exists    : TRUE if a zone is given, 
+ *                    FALSE if a zone is not given  (output)
+ */
+{ /* Check_Zone */
+  int i = 0;
+  int j = 0;
+  int num_digits = 0;
+  long error_code = MGRS_NO_ERROR;
+
+  /* skip any leading blanks */  
+  while (MGRS[i] == ' ')
+    i++;  
+  j = i;
+  while (isdigit(MGRS[i]))
+    i++;
+  num_digits = i - j;
+  if (num_digits <= 2)
+    if (num_digits > 0)
+      *zone_exists = TRUE;
+    else
+      *zone_exists = FALSE;
+  else
+    error_code |= MGRS_STRING_ERROR;
+
+  return error_code;
+} /* Check_Zone */
+
+
+long Round_MGRS (double value)
+/*
+ * The function Round_MGRS rounds the input value to the 
+ * nearest integer, using the standard engineering rule.
+ * The rounded integer value is then returned.
+ *
+ *   value           : Value to be rounded  (input)
+ */
+{ /* Round_MGRS */
+  double ivalue;
+  long ival;
+  double fraction = modf (value, &ivalue);
+  ival = (long)(ivalue);
+  if ((fraction > 0.5) || ((fraction == 0.5) && (ival%2 == 1)))
+    ival++;
+  return (ival);
+} /* Round_MGRS */
+
+
+long Make_MGRS_String (char* MGRS, 
+                       long Zone, 
+                       int Letters[MGRS_LETTERS], 
+                       double Easting, 
+                       double Northing,
+                       long Precision)
+/*
+ * The function Make_MGRS_String constructs an MGRS string 
+ * from its component parts.
+ *
+ *   MGRS           : MGRS coordinate string          (output)
+ *   Zone           : UTM Zone                        (input)
+ *   Letters        : MGRS coordinate string letters  (input)
+ *   Easting        : Easting value                   (input)
+ *   Northing       : Northing value                  (input)
+ *   Precision      : Precision level of MGRS string  (input)
+ */
+{ /* Make_MGRS_String */
+  long i;
+  long j;
+  double divisor;
+  long east;
+  long north;
+  char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  long error_code = MGRS_NO_ERROR;
+
+  i = 0;
+  if (Zone)
+    i = sprintf (MGRS+i,"%2.2ld",Zone);
+  else
+    strncpy(MGRS, "  ", 2);  // 2 spaces
+
+  for (j=0;j<3;j++)
+    MGRS[i++] = alphabet[Letters[j]];
+  divisor = pow (10.0, (5 - Precision));
+  Easting = fmod (Easting, 100000.0);
+  if (Easting >= 99999.5)
+    Easting = 99999.0;
+  east = (long)(Easting/divisor);
+  i += sprintf (MGRS+i, "%*.*ld", (int)Precision, (int)Precision, east);
+  Northing = fmod (Northing, 100000.0);
+  if (Northing >= 99999.5)
+    Northing = 99999.0;
+  north = (long)(Northing/divisor);
+  i += sprintf (MGRS+i, "%*.*ld", (int)Precision, (int)Precision, north);
+  return (error_code);
+} /* Make_MGRS_String */
+
+
+long Break_MGRS_String (char* MGRS,
+                        long* Zone,
+                        long Letters[MGRS_LETTERS],
+                        double* Easting,
+                        double* Northing,
+                        long* Precision)
+/*
+ * The function Break_MGRS_String breaks down an MGRS  
+ * coordinate string into its component parts.
+ *
+ *   MGRS           : MGRS coordinate string          (input)
+ *   Zone           : UTM Zone                        (output)
+ *   Letters        : MGRS coordinate string letters  (output)
+ *   Easting        : Easting value                   (output)
+ *   Northing       : Northing value                  (output)
+ *   Precision      : Precision level of MGRS string  (output)
+ */
+{ /* Break_MGRS_String */
+  long num_digits;
+  long num_letters;
+  long i = 0;
+  long j = 0;
+  long error_code = MGRS_NO_ERROR;
+
+  while (MGRS[i] == ' ')
+    i++;  /* skip any leading blanks */
+  j = i;
+  while (isdigit(MGRS[i]))
+    i++;
+  num_digits = i - j;
+  if (num_digits <= 2)
+    if (num_digits > 0)
+    {
+      char zone_string[3];
+      /* get zone */
+      strncpy (zone_string, MGRS+j, 2);
+      zone_string[2] = 0;
+      sscanf (zone_string, "%ld", Zone);  
+      if ((*Zone < 1) || (*Zone > 60))
+        error_code |= MGRS_STRING_ERROR;
+    }
+    else
+      *Zone = 0;
+  else
+    error_code |= MGRS_STRING_ERROR;
+  j = i;
+
+  while (isalpha(MGRS[i]))
+    i++;
+  num_letters = i - j;
+  if (num_letters == 3)
+  {
+    /* get letters */
+    Letters[0] = (toupper(MGRS[j]) - (long)'A');
+    if ((Letters[0] == LETTER_I) || (Letters[0] == LETTER_O))
+      error_code |= MGRS_STRING_ERROR;
+    Letters[1] = (toupper(MGRS[j+1]) - (long)'A');
+    if ((Letters[1] == LETTER_I) || (Letters[1] == LETTER_O))
+      error_code |= MGRS_STRING_ERROR;
+    Letters[2] = (toupper(MGRS[j+2]) - (long)'A');
+    if ((Letters[2] == LETTER_I) || (Letters[2] == LETTER_O))
+      error_code |= MGRS_STRING_ERROR;
+  }
+  else
+    error_code |= MGRS_STRING_ERROR;
+  j = i;
+  while (isdigit(MGRS[i]))
+    i++;
+  num_digits = i - j;
+  if ((num_digits <= 10) && (num_digits%2 == 0))
+  {
+    long n;
+    char east_string[6];
+    char north_string[6];
+    long east;
+    long north;
+    double multiplier;
+    /* get easting & northing */
+    n = num_digits/2;
+    *Precision = n;
+    if (n > 0)
+    {
+      strncpy (east_string, MGRS+j, n);
+      east_string[n] = 0;
+      sscanf (east_string, "%ld", &east);
+      strncpy (north_string, MGRS+j+n, n);
+      north_string[n] = 0;
+      sscanf (north_string, "%ld", &north);
+      multiplier = pow (10.0, 5 - n);
+      *Easting = east * multiplier;
+      *Northing = north * multiplier;
+    }
+    else
+    {
+      *Easting = 0.0;
+      *Northing = 0.0;
+    }
+  }
+  else
+    error_code |= MGRS_STRING_ERROR;
+
+  return (error_code);
+} /* Break_MGRS_String */
+
+
+void Get_Grid_Values (long zone, 
+                      long* ltr2_low_value, 
+                      long* ltr2_high_value, 
+                      double *pattern_offset)
+/*
+ * The function getGridValues sets the letter range used for 
+ * the 2nd letter in the MGRS coordinate string, based on the set 
+ * number of the utm zone. It also sets the pattern offset using a
+ * value of A for the second letter of the grid square, based on 
+ * the grid pattern and set number of the utm zone.
+ *
+ *    zone            : Zone number             (input)
+ *    ltr2_low_value  : 2nd letter low number   (output)
+ *    ltr2_high_value : 2nd letter high number  (output)
+ *    pattern_offset  : Pattern offset          (output)
+ */
+{ /* BEGIN Get_Grid_Values */
+  long set_number;    /* Set number (1-6) based on UTM zone number */
+  long aa_pattern;    /* Pattern based on ellipsoid code */
+
+  set_number = zone % 6;
+
+  if (!set_number)
+    set_number = 6;
+
+  if (!strcmp(MGRS_Ellipsoid_Code,CLARKE_1866) || !strcmp(MGRS_Ellipsoid_Code, CLARKE_1880) || 
+      !strcmp(MGRS_Ellipsoid_Code,BESSEL_1841) || !strcmp(MGRS_Ellipsoid_Code,BESSEL_1841_NAMIBIA))
+    aa_pattern = FALSE;
+  else
+    aa_pattern = TRUE;
+
+  if ((set_number == 1) || (set_number == 4))
+  {
+    *ltr2_low_value = LETTER_A;
+    *ltr2_high_value = LETTER_H;
+  }
+  else if ((set_number == 2) || (set_number == 5))
+  {
+    *ltr2_low_value = LETTER_J;
+    *ltr2_high_value = LETTER_R;
+  }
+  else if ((set_number == 3) || (set_number == 6))
+  {
+    *ltr2_low_value = LETTER_S;
+    *ltr2_high_value = LETTER_Z;
+  }
+
+  /* False northing at A for second letter of grid square */
+  if (aa_pattern)
+  {
+    if ((set_number % 2) ==  0)
+      *pattern_offset = 500000.0;
+    else
+      *pattern_offset = 0.0;
+  }
+  else
+  {
+    if ((set_number % 2) == 0)
+      *pattern_offset =  1500000.0;
+    else
+      *pattern_offset = 1000000.00;
+  }
+} /* END OF Get_Grid_Values */
+
+
+long UTM_To_MGRS (long Zone,
+                  char Hemisphere,
+                  double Longitude, 
+                  double Latitude,
+                  double Easting,
+                  double Northing,
+                  long Precision, 
+                  char *MGRS)
+/*
+ * The function UTM_To_MGRS calculates an MGRS coordinate string
+ * based on the zone, latitude, easting and northing.
+ *
+ *    Zone      : Zone number             (input)
+ *    Hemisphere: Hemisphere              (input)
+ *    Longitude : Longitude in radians    (input)
+ *    Latitude  : Latitude in radians     (input)
+ *    Easting   : Easting                 (input)
+ *    Northing  : Northing                (input)
+ *    Precision : Precision               (input)
+ *    MGRS      : MGRS coordinate string  (output)
+ */
+{ /* BEGIN UTM_To_MGRS */
+  double pattern_offset;      /* Northing offset for 3rd letter               */
+  double grid_easting;        /* Easting used to derive 2nd letter of MGRS   */
+  double grid_northing;       /* Northing used to derive 3rd letter of MGRS  */
+  long ltr2_low_value;        /* 2nd letter range - low number               */
+  long ltr2_high_value;       /* 2nd letter range - high number              */
+  int letters[MGRS_LETTERS];  /* Number location of 3 letters in alphabet    */
+  double divisor;
+  double rounded_easting;
+  long temp_error_code = MGRS_NO_ERROR;
+  long error_code = MGRS_NO_ERROR;
+
+	divisor = pow (10.0, (5 - Precision));
+	rounded_easting = Round_MGRS (Easting/divisor) * divisor;
+
+	/* Special check for rounding to (truncated) eastern edge of zone 31V */
+	if ((Zone == 31) && (((Latitude >= 56.0 * DEG_TO_RAD) && (Latitude < 64.0 * DEG_TO_RAD)) && ((Longitude >= 3.0 * DEG_TO_RAD) || (rounded_easting >= 500000.0))))
+	{ /* Reconvert to UTM zone 32 */
+    Set_UTM_Parameters (MGRS_a, MGRS_f, 32);
+    temp_error_code = Convert_Geodetic_To_UTM (Latitude, Longitude, &Zone, &Hemisphere, &Easting, &Northing);
+    if(temp_error_code)
+    {
+      if(temp_error_code & UTM_LAT_ERROR)
+        error_code |= MGRS_LAT_ERROR;
+      if(temp_error_code & UTM_LON_ERROR)
+        error_code |= MGRS_LON_ERROR;
+      if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+        error_code |= MGRS_ZONE_ERROR;
+      if(temp_error_code & UTM_EASTING_ERROR)
+        error_code |= MGRS_EASTING_ERROR;
+      if(temp_error_code & UTM_NORTHING_ERROR)
+        error_code |= MGRS_NORTHING_ERROR;
+
+      return error_code;
+    }
+    else
+	    /* Round easting value using new easting */
+	    Easting = Round_MGRS (Easting/divisor) * divisor;
+	}
+  else
+	  Easting = rounded_easting;
+
+	/* Round northing values */
+	Northing = Round_MGRS (Northing/divisor) * divisor;
+
+  if( Latitude <= 0.0 && Northing == 1.0e7)
+  {
+    Latitude = 0.0;
+    Northing = 0.0;
+  }
+
+  Get_Grid_Values(Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
+
+  error_code = Get_Latitude_Letter(Latitude, &letters[0]);
+   
+  if (!error_code)
+  {
+    grid_northing = Northing;
+
+    while (grid_northing >= TWOMIL)
+    {
+      grid_northing = grid_northing - TWOMIL; 
+    }
+    grid_northing = grid_northing + pattern_offset;
+    if(grid_northing >= TWOMIL)
+      grid_northing = grid_northing - TWOMIL;
+
+    letters[2] = (long)(grid_northing / ONEHT); 
+    if (letters[2] > LETTER_H)
+      letters[2] = letters[2] + 1;
+
+    if (letters[2] > LETTER_N)
+      letters[2] = letters[2] + 1;
+
+    grid_easting = Easting;
+    if (((letters[0] == LETTER_V) && (Zone == 31)) && (grid_easting == 500000.0))
+      grid_easting = grid_easting - 1.0; /* SUBTRACT 1 METER */
+
+    letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT) -1); 
+    if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N))
+      letters[1] = letters[1] + 1;
+
+    Make_MGRS_String (MGRS, Zone, letters, grid_easting, Northing, Precision);
+  }
+  return error_code;
+} /* END UTM_To_MGRS */
+
+
+long Set_MGRS_Parameters (double a,
+                          double f,
+                          char   *Ellipsoid_Code)
+/*
+ * The function SET_MGRS_PARAMETERS receives the ellipsoid parameters and sets
+ * the corresponding state variables. If any errors occur, the error code(s)
+ * are returned by the function, otherwise MGRS_NO_ERROR is returned.
+ *
+ *   a                : Semi-major axis of ellipsoid in meters  (input)
+ *   f                : Flattening of ellipsoid					        (input)
+ *   Ellipsoid_Code   : 2-letter code for ellipsoid             (input)
+ */
+{ /* Set_MGRS_Parameters  */
+
+  double inv_f = 1 / f;
+  long Error_Code = MGRS_NO_ERROR;
+
+  if (a <= 0.0)
+  { /* Semi-major axis must be greater than zero */
+    Error_Code |= MGRS_A_ERROR;
+  }
+  if ((inv_f < 250) || (inv_f > 350))
+  { /* Inverse flattening must be between 250 and 350 */
+    Error_Code |= MGRS_INV_F_ERROR;
+  }
+  if (!Error_Code)
+  { /* no errors */
+    MGRS_a = a;
+    MGRS_f = f;
+    strcpy (MGRS_Ellipsoid_Code, Ellipsoid_Code);
+  }
+  return (Error_Code);
+}  /* Set_MGRS_Parameters  */
+
+
+void Get_MGRS_Parameters (double *a,
+                          double *f,
+                          char* Ellipsoid_Code)
+/*
+ * The function Get_MGRS_Parameters returns the current ellipsoid
+ * parameters.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters (output)
+ *  f                : Flattening of ellipsoid					       (output)
+ *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
+ */
+{ /* Get_MGRS_Parameters */
+  *a = MGRS_a;
+  *f = MGRS_f;
+  strcpy (Ellipsoid_Code, MGRS_Ellipsoid_Code);
+  return;
+} /* Get_MGRS_Parameters */
+
+
+long Convert_Geodetic_To_MGRS (double Latitude,
+                               double Longitude,
+                               long Precision,
+                               char* MGRS)
+/*
+ * The function Convert_Geodetic_To_MGRS converts Geodetic (latitude and
+ * longitude) coordinates to an MGRS coordinate string, according to the 
+ * current ellipsoid parameters.  If any errors occur, the error code(s) 
+ * are returned by the function, otherwise MGRS_NO_ERROR is returned.
+ *
+ *    Latitude   : Latitude in radians              (input)
+ *    Longitude  : Longitude in radians             (input)
+ *    Precision  : Precision level of MGRS string   (input)
+ *    MGRS       : MGRS coordinate string           (output)
+ *  
+ */
+{ /* Convert_Geodetic_To_MGRS */
+  long zone;
+  char hemisphere;
+  double easting;
+  double northing;
+  long temp_error_code = MGRS_NO_ERROR;
+  long error_code = MGRS_NO_ERROR;
+
+  if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2))
+  { /* Latitude out of range */
+    error_code |= MGRS_LAT_ERROR;
+  }
+  if ((Longitude < -PI) || (Longitude > (2*PI)))
+  { /* Longitude out of range */
+    error_code |= MGRS_LON_ERROR;
+  }
+  if ((Precision < 0) || (Precision > MAX_PRECISION))
+    error_code |= MGRS_PRECISION_ERROR;
+  if (!error_code)
+  {
+    if ((Latitude < MIN_UTM_LAT) || (Latitude > MAX_UTM_LAT))
+    {
+      temp_error_code = Set_UPS_Parameters (MGRS_a, MGRS_f);
+      if(!temp_error_code)
+      {
+        temp_error_code = Convert_Geodetic_To_UPS (Latitude, Longitude, &hemisphere, &easting, &northing);
+        if(!temp_error_code)
+        {
+          error_code |= Convert_UPS_To_MGRS (hemisphere, easting, northing, Precision, MGRS);
+        }
+        else
+        {
+          if(temp_error_code & UPS_LAT_ERROR)
+            error_code |= MGRS_LAT_ERROR;
+          if(temp_error_code & UPS_LON_ERROR)
+            error_code |= MGRS_LON_ERROR;
+        }
+      }
+      else
+      {
+        if(temp_error_code & UPS_A_ERROR)
+          error_code |= MGRS_A_ERROR;
+        if(temp_error_code & UPS_INV_F_ERROR)
+          error_code |= MGRS_INV_F_ERROR;
+      }
+    }
+    else
+    {
+      temp_error_code = Set_UTM_Parameters (MGRS_a, MGRS_f, 0);
+      if(!temp_error_code)
+      {
+        temp_error_code = Convert_Geodetic_To_UTM (Latitude, Longitude, &zone, &hemisphere, &easting, &northing);
+        if(!temp_error_code)
+          error_code |= UTM_To_MGRS (zone, hemisphere, Longitude, Latitude, easting, northing, Precision, MGRS);
+        else
+        {
+          if(temp_error_code & UTM_LAT_ERROR)
+            error_code |= MGRS_LAT_ERROR;
+          if(temp_error_code & UTM_LON_ERROR)
+            error_code |= MGRS_LON_ERROR;
+          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+            error_code |= MGRS_ZONE_ERROR;
+          if(temp_error_code & UTM_EASTING_ERROR)
+            error_code |= MGRS_EASTING_ERROR;
+          if(temp_error_code & UTM_NORTHING_ERROR)
+            error_code |= MGRS_NORTHING_ERROR;
+        }
+      }
+      else
+      {
+        if(temp_error_code & UTM_A_ERROR)
+          error_code |= MGRS_A_ERROR;
+        if(temp_error_code & UTM_INV_F_ERROR)
+          error_code |= MGRS_INV_F_ERROR;
+        if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+          error_code |= MGRS_ZONE_ERROR;
+      }
+    }
+  }
+  return (error_code);
+} /* Convert_Geodetic_To_MGRS */
+
+
+long Convert_MGRS_To_Geodetic (char* MGRS, 
+                               double *Latitude, 
+                               double *Longitude)
+/*
+ * The function Convert_MGRS_To_Geodetic converts an MGRS coordinate string
+ * to Geodetic (latitude and longitude) coordinates 
+ * according to the current ellipsoid parameters.  If any errors occur, 
+ * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
+ * is returned.
+ *
+ *    MGRS       : MGRS coordinate string           (input)
+ *    Latitude   : Latitude in radians              (output)
+ *    Longitude  : Longitude in radians             (output)
+ *  
+ */
+{ /* Convert_MGRS_To_Geodetic */
+  long zone;
+  char hemisphere;
+  double easting;
+  double northing;
+  long zone_exists;
+  long temp_error_code = MGRS_NO_ERROR;
+  long error_code = MGRS_NO_ERROR;
+
+  error_code = Check_Zone(MGRS, &zone_exists);
+  if (!error_code)
+  {
+    if (zone_exists)
+    {
+      error_code |= Convert_MGRS_To_UTM (MGRS, &zone, &hemisphere, &easting, &northing);
+      if(!error_code || (error_code & MGRS_LAT_WARNING))
+      {
+        temp_error_code = Set_UTM_Parameters (MGRS_a, MGRS_f, 0);
+        if(!temp_error_code)
+        {
+          temp_error_code = Convert_UTM_To_Geodetic (zone, hemisphere, easting, northing, Latitude, Longitude);
+          if(temp_error_code)
+          {
+            if((temp_error_code & UTM_ZONE_ERROR) || (temp_error_code & UTM_HEMISPHERE_ERROR))
+              error_code |= MGRS_STRING_ERROR;
+            if(temp_error_code & UTM_EASTING_ERROR)
+              error_code |= MGRS_EASTING_ERROR;
+            if(temp_error_code & UTM_NORTHING_ERROR)
+              error_code |= MGRS_NORTHING_ERROR;
+          }
+        }
+        else
+        {
+          if(temp_error_code & UTM_A_ERROR)
+            error_code |= MGRS_A_ERROR;
+          if(temp_error_code & UTM_INV_F_ERROR)
+            error_code |= MGRS_INV_F_ERROR;
+          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+            error_code |= MGRS_ZONE_ERROR;
+        }
+      }
+    }
+    else
+    {
+      error_code |= Convert_MGRS_To_UPS (MGRS, &hemisphere, &easting, &northing);
+      if(!error_code)
+      {
+        temp_error_code = Set_UPS_Parameters (MGRS_a, MGRS_f);
+        if(!temp_error_code)
+        {
+          temp_error_code = Convert_UPS_To_Geodetic (hemisphere, easting, northing, Latitude, Longitude);
+          if(temp_error_code)
+          {
+            if(temp_error_code & UPS_HEMISPHERE_ERROR)
+              error_code |= MGRS_STRING_ERROR;
+            if(temp_error_code & UPS_EASTING_ERROR)
+              error_code |= MGRS_EASTING_ERROR;
+            if(temp_error_code & UPS_LAT_ERROR)
+              error_code |= MGRS_NORTHING_ERROR;
+          }
+        }
+        else
+        {
+          if(temp_error_code & UPS_A_ERROR)
+            error_code |= MGRS_A_ERROR;
+          if(temp_error_code & UPS_INV_F_ERROR)
+            error_code |= MGRS_INV_F_ERROR;
+        }
+      }
+    }
+  }
+  return (error_code);
+} /* END OF Convert_MGRS_To_Geodetic */
+
+
+long Convert_UTM_To_MGRS (long Zone,
+                          char Hemisphere,
+                          double Easting,
+                          double Northing,
+                          long Precision,
+                          char* MGRS)
+/*
+ * The function Convert_UTM_To_MGRS converts UTM (zone, easting, and
+ * northing) coordinates to an MGRS coordinate string, according to the 
+ * current ellipsoid parameters.  If any errors occur, the error code(s) 
+ * are returned by the function, otherwise MGRS_NO_ERROR is returned.
+ *
+ *    Zone       : UTM zone                         (input)
+ *    Hemisphere : North or South hemisphere        (input)
+ *    Easting    : Easting (X) in meters            (input)
+ *    Northing   : Northing (Y) in meters           (input)
+ *    Precision  : Precision level of MGRS string   (input)
+ *    MGRS       : MGRS coordinate string           (output)
+ */
+{ /* Convert_UTM_To_MGRS */
+  double latitude;           /* Latitude of UTM point */
+  double longitude;          /* Longitude of UTM point */
+  long utm_error_code = MGRS_NO_ERROR;
+  long error_code = MGRS_NO_ERROR;
+
+  if ((Zone < 1) || (Zone > 60))
+    error_code |= MGRS_ZONE_ERROR;
+  if ((Hemisphere != 'S') && (Hemisphere != 'N'))
+    error_code |= MGRS_HEMISPHERE_ERROR;
+  if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
+    error_code |= MGRS_EASTING_ERROR;
+  if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
+    error_code |= MGRS_NORTHING_ERROR;
+  if ((Precision < 0) || (Precision > MAX_PRECISION))
+    error_code |= MGRS_PRECISION_ERROR;
+  if (!error_code)
+  {
+    Set_UTM_Parameters (MGRS_a, MGRS_f, 0);
+    utm_error_code = Convert_UTM_To_Geodetic (Zone, Hemisphere, Easting, Northing, &latitude, &longitude);
+    if(utm_error_code)
+    {
+      if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
+        error_code |= MGRS_STRING_ERROR;
+      if(utm_error_code & UTM_EASTING_ERROR)
+        error_code |= MGRS_EASTING_ERROR;
+      if(utm_error_code & UTM_NORTHING_ERROR)
+        error_code |= MGRS_NORTHING_ERROR;
+    }
+
+	  error_code = UTM_To_MGRS (Zone, Hemisphere, longitude, latitude, Easting, Northing, Precision, MGRS);
+  }
+  return (error_code);
+} /* Convert_UTM_To_MGRS */
+
+
+long Convert_MGRS_To_UTM (char   *MGRS,
+                          long   *Zone,
+                          char   *Hemisphere,
+                          double *Easting,
+                          double *Northing)
+/*
+ * The function Convert_MGRS_To_UTM converts an MGRS coordinate string
+ * to UTM projection (zone, hemisphere, easting and northing) coordinates 
+ * according to the current ellipsoid parameters.  If any errors occur, 
+ * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
+ * is returned.
+ *
+ *    MGRS       : MGRS coordinate string           (input)
+ *    Zone       : UTM zone                         (output)
+ *    Hemisphere : North or South hemisphere        (output)
+ *    Easting    : Easting (X) in meters            (output)
+ *    Northing   : Northing (Y) in meters           (output)
+ */
+{ /* Convert_MGRS_To_UTM */
+  double min_northing;
+  double northing_offset;
+  long ltr2_low_value;
+  long ltr2_high_value;
+  double pattern_offset;
+  double upper_lat_limit;     /* North latitude limits based on 1st letter  */
+  double lower_lat_limit;     /* South latitude limits based on 1st letter  */
+  double grid_easting;        /* Easting for 100,000 meter grid square      */
+  double grid_northing;       /* Northing for 100,000 meter grid square     */
+  long letters[MGRS_LETTERS];
+  long in_precision;
+  double latitude = 0.0;
+  double longitude = 0.0;
+  double divisor = 1.0;
+  long utm_error_code = MGRS_NO_ERROR;
+  long error_code = MGRS_NO_ERROR;
+
+  error_code = Break_MGRS_String (MGRS, Zone, letters, Easting, Northing, &in_precision);
+  if (!*Zone)
+    error_code |= MGRS_STRING_ERROR;
+  else
+  {
+    if (!error_code)
+    {
+      if ((letters[0] == LETTER_X) && ((*Zone == 32) || (*Zone == 34) || (*Zone == 36)))
+        error_code |= MGRS_STRING_ERROR;
+      else
+      {
+        if (letters[0] < LETTER_N)
+          *Hemisphere = 'S';
+        else
+          *Hemisphere = 'N';
+
+        Get_Grid_Values(*Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
+
+        /* Check that the second letter of the MGRS string is within
+         * the range of valid second letter values 
+         * Also check that the third letter is valid */
+        if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || (letters[2] > LETTER_V))
+          error_code |= MGRS_STRING_ERROR;
+
+        if (!error_code)
+        {
+          double row_letter_northing = (double)(letters[2]) * ONEHT;
+          grid_easting = (double)((letters[1]) - ltr2_low_value + 1) * ONEHT;
+          if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_O))
+            grid_easting = grid_easting - ONEHT;
+
+          if (letters[2] > LETTER_O)
+            row_letter_northing = row_letter_northing - ONEHT;
+
+          if (letters[2] > LETTER_I)
+            row_letter_northing = row_letter_northing - ONEHT; 
+
+          if (row_letter_northing >= TWOMIL)
+            row_letter_northing = row_letter_northing - TWOMIL;
+
+          error_code = Get_Latitude_Band_Min_Northing(letters[0], &min_northing, &northing_offset);
+          if (!error_code)
+          {
+            grid_northing = row_letter_northing - pattern_offset;
+            if(grid_northing < 0)
+              grid_northing += TWOMIL;
+            
+            grid_northing += northing_offset;
+
+            if(grid_northing < min_northing)
+              grid_northing += TWOMIL;
+
+            *Easting = grid_easting + *Easting;
+            *Northing = grid_northing + *Northing;
+
+            /* check that point is within Zone Letter bounds */
+            utm_error_code = Set_UTM_Parameters(MGRS_a,MGRS_f,0);
+            if (!utm_error_code)
+            {
+              utm_error_code = Convert_UTM_To_Geodetic(*Zone,*Hemisphere,*Easting,*Northing,&latitude,&longitude);
+              if (!utm_error_code)
+              {
+                divisor = pow (10.0, in_precision);
+                error_code = Get_Latitude_Range(letters[0], &upper_lat_limit, &lower_lat_limit);
+                if (!error_code)
+                {
+                  if (!(((lower_lat_limit - DEG_TO_RAD/divisor) <= latitude) && (latitude <= (upper_lat_limit + DEG_TO_RAD/divisor))))
+                    error_code |= MGRS_LAT_WARNING;
+                }
+              }
+              else
+              {
+                if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
+                  error_code |= MGRS_STRING_ERROR;
+                if(utm_error_code & UTM_EASTING_ERROR)
+                  error_code |= MGRS_EASTING_ERROR;
+                if(utm_error_code & UTM_NORTHING_ERROR)
+                  error_code |= MGRS_NORTHING_ERROR;
+              }
+            }
+            else
+            {
+              if(utm_error_code & UTM_A_ERROR)
+                error_code |= MGRS_A_ERROR;
+              if(utm_error_code & UTM_INV_F_ERROR)
+                error_code |= MGRS_INV_F_ERROR;
+              if(utm_error_code & UTM_ZONE_OVERRIDE_ERROR)
+                error_code |= MGRS_ZONE_ERROR;
+            }
+          }
+        }
+      }
+    }
+  }
+  return (error_code);
+} /* Convert_MGRS_To_UTM */
+
+
+long Convert_UPS_To_MGRS (char   Hemisphere,
+                          double Easting,
+                          double Northing,
+                          long   Precision,
+                          char*  MGRS)
+/*
+ *  The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, 
+ *  and northing) coordinates to an MGRS coordinate string according to 
+ *  the current ellipsoid parameters.  If any errors occur, the error
+ *  code(s) are returned by the function, otherwise UPS_NO_ERROR is 
+ *  returned.
+ *
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
+ *    Easting       : Easting/X in meters              (input)
+ *    Northing      : Northing/Y in meters             (input)
+ *    Precision     : Precision level of MGRS string   (input)
+ *    MGRS          : MGRS coordinate string           (output)
+ */
+{ /* Convert_UPS_To_MGRS */
+  double false_easting;       /* False easting for 2nd letter                 */
+  double false_northing;      /* False northing for 3rd letter                */
+  double grid_easting;        /* Easting used to derive 2nd letter of MGRS    */
+  double grid_northing;       /* Northing used to derive 3rd letter of MGRS   */
+  long ltr2_low_value;        /* 2nd letter range - low number                */
+  int letters[MGRS_LETTERS];  /* Number location of 3 letters in alphabet     */
+  double divisor;
+  int index = 0;
+  long error_code = MGRS_NO_ERROR;
+
+  if ((Hemisphere != 'N') && (Hemisphere != 'S'))
+    error_code |= MGRS_HEMISPHERE_ERROR;
+  if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH))
+    error_code |= MGRS_EASTING_ERROR;
+  if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH))
+    error_code |= MGRS_NORTHING_ERROR;
+  if ((Precision < 0) || (Precision > MAX_PRECISION))
+    error_code |= MGRS_PRECISION_ERROR;
+  if (!error_code)
+  {
+    divisor = pow (10.0, (5 - Precision));
+    Easting = Round_MGRS (Easting/divisor) * divisor;
+    Northing = Round_MGRS (Northing/divisor) * divisor;
+
+    if (Hemisphere == 'N')
+    {
+      if (Easting >= TWOMIL)
+        letters[0] = LETTER_Z; 
+      else
+        letters[0] = LETTER_Y;
+
+      index = letters[0] - 22;
+      ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
+      false_easting = UPS_Constant_Table[index].false_easting;
+      false_northing = UPS_Constant_Table[index].false_northing;
+    }
+    else
+    {
+      if (Easting >= TWOMIL)
+        letters[0] = LETTER_B;
+      else
+        letters[0] = LETTER_A;
+
+      ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
+      false_easting = UPS_Constant_Table[letters[0]].false_easting;
+      false_northing = UPS_Constant_Table[letters[0]].false_northing;
+    }
+
+    grid_northing = Northing;
+    grid_northing = grid_northing - false_northing;
+    letters[2] = (long)(grid_northing / ONEHT);
+
+    if (letters[2] > LETTER_H)
+      letters[2] = letters[2] + 1;
+
+    if (letters[2] > LETTER_N)
+      letters[2] = letters[2] + 1;
+
+    grid_easting = Easting;
+    grid_easting = grid_easting - false_easting;
+    letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT)); 
+
+    if (Easting < TWOMIL)
+    {
+      if (letters[1] > LETTER_L)
+        letters[1] = letters[1] + 3; 
+
+      if (letters[1] > LETTER_U)
+        letters[1] = letters[1] + 2; 
+    }
+    else
+    {
+      if (letters[1] > LETTER_C)
+        letters[1] = letters[1] + 2; 
+
+      if (letters[1] > LETTER_H)
+        letters[1] = letters[1] + 1;
+      
+      if (letters[1] > LETTER_L)
+        letters[1] = letters[1] + 3; 
+    }
+
+    Make_MGRS_String (MGRS, 0, letters, Easting, Northing, Precision);
+  }
+  return (error_code);
+} /* Convert_UPS_To_MGRS */
+
+
+long Convert_MGRS_To_UPS ( char   *MGRS,
+                           char   *Hemisphere,
+                           double *Easting,
+                           double *Northing)
+/*
+ *  The function Convert_MGRS_To_UPS converts an MGRS coordinate string
+ *  to UPS (hemisphere, easting, and northing) coordinates, according 
+ *  to the current ellipsoid parameters. If any errors occur, the error 
+ *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
+ *
+ *    MGRS          : MGRS coordinate string           (input)
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
+ *    Easting       : Easting/X in meters              (output)
+ *    Northing      : Northing/Y in meters             (output)
+ */
+{ /* Convert_MGRS_To_UPS */
+  long ltr2_high_value;       /* 2nd letter range - high number             */
+  long ltr3_high_value;       /* 3rd letter range - high number (UPS)       */
+  long ltr2_low_value;        /* 2nd letter range - low number              */
+  double false_easting;       /* False easting for 2nd letter               */
+  double false_northing;      /* False northing for 3rd letter              */
+  double grid_easting;        /* easting for 100,000 meter grid square      */
+  double grid_northing;       /* northing for 100,000 meter grid square     */
+  long zone;
+  long letters[MGRS_LETTERS];
+  long in_precision;
+  int index = 0;
+  long error_code = MGRS_NO_ERROR;
+
+  error_code = Break_MGRS_String (MGRS, &zone, letters, Easting, Northing, &in_precision);
+  if (zone)
+    error_code |= MGRS_STRING_ERROR;
+  else
+  {
+    if (!error_code)
+    {
+      if (letters[0] >= LETTER_Y)
+      {
+        *Hemisphere = 'N';
+
+        index = letters[0] - 22;
+        ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
+        ltr2_high_value = UPS_Constant_Table[index].ltr2_high_value;
+        ltr3_high_value = UPS_Constant_Table[index].ltr3_high_value;
+        false_easting = UPS_Constant_Table[index].false_easting;
+        false_northing = UPS_Constant_Table[index].false_northing;
+      }
+      else
+      {
+        *Hemisphere = 'S';
+
+        ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
+        ltr2_high_value = UPS_Constant_Table[letters[0]].ltr2_high_value;
+        ltr3_high_value = UPS_Constant_Table[letters[0]].ltr3_high_value;
+        false_easting = UPS_Constant_Table[letters[0]].false_easting;
+        false_northing = UPS_Constant_Table[letters[0]].false_northing;
+      }
+
+      /* Check that the second letter of the MGRS string is within
+       * the range of valid second letter values 
+       * Also check that the third letter is valid */
+      if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) ||
+          ((letters[1] == LETTER_D) || (letters[1] == LETTER_E) ||
+          (letters[1] == LETTER_M) || (letters[1] == LETTER_N) ||
+          (letters[1] == LETTER_V) || (letters[1] == LETTER_W)) ||
+          (letters[2] > ltr3_high_value))
+          error_code |= MGRS_STRING_ERROR;
+
+      if (!error_code)
+      {
+        grid_northing = (double)letters[2] * ONEHT + false_northing; 
+        if (letters[2] > LETTER_I)
+          grid_northing = grid_northing - ONEHT;
+
+        if (letters[2] > LETTER_O)
+          grid_northing = grid_northing - ONEHT;
+
+        grid_easting = (double)((letters[1]) - ltr2_low_value) * ONEHT + false_easting; 
+        if (ltr2_low_value != LETTER_A)
+        {
+          if (letters[1] > LETTER_L)
+            grid_easting = grid_easting - 300000.0;
+
+          if (letters[1] > LETTER_U)
+            grid_easting = grid_easting - 200000.0;
+        }
+        else
+        {
+          if (letters[1] > LETTER_C)
+            grid_easting = grid_easting - 200000.0;
+
+          if (letters[1] > LETTER_I)
+            grid_easting = grid_easting - ONEHT;
+
+          if (letters[1] > LETTER_L)
+            grid_easting = grid_easting - 300000.0;
+        }
+
+        *Easting = grid_easting + *Easting;
+        *Northing = grid_northing + *Northing;
+      }
+    }
+  }
+  return (error_code);
+} /* Convert_MGRS_To_UPS */
+
+
+
diff --git a/geotranz/mgrs.h b/geotranz/mgrs.h
old mode 100755
new mode 100644
index 05b70d7..79a1c28
--- a/geotranz/mgrs.h
+++ b/geotranz/mgrs.h
@@ -1,253 +1,253 @@
-#ifndef MGRS_H
-  #define MGRS_H
-
-/***************************************************************************/
-/* RSC IDENTIFIER:  MGRS
- *
- * ABSTRACT
- *
- *    This component converts between geodetic coordinates (latitude and 
- *    longitude) and Military Grid Reference System (MGRS) coordinates. 
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found, the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *          MGRS_NO_ERROR          : No errors occurred in function
- *          MGRS_LAT_ERROR         : Latitude outside of valid range 
- *                                    (-90 to 90 degrees)
- *          MGRS_LON_ERROR         : Longitude outside of valid range
- *                                    (-180 to 360 degrees)
- *          MGRS_STR_ERROR         : An MGRS string error: string too long,
- *                                    too short, or badly formed
- *          MGRS_PRECISION_ERROR   : The precision must be between 0 and 5 
- *                                    inclusive.
- *          MGRS_A_ERROR           : Semi-major axis less than or equal to zero
- *          MGRS_INV_F_ERROR       : Inverse flattening outside of valid range
- *									                  (250 to 350)
- *          MGRS_EASTING_ERROR     : Easting outside of valid range
- *                                    (100,000 to 900,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          MGRS_NORTHING_ERROR    : Northing outside of valid range
- *                                    (0 to 10,000,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          MGRS_ZONE_ERROR        : Zone outside of valid range (1 to 60)
- *          MGRS_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
- *
- * REUSE NOTES
- *
- *    MGRS is intended for reuse by any application that does conversions
- *    between geodetic coordinates and MGRS coordinates.
- *
- * REFERENCES
- *
- *    Further information on MGRS can be found in the Reuse Manual.
- *
- *    MGRS originated from : U.S. Army Topographic Engineering Center
- *                           Geospatial Information Division
- *                           7701 Telegraph Road
- *                           Alexandria, VA  22310-3864
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *
- * ENVIRONMENT
- *
- *    MGRS was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC version 2.8.1
- *    2. Windows 95 with MS Visual C++ version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    16-11-94          Original Code
- *    15-09-99          Reengineered upper layers
- *
- */
-
-
-/***************************************************************************/
-/*
- *                              DEFINES
- */
-
-  #define MGRS_NO_ERROR                0x0000
-  #define MGRS_LAT_ERROR               0x0001
-  #define MGRS_LON_ERROR               0x0002
-  #define MGRS_STRING_ERROR            0x0004
-  #define MGRS_PRECISION_ERROR         0x0008
-  #define MGRS_A_ERROR                 0x0010
-  #define MGRS_INV_F_ERROR             0x0020
-  #define MGRS_EASTING_ERROR           0x0040
-  #define MGRS_NORTHING_ERROR          0x0080
-  #define MGRS_ZONE_ERROR              0x0100
-  #define MGRS_HEMISPHERE_ERROR        0x0200
-  #define MGRS_LAT_WARNING             0x0400
-
-
-/***************************************************************************/
-/*
- *                              FUNCTION PROTOTYPES
- */
-
-/* ensure proper linkage to c++ programs */
-  #ifdef __cplusplus
-extern "C" {
-  #endif
-
-
-  long Set_MGRS_Parameters(double a,
-                           double f,
-                           char   *Ellipsoid_Code);
-/*
- * The function Set_MGRS_Parameters receives the ellipsoid parameters and sets
- * the corresponding state variables. If any errors occur, the error code(s)
- * are returned by the function, otherwise MGRS_NO_ERROR is returned.
- *
- *   a                : Semi-major axis of ellipsoid in meters (input)
- *   f                : Flattening of ellipsoid					       (input)
- *   Ellipsoid_Code   : 2-letter code for ellipsoid            (input)
- */
-
-
-  void Get_MGRS_Parameters(double *a,
-                           double *f,
-                           char   *Ellipsoid_Code);
-/*
- * The function Get_MGRS_Parameters returns the current ellipsoid
- * parameters.
- *
- *  a                : Semi-major axis of ellipsoid, in meters (output)
- *  f                : Flattening of ellipsoid					       (output)
- *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
- */
-
-
-  long Convert_Geodetic_To_MGRS (double Latitude,
-                                 double Longitude,
-                                 long   Precision,
-                                 char *MGRS);
-/*
- * The function Convert_Geodetic_To_MGRS converts geodetic (latitude and
- * longitude) coordinates to an MGRS coordinate string, according to the 
- * current ellipsoid parameters.  If any errors occur, the error code(s) 
- * are returned by the  function, otherwise MGRS_NO_ERROR is returned.
- *
- *    Latitude   : Latitude in radians              (input)
- *    Longitude  : Longitude in radians             (input)
- *    Precision  : Precision level of MGRS string   (input)
- *    MGRS       : MGRS coordinate string           (output)
- *  
- */
-
-
-  long Convert_MGRS_To_Geodetic (char *MGRS,
-                                 double *Latitude,
-                                 double *Longitude);
-/*
- * This function converts an MGRS coordinate string to Geodetic (latitude
- * and longitude in radians) coordinates.  If any errors occur, the error 
- * code(s) are returned by the  function, otherwise MGRS_NO_ERROR is returned.  
- *
- *    MGRS       : MGRS coordinate string           (input)
- *    Latitude   : Latitude in radians              (output)
- *    Longitude  : Longitude in radians             (output)
- *  
- */
-
-
-  long Convert_UTM_To_MGRS (long Zone,
-                            char Hemisphere,
-                            double Easting,
-                            double Northing,
-                            long Precision,
-                            char *MGRS);
-/*
- * The function Convert_UTM_To_MGRS converts UTM (zone, easting, and
- * northing) coordinates to an MGRS coordinate string, according to the 
- * current ellipsoid parameters.  If any errors occur, the error code(s) 
- * are returned by the  function, otherwise MGRS_NO_ERROR is returned.
- *
- *    Zone       : UTM zone                         (input)
- *    Hemisphere : North or South hemisphere        (input)
- *    Easting    : Easting (X) in meters            (input)
- *    Northing   : Northing (Y) in meters           (input)
- *    Precision  : Precision level of MGRS string   (input)
- *    MGRS       : MGRS coordinate string           (output)
- */
-
-
-  long Convert_MGRS_To_UTM (char   *MGRS,
-                            long   *Zone,
-                            char   *Hemisphere,
-                            double *Easting,
-                            double *Northing); 
-/*
- * The function Convert_MGRS_To_UTM converts an MGRS coordinate string
- * to UTM projection (zone, hemisphere, easting and northing) coordinates 
- * according to the current ellipsoid parameters.  If any errors occur, 
- * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
- * is returned.
- *
- *    MGRS       : MGRS coordinate string           (input)
- *    Zone       : UTM zone                         (output)
- *    Hemisphere : North or South hemisphere        (output)
- *    Easting    : Easting (X) in meters            (output)
- *    Northing   : Northing (Y) in meters           (output)
- */
-
-
-
-  long Convert_UPS_To_MGRS ( char   Hemisphere,
-                             double Easting,
-                             double Northing,
-                             long Precision,
-                             char *MGRS);
-
-/*
- *  The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, 
- *  and northing) coordinates to an MGRS coordinate string according to 
- *  the current ellipsoid parameters.  If any errors occur, the error
- *  code(s) are returned by the function, otherwise UPS_NO_ERROR is 
- *  returned.
- *
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
- *    Easting       : Easting/X in meters              (input)
- *    Northing      : Northing/Y in meters             (input)
- *    Precision     : Precision level of MGRS string   (input)
- *    MGRS          : MGRS coordinate string           (output)
- */
-
-
-  long Convert_MGRS_To_UPS ( char   *MGRS,
-                             char   *Hemisphere,
-                             double *Easting,
-                             double *Northing);
-/*
- *  The function Convert_MGRS_To_UPS converts an MGRS coordinate string
- *  to UPS (hemisphere, easting, and northing) coordinates, according 
- *  to the current ellipsoid parameters. If any errors occur, the error 
- *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
- *
- *    MGRS          : MGRS coordinate string           (input)
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
- *    Easting       : Easting/X in meters              (output)
- *    Northing      : Northing/Y in meters             (output)
- */
-
-
-
-  #ifdef __cplusplus
-}
-  #endif
-
-#endif /* MGRS_H */
+#ifndef MGRS_H
+  #define MGRS_H
+
+/***************************************************************************/
+/* RSC IDENTIFIER:  MGRS
+ *
+ * ABSTRACT
+ *
+ *    This component converts between geodetic coordinates (latitude and 
+ *    longitude) and Military Grid Reference System (MGRS) coordinates. 
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found, the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *          MGRS_NO_ERROR          : No errors occurred in function
+ *          MGRS_LAT_ERROR         : Latitude outside of valid range 
+ *                                    (-90 to 90 degrees)
+ *          MGRS_LON_ERROR         : Longitude outside of valid range
+ *                                    (-180 to 360 degrees)
+ *          MGRS_STR_ERROR         : An MGRS string error: string too long,
+ *                                    too short, or badly formed
+ *          MGRS_PRECISION_ERROR   : The precision must be between 0 and 5 
+ *                                    inclusive.
+ *          MGRS_A_ERROR           : Semi-major axis less than or equal to zero
+ *          MGRS_INV_F_ERROR       : Inverse flattening outside of valid range
+ *									                  (250 to 350)
+ *          MGRS_EASTING_ERROR     : Easting outside of valid range
+ *                                    (100,000 to 900,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          MGRS_NORTHING_ERROR    : Northing outside of valid range
+ *                                    (0 to 10,000,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          MGRS_ZONE_ERROR        : Zone outside of valid range (1 to 60)
+ *          MGRS_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
+ *
+ * REUSE NOTES
+ *
+ *    MGRS is intended for reuse by any application that does conversions
+ *    between geodetic coordinates and MGRS coordinates.
+ *
+ * REFERENCES
+ *
+ *    Further information on MGRS can be found in the Reuse Manual.
+ *
+ *    MGRS originated from : U.S. Army Topographic Engineering Center
+ *                           Geospatial Information Division
+ *                           7701 Telegraph Road
+ *                           Alexandria, VA  22310-3864
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    MGRS was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC version 2.8.1
+ *    2. Windows 95 with MS Visual C++ version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    16-11-94          Original Code
+ *    15-09-99          Reengineered upper layers
+ *
+ */
+
+
+/***************************************************************************/
+/*
+ *                              DEFINES
+ */
+
+  #define MGRS_NO_ERROR                0x0000
+  #define MGRS_LAT_ERROR               0x0001
+  #define MGRS_LON_ERROR               0x0002
+  #define MGRS_STRING_ERROR            0x0004
+  #define MGRS_PRECISION_ERROR         0x0008
+  #define MGRS_A_ERROR                 0x0010
+  #define MGRS_INV_F_ERROR             0x0020
+  #define MGRS_EASTING_ERROR           0x0040
+  #define MGRS_NORTHING_ERROR          0x0080
+  #define MGRS_ZONE_ERROR              0x0100
+  #define MGRS_HEMISPHERE_ERROR        0x0200
+  #define MGRS_LAT_WARNING             0x0400
+
+
+/***************************************************************************/
+/*
+ *                              FUNCTION PROTOTYPES
+ */
+
+/* ensure proper linkage to c++ programs */
+  #ifdef __cplusplus
+extern "C" {
+  #endif
+
+
+  long Set_MGRS_Parameters(double a,
+                           double f,
+                           char   *Ellipsoid_Code);
+/*
+ * The function Set_MGRS_Parameters receives the ellipsoid parameters and sets
+ * the corresponding state variables. If any errors occur, the error code(s)
+ * are returned by the function, otherwise MGRS_NO_ERROR is returned.
+ *
+ *   a                : Semi-major axis of ellipsoid in meters (input)
+ *   f                : Flattening of ellipsoid					       (input)
+ *   Ellipsoid_Code   : 2-letter code for ellipsoid            (input)
+ */
+
+
+  void Get_MGRS_Parameters(double *a,
+                           double *f,
+                           char   *Ellipsoid_Code);
+/*
+ * The function Get_MGRS_Parameters returns the current ellipsoid
+ * parameters.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters (output)
+ *  f                : Flattening of ellipsoid					       (output)
+ *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
+ */
+
+
+  long Convert_Geodetic_To_MGRS (double Latitude,
+                                 double Longitude,
+                                 long   Precision,
+                                 char *MGRS);
+/*
+ * The function Convert_Geodetic_To_MGRS converts geodetic (latitude and
+ * longitude) coordinates to an MGRS coordinate string, according to the 
+ * current ellipsoid parameters.  If any errors occur, the error code(s) 
+ * are returned by the  function, otherwise MGRS_NO_ERROR is returned.
+ *
+ *    Latitude   : Latitude in radians              (input)
+ *    Longitude  : Longitude in radians             (input)
+ *    Precision  : Precision level of MGRS string   (input)
+ *    MGRS       : MGRS coordinate string           (output)
+ *  
+ */
+
+
+  long Convert_MGRS_To_Geodetic (char *MGRS,
+                                 double *Latitude,
+                                 double *Longitude);
+/*
+ * This function converts an MGRS coordinate string to Geodetic (latitude
+ * and longitude in radians) coordinates.  If any errors occur, the error 
+ * code(s) are returned by the  function, otherwise MGRS_NO_ERROR is returned.  
+ *
+ *    MGRS       : MGRS coordinate string           (input)
+ *    Latitude   : Latitude in radians              (output)
+ *    Longitude  : Longitude in radians             (output)
+ *  
+ */
+
+
+  long Convert_UTM_To_MGRS (long Zone,
+                            char Hemisphere,
+                            double Easting,
+                            double Northing,
+                            long Precision,
+                            char *MGRS);
+/*
+ * The function Convert_UTM_To_MGRS converts UTM (zone, easting, and
+ * northing) coordinates to an MGRS coordinate string, according to the 
+ * current ellipsoid parameters.  If any errors occur, the error code(s) 
+ * are returned by the  function, otherwise MGRS_NO_ERROR is returned.
+ *
+ *    Zone       : UTM zone                         (input)
+ *    Hemisphere : North or South hemisphere        (input)
+ *    Easting    : Easting (X) in meters            (input)
+ *    Northing   : Northing (Y) in meters           (input)
+ *    Precision  : Precision level of MGRS string   (input)
+ *    MGRS       : MGRS coordinate string           (output)
+ */
+
+
+  long Convert_MGRS_To_UTM (char   *MGRS,
+                            long   *Zone,
+                            char   *Hemisphere,
+                            double *Easting,
+                            double *Northing); 
+/*
+ * The function Convert_MGRS_To_UTM converts an MGRS coordinate string
+ * to UTM projection (zone, hemisphere, easting and northing) coordinates 
+ * according to the current ellipsoid parameters.  If any errors occur, 
+ * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
+ * is returned.
+ *
+ *    MGRS       : MGRS coordinate string           (input)
+ *    Zone       : UTM zone                         (output)
+ *    Hemisphere : North or South hemisphere        (output)
+ *    Easting    : Easting (X) in meters            (output)
+ *    Northing   : Northing (Y) in meters           (output)
+ */
+
+
+
+  long Convert_UPS_To_MGRS ( char   Hemisphere,
+                             double Easting,
+                             double Northing,
+                             long Precision,
+                             char *MGRS);
+
+/*
+ *  The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, 
+ *  and northing) coordinates to an MGRS coordinate string according to 
+ *  the current ellipsoid parameters.  If any errors occur, the error
+ *  code(s) are returned by the function, otherwise UPS_NO_ERROR is 
+ *  returned.
+ *
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
+ *    Easting       : Easting/X in meters              (input)
+ *    Northing      : Northing/Y in meters             (input)
+ *    Precision     : Precision level of MGRS string   (input)
+ *    MGRS          : MGRS coordinate string           (output)
+ */
+
+
+  long Convert_MGRS_To_UPS ( char   *MGRS,
+                             char   *Hemisphere,
+                             double *Easting,
+                             double *Northing);
+/*
+ *  The function Convert_MGRS_To_UPS converts an MGRS coordinate string
+ *  to UPS (hemisphere, easting, and northing) coordinates, according 
+ *  to the current ellipsoid parameters. If any errors occur, the error 
+ *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
+ *
+ *    MGRS          : MGRS coordinate string           (input)
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
+ *    Easting       : Easting/X in meters              (output)
+ *    Northing      : Northing/Y in meters             (output)
+ */
+
+
+
+  #ifdef __cplusplus
+}
+  #endif
+
+#endif /* MGRS_H */
diff --git a/geotranz/polarst.c b/geotranz/polarst.c
old mode 100755
new mode 100644
index 2bf46ba..d32f9e8
--- a/geotranz/polarst.c
+++ b/geotranz/polarst.c
@@ -1,523 +1,523 @@
-/***************************************************************************/
-/* RSC IDENTIFIER: POLAR STEREOGRAPHIC 
- *
- *
- * ABSTRACT
- *
- *    This component provides conversions between geodetic (latitude and
- *    longitude) coordinates and Polar Stereographic (easting and northing) 
- *    coordinates.
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid 
- *    value is found the error code is combined with the current error code 
- *    using the bitwise or.  This combining allows multiple error codes to 
- *    be returned. The possible error codes are:
- *
- *          POLAR_NO_ERROR           : No errors occurred in function
- *          POLAR_LAT_ERROR          : Latitude outside of valid range
- *                                      (-90 to 90 degrees)
- *          POLAR_LON_ERROR          : Longitude outside of valid range
- *                                      (-180 to 360 degrees) 
- *          POLAR_ORIGIN_LAT_ERROR   : Latitude of true scale outside of valid
- *                                      range (-90 to 90 degrees)
- *          POLAR_ORIGIN_LON_ERROR   : Longitude down from pole outside of valid
- *                                      range (-180 to 360 degrees)
- *          POLAR_EASTING_ERROR      : Easting outside of valid range,
- *                                      depending on ellipsoid and
- *                                      projection parameters
- *          POLAR_NORTHING_ERROR     : Northing outside of valid range,
- *                                      depending on ellipsoid and
- *                                      projection parameters
- *          POLAR_RADIUS_ERROR       : Coordinates too far from pole,
- *                                      depending on ellipsoid and
- *                                      projection parameters
- *          POLAR_A_ERROR            : Semi-major axis less than or equal to zero
- *          POLAR_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	                  (250 to 350)
- *
- *
- * REUSE NOTES
- *
- *    POLAR STEREOGRAPHIC is intended for reuse by any application that  
- *    performs a Polar Stereographic projection.
- *
- *
- * REFERENCES
- *
- *    Further information on POLAR STEREOGRAPHIC can be found in the
- *    Reuse Manual.
- *
- *
- *    POLAR STEREOGRAPHIC originated from :
- *                                U.S. Army Topographic Engineering Center
- *                                Geospatial Information Division
- *                                7701 Telegraph Road
- *                                Alexandria, VA  22310-3864
- *
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- *
- * RESTRICTIONS
- *
- *    POLAR STEREOGRAPHIC has no restrictions.
- *
- *
- * ENVIRONMENT
- *
- *    POLAR STEREOGRAPHIC was tested and certified in the following
- *    environments:
- *
- *    1. Solaris 2.5 with GCC, version 2.8.1
- *    2. Window 95 with MS Visual C++, version 6
- *
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    06-11-95          Original Code
- *    03-01-97          Original Code
- *
- *
- */
-
-
-/************************************************************************/
-/*
- *                               INCLUDES
- */
-
-#include <math.h>
-#include "polarst.h"
-
-/*
- *    math.h     - Standard C math library
- *    polarst.h  - Is for prototype error checking
- */
-
-
-/************************************************************************/
-/*                               DEFINES
- *
- */
-
-
-#define PI           3.14159265358979323e0       /* PI     */
-#define PI_OVER_2    (PI / 2.0)           
-#define TWO_PI       (2.0 * PI)
-#define POLAR_POW(EsSin)     pow((1.0 - EsSin) / (1.0 + EsSin), es_OVER_2)
-
-/************************************************************************/
-/*                           GLOBAL DECLARATIONS
- *
- */
-
-const double PI_Over_4 = (PI / 4.0);
-
-/* Ellipsoid Parameters, default to WGS 84  */
-static double Polar_a = 6378137.0;                    /* Semi-major axis of ellipsoid in meters  */
-static double Polar_f = 1 / 298.257223563;            /* Flattening of ellipsoid  */
-static double es = 0.08181919084262188000;            /* Eccentricity of ellipsoid    */
-static double es_OVER_2 = .040909595421311;           /* es / 2.0 */
-static double Southern_Hemisphere = 0;                /* Flag variable */
-static double tc = 1.0;
-static double e4 = 1.0033565552493;
-static double Polar_a_mc = 6378137.0;                 /* Polar_a * mc */
-static double two_Polar_a = 12756274.0;               /* 2.0 * Polar_a */
-
-/* Polar Stereographic projection Parameters */
-static double Polar_Origin_Lat = ((PI * 90) / 180);   /* Latitude of origin in radians */
-static double Polar_Origin_Long = 0.0;                /* Longitude of origin in radians */
-static double Polar_False_Easting = 0.0;              /* False easting in meters */
-static double Polar_False_Northing = 0.0;             /* False northing in meters */
-
-/* Maximum variance for easting and northing values for WGS 84. */
-static double Polar_Delta_Easting = 12713601.0;
-static double Polar_Delta_Northing = 12713601.0;
-
-/* These state variables are for optimization purposes. The only function
- * that should modify them is Set_Polar_Stereographic_Parameters.         
- */
-
-
-/************************************************************************/
-/*                              FUNCTIONS
- *
- */
-
-
-long Set_Polar_Stereographic_Parameters (double a,
-                                         double f,
-                                         double Latitude_of_True_Scale,
-                                         double Longitude_Down_from_Pole,
-                                         double False_Easting,
-                                         double False_Northing)
-
-{  /* BEGIN Set_Polar_Stereographic_Parameters   */
-/*  
- *  The function Set_Polar_Stereographic_Parameters receives the ellipsoid
- *  parameters and Polar Stereograpic projection parameters as inputs, and
- *  sets the corresponding state variables.  If any errors occur, error
- *  code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
- *
- *  a                : Semi-major axis of ellipsoid, in meters         (input)
- *  f                : Flattening of ellipsoid					               (input)
- *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (input)
- *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (input)
- *  False_Easting    : Easting (X) at center of projection, in meters  (input)
- *  False_Northing   : Northing (Y) at center of projection, in meters (input)
- */
-
-  double es2;
-  double slat, clat;
-  double essin;
-  double one_PLUS_es, one_MINUS_es;
-  double pow_es;
-  double temp, temp_northing;
-  double inv_f = 1 / f;
-  double mc;                    
-//  const double  epsilon = 1.0e-2;
-  long Error_Code = POLAR_NO_ERROR;
-
-  if (a <= 0.0)
-  { /* Semi-major axis must be greater than zero */
-    Error_Code |= POLAR_A_ERROR;
-  }
-  if ((inv_f < 250) || (inv_f > 350))
-  { /* Inverse flattening must be between 250 and 350 */
-    Error_Code |= POLAR_INV_F_ERROR;
-  }
-  if ((Latitude_of_True_Scale < -PI_OVER_2) || (Latitude_of_True_Scale > PI_OVER_2))
-  { /* Origin Latitude out of range */
-    Error_Code |= POLAR_ORIGIN_LAT_ERROR;
-  }
-  if ((Longitude_Down_from_Pole < -PI) || (Longitude_Down_from_Pole > TWO_PI))
-  { /* Origin Longitude out of range */
-    Error_Code |= POLAR_ORIGIN_LON_ERROR;
-  }
-
-  if (!Error_Code)
-  { /* no errors */
-
-    Polar_a = a;
-    two_Polar_a = 2.0 * Polar_a;
-    Polar_f = f;
-
-    if (Longitude_Down_from_Pole > PI)
-      Longitude_Down_from_Pole -= TWO_PI;
-    if (Latitude_of_True_Scale < 0)
-    {
-      Southern_Hemisphere = 1;
-      Polar_Origin_Lat = -Latitude_of_True_Scale;
-      Polar_Origin_Long = -Longitude_Down_from_Pole;
-    }
-    else
-    {
-      Southern_Hemisphere = 0;
-      Polar_Origin_Lat = Latitude_of_True_Scale;
-      Polar_Origin_Long = Longitude_Down_from_Pole;
-    }
-    Polar_False_Easting = False_Easting;
-    Polar_False_Northing = False_Northing;
-
-    es2 = 2 * Polar_f - Polar_f * Polar_f;
-    es = sqrt(es2);
-    es_OVER_2 = es / 2.0;
-
-    if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
-    {
-      slat = sin(Polar_Origin_Lat);
-      essin = es * slat;
-      pow_es = POLAR_POW(essin);
-      clat = cos(Polar_Origin_Lat);
-      mc = clat / sqrt(1.0 - essin * essin);
-      Polar_a_mc = Polar_a * mc;
-      tc = tan(PI_Over_4 - Polar_Origin_Lat / 2.0) / pow_es;
-    }
-    else
-    {
-      one_PLUS_es = 1.0 + es;
-      one_MINUS_es = 1.0 - es;
-      e4 = sqrt(pow(one_PLUS_es, one_PLUS_es) * pow(one_MINUS_es, one_MINUS_es));
-    }
-
-    /* Calculate Radius */
-    Convert_Geodetic_To_Polar_Stereographic(0, Longitude_Down_from_Pole, 
-                                            &temp, &temp_northing);
-
-    Polar_Delta_Northing = temp_northing;
-    if(Polar_False_Northing)
-      Polar_Delta_Northing -= Polar_False_Northing;
-    if (Polar_Delta_Northing < 0)
-      Polar_Delta_Northing = -Polar_Delta_Northing;
-    Polar_Delta_Northing *= 1.01;
-
-    Polar_Delta_Easting = Polar_Delta_Northing;
-
-  /*  Polar_Delta_Easting = temp_northing;
-    if(Polar_False_Easting)
-      Polar_Delta_Easting -= Polar_False_Easting;
-    if (Polar_Delta_Easting < 0)
-      Polar_Delta_Easting = -Polar_Delta_Easting;
-    Polar_Delta_Easting *= 1.01;*/
-  }
-
-  return (Error_Code);
-} /* END OF Set_Polar_Stereographic_Parameters */
-
-
-
-void Get_Polar_Stereographic_Parameters (double *a,
-                                         double *f,
-                                         double *Latitude_of_True_Scale,
-                                         double *Longitude_Down_from_Pole,
-                                         double *False_Easting,
-                                         double *False_Northing)
-
-{ /* BEGIN Get_Polar_Stereographic_Parameters  */
-/*
- * The function Get_Polar_Stereographic_Parameters returns the current
- * ellipsoid parameters and Polar projection parameters.
- *
- *  a                : Semi-major axis of ellipsoid, in meters         (output)
- *  f                : Flattening of ellipsoid					               (output)
- *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (output)
- *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (output)
- *  False_Easting    : Easting (X) at center of projection, in meters  (output)
- *  False_Northing   : Northing (Y) at center of projection, in meters (output)
- */
-
-  *a = Polar_a;
-  *f = Polar_f;
-  *Latitude_of_True_Scale = Polar_Origin_Lat;
-  *Longitude_Down_from_Pole = Polar_Origin_Long;
-  *False_Easting = Polar_False_Easting;
-  *False_Northing = Polar_False_Northing;
-  return;
-} /* END OF Get_Polar_Stereographic_Parameters */
-
-
-long Convert_Geodetic_To_Polar_Stereographic (double Latitude,
-                                              double Longitude,
-                                              double *Easting,
-                                              double *Northing)
-
-{  /* BEGIN Convert_Geodetic_To_Polar_Stereographic */
-
-/*
- * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic
- * coordinates (latitude and longitude) to Polar Stereographic coordinates
- * (easting and northing), according to the current ellipsoid
- * and Polar Stereographic projection parameters. If any errors occur, error
- * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
- *
- *    Latitude   :  Latitude, in radians                      (input)
- *    Longitude  :  Longitude, in radians                     (input)
- *    Easting    :  Easting (X), in meters                    (output)
- *    Northing   :  Northing (Y), in meters                   (output)
- */
-
-  double dlam;
-  double slat;
-  double essin;
-  double t;
-  double rho;
-  double pow_es;
-  long Error_Code = POLAR_NO_ERROR;
-
-  if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2))
-  {   /* Latitude out of range */
-    Error_Code |= POLAR_LAT_ERROR;
-  }
-  if ((Latitude < 0) && (Southern_Hemisphere == 0))
-  {   /* Latitude and Origin Latitude in different hemispheres */
-    Error_Code |= POLAR_LAT_ERROR;
-  }
-  if ((Latitude > 0) && (Southern_Hemisphere == 1))
-  {   /* Latitude and Origin Latitude in different hemispheres */
-    Error_Code |= POLAR_LAT_ERROR;
-  }
-  if ((Longitude < -PI) || (Longitude > TWO_PI))
-  {  /* Longitude out of range */
-    Error_Code |= POLAR_LON_ERROR;
-  }
-
-
-  if (!Error_Code)
-  {  /* no errors */
-
-    if (fabs(fabs(Latitude) - PI_OVER_2) < 1.0e-10)
-    {
-      *Easting = Polar_False_Easting;
-      *Northing = Polar_False_Northing;
-    }
-    else
-    {
-      if (Southern_Hemisphere != 0)
-      {
-        Longitude *= -1.0;
-        Latitude *= -1.0;
-      }
-      dlam = Longitude - Polar_Origin_Long;
-      if (dlam > PI)
-      {
-        dlam -= TWO_PI;
-      }
-      if (dlam < -PI)
-      {
-        dlam += TWO_PI;
-      }
-      slat = sin(Latitude);
-      essin = es * slat;
-      pow_es = POLAR_POW(essin);
-      t = tan(PI_Over_4 - Latitude / 2.0) / pow_es;
-
-      if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
-        rho = Polar_a_mc * t / tc;
-      else
-        rho = two_Polar_a * t / e4;
-
-
-      if (Southern_Hemisphere != 0)
-      {
-        *Easting = -(rho * sin(dlam) - Polar_False_Easting);
-     //   *Easting *= -1.0;
-        *Northing = rho * cos(dlam) + Polar_False_Northing;
-      }
-      else
-      {
-        *Easting = rho * sin(dlam) + Polar_False_Easting;
-        *Northing = -rho * cos(dlam) + Polar_False_Northing;
-      }
-
-    }
-  }
-  return (Error_Code);
-} /* END OF Convert_Geodetic_To_Polar_Stereographic */
-
-
-long Convert_Polar_Stereographic_To_Geodetic (double Easting,
-                                              double Northing,
-                                              double *Latitude,
-                                              double *Longitude)
-
-{ /*  BEGIN Convert_Polar_Stereographic_To_Geodetic  */
-/*
- *  The function Convert_Polar_Stereographic_To_Geodetic converts Polar
- *  Stereographic coordinates (easting and northing) to geodetic
- *  coordinates (latitude and longitude) according to the current ellipsoid
- *  and Polar Stereographic projection Parameters. If any errors occur, the
- *  code(s) are returned by the function, otherwise POLAR_NO_ERROR
- *  is returned.
- *
- *  Easting          : Easting (X), in meters                   (input)
- *  Northing         : Northing (Y), in meters                  (input)
- *  Latitude         : Latitude, in radians                     (output)
- *  Longitude        : Longitude, in radians                    (output)
- *
- */
-
-  double dy = 0, dx = 0;
-  double rho = 0;
-  double t;
-  double PHI, sin_PHI;
-  double tempPHI = 0.0;
-  double essin;
-  double pow_es;
-  double delta_radius;
-  long Error_Code = POLAR_NO_ERROR;
-  double min_easting = Polar_False_Easting - Polar_Delta_Easting;
-  double max_easting = Polar_False_Easting + Polar_Delta_Easting;
-  double min_northing = Polar_False_Northing - Polar_Delta_Northing;
-  double max_northing = Polar_False_Northing + Polar_Delta_Northing;
-
-  if (Easting > max_easting || Easting < min_easting)
-  { /* Easting out of range */
-    Error_Code |= POLAR_EASTING_ERROR;
-  }
-  if (Northing > max_northing || Northing < min_northing)
-  { /* Northing out of range */
-    Error_Code |= POLAR_NORTHING_ERROR;
-  }
-
-  if (!Error_Code)
-  {
-    dy = Northing - Polar_False_Northing;
-    dx = Easting - Polar_False_Easting;
-
-    /* Radius of point with origin of false easting, false northing */
-    rho = sqrt(dx * dx + dy * dy);   
-    
-    delta_radius = sqrt(Polar_Delta_Easting * Polar_Delta_Easting + Polar_Delta_Northing * Polar_Delta_Northing);
-
-    if(rho > delta_radius)
-    { /* Point is outside of projection area */
-      Error_Code |= POLAR_RADIUS_ERROR;
-    }
-
-    if (!Error_Code)
-    { /* no errors */
-      if ((dy == 0.0) && (dx == 0.0))
-      {
-        *Latitude = PI_OVER_2;
-        *Longitude = Polar_Origin_Long;
-
-      }
-      else
-      {
-        if (Southern_Hemisphere != 0)
-        {
-          dy *= -1.0;
-          dx *= -1.0;
-        }
-
-        if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
-          t = rho * tc / (Polar_a_mc);
-        else
-          t = rho * e4 / (two_Polar_a);
-        PHI = PI_OVER_2 - 2.0 * atan(t);
-        while (fabs(PHI - tempPHI) > 1.0e-10)
-        {
-          tempPHI = PHI;
-          sin_PHI = sin(PHI);
-          essin =  es * sin_PHI;
-          pow_es = POLAR_POW(essin);
-          PHI = PI_OVER_2 - 2.0 * atan(t * pow_es);
-        }
-        *Latitude = PHI;
-        *Longitude = Polar_Origin_Long + atan2(dx, -dy);
-
-        if (*Longitude > PI)
-          *Longitude -= TWO_PI;
-        else if (*Longitude < -PI)
-          *Longitude += TWO_PI;
-
-
-        if (*Latitude > PI_OVER_2)  /* force distorted values to 90, -90 degrees */
-          *Latitude = PI_OVER_2;
-        else if (*Latitude < -PI_OVER_2)
-          *Latitude = -PI_OVER_2;
-
-        if (*Longitude > PI)  /* force distorted values to 180, -180 degrees */
-          *Longitude = PI;
-        else if (*Longitude < -PI)
-          *Longitude = -PI;
-
-      }
-      if (Southern_Hemisphere != 0)
-      {
-        *Latitude *= -1.0;
-        *Longitude *= -1.0;
-      }
-    }
-  }
-  return (Error_Code);
-} /* END OF Convert_Polar_Stereographic_To_Geodetic */
-
-
-
+/***************************************************************************/
+/* RSC IDENTIFIER: POLAR STEREOGRAPHIC 
+ *
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between geodetic (latitude and
+ *    longitude) coordinates and Polar Stereographic (easting and northing) 
+ *    coordinates.
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid 
+ *    value is found the error code is combined with the current error code 
+ *    using the bitwise or.  This combining allows multiple error codes to 
+ *    be returned. The possible error codes are:
+ *
+ *          POLAR_NO_ERROR           : No errors occurred in function
+ *          POLAR_LAT_ERROR          : Latitude outside of valid range
+ *                                      (-90 to 90 degrees)
+ *          POLAR_LON_ERROR          : Longitude outside of valid range
+ *                                      (-180 to 360 degrees) 
+ *          POLAR_ORIGIN_LAT_ERROR   : Latitude of true scale outside of valid
+ *                                      range (-90 to 90 degrees)
+ *          POLAR_ORIGIN_LON_ERROR   : Longitude down from pole outside of valid
+ *                                      range (-180 to 360 degrees)
+ *          POLAR_EASTING_ERROR      : Easting outside of valid range,
+ *                                      depending on ellipsoid and
+ *                                      projection parameters
+ *          POLAR_NORTHING_ERROR     : Northing outside of valid range,
+ *                                      depending on ellipsoid and
+ *                                      projection parameters
+ *          POLAR_RADIUS_ERROR       : Coordinates too far from pole,
+ *                                      depending on ellipsoid and
+ *                                      projection parameters
+ *          POLAR_A_ERROR            : Semi-major axis less than or equal to zero
+ *          POLAR_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	                  (250 to 350)
+ *
+ *
+ * REUSE NOTES
+ *
+ *    POLAR STEREOGRAPHIC is intended for reuse by any application that  
+ *    performs a Polar Stereographic projection.
+ *
+ *
+ * REFERENCES
+ *
+ *    Further information on POLAR STEREOGRAPHIC can be found in the
+ *    Reuse Manual.
+ *
+ *
+ *    POLAR STEREOGRAPHIC originated from :
+ *                                U.S. Army Topographic Engineering Center
+ *                                Geospatial Information Division
+ *                                7701 Telegraph Road
+ *                                Alexandria, VA  22310-3864
+ *
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ *
+ * RESTRICTIONS
+ *
+ *    POLAR STEREOGRAPHIC has no restrictions.
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    POLAR STEREOGRAPHIC was tested and certified in the following
+ *    environments:
+ *
+ *    1. Solaris 2.5 with GCC, version 2.8.1
+ *    2. Window 95 with MS Visual C++, version 6
+ *
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    06-11-95          Original Code
+ *    03-01-97          Original Code
+ *
+ *
+ */
+
+
+/************************************************************************/
+/*
+ *                               INCLUDES
+ */
+
+#include <math.h>
+#include "polarst.h"
+
+/*
+ *    math.h     - Standard C math library
+ *    polarst.h  - Is for prototype error checking
+ */
+
+
+/************************************************************************/
+/*                               DEFINES
+ *
+ */
+
+
+#define PI           3.14159265358979323e0       /* PI     */
+#define PI_OVER_2    (PI / 2.0)           
+#define TWO_PI       (2.0 * PI)
+#define POLAR_POW(EsSin)     pow((1.0 - EsSin) / (1.0 + EsSin), es_OVER_2)
+
+/************************************************************************/
+/*                           GLOBAL DECLARATIONS
+ *
+ */
+
+const double PI_Over_4 = (PI / 4.0);
+
+/* Ellipsoid Parameters, default to WGS 84  */
+static double Polar_a = 6378137.0;                    /* Semi-major axis of ellipsoid in meters  */
+static double Polar_f = 1 / 298.257223563;            /* Flattening of ellipsoid  */
+static double es = 0.08181919084262188000;            /* Eccentricity of ellipsoid    */
+static double es_OVER_2 = .040909595421311;           /* es / 2.0 */
+static double Southern_Hemisphere = 0;                /* Flag variable */
+static double tc = 1.0;
+static double e4 = 1.0033565552493;
+static double Polar_a_mc = 6378137.0;                 /* Polar_a * mc */
+static double two_Polar_a = 12756274.0;               /* 2.0 * Polar_a */
+
+/* Polar Stereographic projection Parameters */
+static double Polar_Origin_Lat = ((PI * 90) / 180);   /* Latitude of origin in radians */
+static double Polar_Origin_Long = 0.0;                /* Longitude of origin in radians */
+static double Polar_False_Easting = 0.0;              /* False easting in meters */
+static double Polar_False_Northing = 0.0;             /* False northing in meters */
+
+/* Maximum variance for easting and northing values for WGS 84. */
+static double Polar_Delta_Easting = 12713601.0;
+static double Polar_Delta_Northing = 12713601.0;
+
+/* These state variables are for optimization purposes. The only function
+ * that should modify them is Set_Polar_Stereographic_Parameters.         
+ */
+
+
+/************************************************************************/
+/*                              FUNCTIONS
+ *
+ */
+
+
+long Set_Polar_Stereographic_Parameters (double a,
+                                         double f,
+                                         double Latitude_of_True_Scale,
+                                         double Longitude_Down_from_Pole,
+                                         double False_Easting,
+                                         double False_Northing)
+
+{  /* BEGIN Set_Polar_Stereographic_Parameters   */
+/*  
+ *  The function Set_Polar_Stereographic_Parameters receives the ellipsoid
+ *  parameters and Polar Stereograpic projection parameters as inputs, and
+ *  sets the corresponding state variables.  If any errors occur, error
+ *  code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters         (input)
+ *  f                : Flattening of ellipsoid					               (input)
+ *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (input)
+ *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (input)
+ *  False_Easting    : Easting (X) at center of projection, in meters  (input)
+ *  False_Northing   : Northing (Y) at center of projection, in meters (input)
+ */
+
+  double es2;
+  double slat, clat;
+  double essin;
+  double one_PLUS_es, one_MINUS_es;
+  double pow_es;
+  double temp, temp_northing = 0;
+  double inv_f = 1 / f;
+  double mc;                    
+//  const double  epsilon = 1.0e-2;
+  long Error_Code = POLAR_NO_ERROR;
+
+  if (a <= 0.0)
+  { /* Semi-major axis must be greater than zero */
+    Error_Code |= POLAR_A_ERROR;
+  }
+  if ((inv_f < 250) || (inv_f > 350))
+  { /* Inverse flattening must be between 250 and 350 */
+    Error_Code |= POLAR_INV_F_ERROR;
+  }
+  if ((Latitude_of_True_Scale < -PI_OVER_2) || (Latitude_of_True_Scale > PI_OVER_2))
+  { /* Origin Latitude out of range */
+    Error_Code |= POLAR_ORIGIN_LAT_ERROR;
+  }
+  if ((Longitude_Down_from_Pole < -PI) || (Longitude_Down_from_Pole > TWO_PI))
+  { /* Origin Longitude out of range */
+    Error_Code |= POLAR_ORIGIN_LON_ERROR;
+  }
+
+  if (!Error_Code)
+  { /* no errors */
+
+    Polar_a = a;
+    two_Polar_a = 2.0 * Polar_a;
+    Polar_f = f;
+
+    if (Longitude_Down_from_Pole > PI)
+      Longitude_Down_from_Pole -= TWO_PI;
+    if (Latitude_of_True_Scale < 0)
+    {
+      Southern_Hemisphere = 1;
+      Polar_Origin_Lat = -Latitude_of_True_Scale;
+      Polar_Origin_Long = -Longitude_Down_from_Pole;
+    }
+    else
+    {
+      Southern_Hemisphere = 0;
+      Polar_Origin_Lat = Latitude_of_True_Scale;
+      Polar_Origin_Long = Longitude_Down_from_Pole;
+    }
+    Polar_False_Easting = False_Easting;
+    Polar_False_Northing = False_Northing;
+
+    es2 = 2 * Polar_f - Polar_f * Polar_f;
+    es = sqrt(es2);
+    es_OVER_2 = es / 2.0;
+
+    if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
+    {
+      slat = sin(Polar_Origin_Lat);
+      essin = es * slat;
+      pow_es = POLAR_POW(essin);
+      clat = cos(Polar_Origin_Lat);
+      mc = clat / sqrt(1.0 - essin * essin);
+      Polar_a_mc = Polar_a * mc;
+      tc = tan(PI_Over_4 - Polar_Origin_Lat / 2.0) / pow_es;
+    }
+    else
+    {
+      one_PLUS_es = 1.0 + es;
+      one_MINUS_es = 1.0 - es;
+      e4 = sqrt(pow(one_PLUS_es, one_PLUS_es) * pow(one_MINUS_es, one_MINUS_es));
+    }
+
+    /* Calculate Radius */
+    Convert_Geodetic_To_Polar_Stereographic(0, Longitude_Down_from_Pole, 
+                                            &temp, &temp_northing);
+
+    Polar_Delta_Northing = temp_northing;
+    if(Polar_False_Northing)
+      Polar_Delta_Northing -= Polar_False_Northing;
+    if (Polar_Delta_Northing < 0)
+      Polar_Delta_Northing = -Polar_Delta_Northing;
+    Polar_Delta_Northing *= 1.01;
+
+    Polar_Delta_Easting = Polar_Delta_Northing;
+
+  /*  Polar_Delta_Easting = temp_northing;
+    if(Polar_False_Easting)
+      Polar_Delta_Easting -= Polar_False_Easting;
+    if (Polar_Delta_Easting < 0)
+      Polar_Delta_Easting = -Polar_Delta_Easting;
+    Polar_Delta_Easting *= 1.01;*/
+  }
+
+  return (Error_Code);
+} /* END OF Set_Polar_Stereographic_Parameters */
+
+
+
+void Get_Polar_Stereographic_Parameters (double *a,
+                                         double *f,
+                                         double *Latitude_of_True_Scale,
+                                         double *Longitude_Down_from_Pole,
+                                         double *False_Easting,
+                                         double *False_Northing)
+
+{ /* BEGIN Get_Polar_Stereographic_Parameters  */
+/*
+ * The function Get_Polar_Stereographic_Parameters returns the current
+ * ellipsoid parameters and Polar projection parameters.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters         (output)
+ *  f                : Flattening of ellipsoid					               (output)
+ *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (output)
+ *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (output)
+ *  False_Easting    : Easting (X) at center of projection, in meters  (output)
+ *  False_Northing   : Northing (Y) at center of projection, in meters (output)
+ */
+
+  *a = Polar_a;
+  *f = Polar_f;
+  *Latitude_of_True_Scale = Polar_Origin_Lat;
+  *Longitude_Down_from_Pole = Polar_Origin_Long;
+  *False_Easting = Polar_False_Easting;
+  *False_Northing = Polar_False_Northing;
+  return;
+} /* END OF Get_Polar_Stereographic_Parameters */
+
+
+long Convert_Geodetic_To_Polar_Stereographic (double Latitude,
+                                              double Longitude,
+                                              double *Easting,
+                                              double *Northing)
+
+{  /* BEGIN Convert_Geodetic_To_Polar_Stereographic */
+
+/*
+ * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic
+ * coordinates (latitude and longitude) to Polar Stereographic coordinates
+ * (easting and northing), according to the current ellipsoid
+ * and Polar Stereographic projection parameters. If any errors occur, error
+ * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
+ *
+ *    Latitude   :  Latitude, in radians                      (input)
+ *    Longitude  :  Longitude, in radians                     (input)
+ *    Easting    :  Easting (X), in meters                    (output)
+ *    Northing   :  Northing (Y), in meters                   (output)
+ */
+
+  double dlam;
+  double slat;
+  double essin;
+  double t;
+  double rho;
+  double pow_es;
+  long Error_Code = POLAR_NO_ERROR;
+
+  if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2))
+  {   /* Latitude out of range */
+    Error_Code |= POLAR_LAT_ERROR;
+  }
+  if ((Latitude < 0) && (Southern_Hemisphere == 0))
+  {   /* Latitude and Origin Latitude in different hemispheres */
+    Error_Code |= POLAR_LAT_ERROR;
+  }
+  if ((Latitude > 0) && (Southern_Hemisphere == 1))
+  {   /* Latitude and Origin Latitude in different hemispheres */
+    Error_Code |= POLAR_LAT_ERROR;
+  }
+  if ((Longitude < -PI) || (Longitude > TWO_PI))
+  {  /* Longitude out of range */
+    Error_Code |= POLAR_LON_ERROR;
+  }
+
+
+  if (!Error_Code)
+  {  /* no errors */
+
+    if (fabs(fabs(Latitude) - PI_OVER_2) < 1.0e-10)
+    {
+      *Easting = Polar_False_Easting;
+      *Northing = Polar_False_Northing;
+    }
+    else
+    {
+      if (Southern_Hemisphere != 0)
+      {
+        Longitude *= -1.0;
+        Latitude *= -1.0;
+      }
+      dlam = Longitude - Polar_Origin_Long;
+      if (dlam > PI)
+      {
+        dlam -= TWO_PI;
+      }
+      if (dlam < -PI)
+      {
+        dlam += TWO_PI;
+      }
+      slat = sin(Latitude);
+      essin = es * slat;
+      pow_es = POLAR_POW(essin);
+      t = tan(PI_Over_4 - Latitude / 2.0) / pow_es;
+
+      if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
+        rho = Polar_a_mc * t / tc;
+      else
+        rho = two_Polar_a * t / e4;
+
+
+      if (Southern_Hemisphere != 0)
+      {
+        *Easting = -(rho * sin(dlam) - Polar_False_Easting);
+     //   *Easting *= -1.0;
+        *Northing = rho * cos(dlam) + Polar_False_Northing;
+      }
+      else
+      {
+        *Easting = rho * sin(dlam) + Polar_False_Easting;
+        *Northing = -rho * cos(dlam) + Polar_False_Northing;
+      }
+
+    }
+  }
+  return (Error_Code);
+} /* END OF Convert_Geodetic_To_Polar_Stereographic */
+
+
+long Convert_Polar_Stereographic_To_Geodetic (double Easting,
+                                              double Northing,
+                                              double *Latitude,
+                                              double *Longitude)
+
+{ /*  BEGIN Convert_Polar_Stereographic_To_Geodetic  */
+/*
+ *  The function Convert_Polar_Stereographic_To_Geodetic converts Polar
+ *  Stereographic coordinates (easting and northing) to geodetic
+ *  coordinates (latitude and longitude) according to the current ellipsoid
+ *  and Polar Stereographic projection Parameters. If any errors occur, the
+ *  code(s) are returned by the function, otherwise POLAR_NO_ERROR
+ *  is returned.
+ *
+ *  Easting          : Easting (X), in meters                   (input)
+ *  Northing         : Northing (Y), in meters                  (input)
+ *  Latitude         : Latitude, in radians                     (output)
+ *  Longitude        : Longitude, in radians                    (output)
+ *
+ */
+
+  double dy = 0, dx = 0;
+  double rho = 0;
+  double t;
+  double PHI, sin_PHI;
+  double tempPHI = 0.0;
+  double essin;
+  double pow_es;
+  double delta_radius;
+  long Error_Code = POLAR_NO_ERROR;
+  double min_easting = Polar_False_Easting - Polar_Delta_Easting;
+  double max_easting = Polar_False_Easting + Polar_Delta_Easting;
+  double min_northing = Polar_False_Northing - Polar_Delta_Northing;
+  double max_northing = Polar_False_Northing + Polar_Delta_Northing;
+
+  if (Easting > max_easting || Easting < min_easting)
+  { /* Easting out of range */
+    Error_Code |= POLAR_EASTING_ERROR;
+  }
+  if (Northing > max_northing || Northing < min_northing)
+  { /* Northing out of range */
+    Error_Code |= POLAR_NORTHING_ERROR;
+  }
+
+  if (!Error_Code)
+  {
+    dy = Northing - Polar_False_Northing;
+    dx = Easting - Polar_False_Easting;
+
+    /* Radius of point with origin of false easting, false northing */
+    rho = sqrt(dx * dx + dy * dy);   
+    
+    delta_radius = sqrt(Polar_Delta_Easting * Polar_Delta_Easting + Polar_Delta_Northing * Polar_Delta_Northing);
+
+    if(rho > delta_radius)
+    { /* Point is outside of projection area */
+      Error_Code |= POLAR_RADIUS_ERROR;
+    }
+
+    if (!Error_Code)
+    { /* no errors */
+      if ((dy == 0.0) && (dx == 0.0))
+      {
+        *Latitude = PI_OVER_2;
+        *Longitude = Polar_Origin_Long;
+
+      }
+      else
+      {
+        if (Southern_Hemisphere != 0)
+        {
+          dy *= -1.0;
+          dx *= -1.0;
+        }
+
+        if (fabs(fabs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
+          t = rho * tc / (Polar_a_mc);
+        else
+          t = rho * e4 / (two_Polar_a);
+        PHI = PI_OVER_2 - 2.0 * atan(t);
+        while (fabs(PHI - tempPHI) > 1.0e-10)
+        {
+          tempPHI = PHI;
+          sin_PHI = sin(PHI);
+          essin =  es * sin_PHI;
+          pow_es = POLAR_POW(essin);
+          PHI = PI_OVER_2 - 2.0 * atan(t * pow_es);
+        }
+        *Latitude = PHI;
+        *Longitude = Polar_Origin_Long + atan2(dx, -dy);
+
+        if (*Longitude > PI)
+          *Longitude -= TWO_PI;
+        else if (*Longitude < -PI)
+          *Longitude += TWO_PI;
+
+
+        if (*Latitude > PI_OVER_2)  /* force distorted values to 90, -90 degrees */
+          *Latitude = PI_OVER_2;
+        else if (*Latitude < -PI_OVER_2)
+          *Latitude = -PI_OVER_2;
+
+        if (*Longitude > PI)  /* force distorted values to 180, -180 degrees */
+          *Longitude = PI;
+        else if (*Longitude < -PI)
+          *Longitude = -PI;
+
+      }
+      if (Southern_Hemisphere != 0)
+      {
+        *Latitude *= -1.0;
+        *Longitude *= -1.0;
+      }
+    }
+  }
+  return (Error_Code);
+} /* END OF Convert_Polar_Stereographic_To_Geodetic */
+
+
+
diff --git a/geotranz/polarst.h b/geotranz/polarst.h
old mode 100755
new mode 100644
index c194c92..60b8aa0
--- a/geotranz/polarst.h
+++ b/geotranz/polarst.h
@@ -1,202 +1,202 @@
-#ifndef POLARST_H
-  #define POLARST_H
-/***************************************************************************/
-/* RSC IDENTIFIER: POLAR STEREOGRAPHIC 
- *
- *
- * ABSTRACT
- *
- *    This component provides conversions between geodetic (latitude and
- *    longitude) coordinates and Polar Stereographic (easting and northing) 
- *    coordinates.
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid 
- *    value is found the error code is combined with the current error code 
- *    using the bitwise or.  This combining allows multiple error codes to 
- *    be returned. The possible error codes are:
- *
- *          POLAR_NO_ERROR           : No errors occurred in function
- *          POLAR_LAT_ERROR          : Latitude outside of valid range
- *                                      (-90 to 90 degrees)
- *          POLAR_LON_ERROR          : Longitude outside of valid range
- *                                      (-180 to 360 degrees) 
- *          POLAR_ORIGIN_LAT_ERROR   : Latitude of true scale outside of valid
- *                                      range (-90 to 90 degrees)
- *          POLAR_ORIGIN_LON_ERROR   : Longitude down from pole outside of valid
- *                                      range (-180 to 360 degrees)
- *          POLAR_EASTING_ERROR      : Easting outside of valid range,
- *                                      depending on ellipsoid and
- *                                      projection parameters
- *          POLAR_NORTHING_ERROR     : Northing outside of valid range,
- *                                      depending on ellipsoid and
- *                                      projection parameters
- *          POLAR_RADIUS_ERROR       : Coordinates too far from pole,
- *                                      depending on ellipsoid and
- *                                      projection parameters
- *          POLAR_A_ERROR            : Semi-major axis less than or equal to zero
- *          POLAR_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	                  (250 to 350)
- *
- *
- * REUSE NOTES
- *
- *    POLAR STEREOGRAPHIC is intended for reuse by any application that  
- *    performs a Polar Stereographic projection.
- *
- *
- * REFERENCES
- *
- *    Further information on POLAR STEREOGRAPHIC can be found in the
- *    Reuse Manual.
- *
- *
- *    POLAR STEREOGRAPHIC originated from :
- *                                U.S. Army Topographic Engineering Center
- *                                Geospatial Information Division
- *                                7701 Telegraph Road
- *                                Alexandria, VA  22310-3864
- *
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- *
- * RESTRICTIONS
- *
- *    POLAR STEREOGRAPHIC has no restrictions.
- *
- *
- * ENVIRONMENT
- *
- *    POLAR STEREOGRAPHIC was tested and certified in the following
- *    environments:
- *
- *    1. Solaris 2.5 with GCC, version 2.8.1
- *    2. Window 95 with MS Visual C++, version 6
- *
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    06-11-95          Original Code
- *    03-01-97          Original Code
- *
- *
- */
-
-
-/**********************************************************************/
-/*
- *                        DEFINES
- */
-
-  #define POLAR_NO_ERROR                0x0000
-  #define POLAR_LAT_ERROR               0x0001
-  #define POLAR_LON_ERROR               0x0002
-  #define POLAR_ORIGIN_LAT_ERROR        0x0004
-  #define POLAR_ORIGIN_LON_ERROR        0x0008
-  #define POLAR_EASTING_ERROR			  0x0010
-  #define POLAR_NORTHING_ERROR		  0x0020
-  #define POLAR_A_ERROR                 0x0040
-  #define POLAR_INV_F_ERROR             0x0080
-  #define POLAR_RADIUS_ERROR            0x0100
-
-/**********************************************************************/
-/*
- *                        FUNCTION PROTOTYPES
- */
-
-/* ensure proper linkage to c++ programs */
-  #ifdef __cplusplus
-extern "C" {
-  #endif
-
-  long Set_Polar_Stereographic_Parameters (double a,
-                                           double f,
-                                           double Latitude_of_True_Scale,
-                                           double Longitude_Down_from_Pole,
-                                           double False_Easting,
-                                           double False_Northing);
-/*  
- *  The function Set_Polar_Stereographic_Parameters receives the ellipsoid
- *  parameters and Polar Stereograpic projection parameters as inputs, and
- *  sets the corresponding state variables.  If any errors occur, error
- *  code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
- *
- *  a                : Semi-major axis of ellipsoid, in meters         (input)
- *  f                : Flattening of ellipsoid                         (input)
- *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (input)
- *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (input)
- *  False_Easting    : Easting (X) at center of projection, in meters  (input)
- *  False_Northing   : Northing (Y) at center of projection, in meters (input)
- */
-
-
-  void Get_Polar_Stereographic_Parameters (double *a,
-                                           double *f,
-                                           double *Latitude_of_True_Scale,
-                                           double *Longitude_Down_from_Pole,
-                                           double *False_Easting,
-                                           double *False_Northing);
-/*
- * The function Get_Polar_Stereographic_Parameters returns the current
- * ellipsoid parameters and Polar projection parameters.
- *
- *  a                : Semi-major axis of ellipsoid, in meters         (output)
- *  f                : Flattening of ellipsoid                         (output)
- *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (output)
- *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (output)
- *  False_Easting    : Easting (X) at center of projection, in meters  (output)
- *  False_Northing   : Northing (Y) at center of projection, in meters (output)
- */
-
-
-  long Convert_Geodetic_To_Polar_Stereographic (double Latitude,
-                                                double Longitude,
-                                                double *Easting,
-                                                double *Northing);
-/*
- * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic
- * coordinates (latitude and longitude) to Polar Stereographic coordinates
- * (easting and northing), according to the current ellipsoid
- * and Polar Stereographic projection parameters. If any errors occur, error
- * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
- *
- *    Latitude   :  Latitude, in radians                      (input)
- *    Longitude  :  Longitude, in radians                     (input)
- *    Easting    :  Easting (X), in meters                    (output)
- *    Northing   :  Northing (Y), in meters                   (output)
- */
-
-
-
-  long Convert_Polar_Stereographic_To_Geodetic (double Easting,
-                                                double Northing,
-                                                double *Latitude,
-                                                double *Longitude);
-
-/*
- *  The function Convert_Polar_Stereographic_To_Geodetic converts Polar
- *  Stereographic coordinates (easting and northing) to geodetic
- *  coordinates (latitude and longitude) according to the current ellipsoid
- *  and Polar Stereographic projection Parameters. If any errors occur, the
- *  code(s) are returned by the function, otherwise POLAR_NO_ERROR
- *  is returned.
- *
- *  Easting          : Easting (X), in meters                   (input)
- *  Northing         : Northing (Y), in meters                  (input)
- *  Latitude         : Latitude, in radians                     (output)
- *  Longitude        : Longitude, in radians                    (output)
- *
- */
-
-  #ifdef __cplusplus
-}
-  #endif
-
-#endif  /* POLARST_H  */
-
+#ifndef POLARST_H
+  #define POLARST_H
+/***************************************************************************/
+/* RSC IDENTIFIER: POLAR STEREOGRAPHIC 
+ *
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between geodetic (latitude and
+ *    longitude) coordinates and Polar Stereographic (easting and northing) 
+ *    coordinates.
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid 
+ *    value is found the error code is combined with the current error code 
+ *    using the bitwise or.  This combining allows multiple error codes to 
+ *    be returned. The possible error codes are:
+ *
+ *          POLAR_NO_ERROR           : No errors occurred in function
+ *          POLAR_LAT_ERROR          : Latitude outside of valid range
+ *                                      (-90 to 90 degrees)
+ *          POLAR_LON_ERROR          : Longitude outside of valid range
+ *                                      (-180 to 360 degrees) 
+ *          POLAR_ORIGIN_LAT_ERROR   : Latitude of true scale outside of valid
+ *                                      range (-90 to 90 degrees)
+ *          POLAR_ORIGIN_LON_ERROR   : Longitude down from pole outside of valid
+ *                                      range (-180 to 360 degrees)
+ *          POLAR_EASTING_ERROR      : Easting outside of valid range,
+ *                                      depending on ellipsoid and
+ *                                      projection parameters
+ *          POLAR_NORTHING_ERROR     : Northing outside of valid range,
+ *                                      depending on ellipsoid and
+ *                                      projection parameters
+ *          POLAR_RADIUS_ERROR       : Coordinates too far from pole,
+ *                                      depending on ellipsoid and
+ *                                      projection parameters
+ *          POLAR_A_ERROR            : Semi-major axis less than or equal to zero
+ *          POLAR_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	                  (250 to 350)
+ *
+ *
+ * REUSE NOTES
+ *
+ *    POLAR STEREOGRAPHIC is intended for reuse by any application that  
+ *    performs a Polar Stereographic projection.
+ *
+ *
+ * REFERENCES
+ *
+ *    Further information on POLAR STEREOGRAPHIC can be found in the
+ *    Reuse Manual.
+ *
+ *
+ *    POLAR STEREOGRAPHIC originated from :
+ *                                U.S. Army Topographic Engineering Center
+ *                                Geospatial Information Division
+ *                                7701 Telegraph Road
+ *                                Alexandria, VA  22310-3864
+ *
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ *
+ * RESTRICTIONS
+ *
+ *    POLAR STEREOGRAPHIC has no restrictions.
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    POLAR STEREOGRAPHIC was tested and certified in the following
+ *    environments:
+ *
+ *    1. Solaris 2.5 with GCC, version 2.8.1
+ *    2. Window 95 with MS Visual C++, version 6
+ *
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    06-11-95          Original Code
+ *    03-01-97          Original Code
+ *
+ *
+ */
+
+
+/**********************************************************************/
+/*
+ *                        DEFINES
+ */
+
+  #define POLAR_NO_ERROR                0x0000
+  #define POLAR_LAT_ERROR               0x0001
+  #define POLAR_LON_ERROR               0x0002
+  #define POLAR_ORIGIN_LAT_ERROR        0x0004
+  #define POLAR_ORIGIN_LON_ERROR        0x0008
+  #define POLAR_EASTING_ERROR			  0x0010
+  #define POLAR_NORTHING_ERROR		  0x0020
+  #define POLAR_A_ERROR                 0x0040
+  #define POLAR_INV_F_ERROR             0x0080
+  #define POLAR_RADIUS_ERROR            0x0100
+
+/**********************************************************************/
+/*
+ *                        FUNCTION PROTOTYPES
+ */
+
+/* ensure proper linkage to c++ programs */
+  #ifdef __cplusplus
+extern "C" {
+  #endif
+
+  long Set_Polar_Stereographic_Parameters (double a,
+                                           double f,
+                                           double Latitude_of_True_Scale,
+                                           double Longitude_Down_from_Pole,
+                                           double False_Easting,
+                                           double False_Northing);
+/*  
+ *  The function Set_Polar_Stereographic_Parameters receives the ellipsoid
+ *  parameters and Polar Stereograpic projection parameters as inputs, and
+ *  sets the corresponding state variables.  If any errors occur, error
+ *  code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters         (input)
+ *  f                : Flattening of ellipsoid                         (input)
+ *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (input)
+ *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (input)
+ *  False_Easting    : Easting (X) at center of projection, in meters  (input)
+ *  False_Northing   : Northing (Y) at center of projection, in meters (input)
+ */
+
+
+  void Get_Polar_Stereographic_Parameters (double *a,
+                                           double *f,
+                                           double *Latitude_of_True_Scale,
+                                           double *Longitude_Down_from_Pole,
+                                           double *False_Easting,
+                                           double *False_Northing);
+/*
+ * The function Get_Polar_Stereographic_Parameters returns the current
+ * ellipsoid parameters and Polar projection parameters.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters         (output)
+ *  f                : Flattening of ellipsoid                         (output)
+ *  Latitude_of_True_Scale  : Latitude of true scale, in radians       (output)
+ *  Longitude_Down_from_Pole : Longitude down from pole, in radians    (output)
+ *  False_Easting    : Easting (X) at center of projection, in meters  (output)
+ *  False_Northing   : Northing (Y) at center of projection, in meters (output)
+ */
+
+
+  long Convert_Geodetic_To_Polar_Stereographic (double Latitude,
+                                                double Longitude,
+                                                double *Easting,
+                                                double *Northing);
+/*
+ * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic
+ * coordinates (latitude and longitude) to Polar Stereographic coordinates
+ * (easting and northing), according to the current ellipsoid
+ * and Polar Stereographic projection parameters. If any errors occur, error
+ * code(s) are returned by the function, otherwise POLAR_NO_ERROR is returned.
+ *
+ *    Latitude   :  Latitude, in radians                      (input)
+ *    Longitude  :  Longitude, in radians                     (input)
+ *    Easting    :  Easting (X), in meters                    (output)
+ *    Northing   :  Northing (Y), in meters                   (output)
+ */
+
+
+
+  long Convert_Polar_Stereographic_To_Geodetic (double Easting,
+                                                double Northing,
+                                                double *Latitude,
+                                                double *Longitude);
+
+/*
+ *  The function Convert_Polar_Stereographic_To_Geodetic converts Polar
+ *  Stereographic coordinates (easting and northing) to geodetic
+ *  coordinates (latitude and longitude) according to the current ellipsoid
+ *  and Polar Stereographic projection Parameters. If any errors occur, the
+ *  code(s) are returned by the function, otherwise POLAR_NO_ERROR
+ *  is returned.
+ *
+ *  Easting          : Easting (X), in meters                   (input)
+ *  Northing         : Northing (Y), in meters                  (input)
+ *  Latitude         : Latitude, in radians                     (output)
+ *  Longitude        : Longitude, in radians                    (output)
+ *
+ */
+
+  #ifdef __cplusplus
+}
+  #endif
+
+#endif  /* POLARST_H  */
+
diff --git a/geotranz/readme.txt b/geotranz/readme.txt
old mode 100755
new mode 100644
index 61b26c2..fd2488a
--- a/geotranz/readme.txt
+++ b/geotranz/readme.txt
@@ -1,41 +1,41 @@
-GEOTRANS 2.4.2
-
-The GEOTRANS 2.4.2 software was developed and tested on Windows XP, Solaris 7, Red Hat Linux Professional 9, and SuSE Linux 9.3 platforms, and should function correctly on more recent versions of those operating systems.
-
-There are eight different GEOTRANS 2.4.2 distribution files - four for Windows, in zip format, and four for UNIX/Linux, in tar/gzip format:
-
-win_user.zip - Windows end-user's package
-win_dev.zip - Windows developer's package
-master.zip - master copy (everything) in zip format
-mgrs.zip - MGRS & supporting projections in zip format
-
-unix_user.tgz - UNIX/Linux end-user's package
-unix_dev.tgz - UNIX/Linux developer's package
-master.tgz - master copy (everything) in tar/gzip format
-mgrs.tgz - MGRS & supporting projections in tar/gzip
-
-The Windows packages omit the UNIX/Linux-specific directories, while the UNIX/Linux packages omit the Windows-specific directories.
-
-The end-user packages contain only executables, DLLs or shared object libraries, documentation, and supporting data.
-
-The developer packages also include source code, as well as makefiles, MS Visual C++ workspace and project files, and everything else necessary to build the libraries and executables.  
-
-The master packages can be considered to be cross-platform developer packages.  They both contain the union of the Windows and UNIX/Linux developer packages.  Only their format is different.  
-
-The MGRS packages contain only the source code for the MGRS, UTM, UPS, Transverse Mercator, and Polar Stereographic conversion modules, and are intended for developers who only want to do MGRS conversions.  Their content is identical.  Only their format is different.
-
-You should only need to copy one of these packages, depending on your platform and your intended usage.
-
-GEOTRANS Terms of Use:
-
-1. The GEOTRANS source code ("the software") is provided free of charge by the National Geospatial-Intelligence Agency (NGA) of the United States Department of Defense. Although NGA makes no copyright claim under Title 17 U.S.C., NGA claims copyrights in the source code under other legal regimes. NGA hereby grants to each user of the software a license to use and distribute the software, and develop derivative works.
-
-2. NGA requests that products developed using the software credit the source of the software with the following statement, "The product was developed using GEOTRANS, a product of the National Geospatial-Intelligence Agency (NGA) and U.S. Army Engineering Research and Development Center."  Do not use the name GEOTRANS for any derived work.
-
-3. Warranty Disclaimer: The software was developed to meet only the internal requirements of the National Geospatial-Intelligence Agency (NGA). The software is provided "as is," and no warranty, express or implied, including but not limited to the implied warranties of merchantability and fitness for particular purpose or arising by statute or otherwise in law or from a course of dealing or usage in trade, is made by NGA as to the accuracy and functioning of the software.
-
-4. NGA and its personnel are not required to provide technical support or general assistance with respect to public use of the software.  Government customers may contact NGA.
-
-5. Neither NGA nor its personnel will be liable for any claims, losses, or damages arising from or connected with the use of the software. The user agrees to hold harmless the United States National Geospatial-Intelligence Agency (NGA). The user's sole and exclusive remedy is to stop using the software.
-
-6. Please be advised that pursuant to the United States Code, 10 U.S.C. 425, the name of the National Geospatial-Intelligence Agency, the initials "NGA", the seal of the National Geospatial-Intelligence Agency, or any colorable imitation thereof shall not be used to imply approval, endorsement, or authorization of a product without prior written permission from United States Secretary of Defense.  Do not create the impression that NGA, the Secretary of Defense or the Director of National [...]
+GEOTRANS 2.4.2
+
+The GEOTRANS 2.4.2 software was developed and tested on Windows XP, Solaris 7, Red Hat Linux Professional 9, and SuSE Linux 9.3 platforms, and should function correctly on more recent versions of those operating systems.
+
+There are eight different GEOTRANS 2.4.2 distribution files - four for Windows, in zip format, and four for UNIX/Linux, in tar/gzip format:
+
+win_user.zip - Windows end-user's package
+win_dev.zip - Windows developer's package
+master.zip - master copy (everything) in zip format
+mgrs.zip - MGRS & supporting projections in zip format
+
+unix_user.tgz - UNIX/Linux end-user's package
+unix_dev.tgz - UNIX/Linux developer's package
+master.tgz - master copy (everything) in tar/gzip format
+mgrs.tgz - MGRS & supporting projections in tar/gzip
+
+The Windows packages omit the UNIX/Linux-specific directories, while the UNIX/Linux packages omit the Windows-specific directories.
+
+The end-user packages contain only executables, DLLs or shared object libraries, documentation, and supporting data.
+
+The developer packages also include source code, as well as makefiles, MS Visual C++ workspace and project files, and everything else necessary to build the libraries and executables.  
+
+The master packages can be considered to be cross-platform developer packages.  They both contain the union of the Windows and UNIX/Linux developer packages.  Only their format is different.  
+
+The MGRS packages contain only the source code for the MGRS, UTM, UPS, Transverse Mercator, and Polar Stereographic conversion modules, and are intended for developers who only want to do MGRS conversions.  Their content is identical.  Only their format is different.
+
+You should only need to copy one of these packages, depending on your platform and your intended usage.
+
+GEOTRANS Terms of Use:
+
+1. The GEOTRANS source code ("the software") is provided free of charge by the National Geospatial-Intelligence Agency (NGA) of the United States Department of Defense. Although NGA makes no copyright claim under Title 17 U.S.C., NGA claims copyrights in the source code under other legal regimes. NGA hereby grants to each user of the software a license to use and distribute the software, and develop derivative works.
+
+2. NGA requests that products developed using the software credit the source of the software with the following statement, "The product was developed using GEOTRANS, a product of the National Geospatial-Intelligence Agency (NGA) and U.S. Army Engineering Research and Development Center."  Do not use the name GEOTRANS for any derived work.
+
+3. Warranty Disclaimer: The software was developed to meet only the internal requirements of the National Geospatial-Intelligence Agency (NGA). The software is provided "as is," and no warranty, express or implied, including but not limited to the implied warranties of merchantability and fitness for particular purpose or arising by statute or otherwise in law or from a course of dealing or usage in trade, is made by NGA as to the accuracy and functioning of the software.
+
+4. NGA and its personnel are not required to provide technical support or general assistance with respect to public use of the software.  Government customers may contact NGA.
+
+5. Neither NGA nor its personnel will be liable for any claims, losses, or damages arising from or connected with the use of the software. The user agrees to hold harmless the United States National Geospatial-Intelligence Agency (NGA). The user's sole and exclusive remedy is to stop using the software.
+
+6. Please be advised that pursuant to the United States Code, 10 U.S.C. 425, the name of the National Geospatial-Intelligence Agency, the initials "NGA", the seal of the National Geospatial-Intelligence Agency, or any colorable imitation thereof shall not be used to imply approval, endorsement, or authorization of a product without prior written permission from United States Secretary of Defense.  Do not create the impression that NGA, the Secretary of Defense or the Director of National [...]
diff --git a/geotranz/releasenotes.txt b/geotranz/releasenotes.txt
old mode 100755
new mode 100644
index c85b324..ab863a9
--- a/geotranz/releasenotes.txt
+++ b/geotranz/releasenotes.txt
@@ -1,465 +1,465 @@
-GEOTRANS Release Notes
-
-Release 2.0.2 - November 1999
-
-1. The datum parameter file 3_param.dat was changed to correct an error in the 
-latitude bounds for the NAD 27 Canada datum.
-
-2. The MGRS module was changed to make the final latitude check on MGRS to UTM 
-conversions sensitive to the precision of the input MGRS coordinate string.  The 
-lower the input precision, the more "slop" is allowed in the final check on the 
-latitude zone letter.  This is to handle an issue raised by some F-16 pilots, 
-who truncate MGRS strings that they receive from the Army.  This truncation can 
-put them on the wrong side of a latitude zone boundary, causing the truncated 
-MGRS string to be considered invalid.  The correction causes truncated strings 
-to be considered valid if any part of the square which they denote lies within 
-the latitude zone specified by the third letter of the string.
-
-Release 2.0.3 - April 2000
-
-1. Problems with the GEOTRANS file processing capability, including problems 
-reading coordinate system/projection parameters, and problems with some 
-coordinates being skipped.  Note that spaces must separate individual coordinate 
-values.
-
-2. The Bonne projection module has been changed to return an error when the 
-Origin Latitude parameter is set to zero.  In the next release, the Sinusoidal 
-projection will be used in this situation.
-
-3. Reported errors in certain cases of conversions between geodetic and MGRS 
-have been corrected.  The error actually occurred in the formatting of the 
-geodetic output.
-
-4. The Equidistant Cylindrical projection parameter that was previously called 
-Origin Latitude has been renamed to Standard Parallel, which more correctly 
-reflects its role in the projection.  The Origin Latitude for the Equidistant 
-Cylindrical projection is always zero (i.e., the Equator).  Error messages and 
-documentation were updated accordingly.  Note that the renaming of this 
-parameter is the only change to the external interface of the GEOTRANS Engine in 
-this release.
-
-5. An error in the method selection logic for datum transformations, in the 
-Datum module, has been corrected.  This error caused the Molodensky method to be 
-used when transforming between the two 7-parameter datums (EUR-7 and OGB-7) and 
-WGS 84.
-
-6. The datum parameter file 3_param.dat was changed to correct the names of 
-several South American (S-42) datums.
-
-7. A leftover debug printf statement in the Geoid module was removed.
-
-8. Several multiple declaration errors that prevented the Motif GUI source code 
-from being compiled successfully using the Gnu g++ compiler were corrected.  
-Additional comments were added to the make file for the GEOTRANS application to 
-facilitate switching between Sun and Gnu compilers.
-
-9. Comments were also added to the make file for the GEOTRANS application 
-concerning locating the libXpm shared object library.  This library, which 
-supports the use of X Window pixmaps, was moved to /usr/local/opt/xpm/lib under 
-Solaris 2.6.
-
-10. Documentation for the Local Cartesian module was corrected so that this 
-coordinate system is no longer referred to as a projection.
-
-11. The usage example in the GEOTRANS Engine Reuse Manual was corrected so that 
-it now compiles successfully.
-
-Release 2.1 - June 2000
-
-1. The geoid separation file has been converted from text to binary form and 
-renamed to egm96.grd to better reflect its implementation of the Earth Gravity 
-Model 1996.  The new binary file is less than half the size of the text file 
-(~4MB vs ~10MB), and is loaded much more quickly when GEOTRANS is started.
-
-2. Inverse flattening is now used as a primary ellipsoid parameter, along with 
-the semi-major axis, instead of the semi-minor axis.  Previously, the inverse 
-flattening was computed from the semi-major and semi-minor axes.  This is a more 
-correct approach and improves overall accuracy slightly.
-
-3. User-defined datums and ellipsoids can now be deleted.  A user-defined 
-ellipsoid can only be deleted if it is not used by any user-defined datum.
-
-4. Additional datum and ellipsoid parameter functions have been added to the 
-external interface of the GEOTRANS Engine, for use by applications.
-
-5. For Windows, a GEOTRANS dynamically linked library (DLL) is now provided 
-which includes the GEOTRANS Engine and all of the DT&CC modules.  A version of 
-the GEOTRANS application, geotrans2d.exe, which uses the DLL is also provided.
-Similarly, for UNIX, a GEOTRANS shared object (.so) library is provided, along 
-with a version of the GEOTRANS application, geotrans2d, which uses the shared 
-object library.
-
-6. The Bonne projection now defaults to the Sinusoidal projection when the origin 
-latitude is zero.
-
-7. A "No Height" option has been added for Geodetic coordinates, as an alternative 
-to "Ellipsoid Height" and "Geoid/MSL Height".  When "No Height" is selected on 
-input, the contents of the Height field is ignored.  When "No Height" is selected 
-on output, no Height value is output.
-
-8. Three new projections have been added:  Azimuthal Equidistant, (Oblique) Gnomonic, 
-and Oblique Mercator.  The only difference between Gnomonic and Oblique Gnomonic 
-is the value of the original latitude parameter.
-
-9. The Windows and Motif GUIs have been updated to make the screen layouts more 
-consistent.  Bidirectional conversion between the upper and lower panels is now 
-supported, using two Convert buttons (Upper-to-Lower and Lower-to-Upper).  Error 
-values are shown separately for each panel.
-
-10. A bug in the MGRS module that occasionally caused 100km errors was corrected.
-Easting and northing values greater than 99999.5 (i.e., less then 1/2m from the
-eastern or northern boundary of a 100km square) were being set to zero, but not 
-moved into the adjacent 100km square.  These values are now rounded to 99999.
-
-11. Documentation and on-line help has been updated to reflect all of the above 
-enhancements.
-
-Release 2.2 - September 2000
-
-1.  The datum code for WGS 72 has been corrected from "WGD" to "WGC".
-
-2.  The default initial output coordinate system type has been changed from 
-Mercator to UTM.
-
-3.  A bug in the Windows GUI that prevented degrees/minutes and decimal degrees
-formats from being selected has been corrected.
-
-4.  In the Windows GUI, the initial default value for inverse flattening and the 
-associated label in the Create Ellipsoid dialog box have been corrected.
-
-5.  A bug in the Windows GUI that allowed multiple Geodetic Height type radio 
-buttons to be selected in the File Processing dialog box has been corrected.
-
-6.  Diagrams showing MGRS grid zone, band, and 100,000m square layouts have been
-added to the Users' Guide and the on-line help.
-
-7.  An error in the implementation of Oblique Mercator has been corrected.
-
-8.  Four new projections have been added:  Ney's (Modified Lambert Conformal Conic), 
-Stereographic, British National Grid, and New Zealand Map Grid.
-
-9.  A prototype Java GUI has been added which runs on both Windows and UNIX 
-platforms.  It requires a Java run-time environment. To run it on a Windows
-platform, go to the /geotrans2/win95 directory and double click on the file 
-geo_22.jar.  To run it on a Solaris platform, cd to the /geotrans2/unix directory 
-and enter the command: make -f javamake.  It may be necessary to edit the 
-javamake file to point to the location of the Java run-time environment on your 
-system. 
-  
-Release 2.2.1 - June 2001
-
-1. Fixed problem(s) in Local Cartesian conversions.
-
-2. Corrected a rounding problem in MGRS coordinates when the output precision was 
-set coarser than 1m (10m, 100m, etc.), and the point being converted rounded to 
-the eastern or northern edge of a 100,000m square. An illegal MGRS string could 
-be produced, with an odd number of digits including a "1" followed by one or more 
-zeros.  This was corrected by rounding the UTM easting or northing BEFORE 
-determining the correct 100,000m square.
-
-3. Corrected a very old error in the determination of the 100,000m square from a
-UPS easting in the easternmost part of the south polar zone.
-
-4. Added more flexible support for delimiters in input files (commas, tabs, spaces) 
-
-5. Corrected a problem in reporting invalid northing errors in UTM.
-
-6. Correct an example in the Polar Stereo reuse manual with Latitude of True Scale 
-erroneously set to 0.0.
-
-7. Removed an invalid Central Meridian line from the header of the Mollweide 
-example input file.
-
-8. Added a special F-16 Grid Reference System, a special version of MGRS.
-
-9. Allowed 90% CE, 90% LE, and 90% SE accuracy values for input coordinates to be 
-specified.  These are used, along with datum transformation accuracy information, 
-in deriving the output coordinate accuracy values.
-
-10. Added a pull-down menu of coordinate sources, including GPS, maps, and digital 
-geospatial data which can be selected to automatically set input accuracy values.
-
-11. Improved the Java GUI to be fully functional, including support for file 
-processsing and improvements in its appearance.
-
-Release 2.2.2 - February 2002
-
-1.  Added two new datums from Amendment 1 to NIMA TR8350.2 (Jan 2001) to 3_param.dat file:
-     - Korean Geodetic Datum 1995 (KGS)
-     - SIRGAS, South America (SIR)
-    Corrected ellipsoid code errors in 3_param.dat file:
-     - Ellipsoid used with TIL datum changed from EA to EB,
-     - Ellipsoid used with IND-P datum changed from EA to EF.
-
-2.  Corrected an "off-by-one" error in the datum index validity check function in
-the GEOTRANS engine, which prevented the last datum in the pull-down list from being
-used.  The Java GUI reported this error, but the Windows and Motif GUIs did not.
-
-3.  Processing of input coordinate files was made more flexible and forgiving:
-     - Case sensitivity of keywords and name strings was eliminated.
-     - Height values with geodetic coordinates were made optional, defaulting 
-        to zero.
-     - Coordinate reference system names were made consistent with the GUI 
-        pull-down menus.
-     - File processing error messages were improved.
-
-4.  A warnings count was added to the file processing GUI.
-
-
-5.  Geodetic height fields are grayed out and the No Height selection is forced 
-whenever 3D conversion is not feasible.  For 3D conversion to be feasible, Geodetic, 
-Geocentric, or Local Cartesian must be selected on both panels.  For file processing, 
-the output Geodetic height field is grayed out and the No Height selection is forced 
-whenever the input coordinate reference system is not a 3D system.
-
-6.  File header generation, using a modified version of the File Processing GUI, was 
-added. (Java GUI Only)
-
-7.  Some results of the review of GEOTRANS by NIMA G&G were implemented:
-     - In the User�s Guide (and on-line help), the description of the use of 3-step 
-        method, rather than Molodenski, in polar regions was reworded.
-     - In the User's Guide (and on-line help), the description of how to specify 
-        Lambert Conformal Conic projection with one standard parallel was clarified.
-     - UTM zone fields were enabled independent of the state of the Override buttons, 
-        with default values of zero, and the valid range of zone numbers (1-60) was 
-        added to the zone field labels.
-     - In the Sources pull-down menus, the values for GPS PPS and GPS SPS were corrected 
-        to be the same, reflecting the shutting off of GPS selective availability (SA).
-     - The words �Warning:� or �Error:�, as appropriate, were explicitly included in 
-        all messages output by the GUIs.
-
-Release 2.2.3 - February 2003
-
-There were no changes made to the external interfaces of the GEOTRANS libraries.
-
-1.   The ellipsoid (ellipse.dat) and datum (3_param.dat) files were updated to correct
-several typos.  Dates were added to all ellipsoid names.
-
-2.   A problem in the MGRS module (mgrs.c) was corrected.  This problem occurred only
-when converting from geodetic to MGRS coordinates that round to the centerline of zone 
-31V.  This zone is �cut in half�, such that its centerline is also its eastern boundary.
-Points that are rounded up (eastward) to this boundary are considered to lie in zone 32V.
-
-3.   An error in the Local Cartesian module (loccart.c) was corrected to properly take 
-into account the longitude of the local Cartesian coordinate system origin in converting 
-between geocentric and local Cartesian coordinates.
-
-4.   A possible problem in the Transverse Mercator module (tranmerc.c) concerning 
-projections at the poles was investigated.  Points at the poles are projected when the 
-Transverse Mercator module is initialized in order to determine the range of valid inputs 
-for the inverse projection.  The tangent of the latitude is calculated, which should be 
-infinite at the poles.  Investigation determined that the tangent functions for both 
-Windows and Solaris actually return very large values in this case, which result in the 
-expected behavior.  However, to avoid this problem on other platforms, the maximum valid 
-latitude for the Transverse Mercator projection was reduced from 90 to 89.99 degrees. 
-
-5.   A reported incompatibility between GEOTRANS 2.2.2 and version 4 of the Boeing 
-Autometric EDGE Viewer on Windows platforms was investigated.  This version of the EDGE 
-Viewer includes the GEOTRANS 2.0.3 libraries.  When the EDGE Viewer is installed, it 
-sets the GEOTRANS environment variables to reference the directory C:/Program Files/Autometric/EDGEViewer/Data/GeoTrans. This overrides the default setting in the 
-GEOTRANS application, causing it to look for its required data files in the EDGE Viewer 
-directory.  The incompatibility arises from the fact that the Earth Gravity Model 1996 
-geoid separation file was renamed and changed from text to binary form in GEOTRANS 2.1,
-which reduced its size from 10MB to 4MB.  When the newer binary file is not found in 
-the EDGE data directory, the GEOTRANS application fails to initialize successfully.  
-Placing a copy of the binary geoid separation file (egm96.grd) into the EDGE Viewer 
-data directory eliminates the problem.  A recent update to the EDGE Viewer eliminates 
-the problem by using a more recent version of the GEOTRANS libraries.  Therefore no 
-changes to the GEOTRANS software were necessary.
-
-6.   The source data accuracy values for GPS PPS and SPS modes were updated from 10m 
-to 20m.
-
-7.   The Linear (i.e., vertical) Error (LE) and Spherical Error (SE) fields are now 
-grayed out whenever a conversion is not three-dimensional.
-
-Release 2.2.4 - August 2003
-
-There were no changes made to the external interfaces of the GEOTRANS libraries.
-
-1.   Minor changes were made to source code to eliminate all compilation warnings. 
-These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application 
-Windows GUI, and GEOTRANS application support source code file.
-
-2.   A bug in the MGRS module was corrected.  This bug caused MGRS coordinates located 
-in small triangular areas north of 64S latitude, and south of the 2,900,000 northing 
-line, to fail to convert correctly to UTM.
-
-3.  The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used 
-with the Bessel 1841 Namibia (BN) ellipsoid.  On-line help was corrected to be consistent 
-with this change.
-
-4.  The MGRS module was updated to eliminate inconsistencies in the conversion of points 
-located on UTM zone and MGRS latitude band boundaries.
-
-5.  The MGRS module was reorganized internally to improve the clarity and efficiency of 
-the source code.  The external interface of the MGRS module was not changed.
-
-Release 2.2.4 - August 2003
-
-There were no changes made to the external interfaces of the GEOTRANS libraries.
-
-1.   Minor changes were made to source code to eliminate all compilation warnings. 
-These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application 
-Windows GUI, and GEOTRANS application support source code file.
-
-2.   A bug in the MGRS module was corrected.  This bug caused MGRS coordinates located 
-in small triangular areas north of 64S latitude, and south of the 2,900,000 northing 
-line, to fail to convert correctly to UTM.
-
-3.  The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used 
-with the Bessel 1841 Namibia (BN) ellipsoid.  On-line help was corrected to be consistent 
-with this change.
-
-4.  The MGRS module was updated to eliminate inconsistencies in the conversion of points 
-located on UTM zone and MGRS latitude band boundaries.
-
-5.  The MGRS module was reorganized internally to improve the clarity and efficiency of 
-the source code.  The external interface of the MGRS module was not changed.
-Release 2.2.4 - August 2003
-
-There were no changes made to the external interfaces of the GEOTRANS libraries.
-
-1.   Minor changes were made to source code to eliminate all compilation warnings. 
-These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application 
-Windows GUI, and GEOTRANS application support source code file.
-
-2.   A bug in the MGRS module was corrected.  This bug caused MGRS coordinates located 
-in small triangular areas north of 64S latitude, and south of the 2,900,000 northing 
-line, to fail to convert correctly to UTM.
-
-3.  The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used 
-with the Bessel 1841 Namibia (BN) ellipsoid.  On-line help was corrected to be consistent 
-with this change.
-
-4.  The MGRS module was updated to eliminate inconsistencies in the conversion of points 
-located on UTM zone and MGRS latitude band boundaries.
-
-5.  The MGRS module was reorganized internally to improve the clarity and efficiency of 
-the source code.  The external interface of the MGRS module was not changed.
-
-Release 2.2.5 - June 2004
-
-There were no changes made to the external interfaces of the GEOTRANS libraries.
-
-1.   A minor correction was made in the Datum module, to correct an "off-by-one" error in the Valid_Datum function, which caused it to return a warning when the last 3-parameter datum was accessed.
-
-2.   A minor correction was made in the Round_DMS function, in the common application GUI support software, which caused incorrect geodetic coordinate values to be displayed when converting to degrees with a precision of .0001.
-
-3    The 3-parameter datum file, 3_param.dat, was updated to reflect new parameter values for the MID (MIDWAY ASTRO 1961, Midway Is.) datum, which went into effect in June 2003.  The longitude limits for the NAS-U (North American 1927, Greenland), the DAL (Dabola, Guinea) and the TRN (Astro Tern Island (Frig) 1961 datums were also corrected.
-
-Release 2.2.6 - June 2005
-
-1.  A minor correction was made in the Get_Geoid_Height function in the GEOID module, which does bilinear interpolation of EGM96 geoid separation values.  The error caused the specified point to be shifted in longitude, mirrored around the east-west midline of the 15-minute grid cell that contains it.
-
-2.  The 3-parameter datum file, 3_param.dat, was updated to correct the latitude and longitude limits for the DID (Deception Island), EUR-S (European 1950, Israel and Iraq), and KEG (Kerguelen Island) datums.
-
-3.  A minor correction was made in the Convert_Orthographic_to_Geodetic function in the ORTHOGR module to use the two-argument arctangent function (atan2) rather than the single-argument arctangent function (atan).  This avoids sign errors in results near the poles.
-
-4.  A minor correction was made in the Convert_Albers_to_Geodetic function in the ALBERS module to avoid infinite loops when the iterative solution for latitude fails to converge.  After 30 iterations, the function now returns an error status.
-
-5.  Support was added for the Lambert Conformal Conic projection with one standard parallel.  A new LAMBERT_1 module was added, and the LAMBERT_2 module was renamed (from LAMBERT) and reengineered to use the new LAMBERT_1 module.  Backward compatibility was maintained at the DT&CC and GEOTRANS Engine levels.  However, all existing functions that include "Lambert", rather than "Lambert1" or "Lambert2", in their names are now considered to be deprecated, and will be removed in a future update.
-
-6. The GEOTRANS application GUI was enhanced to help users avoid incompatible combinations of coordinate systems and datums by color coding the conversion buttons.  Red indicates that the selected coordinate systems and datums are not compatible with one another, and that an error message will result from any attempted conversion operation.  Yellow indicates that the selected datums have disjoint areas of validity, adn that a warning message will result from any attempted conversion operation.
-
-7. A correction was made to the Geodetic_Shift_WGS72_To_WGS84 and Geodetic_Shift_WGS72_To_WGS84 functions in the DATUM module to wrap longitude values across the 180 degree line and wrap latitude values over the poles. 
-
-8. File processing examples in the online help, and in the /examples subdirectory, were improved to use more realistic coordinates, and additional examples were added.
-
-9. The GEOTRANS application GUI was enhanced to provide an option to display leading zeroes on output geodetic coordinate values, including degrees (three digits for longitude, two digits for latitude), minutes (two digits), and seconds (two digits).
-
-Release 2.3 - March 2006
-
-1. The 3-parameter datum file, 3_param.dat, was updated to correct the latitude and longitude limits for DID (Deception Island), switching longitude order, and JOH (Johnston Island) datums.
-
-2. An �off-by-one� error in datum indexing in the JNI interface was corrected.
-
-3. Support for Red Hat Linux (Red Hat Professional 9.3 and later) and for SuSE Linux (SuSE Linux 9 and later) was added.
- 
-4. A reported potential error in file name string underflow/overflow in the Ellipsoid, Datum, and Geoid modules was corrected.
-
-5. Support for multithreading was improved by adding mutual exclusion zones around code that opens and reads data files in the Ellipsoid, Datum, and Geoid modules.
-
-6. Support for three additional gravity-related height models was added, based on requirements from the US military services:
-a. EGM96 with variable grid spacing and natural spline interpolation
-b. EGM84 with 10 degree by 10 degree grid spacing and bilinear interpolation
-c. EGM84 with 10 degree by 10 degree grid spacing and natural spline interpolation
-These are in addition to EGM96 with 15-minute grid spacing and bilinear interpolation, which was previously supported.
-
-Release 2.4 - September 2006
-
-1. The 3-parameter datum file, 3_param.dat, was updated to correct two spelling errors (Columbia -> Colombia, Phillipines -> Philippines).
-
-2. The 3-parameter datum file, 3_param.dat, was updated to adjust the limits for several local datums in the 3-parameter datum file (ADI-E, CAC, CAI, COA, HJO, ING-A, KEG, LCF, NDS, SAE, SAN-A, SAN-C, VOI, and VOR).
-
-3. All required ellipsoid, datum, and geoid data files to a /data subdirectory to eliminate the need for duplicate copies.
-
-4. Error reporting was improved when required ellipsoid, datum, and/or geoid data files cannot be located at initialization.
-
-5. In the Geoid module, problems were corrected in the interpolation of geoid separation values using a variable-resolution grid when converting to or from MSL-EGM96-VG-NS Height.
-
-6. In the MGRS module, a problem was corrected in rounding up to the equator when converting to MGRS.
-
-7. Support was added for the U.S. National Grid (USNG).
-
-8. Support was added for the Global Area Reference System (USNG).
-
-Release 2.4.1 - March 2007
-
-1. Corrected two minor errors (6cm and 1cm) in the values contained in the EGM84 geoid 
-separation file.
-
-2. Improved error handling and reporting in the Transverse Mercator, UTM, and MGRS 
-modules at extreme northern and southern latitudes, for points that are more than 9 
-degrees, but less than 400km, from the central meridian.
-
-3. Modified the US National Grid (USNG) module to truncate, rather than round, USNG 
-coordinates as required by the USNG specification.
-
-4. Corrected an error in the calculation of valid ranges for input easting and northing 
-coordinates in the Mercator module, and several other map projection modules.  This 
-caused valid inputs to be rejected when extremely large (e.g., 20,000,000m) false easting 
-or false northing values were specified for those map projections.
-
-5. Improved error handling and reporting in the Lambert Conformal Conic modules in cases 
-of extremely small scale factor values.
-
-6. Corrected an error in the MGRS module that occurred when a point rounded up to the 
-eastern boundary of the non-standard zone 31V in the northern Atlantic, which is 
-considered to be part of zone 32V.
-
-Release 2.4.2 - August 2008
-
-1.  Corrected an error in the MGRS and USNG modules that incorrectly mapped 100,000m square 
-row letters to northing values in the northern portion of the X latitude band (northings > 9,000,000m).
-
-2.  Revised the handling of warnings reported by the Transverse Mercator (TM) module for points
-located more than 9 degrees from the central meridian, when the TM module is invoked by the UTM and
-MGRS modules, so that UTM or MGRS error checking takes precedence.
-
-3.  Added datum domain checks for those cases where no datum transformations are performed.  
-Previously, coordinates were not checked against the valid domain of the datum when the input datum 
-and output datum were identical.
-
-4.  The default accuracy estimate values for DTED Level 1 and DTED Level 2 were updated to be consistent
-with MIL-PRF-89020B, Performance Specification, Digital Terrain Elevation Data (DTED), 23 May 2000, replacing 
-the values from MIL-PRF-89020A, 19 Apr 1996.  Default spherical accuracy estimate values for all relevant data 
-sources were updated to reflect a more accurate relationship to the corresponding circular (horizontal) 
-accuracy estimates.
-
-5.  In the GEOTRANS application, added commands to save the current selections and options settings as 
-the defaults, and to reset the current selections and options settings from the defaults.
-
-6.  In the GEOTRANS application, added capabilities to create and delete user-defined 7-parameter datums.
-
-7.  Corrected a problem with the checking of input coordinates against the valid region for a local datum 
-when a longitude value greater than +180 degrees was entered.
-
-8.  Corrected the valid regions for the PUK and NAR-E datums to use a range of longitudes that span the 
-+180/-180 degree line.
-
-
-   
-
-
-
+GEOTRANS Release Notes
+
+Release 2.0.2 - November 1999
+
+1. The datum parameter file 3_param.dat was changed to correct an error in the 
+latitude bounds for the NAD 27 Canada datum.
+
+2. The MGRS module was changed to make the final latitude check on MGRS to UTM 
+conversions sensitive to the precision of the input MGRS coordinate string.  The 
+lower the input precision, the more "slop" is allowed in the final check on the 
+latitude zone letter.  This is to handle an issue raised by some F-16 pilots, 
+who truncate MGRS strings that they receive from the Army.  This truncation can 
+put them on the wrong side of a latitude zone boundary, causing the truncated 
+MGRS string to be considered invalid.  The correction causes truncated strings 
+to be considered valid if any part of the square which they denote lies within 
+the latitude zone specified by the third letter of the string.
+
+Release 2.0.3 - April 2000
+
+1. Problems with the GEOTRANS file processing capability, including problems 
+reading coordinate system/projection parameters, and problems with some 
+coordinates being skipped.  Note that spaces must separate individual coordinate 
+values.
+
+2. The Bonne projection module has been changed to return an error when the 
+Origin Latitude parameter is set to zero.  In the next release, the Sinusoidal 
+projection will be used in this situation.
+
+3. Reported errors in certain cases of conversions between geodetic and MGRS 
+have been corrected.  The error actually occurred in the formatting of the 
+geodetic output.
+
+4. The Equidistant Cylindrical projection parameter that was previously called 
+Origin Latitude has been renamed to Standard Parallel, which more correctly 
+reflects its role in the projection.  The Origin Latitude for the Equidistant 
+Cylindrical projection is always zero (i.e., the Equator).  Error messages and 
+documentation were updated accordingly.  Note that the renaming of this 
+parameter is the only change to the external interface of the GEOTRANS Engine in 
+this release.
+
+5. An error in the method selection logic for datum transformations, in the 
+Datum module, has been corrected.  This error caused the Molodensky method to be 
+used when transforming between the two 7-parameter datums (EUR-7 and OGB-7) and 
+WGS 84.
+
+6. The datum parameter file 3_param.dat was changed to correct the names of 
+several South American (S-42) datums.
+
+7. A leftover debug printf statement in the Geoid module was removed.
+
+8. Several multiple declaration errors that prevented the Motif GUI source code 
+from being compiled successfully using the Gnu g++ compiler were corrected.  
+Additional comments were added to the make file for the GEOTRANS application to 
+facilitate switching between Sun and Gnu compilers.
+
+9. Comments were also added to the make file for the GEOTRANS application 
+concerning locating the libXpm shared object library.  This library, which 
+supports the use of X Window pixmaps, was moved to /usr/local/opt/xpm/lib under 
+Solaris 2.6.
+
+10. Documentation for the Local Cartesian module was corrected so that this 
+coordinate system is no longer referred to as a projection.
+
+11. The usage example in the GEOTRANS Engine Reuse Manual was corrected so that 
+it now compiles successfully.
+
+Release 2.1 - June 2000
+
+1. The geoid separation file has been converted from text to binary form and 
+renamed to egm96.grd to better reflect its implementation of the Earth Gravity 
+Model 1996.  The new binary file is less than half the size of the text file 
+(~4MB vs ~10MB), and is loaded much more quickly when GEOTRANS is started.
+
+2. Inverse flattening is now used as a primary ellipsoid parameter, along with 
+the semi-major axis, instead of the semi-minor axis.  Previously, the inverse 
+flattening was computed from the semi-major and semi-minor axes.  This is a more 
+correct approach and improves overall accuracy slightly.
+
+3. User-defined datums and ellipsoids can now be deleted.  A user-defined 
+ellipsoid can only be deleted if it is not used by any user-defined datum.
+
+4. Additional datum and ellipsoid parameter functions have been added to the 
+external interface of the GEOTRANS Engine, for use by applications.
+
+5. For Windows, a GEOTRANS dynamically linked library (DLL) is now provided 
+which includes the GEOTRANS Engine and all of the DT&CC modules.  A version of 
+the GEOTRANS application, geotrans2d.exe, which uses the DLL is also provided.
+Similarly, for UNIX, a GEOTRANS shared object (.so) library is provided, along 
+with a version of the GEOTRANS application, geotrans2d, which uses the shared 
+object library.
+
+6. The Bonne projection now defaults to the Sinusoidal projection when the origin 
+latitude is zero.
+
+7. A "No Height" option has been added for Geodetic coordinates, as an alternative 
+to "Ellipsoid Height" and "Geoid/MSL Height".  When "No Height" is selected on 
+input, the contents of the Height field is ignored.  When "No Height" is selected 
+on output, no Height value is output.
+
+8. Three new projections have been added:  Azimuthal Equidistant, (Oblique) Gnomonic, 
+and Oblique Mercator.  The only difference between Gnomonic and Oblique Gnomonic 
+is the value of the original latitude parameter.
+
+9. The Windows and Motif GUIs have been updated to make the screen layouts more 
+consistent.  Bidirectional conversion between the upper and lower panels is now 
+supported, using two Convert buttons (Upper-to-Lower and Lower-to-Upper).  Error 
+values are shown separately for each panel.
+
+10. A bug in the MGRS module that occasionally caused 100km errors was corrected.
+Easting and northing values greater than 99999.5 (i.e., less then 1/2m from the
+eastern or northern boundary of a 100km square) were being set to zero, but not 
+moved into the adjacent 100km square.  These values are now rounded to 99999.
+
+11. Documentation and on-line help has been updated to reflect all of the above 
+enhancements.
+
+Release 2.2 - September 2000
+
+1.  The datum code for WGS 72 has been corrected from "WGD" to "WGC".
+
+2.  The default initial output coordinate system type has been changed from 
+Mercator to UTM.
+
+3.  A bug in the Windows GUI that prevented degrees/minutes and decimal degrees
+formats from being selected has been corrected.
+
+4.  In the Windows GUI, the initial default value for inverse flattening and the 
+associated label in the Create Ellipsoid dialog box have been corrected.
+
+5.  A bug in the Windows GUI that allowed multiple Geodetic Height type radio 
+buttons to be selected in the File Processing dialog box has been corrected.
+
+6.  Diagrams showing MGRS grid zone, band, and 100,000m square layouts have been
+added to the Users' Guide and the on-line help.
+
+7.  An error in the implementation of Oblique Mercator has been corrected.
+
+8.  Four new projections have been added:  Ney's (Modified Lambert Conformal Conic), 
+Stereographic, British National Grid, and New Zealand Map Grid.
+
+9.  A prototype Java GUI has been added which runs on both Windows and UNIX 
+platforms.  It requires a Java run-time environment. To run it on a Windows
+platform, go to the /geotrans2/win95 directory and double click on the file 
+geo_22.jar.  To run it on a Solaris platform, cd to the /geotrans2/unix directory 
+and enter the command: make -f javamake.  It may be necessary to edit the 
+javamake file to point to the location of the Java run-time environment on your 
+system. 
+  
+Release 2.2.1 - June 2001
+
+1. Fixed problem(s) in Local Cartesian conversions.
+
+2. Corrected a rounding problem in MGRS coordinates when the output precision was 
+set coarser than 1m (10m, 100m, etc.), and the point being converted rounded to 
+the eastern or northern edge of a 100,000m square. An illegal MGRS string could 
+be produced, with an odd number of digits including a "1" followed by one or more 
+zeros.  This was corrected by rounding the UTM easting or northing BEFORE 
+determining the correct 100,000m square.
+
+3. Corrected a very old error in the determination of the 100,000m square from a
+UPS easting in the easternmost part of the south polar zone.
+
+4. Added more flexible support for delimiters in input files (commas, tabs, spaces) 
+
+5. Corrected a problem in reporting invalid northing errors in UTM.
+
+6. Correct an example in the Polar Stereo reuse manual with Latitude of True Scale 
+erroneously set to 0.0.
+
+7. Removed an invalid Central Meridian line from the header of the Mollweide 
+example input file.
+
+8. Added a special F-16 Grid Reference System, a special version of MGRS.
+
+9. Allowed 90% CE, 90% LE, and 90% SE accuracy values for input coordinates to be 
+specified.  These are used, along with datum transformation accuracy information, 
+in deriving the output coordinate accuracy values.
+
+10. Added a pull-down menu of coordinate sources, including GPS, maps, and digital 
+geospatial data which can be selected to automatically set input accuracy values.
+
+11. Improved the Java GUI to be fully functional, including support for file 
+processsing and improvements in its appearance.
+
+Release 2.2.2 - February 2002
+
+1.  Added two new datums from Amendment 1 to NIMA TR8350.2 (Jan 2001) to 3_param.dat file:
+     - Korean Geodetic Datum 1995 (KGS)
+     - SIRGAS, South America (SIR)
+    Corrected ellipsoid code errors in 3_param.dat file:
+     - Ellipsoid used with TIL datum changed from EA to EB,
+     - Ellipsoid used with IND-P datum changed from EA to EF.
+
+2.  Corrected an "off-by-one" error in the datum index validity check function in
+the GEOTRANS engine, which prevented the last datum in the pull-down list from being
+used.  The Java GUI reported this error, but the Windows and Motif GUIs did not.
+
+3.  Processing of input coordinate files was made more flexible and forgiving:
+     - Case sensitivity of keywords and name strings was eliminated.
+     - Height values with geodetic coordinates were made optional, defaulting 
+        to zero.
+     - Coordinate reference system names were made consistent with the GUI 
+        pull-down menus.
+     - File processing error messages were improved.
+
+4.  A warnings count was added to the file processing GUI.
+
+
+5.  Geodetic height fields are grayed out and the No Height selection is forced 
+whenever 3D conversion is not feasible.  For 3D conversion to be feasible, Geodetic, 
+Geocentric, or Local Cartesian must be selected on both panels.  For file processing, 
+the output Geodetic height field is grayed out and the No Height selection is forced 
+whenever the input coordinate reference system is not a 3D system.
+
+6.  File header generation, using a modified version of the File Processing GUI, was 
+added. (Java GUI Only)
+
+7.  Some results of the review of GEOTRANS by NIMA G&G were implemented:
+     - In the User�s Guide (and on-line help), the description of the use of 3-step 
+        method, rather than Molodenski, in polar regions was reworded.
+     - In the User's Guide (and on-line help), the description of how to specify 
+        Lambert Conformal Conic projection with one standard parallel was clarified.
+     - UTM zone fields were enabled independent of the state of the Override buttons, 
+        with default values of zero, and the valid range of zone numbers (1-60) was 
+        added to the zone field labels.
+     - In the Sources pull-down menus, the values for GPS PPS and GPS SPS were corrected 
+        to be the same, reflecting the shutting off of GPS selective availability (SA).
+     - The words �Warning:� or �Error:�, as appropriate, were explicitly included in 
+        all messages output by the GUIs.
+
+Release 2.2.3 - February 2003
+
+There were no changes made to the external interfaces of the GEOTRANS libraries.
+
+1.   The ellipsoid (ellipse.dat) and datum (3_param.dat) files were updated to correct
+several typos.  Dates were added to all ellipsoid names.
+
+2.   A problem in the MGRS module (mgrs.c) was corrected.  This problem occurred only
+when converting from geodetic to MGRS coordinates that round to the centerline of zone 
+31V.  This zone is �cut in half�, such that its centerline is also its eastern boundary.
+Points that are rounded up (eastward) to this boundary are considered to lie in zone 32V.
+
+3.   An error in the Local Cartesian module (loccart.c) was corrected to properly take 
+into account the longitude of the local Cartesian coordinate system origin in converting 
+between geocentric and local Cartesian coordinates.
+
+4.   A possible problem in the Transverse Mercator module (tranmerc.c) concerning 
+projections at the poles was investigated.  Points at the poles are projected when the 
+Transverse Mercator module is initialized in order to determine the range of valid inputs 
+for the inverse projection.  The tangent of the latitude is calculated, which should be 
+infinite at the poles.  Investigation determined that the tangent functions for both 
+Windows and Solaris actually return very large values in this case, which result in the 
+expected behavior.  However, to avoid this problem on other platforms, the maximum valid 
+latitude for the Transverse Mercator projection was reduced from 90 to 89.99 degrees. 
+
+5.   A reported incompatibility between GEOTRANS 2.2.2 and version 4 of the Boeing 
+Autometric EDGE Viewer on Windows platforms was investigated.  This version of the EDGE 
+Viewer includes the GEOTRANS 2.0.3 libraries.  When the EDGE Viewer is installed, it 
+sets the GEOTRANS environment variables to reference the directory C:/Program Files/Autometric/EDGEViewer/Data/GeoTrans. This overrides the default setting in the 
+GEOTRANS application, causing it to look for its required data files in the EDGE Viewer 
+directory.  The incompatibility arises from the fact that the Earth Gravity Model 1996 
+geoid separation file was renamed and changed from text to binary form in GEOTRANS 2.1,
+which reduced its size from 10MB to 4MB.  When the newer binary file is not found in 
+the EDGE data directory, the GEOTRANS application fails to initialize successfully.  
+Placing a copy of the binary geoid separation file (egm96.grd) into the EDGE Viewer 
+data directory eliminates the problem.  A recent update to the EDGE Viewer eliminates 
+the problem by using a more recent version of the GEOTRANS libraries.  Therefore no 
+changes to the GEOTRANS software were necessary.
+
+6.   The source data accuracy values for GPS PPS and SPS modes were updated from 10m 
+to 20m.
+
+7.   The Linear (i.e., vertical) Error (LE) and Spherical Error (SE) fields are now 
+grayed out whenever a conversion is not three-dimensional.
+
+Release 2.2.4 - August 2003
+
+There were no changes made to the external interfaces of the GEOTRANS libraries.
+
+1.   Minor changes were made to source code to eliminate all compilation warnings. 
+These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application 
+Windows GUI, and GEOTRANS application support source code file.
+
+2.   A bug in the MGRS module was corrected.  This bug caused MGRS coordinates located 
+in small triangular areas north of 64S latitude, and south of the 2,900,000 northing 
+line, to fail to convert correctly to UTM.
+
+3.  The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used 
+with the Bessel 1841 Namibia (BN) ellipsoid.  On-line help was corrected to be consistent 
+with this change.
+
+4.  The MGRS module was updated to eliminate inconsistencies in the conversion of points 
+located on UTM zone and MGRS latitude band boundaries.
+
+5.  The MGRS module was reorganized internally to improve the clarity and efficiency of 
+the source code.  The external interface of the MGRS module was not changed.
+
+Release 2.2.4 - August 2003
+
+There were no changes made to the external interfaces of the GEOTRANS libraries.
+
+1.   Minor changes were made to source code to eliminate all compilation warnings. 
+These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application 
+Windows GUI, and GEOTRANS application support source code file.
+
+2.   A bug in the MGRS module was corrected.  This bug caused MGRS coordinates located 
+in small triangular areas north of 64S latitude, and south of the 2,900,000 northing 
+line, to fail to convert correctly to UTM.
+
+3.  The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used 
+with the Bessel 1841 Namibia (BN) ellipsoid.  On-line help was corrected to be consistent 
+with this change.
+
+4.  The MGRS module was updated to eliminate inconsistencies in the conversion of points 
+located on UTM zone and MGRS latitude band boundaries.
+
+5.  The MGRS module was reorganized internally to improve the clarity and efficiency of 
+the source code.  The external interface of the MGRS module was not changed.
+Release 2.2.4 - August 2003
+
+There were no changes made to the external interfaces of the GEOTRANS libraries.
+
+1.   Minor changes were made to source code to eliminate all compilation warnings. 
+These changes involved the Ellisoid module, the GEOTRANS engine, GEOTRANS application 
+Windows GUI, and GEOTRANS application support source code file.
+
+2.   A bug in the MGRS module was corrected.  This bug caused MGRS coordinates located 
+in small triangular areas north of 64S latitude, and south of the 2,900,000 northing 
+line, to fail to convert correctly to UTM.
+
+3.  The MGRS module was corrected so that the MGRS "AL" 100,000m square pattern is used 
+with the Bessel 1841 Namibia (BN) ellipsoid.  On-line help was corrected to be consistent 
+with this change.
+
+4.  The MGRS module was updated to eliminate inconsistencies in the conversion of points 
+located on UTM zone and MGRS latitude band boundaries.
+
+5.  The MGRS module was reorganized internally to improve the clarity and efficiency of 
+the source code.  The external interface of the MGRS module was not changed.
+
+Release 2.2.5 - June 2004
+
+There were no changes made to the external interfaces of the GEOTRANS libraries.
+
+1.   A minor correction was made in the Datum module, to correct an "off-by-one" error in the Valid_Datum function, which caused it to return a warning when the last 3-parameter datum was accessed.
+
+2.   A minor correction was made in the Round_DMS function, in the common application GUI support software, which caused incorrect geodetic coordinate values to be displayed when converting to degrees with a precision of .0001.
+
+3    The 3-parameter datum file, 3_param.dat, was updated to reflect new parameter values for the MID (MIDWAY ASTRO 1961, Midway Is.) datum, which went into effect in June 2003.  The longitude limits for the NAS-U (North American 1927, Greenland), the DAL (Dabola, Guinea) and the TRN (Astro Tern Island (Frig) 1961 datums were also corrected.
+
+Release 2.2.6 - June 2005
+
+1.  A minor correction was made in the Get_Geoid_Height function in the GEOID module, which does bilinear interpolation of EGM96 geoid separation values.  The error caused the specified point to be shifted in longitude, mirrored around the east-west midline of the 15-minute grid cell that contains it.
+
+2.  The 3-parameter datum file, 3_param.dat, was updated to correct the latitude and longitude limits for the DID (Deception Island), EUR-S (European 1950, Israel and Iraq), and KEG (Kerguelen Island) datums.
+
+3.  A minor correction was made in the Convert_Orthographic_to_Geodetic function in the ORTHOGR module to use the two-argument arctangent function (atan2) rather than the single-argument arctangent function (atan).  This avoids sign errors in results near the poles.
+
+4.  A minor correction was made in the Convert_Albers_to_Geodetic function in the ALBERS module to avoid infinite loops when the iterative solution for latitude fails to converge.  After 30 iterations, the function now returns an error status.
+
+5.  Support was added for the Lambert Conformal Conic projection with one standard parallel.  A new LAMBERT_1 module was added, and the LAMBERT_2 module was renamed (from LAMBERT) and reengineered to use the new LAMBERT_1 module.  Backward compatibility was maintained at the DT&CC and GEOTRANS Engine levels.  However, all existing functions that include "Lambert", rather than "Lambert1" or "Lambert2", in their names are now considered to be deprecated, and will be removed in a future update.
+
+6. The GEOTRANS application GUI was enhanced to help users avoid incompatible combinations of coordinate systems and datums by color coding the conversion buttons.  Red indicates that the selected coordinate systems and datums are not compatible with one another, and that an error message will result from any attempted conversion operation.  Yellow indicates that the selected datums have disjoint areas of validity, adn that a warning message will result from any attempted conversion operation.
+
+7. A correction was made to the Geodetic_Shift_WGS72_To_WGS84 and Geodetic_Shift_WGS72_To_WGS84 functions in the DATUM module to wrap longitude values across the 180 degree line and wrap latitude values over the poles. 
+
+8. File processing examples in the online help, and in the /examples subdirectory, were improved to use more realistic coordinates, and additional examples were added.
+
+9. The GEOTRANS application GUI was enhanced to provide an option to display leading zeroes on output geodetic coordinate values, including degrees (three digits for longitude, two digits for latitude), minutes (two digits), and seconds (two digits).
+
+Release 2.3 - March 2006
+
+1. The 3-parameter datum file, 3_param.dat, was updated to correct the latitude and longitude limits for DID (Deception Island), switching longitude order, and JOH (Johnston Island) datums.
+
+2. An �off-by-one� error in datum indexing in the JNI interface was corrected.
+
+3. Support for Red Hat Linux (Red Hat Professional 9.3 and later) and for SuSE Linux (SuSE Linux 9 and later) was added.
+ 
+4. A reported potential error in file name string underflow/overflow in the Ellipsoid, Datum, and Geoid modules was corrected.
+
+5. Support for multithreading was improved by adding mutual exclusion zones around code that opens and reads data files in the Ellipsoid, Datum, and Geoid modules.
+
+6. Support for three additional gravity-related height models was added, based on requirements from the US military services:
+a. EGM96 with variable grid spacing and natural spline interpolation
+b. EGM84 with 10 degree by 10 degree grid spacing and bilinear interpolation
+c. EGM84 with 10 degree by 10 degree grid spacing and natural spline interpolation
+These are in addition to EGM96 with 15-minute grid spacing and bilinear interpolation, which was previously supported.
+
+Release 2.4 - September 2006
+
+1. The 3-parameter datum file, 3_param.dat, was updated to correct two spelling errors (Columbia -> Colombia, Phillipines -> Philippines).
+
+2. The 3-parameter datum file, 3_param.dat, was updated to adjust the limits for several local datums in the 3-parameter datum file (ADI-E, CAC, CAI, COA, HJO, ING-A, KEG, LCF, NDS, SAE, SAN-A, SAN-C, VOI, and VOR).
+
+3. All required ellipsoid, datum, and geoid data files to a /data subdirectory to eliminate the need for duplicate copies.
+
+4. Error reporting was improved when required ellipsoid, datum, and/or geoid data files cannot be located at initialization.
+
+5. In the Geoid module, problems were corrected in the interpolation of geoid separation values using a variable-resolution grid when converting to or from MSL-EGM96-VG-NS Height.
+
+6. In the MGRS module, a problem was corrected in rounding up to the equator when converting to MGRS.
+
+7. Support was added for the U.S. National Grid (USNG).
+
+8. Support was added for the Global Area Reference System (USNG).
+
+Release 2.4.1 - March 2007
+
+1. Corrected two minor errors (6cm and 1cm) in the values contained in the EGM84 geoid 
+separation file.
+
+2. Improved error handling and reporting in the Transverse Mercator, UTM, and MGRS 
+modules at extreme northern and southern latitudes, for points that are more than 9 
+degrees, but less than 400km, from the central meridian.
+
+3. Modified the US National Grid (USNG) module to truncate, rather than round, USNG 
+coordinates as required by the USNG specification.
+
+4. Corrected an error in the calculation of valid ranges for input easting and northing 
+coordinates in the Mercator module, and several other map projection modules.  This 
+caused valid inputs to be rejected when extremely large (e.g., 20,000,000m) false easting 
+or false northing values were specified for those map projections.
+
+5. Improved error handling and reporting in the Lambert Conformal Conic modules in cases 
+of extremely small scale factor values.
+
+6. Corrected an error in the MGRS module that occurred when a point rounded up to the 
+eastern boundary of the non-standard zone 31V in the northern Atlantic, which is 
+considered to be part of zone 32V.
+
+Release 2.4.2 - August 2008
+
+1.  Corrected an error in the MGRS and USNG modules that incorrectly mapped 100,000m square 
+row letters to northing values in the northern portion of the X latitude band (northings > 9,000,000m).
+
+2.  Revised the handling of warnings reported by the Transverse Mercator (TM) module for points
+located more than 9 degrees from the central meridian, when the TM module is invoked by the UTM and
+MGRS modules, so that UTM or MGRS error checking takes precedence.
+
+3.  Added datum domain checks for those cases where no datum transformations are performed.  
+Previously, coordinates were not checked against the valid domain of the datum when the input datum 
+and output datum were identical.
+
+4.  The default accuracy estimate values for DTED Level 1 and DTED Level 2 were updated to be consistent
+with MIL-PRF-89020B, Performance Specification, Digital Terrain Elevation Data (DTED), 23 May 2000, replacing 
+the values from MIL-PRF-89020A, 19 Apr 1996.  Default spherical accuracy estimate values for all relevant data 
+sources were updated to reflect a more accurate relationship to the corresponding circular (horizontal) 
+accuracy estimates.
+
+5.  In the GEOTRANS application, added commands to save the current selections and options settings as 
+the defaults, and to reset the current selections and options settings from the defaults.
+
+6.  In the GEOTRANS application, added capabilities to create and delete user-defined 7-parameter datums.
+
+7.  Corrected a problem with the checking of input coordinates against the valid region for a local datum 
+when a longitude value greater than +180 degrees was entered.
+
+8.  Corrected the valid regions for the PUK and NAR-E datums to use a range of longitudes that span the 
++180/-180 degree line.
+
+
+   
+
+
+
diff --git a/geotranz/tranmerc.c b/geotranz/tranmerc.c
old mode 100755
new mode 100644
index eaffe06..893db7e
--- a/geotranz/tranmerc.c
+++ b/geotranz/tranmerc.c
@@ -1,618 +1,618 @@
-/***************************************************************************/
-/* RSC IDENTIFIER: TRANSVERSE MERCATOR
- *
- * ABSTRACT
- *
- *    This component provides conversions between Geodetic coordinates 
- *    (latitude and longitude) and Transverse Mercator projection coordinates
- *    (easting and northing).
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *       TRANMERC_NO_ERROR           : No errors occurred in function
- *       TRANMERC_LAT_ERROR          : Latitude outside of valid range
- *                                      (-90 to 90 degrees)
- *       TRANMERC_LON_ERROR          : Longitude outside of valid range
- *                                      (-180 to 360 degrees, and within
- *                                        +/-90 of Central Meridian)
- *       TRANMERC_EASTING_ERROR      : Easting outside of valid range
- *                                      (depending on ellipsoid and
- *                                       projection parameters)
- *       TRANMERC_NORTHING_ERROR     : Northing outside of valid range
- *                                      (depending on ellipsoid and
- *                                       projection parameters)
- *       TRANMERC_ORIGIN_LAT_ERROR   : Origin latitude outside of valid range
- *                                      (-90 to 90 degrees)
- *       TRANMERC_CENT_MER_ERROR     : Central meridian outside of valid range
- *                                      (-180 to 360 degrees)
- *       TRANMERC_A_ERROR            : Semi-major axis less than or equal to zero
- *       TRANMERC_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	                  (250 to 350)
- *       TRANMERC_SCALE_FACTOR_ERROR : Scale factor outside of valid
- *                                     range (0.3 to 3.0)
- *		 TM_LON_WARNING              : Distortion will result if longitude is more
- *                                       than 9 degrees from the Central Meridian
- *
- * REUSE NOTES
- *
- *    TRANSVERSE MERCATOR is intended for reuse by any application that 
- *    performs a Transverse Mercator projection or its inverse.
- *    
- * REFERENCES
- *
- *    Further information on TRANSVERSE MERCATOR can be found in the 
- *    Reuse Manual.
- *
- *    TRANSVERSE MERCATOR originated from :  
- *                      U.S. Army Topographic Engineering Center
- *                      Geospatial Information Division
- *                      7701 Telegraph Road
- *                      Alexandria, VA  22310-3864
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *    TRANSVERSE MERCATOR has no restrictions.
- *
- * ENVIRONMENT
- *
- *    TRANSVERSE MERCATOR was tested and certified in the following 
- *    environments:
- *
- *    1. Solaris 2.5 with GCC, version 2.8.1
- *    2. Windows 95 with MS Visual C++, version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    10-02-97          Original Code
- *    03-02-97          Re-engineered Code
- *
- */
-
-
-/***************************************************************************/
-/*
- *                               INCLUDES
- */
-
-#include <math.h>
-#include "tranmerc.h"
-
-/*
- *    math.h      - Standard C math library
- *    tranmerc.h  - Is for prototype error checking
- */
-
-
-/***************************************************************************/
-/*                               DEFINES 
- *
- */
-
-#define PI              3.14159265358979323e0   /* PI     */
-#define PI_OVER_2         (PI/2.0e0)            /* PI over 2 */
-#define MAX_LAT         ((PI * 89.99)/180.0)    /* 89.99 degrees in radians */
-#define MAX_DELTA_LONG  ((PI * 90)/180.0)       /* 90 degrees in radians */
-#define MIN_SCALE_FACTOR  0.3
-#define MAX_SCALE_FACTOR  3.0
-
-#define SPHTMD(Latitude) ((double) (TranMerc_ap * Latitude \
-      - TranMerc_bp * sin(2.e0 * Latitude) + TranMerc_cp * sin(4.e0 * Latitude) \
-      - TranMerc_dp * sin(6.e0 * Latitude) + TranMerc_ep * sin(8.e0 * Latitude) ) )
-
-#define SPHSN(Latitude) ((double) (TranMerc_a / sqrt( 1.e0 - TranMerc_es * \
-      pow(sin(Latitude), 2))))
-
-#define SPHSR(Latitude) ((double) (TranMerc_a * (1.e0 - TranMerc_es) / \
-    pow(DENOM(Latitude), 3)))
-
-#define DENOM(Latitude) ((double) (sqrt(1.e0 - TranMerc_es * pow(sin(Latitude),2))))
-
-
-/**************************************************************************/
-/*                               GLOBAL DECLARATIONS
- *
- */
-
-/* Ellipsoid Parameters, default to WGS 84  */
-static double TranMerc_a = 6378137.0;              /* Semi-major axis of ellipsoid in meters */
-static double TranMerc_f = 1 / 298.257223563;      /* Flattening of ellipsoid  */
-static double TranMerc_es = 0.0066943799901413800; /* Eccentricity (0.08181919084262188000) squared */
-static double TranMerc_ebs = 0.0067394967565869;   /* Second Eccentricity squared */
-
-/* Transverse_Mercator projection Parameters */
-static double TranMerc_Origin_Lat = 0.0;           /* Latitude of origin in radians */
-static double TranMerc_Origin_Long = 0.0;          /* Longitude of origin in radians */
-static double TranMerc_False_Northing = 0.0;       /* False northing in meters */
-static double TranMerc_False_Easting = 0.0;        /* False easting in meters */
-static double TranMerc_Scale_Factor = 1.0;         /* Scale factor  */
-
-/* Isometeric to geodetic latitude parameters, default to WGS 84 */
-static double TranMerc_ap = 6367449.1458008;
-static double TranMerc_bp = 16038.508696861;
-static double TranMerc_cp = 16.832613334334;
-static double TranMerc_dp = 0.021984404273757;
-static double TranMerc_ep = 3.1148371319283e-005;
-
-/* Maximum variance for easting and northing values for WGS 84. */
-static double TranMerc_Delta_Easting = 40000000.0;
-static double TranMerc_Delta_Northing = 40000000.0;
-
-/* These state variables are for optimization purposes. The only function
- * that should modify them is Set_Tranverse_Mercator_Parameters.         */
-
-
-/************************************************************************/
-/*                              FUNCTIONS     
- *
- */
-
-
-long Set_Transverse_Mercator_Parameters(double a,
-                                        double f,
-                                        double Origin_Latitude,
-                                        double Central_Meridian,
-                                        double False_Easting,
-                                        double False_Northing,
-                                        double Scale_Factor)
-
-{ /* BEGIN Set_Tranverse_Mercator_Parameters */
-  /*
-   * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid
-   * parameters and Tranverse Mercator projection parameters as inputs, and
-   * sets the corresponding state variables. If any errors occur, the error
-   * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
-   * returned.
-   *
-   *    a                 : Semi-major axis of ellipsoid, in meters    (input)
-   *    f                 : Flattening of ellipsoid						         (input)
-   *    Origin_Latitude   : Latitude in radians at the origin of the   (input)
-   *                         projection
-   *    Central_Meridian  : Longitude in radians at the center of the  (input)
-   *                         projection
-   *    False_Easting     : Easting/X at the center of the projection  (input)
-   *    False_Northing    : Northing/Y at the center of the projection (input)
-   *    Scale_Factor      : Projection scale factor                    (input) 
-   */
-
-  double tn;        /* True Meridianal distance constant  */
-  double tn2;
-  double tn3;
-  double tn4;
-  double tn5;
-  double dummy_northing;
-  double TranMerc_b; /* Semi-minor axis of ellipsoid, in meters */
-  double inv_f = 1 / f;
-  long Error_Code = TRANMERC_NO_ERROR;
-
-  if (a <= 0.0)
-  { /* Semi-major axis must be greater than zero */
-    Error_Code |= TRANMERC_A_ERROR;
-  }
-  if ((inv_f < 250) || (inv_f > 350))
-  { /* Inverse flattening must be between 250 and 350 */
-    Error_Code |= TRANMERC_INV_F_ERROR;
-  }
-  if ((Origin_Latitude < -PI_OVER_2) || (Origin_Latitude > PI_OVER_2))
-  { /* origin latitude out of range */
-    Error_Code |= TRANMERC_ORIGIN_LAT_ERROR;
-  }
-  if ((Central_Meridian < -PI) || (Central_Meridian > (2*PI)))
-  { /* origin longitude out of range */
-    Error_Code |= TRANMERC_CENT_MER_ERROR;
-  }
-  if ((Scale_Factor < MIN_SCALE_FACTOR) || (Scale_Factor > MAX_SCALE_FACTOR))
-  {
-    Error_Code |= TRANMERC_SCALE_FACTOR_ERROR;
-  }
-  if (!Error_Code)
-  { /* no errors */
-    TranMerc_a = a;
-    TranMerc_f = f;
-    TranMerc_Origin_Lat = Origin_Latitude;
-    if (Central_Meridian > PI)
-      Central_Meridian -= (2*PI);
-    TranMerc_Origin_Long = Central_Meridian;
-    TranMerc_False_Northing = False_Northing;
-    TranMerc_False_Easting = False_Easting; 
-    TranMerc_Scale_Factor = Scale_Factor;
-
-    /* Eccentricity Squared */
-    TranMerc_es = 2 * TranMerc_f - TranMerc_f * TranMerc_f;
-    /* Second Eccentricity Squared */
-    TranMerc_ebs = (1 / (1 - TranMerc_es)) - 1;
-
-    TranMerc_b = TranMerc_a * (1 - TranMerc_f);    
-    /*True meridianal constants  */
-    tn = (TranMerc_a - TranMerc_b) / (TranMerc_a + TranMerc_b);
-    tn2 = tn * tn;
-    tn3 = tn2 * tn;
-    tn4 = tn3 * tn;
-    tn5 = tn4 * tn;
-
-    TranMerc_ap = TranMerc_a * (1.e0 - tn + 5.e0 * (tn2 - tn3)/4.e0
-                                + 81.e0 * (tn4 - tn5)/64.e0 );
-    TranMerc_bp = 3.e0 * TranMerc_a * (tn - tn2 + 7.e0 * (tn3 - tn4)
-                                       /8.e0 + 55.e0 * tn5/64.e0 )/2.e0;
-    TranMerc_cp = 15.e0 * TranMerc_a * (tn2 - tn3 + 3.e0 * (tn4 - tn5 )/4.e0) /16.0;
-    TranMerc_dp = 35.e0 * TranMerc_a * (tn3 - tn4 + 11.e0 * tn5 / 16.e0) / 48.e0;
-    TranMerc_ep = 315.e0 * TranMerc_a * (tn4 - tn5) / 512.e0;
-    Convert_Geodetic_To_Transverse_Mercator(MAX_LAT,
-                                            MAX_DELTA_LONG + Central_Meridian,
-                                            &TranMerc_Delta_Easting,
-                                            &TranMerc_Delta_Northing);
-    Convert_Geodetic_To_Transverse_Mercator(0,
-                                            MAX_DELTA_LONG + Central_Meridian,
-                                            &TranMerc_Delta_Easting,
-                                            &dummy_northing);
-    TranMerc_Delta_Northing++;
-    TranMerc_Delta_Easting++;
-
-  } /* END OF if(!Error_Code) */
-  return (Error_Code);
-}  /* END of Set_Transverse_Mercator_Parameters  */
-
-
-void Get_Transverse_Mercator_Parameters(double *a,
-                                        double *f,
-                                        double *Origin_Latitude,
-                                        double *Central_Meridian,
-                                        double *False_Easting,
-                                        double *False_Northing,
-                                        double *Scale_Factor)
-
-{ /* BEGIN Get_Tranverse_Mercator_Parameters  */
-  /*
-   * The function Get_Transverse_Mercator_Parameters returns the current
-   * ellipsoid and Transverse Mercator projection parameters.
-   *
-   *    a                 : Semi-major axis of ellipsoid, in meters    (output)
-   *    f                 : Flattening of ellipsoid						         (output)
-   *    Origin_Latitude   : Latitude in radians at the origin of the   (output)
-   *                         projection
-   *    Central_Meridian  : Longitude in radians at the center of the  (output)
-   *                         projection
-   *    False_Easting     : Easting/X at the center of the projection  (output)
-   *    False_Northing    : Northing/Y at the center of the projection (output)
-   *    Scale_Factor      : Projection scale factor                    (output) 
-   */
-
-  *a = TranMerc_a;
-  *f = TranMerc_f;
-  *Origin_Latitude = TranMerc_Origin_Lat;
-  *Central_Meridian = TranMerc_Origin_Long;
-  *False_Easting = TranMerc_False_Easting;
-  *False_Northing = TranMerc_False_Northing;
-  *Scale_Factor = TranMerc_Scale_Factor;
-  return;
-} /* END OF Get_Tranverse_Mercator_Parameters */
-
-
-
-long Convert_Geodetic_To_Transverse_Mercator (double Latitude,
-                                              double Longitude,
-                                              double *Easting,
-                                              double *Northing)
-
-{      /* BEGIN Convert_Geodetic_To_Transverse_Mercator */
-
-  /*
-   * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic
-   * (latitude and longitude) coordinates to Transverse Mercator projection
-   * (easting and northing) coordinates, according to the current ellipsoid
-   * and Transverse Mercator projection coordinates.  If any errors occur, the
-   * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
-   * returned.
-   *
-   *    Latitude      : Latitude in radians                         (input)
-   *    Longitude     : Longitude in radians                        (input)
-   *    Easting       : Easting/X in meters                         (output)
-   *    Northing      : Northing/Y in meters                        (output)
-   */
-
-  double c;       /* Cosine of latitude                          */
-  double c2;
-  double c3;
-  double c5;
-  double c7;
-  double dlam;    /* Delta longitude - Difference in Longitude       */
-  double eta;     /* constant - TranMerc_ebs *c *c                   */
-  double eta2;
-  double eta3;
-  double eta4;
-  double s;       /* Sine of latitude                        */
-  double sn;      /* Radius of curvature in the prime vertical       */
-  double t;       /* Tangent of latitude                             */
-  double tan2;
-  double tan3;
-  double tan4;
-  double tan5;
-  double tan6;
-  double t1;      /* Term in coordinate conversion formula - GP to Y */
-  double t2;      /* Term in coordinate conversion formula - GP to Y */
-  double t3;      /* Term in coordinate conversion formula - GP to Y */
-  double t4;      /* Term in coordinate conversion formula - GP to Y */
-  double t5;      /* Term in coordinate conversion formula - GP to Y */
-  double t6;      /* Term in coordinate conversion formula - GP to Y */
-  double t7;      /* Term in coordinate conversion formula - GP to Y */
-  double t8;      /* Term in coordinate conversion formula - GP to Y */
-  double t9;      /* Term in coordinate conversion formula - GP to Y */
-  double tmd;     /* True Meridional distance                        */
-  double tmdo;    /* True Meridional distance for latitude of origin */
-  long    Error_Code = TRANMERC_NO_ERROR;
-  double temp_Origin;
-  double temp_Long;
-
-  if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT))
-  {  /* Latitude out of range */
-    Error_Code|= TRANMERC_LAT_ERROR;
-  }
-  if (Longitude > PI)
-    Longitude -= (2 * PI);
-  if ((Longitude < (TranMerc_Origin_Long - MAX_DELTA_LONG))
-      || (Longitude > (TranMerc_Origin_Long + MAX_DELTA_LONG)))
-  {
-    if (Longitude < 0)
-      temp_Long = Longitude + 2 * PI;
-    else
-      temp_Long = Longitude;
-    if (TranMerc_Origin_Long < 0)
-      temp_Origin = TranMerc_Origin_Long + 2 * PI;
-    else
-      temp_Origin = TranMerc_Origin_Long;
-    if ((temp_Long < (temp_Origin - MAX_DELTA_LONG))
-        || (temp_Long > (temp_Origin + MAX_DELTA_LONG)))
-      Error_Code|= TRANMERC_LON_ERROR;
-  }
-  if (!Error_Code)
-  { /* no errors */
-
-    /* 
-     *  Delta Longitude
-     */
-    dlam = Longitude - TranMerc_Origin_Long;
-
-    if (fabs(dlam) > (9.0 * PI / 180))
-    { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian */
-      Error_Code |= TRANMERC_LON_WARNING;
-    }
-
-    if (dlam > PI)
-      dlam -= (2 * PI);
-    if (dlam < -PI)
-      dlam += (2 * PI);
-    if (fabs(dlam) < 2.e-10)
-      dlam = 0.0;
-
-    s = sin(Latitude);
-    c = cos(Latitude);
-    c2 = c * c;
-    c3 = c2 * c;
-    c5 = c3 * c2;
-    c7 = c5 * c2;
-    t = tan (Latitude);
-    tan2 = t * t;
-    tan3 = tan2 * t;
-    tan4 = tan3 * t;
-    tan5 = tan4 * t;
-    tan6 = tan5 * t;
-    eta = TranMerc_ebs * c2;
-    eta2 = eta * eta;
-    eta3 = eta2 * eta;
-    eta4 = eta3 * eta;
-
-    /* radius of curvature in prime vertical */
-    sn = SPHSN(Latitude);
-
-    /* True Meridianal Distances */
-    tmd = SPHTMD(Latitude);
-
-    /*  Origin  */
-    tmdo = SPHTMD (TranMerc_Origin_Lat);
-
-    /* northing */
-    t1 = (tmd - tmdo) * TranMerc_Scale_Factor;
-    t2 = sn * s * c * TranMerc_Scale_Factor/ 2.e0;
-    t3 = sn * s * c3 * TranMerc_Scale_Factor * (5.e0 - tan2 + 9.e0 * eta 
-                                                + 4.e0 * eta2) /24.e0; 
-
-    t4 = sn * s * c5 * TranMerc_Scale_Factor * (61.e0 - 58.e0 * tan2
-                                                + tan4 + 270.e0 * eta - 330.e0 * tan2 * eta + 445.e0 * eta2
-                                                + 324.e0 * eta3 -680.e0 * tan2 * eta2 + 88.e0 * eta4 
-                                                -600.e0 * tan2 * eta3 - 192.e0 * tan2 * eta4) / 720.e0;
-
-    t5 = sn * s * c7 * TranMerc_Scale_Factor * (1385.e0 - 3111.e0 * 
-                                                tan2 + 543.e0 * tan4 - tan6) / 40320.e0;
-
-    *Northing = TranMerc_False_Northing + t1 + pow(dlam,2.e0) * t2
-                + pow(dlam,4.e0) * t3 + pow(dlam,6.e0) * t4
-                + pow(dlam,8.e0) * t5; 
-
-    /* Easting */
-    t6 = sn * c * TranMerc_Scale_Factor;
-    t7 = sn * c3 * TranMerc_Scale_Factor * (1.e0 - tan2 + eta ) /6.e0;
-    t8 = sn * c5 * TranMerc_Scale_Factor * (5.e0 - 18.e0 * tan2 + tan4
-                                            + 14.e0 * eta - 58.e0 * tan2 * eta + 13.e0 * eta2 + 4.e0 * eta3 
-                                            - 64.e0 * tan2 * eta2 - 24.e0 * tan2 * eta3 )/ 120.e0;
-    t9 = sn * c7 * TranMerc_Scale_Factor * ( 61.e0 - 479.e0 * tan2
-                                             + 179.e0 * tan4 - tan6 ) /5040.e0;
-
-    *Easting = TranMerc_False_Easting + dlam * t6 + pow(dlam,3.e0) * t7 
-               + pow(dlam,5.e0) * t8 + pow(dlam,7.e0) * t9;
-  }
-  return (Error_Code);
-} /* END OF Convert_Geodetic_To_Transverse_Mercator */
-
-
-long Convert_Transverse_Mercator_To_Geodetic (
-                                             double Easting,
-                                             double Northing,
-                                             double *Latitude,
-                                             double *Longitude)
-{      /* BEGIN Convert_Transverse_Mercator_To_Geodetic */
-
-  /*
-   * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse
-   * Mercator projection (easting and northing) coordinates to geodetic
-   * (latitude and longitude) coordinates, according to the current ellipsoid
-   * and Transverse Mercator projection parameters.  If any errors occur, the
-   * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
-   * returned.
-   *
-   *    Easting       : Easting/X in meters                         (input)
-   *    Northing      : Northing/Y in meters                        (input)
-   *    Latitude      : Latitude in radians                         (output)
-   *    Longitude     : Longitude in radians                        (output)
-   */
-
-  double c;       /* Cosine of latitude                          */
-  double de;      /* Delta easting - Difference in Easting (Easting-Fe)    */
-  double dlam;    /* Delta longitude - Difference in Longitude       */
-  double eta;     /* constant - TranMerc_ebs *c *c                   */
-  double eta2;
-  double eta3;
-  double eta4;
-  double ftphi;   /* Footpoint latitude                              */
-  int    i;       /* Loop iterator                   */
-  //double s;       /* Sine of latitude                        */
-  double sn;      /* Radius of curvature in the prime vertical       */
-  double sr;      /* Radius of curvature in the meridian             */
-  double t;       /* Tangent of latitude                             */
-  double tan2;
-  double tan4;
-  double t10;     /* Term in coordinate conversion formula - GP to Y */
-  double t11;     /* Term in coordinate conversion formula - GP to Y */
-  double t12;     /* Term in coordinate conversion formula - GP to Y */
-  double t13;     /* Term in coordinate conversion formula - GP to Y */
-  double t14;     /* Term in coordinate conversion formula - GP to Y */
-  double t15;     /* Term in coordinate conversion formula - GP to Y */
-  double t16;     /* Term in coordinate conversion formula - GP to Y */
-  double t17;     /* Term in coordinate conversion formula - GP to Y */
-  double tmd;     /* True Meridional distance                        */
-  double tmdo;    /* True Meridional distance for latitude of origin */
-  long Error_Code = TRANMERC_NO_ERROR;
-
-  if ((Easting < (TranMerc_False_Easting - TranMerc_Delta_Easting))
-      ||(Easting > (TranMerc_False_Easting + TranMerc_Delta_Easting)))
-  { /* Easting out of range  */
-    Error_Code |= TRANMERC_EASTING_ERROR;
-  }
-  if ((Northing < (TranMerc_False_Northing - TranMerc_Delta_Northing))
-      || (Northing > (TranMerc_False_Northing + TranMerc_Delta_Northing)))
-  { /* Northing out of range */
-    Error_Code |= TRANMERC_NORTHING_ERROR;
-  }
-
-  if (!Error_Code)
-  {
-    /* True Meridional Distances for latitude of origin */
-    tmdo = SPHTMD(TranMerc_Origin_Lat);
-
-    /*  Origin  */
-    tmd = tmdo +  (Northing - TranMerc_False_Northing) / TranMerc_Scale_Factor; 
-
-    /* First Estimate */
-    sr = SPHSR(0.e0);
-    ftphi = tmd/sr;
-
-    for (i = 0; i < 5 ; i++)
-    {
-      t10 = SPHTMD (ftphi);
-      sr = SPHSR(ftphi);
-      ftphi = ftphi + (tmd - t10) / sr;
-    }
-
-    /* Radius of Curvature in the meridian */
-    sr = SPHSR(ftphi);
-
-    /* Radius of Curvature in the meridian */
-    sn = SPHSN(ftphi);
-
-    /* Sine Cosine terms */
-    //s = sin(ftphi);
-    c = cos(ftphi);
-
-    /* Tangent Value  */
-    t = tan(ftphi);
-    tan2 = t * t;
-    tan4 = tan2 * tan2;
-    eta = TranMerc_ebs * pow(c,2);
-    eta2 = eta * eta;
-    eta3 = eta2 * eta;
-    eta4 = eta3 * eta;
-    de = Easting - TranMerc_False_Easting;
-    if (fabs(de) < 0.0001)
-      de = 0.0;
-
-    /* Latitude */
-    t10 = t / (2.e0 * sr * sn * pow(TranMerc_Scale_Factor, 2));
-    t11 = t * (5.e0  + 3.e0 * tan2 + eta - 4.e0 * pow(eta,2)
-               - 9.e0 * tan2 * eta) / (24.e0 * sr * pow(sn,3) 
-                                       * pow(TranMerc_Scale_Factor,4));
-    t12 = t * (61.e0 + 90.e0 * tan2 + 46.e0 * eta + 45.E0 * tan4
-               - 252.e0 * tan2 * eta  - 3.e0 * eta2 + 100.e0 
-               * eta3 - 66.e0 * tan2 * eta2 - 90.e0 * tan4
-               * eta + 88.e0 * eta4 + 225.e0 * tan4 * eta2
-               + 84.e0 * tan2* eta3 - 192.e0 * tan2 * eta4)
-          / ( 720.e0 * sr * pow(sn,5) * pow(TranMerc_Scale_Factor, 6) );
-    t13 = t * ( 1385.e0 + 3633.e0 * tan2 + 4095.e0 * tan4 + 1575.e0 
-                * pow(t,6))/ (40320.e0 * sr * pow(sn,7) * pow(TranMerc_Scale_Factor,8));
-    *Latitude = ftphi - pow(de,2) * t10 + pow(de,4) * t11 - pow(de,6) * t12 
-                + pow(de,8) * t13;
-
-    t14 = 1.e0 / (sn * c * TranMerc_Scale_Factor);
-
-    t15 = (1.e0 + 2.e0 * tan2 + eta) / (6.e0 * pow(sn,3) * c * 
-                                        pow(TranMerc_Scale_Factor,3));
-
-    t16 = (5.e0 + 6.e0 * eta + 28.e0 * tan2 - 3.e0 * eta2
-           + 8.e0 * tan2 * eta + 24.e0 * tan4 - 4.e0 
-           * eta3 + 4.e0 * tan2 * eta2 + 24.e0 
-           * tan2 * eta3) / (120.e0 * pow(sn,5) * c  
-                             * pow(TranMerc_Scale_Factor,5));
-
-    t17 = (61.e0 +  662.e0 * tan2 + 1320.e0 * tan4 + 720.e0 
-           * pow(t,6)) / (5040.e0 * pow(sn,7) * c 
-                          * pow(TranMerc_Scale_Factor,7));
-
-    /* Difference in Longitude */
-    dlam = de * t14 - pow(de,3) * t15 + pow(de,5) * t16 - pow(de,7) * t17;
-
-    /* Longitude */
-    (*Longitude) = TranMerc_Origin_Long + dlam;
-
-    if((fabs)(*Latitude) > (90.0 * PI / 180.0))
-      Error_Code |= TRANMERC_NORTHING_ERROR;
-
-    if((*Longitude) > (PI))
-    {
-      *Longitude -= (2 * PI);
-      if((fabs)(*Longitude) > PI)
-        Error_Code |= TRANMERC_EASTING_ERROR;
-    }
-    else if((*Longitude) < (-PI))
-    {
-      *Longitude += (2 * PI);
-      if((fabs)(*Longitude) > PI)
-        Error_Code |= TRANMERC_EASTING_ERROR;
-    }
-
-    if (fabs(dlam) > (9.0 * PI / 180) * cos(*Latitude))
-    { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian at the equator */
-      /* and decreases to 0 degrees at the poles */
-      /* As you move towards the poles, distortion will become more significant */
-      Error_Code |= TRANMERC_LON_WARNING;
-    }
-  }
-  return (Error_Code);
-} /* END OF Convert_Transverse_Mercator_To_Geodetic */
+/***************************************************************************/
+/* RSC IDENTIFIER: TRANSVERSE MERCATOR
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between Geodetic coordinates 
+ *    (latitude and longitude) and Transverse Mercator projection coordinates
+ *    (easting and northing).
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *       TRANMERC_NO_ERROR           : No errors occurred in function
+ *       TRANMERC_LAT_ERROR          : Latitude outside of valid range
+ *                                      (-90 to 90 degrees)
+ *       TRANMERC_LON_ERROR          : Longitude outside of valid range
+ *                                      (-180 to 360 degrees, and within
+ *                                        +/-90 of Central Meridian)
+ *       TRANMERC_EASTING_ERROR      : Easting outside of valid range
+ *                                      (depending on ellipsoid and
+ *                                       projection parameters)
+ *       TRANMERC_NORTHING_ERROR     : Northing outside of valid range
+ *                                      (depending on ellipsoid and
+ *                                       projection parameters)
+ *       TRANMERC_ORIGIN_LAT_ERROR   : Origin latitude outside of valid range
+ *                                      (-90 to 90 degrees)
+ *       TRANMERC_CENT_MER_ERROR     : Central meridian outside of valid range
+ *                                      (-180 to 360 degrees)
+ *       TRANMERC_A_ERROR            : Semi-major axis less than or equal to zero
+ *       TRANMERC_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	                  (250 to 350)
+ *       TRANMERC_SCALE_FACTOR_ERROR : Scale factor outside of valid
+ *                                     range (0.3 to 3.0)
+ *		 TM_LON_WARNING              : Distortion will result if longitude is more
+ *                                       than 9 degrees from the Central Meridian
+ *
+ * REUSE NOTES
+ *
+ *    TRANSVERSE MERCATOR is intended for reuse by any application that 
+ *    performs a Transverse Mercator projection or its inverse.
+ *    
+ * REFERENCES
+ *
+ *    Further information on TRANSVERSE MERCATOR can be found in the 
+ *    Reuse Manual.
+ *
+ *    TRANSVERSE MERCATOR originated from :  
+ *                      U.S. Army Topographic Engineering Center
+ *                      Geospatial Information Division
+ *                      7701 Telegraph Road
+ *                      Alexandria, VA  22310-3864
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *    TRANSVERSE MERCATOR has no restrictions.
+ *
+ * ENVIRONMENT
+ *
+ *    TRANSVERSE MERCATOR was tested and certified in the following 
+ *    environments:
+ *
+ *    1. Solaris 2.5 with GCC, version 2.8.1
+ *    2. Windows 95 with MS Visual C++, version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    10-02-97          Original Code
+ *    03-02-97          Re-engineered Code
+ *
+ */
+
+
+/***************************************************************************/
+/*
+ *                               INCLUDES
+ */
+
+#include <math.h>
+#include "tranmerc.h"
+
+/*
+ *    math.h      - Standard C math library
+ *    tranmerc.h  - Is for prototype error checking
+ */
+
+
+/***************************************************************************/
+/*                               DEFINES 
+ *
+ */
+
+#define PI              3.14159265358979323e0   /* PI     */
+#define PI_OVER_2         (PI/2.0e0)            /* PI over 2 */
+#define MAX_LAT         ((PI * 89.99)/180.0)    /* 89.99 degrees in radians */
+#define MAX_DELTA_LONG  ((PI * 90)/180.0)       /* 90 degrees in radians */
+#define MIN_SCALE_FACTOR  0.3
+#define MAX_SCALE_FACTOR  3.0
+
+#define SPHTMD(Latitude) ((double) (TranMerc_ap * Latitude \
+      - TranMerc_bp * sin(2.e0 * Latitude) + TranMerc_cp * sin(4.e0 * Latitude) \
+      - TranMerc_dp * sin(6.e0 * Latitude) + TranMerc_ep * sin(8.e0 * Latitude) ) )
+
+#define SPHSN(Latitude) ((double) (TranMerc_a / sqrt( 1.e0 - TranMerc_es * \
+      pow(sin(Latitude), 2))))
+
+#define SPHSR(Latitude) ((double) (TranMerc_a * (1.e0 - TranMerc_es) / \
+    pow(DENOM(Latitude), 3)))
+
+#define DENOM(Latitude) ((double) (sqrt(1.e0 - TranMerc_es * pow(sin(Latitude),2))))
+
+
+/**************************************************************************/
+/*                               GLOBAL DECLARATIONS
+ *
+ */
+
+/* Ellipsoid Parameters, default to WGS 84  */
+static double TranMerc_a = 6378137.0;              /* Semi-major axis of ellipsoid in meters */
+static double TranMerc_f = 1 / 298.257223563;      /* Flattening of ellipsoid  */
+static double TranMerc_es = 0.0066943799901413800; /* Eccentricity (0.08181919084262188000) squared */
+static double TranMerc_ebs = 0.0067394967565869;   /* Second Eccentricity squared */
+
+/* Transverse_Mercator projection Parameters */
+static double TranMerc_Origin_Lat = 0.0;           /* Latitude of origin in radians */
+static double TranMerc_Origin_Long = 0.0;          /* Longitude of origin in radians */
+static double TranMerc_False_Northing = 0.0;       /* False northing in meters */
+static double TranMerc_False_Easting = 0.0;        /* False easting in meters */
+static double TranMerc_Scale_Factor = 1.0;         /* Scale factor  */
+
+/* Isometeric to geodetic latitude parameters, default to WGS 84 */
+static double TranMerc_ap = 6367449.1458008;
+static double TranMerc_bp = 16038.508696861;
+static double TranMerc_cp = 16.832613334334;
+static double TranMerc_dp = 0.021984404273757;
+static double TranMerc_ep = 3.1148371319283e-005;
+
+/* Maximum variance for easting and northing values for WGS 84. */
+static double TranMerc_Delta_Easting = 40000000.0;
+static double TranMerc_Delta_Northing = 40000000.0;
+
+/* These state variables are for optimization purposes. The only function
+ * that should modify them is Set_Tranverse_Mercator_Parameters.         */
+
+
+/************************************************************************/
+/*                              FUNCTIONS     
+ *
+ */
+
+
+long Set_Transverse_Mercator_Parameters(double a,
+                                        double f,
+                                        double Origin_Latitude,
+                                        double Central_Meridian,
+                                        double False_Easting,
+                                        double False_Northing,
+                                        double Scale_Factor)
+
+{ /* BEGIN Set_Tranverse_Mercator_Parameters */
+  /*
+   * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid
+   * parameters and Tranverse Mercator projection parameters as inputs, and
+   * sets the corresponding state variables. If any errors occur, the error
+   * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
+   * returned.
+   *
+   *    a                 : Semi-major axis of ellipsoid, in meters    (input)
+   *    f                 : Flattening of ellipsoid						         (input)
+   *    Origin_Latitude   : Latitude in radians at the origin of the   (input)
+   *                         projection
+   *    Central_Meridian  : Longitude in radians at the center of the  (input)
+   *                         projection
+   *    False_Easting     : Easting/X at the center of the projection  (input)
+   *    False_Northing    : Northing/Y at the center of the projection (input)
+   *    Scale_Factor      : Projection scale factor                    (input) 
+   */
+
+  double tn;        /* True Meridianal distance constant  */
+  double tn2;
+  double tn3;
+  double tn4;
+  double tn5;
+  double dummy_northing;
+  double TranMerc_b; /* Semi-minor axis of ellipsoid, in meters */
+  double inv_f = 1 / f;
+  long Error_Code = TRANMERC_NO_ERROR;
+
+  if (a <= 0.0)
+  { /* Semi-major axis must be greater than zero */
+    Error_Code |= TRANMERC_A_ERROR;
+  }
+  if ((inv_f < 250) || (inv_f > 350))
+  { /* Inverse flattening must be between 250 and 350 */
+    Error_Code |= TRANMERC_INV_F_ERROR;
+  }
+  if ((Origin_Latitude < -PI_OVER_2) || (Origin_Latitude > PI_OVER_2))
+  { /* origin latitude out of range */
+    Error_Code |= TRANMERC_ORIGIN_LAT_ERROR;
+  }
+  if ((Central_Meridian < -PI) || (Central_Meridian > (2*PI)))
+  { /* origin longitude out of range */
+    Error_Code |= TRANMERC_CENT_MER_ERROR;
+  }
+  if ((Scale_Factor < MIN_SCALE_FACTOR) || (Scale_Factor > MAX_SCALE_FACTOR))
+  {
+    Error_Code |= TRANMERC_SCALE_FACTOR_ERROR;
+  }
+  if (!Error_Code)
+  { /* no errors */
+    TranMerc_a = a;
+    TranMerc_f = f;
+    TranMerc_Origin_Lat = Origin_Latitude;
+    if (Central_Meridian > PI)
+      Central_Meridian -= (2*PI);
+    TranMerc_Origin_Long = Central_Meridian;
+    TranMerc_False_Northing = False_Northing;
+    TranMerc_False_Easting = False_Easting; 
+    TranMerc_Scale_Factor = Scale_Factor;
+
+    /* Eccentricity Squared */
+    TranMerc_es = 2 * TranMerc_f - TranMerc_f * TranMerc_f;
+    /* Second Eccentricity Squared */
+    TranMerc_ebs = (1 / (1 - TranMerc_es)) - 1;
+
+    TranMerc_b = TranMerc_a * (1 - TranMerc_f);    
+    /*True meridianal constants  */
+    tn = (TranMerc_a - TranMerc_b) / (TranMerc_a + TranMerc_b);
+    tn2 = tn * tn;
+    tn3 = tn2 * tn;
+    tn4 = tn3 * tn;
+    tn5 = tn4 * tn;
+
+    TranMerc_ap = TranMerc_a * (1.e0 - tn + 5.e0 * (tn2 - tn3)/4.e0
+                                + 81.e0 * (tn4 - tn5)/64.e0 );
+    TranMerc_bp = 3.e0 * TranMerc_a * (tn - tn2 + 7.e0 * (tn3 - tn4)
+                                       /8.e0 + 55.e0 * tn5/64.e0 )/2.e0;
+    TranMerc_cp = 15.e0 * TranMerc_a * (tn2 - tn3 + 3.e0 * (tn4 - tn5 )/4.e0) /16.0;
+    TranMerc_dp = 35.e0 * TranMerc_a * (tn3 - tn4 + 11.e0 * tn5 / 16.e0) / 48.e0;
+    TranMerc_ep = 315.e0 * TranMerc_a * (tn4 - tn5) / 512.e0;
+    Convert_Geodetic_To_Transverse_Mercator(MAX_LAT,
+                                            MAX_DELTA_LONG + Central_Meridian,
+                                            &TranMerc_Delta_Easting,
+                                            &TranMerc_Delta_Northing);
+    Convert_Geodetic_To_Transverse_Mercator(0,
+                                            MAX_DELTA_LONG + Central_Meridian,
+                                            &TranMerc_Delta_Easting,
+                                            &dummy_northing);
+    TranMerc_Delta_Northing++;
+    TranMerc_Delta_Easting++;
+
+  } /* END OF if(!Error_Code) */
+  return (Error_Code);
+}  /* END of Set_Transverse_Mercator_Parameters  */
+
+
+void Get_Transverse_Mercator_Parameters(double *a,
+                                        double *f,
+                                        double *Origin_Latitude,
+                                        double *Central_Meridian,
+                                        double *False_Easting,
+                                        double *False_Northing,
+                                        double *Scale_Factor)
+
+{ /* BEGIN Get_Tranverse_Mercator_Parameters  */
+  /*
+   * The function Get_Transverse_Mercator_Parameters returns the current
+   * ellipsoid and Transverse Mercator projection parameters.
+   *
+   *    a                 : Semi-major axis of ellipsoid, in meters    (output)
+   *    f                 : Flattening of ellipsoid						         (output)
+   *    Origin_Latitude   : Latitude in radians at the origin of the   (output)
+   *                         projection
+   *    Central_Meridian  : Longitude in radians at the center of the  (output)
+   *                         projection
+   *    False_Easting     : Easting/X at the center of the projection  (output)
+   *    False_Northing    : Northing/Y at the center of the projection (output)
+   *    Scale_Factor      : Projection scale factor                    (output) 
+   */
+
+  *a = TranMerc_a;
+  *f = TranMerc_f;
+  *Origin_Latitude = TranMerc_Origin_Lat;
+  *Central_Meridian = TranMerc_Origin_Long;
+  *False_Easting = TranMerc_False_Easting;
+  *False_Northing = TranMerc_False_Northing;
+  *Scale_Factor = TranMerc_Scale_Factor;
+  return;
+} /* END OF Get_Tranverse_Mercator_Parameters */
+
+
+
+long Convert_Geodetic_To_Transverse_Mercator (double Latitude,
+                                              double Longitude,
+                                              double *Easting,
+                                              double *Northing)
+
+{      /* BEGIN Convert_Geodetic_To_Transverse_Mercator */
+
+  /*
+   * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic
+   * (latitude and longitude) coordinates to Transverse Mercator projection
+   * (easting and northing) coordinates, according to the current ellipsoid
+   * and Transverse Mercator projection coordinates.  If any errors occur, the
+   * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
+   * returned.
+   *
+   *    Latitude      : Latitude in radians                         (input)
+   *    Longitude     : Longitude in radians                        (input)
+   *    Easting       : Easting/X in meters                         (output)
+   *    Northing      : Northing/Y in meters                        (output)
+   */
+
+  double c;       /* Cosine of latitude                          */
+  double c2;
+  double c3;
+  double c5;
+  double c7;
+  double dlam;    /* Delta longitude - Difference in Longitude       */
+  double eta;     /* constant - TranMerc_ebs *c *c                   */
+  double eta2;
+  double eta3;
+  double eta4;
+  double s;       /* Sine of latitude                        */
+  double sn;      /* Radius of curvature in the prime vertical       */
+  double t;       /* Tangent of latitude                             */
+  double tan2;
+  double tan3;
+  double tan4;
+  double tan5;
+  double tan6;
+  double t1;      /* Term in coordinate conversion formula - GP to Y */
+  double t2;      /* Term in coordinate conversion formula - GP to Y */
+  double t3;      /* Term in coordinate conversion formula - GP to Y */
+  double t4;      /* Term in coordinate conversion formula - GP to Y */
+  double t5;      /* Term in coordinate conversion formula - GP to Y */
+  double t6;      /* Term in coordinate conversion formula - GP to Y */
+  double t7;      /* Term in coordinate conversion formula - GP to Y */
+  double t8;      /* Term in coordinate conversion formula - GP to Y */
+  double t9;      /* Term in coordinate conversion formula - GP to Y */
+  double tmd;     /* True Meridional distance                        */
+  double tmdo;    /* True Meridional distance for latitude of origin */
+  long    Error_Code = TRANMERC_NO_ERROR;
+  double temp_Origin;
+  double temp_Long;
+
+  if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT))
+  {  /* Latitude out of range */
+    Error_Code|= TRANMERC_LAT_ERROR;
+  }
+  if (Longitude > PI)
+    Longitude -= (2 * PI);
+  if ((Longitude < (TranMerc_Origin_Long - MAX_DELTA_LONG))
+      || (Longitude > (TranMerc_Origin_Long + MAX_DELTA_LONG)))
+  {
+    if (Longitude < 0)
+      temp_Long = Longitude + 2 * PI;
+    else
+      temp_Long = Longitude;
+    if (TranMerc_Origin_Long < 0)
+      temp_Origin = TranMerc_Origin_Long + 2 * PI;
+    else
+      temp_Origin = TranMerc_Origin_Long;
+    if ((temp_Long < (temp_Origin - MAX_DELTA_LONG))
+        || (temp_Long > (temp_Origin + MAX_DELTA_LONG)))
+      Error_Code|= TRANMERC_LON_ERROR;
+  }
+  if (!Error_Code)
+  { /* no errors */
+
+    /* 
+     *  Delta Longitude
+     */
+    dlam = Longitude - TranMerc_Origin_Long;
+
+    if (fabs(dlam) > (9.0 * PI / 180))
+    { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian */
+      Error_Code |= TRANMERC_LON_WARNING;
+    }
+
+    if (dlam > PI)
+      dlam -= (2 * PI);
+    if (dlam < -PI)
+      dlam += (2 * PI);
+    if (fabs(dlam) < 2.e-10)
+      dlam = 0.0;
+
+    s = sin(Latitude);
+    c = cos(Latitude);
+    c2 = c * c;
+    c3 = c2 * c;
+    c5 = c3 * c2;
+    c7 = c5 * c2;
+    t = tan (Latitude);
+    tan2 = t * t;
+    tan3 = tan2 * t;
+    tan4 = tan3 * t;
+    tan5 = tan4 * t;
+    tan6 = tan5 * t;
+    eta = TranMerc_ebs * c2;
+    eta2 = eta * eta;
+    eta3 = eta2 * eta;
+    eta4 = eta3 * eta;
+
+    /* radius of curvature in prime vertical */
+    sn = SPHSN(Latitude);
+
+    /* True Meridianal Distances */
+    tmd = SPHTMD(Latitude);
+
+    /*  Origin  */
+    tmdo = SPHTMD (TranMerc_Origin_Lat);
+
+    /* northing */
+    t1 = (tmd - tmdo) * TranMerc_Scale_Factor;
+    t2 = sn * s * c * TranMerc_Scale_Factor/ 2.e0;
+    t3 = sn * s * c3 * TranMerc_Scale_Factor * (5.e0 - tan2 + 9.e0 * eta 
+                                                + 4.e0 * eta2) /24.e0; 
+
+    t4 = sn * s * c5 * TranMerc_Scale_Factor * (61.e0 - 58.e0 * tan2
+                                                + tan4 + 270.e0 * eta - 330.e0 * tan2 * eta + 445.e0 * eta2
+                                                + 324.e0 * eta3 -680.e0 * tan2 * eta2 + 88.e0 * eta4 
+                                                -600.e0 * tan2 * eta3 - 192.e0 * tan2 * eta4) / 720.e0;
+
+    t5 = sn * s * c7 * TranMerc_Scale_Factor * (1385.e0 - 3111.e0 * 
+                                                tan2 + 543.e0 * tan4 - tan6) / 40320.e0;
+
+    *Northing = TranMerc_False_Northing + t1 + pow(dlam,2.e0) * t2
+                + pow(dlam,4.e0) * t3 + pow(dlam,6.e0) * t4
+                + pow(dlam,8.e0) * t5; 
+
+    /* Easting */
+    t6 = sn * c * TranMerc_Scale_Factor;
+    t7 = sn * c3 * TranMerc_Scale_Factor * (1.e0 - tan2 + eta ) /6.e0;
+    t8 = sn * c5 * TranMerc_Scale_Factor * (5.e0 - 18.e0 * tan2 + tan4
+                                            + 14.e0 * eta - 58.e0 * tan2 * eta + 13.e0 * eta2 + 4.e0 * eta3 
+                                            - 64.e0 * tan2 * eta2 - 24.e0 * tan2 * eta3 )/ 120.e0;
+    t9 = sn * c7 * TranMerc_Scale_Factor * ( 61.e0 - 479.e0 * tan2
+                                             + 179.e0 * tan4 - tan6 ) /5040.e0;
+
+    *Easting = TranMerc_False_Easting + dlam * t6 + pow(dlam,3.e0) * t7 
+               + pow(dlam,5.e0) * t8 + pow(dlam,7.e0) * t9;
+  }
+  return (Error_Code);
+} /* END OF Convert_Geodetic_To_Transverse_Mercator */
+
+
+long Convert_Transverse_Mercator_To_Geodetic (
+                                             double Easting,
+                                             double Northing,
+                                             double *Latitude,
+                                             double *Longitude)
+{      /* BEGIN Convert_Transverse_Mercator_To_Geodetic */
+
+  /*
+   * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse
+   * Mercator projection (easting and northing) coordinates to geodetic
+   * (latitude and longitude) coordinates, according to the current ellipsoid
+   * and Transverse Mercator projection parameters.  If any errors occur, the
+   * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
+   * returned.
+   *
+   *    Easting       : Easting/X in meters                         (input)
+   *    Northing      : Northing/Y in meters                        (input)
+   *    Latitude      : Latitude in radians                         (output)
+   *    Longitude     : Longitude in radians                        (output)
+   */
+
+  double c;       /* Cosine of latitude                          */
+  double de;      /* Delta easting - Difference in Easting (Easting-Fe)    */
+  double dlam;    /* Delta longitude - Difference in Longitude       */
+  double eta;     /* constant - TranMerc_ebs *c *c                   */
+  double eta2;
+  double eta3;
+  double eta4;
+  double ftphi;   /* Footpoint latitude                              */
+  int    i;       /* Loop iterator                   */
+  //double s;       /* Sine of latitude                        */
+  double sn;      /* Radius of curvature in the prime vertical       */
+  double sr;      /* Radius of curvature in the meridian             */
+  double t;       /* Tangent of latitude                             */
+  double tan2;
+  double tan4;
+  double t10;     /* Term in coordinate conversion formula - GP to Y */
+  double t11;     /* Term in coordinate conversion formula - GP to Y */
+  double t12;     /* Term in coordinate conversion formula - GP to Y */
+  double t13;     /* Term in coordinate conversion formula - GP to Y */
+  double t14;     /* Term in coordinate conversion formula - GP to Y */
+  double t15;     /* Term in coordinate conversion formula - GP to Y */
+  double t16;     /* Term in coordinate conversion formula - GP to Y */
+  double t17;     /* Term in coordinate conversion formula - GP to Y */
+  double tmd;     /* True Meridional distance                        */
+  double tmdo;    /* True Meridional distance for latitude of origin */
+  long Error_Code = TRANMERC_NO_ERROR;
+
+  if ((Easting < (TranMerc_False_Easting - TranMerc_Delta_Easting))
+      ||(Easting > (TranMerc_False_Easting + TranMerc_Delta_Easting)))
+  { /* Easting out of range  */
+    Error_Code |= TRANMERC_EASTING_ERROR;
+  }
+  if ((Northing < (TranMerc_False_Northing - TranMerc_Delta_Northing))
+      || (Northing > (TranMerc_False_Northing + TranMerc_Delta_Northing)))
+  { /* Northing out of range */
+    Error_Code |= TRANMERC_NORTHING_ERROR;
+  }
+
+  if (!Error_Code)
+  {
+    /* True Meridional Distances for latitude of origin */
+    tmdo = SPHTMD(TranMerc_Origin_Lat);
+
+    /*  Origin  */
+    tmd = tmdo +  (Northing - TranMerc_False_Northing) / TranMerc_Scale_Factor; 
+
+    /* First Estimate */
+    sr = SPHSR(0.e0);
+    ftphi = tmd/sr;
+
+    for (i = 0; i < 5 ; i++)
+    {
+      t10 = SPHTMD (ftphi);
+      sr = SPHSR(ftphi);
+      ftphi = ftphi + (tmd - t10) / sr;
+    }
+
+    /* Radius of Curvature in the meridian */
+    sr = SPHSR(ftphi);
+
+    /* Radius of Curvature in the meridian */
+    sn = SPHSN(ftphi);
+
+    /* Sine Cosine terms */
+    //s = sin(ftphi);
+    c = cos(ftphi);
+
+    /* Tangent Value  */
+    t = tan(ftphi);
+    tan2 = t * t;
+    tan4 = tan2 * tan2;
+    eta = TranMerc_ebs * pow(c,2);
+    eta2 = eta * eta;
+    eta3 = eta2 * eta;
+    eta4 = eta3 * eta;
+    de = Easting - TranMerc_False_Easting;
+    if (fabs(de) < 0.0001)
+      de = 0.0;
+
+    /* Latitude */
+    t10 = t / (2.e0 * sr * sn * pow(TranMerc_Scale_Factor, 2));
+    t11 = t * (5.e0  + 3.e0 * tan2 + eta - 4.e0 * pow(eta,2)
+               - 9.e0 * tan2 * eta) / (24.e0 * sr * pow(sn,3) 
+                                       * pow(TranMerc_Scale_Factor,4));
+    t12 = t * (61.e0 + 90.e0 * tan2 + 46.e0 * eta + 45.E0 * tan4
+               - 252.e0 * tan2 * eta  - 3.e0 * eta2 + 100.e0 
+               * eta3 - 66.e0 * tan2 * eta2 - 90.e0 * tan4
+               * eta + 88.e0 * eta4 + 225.e0 * tan4 * eta2
+               + 84.e0 * tan2* eta3 - 192.e0 * tan2 * eta4)
+          / ( 720.e0 * sr * pow(sn,5) * pow(TranMerc_Scale_Factor, 6) );
+    t13 = t * ( 1385.e0 + 3633.e0 * tan2 + 4095.e0 * tan4 + 1575.e0 
+                * pow(t,6))/ (40320.e0 * sr * pow(sn,7) * pow(TranMerc_Scale_Factor,8));
+    *Latitude = ftphi - pow(de,2) * t10 + pow(de,4) * t11 - pow(de,6) * t12 
+                + pow(de,8) * t13;
+
+    t14 = 1.e0 / (sn * c * TranMerc_Scale_Factor);
+
+    t15 = (1.e0 + 2.e0 * tan2 + eta) / (6.e0 * pow(sn,3) * c * 
+                                        pow(TranMerc_Scale_Factor,3));
+
+    t16 = (5.e0 + 6.e0 * eta + 28.e0 * tan2 - 3.e0 * eta2
+           + 8.e0 * tan2 * eta + 24.e0 * tan4 - 4.e0 
+           * eta3 + 4.e0 * tan2 * eta2 + 24.e0 
+           * tan2 * eta3) / (120.e0 * pow(sn,5) * c  
+                             * pow(TranMerc_Scale_Factor,5));
+
+    t17 = (61.e0 +  662.e0 * tan2 + 1320.e0 * tan4 + 720.e0 
+           * pow(t,6)) / (5040.e0 * pow(sn,7) * c 
+                          * pow(TranMerc_Scale_Factor,7));
+
+    /* Difference in Longitude */
+    dlam = de * t14 - pow(de,3) * t15 + pow(de,5) * t16 - pow(de,7) * t17;
+
+    /* Longitude */
+    (*Longitude) = TranMerc_Origin_Long + dlam;
+
+    if((fabs)(*Latitude) > (90.0 * PI / 180.0))
+      Error_Code |= TRANMERC_NORTHING_ERROR;
+
+    if((*Longitude) > (PI))
+    {
+      *Longitude -= (2 * PI);
+      if((fabs)(*Longitude) > PI)
+        Error_Code |= TRANMERC_EASTING_ERROR;
+    }
+    else if((*Longitude) < (-PI))
+    {
+      *Longitude += (2 * PI);
+      if((fabs)(*Longitude) > PI)
+        Error_Code |= TRANMERC_EASTING_ERROR;
+    }
+
+    if (fabs(dlam) > (9.0 * PI / 180) * cos(*Latitude))
+    { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian at the equator */
+      /* and decreases to 0 degrees at the poles */
+      /* As you move towards the poles, distortion will become more significant */
+      Error_Code |= TRANMERC_LON_WARNING;
+    }
+  }
+  return (Error_Code);
+} /* END OF Convert_Transverse_Mercator_To_Geodetic */
diff --git a/geotranz/tranmerc.h b/geotranz/tranmerc.h
old mode 100755
new mode 100644
index e494035..5755ddd
--- a/geotranz/tranmerc.h
+++ b/geotranz/tranmerc.h
@@ -1,209 +1,209 @@
-#ifndef TRANMERC_H
-  #define TRANMERC_H
-
-/***************************************************************************/
-/* RSC IDENTIFIER: TRANSVERSE MERCATOR
- *
- * ABSTRACT
- *
- *    This component provides conversions between Geodetic coordinates 
- *    (latitude and longitude) and Transverse Mercator projection coordinates
- *    (easting and northing).
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *       TRANMERC_NO_ERROR           : No errors occurred in function
- *       TRANMERC_LAT_ERROR          : Latitude outside of valid range
- *                                      (-90 to 90 degrees)
- *       TRANMERC_LON_ERROR          : Longitude outside of valid range
- *                                      (-180 to 360 degrees, and within
- *                                        +/-90 of Central Meridian)
- *       TRANMERC_EASTING_ERROR      : Easting outside of valid range
- *                                      (depending on ellipsoid and
- *                                       projection parameters)
- *       TRANMERC_NORTHING_ERROR     : Northing outside of valid range
- *                                      (depending on ellipsoid and
- *                                       projection parameters)
- *       TRANMERC_ORIGIN_LAT_ERROR   : Origin latitude outside of valid range
- *                                      (-90 to 90 degrees)
- *       TRANMERC_CENT_MER_ERROR     : Central meridian outside of valid range
- *                                      (-180 to 360 degrees)
- *       TRANMERC_A_ERROR            : Semi-major axis less than or equal to zero
- *       TRANMERC_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	                  (250 to 350)
- *       TRANMERC_SCALE_FACTOR_ERROR : Scale factor outside of valid
- *                                     range (0.3 to 3.0)
- *		 TM_LON_WARNING              : Distortion will result if longitude is more
- *                                      than 9 degrees from the Central Meridian
- *
- * REUSE NOTES
- *
- *    TRANSVERSE MERCATOR is intended for reuse by any application that 
- *    performs a Transverse Mercator projection or its inverse.
- *    
- * REFERENCES
- *
- *    Further information on TRANSVERSE MERCATOR can be found in the 
- *    Reuse Manual.
- *
- *    TRANSVERSE MERCATOR originated from :  
- *                      U.S. Army Topographic Engineering Center
- *                      Geospatial Information Division
- *                      7701 Telegraph Road
- *                      Alexandria, VA  22310-3864
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *    TRANSVERSE MERCATOR has no restrictions.
- *
- * ENVIRONMENT
- *
- *    TRANSVERSE MERCATOR was tested and certified in the following 
- *    environments:
- *
- *    1. Solaris 2.5 with GCC, version 2.8.1
- *    2. Windows 95 with MS Visual C++, version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    10-02-97          Original Code
- *    03-02-97          Re-engineered Code
- *
- */
-
-
-/***************************************************************************/
-/*
- *                              DEFINES
- */
-
-  #define TRANMERC_NO_ERROR           0x0000
-  #define TRANMERC_LAT_ERROR          0x0001
-  #define TRANMERC_LON_ERROR          0x0002
-  #define TRANMERC_EASTING_ERROR      0x0004
-  #define TRANMERC_NORTHING_ERROR     0x0008
-  #define TRANMERC_ORIGIN_LAT_ERROR   0x0010
-  #define TRANMERC_CENT_MER_ERROR     0x0020
-  #define TRANMERC_A_ERROR            0x0040
-  #define TRANMERC_INV_F_ERROR        0x0080
-  #define TRANMERC_SCALE_FACTOR_ERROR 0x0100
-  #define TRANMERC_LON_WARNING        0x0200
-
-
-/***************************************************************************/
-/*
- *                              FUNCTION PROTOTYPES
- *                                for TRANMERC.C
- */
-
-/* ensure proper linkage to c++ programs */
-  #ifdef __cplusplus
-extern "C" {
-  #endif
-
-
-  long Set_Transverse_Mercator_Parameters(double a,      
-                                          double f,
-                                          double Origin_Latitude,
-                                          double Central_Meridian,
-                                          double False_Easting,
-                                          double False_Northing,
-                                          double Scale_Factor);
-/*
- * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid
- * parameters and Tranverse Mercator projection parameters as inputs, and
- * sets the corresponding state variables. If any errors occur, the error
- * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
- * returned.
- *
- *    a                 : Semi-major axis of ellipsoid, in meters    (input)
- *    f                 : Flattening of ellipsoid                    (input)
- *    Origin_Latitude   : Latitude in radians at the origin of the   (input)
- *                         projection
- *    Central_Meridian  : Longitude in radians at the center of the  (input)
- *                         projection
- *    False_Easting     : Easting/X at the center of the projection  (input)
- *    False_Northing    : Northing/Y at the center of the projection (input)
- *    Scale_Factor      : Projection scale factor                    (input) 
- */
-
-
-  void Get_Transverse_Mercator_Parameters(double *a,
-                                          double *f,
-                                          double *Origin_Latitude,
-                                          double *Central_Meridian,
-                                          double *False_Easting,
-                                          double *False_Northing,
-                                          double *Scale_Factor);
-/*
- * The function Get_Transverse_Mercator_Parameters returns the current
- * ellipsoid and Transverse Mercator projection parameters.
- *
- *    a                 : Semi-major axis of ellipsoid, in meters    (output)
- *    f                 : Flattening of ellipsoid                    (output)
- *    Origin_Latitude   : Latitude in radians at the origin of the   (output)
- *                         projection
- *    Central_Meridian  : Longitude in radians at the center of the  (output)
- *                         projection
- *    False_Easting     : Easting/X at the center of the projection  (output)
- *    False_Northing    : Northing/Y at the center of the projection (output)
- *    Scale_Factor      : Projection scale factor                    (output) 
- */
-
-
-  long Convert_Geodetic_To_Transverse_Mercator (double Latitude,
-                                                double Longitude,
-                                                double *Easting,
-                                                double *Northing);
-
-/*
- * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic
- * (latitude and longitude) coordinates to Transverse Mercator projection
- * (easting and northing) coordinates, according to the current ellipsoid
- * and Transverse Mercator projection coordinates.  If any errors occur, the
- * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
- * returned.
- *
- *    Latitude      : Latitude in radians                         (input)
- *    Longitude     : Longitude in radians                        (input)
- *    Easting       : Easting/X in meters                         (output)
- *    Northing      : Northing/Y in meters                        (output)
- */
-
-
-  long Convert_Transverse_Mercator_To_Geodetic (double Easting,
-                                                double Northing,
-                                                double *Latitude,
-                                                double *Longitude);
-
-/*
- * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse
- * Mercator projection (easting and northing) coordinates to geodetic
- * (latitude and longitude) coordinates, according to the current ellipsoid
- * and Transverse Mercator projection parameters.  If any errors occur, the
- * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
- * returned.
- *
- *    Easting       : Easting/X in meters                         (input)
- *    Northing      : Northing/Y in meters                        (input)
- *    Latitude      : Latitude in radians                         (output)
- *    Longitude     : Longitude in radians                        (output)
- */
-
-
-  #ifdef __cplusplus
-}
-  #endif
-
-#endif /* TRANMERC_H */
+#ifndef TRANMERC_H
+  #define TRANMERC_H
+
+/***************************************************************************/
+/* RSC IDENTIFIER: TRANSVERSE MERCATOR
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between Geodetic coordinates 
+ *    (latitude and longitude) and Transverse Mercator projection coordinates
+ *    (easting and northing).
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *       TRANMERC_NO_ERROR           : No errors occurred in function
+ *       TRANMERC_LAT_ERROR          : Latitude outside of valid range
+ *                                      (-90 to 90 degrees)
+ *       TRANMERC_LON_ERROR          : Longitude outside of valid range
+ *                                      (-180 to 360 degrees, and within
+ *                                        +/-90 of Central Meridian)
+ *       TRANMERC_EASTING_ERROR      : Easting outside of valid range
+ *                                      (depending on ellipsoid and
+ *                                       projection parameters)
+ *       TRANMERC_NORTHING_ERROR     : Northing outside of valid range
+ *                                      (depending on ellipsoid and
+ *                                       projection parameters)
+ *       TRANMERC_ORIGIN_LAT_ERROR   : Origin latitude outside of valid range
+ *                                      (-90 to 90 degrees)
+ *       TRANMERC_CENT_MER_ERROR     : Central meridian outside of valid range
+ *                                      (-180 to 360 degrees)
+ *       TRANMERC_A_ERROR            : Semi-major axis less than or equal to zero
+ *       TRANMERC_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	                  (250 to 350)
+ *       TRANMERC_SCALE_FACTOR_ERROR : Scale factor outside of valid
+ *                                     range (0.3 to 3.0)
+ *		 TM_LON_WARNING              : Distortion will result if longitude is more
+ *                                      than 9 degrees from the Central Meridian
+ *
+ * REUSE NOTES
+ *
+ *    TRANSVERSE MERCATOR is intended for reuse by any application that 
+ *    performs a Transverse Mercator projection or its inverse.
+ *    
+ * REFERENCES
+ *
+ *    Further information on TRANSVERSE MERCATOR can be found in the 
+ *    Reuse Manual.
+ *
+ *    TRANSVERSE MERCATOR originated from :  
+ *                      U.S. Army Topographic Engineering Center
+ *                      Geospatial Information Division
+ *                      7701 Telegraph Road
+ *                      Alexandria, VA  22310-3864
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *    TRANSVERSE MERCATOR has no restrictions.
+ *
+ * ENVIRONMENT
+ *
+ *    TRANSVERSE MERCATOR was tested and certified in the following 
+ *    environments:
+ *
+ *    1. Solaris 2.5 with GCC, version 2.8.1
+ *    2. Windows 95 with MS Visual C++, version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    10-02-97          Original Code
+ *    03-02-97          Re-engineered Code
+ *
+ */
+
+
+/***************************************************************************/
+/*
+ *                              DEFINES
+ */
+
+  #define TRANMERC_NO_ERROR           0x0000
+  #define TRANMERC_LAT_ERROR          0x0001
+  #define TRANMERC_LON_ERROR          0x0002
+  #define TRANMERC_EASTING_ERROR      0x0004
+  #define TRANMERC_NORTHING_ERROR     0x0008
+  #define TRANMERC_ORIGIN_LAT_ERROR   0x0010
+  #define TRANMERC_CENT_MER_ERROR     0x0020
+  #define TRANMERC_A_ERROR            0x0040
+  #define TRANMERC_INV_F_ERROR        0x0080
+  #define TRANMERC_SCALE_FACTOR_ERROR 0x0100
+  #define TRANMERC_LON_WARNING        0x0200
+
+
+/***************************************************************************/
+/*
+ *                              FUNCTION PROTOTYPES
+ *                                for TRANMERC.C
+ */
+
+/* ensure proper linkage to c++ programs */
+  #ifdef __cplusplus
+extern "C" {
+  #endif
+
+
+  long Set_Transverse_Mercator_Parameters(double a,      
+                                          double f,
+                                          double Origin_Latitude,
+                                          double Central_Meridian,
+                                          double False_Easting,
+                                          double False_Northing,
+                                          double Scale_Factor);
+/*
+ * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid
+ * parameters and Tranverse Mercator projection parameters as inputs, and
+ * sets the corresponding state variables. If any errors occur, the error
+ * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
+ * returned.
+ *
+ *    a                 : Semi-major axis of ellipsoid, in meters    (input)
+ *    f                 : Flattening of ellipsoid                    (input)
+ *    Origin_Latitude   : Latitude in radians at the origin of the   (input)
+ *                         projection
+ *    Central_Meridian  : Longitude in radians at the center of the  (input)
+ *                         projection
+ *    False_Easting     : Easting/X at the center of the projection  (input)
+ *    False_Northing    : Northing/Y at the center of the projection (input)
+ *    Scale_Factor      : Projection scale factor                    (input) 
+ */
+
+
+  void Get_Transverse_Mercator_Parameters(double *a,
+                                          double *f,
+                                          double *Origin_Latitude,
+                                          double *Central_Meridian,
+                                          double *False_Easting,
+                                          double *False_Northing,
+                                          double *Scale_Factor);
+/*
+ * The function Get_Transverse_Mercator_Parameters returns the current
+ * ellipsoid and Transverse Mercator projection parameters.
+ *
+ *    a                 : Semi-major axis of ellipsoid, in meters    (output)
+ *    f                 : Flattening of ellipsoid                    (output)
+ *    Origin_Latitude   : Latitude in radians at the origin of the   (output)
+ *                         projection
+ *    Central_Meridian  : Longitude in radians at the center of the  (output)
+ *                         projection
+ *    False_Easting     : Easting/X at the center of the projection  (output)
+ *    False_Northing    : Northing/Y at the center of the projection (output)
+ *    Scale_Factor      : Projection scale factor                    (output) 
+ */
+
+
+  long Convert_Geodetic_To_Transverse_Mercator (double Latitude,
+                                                double Longitude,
+                                                double *Easting,
+                                                double *Northing);
+
+/*
+ * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic
+ * (latitude and longitude) coordinates to Transverse Mercator projection
+ * (easting and northing) coordinates, according to the current ellipsoid
+ * and Transverse Mercator projection coordinates.  If any errors occur, the
+ * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
+ * returned.
+ *
+ *    Latitude      : Latitude in radians                         (input)
+ *    Longitude     : Longitude in radians                        (input)
+ *    Easting       : Easting/X in meters                         (output)
+ *    Northing      : Northing/Y in meters                        (output)
+ */
+
+
+  long Convert_Transverse_Mercator_To_Geodetic (double Easting,
+                                                double Northing,
+                                                double *Latitude,
+                                                double *Longitude);
+
+/*
+ * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse
+ * Mercator projection (easting and northing) coordinates to geodetic
+ * (latitude and longitude) coordinates, according to the current ellipsoid
+ * and Transverse Mercator projection parameters.  If any errors occur, the
+ * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is
+ * returned.
+ *
+ *    Easting       : Easting/X in meters                         (input)
+ *    Northing      : Northing/Y in meters                        (input)
+ *    Latitude      : Latitude in radians                         (output)
+ *    Longitude     : Longitude in radians                        (output)
+ */
+
+
+  #ifdef __cplusplus
+}
+  #endif
+
+#endif /* TRANMERC_H */
diff --git a/geotranz/ups.c b/geotranz/ups.c
old mode 100755
new mode 100644
index 9d94465..f9975f2
--- a/geotranz/ups.c
+++ b/geotranz/ups.c
@@ -1,302 +1,302 @@
-/********************************************************************/
-/* RSC IDENTIFIER: UPS
- *
- *
- * ABSTRACT
- *
- *    This component provides conversions between geodetic (latitude
- *    and longitude) coordinates and Universal Polar Stereographic (UPS)
- *    projection (hemisphere, easting, and northing) coordinates.
- *
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an 
- *    invalid value is found the error code is combined with the 
- *    current error code using the bitwise or.  This combining allows  
- *    multiple error codes to be returned. The possible error codes 
- *    are:
- *
- *         UPS_NO_ERROR           : No errors occurred in function
- *         UPS_LAT_ERROR          : Latitude outside of valid range
- *                                   (North Pole: 83.5 to 90,
- *                                    South Pole: -79.5 to -90)
- *         UPS_LON_ERROR          : Longitude outside of valid range
- *                                   (-180 to 360 degrees)
- *         UPS_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
- *         UPS_EASTING_ERROR      : Easting outside of valid range,
- *                                   (0 to 4,000,000m)
- *         UPS_NORTHING_ERROR     : Northing outside of valid range,
- *                                   (0 to 4,000,000m)
- *         UPS_A_ERROR            : Semi-major axis less than or equal to zero
- *         UPS_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	               (250 to 350)
- *
- *
- * REUSE NOTES
- *
- *    UPS is intended for reuse by any application that performs a Universal
- *    Polar Stereographic (UPS) projection.
- *
- *
- * REFERENCES
- *
- *    Further information on UPS can be found in the Reuse Manual.
- *
- *    UPS originated from :  U.S. Army Topographic Engineering Center
- *                           Geospatial Information Division
- *                           7701 Telegraph Road
- *                           Alexandria, VA  22310-3864
- *
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- *
- * RESTRICTIONS
- *
- *    UPS has no restrictions.
- *
- *
- * ENVIRONMENT
- *
- *    UPS was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC version 2.8.1
- *    2. Windows 95 with MS Visual C++ version 6
- *
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    06-11-95          Original Code
- *    03-01-97          Original Code
- *
- *
- */
-
-
-/************************************************************************/
-/*
- *                               INCLUDES
- */
-
-#include <math.h>
-#include "polarst.h"
-#include "ups.h"
-/*
- *    math.h     - Is needed to call the math functions.
- *    polar.h    - Is used to convert polar stereographic coordinates
- *    ups.h      - Defines the function prototypes for the ups module.
- */
-
-
-/************************************************************************/
-/*                               GLOBAL DECLARATIONS
- *
- */
-
-#define PI       3.14159265358979323e0  /* PI     */
-#define PI_OVER    (PI/2.0e0)           /* PI over 2 */
-#define MAX_LAT    ((PI * 90)/180.0)    /* 90 degrees in radians */
-#define MAX_ORIGIN_LAT ((81.114528 * PI) / 180.0)
-#define MIN_NORTH_LAT (83.5*PI/180.0)
-#define MIN_SOUTH_LAT (-79.5*PI/180.0)
-#define MIN_EAST_NORTH 0
-#define MAX_EAST_NORTH 4000000
-
-/* Ellipsoid Parameters, default to WGS 84  */
-static double UPS_a = 6378137.0;          /* Semi-major axis of ellipsoid in meters   */
-static double UPS_f = 1 / 298.257223563;  /* Flattening of ellipsoid  */
-const double UPS_False_Easting = 2000000;
-const double UPS_False_Northing = 2000000;
-static double UPS_Origin_Latitude = MAX_ORIGIN_LAT;  /*set default = North Hemisphere */
-static double UPS_Origin_Longitude = 0.0;
-
-
-/************************************************************************/
-/*                              FUNCTIONS
- *
- */
-
-
-long Set_UPS_Parameters( double a,
-                         double f)
-{
-/*
- * The function SET_UPS_PARAMETERS receives the ellipsoid parameters and sets
- * the corresponding state variables. If any errors occur, the error code(s)
- * are returned by the function, otherwise UPS_NO_ERROR is returned.
- *
- *   a     : Semi-major axis of ellipsoid in meters (input)
- *   f     : Flattening of ellipsoid					      (input)
- */
-
-  double inv_f = 1 / f;
-  long Error_Code = UPS_NO_ERROR;
-
-  if (a <= 0.0)
-  { /* Semi-major axis must be greater than zero */
-    Error_Code |= UPS_A_ERROR;
-  }
-  if ((inv_f < 250) || (inv_f > 350))
-  { /* Inverse flattening must be between 250 and 350 */
-    Error_Code |= UPS_INV_F_ERROR;
-  }
-
-  if (!Error_Code)
-  { /* no errors */
-    UPS_a = a;
-    UPS_f = f;
-  }
-  return (Error_Code);
-}  /* END of Set_UPS_Parameters  */
-
-
-void Get_UPS_Parameters( double *a,
-                         double *f)
-{
-/*
- * The function Get_UPS_Parameters returns the current ellipsoid parameters.
- *
- *  a      : Semi-major axis of ellipsoid, in meters (output)
- *  f      : Flattening of ellipsoid					       (output)
- */
-
-  *a = UPS_a;
-  *f = UPS_f;
-  return;
-} /* END OF Get_UPS_Parameters */
-
-
-long Convert_Geodetic_To_UPS ( double Latitude,
-                               double Longitude,
-                               char   *Hemisphere,
-                               double *Easting,
-                               double *Northing)
-{
-/*
- *  The function Convert_Geodetic_To_UPS converts geodetic (latitude and
- *  longitude) coordinates to UPS (hemisphere, easting, and northing)
- *  coordinates, according to the current ellipsoid parameters. If any 
- *  errors occur, the error code(s) are returned by the function, 
- *  otherwide UPS_NO_ERROR is returned.
- *
- *    Latitude      : Latitude in radians                       (input)
- *    Longitude     : Longitude in radians                      (input)
- *    Hemisphere    : Hemisphere either 'N' or 'S'              (output)
- *    Easting       : Easting/X in meters                       (output)
- *    Northing      : Northing/Y in meters                      (output)
- */
-
-  double tempEasting, tempNorthing;
-  long Error_Code = UPS_NO_ERROR;
-
-  if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT))
-  {   /* latitude out of range */
-    Error_Code |= UPS_LAT_ERROR;
-  }
-  if ((Latitude < 0) && (Latitude > MIN_SOUTH_LAT))
-    Error_Code |= UPS_LAT_ERROR;
-  if ((Latitude >= 0) && (Latitude < MIN_NORTH_LAT))
-    Error_Code |= UPS_LAT_ERROR;
-  if ((Longitude < -PI) || (Longitude > (2 * PI)))
-  {  /* slam out of range */
-    Error_Code |= UPS_LON_ERROR;
-  }
-
-  if (!Error_Code)
-  {  /* no errors */
-    if (Latitude < 0)
-    {
-      UPS_Origin_Latitude = -MAX_ORIGIN_LAT; 
-      *Hemisphere = 'S';
-    }
-    else
-    {
-      UPS_Origin_Latitude = MAX_ORIGIN_LAT; 
-      *Hemisphere = 'N';
-    }
-
-
-    Set_Polar_Stereographic_Parameters( UPS_a,
-                                        UPS_f,
-                                        UPS_Origin_Latitude,
-                                        UPS_Origin_Longitude,
-                                        UPS_False_Easting,
-                                        UPS_False_Northing);
-
-    Convert_Geodetic_To_Polar_Stereographic(Latitude,
-                                            Longitude,
-                                            &tempEasting,
-                                            &tempNorthing);
-
-    *Easting = tempEasting;
-    *Northing = tempNorthing;
-  }  /*  END of if(!Error_Code)   */
-
-  return Error_Code;
-}  /* END OF Convert_Geodetic_To_UPS  */
-
-
-long Convert_UPS_To_Geodetic(char   Hemisphere,
-                             double Easting,
-                             double Northing,
-                             double *Latitude,
-                             double *Longitude)
-{
-/*
- *  The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, 
- *  and northing) coordinates to geodetic (latitude and longitude) coordinates
- *  according to the current ellipsoid parameters.  If any errors occur, the 
- *  error code(s) are returned by the function, otherwise UPS_NO_ERROR is 
- *  returned.
- *
- *    Hemisphere    : Hemisphere either 'N' or 'S'              (input)
- *    Easting       : Easting/X in meters                       (input)
- *    Northing      : Northing/Y in meters                      (input)
- *    Latitude      : Latitude in radians                       (output)
- *    Longitude     : Longitude in radians                      (output)
- */
-
-  long Error_Code = UPS_NO_ERROR;
-
-  if ((Hemisphere != 'N') && (Hemisphere != 'S'))
-    Error_Code |= UPS_HEMISPHERE_ERROR;
-  if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH))
-    Error_Code |= UPS_EASTING_ERROR;
-  if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH))
-    Error_Code |= UPS_NORTHING_ERROR;
-
-  if (Hemisphere =='N')
-  {UPS_Origin_Latitude = MAX_ORIGIN_LAT;}
-  if (Hemisphere =='S')
-  {UPS_Origin_Latitude = -MAX_ORIGIN_LAT;}
-
-  if (!Error_Code)
-  {   /*  no errors   */
-    Set_Polar_Stereographic_Parameters( UPS_a,
-                                        UPS_f,
-                                        UPS_Origin_Latitude,
-                                        UPS_Origin_Longitude,
-                                        UPS_False_Easting,
-                                        UPS_False_Northing);
-
-
-
-    Convert_Polar_Stereographic_To_Geodetic( Easting,
-                                             Northing,
-                                             Latitude,
-                                             Longitude); 
-
-
-    if ((*Latitude < 0) && (*Latitude > MIN_SOUTH_LAT))
-      Error_Code |= UPS_LAT_ERROR;
-    if ((*Latitude >= 0) && (*Latitude < MIN_NORTH_LAT))
-      Error_Code |= UPS_LAT_ERROR;
-  }  /*  END OF if(!Error_Code) */
-  return (Error_Code);
-}  /*  END OF Convert_UPS_To_Geodetic  */ 
-
+/********************************************************************/
+/* RSC IDENTIFIER: UPS
+ *
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between geodetic (latitude
+ *    and longitude) coordinates and Universal Polar Stereographic (UPS)
+ *    projection (hemisphere, easting, and northing) coordinates.
+ *
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an 
+ *    invalid value is found the error code is combined with the 
+ *    current error code using the bitwise or.  This combining allows  
+ *    multiple error codes to be returned. The possible error codes 
+ *    are:
+ *
+ *         UPS_NO_ERROR           : No errors occurred in function
+ *         UPS_LAT_ERROR          : Latitude outside of valid range
+ *                                   (North Pole: 83.5 to 90,
+ *                                    South Pole: -79.5 to -90)
+ *         UPS_LON_ERROR          : Longitude outside of valid range
+ *                                   (-180 to 360 degrees)
+ *         UPS_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
+ *         UPS_EASTING_ERROR      : Easting outside of valid range,
+ *                                   (0 to 4,000,000m)
+ *         UPS_NORTHING_ERROR     : Northing outside of valid range,
+ *                                   (0 to 4,000,000m)
+ *         UPS_A_ERROR            : Semi-major axis less than or equal to zero
+ *         UPS_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	               (250 to 350)
+ *
+ *
+ * REUSE NOTES
+ *
+ *    UPS is intended for reuse by any application that performs a Universal
+ *    Polar Stereographic (UPS) projection.
+ *
+ *
+ * REFERENCES
+ *
+ *    Further information on UPS can be found in the Reuse Manual.
+ *
+ *    UPS originated from :  U.S. Army Topographic Engineering Center
+ *                           Geospatial Information Division
+ *                           7701 Telegraph Road
+ *                           Alexandria, VA  22310-3864
+ *
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ *
+ * RESTRICTIONS
+ *
+ *    UPS has no restrictions.
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    UPS was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC version 2.8.1
+ *    2. Windows 95 with MS Visual C++ version 6
+ *
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    06-11-95          Original Code
+ *    03-01-97          Original Code
+ *
+ *
+ */
+
+
+/************************************************************************/
+/*
+ *                               INCLUDES
+ */
+
+#include <math.h>
+#include "polarst.h"
+#include "ups.h"
+/*
+ *    math.h     - Is needed to call the math functions.
+ *    polar.h    - Is used to convert polar stereographic coordinates
+ *    ups.h      - Defines the function prototypes for the ups module.
+ */
+
+
+/************************************************************************/
+/*                               GLOBAL DECLARATIONS
+ *
+ */
+
+#define PI       3.14159265358979323e0  /* PI     */
+#define PI_OVER    (PI/2.0e0)           /* PI over 2 */
+#define MAX_LAT    ((PI * 90)/180.0)    /* 90 degrees in radians */
+#define MAX_ORIGIN_LAT ((81.114528 * PI) / 180.0)
+#define MIN_NORTH_LAT (83.5*PI/180.0)
+#define MIN_SOUTH_LAT (-79.5*PI/180.0)
+#define MIN_EAST_NORTH 0
+#define MAX_EAST_NORTH 4000000
+
+/* Ellipsoid Parameters, default to WGS 84  */
+static double UPS_a = 6378137.0;          /* Semi-major axis of ellipsoid in meters   */
+static double UPS_f = 1 / 298.257223563;  /* Flattening of ellipsoid  */
+const double UPS_False_Easting = 2000000;
+const double UPS_False_Northing = 2000000;
+static double UPS_Origin_Latitude = MAX_ORIGIN_LAT;  /*set default = North Hemisphere */
+static double UPS_Origin_Longitude = 0.0;
+
+
+/************************************************************************/
+/*                              FUNCTIONS
+ *
+ */
+
+
+long Set_UPS_Parameters( double a,
+                         double f)
+{
+/*
+ * The function SET_UPS_PARAMETERS receives the ellipsoid parameters and sets
+ * the corresponding state variables. If any errors occur, the error code(s)
+ * are returned by the function, otherwise UPS_NO_ERROR is returned.
+ *
+ *   a     : Semi-major axis of ellipsoid in meters (input)
+ *   f     : Flattening of ellipsoid					      (input)
+ */
+
+  double inv_f = 1 / f;
+  long Error_Code = UPS_NO_ERROR;
+
+  if (a <= 0.0)
+  { /* Semi-major axis must be greater than zero */
+    Error_Code |= UPS_A_ERROR;
+  }
+  if ((inv_f < 250) || (inv_f > 350))
+  { /* Inverse flattening must be between 250 and 350 */
+    Error_Code |= UPS_INV_F_ERROR;
+  }
+
+  if (!Error_Code)
+  { /* no errors */
+    UPS_a = a;
+    UPS_f = f;
+  }
+  return (Error_Code);
+}  /* END of Set_UPS_Parameters  */
+
+
+void Get_UPS_Parameters( double *a,
+                         double *f)
+{
+/*
+ * The function Get_UPS_Parameters returns the current ellipsoid parameters.
+ *
+ *  a      : Semi-major axis of ellipsoid, in meters (output)
+ *  f      : Flattening of ellipsoid					       (output)
+ */
+
+  *a = UPS_a;
+  *f = UPS_f;
+  return;
+} /* END OF Get_UPS_Parameters */
+
+
+long Convert_Geodetic_To_UPS ( double Latitude,
+                               double Longitude,
+                               char   *Hemisphere,
+                               double *Easting,
+                               double *Northing)
+{
+/*
+ *  The function Convert_Geodetic_To_UPS converts geodetic (latitude and
+ *  longitude) coordinates to UPS (hemisphere, easting, and northing)
+ *  coordinates, according to the current ellipsoid parameters. If any 
+ *  errors occur, the error code(s) are returned by the function, 
+ *  otherwide UPS_NO_ERROR is returned.
+ *
+ *    Latitude      : Latitude in radians                       (input)
+ *    Longitude     : Longitude in radians                      (input)
+ *    Hemisphere    : Hemisphere either 'N' or 'S'              (output)
+ *    Easting       : Easting/X in meters                       (output)
+ *    Northing      : Northing/Y in meters                      (output)
+ */
+
+  double tempEasting, tempNorthing;
+  long Error_Code = UPS_NO_ERROR;
+
+  if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT))
+  {   /* latitude out of range */
+    Error_Code |= UPS_LAT_ERROR;
+  }
+  if ((Latitude < 0) && (Latitude > MIN_SOUTH_LAT))
+    Error_Code |= UPS_LAT_ERROR;
+  if ((Latitude >= 0) && (Latitude < MIN_NORTH_LAT))
+    Error_Code |= UPS_LAT_ERROR;
+  if ((Longitude < -PI) || (Longitude > (2 * PI)))
+  {  /* slam out of range */
+    Error_Code |= UPS_LON_ERROR;
+  }
+
+  if (!Error_Code)
+  {  /* no errors */
+    if (Latitude < 0)
+    {
+      UPS_Origin_Latitude = -MAX_ORIGIN_LAT; 
+      *Hemisphere = 'S';
+    }
+    else
+    {
+      UPS_Origin_Latitude = MAX_ORIGIN_LAT; 
+      *Hemisphere = 'N';
+    }
+
+
+    Set_Polar_Stereographic_Parameters( UPS_a,
+                                        UPS_f,
+                                        UPS_Origin_Latitude,
+                                        UPS_Origin_Longitude,
+                                        UPS_False_Easting,
+                                        UPS_False_Northing);
+
+    Convert_Geodetic_To_Polar_Stereographic(Latitude,
+                                            Longitude,
+                                            &tempEasting,
+                                            &tempNorthing);
+
+    *Easting = tempEasting;
+    *Northing = tempNorthing;
+  }  /*  END of if(!Error_Code)   */
+
+  return Error_Code;
+}  /* END OF Convert_Geodetic_To_UPS  */
+
+
+long Convert_UPS_To_Geodetic(char   Hemisphere,
+                             double Easting,
+                             double Northing,
+                             double *Latitude,
+                             double *Longitude)
+{
+/*
+ *  The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, 
+ *  and northing) coordinates to geodetic (latitude and longitude) coordinates
+ *  according to the current ellipsoid parameters.  If any errors occur, the 
+ *  error code(s) are returned by the function, otherwise UPS_NO_ERROR is 
+ *  returned.
+ *
+ *    Hemisphere    : Hemisphere either 'N' or 'S'              (input)
+ *    Easting       : Easting/X in meters                       (input)
+ *    Northing      : Northing/Y in meters                      (input)
+ *    Latitude      : Latitude in radians                       (output)
+ *    Longitude     : Longitude in radians                      (output)
+ */
+
+  long Error_Code = UPS_NO_ERROR;
+
+  if ((Hemisphere != 'N') && (Hemisphere != 'S'))
+    Error_Code |= UPS_HEMISPHERE_ERROR;
+  if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH))
+    Error_Code |= UPS_EASTING_ERROR;
+  if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH))
+    Error_Code |= UPS_NORTHING_ERROR;
+
+  if (Hemisphere =='N')
+  {UPS_Origin_Latitude = MAX_ORIGIN_LAT;}
+  if (Hemisphere =='S')
+  {UPS_Origin_Latitude = -MAX_ORIGIN_LAT;}
+
+  if (!Error_Code)
+  {   /*  no errors   */
+    Set_Polar_Stereographic_Parameters( UPS_a,
+                                        UPS_f,
+                                        UPS_Origin_Latitude,
+                                        UPS_Origin_Longitude,
+                                        UPS_False_Easting,
+                                        UPS_False_Northing);
+
+
+
+    Convert_Polar_Stereographic_To_Geodetic( Easting,
+                                             Northing,
+                                             Latitude,
+                                             Longitude); 
+
+
+    if ((*Latitude < 0) && (*Latitude > MIN_SOUTH_LAT))
+      Error_Code |= UPS_LAT_ERROR;
+    if ((*Latitude >= 0) && (*Latitude < MIN_NORTH_LAT))
+      Error_Code |= UPS_LAT_ERROR;
+  }  /*  END OF if(!Error_Code) */
+  return (Error_Code);
+}  /*  END OF Convert_UPS_To_Geodetic  */ 
+
diff --git a/geotranz/ups.h b/geotranz/ups.h
old mode 100755
new mode 100644
index 19a2f6a..9f6c817
--- a/geotranz/ups.h
+++ b/geotranz/ups.h
@@ -1,175 +1,175 @@
-#ifndef UPS_H
-  #define UPS_H
-/********************************************************************/
-/* RSC IDENTIFIER: UPS
- *
- *
- * ABSTRACT
- *
- *    This component provides conversions between geodetic (latitude
- *    and longitude) coordinates and Universal Polar Stereographic (UPS)
- *    projection (hemisphere, easting, and northing) coordinates.
- *
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an 
- *    invalid value is found the error code is combined with the 
- *    current error code using the bitwise or.  This combining allows  
- *    multiple error codes to be returned. The possible error codes 
- *    are:
- *
- *         UPS_NO_ERROR           : No errors occurred in function
- *         UPS_LAT_ERROR          : Latitude outside of valid range
- *                                   (North Pole: 83.5 to 90,
- *                                    South Pole: -79.5 to -90)
- *         UPS_LON_ERROR          : Longitude outside of valid range
- *                                   (-180 to 360 degrees)
- *         UPS_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
- *         UPS_EASTING_ERROR      : Easting outside of valid range,
- *                                   (0 to 4,000,000m)
- *         UPS_NORTHING_ERROR     : Northing outside of valid range,
- *                                   (0 to 4,000,000m)
- *         UPS_A_ERROR            : Semi-major axis less than or equal to zero
- *         UPS_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	               (250 to 350)
- *
- *
- * REUSE NOTES
- *
- *    UPS is intended for reuse by any application that performs a Universal
- *    Polar Stereographic (UPS) projection.
- *
- *
- * REFERENCES
- *
- *    Further information on UPS can be found in the Reuse Manual.
- *
- *    UPS originated from :  U.S. Army Topographic Engineering Center
- *                           Geospatial Information Division
- *                           7701 Telegraph Road
- *                           Alexandria, VA  22310-3864
- *
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- *
- * RESTRICTIONS
- *
- *    UPS has no restrictions.
- *
- *
- * ENVIRONMENT
- *
- *    UPS was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC version 2.8.1
- *    2. Windows 95 with MS Visual C++ version 6
- *
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    06-11-95          Original Code
- *    03-01-97          Original Code
- *
- *
- */
-
-
-/**********************************************************************/
-/*
- *                        DEFINES
- */
-
-  #define UPS_NO_ERROR                0x0000
-  #define UPS_LAT_ERROR               0x0001
-  #define UPS_LON_ERROR               0x0002
-  #define UPS_HEMISPHERE_ERROR        0x0004
-  #define UPS_EASTING_ERROR           0x0008
-  #define UPS_NORTHING_ERROR          0x0010
-  #define UPS_A_ERROR                 0x0020
-  #define UPS_INV_F_ERROR             0x0040
-
-
-/**********************************************************************/
-/*
- *                        FUNCTION PROTOTYPES
- *                          for UPS.C
- */
-
-/* ensure proper linkage to c++ programs */
-  #ifdef __cplusplus
-extern "C" {
-  #endif
-
-  long Set_UPS_Parameters( double a,
-                           double f);
-/*
- * The function SET_UPS_PARAMETERS receives the ellipsoid parameters and sets
- * the corresponding state variables. If any errors occur, the error code(s)
- * are returned by the function, otherwise UPS_NO_ERROR is returned.
- *
- *   a     : Semi-major axis of ellipsoid in meters (input)
- *   f     : Flattening of ellipsoid                (input)
- */
-
-
-  void Get_UPS_Parameters( double *a,
-                           double *f);
-/*
- * The function Get_UPS_Parameters returns the current ellipsoid parameters.
- *
- *  a      : Semi-major axis of ellipsoid, in meters (output)
- *  f      : Flattening of ellipsoid                 (output)
- */
-
-
-  long Convert_Geodetic_To_UPS ( double Latitude,
-                                 double Longitude,
-                                 char   *Hemisphere,
-                                 double *Easting,
-                                 double *Northing);
-/*
- *  The function Convert_Geodetic_To_UPS converts geodetic (latitude and
- *  longitude) coordinates to UPS (hemisphere, easting, and northing)
- *  coordinates, according to the current ellipsoid parameters. If any 
- *  errors occur, the error code(s) are returned by the function, 
- *  otherwide UPS_NO_ERROR is returned.
- *
- *    Latitude      : Latitude in radians                       (input)
- *    Longitude     : Longitude in radians                      (input)
- *    Hemisphere    : Hemisphere either 'N' or 'S'              (output)
- *    Easting       : Easting/X in meters                       (output)
- *    Northing      : Northing/Y in meters                      (output)
- */
-
-
-  long Convert_UPS_To_Geodetic(char   Hemisphere,
-                               double Easting,
-                               double Northing,
-                               double *Latitude,
-                               double *Longitude);
-
-/*
- *  The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, 
- *  and northing) coordinates to geodetic (latitude and longitude) coordinates
- *  according to the current ellipsoid parameters.  If any errors occur, the 
- *  error code(s) are returned by the function, otherwise UPS_NO_ERROR is 
- *  returned.
- *
- *    Hemisphere    : Hemisphere either 'N' or 'S'              (input)
- *    Easting       : Easting/X in meters                       (input)
- *    Northing      : Northing/Y in meters                      (input)
- *    Latitude      : Latitude in radians                       (output)
- *    Longitude     : Longitude in radians                      (output)
- */
-
-  #ifdef __cplusplus
-}
-  #endif
-
-#endif  /* UPS_H  */
+#ifndef UPS_H
+  #define UPS_H
+/********************************************************************/
+/* RSC IDENTIFIER: UPS
+ *
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between geodetic (latitude
+ *    and longitude) coordinates and Universal Polar Stereographic (UPS)
+ *    projection (hemisphere, easting, and northing) coordinates.
+ *
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an 
+ *    invalid value is found the error code is combined with the 
+ *    current error code using the bitwise or.  This combining allows  
+ *    multiple error codes to be returned. The possible error codes 
+ *    are:
+ *
+ *         UPS_NO_ERROR           : No errors occurred in function
+ *         UPS_LAT_ERROR          : Latitude outside of valid range
+ *                                   (North Pole: 83.5 to 90,
+ *                                    South Pole: -79.5 to -90)
+ *         UPS_LON_ERROR          : Longitude outside of valid range
+ *                                   (-180 to 360 degrees)
+ *         UPS_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
+ *         UPS_EASTING_ERROR      : Easting outside of valid range,
+ *                                   (0 to 4,000,000m)
+ *         UPS_NORTHING_ERROR     : Northing outside of valid range,
+ *                                   (0 to 4,000,000m)
+ *         UPS_A_ERROR            : Semi-major axis less than or equal to zero
+ *         UPS_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	               (250 to 350)
+ *
+ *
+ * REUSE NOTES
+ *
+ *    UPS is intended for reuse by any application that performs a Universal
+ *    Polar Stereographic (UPS) projection.
+ *
+ *
+ * REFERENCES
+ *
+ *    Further information on UPS can be found in the Reuse Manual.
+ *
+ *    UPS originated from :  U.S. Army Topographic Engineering Center
+ *                           Geospatial Information Division
+ *                           7701 Telegraph Road
+ *                           Alexandria, VA  22310-3864
+ *
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ *
+ * RESTRICTIONS
+ *
+ *    UPS has no restrictions.
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    UPS was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC version 2.8.1
+ *    2. Windows 95 with MS Visual C++ version 6
+ *
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    06-11-95          Original Code
+ *    03-01-97          Original Code
+ *
+ *
+ */
+
+
+/**********************************************************************/
+/*
+ *                        DEFINES
+ */
+
+  #define UPS_NO_ERROR                0x0000
+  #define UPS_LAT_ERROR               0x0001
+  #define UPS_LON_ERROR               0x0002
+  #define UPS_HEMISPHERE_ERROR        0x0004
+  #define UPS_EASTING_ERROR           0x0008
+  #define UPS_NORTHING_ERROR          0x0010
+  #define UPS_A_ERROR                 0x0020
+  #define UPS_INV_F_ERROR             0x0040
+
+
+/**********************************************************************/
+/*
+ *                        FUNCTION PROTOTYPES
+ *                          for UPS.C
+ */
+
+/* ensure proper linkage to c++ programs */
+  #ifdef __cplusplus
+extern "C" {
+  #endif
+
+  long Set_UPS_Parameters( double a,
+                           double f);
+/*
+ * The function SET_UPS_PARAMETERS receives the ellipsoid parameters and sets
+ * the corresponding state variables. If any errors occur, the error code(s)
+ * are returned by the function, otherwise UPS_NO_ERROR is returned.
+ *
+ *   a     : Semi-major axis of ellipsoid in meters (input)
+ *   f     : Flattening of ellipsoid                (input)
+ */
+
+
+  void Get_UPS_Parameters( double *a,
+                           double *f);
+/*
+ * The function Get_UPS_Parameters returns the current ellipsoid parameters.
+ *
+ *  a      : Semi-major axis of ellipsoid, in meters (output)
+ *  f      : Flattening of ellipsoid                 (output)
+ */
+
+
+  long Convert_Geodetic_To_UPS ( double Latitude,
+                                 double Longitude,
+                                 char   *Hemisphere,
+                                 double *Easting,
+                                 double *Northing);
+/*
+ *  The function Convert_Geodetic_To_UPS converts geodetic (latitude and
+ *  longitude) coordinates to UPS (hemisphere, easting, and northing)
+ *  coordinates, according to the current ellipsoid parameters. If any 
+ *  errors occur, the error code(s) are returned by the function, 
+ *  otherwide UPS_NO_ERROR is returned.
+ *
+ *    Latitude      : Latitude in radians                       (input)
+ *    Longitude     : Longitude in radians                      (input)
+ *    Hemisphere    : Hemisphere either 'N' or 'S'              (output)
+ *    Easting       : Easting/X in meters                       (output)
+ *    Northing      : Northing/Y in meters                      (output)
+ */
+
+
+  long Convert_UPS_To_Geodetic(char   Hemisphere,
+                               double Easting,
+                               double Northing,
+                               double *Latitude,
+                               double *Longitude);
+
+/*
+ *  The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, 
+ *  and northing) coordinates to geodetic (latitude and longitude) coordinates
+ *  according to the current ellipsoid parameters.  If any errors occur, the 
+ *  error code(s) are returned by the function, otherwise UPS_NO_ERROR is 
+ *  returned.
+ *
+ *    Hemisphere    : Hemisphere either 'N' or 'S'              (input)
+ *    Easting       : Easting/X in meters                       (input)
+ *    Northing      : Northing/Y in meters                      (input)
+ *    Latitude      : Latitude in radians                       (output)
+ *    Longitude     : Longitude in radians                      (output)
+ */
+
+  #ifdef __cplusplus
+}
+  #endif
+
+#endif  /* UPS_H  */
diff --git a/geotranz/usng.c b/geotranz/usng.c
old mode 100755
new mode 100644
index e21de80..dbce146
--- a/geotranz/usng.c
+++ b/geotranz/usng.c
@@ -1,1256 +1,1256 @@
-/***************************************************************************/
-/* RSC IDENTIFIER:  USNG
- *
- * ABSTRACT
- *
- *    This component converts between geodetic coordinates (latitude and
- *    longitude) and United States National Grid (USNG) coordinates.
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found, the error code is combined with the current error code using
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *          USNG_NO_ERROR          : No errors occurred in function
- *          USNG_LAT_ERROR         : Latitude outside of valid range
- *                                    (-90 to 90 degrees)
- *          USNG_LON_ERROR         : Longitude outside of valid range
- *                                    (-180 to 360 degrees)
- *          USNG_STR_ERROR         : An USNG string error: string too long,
- *                                    too short, or badly formed
- *          USNG_PRECISION_ERROR   : The precision must be between 0 and 5
- *                                    inclusive.
- *          USNG_A_ERROR           : Semi-major axis less than or equal to zero
- *          USNG_INV_F_ERROR       : Inverse flattening outside of valid range
- *                                    (250 to 350)
- *          USNG_EASTING_ERROR     : Easting outside of valid range
- *                                    (100,000 to 900,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          USNG_NORTHING_ERROR    : Northing outside of valid range
- *                                    (0 to 10,000,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          USNG_ZONE_ERROR        : Zone outside of valid range (1 to 60)
- *          USNG_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
- *
- * REUSE NOTES
- *
- *    USNG is intended for reuse by any application that does conversions
- *    between geodetic coordinates and USNG coordinates.
- *
- * REFERENCES
- *
- *    Further information on USNG can be found in the Reuse Manual.
- *
- *    USNG originated from : Federal Geographic Data Committee
- *                           590 National Center
- *                           12201 Sunrise Valley Drive
- *                           Reston, VA  22092
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *
- * ENVIRONMENT
- *
- *    USNG was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC version 2.8.1
- *    2. Windows XP with MS Visual C++ version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    06-05-06          Original Code (cloned from MGRS)
- */
-
-
-/***************************************************************************/
-/*
- *                               INCLUDES
- */
-#include <ctype.h>
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
-#include "ups.h"
-#include "utm.h"
-#include "usng.h"
-
-/*
- *      ctype.h     - Standard C character handling library
- *      math.h      - Standard C math library
- *      stdio.h     - Standard C input/output library
- *      string.h    - Standard C string handling library
- *      ups.h       - Universal Polar Stereographic (UPS) projection
- *      utm.h       - Universal Transverse Mercator (UTM) projection
- *      usng.h      - function prototype error checking
- */
-
-
-/***************************************************************************/
-/*
- *                              GLOBAL DECLARATIONS
- */
-#define DEG_TO_RAD       0.017453292519943295 /* PI/180                      */
-#define RAD_TO_DEG       57.29577951308232087 /* 180/PI                      */
-#define LETTER_A               0   /* ARRAY INDEX FOR LETTER A               */
-#define LETTER_B               1   /* ARRAY INDEX FOR LETTER B               */
-#define LETTER_C               2   /* ARRAY INDEX FOR LETTER C               */
-#define LETTER_D               3   /* ARRAY INDEX FOR LETTER D               */
-#define LETTER_E               4   /* ARRAY INDEX FOR LETTER E               */
-#define LETTER_F               5   /* ARRAY INDEX FOR LETTER F               */
-#define LETTER_G               6   /* ARRAY INDEX FOR LETTER G               */
-#define LETTER_H               7   /* ARRAY INDEX FOR LETTER H               */
-#define LETTER_I               8   /* ARRAY INDEX FOR LETTER I               */
-#define LETTER_J               9   /* ARRAY INDEX FOR LETTER J               */
-#define LETTER_K              10   /* ARRAY INDEX FOR LETTER K               */
-#define LETTER_L              11   /* ARRAY INDEX FOR LETTER L               */
-#define LETTER_M              12   /* ARRAY INDEX FOR LETTER M               */
-#define LETTER_N              13   /* ARRAY INDEX FOR LETTER N               */
-#define LETTER_O              14   /* ARRAY INDEX FOR LETTER O               */
-#define LETTER_P              15   /* ARRAY INDEX FOR LETTER P               */
-#define LETTER_Q              16   /* ARRAY INDEX FOR LETTER Q               */
-#define LETTER_R              17   /* ARRAY INDEX FOR LETTER R               */
-#define LETTER_S              18   /* ARRAY INDEX FOR LETTER S               */
-#define LETTER_T              19   /* ARRAY INDEX FOR LETTER T               */
-#define LETTER_U              20   /* ARRAY INDEX FOR LETTER U               */
-#define LETTER_V              21   /* ARRAY INDEX FOR LETTER V               */
-#define LETTER_W              22   /* ARRAY INDEX FOR LETTER W               */
-#define LETTER_X              23   /* ARRAY INDEX FOR LETTER X               */
-#define LETTER_Y              24   /* ARRAY INDEX FOR LETTER Y               */
-#define LETTER_Z              25   /* ARRAY INDEX FOR LETTER Z               */
-#define USNG_LETTERS            3  /* NUMBER OF LETTERS IN USNG              */
-#define ONEHT          100000.e0    /* ONE HUNDRED THOUSAND                  */
-#define TWOMIL        2000000.e0    /* TWO MILLION                           */
-#define TRUE                      1  /* CONSTANT VALUE FOR TRUE VALUE  */
-#define FALSE                     0  /* CONSTANT VALUE FOR FALSE VALUE */
-#define PI    3.14159265358979323e0  /* PI                             */
-#define PI_OVER_2  (PI / 2.0e0)
-
-#define MIN_EASTING  100000
-#define MAX_EASTING  900000
-#define MIN_NORTHING 0
-#define MAX_NORTHING 10000000
-#define MAX_PRECISION           5   /* Maximum precision of easting & northing */
-#define MIN_UTM_LAT      ( (-80 * PI) / 180.0 ) /* -80 degrees in radians    */
-#define MAX_UTM_LAT      ( (84 * PI) / 180.0 )  /* 84 degrees in radians     */
-
-#define MIN_EAST_NORTH 0
-#define MAX_EAST_NORTH 4000000
-
-
-/* Ellipsoid parameters, default to WGS 84 */
-double USNG_a = 6378137.0;    /* Semi-major axis of ellipsoid in meters */
-double USNG_f = 1 / 298.257223563; /* Flattening of ellipsoid           */
-double USNG_recpf = 298.257223563;
-char   USNG_Ellipsoid_Code[3] = {'W','E',0};
-
-
-typedef struct Latitude_Band_Value
-{
-  long letter;            /* letter representing latitude band  */
-  double min_northing;    /* minimum northing for latitude band */
-  double north;           /* upper latitude for latitude band   */
-  double south;           /* lower latitude for latitude band   */
-  double northing_offset; /* latitude band northing offset      */
-} Latitude_Band;
-
-static const Latitude_Band Latitude_Band_Table[20] =
-  {{LETTER_C, 1100000.0, -72.0, -80.5, 0.0}, 
-  {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0},
-  {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0},
-  {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0},
-  {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0},
-  {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0},
-  {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0},
-  {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0},
-  {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0},
-  {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0},
-  {LETTER_N, 0.0, 8.0, 0.0, 0.0},
-  {LETTER_P, 800000.0, 16.0, 8.0, 0.0},
-  {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0},
-  {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0},
-  {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0},
-  {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0},
-  {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0},
-  {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0},
-  {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0},
-  {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}};
-
-
-typedef struct UPS_Constant_Value
-{
-  long letter;            /* letter representing latitude band      */
-  long ltr2_low_value;    /* 2nd letter range - low number         */
-  long ltr2_high_value;   /* 2nd letter range - high number          */
-  long ltr3_high_value;   /* 3rd letter range - high number (UPS)   */
-  double false_easting;   /* False easting based on 2nd letter      */
-  double false_northing;  /* False northing based on 3rd letter     */
-} UPS_Constant;
-
-static const UPS_Constant UPS_Constant_Table[4] =
-  {{LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000.0, 800000.0},
-  {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000.0, 800000.0},
-  {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000.0, 1300000.0},
-  {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000.0, 1300000.0}};
-
-/***************************************************************************/
-/*
- *                              FUNCTIONS
- */
-
-long USNG_Get_Latitude_Band_Min_Northing(long letter, double* min_northing, double* northing_offset)
-/*
- * The function USNG_Get_Latitude_Band_Min_Northing receives a latitude band letter
- * and uses the Latitude_Band_Table to determine the minimum northing and northing offset
- * for that latitude band letter.
- *
- *   letter        : Latitude band letter             (input)
- *   min_northing  : Minimum northing for that letter (output)
- */
-{ /* USNG_Get_Latitude_Band_Min_Northing */
-  long error_code = USNG_NO_ERROR;
-
-  if ((letter >= LETTER_C) && (letter <= LETTER_H))
-  {
-    *min_northing = Latitude_Band_Table[letter-2].min_northing;
-    *northing_offset = Latitude_Band_Table[letter-2].northing_offset;
-  }
-  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
-  {
-    *min_northing = Latitude_Band_Table[letter-3].min_northing;
-    *northing_offset = Latitude_Band_Table[letter-3].northing_offset;
-  }
-  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
-  {
-    *min_northing = Latitude_Band_Table[letter-4].min_northing;
-    *northing_offset = Latitude_Band_Table[letter-4].northing_offset;
-  }
-  else
-    error_code |= USNG_STRING_ERROR;
-
-  return error_code;
-} /* USNG_Get_Latitude_Band_Min_Northing */
-
-
-long USNG_Get_Latitude_Range(long letter, double* north, double* south)
-/*
- * The function USNG_Get_Latitude_Range receives a latitude band letter
- * and uses the Latitude_Band_Table to determine the latitude band
- * boundaries for that latitude band letter.
- *
- *   letter   : Latitude band letter                        (input)
- *   north    : Northern latitude boundary for that letter  (output)
- *   north    : Southern latitude boundary for that letter  (output)
- */
-{ /* USNG_Get_Latitude_Range */
-  long error_code = USNG_NO_ERROR;
-
-  if ((letter >= LETTER_C) && (letter <= LETTER_H))
-  {
-    *north = Latitude_Band_Table[letter-2].north * DEG_TO_RAD;
-    *south = Latitude_Band_Table[letter-2].south * DEG_TO_RAD;
-  }
-  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
-  {
-    *north = Latitude_Band_Table[letter-3].north * DEG_TO_RAD;
-    *south = Latitude_Band_Table[letter-3].south * DEG_TO_RAD;
-  }
-  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
-  {
-    *north = Latitude_Band_Table[letter-4].north * DEG_TO_RAD;
-    *south = Latitude_Band_Table[letter-4].south * DEG_TO_RAD;
-  }
-  else
-    error_code |= USNG_STRING_ERROR;
-
-  return error_code;
-} /* USNG_Get_Latitude_Range */
-
-
-long USNG_Get_Latitude_Letter(double latitude, int* letter)
-/*
- * The function USNG_Get_Latitude_Letter receives a latitude value
- * and uses the Latitude_Band_Table to determine the latitude band
- * letter for that latitude.
- *
- *   latitude   : Latitude              (input)
- *   letter     : Latitude band letter  (output)
- */
-{ /* USNG_Get_Latitude_Letter */
-  double temp = 0.0;
-  long error_code = USNG_NO_ERROR;
-  double lat_deg = latitude * RAD_TO_DEG;
-
-  if (lat_deg >= 72 && lat_deg < 84.5)
-    *letter = LETTER_X;
-  else if (lat_deg > -80.5 && lat_deg < 72)
-  {
-    temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12;
-    *letter = Latitude_Band_Table[(int)temp].letter;
-  }
-  else
-    error_code |= USNG_LAT_ERROR;
-
-  return error_code;
-} /* USNG_Get_Latitude_Letter */
-
-
-long USNG_Check_Zone(char* USNG, long* zone_exists)
-/*
- * The function USNG_Check_Zone receives a USNG coordinate string.
- * If a zone is given, TRUE is returned. Otherwise, FALSE
- * is returned.
- *
- *   USNG           : USNG coordinate string        (input)
- *   zone_exists    : TRUE if a zone is given,
- *                    FALSE if a zone is not given  (output)
- */
-{ /* USNG_Check_Zone */
-  int i = 0;
-  int j = 0;
-  int num_digits = 0;
-  long error_code = USNG_NO_ERROR;
-
-  /* skip any leading blanks */
-  while (USNG[i] == ' ')
-    i++;
-  j = i;
-  while (isdigit(USNG[i]))
-    i++;
-  num_digits = i - j;
-  if (num_digits <= 2)
-    if (num_digits > 0)
-      *zone_exists = TRUE;
-    else
-      *zone_exists = FALSE;
-  else
-    error_code |= USNG_STRING_ERROR;
-
-  return error_code;
-} /* USNG_Check_Zone */
-
-
-long Make_USNG_String (char* USNG,
-                       long Zone,
-                       int Letters[USNG_LETTERS],
-                       double Easting,
-                       double Northing,
-                       long Precision)
-/*
- * The function Make_USNG_String constructs a USNG string
- * from its component parts.
- *
- *   USNG           : USNG coordinate string          (output)
- *   Zone           : UTM Zone                        (input)
- *   Letters        : USNG coordinate string letters  (input)
- *   Easting        : Easting value                   (input)
- *   Northing       : Northing value                  (input)
- *   Precision      : Precision level of USNG string  (input)
- */
-{ /* Make_USNG_String */
-  long i;
-  long j;
-  double divisor;
-  long east;
-  long north;
-  char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-  long error_code = USNG_NO_ERROR;
-
-  i = 0;
-  if (Zone)
-    i = sprintf (USNG+i,"%2.2ld",Zone);
-  else
-    strncpy(USNG, "  ", 2);  // 2 spaces
-
-  for (j=0;j<3;j++)
-    USNG[i++] = alphabet[Letters[j]];
-  divisor = pow (10.0, (5 - Precision));
-  Easting = fmod (Easting, 100000.0);
-  if (Easting >= 99999.5)
-    Easting = 99999.0;
-  east = (long)(Easting/divisor);
-  i += sprintf (USNG+i, "%*.*ld", (int)Precision, (int)Precision, east);
-  Northing = fmod (Northing, 100000.0);
-  if (Northing >= 99999.5)
-    Northing = 99999.0;
-  north = (long)(Northing/divisor);
-  i += sprintf (USNG+i, "%*.*ld", (int)Precision, (int)Precision, north);
-  return (error_code);
-} /* Make_USNG_String */
-
-
-long Break_USNG_String (char* USNG,
-                        long* Zone,
-                        long Letters[USNG_LETTERS],
-                        double* Easting,
-                        double* Northing,
-                        long* Precision)
-/*
- * The function Break_USNG_String breaks down a USNG
- * coordinate string into its component parts.
- *
- *   USNG           : USNG coordinate string          (input)
- *   Zone           : UTM Zone                        (output)
- *   Letters        : USNG coordinate string letters  (output)
- *   Easting        : Easting value                   (output)
- *   Northing       : Northing value                  (output)
- *   Precision      : Precision level of USNG string  (output)
- */
-{ /* Break_USNG_String */
-  long num_digits;
-  long num_letters;
-  long i = 0;
-  long j = 0;
-  long error_code = USNG_NO_ERROR;
-
-  while (USNG[i] == ' ')
-    i++;  /* skip any leading blanks */
-  j = i;
-  while (isdigit(USNG[i]))
-    i++;
-  num_digits = i - j;
-  if (num_digits <= 2)
-    if (num_digits > 0)
-    {
-      char zone_string[3];
-      /* get zone */
-      strncpy (zone_string, USNG+j, 2);
-      zone_string[2] = 0;
-      sscanf (zone_string, "%ld", Zone);
-      if ((*Zone < 1) || (*Zone > 60))
-        error_code |= USNG_STRING_ERROR;
-    }
-    else
-      *Zone = 0;
-  else
-    error_code |= USNG_STRING_ERROR;
-  j = i;
-
-  while (isalpha(USNG[i]))
-    i++;
-  num_letters = i - j;
-  if (num_letters == 3)
-  {
-    /* get letters */
-    Letters[0] = (toupper(USNG[j]) - (long)'A');
-    if ((Letters[0] == LETTER_I) || (Letters[0] == LETTER_O))
-      error_code |= USNG_STRING_ERROR;
-    Letters[1] = (toupper(USNG[j+1]) - (long)'A');
-    if ((Letters[1] == LETTER_I) || (Letters[1] == LETTER_O))
-      error_code |= USNG_STRING_ERROR;
-    Letters[2] = (toupper(USNG[j+2]) - (long)'A');
-    if ((Letters[2] == LETTER_I) || (Letters[2] == LETTER_O))
-      error_code |= USNG_STRING_ERROR;
-  }
-  else
-    error_code |= USNG_STRING_ERROR;
-  j = i;
-  while (isdigit(USNG[i]))
-    i++;
-  num_digits = i - j;
-  if ((num_digits <= 10) && (num_digits%2 == 0))
-  {
-    long n;
-    char east_string[6];
-    char north_string[6];
-    long east;
-    long north;
-    double multiplier;
-    /* get easting & northing */
-    n = num_digits/2;
-    *Precision = n;
-    if (n > 0)
-    {
-      strncpy (east_string, USNG+j, n);
-      east_string[n] = 0;
-      sscanf (east_string, "%ld", &east);
-      strncpy (north_string, USNG+j+n, n);
-      north_string[n] = 0;
-      sscanf (north_string, "%ld", &north);
-      multiplier = pow (10.0, 5 - n);
-      *Easting = east * multiplier;
-      *Northing = north * multiplier;
-    }
-    else
-    {
-      *Easting = 0.0;
-      *Northing = 0.0;
-    }
-  }
-  else
-    error_code |= USNG_STRING_ERROR;
-
-  return (error_code);
-} /* Break_USNG_String */
-
-
-void USNG_Get_Grid_Values (long zone,
-                           long* ltr2_low_value,
-                           long* ltr2_high_value,
-                           double *pattern_offset)
-/*
- * The function USNG_Get_Grid_Values sets the letter range used for
- * the 2nd letter in the USNG coordinate string, based on the set
- * number of the utm zone. It also sets the pattern offset using a
- * value of A for the second letter of the grid square, based on
- * the grid pattern and set number of the utm zone.
- *
- *    zone            : Zone number             (input)
- *    ltr2_low_value  : 2nd letter low number   (output)
- *    ltr2_high_value : 2nd letter high number  (output)
- *    pattern_offset  : Pattern offset          (output)
- */
-{ /* BEGIN USNG_Get_Grid_Values */
-  long set_number;    /* Set number (1-6) based on UTM zone number */
-
-  set_number = zone % 6;
-
-  if (!set_number)
-    set_number = 6;
-
-  if ((set_number == 1) || (set_number == 4))
-  {
-    *ltr2_low_value = LETTER_A;
-    *ltr2_high_value = LETTER_H;
-  }
-  else if ((set_number == 2) || (set_number == 5))
-  {
-    *ltr2_low_value = LETTER_J;
-    *ltr2_high_value = LETTER_R;
-  }
-  else if ((set_number == 3) || (set_number == 6))
-  {
-    *ltr2_low_value = LETTER_S;
-    *ltr2_high_value = LETTER_Z;
-  }
-
-  /* False northing at A for second letter of grid square */
-  if ((set_number % 2) ==  0)
-    *pattern_offset = 500000.0;
-  else
-    *pattern_offset = 0.0;
-
-} /* END OF USNG_Get_Grid_Values */
-
-
-long UTM_To_USNG (long Zone,
-                  double Latitude,
-                  double Easting,
-                  double Northing,
-                  long Precision,
-                  char *USNG)
-/*
- * The function UTM_To_USNG calculates a USNG coordinate string
- * based on the zone, latitude, easting and northing.
- *
- *    Zone      : Zone number             (input)
- *    Latitude  : Latitude in radians     (input)
- *    Easting   : Easting                 (input)
- *    Northing  : Northing                (input)
- *    Precision : Precision               (input)
- *    USNG      : USNG coordinate string  (output)
- */
-{ /* BEGIN UTM_To_USNG */
-  double pattern_offset;      /* Pattern offset for 3rd letter               */
-  double grid_northing;       /* Northing used to derive 3rd letter of USNG  */
-  long ltr2_low_value;        /* 2nd letter range - low number               */
-  long ltr2_high_value;       /* 2nd letter range - high number              */
-  int letters[USNG_LETTERS];  /* Number location of 3 letters in alphabet    */
-  double divisor;
-  long error_code = USNG_NO_ERROR;
-
-  /* Round easting and northing values */
-  divisor = pow (10.0, (5 - Precision));
-  Easting = (long)(Easting/divisor) * divisor;
-  Northing = (long)(Northing/divisor) * divisor;
-
-  if( Latitude <= 0.0 && Northing == 1.0e7)
-  {
-    Latitude = 0.0;
-    Northing = 0.0;
-  }
-
-  USNG_Get_Grid_Values(Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
-
-  error_code = USNG_Get_Latitude_Letter(Latitude, &letters[0]);
-
-  if (!error_code)
-  {
-    grid_northing = Northing;
-
-    while (grid_northing >= TWOMIL)
-    {
-      grid_northing = grid_northing - TWOMIL;
-    }
-    grid_northing = grid_northing + pattern_offset;
-    if(grid_northing >= TWOMIL)
-      grid_northing = grid_northing - TWOMIL;
-
-    letters[2] = (long)(grid_northing / ONEHT);
-    if (letters[2] > LETTER_H)
-      letters[2] = letters[2] + 1;
-
-    if (letters[2] > LETTER_N)
-      letters[2] = letters[2] + 1;
-
-    letters[1] = ltr2_low_value + ((long)(Easting / ONEHT) -1);
-    if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N))
-      letters[1] = letters[1] + 1;
-
-    Make_USNG_String (USNG, Zone, letters, Easting, Northing, Precision);
-  }
-  return error_code;
-} /* END UTM_To_USNG */
-
-
-long Set_USNG_Parameters (double a,
-                          double f,
-                          char   *Ellipsoid_Code)
-/*
- * The function SET_USNG_PARAMETERS receives the ellipsoid parameters and sets
- * the corresponding state variables. If any errors occur, the error code(s)
- * are returned by the function, otherwise USNG_NO_ERROR is returned.
- *
- *   a                : Semi-major axis of ellipsoid in meters  (input)
- *   f                : Flattening of ellipsoid                 (input)
- *   Ellipsoid_Code   : 2-letter code for ellipsoid             (input)
- */
-{ /* Set_USNG_Parameters  */
-
-  double inv_f = 1 / f;
-  long Error_Code = USNG_NO_ERROR;
-
-  if (a <= 0.0)
-  { /* Semi-major axis must be greater than zero */
-    Error_Code |= USNG_A_ERROR;
-  }
-  if ((inv_f < 250) || (inv_f > 350))
-  { /* Inverse flattening must be between 250 and 350 */
-    Error_Code |= USNG_INV_F_ERROR;
-  }
-  if (!Error_Code)
-  { /* no errors */
-    USNG_a = a;
-    USNG_f = f;
-    USNG_recpf = inv_f;
-    strcpy (USNG_Ellipsoid_Code, Ellipsoid_Code);
-  }
-  return (Error_Code);
-}  /* Set_USNG_Parameters  */
-
-
-void Get_USNG_Parameters (double *a,
-                          double *f,
-                          char* Ellipsoid_Code)
-/*
- * The function Get_USNG_Parameters returns the current ellipsoid
- * parameters.
- *
- *  a                : Semi-major axis of ellipsoid, in meters (output)
- *  f                : Flattening of ellipsoid                 (output)
- *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
- */
-{ /* Get_USNG_Parameters */
-  *a = USNG_a;
-  *f = USNG_f;
-  strcpy (Ellipsoid_Code, USNG_Ellipsoid_Code);
-  return;
-} /* Get_USNG_Parameters */
-
-
-long Convert_Geodetic_To_USNG (double Latitude,
-                               double Longitude,
-                               long Precision,
-                               char* USNG)
-/*
- * The function Convert_Geodetic_To_USNG converts Geodetic (latitude and
- * longitude) coordinates to a USNG coordinate string, according to the
- * current ellipsoid parameters.  If any errors occur, the error code(s)
- * are returned by the function, otherwise USNG_NO_ERROR is returned.
- *
- *    Latitude   : Latitude in radians              (input)
- *    Longitude  : Longitude in radians             (input)
- *    Precision  : Precision level of USNG string   (input)
- *    USNG       : USNG coordinate string           (output)
- *
- */
-{ /* Convert_Geodetic_To_USNG */
-  long zone;
-  char hemisphere;
-  double easting;
-  double northing;
-  long temp_error_code = USNG_NO_ERROR;
-  long error_code = USNG_NO_ERROR;
-
-  if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2))
-  { /* Latitude out of range */
-    error_code |= USNG_LAT_ERROR;
-  }
-  if ((Longitude < -PI) || (Longitude > (2*PI)))
-  { /* Longitude out of range */
-    error_code |= USNG_LON_ERROR;
-  }
-  if ((Precision < 0) || (Precision > MAX_PRECISION))
-    error_code |= USNG_PRECISION_ERROR;
-  if (!error_code)
-  {
-    if ((Latitude < MIN_UTM_LAT) || (Latitude > MAX_UTM_LAT))
-    {
-      temp_error_code = Set_UPS_Parameters (USNG_a, USNG_f);
-      if(!temp_error_code)
-      {
-        temp_error_code |= Convert_Geodetic_To_UPS (Latitude, Longitude, &hemisphere, &easting, &northing);
-        if(!temp_error_code)
-          error_code |= Convert_UPS_To_USNG (hemisphere, easting, northing, Precision, USNG);
-        else
-        {
-          if(temp_error_code & UPS_LAT_ERROR)
-            error_code |= USNG_LAT_ERROR;
-          if(temp_error_code & UPS_LON_ERROR)
-            error_code |= USNG_LON_ERROR;
-        }
-      }
-      else
-      {
-        if(temp_error_code & UPS_A_ERROR)
-          error_code |= USNG_A_ERROR;
-        if(temp_error_code & UPS_INV_F_ERROR)
-          error_code |= USNG_INV_F_ERROR;
-      }
-    }
-    else
-    {
-      temp_error_code = Set_UTM_Parameters (USNG_a, USNG_f, 0);
-      if(!temp_error_code)
-      {
-        temp_error_code |= Convert_Geodetic_To_UTM (Latitude, Longitude, &zone, &hemisphere, &easting, &northing);
-        if(!temp_error_code)
-          error_code |= UTM_To_USNG (zone, Latitude, easting, northing, Precision, USNG);
-        else
-        {
-          if(temp_error_code & UTM_LAT_ERROR)
-            error_code |= USNG_LAT_ERROR;
-          if(temp_error_code & UTM_LON_ERROR)
-            error_code |= USNG_LON_ERROR;
-          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-            error_code |= USNG_ZONE_ERROR;
-          if(temp_error_code & UTM_EASTING_ERROR)
-            error_code |= USNG_EASTING_ERROR;
-          if(temp_error_code & UTM_NORTHING_ERROR)
-            error_code |= USNG_NORTHING_ERROR;
-        }
-      }
-      else
-      {
-        if(temp_error_code & UTM_A_ERROR)
-          error_code |= USNG_A_ERROR;
-        if(temp_error_code & UTM_INV_F_ERROR)
-          error_code |= USNG_INV_F_ERROR;
-        if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-          error_code |= USNG_ZONE_ERROR;
-      }
-    }
-  }
-  return (error_code);
-} /* Convert_Geodetic_To_USNG */
-
-
-long Convert_USNG_To_Geodetic (char* USNG,
-                               double *Latitude,
-                               double *Longitude)
-/*
- * The function Convert_USNG_To_Geodetic converts a USNG coordinate string
- * to Geodetic (latitude and longitude) coordinates
- * according to the current ellipsoid parameters.  If any errors occur,
- * the error code(s) are returned by the function, otherwise UTM_NO_ERROR
- * is returned.
- *
- *    USNG       : USNG coordinate string           (input)
- *    Latitude   : Latitude in radians              (output)
- *    Longitude  : Longitude in radians             (output)
- *
- */
-{ /* Convert_USNG_To_Geodetic */
-  long zone;
-  char hemisphere;
-  double easting;
-  double northing;
-  long zone_exists;
-  long temp_error_code = USNG_NO_ERROR;
-  long error_code = USNG_NO_ERROR;
-
-  error_code = USNG_Check_Zone(USNG, &zone_exists);
-  if (!error_code)
-  {
-    if (zone_exists)
-    {
-      error_code |= Convert_USNG_To_UTM (USNG, &zone, &hemisphere, &easting, &northing);
-      if(!error_code || (error_code & USNG_LAT_WARNING))
-      {
-        temp_error_code = Set_UTM_Parameters (USNG_a, USNG_f, 0);
-        if(!temp_error_code)
-        {
-          temp_error_code |= Convert_UTM_To_Geodetic (zone, hemisphere, easting, northing, Latitude, Longitude);
-          if(temp_error_code)
-          {
-            if((temp_error_code & UTM_ZONE_ERROR) || (temp_error_code & UTM_HEMISPHERE_ERROR))
-              error_code |= USNG_STRING_ERROR;
-            if(temp_error_code & UTM_EASTING_ERROR)
-              error_code |= USNG_EASTING_ERROR;
-            if(temp_error_code & UTM_NORTHING_ERROR)
-              error_code |= USNG_NORTHING_ERROR;
-          }
-        }
-        else
-        {
-          if(temp_error_code & UTM_A_ERROR)
-            error_code |= USNG_A_ERROR;
-          if(temp_error_code & UTM_INV_F_ERROR)
-            error_code |= USNG_INV_F_ERROR;
-          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
-            error_code |= USNG_ZONE_ERROR;
-        }
-      }
-    }
-    else
-    {
-      error_code |= Convert_USNG_To_UPS (USNG, &hemisphere, &easting, &northing);
-      if(!error_code)
-      {
-        temp_error_code = Set_UPS_Parameters (USNG_a, USNG_f);
-        if(!temp_error_code)
-        {
-          temp_error_code |= Convert_UPS_To_Geodetic (hemisphere, easting, northing, Latitude, Longitude);
-          if(temp_error_code)
-          {
-            if(temp_error_code & UPS_HEMISPHERE_ERROR)
-              error_code |= USNG_STRING_ERROR;
-            if(temp_error_code & UPS_EASTING_ERROR)
-              error_code |= USNG_EASTING_ERROR;
-            if(temp_error_code & UPS_LAT_ERROR)
-              error_code |= USNG_NORTHING_ERROR;
-          }
-        }
-        else
-        {
-          if(temp_error_code & UPS_A_ERROR)
-            error_code |= USNG_A_ERROR;
-          if(temp_error_code & UPS_INV_F_ERROR)
-            error_code |= USNG_INV_F_ERROR;
-        }
-      }
-    }
-  }
-  return (error_code);
-} /* END OF Convert_USNG_To_Geodetic */
-
-
-long Convert_UTM_To_USNG (long Zone,
-                          char Hemisphere,
-                          double Easting,
-                          double Northing,
-                          long Precision,
-                          char* USNG)
-/*
- * The function Convert_UTM_To_USNG converts UTM (zone, easting, and
- * northing) coordinates to a USNG coordinate string, according to the
- * current ellipsoid parameters.  If any errors occur, the error code(s)
- * are returned by the function, otherwise USNG_NO_ERROR is returned.
- *
- *    Zone       : UTM zone                         (input)
- *    Hemisphere : North or South hemisphere        (input)
- *    Easting    : Easting (X) in meters            (input)
- *    Northing   : Northing (Y) in meters           (input)
- *    Precision  : Precision level of USNG string   (input)
- *    USNG       : USNG coordinate string           (output)
- */
-{ /* Convert_UTM_To_USNG */
-  double latitude;           /* Latitude of UTM point */
-  double longitude;          /* Longitude of UTM point */
-  long utm_error_code = USNG_NO_ERROR;
-  long error_code = USNG_NO_ERROR;
-
-  if ((Zone < 1) || (Zone > 60))
-    error_code |= USNG_ZONE_ERROR;
-  if ((Hemisphere != 'S') && (Hemisphere != 'N'))
-    error_code |= USNG_HEMISPHERE_ERROR;
-  if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
-    error_code |= USNG_EASTING_ERROR;
-  if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
-    error_code |= USNG_NORTHING_ERROR;
-  if ((Precision < 0) || (Precision > MAX_PRECISION))
-    error_code |= USNG_PRECISION_ERROR;
-  if (!error_code)
-  {
-    Set_UTM_Parameters (USNG_a, USNG_f, 0);
-    utm_error_code = Convert_UTM_To_Geodetic (Zone, Hemisphere, Easting, Northing, &latitude, &longitude);
-
-    if(utm_error_code)
-    {
-      if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
-        error_code |= USNG_STRING_ERROR;
-      if(utm_error_code & UTM_EASTING_ERROR)
-        error_code |= USNG_EASTING_ERROR;
-      if(utm_error_code & UTM_NORTHING_ERROR)
-        error_code |= USNG_NORTHING_ERROR;
-    }
-
-    error_code |= UTM_To_USNG (Zone, latitude, Easting, Northing, Precision, USNG);
-  }
-  return (error_code);
-} /* Convert_UTM_To_USNG */
-
-
-long Convert_USNG_To_UTM (char   *USNG,
-                          long   *Zone,
-                          char   *Hemisphere,
-                          double *Easting,
-                          double *Northing)
-/*
- * The function Convert_USNG_To_UTM converts a USNG coordinate string
- * to UTM projection (zone, hemisphere, easting and northing) coordinates
- * according to the current ellipsoid parameters.  If any errors occur,
- * the error code(s) are returned by the function, otherwise UTM_NO_ERROR
- * is returned.
- *
- *    USNG       : USNG coordinate string           (input)
- *    Zone       : UTM zone                         (output)
- *    Hemisphere : North or South hemisphere        (output)
- *    Easting    : Easting (X) in meters            (output)
- *    Northing   : Northing (Y) in meters           (output)
- */
-{ /* Convert_USNG_To_UTM */
-  double min_northing;
-  double northing_offset;
-  long ltr2_low_value;
-  long ltr2_high_value;
-  double pattern_offset;
-  double upper_lat_limit;     /* North latitude limits based on 1st letter  */
-  double lower_lat_limit;     /* South latitude limits based on 1st letter  */
-  double grid_easting;        /* Easting for 100,000 meter grid square      */
-  double grid_northing;       /* Northing for 100,000 meter grid square     */
-  long letters[USNG_LETTERS];
-  long in_precision;
-  double latitude = 0.0;
-  double longitude = 0.0;
-  double divisor = 1.0;
-  long utm_error_code = USNG_NO_ERROR;
-  long error_code = USNG_NO_ERROR;
-
-  error_code = Break_USNG_String (USNG, Zone, letters, Easting, Northing, &in_precision);
-  if (!*Zone)
-    error_code |= USNG_STRING_ERROR;
-  else
-  {
-    if (!error_code)
-    {
-      if ((letters[0] == LETTER_X) && ((*Zone == 32) || (*Zone == 34) || (*Zone == 36)))
-        error_code |= USNG_STRING_ERROR;
-      else
-      {
-        if (letters[0] < LETTER_N)
-          *Hemisphere = 'S';
-        else
-          *Hemisphere = 'N';
-
-        USNG_Get_Grid_Values(*Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
-
-        /* Check that the second letter of the USNG string is within
-         * the range of valid second letter values
-         * Also check that the third letter is valid */
-        if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || (letters[2] > LETTER_V))
-          error_code |= USNG_STRING_ERROR;
-
-        if (!error_code)
-        {
-          double row_letter_northing = (double)(letters[2]) * ONEHT;
-          grid_easting = (double)((letters[1]) - ltr2_low_value + 1) * ONEHT;
-          if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_O))
-            grid_easting = grid_easting - ONEHT;
-
-          if (letters[2] > LETTER_O)
-            row_letter_northing = row_letter_northing - ONEHT;
-
-          if (letters[2] > LETTER_I)
-            row_letter_northing = row_letter_northing - ONEHT; 
-
-          if (row_letter_northing >= TWOMIL)
-            row_letter_northing = row_letter_northing - TWOMIL;
-
-          error_code = USNG_Get_Latitude_Band_Min_Northing(letters[0], &min_northing, &northing_offset);
-          if (!error_code)
-          {
-            grid_northing = row_letter_northing - pattern_offset;
-            if(grid_northing < 0)
-              grid_northing += TWOMIL;
-            
-            grid_northing += northing_offset;
-
-            if(grid_northing < min_northing)
-              grid_northing += TWOMIL;
-
-            *Easting = grid_easting + *Easting;
-            *Northing = grid_northing + *Northing;
-
-            /* check that point is within Zone Letter bounds */
-            utm_error_code = Set_UTM_Parameters(USNG_a, USNG_f, 0);
-            if (!utm_error_code)
-            {
-              utm_error_code = Convert_UTM_To_Geodetic(*Zone,*Hemisphere,*Easting,*Northing,&latitude,&longitude);
-              if (!utm_error_code)
-              {
-                divisor = pow (10.0, in_precision);
-                error_code = USNG_Get_Latitude_Range(letters[0], &upper_lat_limit, &lower_lat_limit);
-                if (!error_code)
-                {
-                  if (!(((lower_lat_limit - DEG_TO_RAD/divisor) <= latitude) && (latitude <= (upper_lat_limit + DEG_TO_RAD/divisor))))
-                    error_code |= USNG_LAT_ERROR;
-                }
-              }
-              else
-              {
-                if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
-                  error_code |= USNG_STRING_ERROR;
-                if(utm_error_code & UTM_EASTING_ERROR)
-                  error_code |= USNG_EASTING_ERROR;
-                if(utm_error_code & UTM_NORTHING_ERROR)
-                  error_code |= USNG_NORTHING_ERROR;
-              }
-            }
-            else
-            {
-              if(utm_error_code & UTM_A_ERROR)
-                error_code |= USNG_A_ERROR;
-              if(utm_error_code & UTM_INV_F_ERROR)
-                error_code |= USNG_INV_F_ERROR;
-              if(utm_error_code & UTM_ZONE_OVERRIDE_ERROR)
-                error_code |= USNG_ZONE_ERROR;
-            }
-          }
-        }
-      }
-    }
-  }
-  return (error_code);
-} /* Convert_USNG_To_UTM */
-
-
-long Convert_UPS_To_USNG (char   Hemisphere,
-                          double Easting,
-                          double Northing,
-                          long   Precision,
-                          char*  USNG)
-/*
- *  The function Convert_UPS_To_USNG converts UPS (hemisphere, easting,
- *  and northing) coordinates to a USNG coordinate string according to
- *  the current ellipsoid parameters.  If any errors occur, the error
- *  code(s) are returned by the function, otherwise UPS_NO_ERROR is
- *  returned.
- *
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
- *    Easting       : Easting/X in meters              (input)
- *    Northing      : Northing/Y in meters             (input)
- *    Precision     : Precision level of USNG string   (input)
- *    USNG          : USNG coordinate string           (output)
- */
-{ /* Convert_UPS_To_USNG */
-  double false_easting;       /* False easting for 2nd letter                 */
-  double false_northing;      /* False northing for 3rd letter                */
-  double grid_easting;        /* Easting used to derive 2nd letter of USNG    */
-  double grid_northing;       /* Northing used to derive 3rd letter of USNG   */
-  long ltr2_low_value;        /* 2nd letter range - low number                */
-  int letters[USNG_LETTERS];  /* Number location of 3 letters in alphabet     */
-  double divisor;
-  int index = 0;
-  long error_code = USNG_NO_ERROR;
-
-  if ((Hemisphere != 'N') && (Hemisphere != 'S'))
-    error_code |= USNG_HEMISPHERE_ERROR;
-  if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH))
-    error_code |= USNG_EASTING_ERROR;
-  if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH))
-    error_code |= USNG_NORTHING_ERROR;
-  if ((Precision < 0) || (Precision > MAX_PRECISION))
-    error_code |= USNG_PRECISION_ERROR;
-  if (!error_code)
-  {
-    divisor = pow (10.0, (5 - Precision));
-    Easting = (long)(Easting/divisor + 1.0e-9) * divisor;
-    Northing = (long)(Northing/divisor) * divisor;
-
-    if (Hemisphere == 'N')
-    {
-      if (Easting >= TWOMIL)
-        letters[0] = LETTER_Z;
-      else
-        letters[0] = LETTER_Y;
-
-      index = letters[0] - 22;
-      ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
-      false_easting = UPS_Constant_Table[index].false_easting;
-      false_northing = UPS_Constant_Table[index].false_northing;
-    }
-    else
-    {
-      if (Easting >= TWOMIL)
-        letters[0] = LETTER_B;
-      else
-        letters[0] = LETTER_A;
-
-      ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
-      false_easting = UPS_Constant_Table[letters[0]].false_easting;
-      false_northing = UPS_Constant_Table[letters[0]].false_northing;
-    }
-
-    grid_northing = Northing;
-    grid_northing = grid_northing - false_northing;
-    letters[2] = (long)(grid_northing / ONEHT);
-
-    if (letters[2] > LETTER_H)
-      letters[2] = letters[2] + 1;
-
-    if (letters[2] > LETTER_N)
-      letters[2] = letters[2] + 1;
-
-    grid_easting = Easting;
-    grid_easting = grid_easting - false_easting;
-    letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT));
-
-    if (Easting < TWOMIL)
-    {
-      if (letters[1] > LETTER_L)
-        letters[1] = letters[1] + 3;
-
-      if (letters[1] > LETTER_U)
-        letters[1] = letters[1] + 2;
-    }
-    else
-    {
-      if (letters[1] > LETTER_C)
-        letters[1] = letters[1] + 2;
-
-      if (letters[1] > LETTER_H)
-        letters[1] = letters[1] + 1;
-
-      if (letters[1] > LETTER_L)
-        letters[1] = letters[1] + 3;
-    }
-
-    Make_USNG_String (USNG, 0, letters, Easting, Northing, Precision);
-  }
-  return (error_code);
-} /* Convert_UPS_To_USNG */
-
-
-long Convert_USNG_To_UPS ( char   *USNG,
-                           char   *Hemisphere,
-                           double *Easting,
-                           double *Northing)
-/*
- *  The function Convert_USNG_To_UPS converts a USNG coordinate string
- *  to UPS (hemisphere, easting, and northing) coordinates, according
- *  to the current ellipsoid parameters. If any errors occur, the error
- *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
- *
- *    USNG          : USNG coordinate string           (input)
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
- *    Easting       : Easting/X in meters              (output)
- *    Northing      : Northing/Y in meters             (output)
- */
-{ /* Convert_USNG_To_UPS */
-  long ltr2_high_value;       /* 2nd letter range - high number             */
-  long ltr3_high_value;       /* 3rd letter range - high number (UPS)       */
-  long ltr2_low_value;        /* 2nd letter range - low number              */
-  double false_easting;       /* False easting for 2nd letter               */
-  double false_northing;      /* False northing for 3rd letter              */
-  double grid_easting;        /* easting for 100,000 meter grid square      */
-  double grid_northing;       /* northing for 100,000 meter grid square     */
-  long zone;
-  long letters[USNG_LETTERS];
-  long in_precision;
-  int index = 0;
-  long error_code = USNG_NO_ERROR;
-
-  error_code = Break_USNG_String (USNG, &zone, letters, Easting, Northing, &in_precision);
-  if (zone)
-    error_code |= USNG_STRING_ERROR;
-  else
-  {
-    if (!error_code)
-    {
-      if (letters[0] >= LETTER_Y)
-      {
-        *Hemisphere = 'N';
-
-        index = letters[0] - 22;
-        ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
-        ltr2_high_value = UPS_Constant_Table[index].ltr2_high_value;
-        ltr3_high_value = UPS_Constant_Table[index].ltr3_high_value;
-        false_easting = UPS_Constant_Table[index].false_easting;
-        false_northing = UPS_Constant_Table[index].false_northing;
-      }
-      else
-      {
-        *Hemisphere = 'S';
-
-        ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
-        ltr2_high_value = UPS_Constant_Table[letters[0]].ltr2_high_value;
-        ltr3_high_value = UPS_Constant_Table[letters[0]].ltr3_high_value;
-        false_easting = UPS_Constant_Table[letters[0]].false_easting;
-        false_northing = UPS_Constant_Table[letters[0]].false_northing;
-      }
-
-      /* Check that the second letter of the USNG string is within
-       * the range of valid second letter values
-       * Also check that the third letter is valid */
-      if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) ||
-          ((letters[1] == LETTER_D) || (letters[1] == LETTER_E) ||
-          (letters[1] == LETTER_M) || (letters[1] == LETTER_N) ||
-          (letters[1] == LETTER_V) || (letters[1] == LETTER_W)) ||
-          (letters[2] > ltr3_high_value))
-          error_code = USNG_STRING_ERROR;
-
-      if (!error_code)
-      {
-        grid_northing = (double)letters[2] * ONEHT + false_northing;
-        if (letters[2] > LETTER_I)
-          grid_northing = grid_northing - ONEHT;
-
-        if (letters[2] > LETTER_O)
-          grid_northing = grid_northing - ONEHT;
-
-        grid_easting = (double)((letters[1]) - ltr2_low_value) * ONEHT + false_easting;
-        if (ltr2_low_value != LETTER_A)
-        {
-          if (letters[1] > LETTER_L)
-            grid_easting = grid_easting - 300000.0;
-
-          if (letters[1] > LETTER_U)
-            grid_easting = grid_easting - 200000.0;
-        }
-        else
-        {
-          if (letters[1] > LETTER_C)
-            grid_easting = grid_easting - 200000.0;
-
-          if (letters[1] > LETTER_I)
-            grid_easting = grid_easting - ONEHT;
-
-          if (letters[1] > LETTER_L)
-            grid_easting = grid_easting - 300000.0;
-        }
-
-        *Easting = grid_easting + *Easting;
-        *Northing = grid_northing + *Northing;
-      }
-    }
-  }
-  return (error_code);
-} /* Convert_USNG_To_UPS */
+/***************************************************************************/
+/* RSC IDENTIFIER:  USNG
+ *
+ * ABSTRACT
+ *
+ *    This component converts between geodetic coordinates (latitude and
+ *    longitude) and United States National Grid (USNG) coordinates.
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found, the error code is combined with the current error code using
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *          USNG_NO_ERROR          : No errors occurred in function
+ *          USNG_LAT_ERROR         : Latitude outside of valid range
+ *                                    (-90 to 90 degrees)
+ *          USNG_LON_ERROR         : Longitude outside of valid range
+ *                                    (-180 to 360 degrees)
+ *          USNG_STR_ERROR         : An USNG string error: string too long,
+ *                                    too short, or badly formed
+ *          USNG_PRECISION_ERROR   : The precision must be between 0 and 5
+ *                                    inclusive.
+ *          USNG_A_ERROR           : Semi-major axis less than or equal to zero
+ *          USNG_INV_F_ERROR       : Inverse flattening outside of valid range
+ *                                    (250 to 350)
+ *          USNG_EASTING_ERROR     : Easting outside of valid range
+ *                                    (100,000 to 900,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          USNG_NORTHING_ERROR    : Northing outside of valid range
+ *                                    (0 to 10,000,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          USNG_ZONE_ERROR        : Zone outside of valid range (1 to 60)
+ *          USNG_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
+ *
+ * REUSE NOTES
+ *
+ *    USNG is intended for reuse by any application that does conversions
+ *    between geodetic coordinates and USNG coordinates.
+ *
+ * REFERENCES
+ *
+ *    Further information on USNG can be found in the Reuse Manual.
+ *
+ *    USNG originated from : Federal Geographic Data Committee
+ *                           590 National Center
+ *                           12201 Sunrise Valley Drive
+ *                           Reston, VA  22092
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    USNG was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC version 2.8.1
+ *    2. Windows XP with MS Visual C++ version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    06-05-06          Original Code (cloned from MGRS)
+ */
+
+
+/***************************************************************************/
+/*
+ *                               INCLUDES
+ */
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include "ups.h"
+#include "utm.h"
+#include "usng.h"
+
+/*
+ *      ctype.h     - Standard C character handling library
+ *      math.h      - Standard C math library
+ *      stdio.h     - Standard C input/output library
+ *      string.h    - Standard C string handling library
+ *      ups.h       - Universal Polar Stereographic (UPS) projection
+ *      utm.h       - Universal Transverse Mercator (UTM) projection
+ *      usng.h      - function prototype error checking
+ */
+
+
+/***************************************************************************/
+/*
+ *                              GLOBAL DECLARATIONS
+ */
+#define DEG_TO_RAD       0.017453292519943295 /* PI/180                      */
+#define RAD_TO_DEG       57.29577951308232087 /* 180/PI                      */
+#define LETTER_A               0   /* ARRAY INDEX FOR LETTER A               */
+#define LETTER_B               1   /* ARRAY INDEX FOR LETTER B               */
+#define LETTER_C               2   /* ARRAY INDEX FOR LETTER C               */
+#define LETTER_D               3   /* ARRAY INDEX FOR LETTER D               */
+#define LETTER_E               4   /* ARRAY INDEX FOR LETTER E               */
+#define LETTER_F               5   /* ARRAY INDEX FOR LETTER F               */
+#define LETTER_G               6   /* ARRAY INDEX FOR LETTER G               */
+#define LETTER_H               7   /* ARRAY INDEX FOR LETTER H               */
+#define LETTER_I               8   /* ARRAY INDEX FOR LETTER I               */
+#define LETTER_J               9   /* ARRAY INDEX FOR LETTER J               */
+#define LETTER_K              10   /* ARRAY INDEX FOR LETTER K               */
+#define LETTER_L              11   /* ARRAY INDEX FOR LETTER L               */
+#define LETTER_M              12   /* ARRAY INDEX FOR LETTER M               */
+#define LETTER_N              13   /* ARRAY INDEX FOR LETTER N               */
+#define LETTER_O              14   /* ARRAY INDEX FOR LETTER O               */
+#define LETTER_P              15   /* ARRAY INDEX FOR LETTER P               */
+#define LETTER_Q              16   /* ARRAY INDEX FOR LETTER Q               */
+#define LETTER_R              17   /* ARRAY INDEX FOR LETTER R               */
+#define LETTER_S              18   /* ARRAY INDEX FOR LETTER S               */
+#define LETTER_T              19   /* ARRAY INDEX FOR LETTER T               */
+#define LETTER_U              20   /* ARRAY INDEX FOR LETTER U               */
+#define LETTER_V              21   /* ARRAY INDEX FOR LETTER V               */
+#define LETTER_W              22   /* ARRAY INDEX FOR LETTER W               */
+#define LETTER_X              23   /* ARRAY INDEX FOR LETTER X               */
+#define LETTER_Y              24   /* ARRAY INDEX FOR LETTER Y               */
+#define LETTER_Z              25   /* ARRAY INDEX FOR LETTER Z               */
+#define USNG_LETTERS            3  /* NUMBER OF LETTERS IN USNG              */
+#define ONEHT          100000.e0    /* ONE HUNDRED THOUSAND                  */
+#define TWOMIL        2000000.e0    /* TWO MILLION                           */
+#define TRUE                      1  /* CONSTANT VALUE FOR TRUE VALUE  */
+#define FALSE                     0  /* CONSTANT VALUE FOR FALSE VALUE */
+#define PI    3.14159265358979323e0  /* PI                             */
+#define PI_OVER_2  (PI / 2.0e0)
+
+#define MIN_EASTING  100000
+#define MAX_EASTING  900000
+#define MIN_NORTHING 0
+#define MAX_NORTHING 10000000
+#define MAX_PRECISION           5   /* Maximum precision of easting & northing */
+#define MIN_UTM_LAT      ( (-80 * PI) / 180.0 ) /* -80 degrees in radians    */
+#define MAX_UTM_LAT      ( (84 * PI) / 180.0 )  /* 84 degrees in radians     */
+
+#define MIN_EAST_NORTH 0
+#define MAX_EAST_NORTH 4000000
+
+
+/* Ellipsoid parameters, default to WGS 84 */
+double USNG_a = 6378137.0;    /* Semi-major axis of ellipsoid in meters */
+double USNG_f = 1 / 298.257223563; /* Flattening of ellipsoid           */
+double USNG_recpf = 298.257223563;
+char   USNG_Ellipsoid_Code[3] = {'W','E',0};
+
+
+typedef struct Latitude_Band_Value
+{
+  long letter;            /* letter representing latitude band  */
+  double min_northing;    /* minimum northing for latitude band */
+  double north;           /* upper latitude for latitude band   */
+  double south;           /* lower latitude for latitude band   */
+  double northing_offset; /* latitude band northing offset      */
+} Latitude_Band;
+
+static const Latitude_Band Latitude_Band_Table[20] =
+  {{LETTER_C, 1100000.0, -72.0, -80.5, 0.0}, 
+  {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0},
+  {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0},
+  {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0},
+  {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0},
+  {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0},
+  {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0},
+  {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0},
+  {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0},
+  {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0},
+  {LETTER_N, 0.0, 8.0, 0.0, 0.0},
+  {LETTER_P, 800000.0, 16.0, 8.0, 0.0},
+  {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0},
+  {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0},
+  {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0},
+  {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0},
+  {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0},
+  {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0},
+  {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0},
+  {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}};
+
+
+typedef struct UPS_Constant_Value
+{
+  long letter;            /* letter representing latitude band      */
+  long ltr2_low_value;    /* 2nd letter range - low number         */
+  long ltr2_high_value;   /* 2nd letter range - high number          */
+  long ltr3_high_value;   /* 3rd letter range - high number (UPS)   */
+  double false_easting;   /* False easting based on 2nd letter      */
+  double false_northing;  /* False northing based on 3rd letter     */
+} UPS_Constant;
+
+static const UPS_Constant UPS_Constant_Table[4] =
+  {{LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000.0, 800000.0},
+  {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000.0, 800000.0},
+  {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000.0, 1300000.0},
+  {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000.0, 1300000.0}};
+
+/***************************************************************************/
+/*
+ *                              FUNCTIONS
+ */
+
+long USNG_Get_Latitude_Band_Min_Northing(long letter, double* min_northing, double* northing_offset)
+/*
+ * The function USNG_Get_Latitude_Band_Min_Northing receives a latitude band letter
+ * and uses the Latitude_Band_Table to determine the minimum northing and northing offset
+ * for that latitude band letter.
+ *
+ *   letter        : Latitude band letter             (input)
+ *   min_northing  : Minimum northing for that letter (output)
+ */
+{ /* USNG_Get_Latitude_Band_Min_Northing */
+  long error_code = USNG_NO_ERROR;
+
+  if ((letter >= LETTER_C) && (letter <= LETTER_H))
+  {
+    *min_northing = Latitude_Band_Table[letter-2].min_northing;
+    *northing_offset = Latitude_Band_Table[letter-2].northing_offset;
+  }
+  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
+  {
+    *min_northing = Latitude_Band_Table[letter-3].min_northing;
+    *northing_offset = Latitude_Band_Table[letter-3].northing_offset;
+  }
+  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
+  {
+    *min_northing = Latitude_Band_Table[letter-4].min_northing;
+    *northing_offset = Latitude_Band_Table[letter-4].northing_offset;
+  }
+  else
+    error_code |= USNG_STRING_ERROR;
+
+  return error_code;
+} /* USNG_Get_Latitude_Band_Min_Northing */
+
+
+long USNG_Get_Latitude_Range(long letter, double* north, double* south)
+/*
+ * The function USNG_Get_Latitude_Range receives a latitude band letter
+ * and uses the Latitude_Band_Table to determine the latitude band
+ * boundaries for that latitude band letter.
+ *
+ *   letter   : Latitude band letter                        (input)
+ *   north    : Northern latitude boundary for that letter  (output)
+ *   north    : Southern latitude boundary for that letter  (output)
+ */
+{ /* USNG_Get_Latitude_Range */
+  long error_code = USNG_NO_ERROR;
+
+  if ((letter >= LETTER_C) && (letter <= LETTER_H))
+  {
+    *north = Latitude_Band_Table[letter-2].north * DEG_TO_RAD;
+    *south = Latitude_Band_Table[letter-2].south * DEG_TO_RAD;
+  }
+  else if ((letter >= LETTER_J) && (letter <= LETTER_N))
+  {
+    *north = Latitude_Band_Table[letter-3].north * DEG_TO_RAD;
+    *south = Latitude_Band_Table[letter-3].south * DEG_TO_RAD;
+  }
+  else if ((letter >= LETTER_P) && (letter <= LETTER_X))
+  {
+    *north = Latitude_Band_Table[letter-4].north * DEG_TO_RAD;
+    *south = Latitude_Band_Table[letter-4].south * DEG_TO_RAD;
+  }
+  else
+    error_code |= USNG_STRING_ERROR;
+
+  return error_code;
+} /* USNG_Get_Latitude_Range */
+
+
+long USNG_Get_Latitude_Letter(double latitude, int* letter)
+/*
+ * The function USNG_Get_Latitude_Letter receives a latitude value
+ * and uses the Latitude_Band_Table to determine the latitude band
+ * letter for that latitude.
+ *
+ *   latitude   : Latitude              (input)
+ *   letter     : Latitude band letter  (output)
+ */
+{ /* USNG_Get_Latitude_Letter */
+  double temp = 0.0;
+  long error_code = USNG_NO_ERROR;
+  double lat_deg = latitude * RAD_TO_DEG;
+
+  if (lat_deg >= 72 && lat_deg < 84.5)
+    *letter = LETTER_X;
+  else if (lat_deg > -80.5 && lat_deg < 72)
+  {
+    temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12;
+    *letter = Latitude_Band_Table[(int)temp].letter;
+  }
+  else
+    error_code |= USNG_LAT_ERROR;
+
+  return error_code;
+} /* USNG_Get_Latitude_Letter */
+
+
+long USNG_Check_Zone(char* USNG, long* zone_exists)
+/*
+ * The function USNG_Check_Zone receives a USNG coordinate string.
+ * If a zone is given, TRUE is returned. Otherwise, FALSE
+ * is returned.
+ *
+ *   USNG           : USNG coordinate string        (input)
+ *   zone_exists    : TRUE if a zone is given,
+ *                    FALSE if a zone is not given  (output)
+ */
+{ /* USNG_Check_Zone */
+  int i = 0;
+  int j = 0;
+  int num_digits = 0;
+  long error_code = USNG_NO_ERROR;
+
+  /* skip any leading blanks */
+  while (USNG[i] == ' ')
+    i++;
+  j = i;
+  while (isdigit(USNG[i]))
+    i++;
+  num_digits = i - j;
+  if (num_digits <= 2)
+    if (num_digits > 0)
+      *zone_exists = TRUE;
+    else
+      *zone_exists = FALSE;
+  else
+    error_code |= USNG_STRING_ERROR;
+
+  return error_code;
+} /* USNG_Check_Zone */
+
+
+long Make_USNG_String (char* USNG,
+                       long Zone,
+                       int Letters[USNG_LETTERS],
+                       double Easting,
+                       double Northing,
+                       long Precision)
+/*
+ * The function Make_USNG_String constructs a USNG string
+ * from its component parts.
+ *
+ *   USNG           : USNG coordinate string          (output)
+ *   Zone           : UTM Zone                        (input)
+ *   Letters        : USNG coordinate string letters  (input)
+ *   Easting        : Easting value                   (input)
+ *   Northing       : Northing value                  (input)
+ *   Precision      : Precision level of USNG string  (input)
+ */
+{ /* Make_USNG_String */
+  long i;
+  long j;
+  double divisor;
+  long east;
+  long north;
+  char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  long error_code = USNG_NO_ERROR;
+
+  i = 0;
+  if (Zone)
+    i = sprintf (USNG+i,"%2.2ld",Zone);
+  else
+    strncpy(USNG, "  ", 2);  // 2 spaces
+
+  for (j=0;j<3;j++)
+    USNG[i++] = alphabet[Letters[j]];
+  divisor = pow (10.0, (5 - Precision));
+  Easting = fmod (Easting, 100000.0);
+  if (Easting >= 99999.5)
+    Easting = 99999.0;
+  east = (long)(Easting/divisor);
+  i += sprintf (USNG+i, "%*.*ld", (int)Precision, (int)Precision, east);
+  Northing = fmod (Northing, 100000.0);
+  if (Northing >= 99999.5)
+    Northing = 99999.0;
+  north = (long)(Northing/divisor);
+  i += sprintf (USNG+i, "%*.*ld", (int)Precision, (int)Precision, north);
+  return (error_code);
+} /* Make_USNG_String */
+
+
+long Break_USNG_String (char* USNG,
+                        long* Zone,
+                        long Letters[USNG_LETTERS],
+                        double* Easting,
+                        double* Northing,
+                        long* Precision)
+/*
+ * The function Break_USNG_String breaks down a USNG
+ * coordinate string into its component parts.
+ *
+ *   USNG           : USNG coordinate string          (input)
+ *   Zone           : UTM Zone                        (output)
+ *   Letters        : USNG coordinate string letters  (output)
+ *   Easting        : Easting value                   (output)
+ *   Northing       : Northing value                  (output)
+ *   Precision      : Precision level of USNG string  (output)
+ */
+{ /* Break_USNG_String */
+  long num_digits;
+  long num_letters;
+  long i = 0;
+  long j = 0;
+  long error_code = USNG_NO_ERROR;
+
+  while (USNG[i] == ' ')
+    i++;  /* skip any leading blanks */
+  j = i;
+  while (isdigit(USNG[i]))
+    i++;
+  num_digits = i - j;
+  if (num_digits <= 2)
+    if (num_digits > 0)
+    {
+      char zone_string[3];
+      /* get zone */
+      strncpy (zone_string, USNG+j, 2);
+      zone_string[2] = 0;
+      sscanf (zone_string, "%ld", Zone);
+      if ((*Zone < 1) || (*Zone > 60))
+        error_code |= USNG_STRING_ERROR;
+    }
+    else
+      *Zone = 0;
+  else
+    error_code |= USNG_STRING_ERROR;
+  j = i;
+
+  while (isalpha(USNG[i]))
+    i++;
+  num_letters = i - j;
+  if (num_letters == 3)
+  {
+    /* get letters */
+    Letters[0] = (toupper(USNG[j]) - (long)'A');
+    if ((Letters[0] == LETTER_I) || (Letters[0] == LETTER_O))
+      error_code |= USNG_STRING_ERROR;
+    Letters[1] = (toupper(USNG[j+1]) - (long)'A');
+    if ((Letters[1] == LETTER_I) || (Letters[1] == LETTER_O))
+      error_code |= USNG_STRING_ERROR;
+    Letters[2] = (toupper(USNG[j+2]) - (long)'A');
+    if ((Letters[2] == LETTER_I) || (Letters[2] == LETTER_O))
+      error_code |= USNG_STRING_ERROR;
+  }
+  else
+    error_code |= USNG_STRING_ERROR;
+  j = i;
+  while (isdigit(USNG[i]))
+    i++;
+  num_digits = i - j;
+  if ((num_digits <= 10) && (num_digits%2 == 0))
+  {
+    long n;
+    char east_string[6];
+    char north_string[6];
+    long east;
+    long north;
+    double multiplier;
+    /* get easting & northing */
+    n = num_digits/2;
+    *Precision = n;
+    if (n > 0)
+    {
+      strncpy (east_string, USNG+j, n);
+      east_string[n] = 0;
+      sscanf (east_string, "%ld", &east);
+      strncpy (north_string, USNG+j+n, n);
+      north_string[n] = 0;
+      sscanf (north_string, "%ld", &north);
+      multiplier = pow (10.0, 5 - n);
+      *Easting = east * multiplier;
+      *Northing = north * multiplier;
+    }
+    else
+    {
+      *Easting = 0.0;
+      *Northing = 0.0;
+    }
+  }
+  else
+    error_code |= USNG_STRING_ERROR;
+
+  return (error_code);
+} /* Break_USNG_String */
+
+
+void USNG_Get_Grid_Values (long zone,
+                           long* ltr2_low_value,
+                           long* ltr2_high_value,
+                           double *pattern_offset)
+/*
+ * The function USNG_Get_Grid_Values sets the letter range used for
+ * the 2nd letter in the USNG coordinate string, based on the set
+ * number of the utm zone. It also sets the pattern offset using a
+ * value of A for the second letter of the grid square, based on
+ * the grid pattern and set number of the utm zone.
+ *
+ *    zone            : Zone number             (input)
+ *    ltr2_low_value  : 2nd letter low number   (output)
+ *    ltr2_high_value : 2nd letter high number  (output)
+ *    pattern_offset  : Pattern offset          (output)
+ */
+{ /* BEGIN USNG_Get_Grid_Values */
+  long set_number;    /* Set number (1-6) based on UTM zone number */
+
+  set_number = zone % 6;
+
+  if (!set_number)
+    set_number = 6;
+
+  if ((set_number == 1) || (set_number == 4))
+  {
+    *ltr2_low_value = LETTER_A;
+    *ltr2_high_value = LETTER_H;
+  }
+  else if ((set_number == 2) || (set_number == 5))
+  {
+    *ltr2_low_value = LETTER_J;
+    *ltr2_high_value = LETTER_R;
+  }
+  else if ((set_number == 3) || (set_number == 6))
+  {
+    *ltr2_low_value = LETTER_S;
+    *ltr2_high_value = LETTER_Z;
+  }
+
+  /* False northing at A for second letter of grid square */
+  if ((set_number % 2) ==  0)
+    *pattern_offset = 500000.0;
+  else
+    *pattern_offset = 0.0;
+
+} /* END OF USNG_Get_Grid_Values */
+
+
+long UTM_To_USNG (long Zone,
+                  double Latitude,
+                  double Easting,
+                  double Northing,
+                  long Precision,
+                  char *USNG)
+/*
+ * The function UTM_To_USNG calculates a USNG coordinate string
+ * based on the zone, latitude, easting and northing.
+ *
+ *    Zone      : Zone number             (input)
+ *    Latitude  : Latitude in radians     (input)
+ *    Easting   : Easting                 (input)
+ *    Northing  : Northing                (input)
+ *    Precision : Precision               (input)
+ *    USNG      : USNG coordinate string  (output)
+ */
+{ /* BEGIN UTM_To_USNG */
+  double pattern_offset;      /* Pattern offset for 3rd letter               */
+  double grid_northing;       /* Northing used to derive 3rd letter of USNG  */
+  long ltr2_low_value;        /* 2nd letter range - low number               */
+  long ltr2_high_value;       /* 2nd letter range - high number              */
+  int letters[USNG_LETTERS];  /* Number location of 3 letters in alphabet    */
+  double divisor;
+  long error_code = USNG_NO_ERROR;
+
+  /* Round easting and northing values */
+  divisor = pow (10.0, (5 - Precision));
+  Easting = (long)(Easting/divisor) * divisor;
+  Northing = (long)(Northing/divisor) * divisor;
+
+  if( Latitude <= 0.0 && Northing == 1.0e7)
+  {
+    Latitude = 0.0;
+    Northing = 0.0;
+  }
+
+  USNG_Get_Grid_Values(Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
+
+  error_code = USNG_Get_Latitude_Letter(Latitude, &letters[0]);
+
+  if (!error_code)
+  {
+    grid_northing = Northing;
+
+    while (grid_northing >= TWOMIL)
+    {
+      grid_northing = grid_northing - TWOMIL;
+    }
+    grid_northing = grid_northing + pattern_offset;
+    if(grid_northing >= TWOMIL)
+      grid_northing = grid_northing - TWOMIL;
+
+    letters[2] = (long)(grid_northing / ONEHT);
+    if (letters[2] > LETTER_H)
+      letters[2] = letters[2] + 1;
+
+    if (letters[2] > LETTER_N)
+      letters[2] = letters[2] + 1;
+
+    letters[1] = ltr2_low_value + ((long)(Easting / ONEHT) -1);
+    if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N))
+      letters[1] = letters[1] + 1;
+
+    Make_USNG_String (USNG, Zone, letters, Easting, Northing, Precision);
+  }
+  return error_code;
+} /* END UTM_To_USNG */
+
+
+long Set_USNG_Parameters (double a,
+                          double f,
+                          char   *Ellipsoid_Code)
+/*
+ * The function SET_USNG_PARAMETERS receives the ellipsoid parameters and sets
+ * the corresponding state variables. If any errors occur, the error code(s)
+ * are returned by the function, otherwise USNG_NO_ERROR is returned.
+ *
+ *   a                : Semi-major axis of ellipsoid in meters  (input)
+ *   f                : Flattening of ellipsoid                 (input)
+ *   Ellipsoid_Code   : 2-letter code for ellipsoid             (input)
+ */
+{ /* Set_USNG_Parameters  */
+
+  double inv_f = 1 / f;
+  long Error_Code = USNG_NO_ERROR;
+
+  if (a <= 0.0)
+  { /* Semi-major axis must be greater than zero */
+    Error_Code |= USNG_A_ERROR;
+  }
+  if ((inv_f < 250) || (inv_f > 350))
+  { /* Inverse flattening must be between 250 and 350 */
+    Error_Code |= USNG_INV_F_ERROR;
+  }
+  if (!Error_Code)
+  { /* no errors */
+    USNG_a = a;
+    USNG_f = f;
+    USNG_recpf = inv_f;
+    strcpy (USNG_Ellipsoid_Code, Ellipsoid_Code);
+  }
+  return (Error_Code);
+}  /* Set_USNG_Parameters  */
+
+
+void Get_USNG_Parameters (double *a,
+                          double *f,
+                          char* Ellipsoid_Code)
+/*
+ * The function Get_USNG_Parameters returns the current ellipsoid
+ * parameters.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters (output)
+ *  f                : Flattening of ellipsoid                 (output)
+ *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
+ */
+{ /* Get_USNG_Parameters */
+  *a = USNG_a;
+  *f = USNG_f;
+  strcpy (Ellipsoid_Code, USNG_Ellipsoid_Code);
+  return;
+} /* Get_USNG_Parameters */
+
+
+long Convert_Geodetic_To_USNG (double Latitude,
+                               double Longitude,
+                               long Precision,
+                               char* USNG)
+/*
+ * The function Convert_Geodetic_To_USNG converts Geodetic (latitude and
+ * longitude) coordinates to a USNG coordinate string, according to the
+ * current ellipsoid parameters.  If any errors occur, the error code(s)
+ * are returned by the function, otherwise USNG_NO_ERROR is returned.
+ *
+ *    Latitude   : Latitude in radians              (input)
+ *    Longitude  : Longitude in radians             (input)
+ *    Precision  : Precision level of USNG string   (input)
+ *    USNG       : USNG coordinate string           (output)
+ *
+ */
+{ /* Convert_Geodetic_To_USNG */
+  long zone;
+  char hemisphere;
+  double easting;
+  double northing;
+  long temp_error_code = USNG_NO_ERROR;
+  long error_code = USNG_NO_ERROR;
+
+  if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2))
+  { /* Latitude out of range */
+    error_code |= USNG_LAT_ERROR;
+  }
+  if ((Longitude < -PI) || (Longitude > (2*PI)))
+  { /* Longitude out of range */
+    error_code |= USNG_LON_ERROR;
+  }
+  if ((Precision < 0) || (Precision > MAX_PRECISION))
+    error_code |= USNG_PRECISION_ERROR;
+  if (!error_code)
+  {
+    if ((Latitude < MIN_UTM_LAT) || (Latitude > MAX_UTM_LAT))
+    {
+      temp_error_code = Set_UPS_Parameters (USNG_a, USNG_f);
+      if(!temp_error_code)
+      {
+        temp_error_code |= Convert_Geodetic_To_UPS (Latitude, Longitude, &hemisphere, &easting, &northing);
+        if(!temp_error_code)
+          error_code |= Convert_UPS_To_USNG (hemisphere, easting, northing, Precision, USNG);
+        else
+        {
+          if(temp_error_code & UPS_LAT_ERROR)
+            error_code |= USNG_LAT_ERROR;
+          if(temp_error_code & UPS_LON_ERROR)
+            error_code |= USNG_LON_ERROR;
+        }
+      }
+      else
+      {
+        if(temp_error_code & UPS_A_ERROR)
+          error_code |= USNG_A_ERROR;
+        if(temp_error_code & UPS_INV_F_ERROR)
+          error_code |= USNG_INV_F_ERROR;
+      }
+    }
+    else
+    {
+      temp_error_code = Set_UTM_Parameters (USNG_a, USNG_f, 0);
+      if(!temp_error_code)
+      {
+        temp_error_code |= Convert_Geodetic_To_UTM (Latitude, Longitude, &zone, &hemisphere, &easting, &northing);
+        if(!temp_error_code)
+          error_code |= UTM_To_USNG (zone, Latitude, easting, northing, Precision, USNG);
+        else
+        {
+          if(temp_error_code & UTM_LAT_ERROR)
+            error_code |= USNG_LAT_ERROR;
+          if(temp_error_code & UTM_LON_ERROR)
+            error_code |= USNG_LON_ERROR;
+          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+            error_code |= USNG_ZONE_ERROR;
+          if(temp_error_code & UTM_EASTING_ERROR)
+            error_code |= USNG_EASTING_ERROR;
+          if(temp_error_code & UTM_NORTHING_ERROR)
+            error_code |= USNG_NORTHING_ERROR;
+        }
+      }
+      else
+      {
+        if(temp_error_code & UTM_A_ERROR)
+          error_code |= USNG_A_ERROR;
+        if(temp_error_code & UTM_INV_F_ERROR)
+          error_code |= USNG_INV_F_ERROR;
+        if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+          error_code |= USNG_ZONE_ERROR;
+      }
+    }
+  }
+  return (error_code);
+} /* Convert_Geodetic_To_USNG */
+
+
+long Convert_USNG_To_Geodetic (char* USNG,
+                               double *Latitude,
+                               double *Longitude)
+/*
+ * The function Convert_USNG_To_Geodetic converts a USNG coordinate string
+ * to Geodetic (latitude and longitude) coordinates
+ * according to the current ellipsoid parameters.  If any errors occur,
+ * the error code(s) are returned by the function, otherwise UTM_NO_ERROR
+ * is returned.
+ *
+ *    USNG       : USNG coordinate string           (input)
+ *    Latitude   : Latitude in radians              (output)
+ *    Longitude  : Longitude in radians             (output)
+ *
+ */
+{ /* Convert_USNG_To_Geodetic */
+  long zone;
+  char hemisphere;
+  double easting;
+  double northing;
+  long zone_exists;
+  long temp_error_code = USNG_NO_ERROR;
+  long error_code = USNG_NO_ERROR;
+
+  error_code = USNG_Check_Zone(USNG, &zone_exists);
+  if (!error_code)
+  {
+    if (zone_exists)
+    {
+      error_code |= Convert_USNG_To_UTM (USNG, &zone, &hemisphere, &easting, &northing);
+      if(!error_code || (error_code & USNG_LAT_WARNING))
+      {
+        temp_error_code = Set_UTM_Parameters (USNG_a, USNG_f, 0);
+        if(!temp_error_code)
+        {
+          temp_error_code |= Convert_UTM_To_Geodetic (zone, hemisphere, easting, northing, Latitude, Longitude);
+          if(temp_error_code)
+          {
+            if((temp_error_code & UTM_ZONE_ERROR) || (temp_error_code & UTM_HEMISPHERE_ERROR))
+              error_code |= USNG_STRING_ERROR;
+            if(temp_error_code & UTM_EASTING_ERROR)
+              error_code |= USNG_EASTING_ERROR;
+            if(temp_error_code & UTM_NORTHING_ERROR)
+              error_code |= USNG_NORTHING_ERROR;
+          }
+        }
+        else
+        {
+          if(temp_error_code & UTM_A_ERROR)
+            error_code |= USNG_A_ERROR;
+          if(temp_error_code & UTM_INV_F_ERROR)
+            error_code |= USNG_INV_F_ERROR;
+          if(temp_error_code & UTM_ZONE_OVERRIDE_ERROR)
+            error_code |= USNG_ZONE_ERROR;
+        }
+      }
+    }
+    else
+    {
+      error_code |= Convert_USNG_To_UPS (USNG, &hemisphere, &easting, &northing);
+      if(!error_code)
+      {
+        temp_error_code = Set_UPS_Parameters (USNG_a, USNG_f);
+        if(!temp_error_code)
+        {
+          temp_error_code |= Convert_UPS_To_Geodetic (hemisphere, easting, northing, Latitude, Longitude);
+          if(temp_error_code)
+          {
+            if(temp_error_code & UPS_HEMISPHERE_ERROR)
+              error_code |= USNG_STRING_ERROR;
+            if(temp_error_code & UPS_EASTING_ERROR)
+              error_code |= USNG_EASTING_ERROR;
+            if(temp_error_code & UPS_LAT_ERROR)
+              error_code |= USNG_NORTHING_ERROR;
+          }
+        }
+        else
+        {
+          if(temp_error_code & UPS_A_ERROR)
+            error_code |= USNG_A_ERROR;
+          if(temp_error_code & UPS_INV_F_ERROR)
+            error_code |= USNG_INV_F_ERROR;
+        }
+      }
+    }
+  }
+  return (error_code);
+} /* END OF Convert_USNG_To_Geodetic */
+
+
+long Convert_UTM_To_USNG (long Zone,
+                          char Hemisphere,
+                          double Easting,
+                          double Northing,
+                          long Precision,
+                          char* USNG)
+/*
+ * The function Convert_UTM_To_USNG converts UTM (zone, easting, and
+ * northing) coordinates to a USNG coordinate string, according to the
+ * current ellipsoid parameters.  If any errors occur, the error code(s)
+ * are returned by the function, otherwise USNG_NO_ERROR is returned.
+ *
+ *    Zone       : UTM zone                         (input)
+ *    Hemisphere : North or South hemisphere        (input)
+ *    Easting    : Easting (X) in meters            (input)
+ *    Northing   : Northing (Y) in meters           (input)
+ *    Precision  : Precision level of USNG string   (input)
+ *    USNG       : USNG coordinate string           (output)
+ */
+{ /* Convert_UTM_To_USNG */
+  double latitude;           /* Latitude of UTM point */
+  double longitude;          /* Longitude of UTM point */
+  long utm_error_code = USNG_NO_ERROR;
+  long error_code = USNG_NO_ERROR;
+
+  if ((Zone < 1) || (Zone > 60))
+    error_code |= USNG_ZONE_ERROR;
+  if ((Hemisphere != 'S') && (Hemisphere != 'N'))
+    error_code |= USNG_HEMISPHERE_ERROR;
+  if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
+    error_code |= USNG_EASTING_ERROR;
+  if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
+    error_code |= USNG_NORTHING_ERROR;
+  if ((Precision < 0) || (Precision > MAX_PRECISION))
+    error_code |= USNG_PRECISION_ERROR;
+  if (!error_code)
+  {
+    Set_UTM_Parameters (USNG_a, USNG_f, 0);
+    utm_error_code = Convert_UTM_To_Geodetic (Zone, Hemisphere, Easting, Northing, &latitude, &longitude);
+
+    if(utm_error_code)
+    {
+      if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
+        error_code |= USNG_STRING_ERROR;
+      if(utm_error_code & UTM_EASTING_ERROR)
+        error_code |= USNG_EASTING_ERROR;
+      if(utm_error_code & UTM_NORTHING_ERROR)
+        error_code |= USNG_NORTHING_ERROR;
+    }
+
+    error_code |= UTM_To_USNG (Zone, latitude, Easting, Northing, Precision, USNG);
+  }
+  return (error_code);
+} /* Convert_UTM_To_USNG */
+
+
+long Convert_USNG_To_UTM (char   *USNG,
+                          long   *Zone,
+                          char   *Hemisphere,
+                          double *Easting,
+                          double *Northing)
+/*
+ * The function Convert_USNG_To_UTM converts a USNG coordinate string
+ * to UTM projection (zone, hemisphere, easting and northing) coordinates
+ * according to the current ellipsoid parameters.  If any errors occur,
+ * the error code(s) are returned by the function, otherwise UTM_NO_ERROR
+ * is returned.
+ *
+ *    USNG       : USNG coordinate string           (input)
+ *    Zone       : UTM zone                         (output)
+ *    Hemisphere : North or South hemisphere        (output)
+ *    Easting    : Easting (X) in meters            (output)
+ *    Northing   : Northing (Y) in meters           (output)
+ */
+{ /* Convert_USNG_To_UTM */
+  double min_northing;
+  double northing_offset;
+  long ltr2_low_value;
+  long ltr2_high_value;
+  double pattern_offset;
+  double upper_lat_limit;     /* North latitude limits based on 1st letter  */
+  double lower_lat_limit;     /* South latitude limits based on 1st letter  */
+  double grid_easting;        /* Easting for 100,000 meter grid square      */
+  double grid_northing;       /* Northing for 100,000 meter grid square     */
+  long letters[USNG_LETTERS];
+  long in_precision;
+  double latitude = 0.0;
+  double longitude = 0.0;
+  double divisor = 1.0;
+  long utm_error_code = USNG_NO_ERROR;
+  long error_code = USNG_NO_ERROR;
+
+  error_code = Break_USNG_String (USNG, Zone, letters, Easting, Northing, &in_precision);
+  if (!*Zone)
+    error_code |= USNG_STRING_ERROR;
+  else
+  {
+    if (!error_code)
+    {
+      if ((letters[0] == LETTER_X) && ((*Zone == 32) || (*Zone == 34) || (*Zone == 36)))
+        error_code |= USNG_STRING_ERROR;
+      else
+      {
+        if (letters[0] < LETTER_N)
+          *Hemisphere = 'S';
+        else
+          *Hemisphere = 'N';
+
+        USNG_Get_Grid_Values(*Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
+
+        /* Check that the second letter of the USNG string is within
+         * the range of valid second letter values
+         * Also check that the third letter is valid */
+        if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) || (letters[2] > LETTER_V))
+          error_code |= USNG_STRING_ERROR;
+
+        if (!error_code)
+        {
+          double row_letter_northing = (double)(letters[2]) * ONEHT;
+          grid_easting = (double)((letters[1]) - ltr2_low_value + 1) * ONEHT;
+          if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_O))
+            grid_easting = grid_easting - ONEHT;
+
+          if (letters[2] > LETTER_O)
+            row_letter_northing = row_letter_northing - ONEHT;
+
+          if (letters[2] > LETTER_I)
+            row_letter_northing = row_letter_northing - ONEHT; 
+
+          if (row_letter_northing >= TWOMIL)
+            row_letter_northing = row_letter_northing - TWOMIL;
+
+          error_code = USNG_Get_Latitude_Band_Min_Northing(letters[0], &min_northing, &northing_offset);
+          if (!error_code)
+          {
+            grid_northing = row_letter_northing - pattern_offset;
+            if(grid_northing < 0)
+              grid_northing += TWOMIL;
+            
+            grid_northing += northing_offset;
+
+            if(grid_northing < min_northing)
+              grid_northing += TWOMIL;
+
+            *Easting = grid_easting + *Easting;
+            *Northing = grid_northing + *Northing;
+
+            /* check that point is within Zone Letter bounds */
+            utm_error_code = Set_UTM_Parameters(USNG_a, USNG_f, 0);
+            if (!utm_error_code)
+            {
+              utm_error_code = Convert_UTM_To_Geodetic(*Zone,*Hemisphere,*Easting,*Northing,&latitude,&longitude);
+              if (!utm_error_code)
+              {
+                divisor = pow (10.0, in_precision);
+                error_code = USNG_Get_Latitude_Range(letters[0], &upper_lat_limit, &lower_lat_limit);
+                if (!error_code)
+                {
+                  if (!(((lower_lat_limit - DEG_TO_RAD/divisor) <= latitude) && (latitude <= (upper_lat_limit + DEG_TO_RAD/divisor))))
+                    error_code |= USNG_LAT_ERROR;
+                }
+              }
+              else
+              {
+                if((utm_error_code & UTM_ZONE_ERROR) || (utm_error_code & UTM_HEMISPHERE_ERROR))
+                  error_code |= USNG_STRING_ERROR;
+                if(utm_error_code & UTM_EASTING_ERROR)
+                  error_code |= USNG_EASTING_ERROR;
+                if(utm_error_code & UTM_NORTHING_ERROR)
+                  error_code |= USNG_NORTHING_ERROR;
+              }
+            }
+            else
+            {
+              if(utm_error_code & UTM_A_ERROR)
+                error_code |= USNG_A_ERROR;
+              if(utm_error_code & UTM_INV_F_ERROR)
+                error_code |= USNG_INV_F_ERROR;
+              if(utm_error_code & UTM_ZONE_OVERRIDE_ERROR)
+                error_code |= USNG_ZONE_ERROR;
+            }
+          }
+        }
+      }
+    }
+  }
+  return (error_code);
+} /* Convert_USNG_To_UTM */
+
+
+long Convert_UPS_To_USNG (char   Hemisphere,
+                          double Easting,
+                          double Northing,
+                          long   Precision,
+                          char*  USNG)
+/*
+ *  The function Convert_UPS_To_USNG converts UPS (hemisphere, easting,
+ *  and northing) coordinates to a USNG coordinate string according to
+ *  the current ellipsoid parameters.  If any errors occur, the error
+ *  code(s) are returned by the function, otherwise UPS_NO_ERROR is
+ *  returned.
+ *
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
+ *    Easting       : Easting/X in meters              (input)
+ *    Northing      : Northing/Y in meters             (input)
+ *    Precision     : Precision level of USNG string   (input)
+ *    USNG          : USNG coordinate string           (output)
+ */
+{ /* Convert_UPS_To_USNG */
+  double false_easting;       /* False easting for 2nd letter                 */
+  double false_northing;      /* False northing for 3rd letter                */
+  double grid_easting;        /* Easting used to derive 2nd letter of USNG    */
+  double grid_northing;       /* Northing used to derive 3rd letter of USNG   */
+  long ltr2_low_value;        /* 2nd letter range - low number                */
+  int letters[USNG_LETTERS];  /* Number location of 3 letters in alphabet     */
+  double divisor;
+  int index = 0;
+  long error_code = USNG_NO_ERROR;
+
+  if ((Hemisphere != 'N') && (Hemisphere != 'S'))
+    error_code |= USNG_HEMISPHERE_ERROR;
+  if ((Easting < MIN_EAST_NORTH) || (Easting > MAX_EAST_NORTH))
+    error_code |= USNG_EASTING_ERROR;
+  if ((Northing < MIN_EAST_NORTH) || (Northing > MAX_EAST_NORTH))
+    error_code |= USNG_NORTHING_ERROR;
+  if ((Precision < 0) || (Precision > MAX_PRECISION))
+    error_code |= USNG_PRECISION_ERROR;
+  if (!error_code)
+  {
+    divisor = pow (10.0, (5 - Precision));
+    Easting = (long)(Easting/divisor + 1.0e-9) * divisor;
+    Northing = (long)(Northing/divisor) * divisor;
+
+    if (Hemisphere == 'N')
+    {
+      if (Easting >= TWOMIL)
+        letters[0] = LETTER_Z;
+      else
+        letters[0] = LETTER_Y;
+
+      index = letters[0] - 22;
+      ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
+      false_easting = UPS_Constant_Table[index].false_easting;
+      false_northing = UPS_Constant_Table[index].false_northing;
+    }
+    else
+    {
+      if (Easting >= TWOMIL)
+        letters[0] = LETTER_B;
+      else
+        letters[0] = LETTER_A;
+
+      ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
+      false_easting = UPS_Constant_Table[letters[0]].false_easting;
+      false_northing = UPS_Constant_Table[letters[0]].false_northing;
+    }
+
+    grid_northing = Northing;
+    grid_northing = grid_northing - false_northing;
+    letters[2] = (long)(grid_northing / ONEHT);
+
+    if (letters[2] > LETTER_H)
+      letters[2] = letters[2] + 1;
+
+    if (letters[2] > LETTER_N)
+      letters[2] = letters[2] + 1;
+
+    grid_easting = Easting;
+    grid_easting = grid_easting - false_easting;
+    letters[1] = ltr2_low_value + ((long)(grid_easting / ONEHT));
+
+    if (Easting < TWOMIL)
+    {
+      if (letters[1] > LETTER_L)
+        letters[1] = letters[1] + 3;
+
+      if (letters[1] > LETTER_U)
+        letters[1] = letters[1] + 2;
+    }
+    else
+    {
+      if (letters[1] > LETTER_C)
+        letters[1] = letters[1] + 2;
+
+      if (letters[1] > LETTER_H)
+        letters[1] = letters[1] + 1;
+
+      if (letters[1] > LETTER_L)
+        letters[1] = letters[1] + 3;
+    }
+
+    Make_USNG_String (USNG, 0, letters, Easting, Northing, Precision);
+  }
+  return (error_code);
+} /* Convert_UPS_To_USNG */
+
+
+long Convert_USNG_To_UPS ( char   *USNG,
+                           char   *Hemisphere,
+                           double *Easting,
+                           double *Northing)
+/*
+ *  The function Convert_USNG_To_UPS converts a USNG coordinate string
+ *  to UPS (hemisphere, easting, and northing) coordinates, according
+ *  to the current ellipsoid parameters. If any errors occur, the error
+ *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
+ *
+ *    USNG          : USNG coordinate string           (input)
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
+ *    Easting       : Easting/X in meters              (output)
+ *    Northing      : Northing/Y in meters             (output)
+ */
+{ /* Convert_USNG_To_UPS */
+  long ltr2_high_value;       /* 2nd letter range - high number             */
+  long ltr3_high_value;       /* 3rd letter range - high number (UPS)       */
+  long ltr2_low_value;        /* 2nd letter range - low number              */
+  double false_easting;       /* False easting for 2nd letter               */
+  double false_northing;      /* False northing for 3rd letter              */
+  double grid_easting;        /* easting for 100,000 meter grid square      */
+  double grid_northing;       /* northing for 100,000 meter grid square     */
+  long zone;
+  long letters[USNG_LETTERS];
+  long in_precision;
+  int index = 0;
+  long error_code = USNG_NO_ERROR;
+
+  error_code = Break_USNG_String (USNG, &zone, letters, Easting, Northing, &in_precision);
+  if (zone)
+    error_code |= USNG_STRING_ERROR;
+  else
+  {
+    if (!error_code)
+    {
+      if (letters[0] >= LETTER_Y)
+      {
+        *Hemisphere = 'N';
+
+        index = letters[0] - 22;
+        ltr2_low_value = UPS_Constant_Table[index].ltr2_low_value;
+        ltr2_high_value = UPS_Constant_Table[index].ltr2_high_value;
+        ltr3_high_value = UPS_Constant_Table[index].ltr3_high_value;
+        false_easting = UPS_Constant_Table[index].false_easting;
+        false_northing = UPS_Constant_Table[index].false_northing;
+      }
+      else
+      {
+        *Hemisphere = 'S';
+
+        ltr2_low_value = UPS_Constant_Table[letters[0]].ltr2_low_value;
+        ltr2_high_value = UPS_Constant_Table[letters[0]].ltr2_high_value;
+        ltr3_high_value = UPS_Constant_Table[letters[0]].ltr3_high_value;
+        false_easting = UPS_Constant_Table[letters[0]].false_easting;
+        false_northing = UPS_Constant_Table[letters[0]].false_northing;
+      }
+
+      /* Check that the second letter of the USNG string is within
+       * the range of valid second letter values
+       * Also check that the third letter is valid */
+      if ((letters[1] < ltr2_low_value) || (letters[1] > ltr2_high_value) ||
+          ((letters[1] == LETTER_D) || (letters[1] == LETTER_E) ||
+          (letters[1] == LETTER_M) || (letters[1] == LETTER_N) ||
+          (letters[1] == LETTER_V) || (letters[1] == LETTER_W)) ||
+          (letters[2] > ltr3_high_value))
+          error_code = USNG_STRING_ERROR;
+
+      if (!error_code)
+      {
+        grid_northing = (double)letters[2] * ONEHT + false_northing;
+        if (letters[2] > LETTER_I)
+          grid_northing = grid_northing - ONEHT;
+
+        if (letters[2] > LETTER_O)
+          grid_northing = grid_northing - ONEHT;
+
+        grid_easting = (double)((letters[1]) - ltr2_low_value) * ONEHT + false_easting;
+        if (ltr2_low_value != LETTER_A)
+        {
+          if (letters[1] > LETTER_L)
+            grid_easting = grid_easting - 300000.0;
+
+          if (letters[1] > LETTER_U)
+            grid_easting = grid_easting - 200000.0;
+        }
+        else
+        {
+          if (letters[1] > LETTER_C)
+            grid_easting = grid_easting - 200000.0;
+
+          if (letters[1] > LETTER_I)
+            grid_easting = grid_easting - ONEHT;
+
+          if (letters[1] > LETTER_L)
+            grid_easting = grid_easting - 300000.0;
+        }
+
+        *Easting = grid_easting + *Easting;
+        *Northing = grid_northing + *Northing;
+      }
+    }
+  }
+  return (error_code);
+} /* Convert_USNG_To_UPS */
diff --git a/geotranz/usng.h b/geotranz/usng.h
old mode 100755
new mode 100644
index 05bb4e3..456cb37
--- a/geotranz/usng.h
+++ b/geotranz/usng.h
@@ -1,252 +1,252 @@
-#ifndef USNG_H
-  #define USNG_H
-
-/***************************************************************************/
-/* RSC IDENTIFIER:  USNG
- *
- * ABSTRACT
- *
- *    This component converts between geodetic coordinates (latitude and 
- *    longitude) and United States National Grid (USNG) coordinates. 
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found, the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *          USNG_NO_ERROR          : No errors occurred in function
- *          USNG_LAT_ERROR         : Latitude outside of valid range 
- *                                    (-90 to 90 degrees)
- *          USNG_LON_ERROR         : Longitude outside of valid range
- *                                    (-180 to 360 degrees)
- *          USNG_STR_ERROR         : An USNG string error: string too long,
- *                                    too short, or badly formed
- *          USNG_PRECISION_ERROR   : The precision must be between 0 and 5 
- *                                    inclusive.
- *          USNG_A_ERROR           : Semi-major axis less than or equal to zero
- *          USNG_INV_F_ERROR       : Inverse flattening outside of valid range
- *									                  (250 to 350)
- *          USNG_EASTING_ERROR     : Easting outside of valid range
- *                                    (100,000 to 900,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          USNG_NORTHING_ERROR    : Northing outside of valid range
- *                                    (0 to 10,000,000 meters for UTM)
- *                                    (0 to 4,000,000 meters for UPS)
- *          USNG_ZONE_ERROR        : Zone outside of valid range (1 to 60)
- *          USNG_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
- *
- * REUSE NOTES
- *
- *    USNG is intended for reuse by any application that does conversions
- *    between geodetic coordinates and USNG coordinates.
- *
- * REFERENCES
- *
- *    Further information on USNG can be found in the Reuse Manual.
- *
- *    USNG originated from : Federal Geographic Data Committee
- *                           590 National Center
- *                           12201 Sunrise Valley Drive
- *                           Reston, VA  22092
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *
- * ENVIRONMENT
- *
- *    USNG was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC version 2.8.1
- *    2. Windows 95 with MS Visual C++ version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    06-05-06          Original Code (cloned from MGRS)
- *
- */
-
-
-/***************************************************************************/
-/*
- *                              DEFINES
- */
-
-  #define USNG_NO_ERROR                0x0000
-  #define USNG_LAT_ERROR               0x0001
-  #define USNG_LON_ERROR               0x0002
-  #define USNG_STRING_ERROR            0x0004
-  #define USNG_PRECISION_ERROR         0x0008
-  #define USNG_A_ERROR                 0x0010
-  #define USNG_INV_F_ERROR             0x0020
-  #define USNG_EASTING_ERROR           0x0040
-  #define USNG_NORTHING_ERROR          0x0080
-  #define USNG_ZONE_ERROR              0x0100
-  #define USNG_HEMISPHERE_ERROR        0x0200
-  #define USNG_LAT_WARNING             0x0400
-
-
-/***************************************************************************/
-/*
- *                              FUNCTION PROTOTYPES
- */
-
-/* ensure proper linkage to c++ programs */
-  #ifdef __cplusplus
-extern "C" {
-  #endif
-
-
-  long Set_USNG_Parameters(double a,
-                           double f,
-                           char   *Ellipsoid_Code);
-/*
- * The function Set_USNG_Parameters receives the ellipsoid parameters and sets
- * the corresponding state variables. If any errors occur, the error code(s)
- * are returned by the function, otherwise USNG_NO_ERROR is returned.
- *
- *   a                : Semi-major axis of ellipsoid in meters (input)
- *   f                : Flattening of ellipsoid					       (input)
- *   Ellipsoid_Code   : 2-letter code for ellipsoid            (input)
- */
-
-
-  void Get_USNG_Parameters(double *a,
-                           double *f,
-                           char   *Ellipsoid_Code);
-/*
- * The function Get_USNG_Parameters returns the current ellipsoid
- * parameters.
- *
- *  a                : Semi-major axis of ellipsoid, in meters (output)
- *  f                : Flattening of ellipsoid					       (output)
- *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
- */
-
-
-  long Convert_Geodetic_To_USNG (double Latitude,
-                                 double Longitude,
-                                 long   Precision,
-                                 char *USNG);
-/*
- * The function Convert_Geodetic_To_USNG converts geodetic (latitude and
- * longitude) coordinates to a USNG coordinate string, according to the 
- * current ellipsoid parameters.  If any errors occur, the error code(s) 
- * are returned by the  function, otherwise USNG_NO_ERROR is returned.
- *
- *    Latitude   : Latitude in radians              (input)
- *    Longitude  : Longitude in radians             (input)
- *    Precision  : Precision level of USNG string   (input)
- *    USNG       : USNG coordinate string           (output)
- *  
- */
-
-
-  long Convert_USNG_To_Geodetic (char *USNG,
-                                 double *Latitude,
-                                 double *Longitude);
-/*
- * This function converts a USNG coordinate string to Geodetic (latitude
- * and longitude in radians) coordinates.  If any errors occur, the error 
- * code(s) are returned by the  function, otherwise USNG_NO_ERROR is returned.  
- *
- *    USNG       : USNG coordinate string           (input)
- *    Latitude   : Latitude in radians              (output)
- *    Longitude  : Longitude in radians             (output)
- *  
- */
-
-
-  long Convert_UTM_To_USNG (long Zone,
-                            char Hemisphere,
-                            double Easting,
-                            double Northing,
-                            long Precision,
-                            char *USNG);
-/*
- * The function Convert_UTM_To_USNG converts UTM (zone, easting, and
- * northing) coordinates to a USNG coordinate string, according to the 
- * current ellipsoid parameters.  If any errors occur, the error code(s) 
- * are returned by the  function, otherwise USNG_NO_ERROR is returned.
- *
- *    Zone       : UTM zone                         (input)
- *    Hemisphere : North or South hemisphere        (input)
- *    Easting    : Easting (X) in meters            (input)
- *    Northing   : Northing (Y) in meters           (input)
- *    Precision  : Precision level of USNG string   (input)
- *    USNG       : USNG coordinate string           (output)
- */
-
-
-  long Convert_USNG_To_UTM (char   *USNG,
-                            long   *Zone,
-                            char   *Hemisphere,
-                            double *Easting,
-                            double *Northing); 
-/*
- * The function Convert_USNG_To_UTM converts a USNG coordinate string
- * to UTM projection (zone, hemisphere, easting and northing) coordinates 
- * according to the current ellipsoid parameters.  If any errors occur, 
- * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
- * is returned.
- *
- *    USNG       : USNG coordinate string           (input)
- *    Zone       : UTM zone                         (output)
- *    Hemisphere : North or South hemisphere        (output)
- *    Easting    : Easting (X) in meters            (output)
- *    Northing   : Northing (Y) in meters           (output)
- */
-
-
-
-  long Convert_UPS_To_USNG ( char   Hemisphere,
-                             double Easting,
-                             double Northing,
-                             long Precision,
-                             char *USNG);
-
-/*
- *  The function Convert_UPS_To_USNG converts UPS (hemisphere, easting, 
- *  and northing) coordinates to a USNG coordinate string according to 
- *  the current ellipsoid parameters.  If any errors occur, the error
- *  code(s) are returned by the function, otherwise UPS_NO_ERROR is 
- *  returned.
- *
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
- *    Easting       : Easting/X in meters              (input)
- *    Northing      : Northing/Y in meters             (input)
- *    Precision     : Precision level of USNG string   (input)
- *    USNG          : USNG coordinate string           (output)
- */
-
-
-  long Convert_USNG_To_UPS ( char   *USNG,
-                             char   *Hemisphere,
-                             double *Easting,
-                             double *Northing);
-/*
- *  The function Convert_USNG_To_UPS converts a USNG coordinate string
- *  to UPS (hemisphere, easting, and northing) coordinates, according 
- *  to the current ellipsoid parameters. If any errors occur, the error 
- *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
- *
- *    USNG          : USNG coordinate string           (input)
- *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
- *    Easting       : Easting/X in meters              (output)
- *    Northing      : Northing/Y in meters             (output)
- */
-
-
-
-  #ifdef __cplusplus
-}
-  #endif
-
-#endif /* USNG_H */
+#ifndef USNG_H
+  #define USNG_H
+
+/***************************************************************************/
+/* RSC IDENTIFIER:  USNG
+ *
+ * ABSTRACT
+ *
+ *    This component converts between geodetic coordinates (latitude and 
+ *    longitude) and United States National Grid (USNG) coordinates. 
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found, the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *          USNG_NO_ERROR          : No errors occurred in function
+ *          USNG_LAT_ERROR         : Latitude outside of valid range 
+ *                                    (-90 to 90 degrees)
+ *          USNG_LON_ERROR         : Longitude outside of valid range
+ *                                    (-180 to 360 degrees)
+ *          USNG_STR_ERROR         : An USNG string error: string too long,
+ *                                    too short, or badly formed
+ *          USNG_PRECISION_ERROR   : The precision must be between 0 and 5 
+ *                                    inclusive.
+ *          USNG_A_ERROR           : Semi-major axis less than or equal to zero
+ *          USNG_INV_F_ERROR       : Inverse flattening outside of valid range
+ *									                  (250 to 350)
+ *          USNG_EASTING_ERROR     : Easting outside of valid range
+ *                                    (100,000 to 900,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          USNG_NORTHING_ERROR    : Northing outside of valid range
+ *                                    (0 to 10,000,000 meters for UTM)
+ *                                    (0 to 4,000,000 meters for UPS)
+ *          USNG_ZONE_ERROR        : Zone outside of valid range (1 to 60)
+ *          USNG_HEMISPHERE_ERROR  : Invalid hemisphere ('N' or 'S')
+ *
+ * REUSE NOTES
+ *
+ *    USNG is intended for reuse by any application that does conversions
+ *    between geodetic coordinates and USNG coordinates.
+ *
+ * REFERENCES
+ *
+ *    Further information on USNG can be found in the Reuse Manual.
+ *
+ *    USNG originated from : Federal Geographic Data Committee
+ *                           590 National Center
+ *                           12201 Sunrise Valley Drive
+ *                           Reston, VA  22092
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *
+ * ENVIRONMENT
+ *
+ *    USNG was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC version 2.8.1
+ *    2. Windows 95 with MS Visual C++ version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    06-05-06          Original Code (cloned from MGRS)
+ *
+ */
+
+
+/***************************************************************************/
+/*
+ *                              DEFINES
+ */
+
+  #define USNG_NO_ERROR                0x0000
+  #define USNG_LAT_ERROR               0x0001
+  #define USNG_LON_ERROR               0x0002
+  #define USNG_STRING_ERROR            0x0004
+  #define USNG_PRECISION_ERROR         0x0008
+  #define USNG_A_ERROR                 0x0010
+  #define USNG_INV_F_ERROR             0x0020
+  #define USNG_EASTING_ERROR           0x0040
+  #define USNG_NORTHING_ERROR          0x0080
+  #define USNG_ZONE_ERROR              0x0100
+  #define USNG_HEMISPHERE_ERROR        0x0200
+  #define USNG_LAT_WARNING             0x0400
+
+
+/***************************************************************************/
+/*
+ *                              FUNCTION PROTOTYPES
+ */
+
+/* ensure proper linkage to c++ programs */
+  #ifdef __cplusplus
+extern "C" {
+  #endif
+
+
+  long Set_USNG_Parameters(double a,
+                           double f,
+                           char   *Ellipsoid_Code);
+/*
+ * The function Set_USNG_Parameters receives the ellipsoid parameters and sets
+ * the corresponding state variables. If any errors occur, the error code(s)
+ * are returned by the function, otherwise USNG_NO_ERROR is returned.
+ *
+ *   a                : Semi-major axis of ellipsoid in meters (input)
+ *   f                : Flattening of ellipsoid					       (input)
+ *   Ellipsoid_Code   : 2-letter code for ellipsoid            (input)
+ */
+
+
+  void Get_USNG_Parameters(double *a,
+                           double *f,
+                           char   *Ellipsoid_Code);
+/*
+ * The function Get_USNG_Parameters returns the current ellipsoid
+ * parameters.
+ *
+ *  a                : Semi-major axis of ellipsoid, in meters (output)
+ *  f                : Flattening of ellipsoid					       (output)
+ *  Ellipsoid_Code   : 2-letter code for ellipsoid             (output)
+ */
+
+
+  long Convert_Geodetic_To_USNG (double Latitude,
+                                 double Longitude,
+                                 long   Precision,
+                                 char *USNG);
+/*
+ * The function Convert_Geodetic_To_USNG converts geodetic (latitude and
+ * longitude) coordinates to a USNG coordinate string, according to the 
+ * current ellipsoid parameters.  If any errors occur, the error code(s) 
+ * are returned by the  function, otherwise USNG_NO_ERROR is returned.
+ *
+ *    Latitude   : Latitude in radians              (input)
+ *    Longitude  : Longitude in radians             (input)
+ *    Precision  : Precision level of USNG string   (input)
+ *    USNG       : USNG coordinate string           (output)
+ *  
+ */
+
+
+  long Convert_USNG_To_Geodetic (char *USNG,
+                                 double *Latitude,
+                                 double *Longitude);
+/*
+ * This function converts a USNG coordinate string to Geodetic (latitude
+ * and longitude in radians) coordinates.  If any errors occur, the error 
+ * code(s) are returned by the  function, otherwise USNG_NO_ERROR is returned.  
+ *
+ *    USNG       : USNG coordinate string           (input)
+ *    Latitude   : Latitude in radians              (output)
+ *    Longitude  : Longitude in radians             (output)
+ *  
+ */
+
+
+  long Convert_UTM_To_USNG (long Zone,
+                            char Hemisphere,
+                            double Easting,
+                            double Northing,
+                            long Precision,
+                            char *USNG);
+/*
+ * The function Convert_UTM_To_USNG converts UTM (zone, easting, and
+ * northing) coordinates to a USNG coordinate string, according to the 
+ * current ellipsoid parameters.  If any errors occur, the error code(s) 
+ * are returned by the  function, otherwise USNG_NO_ERROR is returned.
+ *
+ *    Zone       : UTM zone                         (input)
+ *    Hemisphere : North or South hemisphere        (input)
+ *    Easting    : Easting (X) in meters            (input)
+ *    Northing   : Northing (Y) in meters           (input)
+ *    Precision  : Precision level of USNG string   (input)
+ *    USNG       : USNG coordinate string           (output)
+ */
+
+
+  long Convert_USNG_To_UTM (char   *USNG,
+                            long   *Zone,
+                            char   *Hemisphere,
+                            double *Easting,
+                            double *Northing); 
+/*
+ * The function Convert_USNG_To_UTM converts a USNG coordinate string
+ * to UTM projection (zone, hemisphere, easting and northing) coordinates 
+ * according to the current ellipsoid parameters.  If any errors occur, 
+ * the error code(s) are returned by the function, otherwise UTM_NO_ERROR 
+ * is returned.
+ *
+ *    USNG       : USNG coordinate string           (input)
+ *    Zone       : UTM zone                         (output)
+ *    Hemisphere : North or South hemisphere        (output)
+ *    Easting    : Easting (X) in meters            (output)
+ *    Northing   : Northing (Y) in meters           (output)
+ */
+
+
+
+  long Convert_UPS_To_USNG ( char   Hemisphere,
+                             double Easting,
+                             double Northing,
+                             long Precision,
+                             char *USNG);
+
+/*
+ *  The function Convert_UPS_To_USNG converts UPS (hemisphere, easting, 
+ *  and northing) coordinates to a USNG coordinate string according to 
+ *  the current ellipsoid parameters.  If any errors occur, the error
+ *  code(s) are returned by the function, otherwise UPS_NO_ERROR is 
+ *  returned.
+ *
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (input)
+ *    Easting       : Easting/X in meters              (input)
+ *    Northing      : Northing/Y in meters             (input)
+ *    Precision     : Precision level of USNG string   (input)
+ *    USNG          : USNG coordinate string           (output)
+ */
+
+
+  long Convert_USNG_To_UPS ( char   *USNG,
+                             char   *Hemisphere,
+                             double *Easting,
+                             double *Northing);
+/*
+ *  The function Convert_USNG_To_UPS converts a USNG coordinate string
+ *  to UPS (hemisphere, easting, and northing) coordinates, according 
+ *  to the current ellipsoid parameters. If any errors occur, the error 
+ *  code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
+ *
+ *    USNG          : USNG coordinate string           (input)
+ *    Hemisphere    : Hemisphere either 'N' or 'S'     (output)
+ *    Easting       : Easting/X in meters              (output)
+ *    Northing      : Northing/Y in meters             (output)
+ */
+
+
+
+  #ifdef __cplusplus
+}
+  #endif
+
+#endif /* USNG_H */
diff --git a/geotranz/utm.c b/geotranz/utm.c
old mode 100755
new mode 100644
index dca41bb..81c0f46
--- a/geotranz/utm.c
+++ b/geotranz/utm.c
@@ -1,354 +1,354 @@
-/***************************************************************************/
-/* RSC IDENTIFIER: UTM
- *
- * ABSTRACT
- *
- *    This component provides conversions between geodetic coordinates 
- *    (latitude and longitudes) and Universal Transverse Mercator (UTM)
- *    projection (zone, hemisphere, easting, and northing) coordinates.
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found, the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *          UTM_NO_ERROR           : No errors occurred in function
- *          UTM_LAT_ERROR          : Latitude outside of valid range
- *                                    (-80.5 to 84.5 degrees)
- *          UTM_LON_ERROR          : Longitude outside of valid range
- *                                    (-180 to 360 degrees)
- *          UTM_EASTING_ERROR      : Easting outside of valid range
- *                                    (100,000 to 900,000 meters)
- *          UTM_NORTHING_ERROR     : Northing outside of valid range
- *                                    (0 to 10,000,000 meters)
- *          UTM_ZONE_ERROR         : Zone outside of valid range (1 to 60)
- *          UTM_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
- *          UTM_ZONE_OVERRIDE_ERROR: Zone outside of valid range
- *                                    (1 to 60) and within 1 of 'natural' zone
- *          UTM_A_ERROR            : Semi-major axis less than or equal to zero
- *          UTM_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	                (250 to 350)
- *
- * REUSE NOTES
- *
- *    UTM is intended for reuse by any application that performs a Universal
- *    Transverse Mercator (UTM) projection or its inverse.
- *    
- * REFERENCES
- *
- *    Further information on UTM can be found in the Reuse Manual.
- *
- *    UTM originated from :  U.S. Army Topographic Engineering Center
- *                           Geospatial Information Division
- *                           7701 Telegraph Road
- *                           Alexandria, VA  22310-3864
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *    UTM has no restrictions.
- *
- * ENVIRONMENT
- *
- *    UTM was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC, version 2.8.1
- *    2. MSDOS with MS Visual C++, version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    10-02-97          Original Code
- *
- */
-
-
-/***************************************************************************/
-/*
- *                              INCLUDES
- */
-#include "tranmerc.h"
-#include "utm.h"
-/*
- *    tranmerc.h    - Is used to convert transverse mercator coordinates
- *    utm.h         - Defines the function prototypes for the utm module.
- */
-
-
-/***************************************************************************/
-/*
- *                              DEFINES
- */
-
-#define PI           3.14159265358979323e0    /* PI                        */
-#define MIN_LAT      ( (-80.5 * PI) / 180.0 ) /* -80.5 degrees in radians    */
-#define MAX_LAT      ( (84.5 * PI) / 180.0 )  /* 84.5 degrees in radians     */
-#define MIN_EASTING  100000
-#define MAX_EASTING  900000
-#define MIN_NORTHING 0
-#define MAX_NORTHING 10000000
-
-/***************************************************************************/
-/*
- *                              GLOBAL DECLARATIONS
- */
-
-static double UTM_a = 6378137.0;         /* Semi-major axis of ellipsoid in meters  */
-static double UTM_f = 1 / 298.257223563; /* Flattening of ellipsoid                 */
-static long   UTM_Override = 0;          /* Zone override flag                      */
-
-
-/***************************************************************************/
-/*
- *                                FUNCTIONS
- *
- */
-
-long Set_UTM_Parameters(double a,      
-                        double f,
-                        long   override)
-{
-/*
- * The function Set_UTM_Parameters receives the ellipsoid parameters and
- * UTM zone override parameter as inputs, and sets the corresponding state
- * variables.  If any errors occur, the error code(s) are returned by the 
- * function, otherwise UTM_NO_ERROR is returned.
- *
- *    a                 : Semi-major axis of ellipsoid, in meters       (input)
- *    f                 : Flattening of ellipsoid						            (input)
- *    override          : UTM override zone, zero indicates no override (input)
- */
-
-  double inv_f = 1 / f;
-  long Error_Code = UTM_NO_ERROR;
-
-  if (a <= 0.0)
-  { /* Semi-major axis must be greater than zero */
-    Error_Code |= UTM_A_ERROR;
-  }
-  if ((inv_f < 250) || (inv_f > 350))
-  { /* Inverse flattening must be between 250 and 350 */
-    Error_Code |= UTM_INV_F_ERROR;
-  }
-  if ((override < 0) || (override > 60))
-  {
-    Error_Code |= UTM_ZONE_OVERRIDE_ERROR;
-  }
-  if (!Error_Code)
-  { /* no errors */
-    UTM_a = a;
-    UTM_f = f;
-    UTM_Override = override;
-  }
-  return (Error_Code);
-} /* END OF Set_UTM_Parameters */
-
-
-void Get_UTM_Parameters(double *a,
-                        double *f,
-                        long   *override)
-{
-/*
- * The function Get_UTM_Parameters returns the current ellipsoid
- * parameters and UTM zone override parameter.
- *
- *    a                 : Semi-major axis of ellipsoid, in meters       (output)
- *    f                 : Flattening of ellipsoid						            (output)
- *    override          : UTM override zone, zero indicates no override (output)
- */
-
-  *a = UTM_a;
-  *f = UTM_f;
-  *override = UTM_Override;
-} /* END OF Get_UTM_Parameters */
-
-
-long Convert_Geodetic_To_UTM (double Latitude,
-                              double Longitude,
-                              long   *Zone,
-                              char   *Hemisphere,
-                              double *Easting,
-                              double *Northing)
-{ 
-/*
- * The function Convert_Geodetic_To_UTM converts geodetic (latitude and
- * longitude) coordinates to UTM projection (zone, hemisphere, easting and
- * northing) coordinates according to the current ellipsoid and UTM zone
- * override parameters.  If any errors occur, the error code(s) are returned
- * by the function, otherwise UTM_NO_ERROR is returned.
- *
- *    Latitude          : Latitude in radians                 (input)
- *    Longitude         : Longitude in radians                (input)
- *    Zone              : UTM zone                            (output)
- *    Hemisphere        : North or South hemisphere           (output)
- *    Easting           : Easting (X) in meters               (output)
- *    Northing          : Northing (Y) in meters              (output)
- */
-
-  long Lat_Degrees;
-  long Long_Degrees;
-  long temp_zone;
-  long Error_Code = UTM_NO_ERROR;
-  double Origin_Latitude = 0;
-  double Central_Meridian = 0;
-  double False_Easting = 500000;
-  double False_Northing = 0;
-  double Scale = 0.9996;
-
-  if ((Latitude < MIN_LAT) || (Latitude > MAX_LAT))
-  { /* Latitude out of range */
-    Error_Code |= UTM_LAT_ERROR;
-  }
-  if ((Longitude < -PI) || (Longitude > (2*PI)))
-  { /* Longitude out of range */
-    Error_Code |= UTM_LON_ERROR;
-  }
-  if (!Error_Code)
-  { /* no errors */
-    if((Latitude > -1.0e-9) && (Latitude < 0))
-      Latitude = 0.0;
-    if (Longitude < 0)
-      Longitude += (2*PI) + 1.0e-10;
-
-    Lat_Degrees = (long)(Latitude * 180.0 / PI);
-    Long_Degrees = (long)(Longitude * 180.0 / PI);
-
-    if (Longitude < PI)
-      temp_zone = (long)(31 + ((Longitude * 180.0 / PI) / 6.0));
-    else
-      temp_zone = (long)(((Longitude * 180.0 / PI) / 6.0) - 29);
-
-    if (temp_zone > 60)
-      temp_zone = 1;
-    /* UTM special cases */
-    if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > -1)
-        && (Long_Degrees < 3))
-      temp_zone = 31;
-    if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > 2)
-        && (Long_Degrees < 12))
-      temp_zone = 32;
-    if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 9))
-      temp_zone = 31;
-    if ((Lat_Degrees > 71) && (Long_Degrees > 8) && (Long_Degrees < 21))
-      temp_zone = 33;
-    if ((Lat_Degrees > 71) && (Long_Degrees > 20) && (Long_Degrees < 33))
-      temp_zone = 35;
-    if ((Lat_Degrees > 71) && (Long_Degrees > 32) && (Long_Degrees < 42))
-      temp_zone = 37;
-
-    if (UTM_Override)
-    {
-      if ((temp_zone == 1) && (UTM_Override == 60))
-        temp_zone = UTM_Override;
-      else if ((temp_zone == 60) && (UTM_Override == 1))
-        temp_zone = UTM_Override;
-      else if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 42))
-      {
-        if (((temp_zone-2) <= UTM_Override) && (UTM_Override <= (temp_zone+2)))
-          temp_zone = UTM_Override;
-        else
-          Error_Code = UTM_ZONE_OVERRIDE_ERROR;
-      }
-      else if (((temp_zone-1) <= UTM_Override) && (UTM_Override <= (temp_zone+1)))
-        temp_zone = UTM_Override;
-      else
-        Error_Code = UTM_ZONE_OVERRIDE_ERROR;
-    }
-    if (!Error_Code)
-    {
-      if (temp_zone >= 31)
-        Central_Meridian = (6 * temp_zone - 183) * PI / 180.0;
-      else
-        Central_Meridian = (6 * temp_zone + 177) * PI / 180.0;
-      *Zone = temp_zone;
-      if (Latitude < 0)
-      {
-        False_Northing = 10000000;
-        *Hemisphere = 'S';
-      }
-      else
-        *Hemisphere = 'N';
-      Set_Transverse_Mercator_Parameters(UTM_a, UTM_f, Origin_Latitude,
-                                         Central_Meridian, False_Easting, False_Northing, Scale);
-      Convert_Geodetic_To_Transverse_Mercator(Latitude, Longitude, Easting,
-                                              Northing);
-      if ((*Easting < MIN_EASTING) || (*Easting > MAX_EASTING))
-        Error_Code = UTM_EASTING_ERROR;
-      if ((*Northing < MIN_NORTHING) || (*Northing > MAX_NORTHING))
-        Error_Code |= UTM_NORTHING_ERROR;
-    }
-  } /* END OF if (!Error_Code) */
-  return (Error_Code);
-} /* END OF Convert_Geodetic_To_UTM */
-
-
-long Convert_UTM_To_Geodetic(long   Zone,
-                             char   Hemisphere,
-                             double Easting,
-                             double Northing,
-                             double *Latitude,
-                             double *Longitude)
-{
-/*
- * The function Convert_UTM_To_Geodetic converts UTM projection (zone, 
- * hemisphere, easting and northing) coordinates to geodetic(latitude
- * and  longitude) coordinates, according to the current ellipsoid
- * parameters.  If any errors occur, the error code(s) are returned
- * by the function, otherwise UTM_NO_ERROR is returned.
- *
- *    Zone              : UTM zone                               (input)
- *    Hemisphere        : North or South hemisphere              (input)
- *    Easting           : Easting (X) in meters                  (input)
- *    Northing          : Northing (Y) in meters                 (input)
- *    Latitude          : Latitude in radians                    (output)
- *    Longitude         : Longitude in radians                   (output)
- */
-  long Error_Code = UTM_NO_ERROR;
-  long tm_error_code = UTM_NO_ERROR;
-  double Origin_Latitude = 0;
-  double Central_Meridian = 0;
-  double False_Easting = 500000;
-  double False_Northing = 0;
-  double Scale = 0.9996;
-
-  if ((Zone < 1) || (Zone > 60))
-    Error_Code |= UTM_ZONE_ERROR;
-  if ((Hemisphere != 'S') && (Hemisphere != 'N'))
-    Error_Code |= UTM_HEMISPHERE_ERROR;
-  if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
-    Error_Code |= UTM_EASTING_ERROR;
-  if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
-    Error_Code |= UTM_NORTHING_ERROR;
-  if (!Error_Code)
-  { /* no errors */
-    if (Zone >= 31)
-      Central_Meridian = ((6 * Zone - 183) * PI / 180.0 /*+ 0.00000005*/);
-    else
-      Central_Meridian = ((6 * Zone + 177) * PI / 180.0 /*+ 0.00000005*/);
-    if (Hemisphere == 'S')
-      False_Northing = 10000000;
-    Set_Transverse_Mercator_Parameters(UTM_a, UTM_f, Origin_Latitude,
-                                       Central_Meridian, False_Easting, False_Northing, Scale);
-
-    tm_error_code = Convert_Transverse_Mercator_To_Geodetic(Easting, Northing, Latitude, Longitude);
-    if(tm_error_code)
-    {
-      if(tm_error_code & TRANMERC_EASTING_ERROR)
-        Error_Code |= UTM_EASTING_ERROR;
-      if(tm_error_code & TRANMERC_NORTHING_ERROR)
-        Error_Code |= UTM_NORTHING_ERROR;
-    }
-
-    if ((*Latitude < MIN_LAT) || (*Latitude > MAX_LAT))
-    { /* Latitude out of range */
-      Error_Code |= UTM_NORTHING_ERROR;
-    }
-  }
-  return (Error_Code);
-} /* END OF Convert_UTM_To_Geodetic */
+/***************************************************************************/
+/* RSC IDENTIFIER: UTM
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between geodetic coordinates 
+ *    (latitude and longitudes) and Universal Transverse Mercator (UTM)
+ *    projection (zone, hemisphere, easting, and northing) coordinates.
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found, the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *          UTM_NO_ERROR           : No errors occurred in function
+ *          UTM_LAT_ERROR          : Latitude outside of valid range
+ *                                    (-80.5 to 84.5 degrees)
+ *          UTM_LON_ERROR          : Longitude outside of valid range
+ *                                    (-180 to 360 degrees)
+ *          UTM_EASTING_ERROR      : Easting outside of valid range
+ *                                    (100,000 to 900,000 meters)
+ *          UTM_NORTHING_ERROR     : Northing outside of valid range
+ *                                    (0 to 10,000,000 meters)
+ *          UTM_ZONE_ERROR         : Zone outside of valid range (1 to 60)
+ *          UTM_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
+ *          UTM_ZONE_OVERRIDE_ERROR: Zone outside of valid range
+ *                                    (1 to 60) and within 1 of 'natural' zone
+ *          UTM_A_ERROR            : Semi-major axis less than or equal to zero
+ *          UTM_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	                (250 to 350)
+ *
+ * REUSE NOTES
+ *
+ *    UTM is intended for reuse by any application that performs a Universal
+ *    Transverse Mercator (UTM) projection or its inverse.
+ *    
+ * REFERENCES
+ *
+ *    Further information on UTM can be found in the Reuse Manual.
+ *
+ *    UTM originated from :  U.S. Army Topographic Engineering Center
+ *                           Geospatial Information Division
+ *                           7701 Telegraph Road
+ *                           Alexandria, VA  22310-3864
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *    UTM has no restrictions.
+ *
+ * ENVIRONMENT
+ *
+ *    UTM was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC, version 2.8.1
+ *    2. MSDOS with MS Visual C++, version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    10-02-97          Original Code
+ *
+ */
+
+
+/***************************************************************************/
+/*
+ *                              INCLUDES
+ */
+#include "tranmerc.h"
+#include "utm.h"
+/*
+ *    tranmerc.h    - Is used to convert transverse mercator coordinates
+ *    utm.h         - Defines the function prototypes for the utm module.
+ */
+
+
+/***************************************************************************/
+/*
+ *                              DEFINES
+ */
+
+#define PI           3.14159265358979323e0    /* PI                        */
+#define MIN_LAT      ( (-80.5 * PI) / 180.0 ) /* -80.5 degrees in radians    */
+#define MAX_LAT      ( (84.5 * PI) / 180.0 )  /* 84.5 degrees in radians     */
+#define MIN_EASTING  100000
+#define MAX_EASTING  900000
+#define MIN_NORTHING 0
+#define MAX_NORTHING 10000000
+
+/***************************************************************************/
+/*
+ *                              GLOBAL DECLARATIONS
+ */
+
+static double UTM_a = 6378137.0;         /* Semi-major axis of ellipsoid in meters  */
+static double UTM_f = 1 / 298.257223563; /* Flattening of ellipsoid                 */
+static long   UTM_Override = 0;          /* Zone override flag                      */
+
+
+/***************************************************************************/
+/*
+ *                                FUNCTIONS
+ *
+ */
+
+long Set_UTM_Parameters(double a,      
+                        double f,
+                        long   override)
+{
+/*
+ * The function Set_UTM_Parameters receives the ellipsoid parameters and
+ * UTM zone override parameter as inputs, and sets the corresponding state
+ * variables.  If any errors occur, the error code(s) are returned by the 
+ * function, otherwise UTM_NO_ERROR is returned.
+ *
+ *    a                 : Semi-major axis of ellipsoid, in meters       (input)
+ *    f                 : Flattening of ellipsoid						            (input)
+ *    override          : UTM override zone, zero indicates no override (input)
+ */
+
+  double inv_f = 1 / f;
+  long Error_Code = UTM_NO_ERROR;
+
+  if (a <= 0.0)
+  { /* Semi-major axis must be greater than zero */
+    Error_Code |= UTM_A_ERROR;
+  }
+  if ((inv_f < 250) || (inv_f > 350))
+  { /* Inverse flattening must be between 250 and 350 */
+    Error_Code |= UTM_INV_F_ERROR;
+  }
+  if ((override < 0) || (override > 60))
+  {
+    Error_Code |= UTM_ZONE_OVERRIDE_ERROR;
+  }
+  if (!Error_Code)
+  { /* no errors */
+    UTM_a = a;
+    UTM_f = f;
+    UTM_Override = override;
+  }
+  return (Error_Code);
+} /* END OF Set_UTM_Parameters */
+
+
+void Get_UTM_Parameters(double *a,
+                        double *f,
+                        long   *override)
+{
+/*
+ * The function Get_UTM_Parameters returns the current ellipsoid
+ * parameters and UTM zone override parameter.
+ *
+ *    a                 : Semi-major axis of ellipsoid, in meters       (output)
+ *    f                 : Flattening of ellipsoid						            (output)
+ *    override          : UTM override zone, zero indicates no override (output)
+ */
+
+  *a = UTM_a;
+  *f = UTM_f;
+  *override = UTM_Override;
+} /* END OF Get_UTM_Parameters */
+
+
+long Convert_Geodetic_To_UTM (double Latitude,
+                              double Longitude,
+                              long   *Zone,
+                              char   *Hemisphere,
+                              double *Easting,
+                              double *Northing)
+{ 
+/*
+ * The function Convert_Geodetic_To_UTM converts geodetic (latitude and
+ * longitude) coordinates to UTM projection (zone, hemisphere, easting and
+ * northing) coordinates according to the current ellipsoid and UTM zone
+ * override parameters.  If any errors occur, the error code(s) are returned
+ * by the function, otherwise UTM_NO_ERROR is returned.
+ *
+ *    Latitude          : Latitude in radians                 (input)
+ *    Longitude         : Longitude in radians                (input)
+ *    Zone              : UTM zone                            (output)
+ *    Hemisphere        : North or South hemisphere           (output)
+ *    Easting           : Easting (X) in meters               (output)
+ *    Northing          : Northing (Y) in meters              (output)
+ */
+
+  long Lat_Degrees;
+  long Long_Degrees;
+  long temp_zone;
+  long Error_Code = UTM_NO_ERROR;
+  double Origin_Latitude = 0;
+  double Central_Meridian = 0;
+  double False_Easting = 500000;
+  double False_Northing = 0;
+  double Scale = 0.9996;
+
+  if ((Latitude < MIN_LAT) || (Latitude > MAX_LAT))
+  { /* Latitude out of range */
+    Error_Code |= UTM_LAT_ERROR;
+  }
+  if ((Longitude < -PI) || (Longitude > (2*PI)))
+  { /* Longitude out of range */
+    Error_Code |= UTM_LON_ERROR;
+  }
+  if (!Error_Code)
+  { /* no errors */
+    if((Latitude > -1.0e-9) && (Latitude < 0))
+      Latitude = 0.0;
+    if (Longitude < 0)
+      Longitude += (2*PI) + 1.0e-10;
+
+    Lat_Degrees = (long)(Latitude * 180.0 / PI);
+    Long_Degrees = (long)(Longitude * 180.0 / PI);
+
+    if (Longitude < PI)
+      temp_zone = (long)(31 + ((Longitude * 180.0 / PI) / 6.0));
+    else
+      temp_zone = (long)(((Longitude * 180.0 / PI) / 6.0) - 29);
+
+    if (temp_zone > 60)
+      temp_zone = 1;
+    /* UTM special cases */
+    if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > -1)
+        && (Long_Degrees < 3))
+      temp_zone = 31;
+    if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > 2)
+        && (Long_Degrees < 12))
+      temp_zone = 32;
+    if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 9))
+      temp_zone = 31;
+    if ((Lat_Degrees > 71) && (Long_Degrees > 8) && (Long_Degrees < 21))
+      temp_zone = 33;
+    if ((Lat_Degrees > 71) && (Long_Degrees > 20) && (Long_Degrees < 33))
+      temp_zone = 35;
+    if ((Lat_Degrees > 71) && (Long_Degrees > 32) && (Long_Degrees < 42))
+      temp_zone = 37;
+
+    if (UTM_Override)
+    {
+      if ((temp_zone == 1) && (UTM_Override == 60))
+        temp_zone = UTM_Override;
+      else if ((temp_zone == 60) && (UTM_Override == 1))
+        temp_zone = UTM_Override;
+      else if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 42))
+      {
+        if (((temp_zone-2) <= UTM_Override) && (UTM_Override <= (temp_zone+2)))
+          temp_zone = UTM_Override;
+        else
+          Error_Code = UTM_ZONE_OVERRIDE_ERROR;
+      }
+      else if (((temp_zone-1) <= UTM_Override) && (UTM_Override <= (temp_zone+1)))
+        temp_zone = UTM_Override;
+      else
+        Error_Code = UTM_ZONE_OVERRIDE_ERROR;
+    }
+    if (!Error_Code)
+    {
+      if (temp_zone >= 31)
+        Central_Meridian = (6 * temp_zone - 183) * PI / 180.0;
+      else
+        Central_Meridian = (6 * temp_zone + 177) * PI / 180.0;
+      *Zone = temp_zone;
+      if (Latitude < 0)
+      {
+        False_Northing = 10000000;
+        *Hemisphere = 'S';
+      }
+      else
+        *Hemisphere = 'N';
+      Set_Transverse_Mercator_Parameters(UTM_a, UTM_f, Origin_Latitude,
+                                         Central_Meridian, False_Easting, False_Northing, Scale);
+      Convert_Geodetic_To_Transverse_Mercator(Latitude, Longitude, Easting,
+                                              Northing);
+      if ((*Easting < MIN_EASTING) || (*Easting > MAX_EASTING))
+        Error_Code = UTM_EASTING_ERROR;
+      if ((*Northing < MIN_NORTHING) || (*Northing > MAX_NORTHING))
+        Error_Code |= UTM_NORTHING_ERROR;
+    }
+  } /* END OF if (!Error_Code) */
+  return (Error_Code);
+} /* END OF Convert_Geodetic_To_UTM */
+
+
+long Convert_UTM_To_Geodetic(long   Zone,
+                             char   Hemisphere,
+                             double Easting,
+                             double Northing,
+                             double *Latitude,
+                             double *Longitude)
+{
+/*
+ * The function Convert_UTM_To_Geodetic converts UTM projection (zone, 
+ * hemisphere, easting and northing) coordinates to geodetic(latitude
+ * and  longitude) coordinates, according to the current ellipsoid
+ * parameters.  If any errors occur, the error code(s) are returned
+ * by the function, otherwise UTM_NO_ERROR is returned.
+ *
+ *    Zone              : UTM zone                               (input)
+ *    Hemisphere        : North or South hemisphere              (input)
+ *    Easting           : Easting (X) in meters                  (input)
+ *    Northing          : Northing (Y) in meters                 (input)
+ *    Latitude          : Latitude in radians                    (output)
+ *    Longitude         : Longitude in radians                   (output)
+ */
+  long Error_Code = UTM_NO_ERROR;
+  long tm_error_code = UTM_NO_ERROR;
+  double Origin_Latitude = 0;
+  double Central_Meridian = 0;
+  double False_Easting = 500000;
+  double False_Northing = 0;
+  double Scale = 0.9996;
+
+  if ((Zone < 1) || (Zone > 60))
+    Error_Code |= UTM_ZONE_ERROR;
+  if ((Hemisphere != 'S') && (Hemisphere != 'N'))
+    Error_Code |= UTM_HEMISPHERE_ERROR;
+  if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
+    Error_Code |= UTM_EASTING_ERROR;
+  if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
+    Error_Code |= UTM_NORTHING_ERROR;
+  if (!Error_Code)
+  { /* no errors */
+    if (Zone >= 31)
+      Central_Meridian = ((6 * Zone - 183) * PI / 180.0 /*+ 0.00000005*/);
+    else
+      Central_Meridian = ((6 * Zone + 177) * PI / 180.0 /*+ 0.00000005*/);
+    if (Hemisphere == 'S')
+      False_Northing = 10000000;
+    Set_Transverse_Mercator_Parameters(UTM_a, UTM_f, Origin_Latitude,
+                                       Central_Meridian, False_Easting, False_Northing, Scale);
+
+    tm_error_code = Convert_Transverse_Mercator_To_Geodetic(Easting, Northing, Latitude, Longitude);
+    if(tm_error_code)
+    {
+      if(tm_error_code & TRANMERC_EASTING_ERROR)
+        Error_Code |= UTM_EASTING_ERROR;
+      if(tm_error_code & TRANMERC_NORTHING_ERROR)
+        Error_Code |= UTM_NORTHING_ERROR;
+    }
+
+    if ((*Latitude < MIN_LAT) || (*Latitude > MAX_LAT))
+    { /* Latitude out of range */
+      Error_Code |= UTM_NORTHING_ERROR;
+    }
+  }
+  return (Error_Code);
+} /* END OF Convert_UTM_To_Geodetic */
diff --git a/geotranz/utm.h b/geotranz/utm.h
old mode 100755
new mode 100644
index 1f51380..0b32051
--- a/geotranz/utm.h
+++ b/geotranz/utm.h
@@ -1,178 +1,178 @@
-#ifndef UTM_H
-  #define UTM_H
-
-/***************************************************************************/
-/* RSC IDENTIFIER: UTM
- *
- * ABSTRACT
- *
- *    This component provides conversions between geodetic coordinates 
- *    (latitude and longitudes) and Universal Transverse Mercator (UTM)
- *    projection (zone, hemisphere, easting, and northing) coordinates.
- *
- * ERROR HANDLING
- *
- *    This component checks parameters for valid values.  If an invalid value
- *    is found, the error code is combined with the current error code using 
- *    the bitwise or.  This combining allows multiple error codes to be
- *    returned. The possible error codes are:
- *
- *          UTM_NO_ERROR           : No errors occurred in function
- *          UTM_LAT_ERROR          : Latitude outside of valid range
- *                                    (-80.5 to 84.5 degrees)
- *          UTM_LON_ERROR          : Longitude outside of valid range
- *                                    (-180 to 360 degrees)
- *          UTM_EASTING_ERROR      : Easting outside of valid range
- *                                    (100,000 to 900,000 meters)
- *          UTM_NORTHING_ERROR     : Northing outside of valid range
- *                                    (0 to 10,000,000 meters)
- *          UTM_ZONE_ERROR         : Zone outside of valid range (1 to 60)
- *          UTM_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
- *          UTM_ZONE_OVERRIDE_ERROR: Zone outside of valid range
- *                                    (1 to 60) and within 1 of 'natural' zone
- *          UTM_A_ERROR            : Semi-major axis less than or equal to zero
- *          UTM_INV_F_ERROR        : Inverse flattening outside of valid range
- *								  	                (250 to 350)
- *
- * REUSE NOTES
- *
- *    UTM is intended for reuse by any application that performs a Universal
- *    Transverse Mercator (UTM) projection or its inverse.
- *    
- * REFERENCES
- *
- *    Further information on UTM can be found in the Reuse Manual.
- *
- *    UTM originated from :  U.S. Army Topographic Engineering Center
- *                           Geospatial Information Division
- *                           7701 Telegraph Road
- *                           Alexandria, VA  22310-3864
- *
- * LICENSES
- *
- *    None apply to this component.
- *
- * RESTRICTIONS
- *
- *    UTM has no restrictions.
- *
- * ENVIRONMENT
- *
- *    UTM was tested and certified in the following environments:
- *
- *    1. Solaris 2.5 with GCC, version 2.8.1
- *    2. MSDOS with MS Visual C++, version 6
- *
- * MODIFICATIONS
- *
- *    Date              Description
- *    ----              -----------
- *    10-02-97          Original Code
- *
- */
-
-
-/***************************************************************************/
-/*
- *                              DEFINES
- */
-
-  #define UTM_NO_ERROR            0x0000
-  #define UTM_LAT_ERROR           0x0001
-  #define UTM_LON_ERROR           0x0002
-  #define UTM_EASTING_ERROR       0x0004
-  #define UTM_NORTHING_ERROR      0x0008
-  #define UTM_ZONE_ERROR          0x0010
-  #define UTM_HEMISPHERE_ERROR    0x0020
-  #define UTM_ZONE_OVERRIDE_ERROR 0x0040
-  #define UTM_A_ERROR             0x0080
-  #define UTM_INV_F_ERROR         0x0100
-
-
-/***************************************************************************/
-/*
- *                              FUNCTION PROTOTYPES
- *                                for UTM.C
- */
-
-/* ensure proper linkage to c++ programs */
-  #ifdef __cplusplus
-extern "C" {
-  #endif
-
-  long Set_UTM_Parameters(double a,      
-                          double f,
-                          long   override);
-/*
- * The function Set_UTM_Parameters receives the ellipsoid parameters and
- * UTM zone override parameter as inputs, and sets the corresponding state
- * variables.  If any errors occur, the error code(s) are returned by the 
- * function, otherwise UTM_NO_ERROR is returned.
- *
- *    a                 : Semi-major axis of ellipsoid, in meters       (input)
- *    f                 : Flattening of ellipsoid                       (input)
- *    override          : UTM override zone, zero indicates no override (input)
- */
-
-
-  void Get_UTM_Parameters(double *a,
-                          double *f,
-                          long   *override);
-/*
- * The function Get_UTM_Parameters returns the current ellipsoid
- * parameters and UTM zone override parameter.
- *
- *    a                 : Semi-major axis of ellipsoid, in meters       (output)
- *    f                 : Flattening of ellipsoid                       (output)
- *    override          : UTM override zone, zero indicates no override (output)
- */
-
-
-  long Convert_Geodetic_To_UTM (double Latitude,
-                                double Longitude,
-                                long   *Zone,
-                                char   *Hemisphere,
-                                double *Easting,
-                                double *Northing); 
-/*
- * The function Convert_Geodetic_To_UTM converts geodetic (latitude and
- * longitude) coordinates to UTM projection (zone, hemisphere, easting and
- * northing) coordinates according to the current ellipsoid and UTM zone
- * override parameters.  If any errors occur, the error code(s) are returned
- * by the function, otherwise UTM_NO_ERROR is returned.
- *
- *    Latitude          : Latitude in radians                 (input)
- *    Longitude         : Longitude in radians                (input)
- *    Zone              : UTM zone                            (output)
- *    Hemisphere        : North or South hemisphere           (output)
- *    Easting           : Easting (X) in meters               (output)
- *    Northing          : Northing (Y) in meters              (output)
- */
-
-
-  long Convert_UTM_To_Geodetic(long   Zone,
-                               char   Hemisphere,
-                               double Easting,
-                               double Northing,
-                               double *Latitude,
-                               double *Longitude);
-/*
- * The function Convert_UTM_To_Geodetic converts UTM projection (zone, 
- * hemisphere, easting and northing) coordinates to geodetic(latitude
- * and  longitude) coordinates, according to the current ellipsoid
- * parameters.  If any errors occur, the error code(s) are returned
- * by the function, otherwise UTM_NO_ERROR is returned.
- *
- *    Zone              : UTM zone                               (input)
- *    Hemisphere        : North or South hemisphere              (input)
- *    Easting           : Easting (X) in meters                  (input)
- *    Northing          : Northing (Y) in meters                 (input)
- *    Latitude          : Latitude in radians                    (output)
- *    Longitude         : Longitude in radians                   (output)
- */
-
-  #ifdef __cplusplus
-}
-  #endif
-
-#endif /* UTM_H */
+#ifndef UTM_H
+  #define UTM_H
+
+/***************************************************************************/
+/* RSC IDENTIFIER: UTM
+ *
+ * ABSTRACT
+ *
+ *    This component provides conversions between geodetic coordinates 
+ *    (latitude and longitudes) and Universal Transverse Mercator (UTM)
+ *    projection (zone, hemisphere, easting, and northing) coordinates.
+ *
+ * ERROR HANDLING
+ *
+ *    This component checks parameters for valid values.  If an invalid value
+ *    is found, the error code is combined with the current error code using 
+ *    the bitwise or.  This combining allows multiple error codes to be
+ *    returned. The possible error codes are:
+ *
+ *          UTM_NO_ERROR           : No errors occurred in function
+ *          UTM_LAT_ERROR          : Latitude outside of valid range
+ *                                    (-80.5 to 84.5 degrees)
+ *          UTM_LON_ERROR          : Longitude outside of valid range
+ *                                    (-180 to 360 degrees)
+ *          UTM_EASTING_ERROR      : Easting outside of valid range
+ *                                    (100,000 to 900,000 meters)
+ *          UTM_NORTHING_ERROR     : Northing outside of valid range
+ *                                    (0 to 10,000,000 meters)
+ *          UTM_ZONE_ERROR         : Zone outside of valid range (1 to 60)
+ *          UTM_HEMISPHERE_ERROR   : Invalid hemisphere ('N' or 'S')
+ *          UTM_ZONE_OVERRIDE_ERROR: Zone outside of valid range
+ *                                    (1 to 60) and within 1 of 'natural' zone
+ *          UTM_A_ERROR            : Semi-major axis less than or equal to zero
+ *          UTM_INV_F_ERROR        : Inverse flattening outside of valid range
+ *								  	                (250 to 350)
+ *
+ * REUSE NOTES
+ *
+ *    UTM is intended for reuse by any application that performs a Universal
+ *    Transverse Mercator (UTM) projection or its inverse.
+ *    
+ * REFERENCES
+ *
+ *    Further information on UTM can be found in the Reuse Manual.
+ *
+ *    UTM originated from :  U.S. Army Topographic Engineering Center
+ *                           Geospatial Information Division
+ *                           7701 Telegraph Road
+ *                           Alexandria, VA  22310-3864
+ *
+ * LICENSES
+ *
+ *    None apply to this component.
+ *
+ * RESTRICTIONS
+ *
+ *    UTM has no restrictions.
+ *
+ * ENVIRONMENT
+ *
+ *    UTM was tested and certified in the following environments:
+ *
+ *    1. Solaris 2.5 with GCC, version 2.8.1
+ *    2. MSDOS with MS Visual C++, version 6
+ *
+ * MODIFICATIONS
+ *
+ *    Date              Description
+ *    ----              -----------
+ *    10-02-97          Original Code
+ *
+ */
+
+
+/***************************************************************************/
+/*
+ *                              DEFINES
+ */
+
+  #define UTM_NO_ERROR            0x0000
+  #define UTM_LAT_ERROR           0x0001
+  #define UTM_LON_ERROR           0x0002
+  #define UTM_EASTING_ERROR       0x0004
+  #define UTM_NORTHING_ERROR      0x0008
+  #define UTM_ZONE_ERROR          0x0010
+  #define UTM_HEMISPHERE_ERROR    0x0020
+  #define UTM_ZONE_OVERRIDE_ERROR 0x0040
+  #define UTM_A_ERROR             0x0080
+  #define UTM_INV_F_ERROR         0x0100
+
+
+/***************************************************************************/
+/*
+ *                              FUNCTION PROTOTYPES
+ *                                for UTM.C
+ */
+
+/* ensure proper linkage to c++ programs */
+  #ifdef __cplusplus
+extern "C" {
+  #endif
+
+  long Set_UTM_Parameters(double a,      
+                          double f,
+                          long   override);
+/*
+ * The function Set_UTM_Parameters receives the ellipsoid parameters and
+ * UTM zone override parameter as inputs, and sets the corresponding state
+ * variables.  If any errors occur, the error code(s) are returned by the 
+ * function, otherwise UTM_NO_ERROR is returned.
+ *
+ *    a                 : Semi-major axis of ellipsoid, in meters       (input)
+ *    f                 : Flattening of ellipsoid                       (input)
+ *    override          : UTM override zone, zero indicates no override (input)
+ */
+
+
+  void Get_UTM_Parameters(double *a,
+                          double *f,
+                          long   *override);
+/*
+ * The function Get_UTM_Parameters returns the current ellipsoid
+ * parameters and UTM zone override parameter.
+ *
+ *    a                 : Semi-major axis of ellipsoid, in meters       (output)
+ *    f                 : Flattening of ellipsoid                       (output)
+ *    override          : UTM override zone, zero indicates no override (output)
+ */
+
+
+  long Convert_Geodetic_To_UTM (double Latitude,
+                                double Longitude,
+                                long   *Zone,
+                                char   *Hemisphere,
+                                double *Easting,
+                                double *Northing); 
+/*
+ * The function Convert_Geodetic_To_UTM converts geodetic (latitude and
+ * longitude) coordinates to UTM projection (zone, hemisphere, easting and
+ * northing) coordinates according to the current ellipsoid and UTM zone
+ * override parameters.  If any errors occur, the error code(s) are returned
+ * by the function, otherwise UTM_NO_ERROR is returned.
+ *
+ *    Latitude          : Latitude in radians                 (input)
+ *    Longitude         : Longitude in radians                (input)
+ *    Zone              : UTM zone                            (output)
+ *    Hemisphere        : North or South hemisphere           (output)
+ *    Easting           : Easting (X) in meters               (output)
+ *    Northing          : Northing (Y) in meters              (output)
+ */
+
+
+  long Convert_UTM_To_Geodetic(long   Zone,
+                               char   Hemisphere,
+                               double Easting,
+                               double Northing,
+                               double *Latitude,
+                               double *Longitude);
+/*
+ * The function Convert_UTM_To_Geodetic converts UTM projection (zone, 
+ * hemisphere, easting and northing) coordinates to geodetic(latitude
+ * and  longitude) coordinates, according to the current ellipsoid
+ * parameters.  If any errors occur, the error code(s) are returned
+ * by the function, otherwise UTM_NO_ERROR is returned.
+ *
+ *    Zone              : UTM zone                               (input)
+ *    Hemisphere        : North or South hemisphere              (input)
+ *    Easting           : Easting (X) in meters                  (input)
+ *    Northing          : Northing (Y) in meters                 (input)
+ *    Latitude          : Latitude in radians                    (output)
+ *    Longitude         : Longitude in radians                   (output)
+ */
+
+  #ifdef __cplusplus
+}
+  #endif
+
+#endif /* UTM_H */
diff --git a/grm_sym.h b/grm_sym.h
index 838a4ef..11fb4fa 100644
--- a/grm_sym.h
+++ b/grm_sym.h
@@ -1,501 +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
-
+
+/*
+ * 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(tm) 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 ec40604..06d1c4b 100644
--- a/hdlc_rec.c
+++ b/hdlc_rec.c
@@ -1,645 +1,652 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	hdlc_rec.c
- *
- * Purpose:	Extract HDLC frames from a stream of bits.
- *
- *******************************************************************************/
-
-#include <stdio.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "demod.h"
-#include "hdlc_rec.h"
-#include "hdlc_rec2.h"
-#include "fcs_calc.h"
-#include "textcolor.h"
-#include "ax25_pad.h"
-#include "rrbb.h"
-#include "multi_modem.h"
-#include "demod_9600.h"		/* for descramble() */
-#include "ptt.h"
-
-
-//#define TEST 1				/* Define for unit testing. */
-
-//#define DEBUG3 1				/* monitor the data detect signal. */
-
-
-
-/* 
- * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS. 
- */
-
-#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
-				
-#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
-
-/*
- * This is the current state of the HDLC decoder.
- *
- * It is possible to run multiple decoders concurrently by
- * having a separate set of state variables for each.
- *
- * Should have a reset function instead of initializations here.
- */
-
-struct hdlc_state_s {
-
-
-	int prev_raw;			/* Keep track of previous bit so */
-					/* we can look for transitions. */
-					/* Should be only 0 or 1. */
-
-	int lfsr;			/* Descrambler shift register for 9600 baud. */
-
-	int prev_descram;		/* Previous descrambled for 9600 baud. */
-
-	unsigned char pat_det; 		/* 8 bit pattern detector shift register. */
-					/* See below for more details. */
-
-	unsigned int flag4_det;		/* Last 32 raw bits to look for 4 */
-					/* flag patterns in a row. */
-
-	unsigned char oacc;		/* Accumulator for building up an octet. */
-
-	int olen;			/* Number of bits in oacc. */
-					/* When this reaches 8, oacc is copied */
-					/* to the frame buffer and olen is zeroed. */
-					/* The value of -1 is a special case meaning */
-					/* bits should not be accumulated. */
-
-	unsigned char frame_buf[MAX_FRAME_LEN];
-					/* One frame is kept here. */
-
-	int frame_len;			/* Number of octets in frame_buf. */
-					/* Should be in range of 0 .. MAX_FRAME_LEN. */
-
-	int data_detect;		/* True when HDLC data is detected. */
-					/* This will not be triggered by voice or other */
-					/* noise or even tones.  */
-
-	rrbb_t rrbb;			/* Handle for bit array for raw received bits. */
-					
-};
-
-
-static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS];
-
-static int num_subchan[MAX_CHANS];		//TODO1.2 use ptr rather than copy.
-
-static int composite_dcd[MAX_CHANS];
-
-static void dcd_change (int chan, int subchan, int state);
-
-
-/***********************************************************************************
- *
- * Name:	hdlc_rec_init
- *
- * Purpose:	Call once at the beginning to initialize.
- *
- * Inputs:	None.
- *
- ***********************************************************************************/
-
-static int was_init = 0;
-
-void hdlc_rec_init (struct audio_s *pa)
-{
-	int j, k;
-	struct hdlc_state_s *H;
-
-	//text_color_set(DW_COLOR_DEBUG);
-	//dw_printf ("hdlc_rec_init (%p) \n", pa);
-
-	assert (pa != NULL);
-	
-	for (j=0; j<MAX_CHANS; j++)
-	{
-	  composite_dcd[j] = 0;
-
-	  if (pa->achan[j].valid) {
-
-	    num_subchan[j] = pa->achan[j].num_subchan;
-
-	    assert (num_subchan[j] >= 1 && num_subchan[j] <= MAX_SUBCHANS);
-
-	    for (k=0; k<MAX_SUBCHANS; k++) 
-	    {
-	      H = &hdlc_state[j][k];
-
-	      H->prev_raw = 0;
-	      H->lfsr = 0;
-	      H->prev_descram = 0;
-	      H->pat_det = 0;
-	      H->flag4_det = 0;
-	      H->olen = -1;
-	      H->frame_len = 0;
-	      H->data_detect = 0;
-		// TODO: wasteful if not needed.
-	      H->rrbb = rrbb_new(j, k, pa->achan[j].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram);
-	    }
-	  }
-	}
-
-	hdlc_rec2_init (pa);
-	was_init = 1;
-}
-
-
-
-/***********************************************************************************
- *
- * Name:	hdlc_rec_bit
- *
- * Purpose:	Extract HDLC frames from a stream of bits.
- *
- * Inputs:	chan	- Channel number.  
- *
- *		subchan	- This allows multiple decoders per channel.
- *
- *		raw 	- One bit from the demodulator.
- *			  should be 0 or 1.
- *	
- *		is_scrambled - Is the data scrambled?
- *
- *		descram_state - Current descrambler state.
- *					
- *
- * Description:	This is called once for each received bit.
- *		For each valid frame, process_rec_frame()
- *		is called for further processing.
- *
- ***********************************************************************************/
-
-// TODO: int not_used_remove
-
-
-void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int not_used_remove)
-
-{
-
-	int dbit;			/* Data bit after undoing NRZI. */
-					/* Should be only 0 or 1. */
-	struct hdlc_state_s *H;
-
-	assert (was_init == 1);
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-/*
- * Different state information for each channel.
- */
-	H = &hdlc_state[chan][subchan];
-
-/*
- * Using NRZI encoding,
- *   A '0' bit is represented by an inversion since previous bit.
- *   A '1' bit is represented by no change.
- */
-
-	if (is_scrambled) {
-	  int descram;
-
-	  descram = descramble(raw, &(H->lfsr));
-
-	  dbit = (descram == H->prev_descram);
-	  H->prev_descram = descram;			
-	  H->prev_raw = raw;	}
-	else {
-
-	  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;
-	}
-
-	H->flag4_det >>= 1;
-	if (dbit) {
-	  H->flag4_det |= 0x80000000;
-	}
-
-
-/*
- * "Data Carrier detect" function based on data patterns rather than
- * audio signal strength.
- *
- * Idle time, at beginning of transmission should be filled
- * with the special "flag" characters.
- *
- * Idle time of all zero bits (alternating tones at maximum rate)
- * has also been observed rarely. 
- * Recognize zero(s) followed by a flag even though it vilolates the spec.
- */
-
-/*
- * Originally, this looked for 4 flags in a row or 3 zeros and a flag. 
- * Is that too fussy?
- * Here are the numbers of start of DCD for our favorite Track 2 test.
- *
- *	7e7e7e7e  504 	7e000000  32  	
- *	7e7e7e--  513   7e0000--  33	
- *	7e7e----  555   7e00----  42	
- *	7e------ 2088
- *					
- * I don't think we want to look for a single flag because that would
- * make DCD too sensitive to noise and it would interfere with waiting for a 
- * clear channel to transmit.  Even a two byte match causes a lot of flickering
- * when listening to live signals.  Let's try 3 and see how that works out.
- */
-
-	//if (H->flag4_det == 0x7e7e7e7e) {
-	if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) {	
-	//if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) {	
-
-	  if ( ! H->data_detect) {
-	    H->data_detect = 1;
-	    dcd_change (chan, subchan, 1);
-	  }
-	}
-	//else if (H->flag4_det == 0x7e000000) {	
-	else if ((H->flag4_det & 0xffffff00) == 0x7e000000) {	
-	//else if ((H->flag4_det & 0xffff0000) == 0x7e000000) {	
-	  
-	  if ( ! H->data_detect) {
-	    H->data_detect = 1;
-	    dcd_change (chan, subchan, 1);
-	  }
-	}
-
-
-/* 
- * Loss of signal should result in lack of transitions.
- * (all '1' bits) for at least a little while.
- */
-
-  
-	if (H->pat_det == 0xff) {	
-	  
-	  if ( H->data_detect ) {
-	    H->data_detect = 0;
-	    dcd_change (chan, subchan, 0);
-	  }
-	}
-
-
-/*
- * End of data carrier detect.  
- * 
- * The rest is concerned with framing.
- */
-
-
-	rrbb_append_bit (H->rrbb, raw);
-
-	if (H->pat_det == 0x7e) {
-
-	  rrbb_chop8 (H->rrbb);
-
-/*
- * The special pattern 01111110 indicates beginning and ending of a frame.  
- * If we have an adequate number of whole octets, it is a candidate for 
- * further processing.
- *
- * It might look odd that olen is being tested for 7 instead of 0.
- * This is because oacc would already have 7 bits from the special
- * "flag" pattern before it is detected here.
- */
-
-
-#if OLD_WAY
-
-#if TEST
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("\nfound flag, olen = %d, frame_len = %d\n", olen, frame_len);
-#endif
-	  if (H->olen == 7 && H->frame_len >= MIN_FRAME_LEN) {
-
-	    unsigned short actual_fcs, expected_fcs;
-
-#if TEST
-	    int j;
-	    dw_printf ("TRADITIONAL: frame len = %d\n", H->frame_len);
-	    for (j=0; j<H->frame_len; j++) {
-	      dw_printf ("  %02x", H->frame_buf[j]);
-	    }
-	    dw_printf ("\n");
-
-#endif
-	    /* Check FCS, low byte first, and process... */
-
-	    /* Alternatively, it is possible to include the two FCS bytes */
-	    /* in the CRC calculation and look for a magic constant.  */
-	    /* That would be easier in the case where the CRC is being */
-	    /* accumulated along the way as the octets are received. */
-	    /* I think making a second pass over it and comparing is */
-	    /* easier to understand. */
-
-	    actual_fcs = H->frame_buf[H->frame_len-2] | (H->frame_buf[H->frame_len-1] << 8);
-
-	    expected_fcs = fcs_calc (H->frame_buf, H->frame_len - 2);
-
-	    if (actual_fcs == expected_fcs) {
-	      alevel_t alevel = demod_get_audio_level (chan, subchan);
-
-	      multi_modem_process_rec_frame (chan, subchan, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE);   /* len-2 to remove FCS. */
-	    }
-	    else {
-
-#if TEST
-	      dw_printf ("*** actual fcs = %04x, expected fcs = %04x ***\n", actual_fcs, expected_fcs);
-#endif
-
-	    }
-
-	  }
-
-#else
-
-/*
- * New way - Decode the raw bits in later step.
- */
-
-#if TEST
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("\nfound flag, channel %d.%d, %d bits in frame\n", chan, subchan, rrbb_get_len(H->rrbb) - 1);
-#endif
-	  if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) {
-		
-	    alevel_t alevel = demod_get_audio_level (chan, subchan);
-
-	    rrbb_set_audio_level (H->rrbb, alevel);
-	    hdlc_rec2_block (H->rrbb);
-	    	/* Now owned by someone else who will free it. */
-	    H->rrbb = rrbb_new (chan, subchan, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */
-	  }
-	  else {
-	    rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); 
-	  }
-
-	  H->olen = 0;		/* Allow accumulation of octets. */
-	  H->frame_len = 0;
-
-
-	  rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag.  Needed to get first data bit. */
-						/* Now that we are saving other initial state information, */
-						/* it would be sensible to do the same for this instead */
-						/* of lumping it in with the frame data bits. */
-#endif
-
-	}
-
-//#define EXPERIMENT12B 1
-
-#if EXPERIMENT12B
-
-	else if (H->pat_det == 0xff) {
-
-/*
- * Valid data will never have seven 1 bits in a row.
- *
- *	11111110
- *
- * This indicates loss of signal.
- * But we will let it slip thru because it might diminish
- * our single bit fixup effort.   Instead give up on frame
- * only when we see eight 1 bits in a row.
- *
- *	11111111
- *
- * What is the impact?  No difference.
- *
- *  Before:	atest -P E -F 1 ../02_Track_2.wav	= 1003
- *  After:	atest -P E -F 1 ../02_Track_2.wav	= 1003
- */
-
-#else
-	else if (H->pat_det == 0xfe) {
-
-/*
- * Valid data will never have 7 one bits in a row.
- *
- *	11111110
- *
- * This indicates loss of signal.
- */
-
-#endif
-
-	  H->olen = -1;		/* Stop accumulating octets. */
-	  H->frame_len = 0;	/* Discard anything in progress. */
-
-	  rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); 
-
-	}
-	else if ( (H->pat_det & 0xfc) == 0x7c ) {
-
-/*
- * If we have five '1' bits in a row, followed by a '0' bit,
- *
- *	0111110xx
- *
- * the current '0' bit should be discarded because it was added for 
- * "bit stuffing."
- */
-	  ;
-
-	} else {
-
-/*
- * In all other cases, accumulate bits into octets, and complete octets
- * into the frame buffer.
- */
-	  if (H->olen >= 0) {
-
-	    H->oacc >>= 1;
-	    if (dbit) {
-	      H->oacc |= 0x80;
-	    }
-	    H->olen++;
-
-	    if (H->olen == 8) {
-	      H->olen = 0;
-
-	      if (H->frame_len < MAX_FRAME_LEN) {
-		H->frame_buf[H->frame_len] = H->oacc;
-		H->frame_len++;
-	      }
-	    }
-	  }
-	}
-}
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        hdlc_rec_gathering
- *
- * Purpose:     Report whether bits are currently being gathered into a frame.
- *		This is used to influence the PLL inertia.
- *		The idea is that the PLL should be a little more agreeable to
- *		synchronize with the incoming data stream when not in a frame
- *		and resist changing a little more when capturing a frame.
- *
- * Inputs:	chan
- *		subchan
- *
- * Returns:	True if we are currently gathering bits.
- *		In this case we want the PLL to have more inertia.
- *
- * Discussion:	Originally I used the data carrier detect.
- *		Later, it seemed like the we should be using "olen>=0" instead.
- *
- *		Seems to make no difference for Track 1 and the original
- *		way was a hair better for Track 2.
- *
- *--------------------------------------------------------------------*/
-
-int hdlc_rec_gathering (int chan, int subchan)
-{
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-	// Counts from 	     Track 1 & Track 2
-	// data_detect		992	988
-	// olen>=0		992	985
-	// OR-ed		992	985
-
-
-	return ( hdlc_state[chan][subchan].data_detect );
-
-	//return ( hdlc_state[chan][subchan].olen >= 0);
-
-	//return ( hdlc_state[chan][subchan].data_detect || hdlc_state[chan][subchan].olen >= 0 );
-
-} /* end hdlc_rec_gathering */
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dcd_change
- *
- * Purpose:     Combine DCD states of all subchannels into an overall
- *		state for the channel.
- *
- * Inputs:	chan	
- *		subchan	
- *		state		1 for active, 0 for not.
- *
- * Returns:	None.  Use ??? to retrieve result.
- *
- * Description:	DCD for the channel is active if ANY of the subchannels
- *		is active.  Update the DCD indicator.
- *
- * Future:	Roll DTMF into the final result.
- *
- *--------------------------------------------------------------------*/
-
-
-static void dcd_change (int chan, int subchan, int state)
-{
-	int old, new;
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-	assert (state == 0 || state == 1);
-
-#if DEBUG3
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("DCD %d.%d = %d \n", chan, subchan, state);
-#endif
-
-	old = hdlc_rec_data_detect_any(chan);
-
-	if (state) {
-	  composite_dcd[chan] |= (1 << subchan);
-	}
-	else {
-	  composite_dcd[chan] &=  ~ (1 << subchan);
-	}
-
-	new = hdlc_rec_data_detect_any(chan);
-
-	if (new != old) {
-	  ptt_set (OCTYPE_DCD, chan, new);
-	}
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        hdlc_rec_data_detect_any
- *
- * Purpose:     Determine if the radio channel is curently busy
- *		with packet data.
- *		This version doesn't care about voice or other sounds.
- *		This is used by the transmit logic to transmit only
- *		when the channel is clear.
- *
- * Inputs:	chan	- Audio channel. 
- *
- * Returns:	True if channel is busy (data detected) or 
- *		false if OK to transmit. 
- *
- *
- * Description:	We have two different versions here.
- *
- *		hdlc_rec_data_detect_any sees if ANY of the decoders
- *		for this channel are receving a signal.   This is
- *		used to determine whether the channel is clear and
- *		we can transmit.  This would apply to the 300 baud
- *		HF SSB case where we have multiple decoders running
- *		at the same time.  The channel is busy if ANY of them
- *		thinks the channel is busy.
- *
- *--------------------------------------------------------------------*/
-
-int hdlc_rec_data_detect_any (int chan)
-{
-	int subchan;
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-
-	return (composite_dcd[chan] != 0);
-
-} /* end hdlc_rec_data_detect_any */
-
-
-/* end hdlc_rec.c */
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	hdlc_rec.c
+ *
+ * Purpose:	Extract HDLC frames from a stream of bits.
+ *
+ *******************************************************************************/
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "demod.h"
+#include "hdlc_rec.h"
+#include "hdlc_rec2.h"
+#include "fcs_calc.h"
+#include "textcolor.h"
+#include "ax25_pad.h"
+#include "rrbb.h"
+#include "multi_modem.h"
+#include "demod_9600.h"		/* for descramble() */
+#include "ptt.h"
+
+
+//#define TEST 1				/* Define for unit testing. */
+
+//#define DEBUG3 1				/* monitor the data detect signal. */
+
+
+
+/* 
+ * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS. 
+ */
+
+#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
+				
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+/*
+ * This is the current state of the HDLC decoder.
+ *
+ * It is possible to run multiple decoders concurrently by
+ * having a separate set of state variables for each.
+ *
+ * Should have a reset function instead of initializations here.
+ */
+
+struct hdlc_state_s {
+
+
+	int prev_raw;			/* Keep track of previous bit so */
+					/* we can look for transitions. */
+					/* Should be only 0 or 1. */
+
+	int lfsr;			/* Descrambler shift register for 9600 baud. */
+
+	int prev_descram;		/* Previous descrambled for 9600 baud. */
+
+	unsigned char pat_det; 		/* 8 bit pattern detector shift register. */
+					/* See below for more details. */
+
+	unsigned int flag4_det;		/* Last 32 raw bits to look for 4 */
+					/* flag patterns in a row. */
+
+	unsigned char oacc;		/* Accumulator for building up an octet. */
+
+	int olen;			/* Number of bits in oacc. */
+					/* When this reaches 8, oacc is copied */
+					/* to the frame buffer and olen is zeroed. */
+					/* The value of -1 is a special case meaning */
+					/* bits should not be accumulated. */
+
+	unsigned char frame_buf[MAX_FRAME_LEN];
+					/* One frame is kept here. */
+
+	int frame_len;			/* Number of octets in frame_buf. */
+					/* Should be in range of 0 .. MAX_FRAME_LEN. */
+
+	int data_detect;		/* True when HDLC data is detected. */
+					/* This will not be triggered by voice or other */
+					/* noise or even tones.  */
+
+	rrbb_t rrbb;			/* Handle for bit array for raw received bits. */
+					
+};
+
+static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
+
+static int num_subchan[MAX_CHANS];		//TODO1.2 use ptr rather than copy.
+
+static int composite_dcd[MAX_CHANS][MAX_SUBCHANS+1];
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec_init
+ *
+ * Purpose:	Call once at the beginning to initialize.
+ *
+ * Inputs:	None.
+ *
+ ***********************************************************************************/
+
+static int was_init = 0;
+
+void hdlc_rec_init (struct audio_s *pa)
+{
+	int ch, sub, slice;
+	struct hdlc_state_s *H;
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("hdlc_rec_init (%p) \n", pa);
+
+	assert (pa != NULL);
+	
+	memset (composite_dcd, 0, sizeof(composite_dcd));
+
+	for (ch = 0; ch < MAX_CHANS; ch++)
+	{
+
+	  if (pa->achan[ch].valid) {
+
+	    num_subchan[ch] = pa->achan[ch].num_subchan;
+
+	    assert (num_subchan[ch] >= 1 && num_subchan[ch] <= MAX_SUBCHANS);
+
+	    for (sub = 0; sub < num_subchan[ch]; sub++)
+	    {
+	      for (slice = 0; slice < MAX_SLICERS; slice++) {
+
+	        H = &hdlc_state[ch][sub][slice];
+
+	        H->olen = -1;
+
+		// TODO: FIX13 wasteful if not needed.
+		// Should loop on number of slicers, not max.
+
+	        H->rrbb = rrbb_new(ch, sub, slice, pa->achan[ch].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram);
+	      }
+	    }
+	  }
+	}
+	hdlc_rec2_init (pa);
+	was_init = 1;
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec_bit
+ *
+ * Purpose:	Extract HDLC frames from a stream of bits.
+ *
+ * Inputs:	chan	- Channel number.  
+ *
+ *		subchan	- This allows multiple demodulators per channel.
+ *
+ *		slice	- Allows multiple slicers per demodulator (subchannel).
+ *
+ *		raw 	- One bit from the demodulator.
+ *			  should be 0 or 1.
+ *	
+ *		is_scrambled - Is the data scrambled?
+ *
+ *		descram_state - Current descrambler state.  (not used - remove)
+ *					
+ *
+ * Description:	This is called once for each received bit.
+ *		For each valid frame, process_rec_frame()
+ *		is called for further processing.
+ *
+ ***********************************************************************************/
+
+// TODO: int not_used_remove
+
+void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove)
+{
+
+	int dbit;			/* Data bit after undoing NRZI. */
+					/* Should be only 0 or 1. */
+	struct hdlc_state_s *H;
+
+	assert (was_init == 1);
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+
+	assert (slice >= 0 && slice < MAX_SLICERS);
+
+/*
+ * Different state information for each channel / subchannel / slice.
+ */
+	H = &hdlc_state[chan][subchan][slice];
+
+/*
+ * Using NRZI encoding,
+ *   A '0' bit is represented by an inversion since previous bit.
+ *   A '1' bit is represented by no change.
+ */
+
+	if (is_scrambled) {
+	  int descram;
+
+	  descram = descramble(raw, &(H->lfsr));
+
+	  dbit = (descram == H->prev_descram);
+	  H->prev_descram = descram;			
+	  H->prev_raw = raw;	}
+	else {
+
+	  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;
+	}
+
+	H->flag4_det >>= 1;
+	if (dbit) {
+	  H->flag4_det |= 0x80000000;
+	}
+
+
+/*
+ * "Data Carrier detect" function based on data patterns rather than
+ * audio signal strength.
+ *
+ * Idle time, at beginning of transmission should be filled
+ * with the special "flag" characters.
+ *
+ * Idle time of all zero bits (alternating tones at maximum rate)
+ * has also been observed rarely. 
+ * Recognize zero(s) followed by a flag even though it vilolates the spec.
+ */
+
+/*
+ * Originally, this looked for 4 flags in a row or 3 zeros and a flag. 
+ * Is that too fussy?
+ * Here are the numbers of start of DCD for our favorite Track 2 test.
+ *
+ *	7e7e7e7e  504 	7e000000  32  	
+ *	7e7e7e--  513   7e0000--  33	
+ *	7e7e----  555   7e00----  42	
+ *	7e------ 2088
+ *					
+ * I don't think we want to look for a single flag because that would
+ * make DCD too sensitive to noise and it would interfere with waiting for a 
+ * clear channel to transmit.  Even a two byte match causes a lot of flickering
+ * when listening to live signals.  Let's try 3 and see how that works out.
+ */
+
+	//if (H->flag4_det == 0x7e7e7e7e) {
+	if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) {	
+	//if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) {	
+
+	  if ( ! H->data_detect) {
+	    H->data_detect = 1;
+	    dcd_change (chan, subchan, slice, 1);
+	  }
+	}
+	//else if (H->flag4_det == 0x7e000000) {	
+	else if ((H->flag4_det & 0xffffff00) == 0x7e000000) {	
+	//else if ((H->flag4_det & 0xffff0000) == 0x7e000000) {	
+	  
+	  if ( ! H->data_detect) {
+	    H->data_detect = 1;
+	    dcd_change (chan, subchan, slice, 1);
+	  }
+	}
+
+
+/* 
+ * Loss of signal should result in lack of transitions.
+ * (all '1' bits) for at least a little while.
+ */
+
+  
+	if (H->pat_det == 0xff) {	
+	  
+	  if ( H->data_detect ) {
+	    H->data_detect = 0;
+	    dcd_change (chan, subchan, slice, 0);
+	  }
+	}
+
+
+/*
+ * End of data carrier detect.  
+ * 
+ * The rest is concerned with framing.
+ */
+
+
+	rrbb_append_bit (H->rrbb, raw);
+
+	if (H->pat_det == 0x7e) {
+
+	  rrbb_chop8 (H->rrbb);
+
+/*
+ * The special pattern 01111110 indicates beginning and ending of a frame.  
+ * If we have an adequate number of whole octets, it is a candidate for 
+ * further processing.
+ *
+ * It might look odd that olen is being tested for 7 instead of 0.
+ * This is because oacc would already have 7 bits from the special
+ * "flag" pattern before it is detected here.
+ */
+
+
+#if OLD_WAY
+
+#if TEST
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("\nfound flag, olen = %d, frame_len = %d\n", olen, frame_len);
+#endif
+	  if (H->olen == 7 && H->frame_len >= MIN_FRAME_LEN) {
+
+	    unsigned short actual_fcs, expected_fcs;
+
+#if TEST
+	    int j;
+	    dw_printf ("TRADITIONAL: frame len = %d\n", H->frame_len);
+	    for (j=0; j<H->frame_len; j++) {
+	      dw_printf ("  %02x", H->frame_buf[j]);
+	    }
+	    dw_printf ("\n");
+
+#endif
+	    /* Check FCS, low byte first, and process... */
+
+	    /* Alternatively, it is possible to include the two FCS bytes */
+	    /* in the CRC calculation and look for a magic constant.  */
+	    /* That would be easier in the case where the CRC is being */
+	    /* accumulated along the way as the octets are received. */
+	    /* I think making a second pass over it and comparing is */
+	    /* easier to understand. */
+
+	    actual_fcs = H->frame_buf[H->frame_len-2] | (H->frame_buf[H->frame_len-1] << 8);
+
+	    expected_fcs = fcs_calc (H->frame_buf, H->frame_len - 2);
+
+	    if (actual_fcs == expected_fcs) {
+	      alevel_t alevel = demod_get_audio_level (chan, subchan);
+
+	      multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE);   /* len-2 to remove FCS. */
+	    }
+	    else {
+
+#if TEST
+	      dw_printf ("*** actual fcs = %04x, expected fcs = %04x ***\n", actual_fcs, expected_fcs);
+#endif
+
+	    }
+
+	  }
+
+#else
+
+/*
+ * New way - Decode the raw bits in later step.
+ */
+
+#if TEST
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("\nfound flag, channel %d.%d, %d bits in frame\n", chan, subchan, rrbb_get_len(H->rrbb) - 1);
+#endif
+	  if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) {
+		
+	    alevel_t alevel = demod_get_audio_level (chan, subchan);
+
+	    rrbb_set_audio_level (H->rrbb, alevel);
+	    hdlc_rec2_block (H->rrbb);
+	    	/* Now owned by someone else who will free it. */
+
+	    H->rrbb = rrbb_new (chan, subchan, slice, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */
+	  }
+	  else {
+	    rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); 
+	  }
+
+	  H->olen = 0;		/* Allow accumulation of octets. */
+	  H->frame_len = 0;
+
+
+	  rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag.  Needed to get first data bit. */
+						/* Now that we are saving other initial state information, */
+						/* it would be sensible to do the same for this instead */
+						/* of lumping it in with the frame data bits. */
+#endif
+
+	}
+
+//#define EXPERIMENT12B 1
+
+#if EXPERIMENT12B
+
+	else if (H->pat_det == 0xff) {
+
+/*
+ * Valid data will never have seven 1 bits in a row.
+ *
+ *	11111110
+ *
+ * This indicates loss of signal.
+ * But we will let it slip thru because it might diminish
+ * our single bit fixup effort.   Instead give up on frame
+ * only when we see eight 1 bits in a row.
+ *
+ *	11111111
+ *
+ * What is the impact?  No difference.
+ *
+ *  Before:	atest -P E -F 1 ../02_Track_2.wav	= 1003
+ *  After:	atest -P E -F 1 ../02_Track_2.wav	= 1003
+ */
+
+#else
+	else if (H->pat_det == 0xfe) {
+
+/*
+ * Valid data will never have 7 one bits in a row.
+ *
+ *	11111110
+ *
+ * This indicates loss of signal.
+ */
+
+#endif
+
+	  H->olen = -1;		/* Stop accumulating octets. */
+	  H->frame_len = 0;	/* Discard anything in progress. */
+
+	  rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); 
+
+	}
+	else if ( (H->pat_det & 0xfc) == 0x7c ) {
+
+/*
+ * If we have five '1' bits in a row, followed by a '0' bit,
+ *
+ *	0111110xx
+ *
+ * the current '0' bit should be discarded because it was added for 
+ * "bit stuffing."
+ */
+	  ;
+
+	} else {
+
+/*
+ * In all other cases, accumulate bits into octets, and complete octets
+ * into the frame buffer.
+ */
+	  if (H->olen >= 0) {
+
+	    H->oacc >>= 1;
+	    if (dbit) {
+	      H->oacc |= 0x80;
+	    }
+	    H->olen++;
+
+	    if (H->olen == 8) {
+	      H->olen = 0;
+
+	      if (H->frame_len < MAX_FRAME_LEN) {
+		H->frame_buf[H->frame_len] = H->oacc;
+		H->frame_len++;
+	      }
+	    }
+	  }
+	}
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        hdlc_rec_gathering
+ *
+ * Purpose:     Report whether bits are currently being gathered into a frame.
+ *		This is used to influence the PLL inertia.
+ *		The idea is that the PLL should be a little more agreeable to
+ *		synchronize with the incoming data stream when not in a frame
+ *		and resist changing a little more when capturing a frame.
+ *
+ * Inputs:	chan
+ *		subchan
+ *		slice
+ *
+ * Returns:	True if we are currently gathering bits.
+ *		In this case we want the PLL to have more inertia.
+ *
+ * Discussion:	This simply returns the data carrier detect state.
+ *		A couple other variations were tried but turned out to
+ *		be slightly worse.
+ *
+ *--------------------------------------------------------------------*/
+
+int hdlc_rec_gathering (int chan, int subchan, int slice)
+{
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+	assert (slice >= 0 && slice < MAX_SLICERS);
+
+	// Counts from 	     Track 1 & Track 2
+	// data_detect		992	988
+	// olen>=0		992	985
+	// OR-ed		992	985
+
+	return ( hdlc_state[chan][subchan][slice].data_detect );
+
+} /* end hdlc_rec_gathering */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dcd_change
+ *
+ * Purpose:     Combine DCD states of all subchannels/ into an overall
+ *		state for the channel.
+ *
+ * Inputs:	chan	
+ *
+ *		subchan		0 to MAX_SUBCHANS-1 for HDLC.
+ *				SPECIAL CASE --> MAX_SUBCHANS for DTMF decoder.
+ *
+ *		slice		slicer number, 0 .. MAX_SLICERS - 1.
+ *
+ *		state		1 for active, 0 for not.
+ *
+ * Returns:	None.  Use hdlc_rec_data_detect_any to retrieve result.
+ *
+ * Description:	DCD for the channel is active if ANY of the subchannels/slices
+ *		are active.  Update the DCD indicator.
+ *
+ * version 1.3:	Add DTMF detection into the final result.
+ *		This is now called from dtmf.c too.
+ *
+ *--------------------------------------------------------------------*/
+
+void dcd_change (int chan, int subchan, int slice, int state)
+{
+	int old, new;
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan <= MAX_SUBCHANS);
+	assert (slice >= 0 && slice < MAX_SLICERS);
+	assert (state == 0 || state == 1);
+
+#if DEBUG3
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("DCD %d.%d.%d = %d \n", chan, subchan, slice, state);
+#endif
+
+	old = hdlc_rec_data_detect_any(chan);
+
+	if (state) {
+	  composite_dcd[chan][subchan] |= (1 << slice);
+	}
+	else {
+	  composite_dcd[chan][subchan] &=  ~ (1 << slice);
+	}
+
+	new = hdlc_rec_data_detect_any(chan);
+
+	if (new != old) {
+	  ptt_set (OCTYPE_DCD, chan, new);
+	}
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        hdlc_rec_data_detect_any
+ *
+ * Purpose:     Determine if the radio channel is curently busy
+ *		with packet data.
+ *		This version doesn't care about voice or other sounds.
+ *		This is used by the transmit logic to transmit only
+ *		when the channel is clear.
+ *
+ * Inputs:	chan	- Audio channel. 
+ *
+ * Returns:	True if channel is busy (data detected) or 
+ *		false if OK to transmit. 
+ *
+ *
+ * Description:	We have two different versions here.
+ *
+ *		hdlc_rec_data_detect_any sees if ANY of the decoders
+ *		for this channel are receving a signal.   This is
+ *		used to determine whether the channel is clear and
+ *		we can transmit.  This would apply to the 300 baud
+ *		HF SSB case where we have multiple decoders running
+ *		at the same time.  The channel is busy if ANY of them
+ *		thinks the channel is busy.
+ *
+ * Version 1.3: New option for input signal to inhibit transmit.
+ *
+ *--------------------------------------------------------------------*/
+
+int hdlc_rec_data_detect_any (int chan)
+{
+
+	int sc;
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+	for (sc = 0; sc < num_subchan[chan]; sc++) {
+	  if (composite_dcd[chan][sc] != 0)
+	    return (1);
+	}
+
+	if (get_input(ICTYPE_TXINH, chan) == 1) return (1);
+
+	return (0);
+
+} /* end hdlc_rec_data_detect_any */
+
+/* end hdlc_rec.c */
+
+
diff --git a/hdlc_rec.h b/hdlc_rec.h
index cb1bdd3..69b60a9 100644
--- a/hdlc_rec.h
+++ b/hdlc_rec.h
@@ -1,30 +1,25 @@
-
-
-#include "audio.h"
-
-//#include "rrbb.h"		
-
-
-void hdlc_rec_init (struct audio_s *pa);
-
-
-void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state);
-
-
-
-/* Provided elsewhere to process a complete frame. */
-
-//void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level);
-
-
-/* Is HLDC decoder is currently gathering bits into a frame? */
-/* Similar to, but not exactly the same as, data carrier detect. */
-/* We use this to influence the PLL inertia. */
-
-int hdlc_rec_gathering (int chan, int subchan);
-
-
-/* Transmit needs to know when someone else is transmitting. */
-
-
-int hdlc_rec_data_detect_any (int chan);
+
+
+#include "audio.h"
+
+
+void hdlc_rec_init (struct audio_s *pa);
+
+void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int descram_state);
+
+/* Provided elsewhere to process a complete frame. */
+
+//void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level);
+
+
+/* Is HLDC decoder is currently gathering bits into a frame? */
+/* Similar to, but not exactly the same as, data carrier detect. */
+/* We use this to influence the PLL inertia. */
+
+int hdlc_rec_gathering (int chan, int subchan, int slice);
+
+/* Transmit needs to know when someone else is transmitting. */
+
+void dcd_change (int chan, int subchan, int slice, int state);
+
+int hdlc_rec_data_detect_any (int chan);
diff --git a/hdlc_rec2.c b/hdlc_rec2.c
index e438d42..374fa98 100644
--- a/hdlc_rec2.c
+++ b/hdlc_rec2.c
@@ -1,1345 +1,1015 @@
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	hdlc_rec2.c
- *
- * Purpose:	Extract HDLC frame from a block of bits after someone
- *		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.
- *
- * Test results:	We intentionally use the worst demodulator so there
- *			is more opportunity to try to fix the frames.
- *
- *		atest -P A -F n 02_Track_2.wav
- *
- *		n   	description	frames	sec
- *		--  	----------- 	------	---
- *		0	no attempt	963	40	error-free frames
- *		1	invert 1	979	41	16 more
- *		2	invert 2	982	42	3 more
- *		3	invert 3	982	42	no change
- *		4	remove 1	982	43	no change
- *		5	remove 2	982	43	no change
- *		6	remove 3	982	43	no change
- *		7	insert 1	982	45	no change
- *		8	insert 2	982	47	no change
- *		9	invert two sep	993	178	11 more, some visually obvious errors.
- *		10	invert many?	993	190	no change
- *		11	remove many	995	190	2 more, need to investigate in detail.		
- *		12	remove two sep	995	201	no change
- *
- * Observations:	The "insert" and "remove" techniques had no benefit.  I would not expect them to.
- *			We have a phase locked loop that attempts to track any slight variations in the 
- *			timing so we sample near the middle of the bit interval.  Bits can get corrupted 
- *			by noise but not disappear or just appear.  That would be a gap in the timing.	
- *			These should probably be removed in a future version.
- *
- *	
- * Version 1.2:	Now works for 9600 baud.
- *		This was more complicated due to the data scrambling.
- *		It was necessary to retain more initial state information after
- *		the start flag octet.
- *
- *******************************************************************************/
-
-#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"
-#include "textcolor.h"
-#include "ax25_pad.h"
-#include "rrbb.h"
-#include "rdq.h"
-#include "multi_modem.h"
-#include "dtime_now.h"
-#include "demod_9600.h"		/* for descramble() */
-#include "audio.h"		/* for struct audio_s */
-//#include "ax25_pad.h"		/* for AX25_MAX_ADDR_LEN */
-
-
-//#define DEBUG 1
-//#define DEBUGx 1
-//#define DEBUG_LATER 1
-
-/* Audio configuration. */
-
-static struct audio_s          *save_audio_config_p;
-
-
-/* 
- * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS. 
- */
-
-#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
-				
-#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
-
-/*
- * This is the current state of the HDLC decoder.
- *
- * It is possible to run multiple decoders concurrently by
- * having a separate set of state variables for each.
- *
- * Should have a reset function instead of initializations here.
- */
-
-struct hdlc_state_s {
-
-	int prev_raw;			/* Keep track of previous bit so */
-					/* we can look for transitions. */
-					/* Should be only 0 or 1. */
-
-	int is_scrambled;		/* Set for 9600 baud. */
-	int lfsr;			/* Descrambler shift register for 9600 baud. */
-	int prev_descram;		/* Previous unscrambled for 9600 baud. */
-
-
-	unsigned char pat_det; 		/* 8 bit pattern detector shift register. */
-					/* See below for more details. */
-
-	unsigned char oacc;		/* Accumulator for building up an octet. */
-
-	int olen;			/* Number of bits in oacc. */
-					/* When this reaches 8, oacc is copied */
-					/* to the frame buffer and olen is zeroed. */
-
-	unsigned char frame_buf[MAX_FRAME_LEN];
-					/* One frame is kept here. */
-
-	int frame_len;			/* Number of octets in frame_buf. */
-					/* Should be in range of 0 .. MAX_FRAME_LEN. */
-
-};
-
-#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, alevel_t alevel, retry_conf_t retry_conf, int passall);
-static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, alevel_t alevel);
-static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test);
-
-
-/***********************************************************************************
- *
- * Name:	hdlc_rec2_init
- *
- * Purpose:	Initialization.   
- *
- * Inputs:	p_audio_config	 - Pointer to configuration settings.
- *				   This is what we care about for each channel.
- *
- *	   			enum retry_e fix_bits;	
- *					Level of effort to recover from 
- *					a bad FCS on the frame. 
- *					0 = no effort 
- *					1 = try fixing a single bit 
- *					2... = more techniques... 
- *
- *	    			enum sanity_e sanity_test;
- *					Sanity test to apply when finding a good 
- *					CRC after changing one or more bits. 
- *					Must look like APRS, AX.25, or anything. 
- *
- *	    			int passall;		
- *					Allow thru even with bad CRC after exhausting
- *					all fixup attempts.
- *
- * Description:	Save pointer to configuration for later use.
- *
- ***********************************************************************************/
-
-void hdlc_rec2_init (struct audio_s *p_audio_config)
-{
-	save_audio_config_p = p_audio_config;
-}
-
-
-
-/***********************************************************************************
- *
- * Name:	hdlc_rec2_block
- *
- * Purpose:	Extract HDLC frame from a stream of bits.
- *
- * Inputs:	block 		- Handle for bit array.
- *
- * Description:	The other (original) hdlc decoder took one bit at a time
- *		right out of the demodulator.
- *
- *		This is different in that it processes a block of bits
- *		previously extracted from between two "flag" patterns.
- *
- *		This allows us to try decoding the same received data more
- *		than once.
- *
- * Version 1.2:	Now works properly for G3RUH type scrambling.
- *
- ***********************************************************************************/
-
-
-void hdlc_rec2_block (rrbb_t block)
-{
-	int chan = rrbb_get_chan(block);
-	int subchan = rrbb_get_subchan(block);
-	alevel_t alevel = rrbb_get_audio_level(block);
-	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
-	int passall = save_audio_config_p->achan[chan].passall;
-	int ok;
-	int n;
-
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("\n--- try to decode ---\n");
-#endif
-
-	/* Create an empty retry configuration */
-	retry_conf_t retry_cfg;
-
-/* 
- * For our first attempt we don't try to alter any bits.
- * Still let it thru if passall AND no retries are desired.
- */
-
-	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, passall & (fix_bits == RETRY_NONE));
-
-	if (ok) {
-#if DEBUG
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Got it the first time.\n");
-#endif
-	 rrbb_delete (block);
-	 return;
-	}
-
-/*
- * Not successful with frame in orginal form.
- * Try the quick techniques with time proportional to the frame length.
- */	
-	if (try_to_fix_quick_now (block, chan, subchan, alevel)) {
-	  rrbb_delete (block);
-	  return;
-	}
-
-/*
- * Not successful with the quick fix up attempts.
- * Do we want to try the more aggressive techniques where processing
- * time is proportional to the square of length?
- * Rather than doing it now, we throw it in a queue for processing
- * by a different thread.
- */
-
-	if (fix_bits >= RETRY_SWAP_TWO_SEP) {
-	  rdq_append (block);
-	}
-	else if (passall) {
-	  /* Exhausted all desired fix up attempts. */
-	  /* Let thru even with bad CRC.  Of course, it still */
-	  /* needs to be a minimum number of whole octets. */
-	  ok = try_decode (block, chan, subchan, alevel, retry_cfg, 1);
-	  rrbb_delete (block);
-	}
-	else {  
-	  rrbb_delete (block); 
-	}
-
-} /* end hdlc_rec2_block */
-
-
-/***********************************************************************************
- *
- * Name:	try_to_fix_quick_now
- *
- * Purpose:	Attempt some quick fixups that don't take very long.
- *
- * Inputs:	block	- Stream of bits that might be a frame.
- *		chan	- Radio channel from which it was received.
- *		subchan	- Which demodulator when more than one per channel.
- *		alevel	- Audio level for later reporting.
- *
- * Global In:	configuration fix_bits - Maximum level of fix up to attempt.
- *
- *				RETRY_NONE (0)	- Don't try any.
- *				RETRY_SWAP_SINGLE (1)  - Try inverting single bits.
- *				etc.
- *
- *		configuration passall - Let it thru with bad CRC after exhausting
- *				all fixup attempts.
- *
- *
- * Returns:	1 for success.  "try_decode" has passed the result along to the 
- *				processing step.
- *		0 for failure.  Caller might continue with more aggressive attempts.
- *
- * Description:	Some of the attempted fix up techniques are quick.
- *		We will attempt them immediately after receiving the frame.
- *		Others, that take time order N**2, will be done in a later section.
- *
- * Version 1.2:	Now works properly for G3RUH type scrambling.
- *
- ***********************************************************************************/
-
-static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, alevel_t alevel)
-{
-	int ok;
-	int len, i,j;
-	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
-	int passall = save_audio_config_p->achan[chan].passall;
-
-
-	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_SWAP_SINGLE) {
-
-	  /* Stop before single bit fix up. */
-
-	  return 0;	/* failure. */
-	}
-	/* 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++) {
-	  /* 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, 0);
-	  if (ok) {
-#if DEBUG
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("*** Success by flipping SINGLE bit %d of %d ***\n", i, len);
-#endif
-	    return 1;
-	  }
-	}
-
-/* 
- * Try fixing two adjacent bits.  
- */
-	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++) {
-	  retry_cfg.u_bits.contig.bit_idx = i;
-	  ok = try_decode (block, chan, subchan, alevel, retry_cfg, 0);
-	  if (ok) {
-#if DEBUG
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("*** Success by flipping DOUBLE bit %d of %d ***\n", i, len);
-#endif
-	    return 1;
-	  }
-	}
-
-/*
- * Try fixing adjacent three bits.
- */
-	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;
-
-	for (i=0; i<len-2; i++) {
-	  retry_cfg.u_bits.contig.bit_idx = i;
-	  ok = try_decode (block, chan, subchan, alevel, retry_cfg, 0);
-	  if (ok) {
-#if DEBUG
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("*** Success by flipping TRIPLE bit %d of %d ***\n", i, len);
-#endif
-	    return 1;
-	  }
-	}
-
-	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, 0);
-		  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, 0);
-		  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, 0);
-		  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, 0);
-		  if (!ok) {
-		    retry_cfg.insert_value=1;
-		    ok = try_decode (block, chan, subchan, alevel, retry_cfg, 0);
-		  }
-		  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, 0);
-
-		    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;
-}
-
-
-/***********************************************************************************
- *
- * Name:	hdlc_rec2_try_to_fix_later
- *
- * Purpose:	Attempt some more time-consuming techniques.
- *		Rather than trying these immediately, the information is
- *		put into a queue and processed by another thread.
- *
- * Inputs:	block	- Stream of bits that might be a frame.
- *		chan	- Radio channel from which it was received.
- *		subchan	- Which demodulator when more than one per channel.
- *		alevel	- Audio level for later reporting.
- *
- * Global In:	configuration fix_bits - Maximum level of fix up to attempt.
- *
- *				RETRY_NONE (0)	- Don't try any.
- *				RETRY_SWAP_SINGLE (1)  - Try inverting single bits.
- *				etc.
- *
- *		configuration passall - Let it thru with bad CRC after exhausting
- *				all fixup attempts.
- *
- *
- * Returns:	1 for success.  "try_decode" has passed the result along to the 
- *				processing step.
- *		0 for failure.  Caller might try again if "passall" option specified.
- *
- ***********************************************************************************/
-
-
-int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, alevel_t alevel)
-{
-	int ok;
-	int len, i, j;
-	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
-	int passall = save_audio_config_p->achan[chan].passall;
-#if DEBUG_LATER
-	double tstart, tend;
-#endif
-	retry_conf_t retry_cfg;
-	len = rrbb_get_len(block);
-
-	
-
-	if (fix_bits < RETRY_SWAP_TWO_SEP) {
-	  goto failure;  
-	}
-
-
-	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 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;
-
-#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++) {
-	    retry_cfg.u_bits.sep.bit_idx_b = j;
-	    ok = try_decode (block, chan, subchan, alevel, retry_cfg, 0);
-	    if (ok) {
-	      break;
-	    }
-
-	  }	  
-	  if (ok) {
-#if DEBUG
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("*** Success by flipping TWO SEPARATED bits %d and %d of %d \n", i, j, len);
-#endif
-	    return (1);
-	  }
-	}
-#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
-
-	if (fix_bits < RETRY_SWAP_MANY) {
-	  goto failure;
-	}
-	/* 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, 0);
-	    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 (1);
-	    }
-	  }
-	}
-#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) {
-	  goto failure;
-	}
-
-
-	/* 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, 0);
-	    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 (1);
-	    }
-	  }
-	}
-#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) {
-	  goto failure;
-	}
-
-/*
- * 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, 0);
-	    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 (1);
-	  }
-	}
-#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
-
-failure:
-
-/*
- * All fix up attempts have failed.  
- * Should we pass it along anyhow with a bad CRC?
- * Note that we still need a minimum number of whole octets.
- */
-	if (passall) {
-
-	  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;
-	
-	  ok = try_decode (block, chan, subchan, alevel, retry_cfg, passall);
-	  return (ok);
-	}
-
-	return (0);
-
-}  /* end hdlc_rec2_try_to_fix_later */
-
-
-/* 
- * 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];
-}
-
-/***********************************************************************************
- *
- * Name:	try_decode
- *
- * Purpose:	   
- *
- * Inputs:	block		- Bit string that was collected between "flag" patterns.
- *
- *		chan, subchan	- where it came from.
- *
- *		alevel		- audio level for later reporting.
- *
- *		retry_conf	- Controls changes that will be attempted to get a good CRC.
- *
- *	   			retry:	
- *					Level of effort to recover from A bad FCS on the frame. 
- *				                RETRY_NONE=0,
- *				                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,
- *
- *	    			mode:	RETRY_MODE_CONTIGUOUS - change adjacent bits.
- *						contig.bit_idx - first bit position
- *						contig.nr_bits - number of bits
- *
- *				        RETRY_MODE_SEPARATED  - change bits not next to each other.
- *						sep.bit_idx_a - bit positions
- *						sep.bit_idx_b - bit positions
- *						sep.bit_idx_c - bit positions
- *
- *				type:	RETRY_TYPE_NONE	- Make no changes.
- *					RETRY_TYPE_SWAP - Try inverting.
- *					RETRY_TYPE_REMOVE - Try removing.
- *					RETRY_TYPE_INSERT - Try inserting.
- *					
- *		passall		- All it thru even with bad CRC.
- *				  Valid only when no changes make.  i.e.
- *					retry == RETRY_NONE, type == RETRY_TYPE_NONE
- *
- * Returns:	1 = successfully extracted something.
- *		0 = failure.
- *
- ***********************************************************************************/
-
-
-static int try_decode (rrbb_t block, int chan, int subchan, alevel_t alevel, retry_conf_t retry_conf, int passall)
-{
-	struct hdlc_state_s H;	
-	int blen;			/* Block length in bits. */
-	int i;
-	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.is_scrambled = rrbb_get_is_scrambled (block);
-	H.prev_descram = rrbb_get_prev_descram (block);
-	H.lfsr = rrbb_get_descram_state (block);
-	H.prev_raw = get_bit (block, 0);	  /* Actually last bit of the */
-					/* opening flag so we can derive the */
-					/* first data bit.  */
-
-	/* Does this make sense? */
-	/* This is the last bit of the "flag" pattern. */
-	/* If it was corrupted we wouldn't have detected */
-	/* the start of frame. */
-
-	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;
-	}
-
-	H.pat_det = 0;
-	H.oacc = 0;
-	H.olen = 0;
-	H.frame_len = 0;
-
-	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);
-        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;
-            } 
-
-	  } 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 
- */
-
-	    int dbit ;
-
-	    if (H.is_scrambled) {
-	      int descram;
-
-	      descram = descramble(raw, &(H.lfsr));
-
-	      dbit = (descram == H.prev_descram);
-	      H.prev_descram = descram;
-	      H.prev_raw = raw;
-	    }
-	    else {
-
-	      dbit = (raw == H.prev_raw);
-	      H.prev_raw = raw;
-	    }
-
-	    if (dbit) {
-
-	      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 abort, i=%d\n", i);
-#endif
-	        return 0;
-	      }
-	      H.oacc >>= 1;
-	      H.oacc |= 0x80;
-	    } else {
-	      
-	      /* 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 flag, i=%d\n", i);
-#endif
-	      return 0;
-/*
- * If we have five '1' bits in a row, followed by a '0' bit,
- *
- *	011111xx
- *
- * the current '0' bit should be discarded because it was added for 
- * "bit stuffing."
- */
-	
-	      } else if ( (H.pat_det >> 2) == 0x1f ) {
-	        continue;
-	      }
-	      H.oacc >>= 1;
-	    }
-
-/*
- * Now accumulate bits into octets, and complete octets
- * into the frame buffer.
- */
-
-	    H.olen++;
-
-	    if (H.olen & 8) {
-	      H.olen = 0;
-
-	      if (H.frame_len < MAX_FRAME_LEN) {
-	        H.frame_buf[H.frame_len] = H.oacc;
-		H.frame_len++;
-	      
-	      }
-	    }
-	  }	/* end of loop on all bits in block */
-/* 
- * Do we have a minimum number of complete bytes?
- */
-
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("try_decode: olen=%d, frame_len=%d\n", H.olen, H.frame_len);
-#endif
-
-	if (H.olen == 0 && H.frame_len >= MIN_FRAME_LEN) {
-
-	  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);
-	  for (j=0; j<H.frame_len; j++) {
-	    dw_printf ("  %02x", H.frame_buf[j]);
-	  }
-	  dw_printf ("\n");
-
-        }
-#endif
-	  /* Check FCS, low byte first, and process... */
-
-	  /* Alternatively, it is possible to include the two FCS bytes */
-	  /* in the CRC calculation and look for a magic constant.  */
-	  /* That would be easier in the case where the CRC is being */
-	  /* accumulated along the way as the octets are received. */
-	  /* I think making a second pass over it and comparing is */
-	  /* easier to understand. */
-
-	  actual_fcs = H.frame_buf[H.frame_len-2] | (H.frame_buf[H.frame_len-1] << 8);
-
-	  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, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) {
-
-	      // TODO: Shouldn't be necessary to pass chan, subchan, alevel into
-	      // try_decode because we can obtain them from block.
-	      // Let's make sure that assumption is good...
-
-	      assert (rrbb_get_chan(block) == chan);
-	      assert (rrbb_get_subchan(block) == subchan);
-
-	      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 if (passall) {
-	    if (retry_conf_retry == RETRY_NONE && retry_conf_type == RETRY_TYPE_NONE) {
-
-	      //text_color_set(DW_COLOR_ERROR);
-	      //dw_printf ("ATTEMPTING PASSALL PROCESSING\n");
-  
-	      multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX);   /* len-2 to remove FCS. */
-	      return 1;		/* success */
-	    }
-	    else {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("try_decode: internal error passall = %d, retry_conf_retry = %d, retry_conf_type = %d\n", 
-				passall, retry_conf_retry, retry_conf_type);
-	    }
-	  } 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 */
-
-
-
-/***********************************************************************************
- *
- * Name:	sanity_check
- *
- * Purpose:	Try to weed out bogus packets from initially failed FCS matches.
- *
- * Inputs:	buf
- *
- *		blen
- *
- *		bits_flipped
- *
- *		sanity		How much sanity checking to perform:
- *					SANITY_APRS - Looks like APRS.  See User Guide,
- *						section that discusses bad apples.
- *					SANITY_AX25 - Has valid AX.25 address part.
- *						No checking of the rest.  Useful for 
- *						connected mode packet.
- *					SANITY_NONE - No checking.  Would be suitable
- *						only if using frames that don't conform
- *						to AX.25 standard.
- *
- * Returns:	1 if it passes the sanity test.
- *
- * Description:	
- *
- ***********************************************************************************/
-
-
-static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test)
-{
-	int alen;		/* Length of address part. */
-	int j;
-
-/*
- * No sanity check if we didn't try fixing the data.
- * Should we have different levels of checking depending on 
- * how much we try changing the raw data?
- */
-	if (bits_flipped == RETRY_NONE) {
-	  return 1;
-	}
-
-
-/*
- * If using frames that do not conform to AX.25, it might be
- * desirable to skip the sanity check entirely.
- */
-	if (sanity_test == SANITY_NONE) {
-	  return (1);
-	}
-
-/*
- * Address part must be a multiple of 7. 
- */
-
-	alen = 0;
-	for (j=0; j<blen && alen==0; j++) {
-	  if (buf[j] & 0x01) {
-	    alen = j + 1;
-	  }
-	}
-
-	if (alen % 7 != 0) {
-#if DEBUGx
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("sanity_check: FAILED.  Address part length %d not multiple of 7.\n", alen);
-#endif
-	  return 0;
-	}
-
-/*
- * Need at least 2 addresses and maximum of 8 digipeaters. 
- */
-
-	if (alen/7 < 2 || alen/7 > 10) {
-#if DEBUGx
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("sanity_check: FAILED.  Too few or many addresses.\n");
-#endif
-	  return 0;
-	}
-
-/* 
- * Addresses can contain only upper case letters, digits, and space. 
- */
-
-	for (j=0; j<alen; j+=7) {
-
-	  char addr[7];
-
-	  addr[0] = buf[j+0] >> 1;
-	  addr[1] = buf[j+1] >> 1;
-	  addr[2] = buf[j+2] >> 1;
-	  addr[3] = buf[j+3] >> 1;
-	  addr[4] = buf[j+4] >> 1;
-	  addr[5] = buf[j+5] >> 1;
-	  addr[6] = '\0';
-
-
-	  if ( (! isupper(addr[0]) && ! isdigit(addr[0])) ||
-	       (! isupper(addr[1]) && ! isdigit(addr[1]) && addr[1] != ' ') ||
-	       (! isupper(addr[2]) && ! isdigit(addr[2]) && addr[2] != ' ') ||
-	       (! isupper(addr[3]) && ! isdigit(addr[3]) && addr[3] != ' ') ||
-	       (! isupper(addr[4]) && ! isdigit(addr[4]) && addr[4] != ' ') ||
-	       (! isupper(addr[5]) && ! isdigit(addr[5]) && addr[5] != ' ')) {
-#if DEBUGx	  
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("sanity_check: FAILED.  Invalid characters in addresses \"%s\"\n", addr);
-#endif
-	    return 0;
-	  }
-	}
-
-
-/*
- * That's good enough for the AX.25 sanity check.
- * Continue below for additional APRS checking.
- */
-	if (sanity_test == SANITY_AX25) {
-	  return (1);
-	}
-
-/*
- * The next two bytes should be 0x03 and 0xf0 for APRS.
- */
-
-	if (buf[alen] != 0x03 || buf[alen+1] != 0xf0) {
-	  return (0);
-	}
-
-/*
- * Finally, look for bogus characters in the information part.
- * In theory, the bytes could have any values.
- * In practice, we find only printable ASCII characters and:
- *	
- *	0x0a	line feed
- *	0x0d	carriage return	
- *	0x1c	MIC-E
- *	0x1d	MIC-E
- *	0x1e	MIC-E
- *	0x1f	MIC-E
- *	0x7f	MIC-E
- *	0x80	"{UIV32N}<0x0d><0x9f><0x80>"
- *	0x9f	"{UIV32N}<0x0d><0x9f><0x80>"
- *	0xb0	degree symbol, ISO LATIN1
- *		  (Note: UTF-8 uses two byte sequence 0xc2 0xb0.)
- *	0xbe	invalid MIC-E encoding.
- *	0xf8	degree symbol, Microsoft code page 437
- *
- * So, if we have something other than these (in English speaking countries!), 
- * chances are that we have bogus data from twiddling the wrong bits.
- *
- * Notice that we shouldn't get here for good packets.  This extra level
- * of checking happens only if we twiddled a couple of bits, possibly
- * creating bad data.  We want to be very fussy.
- */
-
- 	for (j=alen+2; j<blen; j++) {
-	  int ch = buf[j];
-
-	  if ( ! (( ch >= 0x1c && ch <= 0x7f) 
-			|| ch == 0x0a 
-			|| ch == 0x0d
-			|| ch == 0x80
-			|| ch == 0x9f
-			|| ch == 0xc2
-			|| ch == 0xb0
-			|| ch == 0xf8) ) {
-#if DEBUGx
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("sanity_check: FAILED.  Probably bogus info char 0x%02x\n", ch);
-#endif
-	    return 0;
-	  }
-	}
-
-	return 1;
-}
-
-
-/* end hdlc_rec2.c */
-
-
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	hdlc_rec2.c
+ *
+ * Purpose:	Extract HDLC frame from a block of bits after someone
+ *		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.
+ *
+ * Test results:	We intentionally use the worst demodulator so there
+ *			is more opportunity to try to fix the frames.
+ *
+ *		atest -P A -F n 02_Track_2.wav
+ *
+ *		n   	description	frames	sec
+ *		--  	----------- 	------	---
+ *		0	no attempt	963	40	error-free frames
+ *		1	invert 1	979	41	16 more
+ *		2	invert 2	982	42	3 more
+ *		3	invert 3	982	42	no change
+ *		4	remove 1	982	43	no change
+ *		5	remove 2	982	43	no change
+ *		6	remove 3	982	43	no change
+ *		7	insert 1	982	45	no change
+ *		8	insert 2	982	47	no change
+ *		9	invert two sep	993	178	11 more, some visually obvious errors.
+ *		10	invert many?	993	190	no change
+ *		11	remove many	995	190	2 more, need to investigate in detail.		
+ *		12	remove two sep	995	201	no change
+ *
+ * Observations:	The "insert" and "remove" techniques had no benefit.  I would not expect them to.
+ *			We have a phase locked loop that attempts to track any slight variations in the 
+ *			timing so we sample near the middle of the bit interval.  Bits can get corrupted 
+ *			by noise but not disappear or just appear.  That would be a gap in the timing.	
+ *			These should probably be removed in a future version.
+ *
+ *	
+ * Version 1.2:	Now works for 9600 baud.
+ *		This was more complicated due to the data scrambling.
+ *		It was necessary to retain more initial state information after
+ *		the start flag octet.
+ *
+ * Version 1.3: Took out all of the "insert" and "remove" cases because they
+ *		offer no benenfit.
+ *
+ *		Took out the delayed processing and just do it realtime.
+ *		Changed SWAP to INVERT because it is more descriptive.
+ *
+ *******************************************************************************/
+
+#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"
+#include "textcolor.h"
+#include "ax25_pad.h"
+#include "rrbb.h"
+#include "rdq.h"
+#include "multi_modem.h"
+#include "dtime_now.h"
+#include "demod_9600.h"		/* for descramble() */
+#include "audio.h"		/* for struct audio_s */
+//#include "ax25_pad.h"		/* for AX25_MAX_ADDR_LEN */
+
+
+//#define DEBUG 1
+//#define DEBUGx 1
+//#define DEBUG_LATER 1
+
+/* Audio configuration. */
+
+static struct audio_s          *save_audio_config_p;
+
+
+/* 
+ * Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS. 
+ */
+
+#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
+				
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+/*
+ * This is the current state of the HDLC decoder.
+ *
+ * It is possible to run multiple decoders concurrently by
+ * having a separate set of state variables for each.
+ *
+ * Should have a reset function instead of initializations here.
+ */
+
+struct hdlc_state_s {
+
+	int prev_raw;			/* Keep track of previous bit so */
+					/* we can look for transitions. */
+					/* Should be only 0 or 1. */
+
+	int is_scrambled;		/* Set for 9600 baud. */
+	int lfsr;			/* Descrambler shift register for 9600 baud. */
+	int prev_descram;		/* Previous unscrambled for 9600 baud. */
+
+
+	unsigned char pat_det; 		/* 8 bit pattern detector shift register. */
+					/* See below for more details. */
+
+	unsigned char oacc;		/* Accumulator for building up an octet. */
+
+	int olen;			/* Number of bits in oacc. */
+					/* When this reaches 8, oacc is copied */
+					/* to the frame buffer and olen is zeroed. */
+
+	unsigned char frame_buf[MAX_FRAME_LEN];
+					/* One frame is kept here. */
+
+	int frame_len;			/* Number of octets in frame_buf. */
+					/* Should be in range of 0 .. MAX_FRAME_LEN. */
+
+};
+
+
+static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall);
+
+static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel);
+
+static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test);
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec2_init
+ *
+ * Purpose:	Initialization.   
+ *
+ * Inputs:	p_audio_config	 - Pointer to configuration settings.
+ *				   This is what we care about for each channel.
+ *
+ *	   			enum retry_e fix_bits;	
+ *					Level of effort to recover from 
+ *					a bad FCS on the frame. 
+ *					0 = no effort 
+ *					1 = try inverting a single bit
+ *					2... = more techniques... 
+ *
+ *	    			enum sanity_e sanity_test;
+ *					Sanity test to apply when finding a good 
+ *					CRC after changing one or more bits. 
+ *					Must look like APRS, AX.25, or anything. 
+ *
+ *	    			int passall;		
+ *					Allow thru even with bad CRC after exhausting
+ *					all fixup attempts.
+ *
+ * Description:	Save pointer to configuration for later use.
+ *
+ ***********************************************************************************/
+
+void hdlc_rec2_init (struct audio_s *p_audio_config)
+{
+	save_audio_config_p = p_audio_config;
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	hdlc_rec2_block
+ *
+ * Purpose:	Extract HDLC frame from a stream of bits.
+ *
+ * Inputs:	block 		- Handle for bit array.
+ *
+ * Description:	The other (original) hdlc decoder took one bit at a time
+ *		right out of the demodulator.
+ *
+ *		This is different in that it processes a block of bits
+ *		previously extracted from between two "flag" patterns.
+ *
+ *		This allows us to try decoding the same received data more
+ *		than once.
+ *
+ * Version 1.2:	Now works properly for G3RUH type scrambling.
+ *
+ ***********************************************************************************/
+
+
+void hdlc_rec2_block (rrbb_t block)
+{
+	int chan = rrbb_get_chan(block);
+	int subchan = rrbb_get_subchan(block);
+	int slice = rrbb_get_slice(block);
+	alevel_t alevel = rrbb_get_audio_level(block);
+	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	int passall = save_audio_config_p->achan[chan].passall;
+	int ok;
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("\n--- try to decode ---\n");
+#endif
+
+	/* Create an empty retry configuration */
+	retry_conf_t retry_cfg;
+
+/* 
+ * For our first attempt we don't try to alter any bits.
+ * Still let it thru if passall AND no retries are desired.
+ */
+
+	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;
+
+	ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE));
+	if (ok) {
+#if DEBUG
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Got it the first time.\n");
+#endif
+	 rrbb_delete (block);
+	 return;
+	}
+
+/*
+ * Not successful with frame in orginal form.
+ * See if we can "fix" it.
+ */
+	if (try_to_fix_quick_now (block, chan, subchan, slice, alevel)) {
+	  rrbb_delete (block);
+	  return;
+	}
+
+
+	if (passall) {
+	  /* Exhausted all desired fix up attempts. */
+	  /* Let thru even with bad CRC.  Of course, it still */
+	  /* needs to be a minimum number of whole octets. */
+	  ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, 1);
+	  rrbb_delete (block);
+	}
+	else {  
+	  rrbb_delete (block); 
+	}
+
+} /* end hdlc_rec2_block */
+
+
+/***********************************************************************************
+ *
+ * Name:	try_to_fix_quick_now
+ *
+ * Purpose:	Attempt some quick fixups that don't take very long.
+ *
+ * Inputs:	block	- Stream of bits that might be a frame.
+ *		chan	- Radio channel from which it was received.
+ *		subchan	- Which demodulator when more than one per channel.
+ *		alevel	- Audio level for later reporting.
+ *
+ * Global In:	configuration fix_bits - Maximum level of fix up to attempt.
+ *
+ *				RETRY_NONE (0)	- Don't try any.
+ *				RETRY_INVERT_SINGLE (1)  - Try inverting single bits.
+ *				etc.
+ *
+ *		configuration passall - Let it thru with bad CRC after exhausting
+ *				all fixup attempts.
+ *
+ *
+ * Returns:	1 for success.  "try_decode" has passed the result along to the 
+ *				processing step.
+ *		0 for failure.  Caller might continue with more aggressive attempts.
+ *
+ * Original:	Some of the attempted fix up techniques are quick.
+ *		We will attempt them immediately after receiving the frame.
+ *		Others, that take time order N**2, will be done in a later section.
+ *
+ * Version 1.2:	Now works properly for G3RUH type scrambling.
+ *
+ * Version 1.3: Removed the extra cases that didn't help.
+ *		The separated bit case is now handled immediately instead of
+ *		being thrown in a queue for later processing.
+ *
+ ***********************************************************************************/
+
+static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
+{
+	int ok;
+	int len, i,j;
+	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	//int passall = save_audio_config_p->achan[chan].passall;
+
+
+	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 inverting one bit.
+ */
+	if (fix_bits < RETRY_INVERT_SINGLE) {
+
+	  /* Stop before single bit fix up. */
+
+	  return 0;	/* failure. */
+	}
+	/* Try to swap one bit */
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_INVERT_SINGLE;
+	retry_cfg.u_bits.contig.nr_bits = 1;
+
+	for (i=0; i<len; i++) {
+	  /* Set the index of the bit to swap */
+	  retry_cfg.u_bits.contig.bit_idx = i;
+	  ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, 0);
+	  if (ok) {
+#if DEBUG
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("*** Success by flipping SINGLE bit %d of %d ***\n", i, len);
+#endif
+	    return 1;
+	  }
+	}
+
+/* 
+ * Try inverting two adjacent bits.
+ */
+	if (fix_bits < RETRY_INVERT_DOUBLE) {
+	  return 0;
+	}
+	/* Try to swap two contiguous bits */
+	retry_cfg.retry = RETRY_INVERT_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, slice, alevel, retry_cfg, 0);
+	  if (ok) {
+#if DEBUG
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("*** Success by flipping DOUBLE bit %d of %d ***\n", i, len);
+#endif
+	    return 1;
+	  }
+	}
+
+/*
+ * Try inverting adjacent three bits.
+ */
+	if (fix_bits < RETRY_INVERT_TRIPLE) {
+	  return 0;
+	}
+	/* Try to swap three contiguous bits */
+	retry_cfg.retry = RETRY_INVERT_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, slice, alevel, retry_cfg, 0);
+	  if (ok) {
+#if DEBUG
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("*** Success by flipping TRIPLE bit %d of %d ***\n", i, len);
+#endif
+	    return 1;
+	  }
+	}
+
+
+/*
+ * Two  non-adjacent ("separated") single bits.
+ * It chews up a lot of CPU time.  Usual test takes 4 times longer to run.
+ *
+ * Processing time is order N squared so time goes up rapidly with larger frames.
+ */
+	if (fix_bits < RETRY_INVERT_TWO_SEP) {
+	  return 0;
+	}
+
+	retry_cfg.mode = RETRY_MODE_SEPARATED;
+	retry_cfg.type = RETRY_TYPE_SWAP;
+	retry_cfg.retry = RETRY_INVERT_TWO_SEP;
+	retry_cfg.u_bits.sep.bit_idx_c = -1;
+
+#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++) {
+	    retry_cfg.u_bits.sep.bit_idx_b = j;
+	    ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, 0);
+	    if (ok) {
+	      break;
+	    }
+
+	  }	  
+	  if (ok) {
+#if DEBUG
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("*** Success by flipping TWO SEPARATED bits %d and %d of %d \n", i, j, len);
+#endif
+	    return (1);
+	  }
+	}
+
+	return 0;
+}
+
+
+
+// TODO:  Remove this.  but first figure out what to do in atest.c
+
+
+
+int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
+{
+	int ok;
+	int len, i, j;
+	retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	int passall = save_audio_config_p->achan[chan].passall;
+#if DEBUG_LATER
+	double tstart, tend;
+#endif
+	retry_conf_t retry_cfg;
+	len = rrbb_get_len(block);
+
+
+/*
+ * All fix up attempts have failed.  
+ * Should we pass it along anyhow with a bad CRC?
+ * Note that we still need a minimum number of whole octets.
+ */
+	if (passall) {
+
+	  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;
+	  ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, passall);
+	  return (ok);
+	}
+
+	return (0);
+
+}  /* end hdlc_rec2_try_to_fix_later */
+
+
+
+/* 
+ * 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;
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	try_decode
+ *
+ * Purpose:	   
+ *
+ * Inputs:	block		- Bit string that was collected between "flag" patterns.
+ *
+ *		chan, subchan	- where it came from.
+ *
+ *		alevel		- audio level for later reporting.
+ *
+ *		retry_conf	- Controls changes that will be attempted to get a good CRC.
+ *
+ *	   			retry:	
+ *					Level of effort to recover from a bad FCS on the frame.
+ *				                RETRY_NONE = 0
+ *				                RETRY_INVERT_SINGLE = 1
+ *				                RETRY_INVERT_DOUBLE = 2
+ *		                                RETRY_INVERT_TRIPLE = 3
+ *		                                RETRY_INVERT_TWO_SEP = 4
+ *
+ *	    			mode:	RETRY_MODE_CONTIGUOUS - change adjacent bits.
+ *						contig.bit_idx - first bit position
+ *						contig.nr_bits - number of bits
+ *
+ *				        RETRY_MODE_SEPARATED  - change bits not next to each other.
+ *						sep.bit_idx_a - bit positions
+ *						sep.bit_idx_b - bit positions
+ *						sep.bit_idx_c - bit positions
+ *
+ *				type:	RETRY_TYPE_NONE	- Make no changes.
+ *					RETRY_TYPE_SWAP - Try inverting.
+ *					
+ *		passall		- All it thru even with bad CRC.
+ *				  Valid only when no changes make.  i.e.
+ *					retry == RETRY_NONE, type == RETRY_TYPE_NONE
+ *
+ * Returns:	1 = successfully extracted something.
+ *		0 = failure.
+ *
+ ***********************************************************************************/
+
+static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall)
+{
+	struct hdlc_state_s H;	
+	int blen;			/* Block length in bits. */
+	int i;
+	unsigned int raw;			/* From demodulator. */
+#if DEBUGx
+	int crc_failed = 1;
+#endif
+	int retry_conf_mode = retry_conf.mode;
+	int retry_conf_type = retry_conf.type;
+	int retry_conf_retry = retry_conf.retry;
+
+
+	H.is_scrambled = rrbb_get_is_scrambled (block);
+	H.prev_descram = rrbb_get_prev_descram (block);
+	H.lfsr = rrbb_get_descram_state (block);
+	H.prev_raw = rrbb_get_bit (block, 0);	  /* Actually last bit of the */
+					/* opening flag so we can derive the */
+					/* first data bit.  */
+
+	/* Does this make sense? */
+	/* This is the last bit of the "flag" pattern. */
+	/* If it was corrupted we wouldn't have detected */
+	/* the start of frame. */
+
+	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;
+	}
+
+	H.pat_det = 0;
+	H.oacc = 0;
+	H.olen = 0;
+	H.frame_len = 0;
+
+	blen = rrbb_get_len(block);
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+        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 = rrbb_get_bit (block, i);
+	  /* If swap two sep mode , swap the bit if needed */
+	  if (retry_conf_retry == RETRY_INVERT_TWO_SEP) {
+	      if (is_sep_bit_modified(i, retry_conf))
+	        raw = ! raw;
+	  } 
+	  /* Else handle all the others contiguous modes */
+	  else if (retry_conf_mode == RETRY_MODE_CONTIGUOUS) {
+
+            if (retry_conf_type == RETRY_TYPE_SWAP) {
+	        /* If this is the bit to swap */
+	        if (is_contig_bit_modified(i, retry_conf))
+	          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 
+ */
+
+	    int dbit ;
+
+	    if (H.is_scrambled) {
+	      int descram;
+
+	      descram = descramble(raw, &(H.lfsr));
+
+	      dbit = (descram == H.prev_descram);
+	      H.prev_descram = descram;
+	      H.prev_raw = raw;
+	    }
+	    else {
+
+	      dbit = (raw == H.prev_raw);
+	      H.prev_raw = raw;
+	    }
+
+	    if (dbit) {
+
+	      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 abort, i=%d\n", i);
+#endif
+	        return 0;
+	      }
+	      H.oacc >>= 1;
+	      H.oacc |= 0x80;
+	    } else {
+	      
+	      /* 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 flag, i=%d\n", i);
+#endif
+	      return 0;
+/*
+ * If we have five '1' bits in a row, followed by a '0' bit,
+ *
+ *	011111xx
+ *
+ * the current '0' bit should be discarded because it was added for 
+ * "bit stuffing."
+ */
+	
+	      } else if ( (H.pat_det >> 2) == 0x1f ) {
+	        continue;
+	      }
+	      H.oacc >>= 1;
+	    }
+
+/*
+ * Now accumulate bits into octets, and complete octets
+ * into the frame buffer.
+ */
+
+	    H.olen++;
+
+	    if (H.olen & 8) {
+	      H.olen = 0;
+
+	      if (H.frame_len < MAX_FRAME_LEN) {
+	        H.frame_buf[H.frame_len] = H.oacc;
+		H.frame_len++;
+	      
+	      }
+	    }
+	  }	/* end of loop on all bits in block */
+/* 
+ * Do we have a minimum number of complete bytes?
+ */
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("try_decode: olen=%d, frame_len=%d\n", H.olen, H.frame_len);
+#endif
+
+	if (H.olen == 0 && H.frame_len >= MIN_FRAME_LEN) {
+
+	  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);
+	  for (j=0; j<H.frame_len; j++) {
+	    dw_printf ("  %02x", H.frame_buf[j]);
+	  }
+	  dw_printf ("\n");
+
+        }
+#endif
+	  /* Check FCS, low byte first, and process... */
+
+	  /* Alternatively, it is possible to include the two FCS bytes */
+	  /* in the CRC calculation and look for a magic constant.  */
+	  /* That would be easier in the case where the CRC is being */
+	  /* accumulated along the way as the octets are received. */
+	  /* I think making a second pass over it and comparing is */
+	  /* easier to understand. */
+
+	  actual_fcs = H.frame_buf[H.frame_len-2] | (H.frame_buf[H.frame_len-1] << 8);
+
+	  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, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) {
+
+	      // TODO: Shouldn't be necessary to pass chan, subchan, alevel into
+	      // try_decode because we can obtain them from block.
+	      // Let's make sure that assumption is good...
+
+	      assert (rrbb_get_chan(block) == chan);
+	      assert (rrbb_get_subchan(block) == subchan);
+	      multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry);   /* len-2 to remove FCS. */
+	      return 1;		/* success */
+
+	  } else if (passall) {
+	    if (retry_conf_retry == RETRY_NONE && retry_conf_type == RETRY_TYPE_NONE) {
+
+	      //text_color_set(DW_COLOR_ERROR);
+	      //dw_printf ("ATTEMPTING PASSALL PROCESSING\n");
+  
+	      multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX);   /* len-2 to remove FCS. */
+	      return 1;		/* success */
+	    }
+	    else {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("try_decode: internal error passall = %d, retry_conf_retry = %d, retry_conf_type = %d\n", 
+				passall, retry_conf_retry, retry_conf_type);
+	    }
+	  } else {
+
+              goto failure;
+          }
+	} else {
+#if DEBUGx
+              crc_failed = 0;
+#endif
+              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");
+        }
+end:
+#endif
+	return 0;	/* failure. */
+
+} /* end try_decode */
+
+
+
+/***********************************************************************************
+ *
+ * Name:	sanity_check
+ *
+ * Purpose:	Try to weed out bogus packets from initially failed FCS matches.
+ *
+ * Inputs:	buf
+ *
+ *		blen
+ *
+ *		bits_flipped
+ *
+ *		sanity		How much sanity checking to perform:
+ *					SANITY_APRS - Looks like APRS.  See User Guide,
+ *						section that discusses bad apples.
+ *					SANITY_AX25 - Has valid AX.25 address part.
+ *						No checking of the rest.  Useful for 
+ *						connected mode packet.
+ *					SANITY_NONE - No checking.  Would be suitable
+ *						only if using frames that don't conform
+ *						to AX.25 standard.
+ *
+ * Returns:	1 if it passes the sanity test.
+ *
+ * Description:	This is NOT a validity check.
+ *		We don't know if modifying the frame fixed the problem or made it worse.
+ *		We can only test if it looks reasonable.
+ *
+ ***********************************************************************************/
+
+
+static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test)
+{
+	int alen;		/* Length of address part. */
+	int j;
+
+/*
+ * No sanity check if we didn't try fixing the data.
+ * Should we have different levels of checking depending on 
+ * how much we try changing the raw data?
+ */
+	if (bits_flipped == RETRY_NONE) {
+	  return 1;
+	}
+
+
+/*
+ * If using frames that do not conform to AX.25, it might be
+ * desirable to skip the sanity check entirely.
+ */
+	if (sanity_test == SANITY_NONE) {
+	  return (1);
+	}
+
+/*
+ * Address part must be a multiple of 7. 
+ */
+
+	alen = 0;
+	for (j=0; j<blen && alen==0; j++) {
+	  if (buf[j] & 0x01) {
+	    alen = j + 1;
+	  }
+	}
+
+	if (alen % 7 != 0) {
+#if DEBUGx
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("sanity_check: FAILED.  Address part length %d not multiple of 7.\n", alen);
+#endif
+	  return 0;
+	}
+
+/*
+ * Need at least 2 addresses and maximum of 8 digipeaters. 
+ */
+
+	if (alen/7 < 2 || alen/7 > 10) {
+#if DEBUGx
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("sanity_check: FAILED.  Too few or many addresses.\n");
+#endif
+	  return 0;
+	}
+
+/* 
+ * Addresses can contain only upper case letters, digits, and space. 
+ */
+
+	for (j=0; j<alen; j+=7) {
+
+	  char addr[7];
+
+	  addr[0] = buf[j+0] >> 1;
+	  addr[1] = buf[j+1] >> 1;
+	  addr[2] = buf[j+2] >> 1;
+	  addr[3] = buf[j+3] >> 1;
+	  addr[4] = buf[j+4] >> 1;
+	  addr[5] = buf[j+5] >> 1;
+	  addr[6] = '\0';
+
+
+	  if ( (! isupper(addr[0]) && ! isdigit(addr[0])) ||
+	       (! isupper(addr[1]) && ! isdigit(addr[1]) && addr[1] != ' ') ||
+	       (! isupper(addr[2]) && ! isdigit(addr[2]) && addr[2] != ' ') ||
+	       (! isupper(addr[3]) && ! isdigit(addr[3]) && addr[3] != ' ') ||
+	       (! isupper(addr[4]) && ! isdigit(addr[4]) && addr[4] != ' ') ||
+	       (! isupper(addr[5]) && ! isdigit(addr[5]) && addr[5] != ' ')) {
+#if DEBUGx	  
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("sanity_check: FAILED.  Invalid characters in addresses \"%s\"\n", addr);
+#endif
+	    return 0;
+	  }
+	}
+
+
+/*
+ * That's good enough for the AX.25 sanity check.
+ * Continue below for additional APRS checking.
+ */
+	if (sanity_test == SANITY_AX25) {
+	  return (1);
+	}
+
+/*
+ * The next two bytes should be 0x03 and 0xf0 for APRS.
+ */
+
+	if (buf[alen] != 0x03 || buf[alen+1] != 0xf0) {
+	  return (0);
+	}
+
+/*
+ * Finally, look for bogus characters in the information part.
+ * In theory, the bytes could have any values.
+ * In practice, we find only printable ASCII characters and:
+ *	
+ *	0x0a	line feed
+ *	0x0d	carriage return	
+ *	0x1c	MIC-E
+ *	0x1d	MIC-E
+ *	0x1e	MIC-E
+ *	0x1f	MIC-E
+ *	0x7f	MIC-E
+ *	0x80	"{UIV32N}<0x0d><0x9f><0x80>"
+ *	0x9f	"{UIV32N}<0x0d><0x9f><0x80>"
+ *	0xb0	degree symbol, ISO LATIN1
+ *		  (Note: UTF-8 uses two byte sequence 0xc2 0xb0.)
+ *	0xbe	invalid MIC-E encoding.
+ *	0xf8	degree symbol, Microsoft code page 437
+ *
+ * So, if we have something other than these (in English speaking countries!), 
+ * chances are that we have bogus data from twiddling the wrong bits.
+ *
+ * Notice that we shouldn't get here for good packets.  This extra level
+ * of checking happens only if we twiddled a couple of bits, possibly
+ * creating bad data.  We want to be very fussy.
+ */
+
+ 	for (j=alen+2; j<blen; j++) {
+	  int ch = buf[j];
+
+	  if ( ! (( ch >= 0x1c && ch <= 0x7f) 
+			|| ch == 0x0a 
+			|| ch == 0x0d
+			|| ch == 0x80
+			|| ch == 0x9f
+			|| ch == 0xc2
+			|| ch == 0xb0
+			|| ch == 0xf8) ) {
+#if DEBUGx
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("sanity_check: FAILED.  Probably bogus info char 0x%02x\n", ch);
+#endif
+	    return 0;
+	  }
+	}
+
+	return 1;
+}
+
+
+/* end hdlc_rec2.c */
+
+
diff --git a/hdlc_rec2.h b/hdlc_rec2.h
index cf1608a..bb04bd7 100644
--- a/hdlc_rec2.h
+++ b/hdlc_rec2.h
@@ -1,78 +1,67 @@
-
-#ifndef HDLC_REC2_H
-#define HDLC_REC2_H 1
-
-
-#include "ax25_pad.h"	/* for packet_t, alevel_t */
-#include "rrbb.h"
-#include "audio.h"		/* for struct audio_s */
-
-
-
-
-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)
-
-static const char * retry_text[] = {
-		"NONE",
-		"SINGLE",
-		"DOUBLE",
-		"TRIPLE",
-		"REMOVE_SINGLE",
-		"REMOVE_DOUBLE",
-		"REMOVE_TRIPLE",
-		"INSERT_SINGLE",
-		"INSERT_DOUBLE",
-		"TWO_SEP",
-		"MANY",
-		"REMOVE_MANY",
-		"REMOVE_SEP",
-		"PASSALL" };
-#endif
-
-void hdlc_rec2_init (struct audio_s *audio_config_p);
-
-void hdlc_rec2_block (rrbb_t block);
-
-int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, alevel_t alevel);
-
-/* Provided by the top level application to process a complete frame. */
-
-void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t level, retry_t retries, char *spectrum);
-
-
-#endif
+
+#ifndef HDLC_REC2_H
+#define HDLC_REC2_H 1
+
+
+#include "ax25_pad.h"	/* for packet_t, alevel_t */
+#include "rrbb.h"
+#include "audio.h"		/* for struct audio_s */
+
+
+
+
+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_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)
+
+static const char * retry_text[] = {
+		"NONE",
+		"SINGLE",
+		"DOUBLE",
+		"TRIPLE",
+		"TWO_SEP",
+		"PASSALL" };
+#endif
+
+void hdlc_rec2_init (struct audio_s *audio_config_p);
+
+void hdlc_rec2_block (rrbb_t block);
+
+int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel);
+
+/* Provided by the top level application to process a complete frame. */
+
+void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, retry_t retries, char *spectrum);
+
+#endif
diff --git a/hdlc_send.c b/hdlc_send.c
index fdf0091..3511488 100644
--- a/hdlc_send.c
+++ b/hdlc_send.c
@@ -1,217 +1,217 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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 <stdio.h>
-
-#include "direwolf.h"
-#include "hdlc_send.h"
-#include "audio.h"
-#include "gen_tone.h"
-#include "textcolor.h"
-#include "fcs_calc.h"
-
-static void send_control (int, int);
-static void send_data (int, int);
-static void send_bit (int, int);
-
-
-
-static int number_of_bits_sent[MAX_CHANS];
-
-
-
-/*-------------------------------------------------------------
- *
- * Name:	hdlc_send
- *
- * Purpose:	Convert HDLC frames to a stream of bits.
- *
- * Inputs:	chan	- Audio channel number, 0 = first.
- *
- *		fbuf	- Frame buffer address.
- *
- *		flen	- Frame length, not including the FCS.
- *
- * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
- *
- * Returns:	Number of bits sent including "flags" and the
- *		stuffing bits.  
- *		The required time can be calculated by dividing this
- *		number by the transmit rate of bits/sec.
- *
- * Description:	Convert to stream of bits including:
- *			start flag
- *			bit stuffed data
- *			calculated FCS
- *			end flag
- *		NRZI encoding
- *
- * 
- * Assumptions:	It is assumed that the tone_gen module has been
- *		properly initialized so that bits sent with 
- *		tone_gen_put_bit() are processed correctly.
- *
- *--------------------------------------------------------------*/
-
-
-int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
-{
-	int j, fcs;
-	
-
-	number_of_bits_sent[chan] = 0;
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen);
-	fflush (stdout);
-#endif
-
-
-	send_control (chan, 0x7e);	/* Start frame */
-	
-	for (j=0; j<flen; j++) {
-	  send_data (chan, fbuf[j]);
-	}
-
-	fcs = fcs_calc (fbuf, flen);
-
-	send_data (chan, fcs & 0xff);
-	send_data (chan, (fcs >> 8) & 0xff);
-
-	send_control (chan, 0x7e);	/* End frame */
-
-	return (number_of_bits_sent[chan]);
-}
-
-
-/*-------------------------------------------------------------
- *
- * Name:	hdlc_send_flags
- *
- * Purpose:	Send HDLC flags before and after the frame.
- *
- * Inputs:	chan	- Audio channel number, 0 = first.
- *
- *		nflags	- Number of flag patterns to send.
- *
- *		finish	- True for end of transmission.
- *			  This causes the last audio buffer to be flushed.
- *
- * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
- *
- * Returns:	Number of bits sent.  
- *		There is no bit-stuffing so we would expect this to
- *		be 8 * nflags.
- *		The required time can be calculated by dividing this
- *		number by the transmit rate of bits/sec.
- *
- * Assumptions:	It is assumed that the tone_gen module has been
- *		properly initialized so that bits sent with 
- *		tone_gen_put_bit() are processed correctly.
- *
- *--------------------------------------------------------------*/
-
-int hdlc_send_flags (int chan, int nflags, int finish)
-{
-	int j;
-	
-
-	number_of_bits_sent[chan] = 0;
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("hdlc_send_flags ( chan = %d, nflags = %d, finish = %d )\n", chan, nflags, finish);
-	fflush (stdout);
-#endif
-
-	/* The AX.25 spec states that when the transmitter is on but not sending data */
-	/* it should send a continuous stream of "flags." */
-
-	for (j=0; j<nflags; j++) {
-	  send_control (chan, 0x7e);
-	}
-
-/* Push out the final partial buffer! */
-
-	if (finish) {
-	  audio_flush(ACHAN2ADEV(chan));
-	}
-
-	return (number_of_bits_sent[chan]);
-}
-
-
-
-static int stuff = 0;
-
-static void send_control (int chan, int x) 
-{
-	int i;
-
-	for (i=0; i<8; i++) {
-	  send_bit (chan, x & 1);
-	  x >>= 1;
-	}
-	
-	stuff = 0;
-}
-
-static void send_data (int chan, int x) 
-{
-	int i;
-
-	for (i=0; i<8; i++) {
-	  send_bit (chan, x & 1);
-	  if (x & 1) {
-	    stuff++;
-	    if (stuff == 5) {
-	      send_bit (chan, 0);
-	      stuff = 0;
-	    }
-	  } else {
-	    stuff = 0;
-          }
-	  x >>= 1;
-	}
-}
-
-/*
- * NRZI encoding.
- * data 1 bit -> no change.
- * data 0 bit -> invert signal.
- */
-
-static void send_bit (int chan, int b)
-{
-	static int output;
-
-	if (b == 0) {
-	  output = ! output;
-	}
-
-	tone_gen_put_bit (chan, output);
-
-	number_of_bits_sent[chan]++;
-}
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    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
+//    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 <stdio.h>
+
+#include "direwolf.h"
+#include "hdlc_send.h"
+#include "audio.h"
+#include "gen_tone.h"
+#include "textcolor.h"
+#include "fcs_calc.h"
+
+static void send_control (int, int);
+static void send_data (int, int);
+static void send_bit (int, int);
+
+
+
+static int number_of_bits_sent[MAX_CHANS];
+
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	hdlc_send
+ *
+ * Purpose:	Convert HDLC frames to a stream of bits.
+ *
+ * Inputs:	chan	- Audio channel number, 0 = first.
+ *
+ *		fbuf	- Frame buffer address.
+ *
+ *		flen	- Frame length, not including the FCS.
+ *
+ * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
+ *
+ * Returns:	Number of bits sent including "flags" and the
+ *		stuffing bits.  
+ *		The required time can be calculated by dividing this
+ *		number by the transmit rate of bits/sec.
+ *
+ * Description:	Convert to stream of bits including:
+ *			start flag
+ *			bit stuffed data
+ *			calculated FCS
+ *			end flag
+ *		NRZI encoding
+ *
+ * 
+ * Assumptions:	It is assumed that the tone_gen module has been
+ *		properly initialized so that bits sent with 
+ *		tone_gen_put_bit() are processed correctly.
+ *
+ *--------------------------------------------------------------*/
+
+
+int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
+{
+	int j, fcs;
+	
+
+	number_of_bits_sent[chan] = 0;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen);
+	fflush (stdout);
+#endif
+
+
+	send_control (chan, 0x7e);	/* Start frame */
+	
+	for (j=0; j<flen; j++) {
+	  send_data (chan, fbuf[j]);
+	}
+
+	fcs = fcs_calc (fbuf, flen);
+
+	send_data (chan, fcs & 0xff);
+	send_data (chan, (fcs >> 8) & 0xff);
+
+	send_control (chan, 0x7e);	/* End frame */
+
+	return (number_of_bits_sent[chan]);
+}
+
+
+/*-------------------------------------------------------------
+ *
+ * Name:	hdlc_send_flags
+ *
+ * Purpose:	Send HDLC flags before and after the frame.
+ *
+ * Inputs:	chan	- Audio channel number, 0 = first.
+ *
+ *		nflags	- Number of flag patterns to send.
+ *
+ *		finish	- True for end of transmission.
+ *			  This causes the last audio buffer to be flushed.
+ *
+ * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
+ *
+ * Returns:	Number of bits sent.  
+ *		There is no bit-stuffing so we would expect this to
+ *		be 8 * nflags.
+ *		The required time can be calculated by dividing this
+ *		number by the transmit rate of bits/sec.
+ *
+ * Assumptions:	It is assumed that the tone_gen module has been
+ *		properly initialized so that bits sent with 
+ *		tone_gen_put_bit() are processed correctly.
+ *
+ *--------------------------------------------------------------*/
+
+int hdlc_send_flags (int chan, int nflags, int finish)
+{
+	int j;
+	
+
+	number_of_bits_sent[chan] = 0;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("hdlc_send_flags ( chan = %d, nflags = %d, finish = %d )\n", chan, nflags, finish);
+	fflush (stdout);
+#endif
+
+	/* The AX.25 spec states that when the transmitter is on but not sending data */
+	/* it should send a continuous stream of "flags." */
+
+	for (j=0; j<nflags; j++) {
+	  send_control (chan, 0x7e);
+	}
+
+/* Push out the final partial buffer! */
+
+	if (finish) {
+	  audio_flush(ACHAN2ADEV(chan));
+	}
+
+	return (number_of_bits_sent[chan]);
+}
+
+
+
+static int stuff = 0;
+
+static void send_control (int chan, int x) 
+{
+	int i;
+
+	for (i=0; i<8; i++) {
+	  send_bit (chan, x & 1);
+	  x >>= 1;
+	}
+	
+	stuff = 0;
+}
+
+static void send_data (int chan, int x) 
+{
+	int i;
+
+	for (i=0; i<8; i++) {
+	  send_bit (chan, x & 1);
+	  if (x & 1) {
+	    stuff++;
+	    if (stuff == 5) {
+	      send_bit (chan, 0);
+	      stuff = 0;
+	    }
+	  } else {
+	    stuff = 0;
+          }
+	  x >>= 1;
+	}
+}
+
+/*
+ * NRZI encoding.
+ * data 1 bit -> no change.
+ * data 0 bit -> invert signal.
+ */
+
+static void send_bit (int chan, int b)
+{
+	static int output;
+
+	if (b == 0) {
+	  output = ! output;
+	}
+
+	tone_gen_put_bit (chan, output);
+
+	number_of_bits_sent[chan]++;
+}
+
 /* end hdlc_send.c */
\ No newline at end of file
diff --git a/hdlc_send.h b/hdlc_send.h
index dc6c304..41d44b1 100644
--- a/hdlc_send.h
+++ b/hdlc_send.h
@@ -1,10 +1,10 @@
-
-/* hdlc_send.h */
-
-int hdlc_send_frame (int chan, unsigned char *fbuf, int flen);
-
-int hdlc_send_flags (int chan, int flags, int finish);
-
-/* end hdlc_send.h */
-
-
+
+/* hdlc_send.h */
+
+int hdlc_send_frame (int chan, unsigned char *fbuf, int flen);
+
+int hdlc_send_flags (int chan, int flags, int finish);
+
+/* end hdlc_send.h */
+
+
diff --git a/igate.c b/igate.c
index c498430..16041b6 100644
--- a/igate.c
+++ b/igate.c
@@ -1,1532 +1,2076 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      igate.c
- *
- * Purpose:   	IGate client.
- *		
- * Description:	Establish connection with a tier 2 IGate server
- *		and relay packets between RF and Internet.
- *
- * References:	APRS-IS (Automatic Packet Reporting System-Internet Service)
- *		http://www.aprs-is.net/Default.aspx
- *
- *		APRS iGate properties
- *		http://wiki.ham.fi/APRS_iGate_properties
- *
- *---------------------------------------------------------------*/
-
-/*------------------------------------------------------------------
- *
- * From http://windows.microsoft.com/en-us/windows7/ipv6-frequently-asked-questions
- * 
- * How can I enable IPv6?
- * Follow these steps:
- * 
- * Open Network Connections by clicking the Start button, and then clicking 
- * Control Panel. In the search box, type adapter, and then, under Network 
- * and Sharing Center, click View network connections.
- * 
- * Right-click your network connection, and then click Properties.   
- * If you're prompted for an administrator password or confirmation, type 
- * the password or provide confirmation.
- *
- * Select the check box next to Internet Protocol Version 6 (TCP/IPv6).
- *
- *---------------------------------------------------------------*/
-
-/*
- * Native Windows:	Use the Winsock interface.
- * Linux:		Use the BSD socket interface.
- * Cygwin:		Can use either one.
- */
-
-
-#if __WIN32__
-
-/* The goal is to support Windows XP and later. */
-
-#include <winsock2.h>
-// default is 0x0400
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0501	/* Minimum OS version is XP. */
-//#define _WIN32_WINNT 0x0502	/* Minimum OS version is XP with SP2. */
-//#define _WIN32_WINNT 0x0600	/* Minimum OS version is Vista. */
-#include <ws2tcpip.h>
-#else 
-#include <stdlib.h>
-#include <netdb.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#endif
-
-#include <unistd.h>
-#include <stdio.h>
-#include <assert.h>
-
-#include <string.h>
-
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "version.h"
-#include "digipeater.h"
-#include "tq.h"
-#include "igate.h"
-#include "latlong.h"
-#include "pfilter.h"
-
-
-#if __WIN32__
-static unsigned __stdcall connnect_thread (void *arg);
-static unsigned __stdcall igate_recv_thread (void *arg);
-#else
-static void * connnect_thread (void *arg);
-static void * igate_recv_thread (void *arg);
-#endif
-
-static void send_msg_to_server (char *msg);
-static void xmit_packet (char *message);
-
-static void rx_to_ig_init (void);
-static void rx_to_ig_remember (packet_t pp);
-static int rx_to_ig_allow (packet_t pp);
-
-static void ig_to_tx_init (void);
-static void ig_to_tx_remember (packet_t pp);
-static int ig_to_tx_allow (packet_t pp);
-
-
-/* 
- * File descriptor for socket to IGate server. 
- * Set to -1 if not connected. 
- * (Don't use SOCKET type because it is unsigned.) 
-*/
-
-static volatile int igate_sock = -1;	
-
-/*
- * After connecting to server, we want to make sure
- * that the login sequence is sent first.
- * This is set to true after the login is complete.
- */
-
-static volatile int ok_to_send = 0;
-
-
-
-
-/*
- * Convert Internet address to text.
- * Can't use InetNtop because it is supported only on Windows Vista and later. 
- */
-
-static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
-{
-	struct sockaddr_in *sa4;
-	struct sockaddr_in6 *sa6;
-
-	switch (Family) {
-	  case AF_INET:
-	    sa4 = (struct sockaddr_in *)pAddr;
-#if __WIN32__
-	    sprintf (pStringBuf, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
-						sa4->sin_addr.S_un.S_un_b.s_b2,
-						sa4->sin_addr.S_un.S_un_b.s_b3,
-						sa4->sin_addr.S_un.S_un_b.s_b4);
-#else
-	    inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
-#endif
-	    break;
-	  case AF_INET6:
-	    sa6 = (struct sockaddr_in6 *)pAddr;
-#if __WIN32__
-	    sprintf (pStringBuf, "%x:%x:%x:%x:%x:%x:%x:%x",  
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
-#else
-	    inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
-#endif
-	    break;
-	  default:
-	    sprintf (pStringBuf, "Invalid address family!");
-	}
-	assert (strlen(pStringBuf) < StringBufSize);
-	return pStringBuf;
-}
-
-
-#if ITEST
-
-/* For unit testing. */
-
-int main (int argc, char *argv[])
-{
-	struct audio_s audio_config;
-	struct igate_config_s igate_config;
-	struct digi_config_s digi_config;
-	packet_t pp;
-
-	memset (&audio_config, 0, sizeof(audio_config));
-	audio_config.adev[0].num_chans = 2;
-	strcpy (audio_config.achan[0].mycall, "WB2OSZ-1");
-	strcpy (audio_config.achan[0].mycall, "WB2OSZ-2");
-
-	memset (&igate_config, 0, sizeof(igate_config));
-
-	strcpy (igate_config.t2_server_name, "localhost");
-	igate_config.t2_server_port = 14580;
-	strcpy (igate_config.t2_login, "WB2OSZ-JL");
-	strcpy (igate_config.t2_passcode, "-1");
-	igate_config.t2_filter = strdup ("r/1/2/3");
-	
-	igate_config.tx_chan = 0;
-	strcpy (igate_config.tx_via, ",WIDE2-1");
-	igate_config.tx_limit_1 = 3;
-	igate_config.tx_limit_5 = 5;
-
-	memset (&digi_config, 0, sizeof(digi_config));
-
-	igate_init(&igate_config, &digi_config);
-
-	while (igate_sock == -1) {
-	  SLEEP_SEC(1);
-	}
-
-	SLEEP_SEC (2);
-	pp = ax25_from_text ("A>B,C,D:Ztest message 1", 0);
-	igate_send_rec_packet (0, pp);
-	ax25_delete (pp);
-
-	SLEEP_SEC (2);
-	pp = ax25_from_text ("A>B,C,D:Ztest message 2", 0);
-	igate_send_rec_packet (0, pp);
-	ax25_delete (pp);
-
-	SLEEP_SEC (2);
-	pp = ax25_from_text ("A>B,C,D:Ztest message 2", 0);   /* Should suppress duplicate. */
-	igate_send_rec_packet (0, pp);
-	ax25_delete (pp);
-
-	SLEEP_SEC (2);
-	pp = ax25_from_text ("A>B,TCPIP,D:ZShould drop this due to path", 0);
-	igate_send_rec_packet (0, pp);
-	ax25_delete (pp);
-
-	SLEEP_SEC (2);
-	pp = ax25_from_text ("A>B,C,D:?Should drop query", 0);
-	igate_send_rec_packet (0, pp);
-	ax25_delete (pp);
-
-	SLEEP_SEC (5);
-	pp = ax25_from_text ("A>B,C,D:}E>F,G*,H:Zthird party stuff", 0);
-	igate_send_rec_packet (0, pp);
-	ax25_delete (pp);
-
-#if 1
-	while (1) {
-	  SLEEP_SEC (20);
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Send received packet\n");
-	  send_msg_to_server ("W1ABC>APRS:?\r\n");
-	}
-#endif
-	return 0;
-}
-
-#endif
-
-
-/*
- * Global stuff (to this file)
- *
- * These are set by init function and need to 
- * be kept around in case connection is lost and
- * we need to reestablish the connection later.
- */
-
-
-static struct audio_s		*save_audio_config_p;
-static struct igate_config_s	*save_igate_config_p;
-static struct digi_config_s 	*save_digi_config_p;
-
-/*
- * Statistics.  
- * TODO: need print function.
- */
-
-static int stats_failed_connect;	/* Number of times we tried to connect to */
-					/* a server and failed.  A small number is not */
-					/* a bad thing.  Each name should have a bunch */
-					/* of addresses for load balancing and */
-					/* redundancy. */
-
-static int stats_connects;		/* Number of successful connects to a server. */
-					/* Normally you'd expect this to be 1.  */
-					/* Could be larger if one disappears and we */
-					/* try again to find a different one. */
-
-static time_t stats_connect_at;		/* Most recent time connection was established. */
-					/* can be used to determine elapsed connect time. */
-
-static int stats_rf_recv_packets;	/* Number of candidate packets from the radio. */
-
-static int stats_rx_igate_packets;	/* Number of packets passed along to the IGate */
-					/* server after filtering. */
-
-static int stats_uplink_bytes;		/* Total number of bytes sent to IGate server */
-					/* including login, packets, and hearbeats. */
-
-static int stats_downlink_bytes;	/* Total number of bytes from IGate server including */
-					/* packets, heartbeats, other messages. */
-
-static int stats_tx_igate_packets;	/* Number of packets from IGate server. */
-
-static int stats_rf_xmit_packets;	/* Number of packets passed along to radio */
-					/* after rate limiting or other restrictions. */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        igate_init
- *
- * Purpose:     One time initialization when main application starts up.
- *
- * Inputs:	p_audio_config	- Audio channel configuration.  All we care about is:
- *				  - Number of radio channels.
- *				  - Radio call and SSID for each channel.
- *
- *		p_igate_config	- IGate configuration.
- *
- *		p_digi_config	- Digipeater configuration.  
- *				  All we care about here is the packet filtering options.
- *
- * Description:	This starts two threads:
- *
- *		  *  to establish and maintain a connection to the server.
- *		  *  to listen for packets from the server.
- *
- *--------------------------------------------------------------------*/
-
-
-void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config)
-{
-#if __WIN32__
-	HANDLE connnect_th;
-	HANDLE cmd_recv_th;
-#else
-	pthread_t connect_listen_tid;
-	pthread_t cmd_listen_tid;
-	int e;
-#endif
-	int j;
-
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("igate_init ( %s, %d, %s, %s, %s )\n", 
-				p_igate_config->t2_server_name, 
-				p_igate_config->t2_server_port, 
-				p_igate_config->t2_login, 
-				p_igate_config->t2_passcode, 
-				p_igate_config->t2_filter);
-#endif
-
-/*
- * Save the arguments for later use.
- */
-	save_audio_config_p = p_audio_config;
-	save_igate_config_p = p_igate_config;
-	save_digi_config_p = p_digi_config;
-
-	stats_failed_connect = 0;	
-	stats_connects = 0;		
-	stats_connect_at = 0;		
-	stats_rf_recv_packets = 0;	
-	stats_rx_igate_packets = 0;	
-	stats_uplink_bytes = 0;		
-	stats_downlink_bytes = 0;	
-	stats_tx_igate_packets = 0;	
-	stats_rf_xmit_packets = 0;
-	
-	rx_to_ig_init ();
-	ig_to_tx_init ();
-
-
-/*
- * Continue only if we have server name, login, and passcode.
- */
-	if (strlen(p_igate_config->t2_server_name) == 0 ||
-	    strlen(p_igate_config->t2_login) == 0 ||
-	    strlen(p_igate_config->t2_passcode) == 0) {
-	  return;
-	}
-
-/*
- * This connects to the server and sets igate_sock.
- * It also sends periodic messages to say I'm still alive.
- */
-
-#if __WIN32__
-	connnect_th = (HANDLE)_beginthreadex (NULL, 0, connnect_thread, (void *)NULL, 0, NULL);
-	if (connnect_th == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error: Could not create IGate connection thread\n");
-	  return;
-	}
-#else
-	e = pthread_create (&connect_listen_tid, NULL, connnect_thread, (void *)NULL);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Internal error: Could not create IGate connection thread");
-	  return;
-	}
-#endif
-
-/*
- * This reads messages from client when igate_sock is valid.
- */
-
-#if __WIN32__
-	cmd_recv_th = (HANDLE)_beginthreadex (NULL, 0, igate_recv_thread, NULL, 0, NULL);
-	if (cmd_recv_th == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error: Could not create IGate reading thread\n");
-	  return;
-	}
-#else
-	e = pthread_create (&cmd_listen_tid, NULL, igate_recv_thread, NULL);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Internal error: Could not create IGate reading thread");
-	  return;
-	}
-#endif
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        connnect_thread
- *
- * Purpose:     Establish connection with IGate server.
- *		Send periodic heartbeat to keep keep connection active.
- *		Reconnect if something goes wrong and we got disconnected.
- *
- * Inputs:	arg		- Not used.
- *
- * Outputs:	igate_sock	- File descriptor for communicating with client app.
- *				  Will be -1 if not connected.
- *
- * References:	TCP client example.
- *		http://msdn.microsoft.com/en-us/library/windows/desktop/ms737591(v=vs.85).aspx
- *
- *		Linux IPv6 HOWTO
- *		http://www.tldp.org/HOWTO/Linux+IPv6-HOWTO/
- *
- *--------------------------------------------------------------------*/
-
-/*
- * Addresses don't get mixed up very well.
- * IPv6 always shows up last so we'd probably never
- * end up using any of them.  Use our own shuffle.
- */
-
-static void shuffle (struct addrinfo *host[], int nhosts)
-{
-        int j, k;
-
-        assert (RAND_MAX >= nhosts);  /* for % to work right */
-
-        if (nhosts < 2) return;
-
-        srand (time(NULL));
-
-        for (j=0; j<nhosts; j++) {
-          k = rand() % nhosts;
-          assert (k >=0 && k<nhosts);
-          if (j != k) {
-            struct addrinfo *temp;
-            temp = host[j]; host[j] = host[k]; host[k] = temp;
-          }
-        }
-}
-
-#define MAX_HOSTS 50
-
-
-
-
-#if __WIN32__
-static unsigned __stdcall connnect_thread (void *arg)
-#else
-static void * connnect_thread (void *arg)	
-#endif	
-{
-	struct addrinfo hints;
-	struct addrinfo *ai_head = NULL;
-	struct addrinfo *ai;
-	struct addrinfo *hosts[MAX_HOSTS];
-	int num_hosts, n;
-	int err;
-	char server_port_str[12];	/* text form of port number */
-	char ipaddr_str[46];		/* text form of IP address */
-#if __WIN32__
-	WSADATA wsadata;
-#endif
-
-	sprintf (server_port_str, "%d", save_igate_config_p->t2_server_port);
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-        dw_printf ("DEBUG: igate connect_thread start, port = %d = '%s'\n", save_igate_config_p->t2_server_port, server_port_str);
-#endif
-
-#if __WIN32__
-	err = WSAStartup (MAKEWORD(2,2), &wsadata);
-	if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("WSAStartup failed: %d\n", err);
-	    return (0);
-	}
-
-	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Could not find a usable version of Winsock.dll\n");
-          WSACleanup();
-	  //sleep (1);
-          return (0);
-	}
-#endif
-
-	memset (&hints, 0, sizeof(hints));
-
-	hints.ai_family = AF_UNSPEC;	/* Allow either IPv4 or IPv6. */
-
-	// IPv6 is half baked on Windows XP.
-	// We might need to leave out IPv6 support for Windows version.
-	// hints.ai_family = AF_INET;	/* IPv4 only. */
-
-#if IPV6_ONLY
-	/* IPv6 addresses always show up at end of list. */
-	/* Force use of them for testing. */
-	hints.ai_family = AF_INET6;	/* IPv6 only */
-#endif					
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_protocol = IPPROTO_TCP;
-
-
-/*
- * Repeat forever.
- */
-
-	while (1) {
-
-/*
- * Connect to IGate server if not currently connected.
- */
-
-	  if (igate_sock == -1) {
-
-	    SLEEP_SEC (5);
-
-	    ai_head = NULL;
-	    err = getaddrinfo(save_igate_config_p->t2_server_name, server_port_str, &hints, &ai_head);
-	    if (err != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-#if __WIN32__
-	      dw_printf ("Can't get address for IGate server %s, err=%d\n", 
-					save_igate_config_p->t2_server_name, WSAGetLastError());
-#else 
-	      dw_printf ("Can't get address for IGate server %s, %s\n", 
-					save_igate_config_p->t2_server_name, gai_strerror(err));
-#endif
-	      freeaddrinfo(ai_head);
-
-	      continue;
-	    }
-
-#if DEBUG_DNS
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("getaddrinfo returns:\n");
-#endif
-	    num_hosts = 0;
-	    for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
-#if DEBUG_DNS
-	      text_color_set(DW_COLOR_DEBUG);
-	      ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	      dw_printf ("    %s\n", ipaddr_str);
-#endif
-	      hosts[num_hosts] = ai;
-	      if (num_hosts < MAX_HOSTS) num_hosts++;
-	    }
-
-	    // We can get multiple addresses back for the host name.
-	    // These should be somewhat randomized for load balancing. 
-	    // It turns out the IPv6 addresses are always at the 
-	    // end for both Windows and Linux.   We do our own shuffling
-	    // to mix them up better and give IPv6 a chance. 
-
-	    shuffle (hosts, num_hosts);
-
-#if DEBUG_DNS
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("after shuffling:\n");
-	    for (n=0; n<num_hosts; n++) {
-	      ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	      dw_printf ("    %s\n", ipaddr_str);
-	    }
-#endif
-
-	    // Try each address until we find one that is successful.
-
-	    for (n=0; n<num_hosts; n++) {
-	      int is;
-
-	      ai = hosts[n];
-
-	      ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	      is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-#if __WIN32__
-	      if (is == INVALID_SOCKET) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("IGate: Socket creation failed, err=%d", WSAGetLastError());
-	        WSACleanup();
-	        is = -1;
-		stats_failed_connect++;
-	        continue;
-	      }
-#else
-	      if (err != 0) {
-	        text_color_set(DW_COLOR_INFO);
-	        dw_printf("Connect to IGate server %s (%s) failed.\n\n",
-					save_igate_config_p->t2_server_name, ipaddr_str);
-	        (void) close (is);
-	        is = -1;
-		stats_failed_connect++;
-	        continue;
-	      }
-#endif
-
-#ifndef DEBUG_DNS 
-	      err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
-#if __WIN32__
-	      if (err == SOCKET_ERROR) {
-	        text_color_set(DW_COLOR_INFO);
-	        dw_printf("Connect to IGate server %s (%s) failed.\n\n",
-					save_igate_config_p->t2_server_name, ipaddr_str);
-	        closesocket (is);
-	        is = -1;
-		stats_failed_connect++; 
-	        continue;
-	      }
-	      // TODO: set TCP_NODELAY?
-#else
-	      if (err != 0) {
-	        text_color_set(DW_COLOR_INFO);
-	        dw_printf("Connect to IGate server %s (%s) failed.\n\n",
-					save_igate_config_p->t2_server_name, ipaddr_str);
-	        (void) close (is);
-	        is = -1;
-		stats_failed_connect++;
-	        continue;
-	      }
-	      /* IGate documentation says to use it.  */
-	      /* Does it really make a difference for this application? */
-	      int flag = 1;
-	      err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), sizeof(flag));
-	      if (err < 0) {
-	        text_color_set(DW_COLOR_INFO);
-	        dw_printf("setsockopt TCP_NODELAY failed.\n");
-	      }
-#endif
-	      stats_connects++;
-	      stats_connect_at = time(NULL);
-
-/* Success. */
-
-	      text_color_set(DW_COLOR_INFO);
- 	      dw_printf("\nNow connected to IGate server %s (%s)\n", save_igate_config_p->t2_server_name, ipaddr_str );
-	      if (strchr(ipaddr_str, ':') != NULL) {
-	      	dw_printf("Check server status here http://[%s]:14501\n\n", ipaddr_str);
-	      }
-	      else {
-	        dw_printf("Check server status here http://%s:14501\n\n", ipaddr_str);
-	      }
-
-/* 
- * Set igate_sock so everyone else can start using it. 
- * But make the Rx -> Internet messages wait until after login.
- */
-
-	      ok_to_send = 0;
-	      igate_sock = is;
-#endif	  
-	      break;
-	    }
-
-	    freeaddrinfo(ai_head);
-
-	    if (igate_sock != -1) {
-	      char stemp[256];
-
-/* 
- * Send login message.
- * Software name and version must not contain spaces.
- */
-
-	      SLEEP_SEC(3);
-	      sprintf (stemp, "user %s pass %s vers Dire-Wolf %d.%d", 
-			save_igate_config_p->t2_login, save_igate_config_p->t2_passcode,
-			MAJOR_VERSION, MINOR_VERSION);
-	      if (save_igate_config_p->t2_filter != NULL) {
-	        strcat (stemp, " filter ");
-	        strcat (stemp, save_igate_config_p->t2_filter);
-	      }
-	      strcat (stemp, "\r\n");
-	      send_msg_to_server (stemp);
-
-/* Delay until it is ok to start sending packets. */
-
-	      SLEEP_SEC(7);
-	      ok_to_send = 1;
-	    }
-	  }
-
-/*
- * If connected to IGate server, send heartbeat periodically to keep connection active.
- */
-	  if (igate_sock != -1) {
-	    SLEEP_SEC(10);
-	  }
-	  if (igate_sock != -1) {
-	    SLEEP_SEC(10);
-	  }
-	  if (igate_sock != -1) {
-	    SLEEP_SEC(10);
-	  }
-
-
-	  if (igate_sock != -1) {
-
-	    char heartbeat[10];
-
-	    strcpy (heartbeat, "#\r\n");
-
-	    /* This will close the socket if any error. */
-	    send_msg_to_server (heartbeat);
-
-	  }
-	}
-} /* end connnect_thread */
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        igate_send_rec_packet
- *
- * Purpose:     Send a packet to the IGate server
- *
- * Inputs:	chan	- Radio channel it was received on.
- *
- *		recv_pp	- Pointer to packet object.
- *			  *** CALLER IS RESPONSIBLE FOR DELETING IT! **
- *		
- *
- * Description:	Send message to IGate Server if connected.
- *
- * Assumptions:	(1) Caller has already verified it is an APRS packet.
- *		i.e. control = 3 for UI frame, protocol id = 0xf0 for no layer 3
- *
- *		(2) This is being called only for packets received with
- *		a correct CRC.  We don't want to propagate corrupted data.
- *
- *--------------------------------------------------------------------*/
-
-void igate_send_rec_packet (int chan, packet_t recv_pp)
-{
-	packet_t pp;
-	int n;
-	unsigned char *pinfo;
-	char *p;
-	char msg[520];		/* Message to IGate max 512 characters. */
-	int info_len;
-	
-
-	if (igate_sock == -1) {
-	  return;	/* Silently discard if not connected. */
-	}
-
-	if ( ! ok_to_send) {
-	  return;	/* Login not complete. */
-	}
-
-/*
- * Check for filtering from specified channel to the IGate server.
- */
-
-	if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) {
-
-	  if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp) != 1) {
-
-// TODO1.2: take out debug message.  
-//#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]);
-//#endif
-	    return;
-	  }
-	}
-
-
-	/* Count only while connected. */
-	stats_rf_recv_packets++;
-
-/*
- * First make a copy of it because it might be modified in place.
- */
-
-	pp = ax25_dup (recv_pp);
-
-/*
- * Third party frames require special handling to unwrap payload.
- */
-	while (ax25_get_dti(pp) == '}') {
-	  packet_t inner_pp;
-
-	  for (n = 0; n < ax25_get_num_repeaters(pp); n++) {
-	    char via[AX25_MAX_ADDR_LEN];	/* includes ssid. Do we want to ignore it? */
-
-	    ax25_get_addr_with_ssid (pp, n + AX25_REPEATER_1, via);
-
-	    if (strcmp(via, "TCPIP") == 0 ||
-	        strcmp(via, "TCPXX") == 0 ||
-	        strcmp(via, "RFONLY") == 0 ||
-	        strcmp(via, "NOGATE") == 0) {
-#if DEBUGx
-	      text_color_set(DW_COLOR_DEBUG);
-	      dw_printf ("Rx IGate: Do not relay with TCPIP etc. in path.\n");
-#endif
-	      ax25_delete (pp);
-	      return;
-	    }
-	  }
-
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Rx IGate: Unwrap third party message.\n");
-#endif
-	  inner_pp = ax25_unwrap_third_party(pp);
-	  if (inner_pp == NULL) {
-	    ax25_delete (pp);
-	    return;
-	  }
-	  ax25_delete (pp);
-	  pp = inner_pp;
-	}
-
-/* 
- * Do not relay packets with TCPIP, TCPXX, RFONLY, or NOGATE in the via path.
- */
-	for (n = 0; n < ax25_get_num_repeaters(pp); n++) {
-	  char via[AX25_MAX_ADDR_LEN];	/* includes ssid. Do we want to ignore it? */
-
-	  ax25_get_addr_with_ssid (pp, n + AX25_REPEATER_1, via);
-
-	  if (strcmp(via, "TCPIP") == 0 ||
-	      strcmp(via, "TCPXX") == 0 ||
-	      strcmp(via, "RFONLY") == 0 ||
-	      strcmp(via, "NOGATE") == 0) {
-#if DEBUGx
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("Rx IGate: Do not relay with TCPIP etc. in path.\n");
-#endif
-	    ax25_delete (pp);
-	    return;
-	  }
-	}
-
-/*
- * Do not relay generic query.
- */
-	if (ax25_get_dti(pp) == '?') {
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Rx IGate: Do not relay generic query.\n");
-#endif
-	  ax25_delete (pp);
-	  return;
-	}
-
-
-/*
- * Cut the information part at the first CR or LF.
- */
-
-	info_len = ax25_get_info (pp, &pinfo);
-
-	if ((p = strchr ((char*)pinfo, '\r')) != NULL) {
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Rx IGate: Truncated information part at CR.\n");
-#endif
-          *p = '\0';
-	}
-
-	if ((p = strchr ((char*)pinfo, '\n')) != NULL) {
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Rx IGate: Truncated information part at LF.\n");
-#endif
-          *p = '\0';
-	}
-
-
-/*
- * Someone around here occasionally sends a packet with no information part.
- */
-	if (strlen(pinfo) == 0) {
-
-#if DEBUGx
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Rx IGate: Information part length is zero.\n");
-#endif
-	  ax25_delete (pp);
-	  return;
-	}
-
-// TODO: Should we drop raw touch tone data object type generated here?
-
-/*
- * Do not relay if a duplicate of something sent recently.
- */
-
-	if ( ! rx_to_ig_allow(pp)) {
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("Rx IGate: Drop duplicate of same packet seen recently.\n");
-#endif
-	  ax25_delete (pp);
-	  return;
-	}
-
-/* 
- * Finally, append ",qAR," and my call to the path.
- */
-
-	ax25_format_addrs (pp, msg);
-	msg[strlen(msg)-1] = '\0';    /* Remove trailing ":" */
-	strcat (msg, ",qAR,");
-	strcat (msg, save_audio_config_p->achan[chan].mycall);
-	strcat (msg, ":");
-	strcat (msg, (char*)pinfo);
-	strcat (msg, "\r\n");
-
-	send_msg_to_server (msg);
-	stats_rx_igate_packets++;
-
-/*
- * Remember what was sent to avoid duplicates in near future.
- */
-	rx_to_ig_remember (pp);
-
-	ax25_delete (pp);
-
-} /* end igate_send_rec_packet */
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        send_msg_to_server
- *
- * Purpose:     Send to the IGate server.
- *		This one function should be used for login, hearbeats,
- *		and packets.
- *
- * Inputs:	msg	- Message.  Should end with CR/LF.
- *		
- *
- * Description:	Send message to IGate Server if connected.
- *		Disconnect from server, and notify user, if any error.
- *
- *--------------------------------------------------------------------*/
-
-
-static void send_msg_to_server (char *msg)
-{
-	int err;
-
-
-	if (igate_sock == -1) {
-	  return;	/* Silently discard if not connected. */
-	}
-
-	stats_uplink_bytes += strlen(msg);
-
-#if DEBUG
-	text_color_set(DW_COLOR_XMIT);
-	dw_printf ("[ig] ");
-	ax25_safe_print (msg, strlen(msg), 0);
-	dw_printf ("\n");
-#endif
-
-#if __WIN32__	
-        err = send (igate_sock, msg, strlen(msg), 0);
-	if (err == SOCKET_ERROR)
-	{
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError %d sending message to IGate server.  Closing connection.\n\n", WSAGetLastError());
-	  //dw_printf ("DEBUG: igate_sock=%d, line=%d\n", igate_sock, __LINE__);
-	  closesocket (igate_sock);
-	  igate_sock = -1;
-	  WSACleanup();
-	}
-#else
-        err = write (igate_sock, msg, strlen(msg));
-	if (err <= 0)
-	{
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError sending message to IGate server.  Closing connection.\n\n");
-	  close (igate_sock);
-	  igate_sock = -1;    
-	}
-#endif
-	
-} /* end send_msg_to_server */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        get1ch
- *
- * Purpose:     Read one byte from socket.
- *
- * Inputs:	igate_sock	- file handle for socket.
- *
- * Returns:	One byte from stream.
- *		Waits and tries again later if any error.
- *
- *
- *--------------------------------------------------------------------*/
-
-static int get1ch (void)
-{
-	unsigned char ch;
-	int n;
-
-	while (1) {
-
-	  while (igate_sock == -1) {
-	    SLEEP_SEC(5);			/* Not connected.  Try again later. */
-	  }
-
-	  /* Just get one byte at a time. */
-	  // TODO: might read complete packets and unpack from own buffer
-	  // rather than using a system call for each byte.
-
-#if __WIN32__
-	  n = recv (igate_sock, (char*)(&ch), 1, 0);
-#else
-	  n = read (igate_sock, &ch, 1);
-#endif
-
-	  if (n == 1) {
-#if DEBUG9
-	    dw_printf (log_fp, "%02x %c %c", ch, 
-			isprint(ch) ? ch : '.' , 
-			(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
-	    if (ch == '\r') fprintf (log_fp, "  CR");
-	    if (ch == '\n') fprintf (log_fp, "  LF");
-	    fprintf (log_fp, "\n");
-#endif
-	    return(ch);	
-	  }
-
-          text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError reading from IGate server.  Closing connection.\n\n");
-#if __WIN32__
-	  closesocket (igate_sock);
-#else
-	  close (igate_sock);
-#endif
-	  igate_sock = -1;
-	}
-
-} /* end get1ch */
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        igate_recv_thread
- *
- * Purpose:     Wait for messages from IGate Server.
- *
- * Inputs:	arg		- Not used.
- *
- * Outputs:	igate_sock	- File descriptor for communicating with client app.
- *
- * Description:	Process messages from the IGate server.
- *
- *--------------------------------------------------------------------*/
-
-#if __WIN32__
-static unsigned __stdcall igate_recv_thread (void *arg)
-#else
-static void * igate_recv_thread (void *arg)
-#endif
-{
-	unsigned char ch;
-	unsigned char message[1000];  // Spec says max 500 or so.
-	int len;
-	
-			
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("igate_recv_thread ( socket = %d )\n", igate_sock);
-#endif
-
-	while (1) {
-
-	  len = 0;
-
-	  do
-	  {
-	    ch = get1ch();
-	    stats_downlink_bytes++;
-
-	    if (len < sizeof(message)) 
-	    {
-	      message[len] = ch;
-	    }
-	    len++;
-	    
-	  } while (ch != '\n');
-
-/*
- * We have a complete message terminated by LF.
- */
-	  if (len == 0) 
-	  {
-/* 
- * Discard if zero length. 
- */
-	  }
-	  else if (message[0] == '#') {
-/*
- * Heartbeat or other control message.
- *
- * Print only if within seconds of logging in.
- * That way we can see login confirmation but not 
- * be bothered by the heart beat messages.
- */
-#ifndef DEBUG
-	    if ( ! ok_to_send) {
-#endif
-	      text_color_set(DW_COLOR_REC);
-	      dw_printf ("[ig] ");
-	      ax25_safe_print ((char *)message, len, 0);
-	      dw_printf ("\n");
-#ifndef DEBUG
-	    }
-#endif
-	  }
-	  else 
-	  {
-/*
- * Convert to third party packet and transmit.
- */
-	    
-	    text_color_set(DW_COLOR_REC);
-	    dw_printf ("\n[ig] ");
-	    ax25_safe_print ((char *)message, len, 0);
-	    dw_printf ("\n");
-
-/*
- * Remove CR LF from end.
- */
-	    if (len >=2 && message[len-1] == '\n') { message[len-1] = '\0'; len--; }
-	    if (len >=1 && message[len-1] == '\r') { message[len-1] = '\0'; len--; }
-
-	    xmit_packet ((char*)message);
-	  }
-
-	}  /* while (1) */
-	return (0);
-
-} /* end igate_recv_thread */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        xmit_packet
- *
- * Purpose:     Convert text string, from IGate server, to third party
- *		packet and send to transmit queue.
- *
- * Inputs:	message		- As sent by the server.  
- *
- *--------------------------------------------------------------------*/
-
-static void xmit_packet (char *message)
-{
-	packet_t pp3;
-	char payload[500];	/* what is max len? */
-	char *pinfo = NULL;
-	int info_len;
-	int to_chan = save_igate_config_p->tx_chan;	/* which could be -1 if not configured for xmit!!! */
-
-/*
- * Is IGate to Radio direction enabled?
- */
-	if (to_chan == -1) {
-	  return;
-	}
-
-	stats_tx_igate_packets++;
-
-	assert (save_igate_config_p->tx_chan >= 0 && save_igate_config_p->tx_chan < MAX_CHANS);
-
-/*
- * Try to parse it into a packet object.
- * Bug:  Up to 8 digipeaters are allowed in radio format.
- * There is a potential of finding more here.
- */
-	pp3 = ax25_from_text(message, 0);
-	if (pp3 == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Tx IGate: Could not parse message from server.\n");
-	  dw_printf ("%s\n", message);
-	  return;
-	}
-
-
-/*
- * Apply our own packet filtering if configured.
- */
-
-//TODO1.2: Should we allow IGating to RF with more than one channel?
-
-	assert (to_chan >= 0 && to_chan < MAX_CHANS);
-
-	if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) {
-
-	  if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3) != 1) {
-
-// TODO1.2: take out debug message.  One person liked it as a confirmation of what was going on.
-// Maybe it should be part of a more comprehensive debug facility?
-//#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]);
-//#endif
-	    ax25_delete (pp3);
-	    return;
-	  }
-	}
-
-/*
- * TODO: Discard if qAX in path???  others?
- */
-
-/*
- * Remove the VIA path.
- */
-	while (ax25_get_num_repeaters(pp3) > 0) {
-	  ax25_remove_addr (pp3, AX25_REPEATER_1);
-	}
-
-/* 
- * Replace the VIA path with TCPIP and my call.
- * Mark my call as having been used.
- */
-	ax25_set_addr (pp3, AX25_REPEATER_1, "TCPIP");
-	ax25_set_h (pp3, AX25_REPEATER_1);
-	ax25_set_addr (pp3, AX25_REPEATER_2, save_audio_config_p->achan[save_igate_config_p->tx_chan].mycall); 
-	ax25_set_h (pp3, AX25_REPEATER_2);
-
-/*
- * Convert to text representation.
- */
-	ax25_format_addrs (pp3, payload);
-	info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo));
-	strcat (payload, pinfo);
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("Tx IGate: payload=%s\n", payload);
-#endif
-	
-/*
- * Encapsulate for sending over radio if no reason to drop it.
- */
-	if (ig_to_tx_allow (pp3)) {
-	  char radio [500];
-	  packet_t pradio;
-
-	  sprintf (radio, "%s>%s%d%d%s:}%s",
-				save_audio_config_p->achan[save_igate_config_p->tx_chan].mycall,
-				APP_TOCALL, MAJOR_VERSION, MINOR_VERSION,
-				save_igate_config_p->tx_via,
-				payload);
-
-	  pradio = ax25_from_text (radio, 1);
-#if ITEST
-	  text_color_set(DW_COLOR_XMIT);
-	  dw_printf ("Xmit: %s\n", radio);
-	  ax25_delete (pradio);
-#else
-	  /* This consumes packet so don't reference it again! */
-	  tq_append (save_igate_config_p->tx_chan, TQ_PRIO_1_LO, pradio);
-#endif
-	  stats_rf_xmit_packets++;
-	  ig_to_tx_remember (pp3);
-	}
-
-	ax25_delete (pp3);
-
-} /* end xmit_packet */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        rx_to_ig_remember
- *
- * Purpose:     Keep a record of packets sent to the IGate server
- *		so we don't send duplicates within some set amount of time.
- *
- * Inputs:	pp	- Pointer to a packet object.
- *
- *-------------------------------------------------------------------
- *
- * Name:	rx_to_ig_allow
- * 
- * Purpose:	Check whether this is a duplicate of another sent recently.
- *
- * Input:	pp	- Pointer to packet object.
- *		
- * Returns:	True if it is OK to send.
- *		
- *-------------------------------------------------------------------
- *
- * Description: These two functions perform the final stage of filtering
- *		before sending a received (from radio) packet to the IGate server.
- *
- *		rx_to_ig_remember must be called for every packet sent to the server.
- *
- *		rx_to_ig_allow decides whether this should be allowed thru
- *		based on recent activity.  We will drop the packet if it is a
- *		duplicate of another sent recently.
- *
- *		Rather than storing the entire packet, we just keep a CRC to 
- *		reduce memory and processing requirements.  We do the same in
- *		the digipeater function to suppress duplicates.
- *
- *		There is a 1 / 65536 chance of getting a false positive match
- *		which is good enough for this application.
- *
- *--------------------------------------------------------------------*/
-
-#define RX2IG_DEDUPE_TIME 60		/* Do not send duplicate within 60 seconds. */
-#define RX2IG_HISTORY_MAX 30		/* Remember the last 30 sent to IGate server. */
-
-static int rx2ig_insert_next;
-static time_t rx2ig_time_stamp[RX2IG_HISTORY_MAX];
-static unsigned short rx2ig_checksum[RX2IG_HISTORY_MAX];
-
-static void rx_to_ig_init (void)
-{
-	int n;
-	for (n=0; n<RX2IG_HISTORY_MAX; n++) {
-	  rx2ig_time_stamp[n] = 0;
-	  rx2ig_checksum[n] = 0;
-	}
-	rx2ig_insert_next = 0;
-}
-	
-
-static void rx_to_ig_remember (packet_t pp)
-{
-       	rx2ig_time_stamp[rx2ig_insert_next] = time(NULL);
-        rx2ig_checksum[rx2ig_insert_next] = ax25_dedupe_crc(pp);
-
-        rx2ig_insert_next++;
-        if (rx2ig_insert_next >= RX2IG_HISTORY_MAX) {
-          rx2ig_insert_next = 0;
-        }
-}
-
-static int rx_to_ig_allow (packet_t pp)
-{
-	unsigned short crc = ax25_dedupe_crc(pp);
-	time_t now = time(NULL);
-	int j;
-
-	for (j=0; j<RX2IG_HISTORY_MAX; j++) {
-	  if (rx2ig_time_stamp[j] >= now - RX2IG_DEDUPE_TIME && rx2ig_checksum[j] == crc) {
-	    return 0;
-	  }
-	}
-	return 1;
-
-} /* end rx_to_ig_allow */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        ig_to_tx_remember
- *
- * Purpose:     Keep a record of packets sent from IGate server to radio transmitter
- *		so we don't send duplicates within some set amount of time.
- *
- * Inputs:	pp	- Pointer to a packet object.
- *
- *------------------------------------------------------------------------------
- *
- * Name:	ig_to_tx_allow
- * 
- * Purpose:	Check whether this is a duplicate of another sent recently
- *		or if we exceed the transmit rate limits.
- *
- * Input:	pp	- Pointer to packet object.
- *		
- * Returns:	True if it is OK to send.
- *		
- *------------------------------------------------------------------------------
- *
- * Description: These two functions perform the final stage of filtering
- *		before sending a packet from the IGate server to the radio.
- *
- *		ig_to_tx_remember must be called for every packet, from the IGate 
- *		server, sent to the radio transmitter.
- *
- *		ig_to_tx_allow decides whether this should be allowed thru
- *		based on recent activity.  We will drop the packet if it is a
- *		duplicate of another sent recently.
- *
- *		This is the essentially the same as the pair of functions
- *		above with one addition restriction.  
- *
- *		The typical residential Internet connection is about 10,000
- *		times faster than the radio links we are using.  It would
- *		be easy to completely saturate the radio channel if we are
- *		not careful.
- *
- *		Besides looking for duplicates, this will also tabulate the 
- *		number of packets sent during the past minute and past 5
- *		minutes and stop sending if a limit is reached.
- *
- * Future?	We might also want to avoid transmitting if the same packet
- *		was heard on the radio recently.  If everything is kept in
- *		the same table, we'd need to distinguish between those from
- *		the IGate server and those heard on the radio.
- *		Those heard on the radio would not count toward the
- *		1 and 5 minute rate limiting.
- *		Maybe even provide informative information such as -
- *		Tx IGate: Same packet heard recently from W1ABC and W9XYZ.
- *
- *		Of course, the radio encapsulation would need to be removed
- *		and only the 3rd party packet inside compared.
- *
- *--------------------------------------------------------------------*/
-
-#define IG2TX_DEDUPE_TIME 60		/* Do not send duplicate within 60 seconds. */
-#define IG2TX_HISTORY_MAX 50		/* Remember the last 50 sent from server to radio. */
-
-static int ig2tx_insert_next;
-static time_t ig2tx_time_stamp[IG2TX_HISTORY_MAX];
-static unsigned short ig2tx_checksum[IG2TX_HISTORY_MAX];
-
-static void ig_to_tx_init (void)
-{
-	int n;
-	for (n=0; n<IG2TX_HISTORY_MAX; n++) {
-	  ig2tx_time_stamp[n] = 0;
-	  ig2tx_checksum[n] = 0;
-	}
-	ig2tx_insert_next = 0;
-}
-	
-
-static void ig_to_tx_remember (packet_t pp)
-{
-       	ig2tx_time_stamp[ig2tx_insert_next] = time(NULL);
-        ig2tx_checksum[ig2tx_insert_next] = ax25_dedupe_crc(pp);
-
-        ig2tx_insert_next++;
-        if (ig2tx_insert_next >= IG2TX_HISTORY_MAX) {
-          ig2tx_insert_next = 0;
-        }
-}
-
-static int ig_to_tx_allow (packet_t pp)
-{
-	unsigned short crc = ax25_dedupe_crc(pp);
-	time_t now = time(NULL);
-	int j;
-	int count_1, count_5;
-
-	for (j=0; j<IG2TX_HISTORY_MAX; j++) {
-	  if (ig2tx_time_stamp[j] >= now - IG2TX_DEDUPE_TIME && ig2tx_checksum[j] == crc) {
-	    text_color_set(DW_COLOR_INFO);
-	    dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n");
-	    return 0;
-	  }
-	}
-	count_1 = 0;
-	count_5 = 0;
-	for (j=0; j<IG2TX_HISTORY_MAX; j++) {
-	  if (ig2tx_time_stamp[j] >= now - 60) count_1++;
-	  if (ig2tx_time_stamp[j] >= now - 300) count_5++;
-	}
-
-	if (count_1 >= save_igate_config_p->tx_limit_1) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", save_igate_config_p->tx_limit_1);
-	  return 0;
-	}
-	if (count_5 >= save_igate_config_p->tx_limit_5) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5);
-	  return 0;
-	}
-
-	return 1;
-
-} /* end ig_to_tx_allow */
-
-/* end igate.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015, 2016  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      igate.c
+ *
+ * Purpose:   	IGate client.
+ *		
+ * Description:	Establish connection with a tier 2 IGate server
+ *		and relay packets between RF and Internet.
+ *
+ * References:	APRS-IS (Automatic Packet Reporting System-Internet Service)
+ *		http://www.aprs-is.net/Default.aspx
+ *
+ *		APRS iGate properties
+ *		http://wiki.ham.fi/APRS_iGate_properties
+ *
+ *		SATgate mode.
+ *		http://www.tapr.org/pipermail/aprssig/2016-January/045283.html
+ *
+ *---------------------------------------------------------------*/
+
+/*------------------------------------------------------------------
+ *
+ * From http://windows.microsoft.com/en-us/windows7/ipv6-frequently-asked-questions
+ * 
+ * How can I enable IPv6?
+ * Follow these steps:
+ * 
+ * Open Network Connections by clicking the Start button, and then clicking 
+ * Control Panel. In the search box, type adapter, and then, under Network 
+ * and Sharing Center, click View network connections.
+ * 
+ * Right-click your network connection, and then click Properties.   
+ * If you're prompted for an administrator password or confirmation, type 
+ * the password or provide confirmation.
+ *
+ * Select the check box next to Internet Protocol Version 6 (TCP/IPv6).
+ *
+ *---------------------------------------------------------------*/
+
+/*
+ * Native Windows:	Use the Winsock interface.
+ * Linux:		Use the BSD socket interface.
+ * Cygwin:		Can use either one.
+ */
+
+
+#if __WIN32__
+
+/* The goal is to support Windows XP and later. */
+
+#include <winsock2.h>
+// default is 0x0400
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501	/* Minimum OS version is XP. */
+//#define _WIN32_WINNT 0x0502	/* Minimum OS version is XP with SP2. */
+//#define _WIN32_WINNT 0x0600	/* Minimum OS version is Vista. */
+#include <ws2tcpip.h>
+#else 
+#include <stdlib.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "version.h"
+#include "digipeater.h"
+#include "tq.h"
+#include "igate.h"
+#include "latlong.h"
+#include "pfilter.h"
+#include "dtime_now.h"
+
+
+#if __WIN32__
+static unsigned __stdcall connnect_thread (void *arg);
+static unsigned __stdcall igate_recv_thread (void *arg);
+static unsigned __stdcall satgate_delay_thread (void *arg);
+#else
+static void * connnect_thread (void *arg);
+static void * igate_recv_thread (void *arg);
+static void * satgate_delay_thread (void *arg);
+#endif
+
+
+static dw_mutex_t dp_mutex;				/* Critical section for delayed packet queue. */
+static packet_t dp_queue_head;
+
+static void satgate_delay_packet (packet_t pp, int chan);
+static void send_packet_to_server (packet_t pp, int chan);
+static void send_msg_to_server (const char *msg);
+static void xmit_packet (char *message, int chan);
+
+static void rx_to_ig_init (void);
+static void rx_to_ig_remember (packet_t pp);
+static int rx_to_ig_allow (packet_t pp);
+
+static void ig_to_tx_init (void);
+static int ig_to_tx_allow (packet_t pp, int chan);
+
+
+/* 
+ * File descriptor for socket to IGate server. 
+ * Set to -1 if not connected. 
+ * (Don't use SOCKET type because it is unsigned.) 
+*/
+
+static volatile int igate_sock = -1;	
+
+/*
+ * After connecting to server, we want to make sure
+ * that the login sequence is sent first.
+ * This is set to true after the login is complete.
+ */
+
+static volatile int ok_to_send = 0;
+
+
+
+
+/*
+ * Convert Internet address to text.
+ * Can't use InetNtop because it is supported only on Windows Vista and later. 
+ */
+
+static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
+{
+	struct sockaddr_in *sa4;
+	struct sockaddr_in6 *sa6;
+
+	switch (Family) {
+	  case AF_INET:
+	    sa4 = (struct sockaddr_in *)pAddr;
+#if __WIN32__
+	    snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
+						sa4->sin_addr.S_un.S_un_b.s_b2,
+						sa4->sin_addr.S_un.S_un_b.s_b3,
+						sa4->sin_addr.S_un.S_un_b.s_b4);
+#else
+	    inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
+#endif
+	    break;
+	  case AF_INET6:
+	    sa6 = (struct sockaddr_in6 *)pAddr;
+#if __WIN32__
+	    snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",  
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
+#else
+	    inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
+#endif
+	    break;
+	  default:
+	    snprintf (pStringBuf, StringBufSize, "Invalid address family!");
+	}
+	//assert (strlen(pStringBuf) < StringBufSize);
+	return pStringBuf;
+}
+
+
+#if ITEST
+
+/* For unit testing. */
+
+int main (int argc, char *argv[])
+{
+	struct audio_s audio_config;
+	struct igate_config_s igate_config;
+	struct digi_config_s digi_config;
+	packet_t pp;
+
+	memset (&audio_config, 0, sizeof(audio_config));
+	audio_config.adev[0].num_chans = 2;
+	strlcpy (audio_config.achan[0].mycall, "WB2OSZ-1", sizeof(audio_config.achan[0].mycall));
+	strlcpy (audio_config.achan[1].mycall, "WB2OSZ-2", sizeof(audio_config.achan[0].mycall));
+
+	memset (&igate_config, 0, sizeof(igate_config));
+
+	strlcpy (igate_config.t2_server_name, "localhost", sizeof(igate_config.t2_server_name));
+	igate_config.t2_server_port = 14580;
+	strlcpy (igate_config.t2_login, "WB2OSZ-JL", sizeof(igate_config.t2_login));
+	strlcpy (igate_config.t2_passcode, "-1", sizeof(igate_config.t2_passcode));
+	igate_config.t2_filter = strdup ("r/1/2/3");
+	
+	igate_config.tx_chan = 0;
+	strlcpy (igate_config.tx_via, ",WIDE2-1", sizeof(igate_config.tx_via));
+	igate_config.tx_limit_1 = 3;
+	igate_config.tx_limit_5 = 5;
+
+	memset (&digi_config, 0, sizeof(digi_config));
+
+	igate_init(&igate_config, &digi_config);
+
+	while (igate_sock == -1) {
+	  SLEEP_SEC(1);
+	}
+
+	SLEEP_SEC (2);
+	pp = ax25_from_text ("A>B,C,D:Ztest message 1", 0);
+	igate_send_rec_packet (0, pp);
+	ax25_delete (pp);
+
+	SLEEP_SEC (2);
+	pp = ax25_from_text ("A>B,C,D:Ztest message 2", 0);
+	igate_send_rec_packet (0, pp);
+	ax25_delete (pp);
+
+	SLEEP_SEC (2);
+	pp = ax25_from_text ("A>B,C,D:Ztest message 2", 0);   /* Should suppress duplicate. */
+	igate_send_rec_packet (0, pp);
+	ax25_delete (pp);
+
+	SLEEP_SEC (2);
+	pp = ax25_from_text ("A>B,TCPIP,D:ZShould drop this due to path", 0);
+	igate_send_rec_packet (0, pp);
+	ax25_delete (pp);
+
+	SLEEP_SEC (2);
+	pp = ax25_from_text ("A>B,C,D:?Should drop query", 0);
+	igate_send_rec_packet (0, pp);
+	ax25_delete (pp);
+
+	SLEEP_SEC (5);
+	pp = ax25_from_text ("A>B,C,D:}E>F,G*,H:Zthird party stuff", 0);
+	igate_send_rec_packet (0, pp);
+	ax25_delete (pp);
+
+#if 1
+	while (1) {
+	  SLEEP_SEC (20);
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Send received packet\n");
+	  send_msg_to_server ("W1ABC>APRS:?");
+	}
+#endif
+	return 0;
+}
+
+#endif
+
+
+/*
+ * Global stuff (to this file)
+ *
+ * These are set by init function and need to 
+ * be kept around in case connection is lost and
+ * we need to reestablish the connection later.
+ */
+
+
+static struct audio_s		*save_audio_config_p;
+static struct igate_config_s	*save_igate_config_p;
+static struct digi_config_s 	*save_digi_config_p;
+static int 			s_debug;
+
+
+/*
+ * Statistics.  
+ * TODO: need print function.
+ */
+
+static int stats_failed_connect;	/* Number of times we tried to connect to */
+					/* a server and failed.  A small number is not */
+					/* a bad thing.  Each name should have a bunch */
+					/* of addresses for load balancing and */
+					/* redundancy. */
+
+static int stats_connects;		/* Number of successful connects to a server. */
+					/* Normally you'd expect this to be 1.  */
+					/* Could be larger if one disappears and we */
+					/* try again to find a different one. */
+
+static time_t stats_connect_at;		/* Most recent time connection was established. */
+					/* can be used to determine elapsed connect time. */
+
+static int stats_rf_recv_packets;	/* Number of candidate packets from the radio. */
+
+static int stats_rx_igate_packets;	/* Number of packets passed along to the IGate */
+					/* server after filtering. */
+
+static int stats_uplink_bytes;		/* Total number of bytes sent to IGate server */
+					/* including login, packets, and hearbeats. */
+
+static int stats_downlink_bytes;	/* Total number of bytes from IGate server including */
+					/* packets, heartbeats, other messages. */
+
+static int stats_tx_igate_packets;	/* Number of packets from IGate server. */
+
+static int stats_rf_xmit_packets;	/* Number of packets passed along to radio */
+					/* after rate limiting or other restrictions. */
+
+/* We have some statistics.  What do we do with them?
+
+
+	IGate stations often send packets like this:
+
+	<IGATE MSG_CNT=1238 LOC_CNT=0 FILL_CNT=0
+	<IGATE,MSG_CNT=1,LOC_CNT=25
+	<IGATE,MSG_CNT=0,LOC_CNT=46,DIR_CNT=13,RF_CNT=49,RFPORT_ID=0
+
+	What does it all mean?
+	Why do some have spaces instead of commas between the capabilities?
+
+	The APRS Protocol Reference ( http://www.aprs.org/doc/APRS101.PDF ),
+	section 15, briefly discusses station capabilities and gives the example
+	IGATE,MSG_CNT=n,LOC_CNT=n
+
+	IGate Design ( http://www.aprs-is.net/IGating.aspx ) barely mentions
+	<IGATE,MSG_CNT=n,LOC_CNT=n
+
+	This leaves many questions.  Does "number of messages transmitted" mean only
+	the APRS "Message" (data type indicator ":") or does it mean any type of
+	APRS packet?   What are "local" stations?   Those we hear directly without
+	going thru a digipeater?
+
+	What are DIR_CNT, RF_CNT, and so on?
+
+	Are the counts since the system started up or are they for some interval?
+
+*/
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        igate_init
+ *
+ * Purpose:     One time initialization when main application starts up.
+ *
+ * Inputs:	p_audio_config	- Audio channel configuration.  All we care about is:
+ *				  - Number of radio channels.
+ *				  - Radio call and SSID for each channel.
+ *
+ *		p_igate_config	- IGate configuration.
+ *
+ *		p_digi_config	- Digipeater configuration.  
+ *				  All we care about here is the packet filtering options.
+ *
+ *		debug_level	- 0  print packets FROM APRS-IS,
+ *				     establishing connection with sergver, and
+ *				     and anything rejected by client side filtering.
+ *				  1  plus packets sent TO server or why not.
+ *				  2  plus duplicate detection overview.
+ *				  3  plus duplicate detection details.
+ *
+ * Description:	This starts two threads:
+ *
+ *		  *  to establish and maintain a connection to the server.
+ *		  *  to listen for packets from the server.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config, int debug_level)
+{
+#if __WIN32__
+	HANDLE connnect_th;
+	HANDLE cmd_recv_th;
+	HANDLE satgate_delay_th;
+#else
+	pthread_t connect_listen_tid;
+	pthread_t cmd_listen_tid;
+	pthread_t satgate_delay_tid;
+	int e;
+#endif
+	s_debug = debug_level;
+	dp_queue_head = NULL;
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("igate_init ( %s, %d, %s, %s, %s )\n", 
+				p_igate_config->t2_server_name, 
+				p_igate_config->t2_server_port, 
+				p_igate_config->t2_login, 
+				p_igate_config->t2_passcode, 
+				p_igate_config->t2_filter);
+#endif
+
+/*
+ * Save the arguments for later use.
+ */
+	save_audio_config_p = p_audio_config;
+	save_igate_config_p = p_igate_config;
+	save_digi_config_p = p_digi_config;
+
+	stats_failed_connect = 0;	
+	stats_connects = 0;		
+	stats_connect_at = 0;		
+	stats_rf_recv_packets = 0;	
+	stats_rx_igate_packets = 0;	
+	stats_uplink_bytes = 0;		
+	stats_downlink_bytes = 0;	
+	stats_tx_igate_packets = 0;	
+	stats_rf_xmit_packets = 0;
+	
+	rx_to_ig_init ();
+	ig_to_tx_init ();
+
+
+/*
+ * Continue only if we have server name, login, and passcode.
+ */
+	if (strlen(p_igate_config->t2_server_name) == 0 ||
+	    strlen(p_igate_config->t2_login) == 0 ||
+	    strlen(p_igate_config->t2_passcode) == 0) {
+	  return;
+	}
+
+/*
+ * This connects to the server and sets igate_sock.
+ * It also sends periodic messages to say I'm still alive.
+ */
+
+#if __WIN32__
+	connnect_th = (HANDLE)_beginthreadex (NULL, 0, connnect_thread, (void *)NULL, 0, NULL);
+	if (connnect_th == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: Could not create IGate connection thread\n");
+	  return;
+	}
+#else
+	e = pthread_create (&connect_listen_tid, NULL, connnect_thread, (void *)NULL);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Internal error: Could not create IGate connection thread");
+	  return;
+	}
+#endif
+
+/*
+ * This reads messages from client when igate_sock is valid.
+ */
+
+#if __WIN32__
+	cmd_recv_th = (HANDLE)_beginthreadex (NULL, 0, igate_recv_thread, NULL, 0, NULL);
+	if (cmd_recv_th == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error: Could not create IGate reading thread\n");
+	  return;
+	}
+#else
+	e = pthread_create (&cmd_listen_tid, NULL, igate_recv_thread, NULL);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Internal error: Could not create IGate reading thread");
+	  return;
+	}
+#endif
+
+/*
+ * This lets delayed packets continue after specified amount of time.
+ */
+
+	if (p_igate_config->satgate_delay > 0) {
+#if __WIN32__
+	  satgate_delay_th = (HANDLE)_beginthreadex (NULL, 0, satgate_delay_thread, NULL, 0, NULL);
+	  if (satgate_delay_th == NULL) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Internal error: Could not create SATgate delay thread\n");
+	    return;
+	  }
+#else
+	  e = pthread_create (&satgate_delay_tid, NULL, satgate_delay_thread, NULL);
+	  if (e != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    perror("Internal error: Could not create SATgate delay thread");
+	    return;
+	  }
+#endif
+	  dw_mutex_init(&dp_mutex);
+	}
+
+} /* end igate_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        connnect_thread
+ *
+ * Purpose:     Establish connection with IGate server.
+ *		Send periodic heartbeat to keep keep connection active.
+ *		Reconnect if something goes wrong and we got disconnected.
+ *
+ * Inputs:	arg		- Not used.
+ *
+ * Outputs:	igate_sock	- File descriptor for communicating with client app.
+ *				  Will be -1 if not connected.
+ *
+ * References:	TCP client example.
+ *		http://msdn.microsoft.com/en-us/library/windows/desktop/ms737591(v=vs.85).aspx
+ *
+ *		Linux IPv6 HOWTO
+ *		http://www.tldp.org/HOWTO/Linux+IPv6-HOWTO/
+ *
+ *--------------------------------------------------------------------*/
+
+/*
+ * Addresses don't get mixed up very well.
+ * IPv6 always shows up last so we'd probably never
+ * end up using any of them.  Use our own shuffle.
+ */
+
+static void shuffle (struct addrinfo *host[], int nhosts)
+{
+        int j, k;
+
+        assert (RAND_MAX >= nhosts);  /* for % to work right */
+
+        if (nhosts < 2) return;
+
+        srand (time(NULL));
+
+        for (j=0; j<nhosts; j++) {
+          k = rand() % nhosts;
+          assert (k >=0 && k<nhosts);
+          if (j != k) {
+            struct addrinfo *temp;
+            temp = host[j]; host[j] = host[k]; host[k] = temp;
+          }
+        }
+}
+
+#define MAX_HOSTS 50
+
+
+
+
+#if __WIN32__
+static unsigned __stdcall connnect_thread (void *arg)
+#else
+static void * connnect_thread (void *arg)	
+#endif	
+{
+	struct addrinfo hints;
+	struct addrinfo *ai_head = NULL;
+	struct addrinfo *ai;
+	struct addrinfo *hosts[MAX_HOSTS];
+	int num_hosts, n;
+	int err;
+	char server_port_str[12];	/* text form of port number */
+	char ipaddr_str[46];		/* text form of IP address */
+#if __WIN32__
+	WSADATA wsadata;
+#endif
+
+	snprintf (server_port_str, sizeof(server_port_str), "%d", save_igate_config_p->t2_server_port);
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+        dw_printf ("DEBUG: igate connect_thread start, port = %d = '%s'\n", save_igate_config_p->t2_server_port, server_port_str);
+#endif
+
+#if __WIN32__
+	err = WSAStartup (MAKEWORD(2,2), &wsadata);
+	if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("WSAStartup failed: %d\n", err);
+	    return (0);
+	}
+
+	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Could not find a usable version of Winsock.dll\n");
+          WSACleanup();
+	  //sleep (1);
+          return (0);
+	}
+#endif
+
+	memset (&hints, 0, sizeof(hints));
+
+	hints.ai_family = AF_UNSPEC;	/* Allow either IPv4 or IPv6. */
+
+	// IPv6 is half baked on Windows XP.
+	// We might need to leave out IPv6 support for Windows version.
+	// hints.ai_family = AF_INET;	/* IPv4 only. */
+
+#if IPV6_ONLY
+	/* IPv6 addresses always show up at end of list. */
+	/* Force use of them for testing. */
+	hints.ai_family = AF_INET6;	/* IPv6 only */
+#endif					
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+
+/*
+ * Repeat forever.
+ */
+
+	while (1) {
+
+/*
+ * Connect to IGate server if not currently connected.
+ */
+
+	  if (igate_sock == -1) {
+
+	    SLEEP_SEC (5);
+
+	    ai_head = NULL;
+	    err = getaddrinfo(save_igate_config_p->t2_server_name, server_port_str, &hints, &ai_head);
+	    if (err != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+#if __WIN32__
+	      dw_printf ("Can't get address for IGate server %s, err=%d\n", 
+					save_igate_config_p->t2_server_name, WSAGetLastError());
+#else 
+	      dw_printf ("Can't get address for IGate server %s, %s\n", 
+					save_igate_config_p->t2_server_name, gai_strerror(err));
+#endif
+	      freeaddrinfo(ai_head);
+
+	      continue;
+	    }
+
+#if DEBUG_DNS
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("getaddrinfo returns:\n");
+#endif
+	    num_hosts = 0;
+	    for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
+#if DEBUG_DNS
+	      text_color_set(DW_COLOR_DEBUG);
+	      ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	      dw_printf ("    %s\n", ipaddr_str);
+#endif
+	      hosts[num_hosts] = ai;
+	      if (num_hosts < MAX_HOSTS) num_hosts++;
+	    }
+
+	    // We can get multiple addresses back for the host name.
+	    // These should be somewhat randomized for load balancing. 
+	    // It turns out the IPv6 addresses are always at the 
+	    // end for both Windows and Linux.   We do our own shuffling
+	    // to mix them up better and give IPv6 a chance. 
+
+	    shuffle (hosts, num_hosts);
+
+#if DEBUG_DNS
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("after shuffling:\n");
+	    for (n=0; n<num_hosts; n++) {
+	      ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	      dw_printf ("    %s\n", ipaddr_str);
+	    }
+#endif
+
+	    // Try each address until we find one that is successful.
+
+	    for (n=0; n<num_hosts; n++) {
+	      int is;
+
+	      ai = hosts[n];
+
+	      ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	      is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+#if __WIN32__
+	      if (is == INVALID_SOCKET) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("IGate: Socket creation failed, err=%d", WSAGetLastError());
+	        WSACleanup();
+	        is = -1;
+		stats_failed_connect++;
+	        continue;
+	      }
+#else
+	      if (err != 0) {
+	        text_color_set(DW_COLOR_INFO);
+	        dw_printf("Connect to IGate server %s (%s) failed.\n\n",
+					save_igate_config_p->t2_server_name, ipaddr_str);
+	        (void) close (is);
+	        is = -1;
+		stats_failed_connect++;
+	        continue;
+	      }
+#endif
+
+#ifndef DEBUG_DNS 
+	      err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
+#if __WIN32__
+	      if (err == SOCKET_ERROR) {
+	        text_color_set(DW_COLOR_INFO);
+	        dw_printf("Connect to IGate server %s (%s) failed.\n\n",
+					save_igate_config_p->t2_server_name, ipaddr_str);
+	        closesocket (is);
+	        is = -1;
+		stats_failed_connect++; 
+	        continue;
+	      }
+	      // TODO: set TCP_NODELAY?
+#else
+	      if (err != 0) {
+	        text_color_set(DW_COLOR_INFO);
+	        dw_printf("Connect to IGate server %s (%s) failed.\n\n",
+					save_igate_config_p->t2_server_name, ipaddr_str);
+	        (void) close (is);
+	        is = -1;
+		stats_failed_connect++;
+	        continue;
+	      }
+	      /* IGate documentation says to use it.  */
+	      /* Does it really make a difference for this application? */
+	      int flag = 1;
+	      err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), sizeof(flag));
+	      if (err < 0) {
+	        text_color_set(DW_COLOR_INFO);
+	        dw_printf("setsockopt TCP_NODELAY failed.\n");
+	      }
+#endif
+	      stats_connects++;
+	      stats_connect_at = time(NULL);
+
+/* Success. */
+
+	      text_color_set(DW_COLOR_INFO);
+ 	      dw_printf("\nNow connected to IGate server %s (%s)\n", save_igate_config_p->t2_server_name, ipaddr_str );
+	      if (strchr(ipaddr_str, ':') != NULL) {
+	      	dw_printf("Check server status here http://[%s]:14501\n\n", ipaddr_str);
+	      }
+	      else {
+	        dw_printf("Check server status here http://%s:14501\n\n", ipaddr_str);
+	      }
+
+/* 
+ * Set igate_sock so everyone else can start using it. 
+ * But make the Rx -> Internet messages wait until after login.
+ */
+
+	      ok_to_send = 0;
+	      igate_sock = is;
+#endif	  
+	      break;
+	    }
+
+	    freeaddrinfo(ai_head);
+
+	    if (igate_sock != -1) {
+	      char stemp[256];
+
+/* 
+ * Send login message.
+ * Software name and version must not contain spaces.
+ */
+
+	      SLEEP_SEC(3);
+	      snprintf (stemp, sizeof(stemp), "user %s pass %s vers Dire-Wolf %d.%d", 
+			save_igate_config_p->t2_login, save_igate_config_p->t2_passcode,
+			MAJOR_VERSION, MINOR_VERSION);
+	      if (save_igate_config_p->t2_filter != NULL) {
+	        strlcat (stemp, " filter ", sizeof(stemp));
+	        strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp));
+	      }
+	      send_msg_to_server (stemp);
+
+/* Delay until it is ok to start sending packets. */
+
+	      SLEEP_SEC(7);
+	      ok_to_send = 1;
+	    }
+	  }
+
+/*
+ * If connected to IGate server, send heartbeat periodically to keep connection active.
+ */
+	  if (igate_sock != -1) {
+	    SLEEP_SEC(10);
+	  }
+	  if (igate_sock != -1) {
+	    SLEEP_SEC(10);
+	  }
+	  if (igate_sock != -1) {
+	    SLEEP_SEC(10);
+	  }
+
+
+	  if (igate_sock != -1) {
+
+	    char heartbeat[10];
+
+	    strlcpy (heartbeat, "#", sizeof(heartbeat));
+
+	    /* This will close the socket if any error. */
+	    send_msg_to_server (heartbeat);
+
+	  }
+	}
+} /* end connnect_thread */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        igate_send_rec_packet
+ *
+ * Purpose:     Send a packet to the IGate server
+ *
+ * Inputs:	chan	- Radio channel it was received on.
+ *
+ *		recv_pp	- Pointer to packet object.
+ *			  *** CALLER IS RESPONSIBLE FOR DELETING IT! **
+ *		
+ *
+ * Description:	Send message to IGate Server if connected.
+ *
+ * Assumptions:	(1) Caller has already verified it is an APRS packet.
+ *		i.e. control = 3 for UI frame, protocol id = 0xf0 for no layer 3
+ *
+ *		(2) This is being called only for packets received with
+ *		a correct CRC.  We don't want to propagate corrupted data.
+ *
+ *--------------------------------------------------------------------*/
+
+#define IGATE_MAX_MSG 520	/* Message to IGate max 512 characters. */
+
+void igate_send_rec_packet (int chan, packet_t recv_pp)
+{
+	packet_t pp;
+	int n;
+	unsigned char *pinfo;
+	char *p;
+	int info_len;
+	
+
+	if (igate_sock == -1) {
+	  return;	/* Silently discard if not connected. */
+	}
+
+	if ( ! ok_to_send) {
+	  return;	/* Login not complete. */
+	}
+
+/*
+ * Check for filtering from specified channel to the IGate server.
+ */
+
+	if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) {
+
+	  if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp) != 1) {
+
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]);
+
+	    return;
+	  }
+	}
+
+
+	/* Gather statistics. */
+
+	stats_rf_recv_packets++;
+
+/*
+ * First make a copy of it because it might be modified in place.
+ */
+
+	pp = ax25_dup (recv_pp);
+	assert (pp != NULL);
+
+/*
+ * Third party frames require special handling to unwrap payload.
+ */
+	while (ax25_get_dti(pp) == '}') {
+	  packet_t inner_pp;
+
+	  for (n = 0; n < ax25_get_num_repeaters(pp); n++) {
+	    char via[AX25_MAX_ADDR_LEN];	/* includes ssid. Do we want to ignore it? */
+
+	    ax25_get_addr_with_ssid (pp, n + AX25_REPEATER_1, via);
+
+	    if (strcmp(via, "TCPIP") == 0 ||
+	        strcmp(via, "TCPXX") == 0 ||
+	        strcmp(via, "RFONLY") == 0 ||
+	        strcmp(via, "NOGATE") == 0) {
+
+	      if (s_debug >= 1) {
+	        text_color_set(DW_COLOR_DEBUG);
+	        dw_printf ("Rx IGate: Do not relay with %s in path.\n", via);
+	      }
+
+	      ax25_delete (pp);
+	      return;
+	    }
+	  }
+
+	  if (s_debug >= 1) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Rx IGate: Unwrap third party message.\n");
+	  }
+
+	  inner_pp = ax25_unwrap_third_party(pp);
+	  if (inner_pp == NULL) {
+	    ax25_delete (pp);
+	    return;
+	  }
+	  ax25_delete (pp);
+	  pp = inner_pp;
+	}
+
+/* 
+ * Do not relay packets with TCPIP, TCPXX, RFONLY, or NOGATE in the via path.
+ */
+	for (n = 0; n < ax25_get_num_repeaters(pp); n++) {
+	  char via[AX25_MAX_ADDR_LEN];	/* includes ssid. Do we want to ignore it? */
+
+	  ax25_get_addr_with_ssid (pp, n + AX25_REPEATER_1, via);
+
+	  if (strcmp(via, "TCPIP") == 0 ||
+	      strcmp(via, "TCPXX") == 0 ||
+	      strcmp(via, "RFONLY") == 0 ||
+	      strcmp(via, "NOGATE") == 0) {
+
+	    if (s_debug >= 1) {
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("Rx IGate: Do not relay with %s in path.\n", via);
+	    }
+
+	    ax25_delete (pp);
+	    return;
+	  }
+	}
+
+/*
+ * Do not relay generic query.
+ */
+	if (ax25_get_dti(pp) == '?') {
+	  if (s_debug >= 1) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Rx IGate: Do not relay generic query.\n");
+	  }
+	  ax25_delete (pp);
+	  return;
+	}
+
+
+/*
+ * Cut the information part at the first CR or LF.
+ */
+
+	info_len = ax25_get_info (pp, &pinfo);
+	(void)(info_len);
+
+	if ((p = strchr ((char*)pinfo, '\r')) != NULL) {
+	  if (s_debug >= 1) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Rx IGate: Truncated information part at CR.\n");
+	  }
+          *p = '\0';
+	}
+
+	if ((p = strchr ((char*)pinfo, '\n')) != NULL) {
+	  if (s_debug >= 1) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Rx IGate: Truncated information part at LF.\n");
+	  }
+          *p = '\0';
+	}
+
+
+/*
+ * Someone around here occasionally sends a packet with no information part.
+ */
+	if (strlen((char*)pinfo) == 0) {
+
+	  if (s_debug >= 1) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Rx IGate: Information part length is zero.\n");
+	  }
+	  ax25_delete (pp);
+	  return;
+	}
+
+// TODO: Should we drop raw touch tone data object type generated here?
+
+
+/*
+ * If the SATgate mode is enabled, see if it should be delayed.
+ * The rule is if we hear it directly and it has at least one
+ * digipeater so there is potential of being re-transmitted.
+ * (Digis are all unused if we are hearing it directly from source.)
+ */
+	if (save_igate_config_p->satgate_delay > 0 &&
+	    ax25_get_heard(pp) == AX25_SOURCE &&
+	    ax25_get_num_repeaters(pp) > 0) {
+
+	  satgate_delay_packet (pp, chan);
+	}
+	else {
+	  send_packet_to_server (pp, chan);
+	}
+
+} /* end igate_send_rec_packet */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        send_packet_to_server
+ *
+ * Purpose:     Convert to text and send to the IGate server.
+ *
+ * Inputs:	pp 	- Packet object.
+ *
+ *		chan	- Radio channel where it was received.
+ *
+ * Description:	Duplicate detection is handled here.
+ *		Suppress if same was sent recently.
+ *
+ *--------------------------------------------------------------------*/
+
+static void send_packet_to_server (packet_t pp, int chan)
+{
+	unsigned char *pinfo;
+	int info_len;
+	char msg[IGATE_MAX_MSG];
+
+
+	info_len = ax25_get_info (pp, &pinfo);
+	(void)(info_len);
+
+/*
+ * Do not relay if a duplicate of something sent recently.
+ */
+
+	if ( ! rx_to_ig_allow(pp)) {
+	  if (s_debug >= 1) {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("Rx IGate: Drop duplicate of same packet seen recently.\n");
+	  }
+	  ax25_delete (pp);
+	  return;
+	}
+
+/* 
+ * Finally, append ",qAR," and my call to the path.
+ */
+
+	ax25_format_addrs (pp, msg);
+	msg[strlen(msg)-1] = '\0';    /* Remove trailing ":" */
+	strlcat (msg, ",qAR,", sizeof(msg));
+	strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg));
+	strlcat (msg, ":", sizeof(msg));
+	strlcat (msg, (char*)pinfo, sizeof(msg));
+
+	send_msg_to_server (msg);
+	stats_rx_igate_packets++;
+
+/*
+ * Remember what was sent to avoid duplicates in near future.
+ */
+	rx_to_ig_remember (pp);
+
+	ax25_delete (pp);
+
+} /* end send_packet_to_server */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        send_msg_to_server
+ *
+ * Purpose:     Send to the IGate server.
+ *		This one function should be used for login, hearbeats,
+ *		and packets.
+ *
+ * Inputs:	imsg	- Message.  We will add CR/LF.
+ *		
+ *
+ * Description:	Send message to IGate Server if connected.
+ *		Disconnect from server, and notify user, if any error.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static void send_msg_to_server (const char *imsg)
+{
+	int err;
+	char stemp[IGATE_MAX_MSG];
+
+	if (igate_sock == -1) {
+	  return;	/* Silently discard if not connected. */
+	}
+
+	strlcpy(stemp, imsg, sizeof(stemp));
+
+	if (s_debug >= 1) {
+	  text_color_set(DW_COLOR_XMIT);
+	  dw_printf ("[rx>ig] ");
+	  ax25_safe_print (stemp, strlen(stemp), 0);
+	  dw_printf ("\n");
+	}
+
+	strlcat (stemp, "\r\n", sizeof(stemp));
+
+	stats_uplink_bytes += strlen(stemp);
+
+#if __WIN32__	
+        err = send (igate_sock, stemp, strlen(stemp), 0);
+	if (err == SOCKET_ERROR)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError %d sending message to IGate server.  Closing connection.\n\n", WSAGetLastError());
+	  //dw_printf ("DEBUG: igate_sock=%d, line=%d\n", igate_sock, __LINE__);
+	  closesocket (igate_sock);
+	  igate_sock = -1;
+	  WSACleanup();
+	}
+#else
+        err = write (igate_sock, stemp, strlen(stemp));
+	if (err <= 0)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError sending message to IGate server.  Closing connection.\n\n");
+	  close (igate_sock);
+	  igate_sock = -1;    
+	}
+#endif
+	
+} /* end send_msg_to_server */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        get1ch
+ *
+ * Purpose:     Read one byte from socket.
+ *
+ * Inputs:	igate_sock	- file handle for socket.
+ *
+ * Returns:	One byte from stream.
+ *		Waits and tries again later if any error.
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+static int get1ch (void)
+{
+	unsigned char ch;
+	int n;
+
+	while (1) {
+
+	  while (igate_sock == -1) {
+	    SLEEP_SEC(5);			/* Not connected.  Try again later. */
+	  }
+
+	  /* Just get one byte at a time. */
+	  // TODO: might read complete packets and unpack from own buffer
+	  // rather than using a system call for each byte.
+
+#if __WIN32__
+	  n = recv (igate_sock, (char*)(&ch), 1, 0);
+#else
+	  n = read (igate_sock, &ch, 1);
+#endif
+
+	  if (n == 1) {
+#if DEBUG9
+	    dw_printf (log_fp, "%02x %c %c", ch, 
+			isprint(ch) ? ch : '.' , 
+			(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
+	    if (ch == '\r') fprintf (log_fp, "  CR");
+	    if (ch == '\n') fprintf (log_fp, "  LF");
+	    fprintf (log_fp, "\n");
+#endif
+	    return(ch);	
+	  }
+
+          text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError reading from IGate server.  Closing connection.\n\n");
+#if __WIN32__
+	  closesocket (igate_sock);
+#else
+	  close (igate_sock);
+#endif
+	  igate_sock = -1;
+	}
+
+} /* end get1ch */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        igate_recv_thread
+ *
+ * Purpose:     Wait for messages from IGate Server.
+ *
+ * Inputs:	arg		- Not used.
+ *
+ * Outputs:	igate_sock	- File descriptor for communicating with client app.
+ *
+ * Description:	Process messages from the IGate server.
+ *
+ *--------------------------------------------------------------------*/
+
+#if __WIN32__
+static unsigned __stdcall igate_recv_thread (void *arg)
+#else
+static void * igate_recv_thread (void *arg)
+#endif
+{
+	unsigned char ch;
+	unsigned char message[1000];  // Spec says max 500 or so.
+	int len;
+	
+			
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("igate_recv_thread ( socket = %d )\n", igate_sock);
+#endif
+
+	while (1) {
+
+	  len = 0;
+
+	  do
+	  {
+	    ch = get1ch();
+	    stats_downlink_bytes++;
+
+	    if (len < sizeof(message)) 
+	    {
+	      message[len] = ch;
+	    }
+	    len++;
+	    
+	  } while (ch != '\n');
+
+/*
+ * We have a complete message terminated by LF.
+ *
+ * Remove CR LF from end.
+ * This is a record separator for the protocol, not part of the data.
+ * Should probably have an error if we don't have this.
+ */
+	  if (len >=2 && message[len-1] == '\n') { message[len-1] = '\0'; len--; }
+	  if (len >=1 && message[len-1] == '\r') { message[len-1] = '\0'; len--; }
+
+/*
+ * I've seen a case where the original RF packet had a trailing CR but
+ * after someone else sent it to the server and it came back to me, that
+ * CR was now a trailing space.
+ * At first I was tempted to trim a trailing space as well.
+ * By fixing this one case it might corrupt the data in other cases.
+ * We compensate for this by ignoring trailing spaces when performing
+ * the duplicate detection and removal.
+ */
+
+/*
+ * I've also seen a multiple trailing spaces like this.
+ * Notice how safe_print shows a trailing space in hexadecimal to make it obvious.
+ *
+ * W1CLA-1>APVR30,TCPIP*,qAC,T2TOKYO3:;IRLP-4942*141503z4218.46NI07108.24W0446325-146IDLE    <0x20>
+ */
+
+	  if (len == 0) 
+	  {
+/* 
+ * Discard if zero length. 
+ */
+	  }
+	  else if (message[0] == '#') {
+/*
+ * Heartbeat or other control message.
+ *
+ * Print only if within seconds of logging in.
+ * That way we can see login confirmation but not 
+ * be bothered by the heart beat messages.
+ */
+
+	    if ( ! ok_to_send) {
+	      text_color_set(DW_COLOR_REC);
+	      dw_printf ("[ig] ");
+	      ax25_safe_print ((char *)message, len, 0);
+	      dw_printf ("\n");
+	    }
+	  }
+	  else 
+	  {
+/*
+ * Convert to third party packet and transmit.
+ *
+ * Future: might have ability to configure multiple transmit
+ * channels, each with own client side filtering and via path.
+ * Loop here over all configured channels.
+ */
+	    text_color_set(DW_COLOR_REC);
+	    dw_printf ("\n[ig>tx] ");		// formerly just [ig]
+	    ax25_safe_print ((char *)message, len, 0);
+	    dw_printf ("\n");
+
+	    int to_chan = save_igate_config_p->tx_chan;
+
+	    if (to_chan >= 0) {
+	      xmit_packet ((char*)message, to_chan);
+	    }
+	  }
+
+	}  /* while (1) */
+	return (0);
+
+} /* end igate_recv_thread */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        satgate_delay_packet
+ *
+ * Purpose:     Put packet into holding area for a while rather than
+ *		sending it immediately to the IS server.
+ *
+ * Inputs:	pp	- Packet object.
+ *
+ *		chan	- Radio channel where received.
+ *
+ * Outputs:	Appended to queue.
+ *
+ * Description:	If we hear a packet directly and the same one digipeated,
+ *		we only send the first to the APRS IS due to duplicate removal.
+ *		It may be desirable to favor the digipeated packet over the
+ *		original.  For this situation, we have an option which delays
+ *		a packet if we hear it directly and the via path is not empty.
+ *		We know we heard it directly if none of the digipeater
+ *		addresses have been used.
+ *		This way the digipeated packet will go first.
+ *		The original is sent about 10 seconds later.
+ *		Duplicate removal will drop the original if there is no
+ *		corresponding digipeated version.
+ *
+ *--------------------------------------------------------------------*/
+
+static void satgate_delay_packet (packet_t pp, int chan)
+{
+	packet_t pnext, plast;
+
+
+	//if (s_debug >= 1) {
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Rx IGate: SATgate mode, delay packet heard directly.\n");
+	//}
+
+	ax25_set_release_time (pp, dtime_now() + save_igate_config_p->satgate_delay);
+//TODO: save channel too.
+
+	dw_mutex_lock (&dp_mutex);
+
+	if (dp_queue_head == NULL) {
+	  dp_queue_head = pp;
+	}
+	else {
+	  plast = dp_queue_head;
+	  while ((pnext = ax25_get_nextp(plast)) != NULL) {
+	    plast = pnext;
+	  }
+	  ax25_set_nextp (plast, pp);
+	}
+
+	dw_mutex_unlock (&dp_mutex);
+
+} /* end satgate_delay_packet */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        satgate_delay_thread
+ *
+ * Purpose:     Release packet when specified release time has arrived.
+ *
+ * Inputs:	dp_queue_head	- Queue of packets.
+ *
+ * Outputs:	Sent to APRS IS.
+ *
+ * Description:	For simplicity we'll just poll each second.
+ *		Release the packet when its time has arrived.
+ *
+ *--------------------------------------------------------------------*/
+
+#if __WIN32__
+static unsigned __stdcall satgate_delay_thread (void *arg)
+#else
+static void * satgate_delay_thread (void *arg)
+#endif
+{
+	double release_time;
+	int chan = 0;				// TODO:  get receive channel somehow.
+						// only matters if multi channel with different names.
+		
+	while (1) {
+	  SLEEP_SEC (1);
+
+/* Don't need critical region just to peek */
+
+	  if (dp_queue_head != NULL) {
+
+	    double now = dtime_now();
+
+	    release_time = ax25_get_release_time (dp_queue_head);
+
+#if 0
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("SATgate:  %.1f sec remaining\n", release_time - now);
+#endif
+	    if (now > release_time) {
+	      packet_t pp;
+
+	      dw_mutex_lock (&dp_mutex);
+
+	      pp = dp_queue_head;
+	      dp_queue_head = ax25_get_nextp(pp);
+
+	      dw_mutex_unlock (&dp_mutex);
+	      ax25_set_nextp (pp, NULL);
+
+	      send_packet_to_server (pp, chan);
+	    }
+	  }  /* if something in queue */
+	}  /* while (1) */
+	return (0);
+
+} /* end satgate_delay_thread */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_packet
+ *
+ * Purpose:     Convert text string, from IGate server, to third party
+ *		packet and send to transmit queue.
+ *
+ * Inputs:	message		- As sent by the server.  
+ *				  Any trailing CRLF should have been removed.
+ *				  Typical examples:
+ *
+ *				KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/
+ *				N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:<IGATE,MSG_CNT=0,LOC_CNT=0
+ *				K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148
+ *				KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile
+ *
+ *				  Notice how the final address in the header might not
+ *				  be a valid AX.25 address.  We see a 9 character address
+ *				  (with no ssid) and an ssid of two letters.
+ *				  We don't care because we end up discarding them before
+ *				  repackaging to go over the radio.
+ *
+ *				  The "q construct"  ( http://www.aprs-is.net/q.aspx ) provides
+ *				  a clue about the journey taken but I don't think we care here.
+ *
+ *		to_chan		- Radio channel for transmitting.
+ *
+ *--------------------------------------------------------------------*/
+
+static void xmit_packet (char *message, int to_chan)
+{
+	packet_t pp3;
+	char payload[AX25_MAX_PACKET_LEN];	/* what is max len? */
+	char *pinfo = NULL;
+	int info_len;
+
+	assert (to_chan >= 0 && to_chan < MAX_CHANS);
+
+
+/*
+ * Try to parse it into a packet object.
+ * This will contain "q constructs" and we might see an address
+ * with two alphnumeric characters in the SSID so we must use
+ * the non-strict parsing.
+ *
+ * Bug:  Up to 8 digipeaters are allowed in radio format.
+ * There is a potential of finding a larger number here.
+ */
+	pp3 = ax25_from_text(message, 0);
+	if (pp3 == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Tx IGate: Could not parse message from server.\n");
+	  dw_printf ("%s\n", message);
+	  return;
+	}
+
+
+/*
+ * Apply our own packet filtering if configured.
+ * Do we want to do this before or after removing the VIA path?
+ * I suppose by doing it first, we have the possibility of
+ * filtering by stations along the way or the q construct.
+ */
+
+	assert (to_chan >= 0 && to_chan < MAX_CHANS);
+
+	if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) {
+
+	  if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3) != 1) {
+
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]);
+
+	    ax25_delete (pp3);
+	    return;
+	  }
+	}
+
+
+/*
+ * Remove the VIA path.
+ *
+ * For example, we might get something like this from the server.
+ *	K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000<0x0d><0x0a>
+ *
+ * We want to reduce it to this before wrapping it as third party traffic.
+ *	K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a>
+ */
+
+	while (ax25_get_num_repeaters(pp3) > 0) {
+	  ax25_remove_addr (pp3, AX25_REPEATER_1);
+	}
+
+
+/* 
+ * Replace the VIA path with TCPIP and my call.
+ * Mark my call as having been used.
+ */
+	ax25_set_addr (pp3, AX25_REPEATER_1, "TCPIP");
+	ax25_set_h (pp3, AX25_REPEATER_1);
+	ax25_set_addr (pp3, AX25_REPEATER_2, save_audio_config_p->achan[to_chan].mycall);
+	ax25_set_h (pp3, AX25_REPEATER_2);
+
+/*
+ * Convert to text representation.
+ */
+	memset (payload, 0, sizeof(payload));
+
+	ax25_format_addrs (pp3, payload);
+	info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo));
+	(void)(info_len);
+	strlcat (payload, pinfo, sizeof(payload));
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("Tx IGate: payload=%s\n", payload);
+#endif
+	
+/*
+ * Encapsulate for sending over radio if no reason to drop it.
+ */
+	if (ig_to_tx_allow (pp3, to_chan)) {
+	  char radio [500];
+	  packet_t pradio;
+
+	  snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s",
+				save_audio_config_p->achan[to_chan].mycall,
+				APP_TOCALL, MAJOR_VERSION, MINOR_VERSION,
+				save_igate_config_p->tx_via,
+				payload);
+
+	  pradio = ax25_from_text (radio, 1);
+
+	  /* Oops.  Didn't have a check for NULL here. */
+	  /* Could this be the cause of rare and elusive crashes in 1.2? */
+
+	  if (pradio != NULL) {
+
+	    stats_tx_igate_packets++;
+
+#if ITEST
+	    text_color_set(DW_COLOR_XMIT);
+	    dw_printf ("Xmit: %s\n", radio);
+	    ax25_delete (pradio);
+#else
+	    /* This consumes packet so don't reference it again! */
+	    tq_append (to_chan, TQ_PRIO_1_LO, pradio);
+#endif
+	    stats_rf_xmit_packets++;
+	    ig_to_tx_remember (pp3, save_igate_config_p->tx_chan, 0);	// correct. version before encapsulating it.
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Received invalid packet from IGate.\n");
+	    dw_printf ("%s\n", payload);	
+	    dw_printf ("Will not attempt to transmit third party packet.\n");
+	    dw_printf ("%s\n", radio);	
+	  }
+
+	}
+
+	ax25_delete (pp3);
+
+} /* end xmit_packet */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        rx_to_ig_remember
+ *
+ * Purpose:     Keep a record of packets sent to the IGate server
+ *		so we don't send duplicates within some set amount of time.
+ *
+ * Inputs:	pp	- Pointer to a packet object.
+ *
+ *-------------------------------------------------------------------
+ *
+ * Name:	rx_to_ig_allow
+ * 
+ * Purpose:	Check whether this is a duplicate of another
+ *		recently received from RF and sent to the Server
+ *
+ * Input:	pp	- Pointer to packet object.
+ *		
+ * Returns:	True if it is OK to send.
+ *		
+ *-------------------------------------------------------------------
+ *
+ * Description: These two functions perform the final stage of filtering
+ *		before sending a received (from radio) packet to the IGate server.
+ *
+ *		rx_to_ig_remember must be called for every packet sent to the server.
+ *
+ *		rx_to_ig_allow decides whether this should be allowed thru
+ *		based on recent activity.  We will drop the packet if it is a
+ *		duplicate of another sent recently.
+ *
+ *		Rather than storing the entire packet, we just keep a CRC to 
+ *		reduce memory and processing requirements.  We do the same in
+ *		the digipeater function to suppress duplicates.
+ *
+ *		There is a 1 / 65536 chance of getting a false positive match
+ *		which is good enough for this application.
+ *
+ *--------------------------------------------------------------------*/
+
+#define RX2IG_DEDUPE_TIME 60		/* Do not send duplicate within 60 seconds. */
+#define RX2IG_HISTORY_MAX 30		/* Remember the last 30 sent to IGate server. */
+
+static int rx2ig_insert_next;
+static time_t rx2ig_time_stamp[RX2IG_HISTORY_MAX];
+static unsigned short rx2ig_checksum[RX2IG_HISTORY_MAX];
+
+static void rx_to_ig_init (void)
+{
+	int n;
+	for (n=0; n<RX2IG_HISTORY_MAX; n++) {
+	  rx2ig_time_stamp[n] = 0;
+	  rx2ig_checksum[n] = 0;
+	}
+	rx2ig_insert_next = 0;
+}
+	
+
+static void rx_to_ig_remember (packet_t pp)
+{
+
+       	rx2ig_time_stamp[rx2ig_insert_next] = time(NULL);
+        rx2ig_checksum[rx2ig_insert_next] = ax25_dedupe_crc(pp);
+
+	if (s_debug >= 3) {
+	  char src[AX25_MAX_ADDR_LEN];
+	  char dest[AX25_MAX_ADDR_LEN];
+	  unsigned char *pinfo;
+	  int info_len;
+
+	  ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
+	  ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
+	  info_len = ax25_get_info (pp, &pinfo);
+
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("rx_to_ig_remember [%d] = %d %d \"%s>%s:%s\"\n",
+			rx2ig_insert_next,
+			(int)(rx2ig_time_stamp[rx2ig_insert_next]),
+			rx2ig_checksum[rx2ig_insert_next],
+			src, dest, pinfo);
+	}
+
+        rx2ig_insert_next++;
+        if (rx2ig_insert_next >= RX2IG_HISTORY_MAX) {
+          rx2ig_insert_next = 0;
+        }
+}
+
+static int rx_to_ig_allow (packet_t pp)
+{
+	unsigned short crc = ax25_dedupe_crc(pp);
+	time_t now = time(NULL);
+	int j;
+
+	if (s_debug >= 2) {
+	  char src[AX25_MAX_ADDR_LEN];
+	  char dest[AX25_MAX_ADDR_LEN];
+	  unsigned char *pinfo;
+	  int info_len;
+
+	  ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
+	  ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
+	  info_len = ax25_get_info (pp, &pinfo);
+
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo);
+	}
+
+	for (j=0; j<RX2IG_HISTORY_MAX; j++) {
+	  if (rx2ig_checksum[j] == crc && rx2ig_time_stamp[j] >= now - RX2IG_DEDUPE_TIME) {
+	    if (s_debug >= 2) {
+	      text_color_set(DW_COLOR_DEBUG);
+	      // could be multiple entries and this might not be the most recent.
+	      dw_printf ("rx_to_ig_allow? NO. Seen %d seconds ago.\n", (int)(now - rx2ig_time_stamp[j]));
+	    }
+	    return 0;
+	  }
+	}
+
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("rx_to_ig_allow? YES\n");
+	}
+	return 1;
+
+} /* end rx_to_ig_allow */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ig_to_tx_remember
+ *
+ * Purpose:     Keep a record of packets sent from IGate server to radio transmitter
+ *		so we don't send duplicates within some set amount of time.
+ *
+ * Inputs:	pp	- Pointer to a packet object.
+ *
+ *		chan	- Channel number where it is being transmitted.
+ *			  Duplicate detection needs to be separate for each radio channel.
+ *
+ *		bydigi	- True if transmitted by digipeater function.  False for IGate.
+ *			  Why do we care about digpeating here?  See discussion below.
+ *
+ *------------------------------------------------------------------------------
+ *
+ * Name:	ig_to_tx_allow
+ * 
+ * Purpose:	Check whether this is a duplicate of another sent recently
+ *		or if we exceed the transmit rate limits.
+ *
+ * Input:	pp	- Pointer to packet object.
+ *
+ *		chan	- Radio channel number where we want to transmit.
+ *		
+ * Returns:	True if it is OK to send.
+ *		
+ *------------------------------------------------------------------------------
+ *
+ * Description: These two functions perform the final stage of filtering
+ *		before sending a packet from the IGate server to the radio.
+ *
+ *		ig_to_tx_remember must be called for every packet, from the IGate 
+ *		server, sent to the radio transmitter.
+ *
+ *		ig_to_tx_allow decides whether this should be allowed thru
+ *		based on recent activity.  We will drop the packet if it is a
+ *		duplicate of another sent recently.
+ *
+ *		This is the essentially the same as the pair of functions
+ *		above with one addition restriction.  
+ *
+ *		The typical residential Internet connection is around 10,000
+ *		to 50,000 times faster than the radio links we are using.  It would
+ *		be easy to completely saturate the radio channel if we are
+ *		not careful.
+ *
+ *		Besides looking for duplicates, this will also tabulate the 
+ *		number of packets sent during the past minute and past 5
+ *		minutes and stop sending if a limit is reached.
+ *
+ * More Discussion:
+ *
+ *		Consider the following example.
+ *		I hear a packet from W1TG-1 three times over the radio then get the
+ *		(almost) same thing twice from APRS-IS.
+ *
+ *
+ *		Digipeater N3LEE-10 audio level = 23(10/6)   [NONE]   __|||||||
+ *		[0.5] W1TG-1>APU25N,N3LEE-10*,WIDE2-1:<IGATE,MSG_CNT=30,LOC_CNT=61<0x0d>
+ *		Station Capabilities, Ambulance, UIview 32 bit apps
+ *		IGATE,MSG_CNT=30,LOC_CNT=61
+ *
+ *		[0H] W1TG-1>APU25N,N3LEE-10,WB2OSZ-14*:<IGATE,MSG_CNT=30,LOC_CNT=61<0x0d>
+ *
+ *		Digipeater WIDE2 (probably N3LEE-4) audio level = 22(10/6)   [NONE]   __|||||||
+ *		[0.5] W1TG-1>APU25N,N3LEE-10,N3LEE-4,WIDE2*:<IGATE,MSG_CNT=30,LOC_CNT=61<0x0d>
+ *		Station Capabilities, Ambulance, UIview 32 bit apps
+ *		IGATE,MSG_CNT=30,LOC_CNT=61
+ *
+ *		Digipeater WIDE2 (probably AB1OC-10) audio level = 31(14/11)   [SINGLE]   ____:____
+ *		[0.4] W1TG-1>APU25N,N3LEE-10,AB1OC-10,WIDE2*:<IGATE,MSG_CNT=30,LOC_CNT=61<0x0d>
+ *		Station Capabilities, Ambulance, UIview 32 bit apps
+ *		IGATE,MSG_CNT=30,LOC_CNT=61
+ *
+ *		[ig] W1TG-1>APU25N,WIDE2-2,qAR,W1GLO-11:<IGATE,MSG_CNT=30,LOC_CNT=61
+ *		[0L] WB2OSZ-14>APDW13,WIDE1-1:}W1TG-1>APU25N,TCPIP,WB2OSZ-14*:<IGATE,MSG_CNT=30,LOC_CNT=61
+ *
+ *		[ig] W1TG-1>APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15:<IGATE,MSG_CNT=30,LOC_CNT=61<0x20>
+ *		[0L] WB2OSZ-14>APDW13,WIDE1-1:}W1TG-1>APU25N,TCPIP,WB2OSZ-14*:<IGATE,MSG_CNT=30,LOC_CNT=61<0x20>
+ *
+ *
+ *		The first one gets retransmitted by digipeating.
+ *
+ *		Why are we getting the same thing twice from APRS-IS?  Shouldn't remove duplicates?
+ *		Look closely.  The original packet, on RF, had a CR character at the end.
+ *		At first I thought duplicate removal was broken but it turns out they
+ *		are not exactly the same.
+ *
+ *		The receive IGate spec says a packet should be cut at a CR.
+ *		In one case it is removed as expected   In another case, it is replaced by a trailing
+ *		space character.  Maybe someone thought non printable characters should be
+ *		replaced by spaces???
+ *
+ *		At first I was tempted to remove any trailing spaces to make up for the other
+ *		IGate adding it.  Two wrongs don't make a right.   Trailing spaces are not that
+ *		rare and removing them would corrupt the data.  My new strategy is for
+ *		the duplicate detection compare to ignore trailing space, CR, and LF.
+ *
+ *		We already transmitted the same thing by the digipeater function so this should
+ *		also go into memory for avoiding duplicates out of the transmit IGate.
+ *
+ * Future:
+ *		Should the digipeater function avoid transmitting something if it
+ *		was recently transmitted by the IGate funtion?
+ *		This code is pretty much the same as dedupe.c. Maybe it could all
+ *		be combined into one.  Need to ponder this some more.
+ * 
+ *--------------------------------------------------------------------*/
+
+/*
+Here is another complete example, with the "-diii" debugging option to show details.
+
+
+We receive the signal directly from the source: (zzz.log 1011)
+
+	N1ZKO-7 audio level = 33(16/10)   [NONE]   ___||||||
+	[0.5] N1ZKO-7>T2TS7X,WIDE1-1,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d>
+	MIC-E, Human, Kenwood TH-D72, In Service
+	N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft
+	[scanning]
+
+We did not send it to the IS server recently.
+
+	Rx IGate: Truncated information part at CR.
+	rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]="
+	rx_to_ig_allow? YES
+
+Send it now and remember that fact.
+
+	[rx>ig] N1ZKO-7>T2TS7X,WIDE1-1,WIDE2-1,qAR,WB2OSZ-14:`c6wl!i[/>"4]}[scanning]=
+	rx_to_ig_remember [21] = 1447683040 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]="
+
+Digipeat it.  Notice how it has a trailing CR.
+TODO:  Why is the CRC different?  Content looks the same.
+
+	ig_to_tx_remember [38] = ch0 d1 1447683040 27598 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]=
"
+	[0H] N1ZKO-7>T2TS7X,WB2OSZ-14*,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d>
+
+Now we hear it again, thru a digipeater.
+Not sure who.   Was it UNCAN or was it someone else who doesn't use tracing?
+See my rant in the User Guide about this.
+
+	Digipeater WIDE2 (probably UNCAN) audio level = 30(15/10)   [NONE]   __|||::__
+	[0.4] N1ZKO-7>T2TS7X,KB1POR-2,UNCAN,WIDE2*:`c6wl!i[/>"4]}[scanning]=<0x0d>
+	MIC-E, Human, Kenwood TH-D72, In Service
+	N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft
+	[scanning]
+
+Was sent to server recently so don't do it again.
+
+	Rx IGate: Truncated information part at CR.
+	rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]="
+	rx_to_ig_allow? NO. Seen 1 seconds ago.
+	Rx IGate: Drop duplicate of same packet seen recently.
+
+We hear it a third time, by a different digipeater.
+
+	Digipeater WIDE1 (probably N3LEE-10) audio level = 23(12/6)   [NONE]   __|||||||
+	[0.5] N1ZKO-7>T2TS7X,N3LEE-10,WIDE1*,WIDE2-1:`c6wl!i[/>"4]}[scanning]=<0x0d>
+	MIC-E, Human, Kenwood TH-D72, In Service
+	N 42 43.7800, W 071 26.9100, 0 MPH, course 177, alt 230 ft
+	[scanning]
+
+It's a duplicate, so don't send to server.
+
+	Rx IGate: Truncated information part at CR.
+	rx_to_ig_allow? 57185 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]="
+	rx_to_ig_allow? NO. Seen 2 seconds ago.
+	Rx IGate: Drop duplicate of same packet seen recently.
+	Digipeater: Drop redundant packet to channel 0.
+
+The server sends it to us.
+NOTICE: The CR at the end has been replaced by a space.
+
+	[ig>tx] N1ZKO-7>T2TS7X,K1FFK,WA2MJM-15*,qAR,WB2ZII-15:`c6wl!i[/>"4]}[scanning]=<0x20>
+
+Should we transmit it?
+No, we sent it recently by the digipeating function (note "bydigi=1").
+
+	DEBUG:  ax25_dedupe_crc ignoring trailing space.
+	ig_to_tx_allow? ch0 27598 "N1ZKO-7>T2TS7X:`c6wl!i[/>"4]}[scanning]= "
+	ig_to_tx_allow? NO. Sent 4 seconds ago. bydigi=1
+	Tx IGate: Drop duplicate packet transmitted recently.
+	[0L] WB2OSZ-14>APDW13,WIDE1-1:}W1AST>TRPR4T,TCPIP,WB2OSZ-14*:`d=Ml!3>/"4N}
+	[rx>ig] #
+*/
+
+
+#define IG2TX_DEDUPE_TIME 60		/* Do not send duplicate within 60 seconds. */
+#define IG2TX_HISTORY_MAX 50		/* Remember the last 50 sent from server to radio. */
+
+/* Ideally this should be a critical region because */
+/* it is being written by two threads but I'm not that concerned. */
+
+static int ig2tx_insert_next;
+static time_t ig2tx_time_stamp[IG2TX_HISTORY_MAX];
+static unsigned short ig2tx_checksum[IG2TX_HISTORY_MAX];
+static unsigned char ig2tx_chan[IG2TX_HISTORY_MAX];
+static unsigned short ig2tx_bydigi[IG2TX_HISTORY_MAX];
+
+static void ig_to_tx_init (void)
+{
+	int n;
+	for (n=0; n<IG2TX_HISTORY_MAX; n++) {
+	  ig2tx_time_stamp[n] = 0;
+	  ig2tx_checksum[n] = 0;
+	  ig2tx_chan[n] = 0xff;
+	  ig2tx_bydigi[n] = 0;
+	}
+	ig2tx_insert_next = 0;
+}
+	
+
+void ig_to_tx_remember (packet_t pp, int chan, int bydigi)
+{
+	time_t now = time(NULL);
+	unsigned short crc = ax25_dedupe_crc(pp);
+
+	if (s_debug >= 3) {
+	  char src[AX25_MAX_ADDR_LEN];
+	  char dest[AX25_MAX_ADDR_LEN];
+	  unsigned char *pinfo;
+	  int info_len;
+
+	  ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
+	  ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
+	  info_len = ax25_get_info (pp, &pinfo);
+
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ig_to_tx_remember [%d] = ch%d d%d %d %d \"%s>%s:%s\"\n",
+			ig2tx_insert_next,
+			chan, bydigi,
+			(int)(now), crc,
+			src, dest, pinfo);
+	}
+
+	ig2tx_time_stamp[ig2tx_insert_next] = now;
+	ig2tx_checksum[ig2tx_insert_next] = crc;
+	ig2tx_chan[ig2tx_insert_next] = chan;
+	ig2tx_bydigi[ig2tx_insert_next] = bydigi;
+
+        ig2tx_insert_next++;
+        if (ig2tx_insert_next >= IG2TX_HISTORY_MAX) {
+          ig2tx_insert_next = 0;
+        }
+}
+
+static int ig_to_tx_allow (packet_t pp, int chan)
+{
+	unsigned short crc = ax25_dedupe_crc(pp);
+	time_t now = time(NULL);
+	int j;
+	int count_1, count_5;
+
+	if (s_debug >= 2) {
+	  char src[AX25_MAX_ADDR_LEN];
+	  char dest[AX25_MAX_ADDR_LEN];
+	  unsigned char *pinfo;
+	  int info_len;
+
+	  ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
+	  ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
+	  info_len = ax25_get_info (pp, &pinfo);
+
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ig_to_tx_allow? ch%d %d \"%s>%s:%s\"\n", chan, crc, src, dest, pinfo);
+	}
+
+	/* Consider transmissions on this channel only by either digi or IGate. */
+
+	for (j=0; j<IG2TX_HISTORY_MAX; j++) {
+	  if (ig2tx_checksum[j] == crc && ig2tx_chan[j] == chan && ig2tx_time_stamp[j] >= now - IG2TX_DEDUPE_TIME) {
+	    if (s_debug >= 2) {
+	      text_color_set(DW_COLOR_DEBUG);
+	      // could be multiple entries and this might not be the most recent.
+	      dw_printf ("ig_to_tx_allow? NO. Sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]);
+	    }
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n");
+	    return 0;
+	  }
+	}
+
+	/* IGate transmit counts must not include digipeater transmissions. */
+
+	count_1 = 0;
+	count_5 = 0;
+	for (j=0; j<IG2TX_HISTORY_MAX; j++) {
+	  if (ig2tx_chan[j] == chan && ig2tx_bydigi[j] == 0) {
+	    if (ig2tx_time_stamp[j] >= now - 60) count_1++;
+	    if (ig2tx_time_stamp[j] >= now - 300) count_5++;
+	  }
+	}
+
+	if (count_1 >= save_igate_config_p->tx_limit_1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", save_igate_config_p->tx_limit_1);
+	  return 0;
+	}
+	if (count_5 >= save_igate_config_p->tx_limit_5) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5);
+	  return 0;
+	}
+
+	if (s_debug >= 2) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("ig_to_tx_allow? YES\n");
+	}
+
+	return 1;
+
+} /* end ig_to_tx_allow */
+
+/* end igate.c */
diff --git a/igate.h b/igate.h
index 5dfddaf..3e2d003 100644
--- a/igate.h
+++ b/igate.h
@@ -1,67 +1,87 @@
-
-/*----------------------------------------------------------------------------
- * 
- * Name:	igate.h
- *
- * Purpose:	Interface to the Internet Gateway functions.
- *
- *-----------------------------------------------------------------------------*/
-
-
-#ifndef IGATE_H
-#define IGATE_H 1
-
-
-#include "ax25_pad.h"
-#include "digipeater.h"
-#include "audio.h"
-
-
-#define DEFAULT_IGATE_PORT 14580
-
-
-struct igate_config_s {
-
-/*
- * For logging into the IGate server.
- */
-	char t2_server_name[40];	/* Tier 2 IGate server name. */
-
-	int t2_server_port;		/* Typically 14580. */
-
-	char t2_login[AX25_MAX_ADDR_LEN];/* e.g. WA9XYZ-15 */
-					/* Note that the ssid could be any two alphanumeric */
-					/* characters not just 1 thru 15. */
-					/* Could be same or different than the radio call(s). */
-					/* Not sure what the consequences would be. */
-
-	char t2_passcode[8];		/* Max. 5 digits. Could be "-1". */
-
-	char *t2_filter;		/* Optional filter for IS -> RF direction. */
-
-/*
- * For transmitting.
- */
-	int tx_chan;			/* Radio channel for transmitting. */
-					/* 0=first, etc.  -1 for none. */
-
-	char tx_via[80];		/* VIA path for transmitting third party packets. */
-					/* Usual text representation.  */
-					/* Must start with "," if not empty so it can */
-					/* simply be inserted after the destination address. */
-
-	int tx_limit_1;			/* Max. packets to transmit in 1 minute. */
-
-	int tx_limit_5;			/* Max. packets to transmit in 5 minutes. */
-};
-
-/* Call this once at startup */
-
-void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config);
-
-/* Call this with each packet received from the radio. */
-
-void igate_send_rec_packet (int chan, packet_t recv_pp);
-
-
-#endif
+
+/*----------------------------------------------------------------------------
+ * 
+ * Name:	igate.h
+ *
+ * Purpose:	Interface to the Internet Gateway functions.
+ *
+ *-----------------------------------------------------------------------------*/
+
+
+#ifndef IGATE_H
+#define IGATE_H 1
+
+
+#include "ax25_pad.h"
+#include "digipeater.h"
+#include "audio.h"
+
+
+#define DEFAULT_IGATE_PORT 14580
+
+
+
+struct igate_config_s {
+
+/*
+ * For logging into the IGate server.
+ */
+	char t2_server_name[40];	/* Tier 2 IGate server name. */
+
+	int t2_server_port;		/* Typically 14580. */
+
+	char t2_login[AX25_MAX_ADDR_LEN];/* e.g. WA9XYZ-15 */
+					/* Note that the ssid could be any two alphanumeric */
+					/* characters not just 1 thru 15. */
+					/* Could be same or different than the radio call(s). */
+					/* Not sure what the consequences would be. */
+
+	char t2_passcode[8];		/* Max. 5 digits. Could be "-1". */
+
+	char *t2_filter;		/* Optional filter for IS -> RF direction. */
+
+/*
+ * For transmitting.
+ */
+	int tx_chan;			/* Radio channel for transmitting. */
+					/* 0=first, etc.  -1 for none. */
+
+	char tx_via[80];		/* VIA path for transmitting third party packets. */
+					/* Usual text representation.  */
+					/* Must start with "," if not empty so it can */
+					/* simply be inserted after the destination address. */
+
+	int tx_limit_1;			/* Max. packets to transmit in 1 minute. */
+
+	int tx_limit_5;			/* Max. packets to transmit in 5 minutes. */
+/*
+ * Special SATgate mode to delay packets heard directly.
+ */
+	int satgate_delay;		/* seconds.  0 to disable. */
+};
+
+
+#define IGATE_TX_LIMIT_1_DEFAULT 6
+#define IGATE_TX_LIMIT_1_MAX     20
+
+#define IGATE_TX_LIMIT_5_DEFAULT 20
+#define IGATE_TX_LIMIT_5_MAX     80
+
+#define DEFAULT_SATGATE_DELAY 10
+#define MIN_SATGATE_DELAY 5
+#define MAX_SATGATE_DELAY 30
+
+
+/* Call this once at startup */
+
+void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config, int debug_level);
+
+/* Call this with each packet received from the radio. */
+
+void igate_send_rec_packet (int chan, packet_t recv_pp);
+
+/* This when digipeater transmits.  Set bydigi to 1 . */
+
+void ig_to_tx_remember (packet_t pp, int chan, int bydigi);
+
+#endif
diff --git a/kiss.c b/kiss.c
index 4a4cc9b..db68d69 100644
--- a/kiss.c
+++ b/kiss.c
@@ -1,962 +1,962 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-
-
-/*------------------------------------------------------------------
- *
- * Module:      kiss.c
- *
- * Purpose:   	Act as a virtual KISS TNC for use by other packet radio applications.
- *		
- * Input:	
- *
- * Outputs:	  
- *
- * Description:	This provides a pseudo terminal for communication with a client application.
- *
- *		It implements the KISS TNC protocol as described in:
- *		http://www.ka9q.net/papers/kiss.html
- *
- * 		Briefly, a frame is composed of 
- *
- *			* FEND (0xC0)
- *			* Contents - with special escape sequences so a 0xc0
- *				byte in the data is not taken as end of frame.
- *				as part of the data.
- *			* FEND
- *
- *		The first byte of the frame contains:
- *	
- *			* port number in upper nybble.
- *			* command in lower nybble.
- *
- *	
- *		Commands from application recognized:
- *
- *			0	Data Frame	AX.25 frame in raw format.
- *
- *			1	TXDELAY		See explanation in xmit.c.
- *
- *			2	Persistence	"	"
- *
- *			3 	SlotTime	"	"
- *
- *			4	TXtail		"	"
- *						Spec says it is obsolete but Xastir
- *						sends it and we respect it.
- *
- *			5	FullDuplex	Ignored.  Always full duplex.
- *		
- *			6	SetHardware	TNC specific.  Ignored.
- *			
- *			FF	Return		Exit KISS mode.  Ignored.
- *
- *
- *		Messages sent to client application:
- *
- *			0	Data Frame	Received AX.25 frame in raw format.
- *
- *
- *		
- * Platform differences:
- *
- *		We can use a pseudo terminal for Linux or Cygwin applications.
- *		However, Microsoft Windows doesn't seem to have similar functionality.
- *		Native Windows applications expect to see a device named COM1,
- *		COM2, COM3, or COM4.  Some might offer more flexibility but others
- *		might be limited to these four choices.
- *
- *		The documentation instucts the user to install the com0com 
- *		�Null-modem emulator� from http://sourceforge.net/projects/com0com/   
- *		and configure it for COM3 & COM4.
- *
- *		By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!)
- *		and the client application will use COM4 (available as /dev/ttyS or
- *		/dev/com4 for Cygwin applications).
- *
- *
- *		This can get confusing.
- *
- *		If __WIN32__ is defined, 
- *			We use the Windows interface to the specfied serial port.
- *			This could be a real serial port or the nullmodem driver
- *			connected to another application.
- *		
- *		If __CYGWIN__ is defined,
- *			We connect to a serial port as in the previous case but
- *			use the Linux I/O interface.
- *			We also supply a pseudo terminal for any Cygwin applications 
- *			such as Xastir so the null modem is not needed.
- *
- *		For the Linux case,
- *			We supply a pseudo terminal for use by other applications.
- *
- *
- * Reference:	http://www.robbayer.com/files/serial-win.pdf
- *
- *---------------------------------------------------------------*/
-
-#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>
-#ifdef __OpenBSD__
-#include <errno.h>
-#else
-#include <sys/errno.h>
-#endif
-#endif
-
-#include <assert.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "tq.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "kiss.h"
-#include "kiss_frame.h"
-#include "xmit.h"
-
-
-#if __WIN32__
-typedef HANDLE MYFDTYPE;
-#define MYFDERROR INVALID_HANDLE_VALUE
-#else
-typedef int MYFDTYPE;
-#define MYFDERROR (-1)
-#endif
-
-
-static kiss_frame_t kf;		/* Accumulated KISS frame and state of decoder. */
-
-
-/*
- * These are for a Linux/Cygwin pseudo terminal.
- */
-
-#if ! __WIN32__
-
-static MYFDTYPE pt_master_fd = MYFDERROR;	/* File descriptor for my end. */
-
-static char pt_slave_name[32];			/* Pseudo terminal slave name  */
-						/* like /dev/pts/999 */
-
-
-
-/*
- * Symlink to pseudo terminal name which changes.
- */
-
-#define TMP_KISSTNC_SYMLINK "/tmp/kisstnc"
-
-#endif
-
-/*
- * This is for native Windows applications and a virtual null modem.
- */
-
-#if __CYGWIN__ || __WIN32__
-
-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 THREAD_F kiss_listen_thread (void *arg);
-
-
-
-#if DEBUG9
-static FILE *log_fp;
-#endif
-
-
-static int kiss_debug = 0;		/* Print information flowing from and to client. */
-
-void kiss_serial_set_debug (int n) 
-{	
-	kiss_debug = n;
-}
-
-
-/* In server.c.  Should probably move to some misc. function file. */
-
-void hex_dump (unsigned char *p, int len);
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kiss_init
- *
- * Purpose:     Set up a pseudo terminal acting as a virtual KISS TNC.
- *		
- *
- * Inputs:	mc->nullmodem	- name of device for our end of nullmodem.
- *
- * Outputs:	
- *
- * Description:	(1) Create a pseudo terminal for the client to use.
- *		(2) Start a new thread to listen for commands from client app
- *		    so the main application doesn't block while we wait.
- *
- *
- *--------------------------------------------------------------------*/
-
-static MYFDTYPE kiss_open_pt (void);
-static MYFDTYPE kiss_open_nullmodem (char *device);
-
-void kiss_init (struct misc_config_s *mc)
-{
-	int e;
-#if __WIN32__
-	HANDLE kiss_nullmodem_listen_th;
-#else
-	pthread_t kiss_pterm_listen_tid;
-	pthread_t kiss_nullmodem_listen_tid;
-#endif
-
-	memset (&kf, 0, sizeof(kf));
-
-/*
- * This reads messages from client.
- */
-
-#if ! __WIN32__
-
-/*
- * Pseudo terminal for Cygwin and Linux versions.
- */
-	pt_master_fd = MYFDERROR;
-
-	if (mc->enable_kiss_pt) {
-
-	  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, NULL);
-	    if (e != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      perror("Could not create kiss listening thread for Linux pseudo terminal");
-	    }
-	  }
-	}
-	else {
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n");
-	}
-#endif
-
-#if __CYGWIN__ || __WIN32
-
-/*
- * Cygwin and native Windows versions have serial port connection.
- */
-	if (strlen(mc->nullmodem) > 0) {
-
-#if ! __WIN32__
-
-	  /* Translate Windows device name into Linux name. */
-	  /* COM1 -> /dev/ttyS0, etc. */
-
-	  if (strncasecmp(mc->nullmodem, "COM", 3) == 0) {
-	    int n = atoi (mc->nullmodem + 3);
-	    text_color_set(DW_COLOR_INFO);
-	    dw_printf ("Converted nullmodem device '%s'", mc->nullmodem);
-	    if (n < 1) n = 1;
-	    sprintf (mc->nullmodem, "/dev/ttyS%d", n-1);
-	    dw_printf (" to Linux equivalent '%s'\n", mc->nullmodem);
-	  }
-#endif
-	  nullmodem_fd = kiss_open_nullmodem (mc->nullmodem);
-
-	  if (nullmodem_fd != MYFDERROR) {
-#if __WIN32__
-	    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, NULL);
-	    if (e != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      perror("Could not create kiss listening thread for Windows virtual COM port.");
-	    
-	    }
-#endif
-	  }
-	}
-#endif
-
-
-#if DEBUG
-	text_color_set (DW_COLOR_DEBUG);
-#if ! __WIN32__
-	dw_printf ("end of kiss_init: pt_master_fd = %d\n", pt_master_fd);
-#endif
-#if __CYGWIN__ || __WIN32__
-	dw_printf ("end of kiss_init: nullmodem_fd = %d\n", nullmodem_fd);
-#endif
-
-#endif
-}
-
-
-/*
- * Returns fd for master side of pseudo terminal or MYFDERROR for error.
- */
-
-#if ! __WIN32__
-
-static MYFDTYPE kiss_open_pt (void)
-{
-	int fd;
-	char *pts;
-	struct termios ts;
-	int e;
-	//int flags;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kiss_open_pt (  )\n");
-#endif
-	
-
-	fd = posix_openpt(O_RDWR|O_NOCTTY);
-
-	if (fd == MYFDERROR
-	    || grantpt (fd) == MYFDERROR
-	    || unlockpt (fd) == MYFDERROR
-	    || (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) { 
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Can't get pseudo terminal attributes, err=%d\n", e);
-	  perror ("pt tcgetattr"); 
-	}
-
-	cfmakeraw (&ts);
-	
-	ts.c_cc[VMIN] = 1;	/* wait for at least one character */
-	ts.c_cc[VTIME] = 0;	/* no fancy timing. */
-				
-
-	e = tcsetattr (fd, TCSANOW, &ts);
-	if (e != 0) { 
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Can't set pseudo terminal attributes, err=%d\n", e);
-	  perror ("pt tcsetattr"); 
-	}
-
-/*
- * After running for a while on Linux, the write eventually
- * blocks if no one is reading from the other side of
- * the pseudo terminal.  We get stuck on the kiss data
- * write and reception stops.
- *
- * I tried using ioctl(,TIOCOUTQ,) to see how much was in 
- * the queue but that always returned zero.  (Ubuntu)
- *
- * Let's try using non-blocking writes and see if we get
- * the EWOULDBLOCK status instead of hanging.
- */
-
-#if 0 	// this is worse. all writes fail. errno = 0 bad file descriptor
-	flags = fcntl(fd, F_GETFL, 0);
-	e = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
-	if (e != 0) { 
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno);
-	  perror ("pt fcntl"); 
-	}
-#endif
-#if 0  // same  
-	flags = 1;	
-	e = ioctl (fd, FIONBIO, &flags);
-	if (e != 0) { 
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno);
-	  perror ("pt ioctl"); 
-	}
-#endif
-	text_color_set(DW_COLOR_INFO);
-	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
- * be necessary to change the device name in the configuration.
- * Create a symlink, /tmp/kisstnc, so the application configuration
- * does not need to change when the pseudo terminal name changes.
- */
-
-	unlink (TMP_KISSTNC_SYMLINK);
-
-
-// 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", TMP_KISSTNC_SYMLINK);	
-	    perror ("");
-	}
-
-	return (fd);
-}
-
-#endif
-
-/*
- * Returns fd for our side of null modem or MYFDERROR for error.
- */
-
-
-#if __CYGWIN__ || __WIN32__
-
-static MYFDTYPE kiss_open_nullmodem (char *devicename)
-{
-
-#if __WIN32__
-
-	MYFDTYPE fd;
-	DCB dcb;
-	int ok;	
-	char bettername[50];
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
-#endif
-	
-#if DEBUG9
-	log_fp = fopen ("kiss-debug.txt", "w");
-#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 connect to %s side of null modem for Windows KISS TNC.\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 ("kiss_open_nullmodem: GetCommState failed.\n");
-	}
-
-	/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
-
-	dcb.DCBlength = sizeof(DCB);
-	dcb.BaudRate = CBR_9600;	// shouldn't matter 
-	dcb.fBinary = 1;
-	dcb.fParity = 0;
-	dcb.fOutxCtsFlow = 0;
-	dcb.fOutxDsrFlow = 0;
-	dcb.fDtrControl = 0;
-	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 ("kiss_open_nullmodem: SetCommState failed.\n");
-	}
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
-
-#else
-
-/* Cygwin version. */
-
-	int fd;
-	struct termios ts;
-	int e;
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
-#endif
-
-	fd = open (devicename, O_RDWR);
-
-	if (fd == MYFDERROR) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\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. */
-
-	e = tcsetattr (fd, TCSANOW, &ts);
-	if (e != 0) { perror ("nm tcsetattr"); }
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
-
-#endif
-
-	return (fd);
-}
-
-#endif
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kiss_send_rec_packet
- *
- * Purpose:     Send a received packet or text string to the client app.
- *
- * Inputs:	chan		- Channel number where packet was received.
- *				  0 = first, 1 = second if any.
- *
- *		pp		- Identifier for packet object.
- *
- *		fbuf		- Address of raw received frame buffer
- *				  or a text string.
- *
- *		flen		- Length of raw received frame not including the FCS
- *				  or -1 for a text string.
- *		
- *
- * Description:	Send message to client.
- *		We really don't care if anyone is listening or not.
- *		I don't even know if we can find out.
- *
- *
- *--------------------------------------------------------------------*/
-
-
-void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen)
-{
-	unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
-	int kiss_len;
-	int j;
-	int err;
-
-#if ! __WIN32__
-	if (pt_master_fd == MYFDERROR) {
-	  return;
-	}
-#endif
-
-#if __CYGWIN__ || __WIN32__
-
-	if (nullmodem_fd == MYFDERROR) {
-	  return;
-	}
-#endif
-	
-	if (flen < 0) {
-	  flen = strlen((char*)fbuf);
-	  if (kiss_debug) {
-	    kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
-	  }
-	  strcpy ((char *)kiss_buff, (char *)fbuf);
-	  kiss_len = strlen((char *)kiss_buff);
-	}
-	else {
-
-
-	  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_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, kiss_len);
-	  }
-
-	}
-
-#if ! __WIN32__
-
-/* Pseudo terminal for Cygwin and Linux. */
-
-
-        err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
-
-	if (err == -1 && errno == EWOULDBLOCK) {
-#if DEBUG 
-	  text_color_set (DW_COLOR_INFO);
-	  dw_printf ("KISS SEND - discarding message because write would block.\n");
-#endif
-	}
-	else if (err != kiss_len)
-	{
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError sending KISS message to client application on pseudo terminal.  fd=%d, len=%d, write returned %d, errno = %d\n\n",
-		pt_master_fd, kiss_len, err, errno);
-	  perror ("pt write"); 
-	}
-
-#endif
-
-#if __CYGWIN__ || __WIN32__
-
-
-/*
- * This write can block if nothing is connected to the other end.
- * The solution is found in the com0com ReadMe file:
- *
- *	Q. My application hangs during its startup when it sends anything to one paired
- *	   COM port. The only way to unhang it is to start HyperTerminal, which is connected
- *	   to the other paired COM port. I didn't have this problem with physical serial
- *	   ports.
- *	A. Your application can hang because receive buffer overrun is disabled by
- *	   default. You can fix the problem by enabling receive buffer overrun for the
- *	   receiving port. Also, to prevent some flow control issues you need to enable
- *	   baud rate emulation for the sending port. So, if your application use port CNCA0
- *	   and other paired port is CNCB0, then:
- *	
- *	   1. Launch the Setup Command Prompt shortcut.
- *	   2. Enter the change commands, for example:
- *	
- *	      command> change CNCB0 EmuOverrun=yes
- *	      command> change CNCA0 EmuBR=yes
- */
-
-#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 (nullmodem_fd, kiss_buff, kiss_len, &nwritten, &ov_wr))
-	  {
-	    err = GetLastError();
-	    if (err != ERROR_IO_PENDING) 
-	    {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("\nError sending KISS message to client application thru null modem.  Error %d.\n\n", (int)GetLastError());
-	      //CloseHandle (nullmodem_fd);
-	      //nullmodem_fd = MYFDERROR;
-	    }
-	  }
-	  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);
-	    //CloseHandle (nullmodem_fd);
-	    //nullmodem_fd = MYFDERROR;
-	  }
-
-#if DEBUG
-	  /* Could wait with GetOverlappedResult but we never */
-	  /* have an issues in this direction. */
-	  //text_color_set(DW_COLOR_DEBUG);
-	  //dw_printf ("KISS SEND completed.  wrote %d / %d\n", nwritten, kiss_len);
-#endif
-
-#else
-          err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len);
-	  if (err != len)
-	  {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("\nError sending KISS message to client application thru null modem. err=%d\n\n", err);
-	    //close (nullmodem_fd);
-	    //nullmodem_fd = MYFDERROR;
-	  }
-#endif
-
-#endif
-
-} /* kiss_send_rec_packet */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kiss_listen_thread
- *
- * Purpose:     Wait for messages from an application.
- *
- * Global In:	nullmodem_fd or pt_master_fd
- *
- * Description:	Process messages from the client application.
- *
- *--------------------------------------------------------------------*/
-
-/* Return one byte (value 0 - 255) or terminate thread on error. */
-
-
-static int kiss_get (/* MYFDTYPE fd*/ void )
-{
-	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 (nullmodem_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 (nullmodem_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 (nullmodem_fd);
-	      nullmodem_fd = MYFDERROR;
-	      //pthread_exit (NULL);
-	    }
-	  }
-
-	}	/* end while n==0 */
-
-	CloseHandle(ov_rd.hEvent); 
-
-	if (n != 1) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nKISS failed to get one byte. n=%d.\n\n", (int)n);
-
-#if DEBUG9
-	  fprintf (log_fp, "n=%d\n", n);
-#endif
-	}
-
-
-#else		/* Linux/Cygwin version */
-
-	int n = 0;
-
-	while ( n == 0 ) {
-
-	  n = read(pt_master_fd, &ch, (size_t)1);
-
-	  if (n != 1) {
-
-	    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. */
-
-	    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
-
-#if DEBUGx
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kiss_get(%d) returns 0x%02x\n", fd, ch);
-#endif
-
-#if DEBUG9
-	fprintf (log_fp, "%02x %c %c", ch, 
-			isprint(ch) ? ch : '.' , 
-			(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
-	if (ch == FEND) fprintf (log_fp, "  FEND");
-	if (ch == FESC) fprintf (log_fp, "  FESC");
-	if (ch == TFEND) fprintf (log_fp, "  TFEND");
-	if (ch == TFESC) fprintf (log_fp, "  TFESC");
-	if (ch == '\r') fprintf (log_fp, "  CR");
-	if (ch == '\n') fprintf (log_fp, "  LF");
-	fprintf (log_fp, "\n");
-	if (ch == FEND) fflush (log_fp);
-#endif
-	return (ch);
-}
-
-
-
-
-static THREAD_F kiss_listen_thread (void *arg)
-{
-	unsigned char ch;
-			
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kiss_listen_thread ( %d )\n", fd);
-#endif
-
-
-	while (1) {
-	  ch = kiss_get();
-	  kiss_rec_byte (&kf, ch, kiss_debug, kiss_send_rec_packet);
-	}
-
-#if __WIN32__
-	return(0);
-#else
-	return;	/* Unreachable but avoids compiler warning. */
-#endif
-}
-
-/* end kiss.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    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
+//    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/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      kiss.c
+ *
+ * Purpose:   	Act as a virtual KISS TNC for use by other packet radio applications.
+ *		
+ * Input:	
+ *
+ * Outputs:	  
+ *
+ * Description:	This provides a pseudo terminal for communication with a client application.
+ *
+ *		It implements the KISS TNC protocol as described in:
+ *		http://www.ka9q.net/papers/kiss.html
+ *
+ * 		Briefly, a frame is composed of 
+ *
+ *			* FEND (0xC0)
+ *			* Contents - with special escape sequences so a 0xc0
+ *				byte in the data is not taken as end of frame.
+ *				as part of the data.
+ *			* FEND
+ *
+ *		The first byte of the frame contains:
+ *	
+ *			* port number in upper nybble.
+ *			* command in lower nybble.
+ *
+ *	
+ *		Commands from application recognized:
+ *
+ *			0	Data Frame	AX.25 frame in raw format.
+ *
+ *			1	TXDELAY		See explanation in xmit.c.
+ *
+ *			2	Persistence	"	"
+ *
+ *			3 	SlotTime	"	"
+ *
+ *			4	TXtail		"	"
+ *						Spec says it is obsolete but Xastir
+ *						sends it and we respect it.
+ *
+ *			5	FullDuplex	Ignored.  Always full duplex.
+ *		
+ *			6	SetHardware	TNC specific.  Ignored.
+ *			
+ *			FF	Return		Exit KISS mode.  Ignored.
+ *
+ *
+ *		Messages sent to client application:
+ *
+ *			0	Data Frame	Received AX.25 frame in raw format.
+ *
+ *
+ *		
+ * Platform differences:
+ *
+ *		We can use a pseudo terminal for Linux or Cygwin applications.
+ *		However, Microsoft Windows doesn't seem to have similar functionality.
+ *		Native Windows applications expect to see a device named COM1,
+ *		COM2, COM3, or COM4.  Some might offer more flexibility but others
+ *		might be limited to these four choices.
+ *
+ *		The documentation instucts the user to install the com0com 
+ *		"Null-modem emulator" from http://sourceforge.net/projects/com0com/   
+ *		and configure it for COM3 & COM4.
+ *
+ *		By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!)
+ *		and the client application will use COM4 (available as /dev/ttyS or
+ *		/dev/com4 for Cygwin applications).
+ *
+ *
+ *		This can get confusing.
+ *
+ *		If __WIN32__ is defined, 
+ *			We use the Windows interface to the specfied serial port.
+ *			This could be a real serial port or the nullmodem driver
+ *			connected to another application.
+ *		
+ *		If __CYGWIN__ is defined,
+ *			We connect to a serial port as in the previous case but
+ *			use the Linux I/O interface.
+ *			We also supply a pseudo terminal for any Cygwin applications 
+ *			such as Xastir so the null modem is not needed.
+ *
+ *		For the Linux case,
+ *			We supply a pseudo terminal for use by other applications.
+ *
+ *
+ * Reference:	http://www.robbayer.com/files/serial-win.pdf
+ *
+ *---------------------------------------------------------------*/
+
+#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>
+#ifdef __OpenBSD__
+#include <errno.h>
+#else
+#include <sys/errno.h>
+#endif
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "tq.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "kiss.h"
+#include "kiss_frame.h"
+#include "xmit.h"
+
+
+#if __WIN32__
+typedef HANDLE MYFDTYPE;
+#define MYFDERROR INVALID_HANDLE_VALUE
+#else
+typedef int MYFDTYPE;
+#define MYFDERROR (-1)
+#endif
+
+
+static kiss_frame_t kf;		/* Accumulated KISS frame and state of decoder. */
+
+
+/*
+ * These are for a Linux/Cygwin pseudo terminal.
+ */
+
+#if ! __WIN32__
+
+static MYFDTYPE pt_master_fd = MYFDERROR;	/* File descriptor for my end. */
+
+static char pt_slave_name[32];			/* Pseudo terminal slave name  */
+						/* like /dev/pts/999 */
+
+
+
+/*
+ * Symlink to pseudo terminal name which changes.
+ */
+
+#define TMP_KISSTNC_SYMLINK "/tmp/kisstnc"
+
+#endif
+
+/*
+ * This is for native Windows applications and a virtual null modem.
+ */
+
+#if __CYGWIN__ || __WIN32__
+
+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 THREAD_F kiss_listen_thread (void *arg);
+
+
+
+#if DEBUG9
+static FILE *log_fp;
+#endif
+
+
+static int kiss_debug = 0;		/* Print information flowing from and to client. */
+
+void kiss_serial_set_debug (int n) 
+{	
+	kiss_debug = n;
+}
+
+
+/* In server.c.  Should probably move to some misc. function file. */
+
+void hex_dump (unsigned char *p, int len);
+
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_init
+ *
+ * Purpose:     Set up a pseudo terminal acting as a virtual KISS TNC.
+ *		
+ *
+ * Inputs:	mc->nullmodem	- name of device for our end of nullmodem.
+ *
+ * Outputs:	
+ *
+ * Description:	(1) Create a pseudo terminal for the client to use.
+ *		(2) Start a new thread to listen for commands from client app
+ *		    so the main application doesn't block while we wait.
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+static MYFDTYPE kiss_open_pt (void);
+static MYFDTYPE kiss_open_nullmodem (char *device);
+
+void kiss_init (struct misc_config_s *mc)
+{
+	int e;
+#if __WIN32__
+	HANDLE kiss_nullmodem_listen_th;
+#else
+	pthread_t kiss_pterm_listen_tid;
+	pthread_t kiss_nullmodem_listen_tid;
+#endif
+
+	memset (&kf, 0, sizeof(kf));
+
+/*
+ * This reads messages from client.
+ */
+
+#if ! __WIN32__
+
+/*
+ * Pseudo terminal for Cygwin and Linux versions.
+ */
+	pt_master_fd = MYFDERROR;
+
+	if (mc->enable_kiss_pt) {
+
+	  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, NULL);
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror("Could not create kiss listening thread for Linux pseudo terminal");
+	    }
+	  }
+	}
+	else {
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n");
+	}
+#endif
+
+#if __CYGWIN__ || __WIN32
+
+/*
+ * Cygwin and native Windows versions have serial port connection.
+ */
+	if (strlen(mc->nullmodem) > 0) {
+
+#if ! __WIN32__
+
+	  /* Translate Windows device name into Linux name. */
+	  /* COM1 -> /dev/ttyS0, etc. */
+
+	  if (strncasecmp(mc->nullmodem, "COM", 3) == 0) {
+	    int n = atoi (mc->nullmodem + 3);
+	    text_color_set(DW_COLOR_INFO);
+	    dw_printf ("Converted nullmodem device '%s'", mc->nullmodem);
+	    if (n < 1) n = 1;
+	    snprintf (mc->nullmodem, sizeof(mc->nullmodem), "/dev/ttyS%d", n-1);
+	    dw_printf (" to Linux equivalent '%s'\n", mc->nullmodem);
+	  }
+#endif
+	  nullmodem_fd = kiss_open_nullmodem (mc->nullmodem);
+
+	  if (nullmodem_fd != MYFDERROR) {
+#if __WIN32__
+	    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, NULL);
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror("Could not create kiss listening thread for Windows virtual COM port.");
+	    
+	    }
+#endif
+	  }
+	}
+#endif
+
+
+#if DEBUG
+	text_color_set (DW_COLOR_DEBUG);
+#if ! __WIN32__
+	dw_printf ("end of kiss_init: pt_master_fd = %d\n", pt_master_fd);
+#endif
+#if __CYGWIN__ || __WIN32__
+	dw_printf ("end of kiss_init: nullmodem_fd = %d\n", nullmodem_fd);
+#endif
+
+#endif
+}
+
+
+/*
+ * Returns fd for master side of pseudo terminal or MYFDERROR for error.
+ */
+
+#if ! __WIN32__
+
+static MYFDTYPE kiss_open_pt (void)
+{
+	int fd;
+	char *pts;
+	struct termios ts;
+	int e;
+	//int flags;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kiss_open_pt (  )\n");
+#endif
+	
+
+	fd = posix_openpt(O_RDWR|O_NOCTTY);
+
+	if (fd == MYFDERROR
+	    || grantpt (fd) == MYFDERROR
+	    || unlockpt (fd) == MYFDERROR
+	    || (pts = ptsname (fd)) == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n");
+	  return (MYFDERROR);
+	}
+
+	strlcpy (pt_slave_name, pts, sizeof(pt_slave_name));
+
+	e = tcgetattr (fd, &ts);
+	if (e != 0) { 
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Can't get pseudo terminal attributes, err=%d\n", e);
+	  perror ("pt tcgetattr"); 
+	}
+
+	cfmakeraw (&ts);
+	
+	ts.c_cc[VMIN] = 1;	/* wait for at least one character */
+	ts.c_cc[VTIME] = 0;	/* no fancy timing. */
+				
+
+	e = tcsetattr (fd, TCSANOW, &ts);
+	if (e != 0) { 
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Can't set pseudo terminal attributes, err=%d\n", e);
+	  perror ("pt tcsetattr"); 
+	}
+
+/*
+ * After running for a while on Linux, the write eventually
+ * blocks if no one is reading from the other side of
+ * the pseudo terminal.  We get stuck on the kiss data
+ * write and reception stops.
+ *
+ * I tried using ioctl(,TIOCOUTQ,) to see how much was in 
+ * the queue but that always returned zero.  (Ubuntu)
+ *
+ * Let's try using non-blocking writes and see if we get
+ * the EWOULDBLOCK status instead of hanging.
+ */
+
+#if 0 	// this is worse. all writes fail. errno = 0 bad file descriptor
+	flags = fcntl(fd, F_GETFL, 0);
+	e = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+	if (e != 0) { 
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno);
+	  perror ("pt fcntl"); 
+	}
+#endif
+#if 0  // same  
+	flags = 1;	
+	e = ioctl (fd, FIONBIO, &flags);
+	if (e != 0) { 
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno);
+	  perror ("pt ioctl"); 
+	}
+#endif
+	text_color_set(DW_COLOR_INFO);
+	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
+ * be necessary to change the device name in the configuration.
+ * Create a symlink, /tmp/kisstnc, so the application configuration
+ * does not need to change when the pseudo terminal name changes.
+ */
+
+	unlink (TMP_KISSTNC_SYMLINK);
+
+
+// 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", TMP_KISSTNC_SYMLINK);	
+	    perror ("");
+	}
+
+	return (fd);
+}
+
+#endif
+
+/*
+ * Returns fd for our side of null modem or MYFDERROR for error.
+ */
+
+
+#if __CYGWIN__ || __WIN32__
+
+static MYFDTYPE kiss_open_nullmodem (char *devicename)
+{
+
+#if __WIN32__
+
+	MYFDTYPE fd;
+	DCB dcb;
+	int ok;	
+	char bettername[50];
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
+#endif
+	
+#if DEBUG9
+	log_fp = fopen ("kiss-debug.txt", "w");
+#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
+
+	strlcpy (bettername, devicename, sizeof(bettername));
+	if (strncasecmp(devicename, "COM", 3) == 0) {
+	  int n;
+	  n = atoi(devicename+3);
+	  if (n >= 10) {
+	    strlcpy (bettername, "\\\\.\\", sizeof(bettername));
+	    strlcat (bettername, devicename, sizeof(bettername));
+	  }
+	}
+	
+	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 connect to %s side of null modem for Windows KISS TNC.\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 ("kiss_open_nullmodem: GetCommState failed.\n");
+	}
+
+	/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
+
+	dcb.DCBlength = sizeof(DCB);
+	dcb.BaudRate = CBR_9600;	// shouldn't matter 
+	dcb.fBinary = 1;
+	dcb.fParity = 0;
+	dcb.fOutxCtsFlow = 0;
+	dcb.fOutxDsrFlow = 0;
+	dcb.fDtrControl = 0;
+	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 ("kiss_open_nullmodem: SetCommState failed.\n");
+	}
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
+
+#else
+
+/* Cygwin version. */
+
+	int fd;
+	struct termios ts;
+	int e;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
+#endif
+
+	fd = open (devicename, O_RDWR);
+
+	if (fd == MYFDERROR) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\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. */
+
+	e = tcsetattr (fd, TCSANOW, &ts);
+	if (e != 0) { perror ("nm tcsetattr"); }
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
+
+#endif
+
+	return (fd);
+}
+
+#endif
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_send_rec_packet
+ *
+ * Purpose:     Send a received packet or text string to the client app.
+ *
+ * Inputs:	chan		- Channel number where packet was received.
+ *				  0 = first, 1 = second if any.
+ *
+ *		pp		- Identifier for packet object.
+ *
+ *		fbuf		- Address of raw received frame buffer
+ *				  or a text string.
+ *
+ *		flen		- Length of raw received frame not including the FCS
+ *				  or -1 for a text string.
+ *		
+ *
+ * Description:	Send message to client.
+ *		We really don't care if anyone is listening or not.
+ *		I don't even know if we can find out.
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+
+void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen)
+{
+	unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
+	int kiss_len;
+	int j;
+	int err;
+
+#if ! __WIN32__
+	if (pt_master_fd == MYFDERROR) {
+	  return;
+	}
+#endif
+
+#if __CYGWIN__ || __WIN32__
+
+	if (nullmodem_fd == MYFDERROR) {
+	  return;
+	}
+#endif
+	
+	if (flen < 0) {
+	  flen = strlen((char*)fbuf);
+	  if (kiss_debug) {
+	    kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
+	  }
+	  strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
+	  kiss_len = strlen((char *)kiss_buff);
+	}
+	else {
+
+
+	  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 (fbuf, flen);
+	  }
+
+	  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, kiss_len);
+	  }
+
+	}
+
+#if ! __WIN32__
+
+/* Pseudo terminal for Cygwin and Linux. */
+
+
+        err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
+
+	if (err == -1 && errno == EWOULDBLOCK) {
+#if DEBUG 
+	  text_color_set (DW_COLOR_INFO);
+	  dw_printf ("KISS SEND - discarding message because write would block.\n");
+#endif
+	}
+	else if (err != kiss_len)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError sending KISS message to client application on pseudo terminal.  fd=%d, len=%d, write returned %d, errno = %d\n\n",
+		pt_master_fd, kiss_len, err, errno);
+	  perror ("pt write"); 
+	}
+
+#endif
+
+#if __CYGWIN__ || __WIN32__
+
+
+/*
+ * This write can block if nothing is connected to the other end.
+ * The solution is found in the com0com ReadMe file:
+ *
+ *	Q. My application hangs during its startup when it sends anything to one paired
+ *	   COM port. The only way to unhang it is to start HyperTerminal, which is connected
+ *	   to the other paired COM port. I didn't have this problem with physical serial
+ *	   ports.
+ *	A. Your application can hang because receive buffer overrun is disabled by
+ *	   default. You can fix the problem by enabling receive buffer overrun for the
+ *	   receiving port. Also, to prevent some flow control issues you need to enable
+ *	   baud rate emulation for the sending port. So, if your application use port CNCA0
+ *	   and other paired port is CNCB0, then:
+ *	
+ *	   1. Launch the Setup Command Prompt shortcut.
+ *	   2. Enter the change commands, for example:
+ *	
+ *	      command> change CNCB0 EmuOverrun=yes
+ *	      command> change CNCA0 EmuBR=yes
+ */
+
+#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 (nullmodem_fd, kiss_buff, kiss_len, &nwritten, &ov_wr))
+	  {
+	    err = GetLastError();
+	    if (err != ERROR_IO_PENDING) 
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("\nError sending KISS message to client application thru null modem.  Error %d.\n\n", (int)GetLastError());
+	      //CloseHandle (nullmodem_fd);
+	      //nullmodem_fd = MYFDERROR;
+	    }
+	  }
+	  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);
+	    //CloseHandle (nullmodem_fd);
+	    //nullmodem_fd = MYFDERROR;
+	  }
+
+#if DEBUG
+	  /* Could wait with GetOverlappedResult but we never */
+	  /* have an issues in this direction. */
+	  //text_color_set(DW_COLOR_DEBUG);
+	  //dw_printf ("KISS SEND completed.  wrote %d / %d\n", nwritten, kiss_len);
+#endif
+
+#else
+          err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len);
+	  if (err != len)
+	  {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("\nError sending KISS message to client application thru null modem. err=%d\n\n", err);
+	    //close (nullmodem_fd);
+	    //nullmodem_fd = MYFDERROR;
+	  }
+#endif
+
+#endif
+
+} /* kiss_send_rec_packet */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_listen_thread
+ *
+ * Purpose:     Wait for messages from an application.
+ *
+ * Global In:	nullmodem_fd or pt_master_fd
+ *
+ * Description:	Process messages from the client application.
+ *
+ *--------------------------------------------------------------------*/
+
+/* Return one byte (value 0 - 255) or terminate thread on error. */
+
+
+static int kiss_get (/* MYFDTYPE fd*/ void )
+{
+	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 (nullmodem_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 (nullmodem_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 (nullmodem_fd);
+	      nullmodem_fd = MYFDERROR;
+	      //pthread_exit (NULL);
+	    }
+	  }
+
+	}	/* end while n==0 */
+
+	CloseHandle(ov_rd.hEvent); 
+
+	if (n != 1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nKISS failed to get one byte. n=%d.\n\n", (int)n);
+
+#if DEBUG9
+	  fprintf (log_fp, "n=%d\n", n);
+#endif
+	}
+
+
+#else		/* Linux/Cygwin version */
+
+	int n = 0;
+
+	while ( n == 0 ) {
+
+	  n = read(pt_master_fd, &ch, (size_t)1);
+
+	  if (n != 1) {
+
+	    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. */
+
+	    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
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kiss_get(%d) returns 0x%02x\n", fd, ch);
+#endif
+
+#if DEBUG9
+	fprintf (log_fp, "%02x %c %c", ch, 
+			isprint(ch) ? ch : '.' , 
+			(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
+	if (ch == FEND) fprintf (log_fp, "  FEND");
+	if (ch == FESC) fprintf (log_fp, "  FESC");
+	if (ch == TFEND) fprintf (log_fp, "  TFEND");
+	if (ch == TFESC) fprintf (log_fp, "  TFESC");
+	if (ch == '\r') fprintf (log_fp, "  CR");
+	if (ch == '\n') fprintf (log_fp, "  LF");
+	fprintf (log_fp, "\n");
+	if (ch == FEND) fflush (log_fp);
+#endif
+	return (ch);
+}
+
+
+
+
+static THREAD_F kiss_listen_thread (void *arg)
+{
+	unsigned char ch;
+			
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kiss_listen_thread ( %d )\n", fd);
+#endif
+
+
+	while (1) {
+	  ch = kiss_get();
+	  kiss_rec_byte (&kf, ch, kiss_debug, kiss_send_rec_packet);
+	}
+
+#if __WIN32__
+	return(0);
+#else
+	return (THREAD_F) 0;	/* Unreachable but avoids compiler warning. */
+#endif
+}
+
+/* end kiss.c */
diff --git a/kiss.h b/kiss.h
index 83563c4..4c037fb 100644
--- a/kiss.h
+++ b/kiss.h
@@ -1,21 +1,21 @@
-
-/* 
- * Name:	kiss.h
- */
-
-
-#include "ax25_pad.h"		/* for packet_t */
-
-#include "config.h"
-
-
-
-
-void kiss_init (struct misc_config_s *misc_config);
-
-void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen);
-
-void kiss_serial_set_debug (int n);
-
-
-/* end kiss.h */
+
+/* 
+ * Name:	kiss.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+
+
+
+void kiss_init (struct misc_config_s *misc_config);
+
+void kiss_send_rec_packet (int chan, unsigned char *fbuf,  int flen);
+
+void kiss_serial_set_debug (int n);
+
+
+/* end kiss.h */
diff --git a/kiss_frame.c b/kiss_frame.c
index 3aff9bd..f988412 100644
--- a/kiss_frame.c
+++ b/kiss_frame.c
@@ -1,662 +1,670 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-
-
-/*------------------------------------------------------------------
- *
- * Module:      kiss_frame.c
- *
- * Purpose:   	Common code used by Serial port and network versions of KISS protocol.
- *		
- * Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html
- *
- * 		Briefly, a frame is composed of 
- *
- *			* FEND (0xC0)
- *			* Contents - with special escape sequences so a 0xc0
- *				byte in the data is not taken as end of frame.
- *				as part of the data.
- *			* FEND
- *
- *		The first byte of the frame contains:
- *	
- *			* port number in upper nybble.
- *			* command in lower nybble.
- *
- *	
- *		Commands from application recognized:
- *
- *			0	Data Frame	AX.25 frame in raw format.
- *
- *			1	TXDELAY		See explanation in xmit.c.
- *
- *			2	Persistence	"	"
- *
- *			3 	SlotTime	"	"
- *
- *			4	TXtail		"	"
- *						Spec says it is obsolete but Xastir
- *						sends it and we respect it.
- *
- *			5	FullDuplex	Ignored.  Always full duplex.
- *		
- *			6	SetHardware	TNC specific.  Ignored.
- *			
- *			FF	Return		Exit KISS mode.  Ignored.
- *
- *
- *		Messages sent to client application:
- *
- *			0	Data Frame	Received AX.25 frame in raw format.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-
-#include <stdlib.h>
-#include <ctype.h>
-
-#include <assert.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "kiss_frame.h"
-#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_frame_init 
- *
- * Purpose:     Save information about valid channels for later error checking.
- *
- * Inputs:      pa		- Address of structure of type audio_s.
- *
- *-----------------------------------------------------------------*/
-
-static struct audio_s *save_audio_config_p;
-
-void kiss_frame_init (struct audio_s *pa)
-{
-	save_audio_config_p = pa;
-}
-
-
-/*-------------------------------------------------------------------
- *
- * 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_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!
- *
- * 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.
- *		debug	- Activates debug output.
- *		sendfun	- Function to send something to the client application.
- *
- * Outputs:	kf	- Current state is updated.
- *
- * Returns:	none.
- *
- *-----------------------------------------------------------------*/
-
-/*
- * Application might send some commands to put TNC into KISS mode.  
- * For example, APRSIS32 sends something like:
- *
- *	<0x0d>
- *	<0x0d>
- *	XFLOW OFF<0x0d>
- *	FULLDUP OFF<0x0d>
- *	KISS ON<0x0d>
- *	RESTART<0x0d>
- *	<0x03><0x03><0x03>
- *	TC 1<0x0d>
- *	TN 2,0<0x0d><0x0d><0x0d>
- *	XFLOW OFF<0x0d>
- *	FULLDUP OFF<0x0d>
- *	KISS ON<0x0d>
- *	RESTART<0x0d>
- *
- * This keeps repeating over and over and over and over again if
- * it doesn't get any sort of response.
- *
- * Let's try to keep it happy by sending back a command prompt.
- */
-
-
-
-
-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) {
-	      
-	      /* Start of frame.  But first print any collected noise for debugging. */
-
-	      if (kf->noise_len > 0) {
-		if (debug) {
-		  kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len);
-	        }
-		kf->noise_len = 0;
-	      }
-	      
-	      kf->kiss_len = 0;
-	      kf->kiss_msg[kf->kiss_len++] = ch;
-	      kf->state = KS_COLLECTING;
-	      return;
-	    }
-
-	    /* Noise to be rejected. */
-
-	    if (kf->noise_len < MAX_NOISE_LEN) {
-	      kf->noise[kf->noise_len++] = ch;
-	    }
-	    if (ch == '\r') {
-	      if (debug) {
-		kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len);
-	   	kf->noise[kf->noise_len] = '\0';
-	      }
-
-	      /* 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);
-	      }
-	      else {
-	   	  (*sendfun) (0, (unsigned char *)"\r\ncmd:", -1);
-	      }
-	      kf->noise_len = 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;
-	      }
-
-	      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;
-	    }
-
-	    if (kf->kiss_len < MAX_KISS_LEN) {
-	      kf->kiss_msg[kf->kiss_len++] = ch;
-	    }
-	    else {	    
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("KISS message exceeded maximum length.\n");
-	    }	      
-	    return;
-	    break;
-	}
-	
-	return;	/* unreachable but suppress compiler warning. */
-
-} /* end kiss_rec_byte */   
-	      	    
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kiss_process_msg 
- *
- * Purpose:     Process a message from the KISS client.
- *
- * 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.
- *
- *-----------------------------------------------------------------*/
-
-static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
-{
-	int port;
-	int cmd;
-	packet_t pp;
-	alevel_t alevel;
-
-	port = (kiss_msg[0] >> 4) & 0xf;
-	cmd = kiss_msg[0] & 0xf;
-
-	switch (cmd) 
-	{
-	  case 0:				/* Data Frame */
-
-	    /* Special hack - Discard apparently bad data from Linux AX25. */
-
-	    if ((port == 2 || port == 8) && 
-		 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);
-	        dw_printf ("Special case - Drop packets which appear to be in error.\n");
-	      }
-	      return;
-	    }
-	
-	    /* Verify that the port (channel) number is valid. */
-
-	    if (port < 0 || port >= MAX_CHANS || ! save_audio_config_p->achan[port].valid) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Invalid transmit channel %d from KISS client app.\n", port);
-              text_color_set(DW_COLOR_DEBUG);
-	      kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len);
-	      return;
-	    }
-
-	    memset (&alevel, 0xff, sizeof(alevel));
-	    pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
-	    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. */
-
-	      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;
-
-        case 1:				/* TXDELAY */
-
-          text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, 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", 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 (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, 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 (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, 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", kiss_msg[1], port);
-	  break;
-
-        case 6:				/* TNC specific */
-
-          text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol set hardware - ignored.\n");
-	  break;
-
-        case 15:				/* End KISS mode, port should be 15. */
-						/* Ignore it. */
-          text_color_set(DW_COLOR_INFO);
-	  dw_printf ("KISS protocol end KISS mode\n");
-	  break;
-
-        default:			
-          text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("KISS Invalid command %d\n", cmd);
-          kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len);
-	  break;
-	}
-
-} /* end kiss_process_msg */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kiss_debug_print 
- *
- * Purpose:     Print message to/from client for debugging.
- *
- * Inputs:	fromto		- Direction of message.
- *		special		- Comment if not a KISS frame.
- *		pmsg		- Address of the message block.
- *		msg_len		- Length of the message.
- *
- *--------------------------------------------------------------------*/
-
-
-void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len)
-{
-	const char *direction [2] = { "from", "to" };
-	const char *prefix [2] = { "<<<", ">>>" };
-	const char *function[16] = { 
-		"Data frame",	"TXDELAY",	"P",		"SlotTime",
-		"TXtail",	"FullDuplex",	"SetHardware",	"Invalid 7",
-		"Invalid 8", 	"Invalid 9",	"Invalid 10",	"Invalid 11",
-		"Invalid 12", 	"Invalid 13",	"Invalid 14",	"Return" };
-
-
-	text_color_set(DW_COLOR_DEBUG);
-	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[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 (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 */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    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
+//    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/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      kiss_frame.c
+ *
+ * Purpose:   	Common code used by Serial port and network versions of KISS protocol.
+ *		
+ * Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html
+ *
+ * 		Briefly, a frame is composed of 
+ *
+ *			* FEND (0xC0)
+ *			* Contents - with special escape sequences so a 0xc0
+ *				byte in the data is not taken as end of frame.
+ *				as part of the data.
+ *			* FEND
+ *
+ *		The first byte of the frame contains:
+ *	
+ *			* port number in upper nybble.
+ *			* command in lower nybble.
+ *
+ *	
+ *		Commands from application recognized:
+ *
+ *			0	Data Frame	AX.25 frame in raw format.
+ *
+ *			1	TXDELAY		See explanation in xmit.c.
+ *
+ *			2	Persistence	"	"
+ *
+ *			3 	SlotTime	"	"
+ *
+ *			4	TXtail		"	"
+ *						Spec says it is obsolete but Xastir
+ *						sends it and we respect it.
+ *
+ *			5	FullDuplex	Ignored.  Always full duplex.
+ *		
+ *			6	SetHardware	TNC specific.  Ignored.
+ *			
+ *			FF	Return		Exit KISS mode.  Ignored.
+ *
+ *
+ *		Messages sent to client application:
+ *
+ *			0	Data Frame	Received AX.25 frame in raw format.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "kiss_frame.h"
+#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 KISSTEST
+
+#define dw_printf printf
+
+void text_color_set (dw_color_t c)
+{
+	return;
+}
+
+#endif
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_frame_init 
+ *
+ * Purpose:     Save information about valid channels for later error checking.
+ *
+ * Inputs:      pa		- Address of structure of type audio_s.
+ *
+ *-----------------------------------------------------------------*/
+
+static struct audio_s *save_audio_config_p;
+
+void kiss_frame_init (struct audio_s *pa)
+{
+	save_audio_config_p = pa;
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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.
+ *
+ *			  This seems cumbersome and confusing to have this
+ *			  one byte offset when encapsulating an AX.25 frame.
+ *			  Maybe the type/channel byte should be passed in 
+ *			  as a separate argument.
+ *
+ *			  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 */
+
+
+#ifndef WALK96
+
+/*-------------------------------------------------------------------
+ *
+ * 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!
+ *
+ * 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 KISSTEST
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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.
+ *		debug	- Activates debug output.
+ *		sendfun	- Function to send something to the client application.
+ *
+ * Outputs:	kf	- Current state is updated.
+ *
+ * Returns:	none.
+ *
+ *-----------------------------------------------------------------*/
+
+/*
+ * Application might send some commands to put TNC into KISS mode.  
+ * For example, APRSIS32 sends something like:
+ *
+ *	<0x0d>
+ *	<0x0d>
+ *	XFLOW OFF<0x0d>
+ *	FULLDUP OFF<0x0d>
+ *	KISS ON<0x0d>
+ *	RESTART<0x0d>
+ *	<0x03><0x03><0x03>
+ *	TC 1<0x0d>
+ *	TN 2,0<0x0d><0x0d><0x0d>
+ *	XFLOW OFF<0x0d>
+ *	FULLDUP OFF<0x0d>
+ *	KISS ON<0x0d>
+ *	RESTART<0x0d>
+ *
+ * This keeps repeating over and over and over and over again if
+ * it doesn't get any sort of response.
+ *
+ * Let's try to keep it happy by sending back a command prompt.
+ */
+
+
+
+
+void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int)) 
+{
+
+	//dw_printf ("kiss_frame ( %c %02x ) \n", ch, ch);
+	
+	switch (kf->state) {
+	 
+  	  case KS_SEARCHING:		/* Searching for starting FEND. */
+	  default:
+
+	    if (ch == FEND) {
+	      
+	      /* Start of frame.  But first print any collected noise for debugging. */
+
+	      if (kf->noise_len > 0) {
+		if (debug) {
+		  kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len);
+	        }
+		kf->noise_len = 0;
+	      }
+	      
+	      kf->kiss_len = 0;
+	      kf->kiss_msg[kf->kiss_len++] = ch;
+	      kf->state = KS_COLLECTING;
+	      return;
+	    }
+
+	    /* Noise to be rejected. */
+
+	    if (kf->noise_len < MAX_NOISE_LEN) {
+	      kf->noise[kf->noise_len++] = ch;
+	    }
+	    if (ch == '\r') {
+	      if (debug) {
+		kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len);
+	   	kf->noise[kf->noise_len] = '\0';
+	      }
+
+	      /* 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);
+	      }
+	      else {
+	   	  (*sendfun) (0, (unsigned char *)"\r\ncmd:", -1);
+	      }
+	      kf->noise_len = 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;
+	      }
+
+	      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;
+	    }
+
+	    if (kf->kiss_len < MAX_KISS_LEN) {
+	      kf->kiss_msg[kf->kiss_len++] = ch;
+	    }
+	    else {	    
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("KISS message exceeded maximum length.\n");
+	    }	      
+	    return;
+	    break;
+	}
+	
+	return;	/* unreachable but suppress compiler warning. */
+
+} /* end kiss_rec_byte */   
+	      	    
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_process_msg 
+ *
+ * Purpose:     Process a message from the KISS client.
+ *
+ * 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.
+ *
+ *-----------------------------------------------------------------*/
+
+static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
+{
+	int port;
+	int cmd;
+	packet_t pp;
+	alevel_t alevel;
+
+	port = (kiss_msg[0] >> 4) & 0xf;
+	cmd = kiss_msg[0] & 0xf;
+
+	switch (cmd) 
+	{
+	  case 0:				/* Data Frame */
+
+	    /* Special hack - Discard apparently bad data from Linux AX25. */
+
+	    if ((port == 2 || port == 8) && 
+		 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);
+	        dw_printf ("Special case - Drop packets which appear to be in error.\n");
+	      }
+	      return;
+	    }
+	
+	    /* Verify that the port (channel) number is valid. */
+
+	    if (port < 0 || port >= MAX_CHANS || ! save_audio_config_p->achan[port].valid) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Invalid transmit channel %d from KISS client app.\n", port);
+              text_color_set(DW_COLOR_DEBUG);
+	      kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len);
+	      return;
+	    }
+
+	    memset (&alevel, 0xff, sizeof(alevel));
+	    pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
+	    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. */
+
+	      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;
+
+        case 1:				/* TXDELAY */
+
+          text_color_set(DW_COLOR_INFO);
+	  dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, 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", 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 (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, 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 (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, 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", kiss_msg[1], port);
+	  break;
+
+        case 6:				/* TNC specific */
+
+          text_color_set(DW_COLOR_INFO);
+	  dw_printf ("KISS protocol set hardware - ignored.\n");
+	  break;
+
+        case 15:				/* End KISS mode, port should be 15. */
+						/* Ignore it. */
+          text_color_set(DW_COLOR_INFO);
+	  dw_printf ("KISS protocol end KISS mode\n");
+	  break;
+
+        default:			
+          text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("KISS Invalid command %d\n", cmd);
+          kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len);
+	  break;
+	}
+
+} /* end kiss_process_msg */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kiss_debug_print 
+ *
+ * Purpose:     Print message to/from client for debugging.
+ *
+ * Inputs:	fromto		- Direction of message.
+ *		special		- Comment if not a KISS frame.
+ *		pmsg		- Address of the message block.
+ *		msg_len		- Length of the message.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len)
+{
+	const char *direction [2] = { "from", "to" };
+	const char *prefix [2] = { "<<<", ">>>" };
+	const char *function[16] = { 
+		"Data frame",	"TXDELAY",	"P",		"SlotTime",
+		"TXtail",	"FullDuplex",	"SetHardware",	"Invalid 7",
+		"Invalid 8", 	"Invalid 9",	"Invalid 10",	"Invalid 11",
+		"Invalid 12", 	"Invalid 13",	"Invalid 14",	"Return" };
+
+
+	text_color_set(DW_COLOR_DEBUG);
+	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[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 (pmsg, msg_len);
+
+} /* end kiss_debug_print */
+
+
+#endif
+
+
+/* Quick unit test for encapsulate & unwrap */
+
+// $ gcc -DKISSTEST kiss_frame.c ; ./a
+// Quick KISS test passed OK.
+
+
+#if KISSTEST
+
+
+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);
+
+	dw_printf ("Quick KISS test passed OK.\n");
+	exit (EXIT_SUCCESS);
+}
+
+#endif
+
+#endif /* WALK96 */
+
+/* end kiss_frame.c */
diff --git a/kiss_frame.h b/kiss_frame.h
index ba535c9..ac84b82 100644
--- a/kiss_frame.h
+++ b/kiss_frame.h
@@ -1,54 +1,54 @@
-
-/* kiss_frame.h */
-
-#include "audio.h"		/* for struct audio_s */
-
-
-/*
- * Special characters used by SLIP protocol.
- */
-
-#define FEND 0xC0
-#define FESC 0xDB
-#define TFEND 0xDC
-#define TFESC 0xDD
-
-
-enum kiss_state_e {
-	KS_SEARCHING,		/* Looking for FEND to start KISS 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
-
-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];
-	int noise_len;
-
-} kiss_frame_t;
-
-
-void kiss_frame_init (struct audio_s *pa);
-
-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)); 
- 
-
-typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
-
-void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
-
+
+/* kiss_frame.h */
+
+#include "audio.h"		/* for struct audio_s */
+
+
+/*
+ * Special characters used by SLIP protocol.
+ */
+
+#define FEND 0xC0
+#define FESC 0xDB
+#define TFEND 0xDC
+#define TFESC 0xDD
+
+
+enum kiss_state_e {
+	KS_SEARCHING,		/* Looking for FEND to start KISS 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
+
+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];
+	int noise_len;
+
+} kiss_frame_t;
+
+
+void kiss_frame_init (struct audio_s *pa);
+
+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)); 
+ 
+
+typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
+
+void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
+
 /* end kiss_frame.h */
\ No newline at end of file
diff --git a/kissnet.c b/kissnet.c
index 651941e..e377657 100644
--- a/kissnet.c
+++ b/kissnet.c
@@ -1,683 +1,692 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011-2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      kissnet.c
- *
- * Purpose:   	Provide service to other applications via KISS protocol via TCP socket.
- *		
- * Input:	
- *
- * Outputs:	  
- *
- * Description:	This provides a TCP socket for communication with a client application.
- *
- *		It implements the KISS TNS protocol as described in:
- *		http://www.ka9q.net/papers/kiss.html
- *
- * 		Briefly, a frame is composed of 
- *
- *			* FEND (0xC0)
- *			* Contents - with special escape sequences so a 0xc0
- *				byte in the data is not taken as end of frame.
- *				as part of the data.
- *			* FEND
- *
- *		The first byte of the frame contains:
- *	
- *			* port number in upper nybble.
- *			* command in lower nybble.
- *
- *	
- *		Commands from application recognized:
- *
- *			0	Data Frame	AX.25 frame in raw format.
- *
- *			1	TXDELAY		See explanation in xmit.c.
- *
- *			2	Persistence	"	"
- *
- *			3 	SlotTime	"	"
- *
- *			4	TXtail		"	"
- *						Spec says it is obsolete but Xastir
- *						sends it and we respect it.
- *
- *			5	FullDuplex	Ignored.  Always full duplex.
- *		
- *			6	SetHardware	TNC specific.  Ignored.
- *			
- *			FF	Return		Exit KISS mode.  Ignored.
- *
- *
- *		Messages sent to client application:
- *
- *			0	Data Frame	Received AX.25 frame in raw format.
- *
- *
- *		
- *
- * References:	Getting Started with Winsock
- *		http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
- *
- * Future:	Originally we had:
- *			KISS over serial port.
- *			AGW over socket.
- *		This is the two of them munged together and we end up with duplicate code.
- *		It would have been better to separate out the transport and application layers.
- *		Maybe someday.
- *
- *---------------------------------------------------------------*/
-
-
-/*
- * Native Windows:	Use the Winsock interface.
- * Linux:		Use the BSD socket interface.
- * Cygwin:		Can use either one.
- */
-
-
-#if __WIN32__
-#include <winsock2.h>
-#define _WIN32_WINNT 0x0501
-#include <ws2tcpip.h>
-#else 
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#ifdef __OpenBSD__
-#include <errno.h>
-#else
-#include <sys/errno.h>
-#endif
-#endif
-
-#include <unistd.h>
-#include <stdio.h>
-#include <assert.h>
-
-#include <string.h>
-
-
-#include "direwolf.h"
-#include "tq.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "kissnet.h"
-#include "kiss_frame.h"
-#include "xmit.h"
-
-
-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 */
-				/* communication with client application. */
-				/* Set to -1 if not connected. */
-				/* (Don't use SOCKET type because it is unsigned.) */
-
-
-static void * connect_listen_thread (void *arg);
-static void * kissnet_listen_thread (void *arg);
-
-
-
-static int kiss_debug = 0;		/* Print information flowing from and to client. */
-
-void kiss_net_set_debug (int n) 
-{	
-	kiss_debug = n;
-}
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kissnet_init
- *
- * Purpose:     Set up a server to listen for connection requests from
- *		an application such as Xastir or APRSIS32.
- *
- * Inputs:	mc->kiss_port	- TCP port for server.
- *				  Main program has default of 8000 but allows
- *				  an alternative to be specified on the command line
- *
- *				0 means disable.  New in version 1.2.
- *
- * Outputs:	
- *
- * Description:	This starts two threads:
- *		  *  to listen for a connection from client app.
- *		  *  to listen for commands from client app.
- *		so the main application doesn't block while we wait for these.
- *
- *--------------------------------------------------------------------*/
-
-
-void kissnet_init (struct misc_config_s *mc)
-{
-#if __WIN32__
-	HANDLE connect_listen_th;
-	HANDLE cmd_listen_th;
-#else
-	pthread_t connect_listen_tid;
-	pthread_t cmd_listen_tid;
-#endif
-	int e;
-	int kiss_port = mc->kiss_port;
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kissnet_init ( %d )\n", kiss_port);
-#endif
-
-	memset (&kf, 0, sizeof(kf));
-	
-	client_sock = -1;
-
-	if (kiss_port == 0) {
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Disabled KISS network client port.\n");
-	  return;
-	}
-	
-/*
- * This waits for a client to connect and sets client_sock.
- */
-#if __WIN32__
-	connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL);
-	if (connect_listen_th == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not create KISS socket connect listening thread\n");
-	  return;
-	}
-#else
-	e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)kiss_port);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Could not create KISS socket connect listening thread");
-	  return;
-	}
-#endif
-
-/*
- * This reads messages from client when client_sock is valid.
- */
-#if __WIN32__
-	cmd_listen_th = _beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL);
-	if (cmd_listen_th == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not create KISS socket command listening thread\n");
-	  return;
-	}
-#else
-	e = pthread_create (&cmd_listen_tid, NULL, kissnet_listen_thread, NULL);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Could not create KISS socket command listening thread");
-	  return;
-	}
-#endif
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        connect_listen_thread
- *
- * Purpose:     Wait for a connection request from an application.
- *
- * Inputs:	arg		- TCP port for server.
- *				  Main program has default of 8001 but allows
- *				  an alternative to be specified on the command line
- *
- * Outputs:	client_sock	- File descriptor for communicating with client app.
- *
- * Description:	Wait for connection request from client and establish
- *		communication.
- *		Note that the client can go away and come back again and
- *		re-establish communication without restarting this application.
- *
- *--------------------------------------------------------------------*/
-
-static void * connect_listen_thread (void *arg)
-{
-#if __WIN32__
-
-	struct addrinfo hints;
-	struct addrinfo *ai = NULL;
-	int err;
-	char kiss_port_str[12];
-
-	SOCKET listen_sock;  
-	WSADATA wsadata;
-
-	sprintf (kiss_port_str, "%d", (int)(long)arg);
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-        dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(long)arg, kiss_port_str);
-#endif
-	err = WSAStartup (MAKEWORD(2,2), &wsadata);
-	if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("WSAStartup failed: %d\n", err);
-	    return (NULL);
-	}
-
-	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Could not find a usable version of Winsock.dll\n");
-          WSACleanup();
-	  //sleep (1);
-          return (NULL);
-	}
-
-	memset (&hints, 0, sizeof(hints));
-	hints.ai_family = AF_INET;
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_protocol = IPPROTO_TCP;
-	hints.ai_flags = AI_PASSIVE;
-
-	err = getaddrinfo(NULL, kiss_port_str, &hints, &ai);
-	if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("getaddrinfo failed: %d\n", err);
-	    //sleep (1);
-	    WSACleanup();
-	    return (NULL);
-	}
-
-	listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-	if (listen_sock == INVALID_SOCKET) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
-	  return (NULL);
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-    	dw_printf("Binding to port %s ... \n", kiss_port_str);
-#endif
-
-	err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
-	if (err == SOCKET_ERROR) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Bind failed with error: %d\n", WSAGetLastError());		// TODO: provide corresponding text.
-	  dw_printf("Some other application is probably already using port %s.\n", kiss_port_str);
-	  dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
-          freeaddrinfo(ai);
-          closesocket(listen_sock);
-          WSACleanup();
-          return (NULL);
-        }
-
-	freeaddrinfo(ai);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
- 	dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, kiss_port_str );
-#endif
-
- 	while (1) {
-  	 
-	  while (client_sock > 0) {
-	    SLEEP_SEC(1);			/* Already connected.  Try again later. */
-	  }
-
-#define QUEUE_SIZE 5
-
-	  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);
-	  }
-	
-	  text_color_set(DW_COLOR_INFO);
-          dw_printf("Ready to accept KISS client application on port %s ...\n", kiss_port_str);
-         
-          client_sock = 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);
-          }
-
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf("\nConnected to KISS client application ...\n\n");
-
- 	}
-
-
-#else		/* End of Windows case, now Linux. */
-
-
-    	struct sockaddr_in sockaddr; /* Internet socket address stuct */
-    	socklen_t sockaddr_size = sizeof(struct sockaddr_in);
-	int kiss_port = (int)(long)arg;
-	int listen_sock;  
-
-	listen_sock= socket(AF_INET,SOCK_STREAM,0);
-	if (listen_sock == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror ("connect_listen_thread: Socket creation failed");
-	  return (NULL);
-	}
-
-    	sockaddr.sin_addr.s_addr = INADDR_ANY;
-    	sockaddr.sin_port = htons(kiss_port);
-    	sockaddr.sin_family = AF_INET;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-    	dw_printf("Binding to port %d ... \n", kiss_port);
-#endif
-
-        if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))  == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Bind failed with error: %d\n", errno);	
-          dw_printf("%s\n", strerror(errno));
-	  dw_printf("Some other application is probably already using port %d.\n", kiss_port);
-	  dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
-          return (NULL);
-	}
-
-	getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
- 	dw_printf("opened KISS socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
-#endif
-
- 	while (1) {
-  	 
-	  while (client_sock > 0) {
-	    SLEEP_SEC(1);			/* Already connected.  Try again later. */
-	  }
-
-#define QUEUE_SIZE 5
-
-	  if(listen(listen_sock,QUEUE_SIZE) == -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 KISS client application on port %d ...\n", kiss_port);
-         
-          client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
-
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf("\nConnected to KISS client application ...\n\n");
-
- 	}
-#endif
-}
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kissnet_send_rec_packet
- *
- * Purpose:     Send a received packet to the client app.
- *
- * Inputs:	chan		- Channel number where packet was received.
- *				  0 = first, 1 = second if any.
- *
- *		fbuf		- Address of raw received frame buffer
- *				  or a text string.
- *
- *		flen		- Number of bytes for AX.25 frame.
- *				  or -1 for a text string.
- *		
- *
- * Description:	Send message to client if connected.
- *		Disconnect from client, and notify user, if any error.
- *
- *--------------------------------------------------------------------*/
-
-
-void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
-{
-	unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
-	int kiss_len;
-	int j;
-	int err;
-
-
-	if (client_sock == -1) {
-	  return;
-	}
-	if (flen < 0) {
-	  flen = strlen((char*)fbuf);
-	  if (kiss_debug) {
-	    kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
-	  }
-	  strcpy ((char *)kiss_buff, (char *)fbuf);
-	  kiss_len = strlen((char *)kiss_buff);
-	}
-	else {
-
-
-	  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_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, kiss_len);
-	  }
-	}
-
-#if __WIN32__	
-        err = send (client_sock, (char*)kiss_buff, kiss_len, 0);
-	if (err == SOCKET_ERROR)
-	{
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError %d sending message to KISS client application.  Closing connection.\n\n", WSAGetLastError());
-	  closesocket (client_sock);
-	  client_sock = -1;
-	  WSACleanup();
-	}
-#else
-        err = write (client_sock, kiss_buff, kiss_len);
-	if (err <= 0)
-	{
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError sending message to KISS client application.  Closing connection.\n\n");
-	  close (client_sock);
-	  client_sock = -1;    
-	}
-#endif
-	
-} /* end kissnet_send_rec_packet */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        read_from_socket
- *
- * Purpose:     Read from socket until we have desired number of bytes.
- *
- * Inputs:	fd		- file descriptor.
- *		ptr		- address where data should be placed.
- *		len		- desired number of bytes.
- *
- * Description:	Just a wrapper for the "read" system call but it should
- *		never return fewer than the desired number of bytes.
- *
- * 		Not really needed for KISS because we are dealing with
- *		a stream of bytes rather than message blocks.
- *
- *--------------------------------------------------------------------*/
-
-static int read_from_socket (int fd, char *ptr, int len)
-{
-	int got_bytes = 0;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
-#endif
-	while (got_bytes < len) {
-	  int n;
-
-#if __WIN32__
-
-//TODO: any flags for send/recv?
-
-	  n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
-#else
-	  n = read (fd, ptr + got_bytes, len - got_bytes);
-#endif
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("read_from_socket: n = %d\n", n);
-#endif
-	  if (n <= 0) {
-	    return (n);
-	  }
-
-	  got_bytes += n;
-	}
-	assert (got_bytes >= 0 && got_bytes <= len);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("read_from_socket: return %d\n", got_bytes);
-#endif
-	return (got_bytes);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        kissnet_listen_thread
- *
- * Purpose:     Wait for KISS messages from an application.
- *
- * Inputs:	arg		- Not used.
- *
- * Outputs:	client_sock	- 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
- *		re-establish communication without restarting this application.
- *
- *--------------------------------------------------------------------*/
-
-
-/* Return one byte (value 0 - 255) */
-
-
-static int kiss_get (void)
-{
-	unsigned char ch;
-	int n;
-
-	while (1) {
-
-	  while (client_sock <= 0) {
-	    SLEEP_SEC(1);			/* Not connected.  Try again later. */
-	  }
-
-	  /* Just get one byte at a time. */
-
-	  n = read_from_socket (client_sock, (char *)(&ch), 1);
-
-	  if (n == 1) {
-#if DEBUG9
-	    dw_printf (log_fp, "%02x %c %c", ch, 
-			isprint(ch) ? ch : '.' , 
-			(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
-	    if (ch == FEND) fprintf (log_fp, "  FEND");
-	    if (ch == FESC) fprintf (log_fp, "  FESC");
-	    if (ch == TFEND) fprintf (log_fp, "  TFEND");
-	    if (ch == TFESC) fprintf (log_fp, "  TFESC");
-	    if (ch == '\r') fprintf (log_fp, "  CR");
-	    if (ch == '\n') fprintf (log_fp, "  LF");
-	    fprintf (log_fp, "\n");
-	    if (ch == FEND) fflush (log_fp);
-#endif
-	    return(ch);	
-	  }
-
-          text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("\nError reading KISS byte from clent application.  Closing connection.\n\n");
-#if __WIN32__
-	  closesocket (client_sock);
-#else
-	  close (client_sock);
-#endif
-	  client_sock = -1;
-	}
-}
-
-
-
-static void * kissnet_listen_thread (void *arg)
-{
-	unsigned char ch;
-			
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("kissnet_listen_thread ( socket = %d )\n", client_sock);
-#endif
-
-	while (1) {
-	  ch = kiss_get();
-	  kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet);
-	}  
-
-	return (NULL);	/* to suppress compiler warning. */
-
-} /* end kissnet_listen_thread */
-
-/* end kissnet.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011-2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      kissnet.c
+ *
+ * Purpose:   	Provide service to other applications via KISS protocol via TCP socket.
+ *		
+ * Input:	
+ *
+ * Outputs:	  
+ *
+ * Description:	This provides a TCP socket for communication with a client application.
+ *
+ *		It implements the KISS TNS protocol as described in:
+ *		http://www.ka9q.net/papers/kiss.html
+ *
+ * 		Briefly, a frame is composed of 
+ *
+ *			* FEND (0xC0)
+ *			* Contents - with special escape sequences so a 0xc0
+ *				byte in the data is not taken as end of frame.
+ *				as part of the data.
+ *			* FEND
+ *
+ *		The first byte of the frame contains:
+ *	
+ *			* port number in upper nybble.
+ *			* command in lower nybble.
+ *
+ *	
+ *		Commands from application recognized:
+ *
+ *			0	Data Frame	AX.25 frame in raw format.
+ *
+ *			1	TXDELAY		See explanation in xmit.c.
+ *
+ *			2	Persistence	"	"
+ *
+ *			3 	SlotTime	"	"
+ *
+ *			4	TXtail		"	"
+ *						Spec says it is obsolete but Xastir
+ *						sends it and we respect it.
+ *
+ *			5	FullDuplex	Ignored.  Always full duplex.
+ *		
+ *			6	SetHardware	TNC specific.  Ignored.
+ *			
+ *			FF	Return		Exit KISS mode.  Ignored.
+ *
+ *
+ *		Messages sent to client application:
+ *
+ *			0	Data Frame	Received AX.25 frame in raw format.
+ *
+ *
+ *		
+ *
+ * References:	Getting Started with Winsock
+ *		http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
+ *
+ * Future:	Originally we had:
+ *			KISS over serial port.
+ *			AGW over socket.
+ *		This is the two of them munged together and we end up with duplicate code.
+ *		It would have been better to separate out the transport and application layers.
+ *		Maybe someday.
+ *
+ *---------------------------------------------------------------*/
+
+
+/*
+ * Native Windows:	Use the Winsock interface.
+ * Linux:		Use the BSD socket interface.
+ * Cygwin:		Can use either one.
+ */
+
+
+#if __WIN32__
+#include <winsock2.h>
+#define _WIN32_WINNT 0x0501
+#include <ws2tcpip.h>
+#else 
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#ifdef __OpenBSD__
+#include <errno.h>
+#else
+#include <sys/errno.h>
+#endif
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <string.h>
+
+
+#include "direwolf.h"
+#include "tq.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "kissnet.h"
+#include "kiss_frame.h"
+#include "xmit.h"
+
+
+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 */
+				/* communication with client application. */
+				/* Set to -1 if not connected. */
+				/* (Don't use SOCKET type because it is unsigned.) */
+
+
+static void * connect_listen_thread (void *arg);
+static void * kissnet_listen_thread (void *arg);
+
+
+
+static int kiss_debug = 0;		/* Print information flowing from and to client. */
+
+void kiss_net_set_debug (int n) 
+{	
+	kiss_debug = n;
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kissnet_init
+ *
+ * Purpose:     Set up a server to listen for connection requests from
+ *		an application such as Xastir or APRSIS32.
+ *
+ * Inputs:	mc->kiss_port	- TCP port for server.
+ *				  Main program has default of 8000 but allows
+ *				  an alternative to be specified on the command line
+ *
+ *				0 means disable.  New in version 1.2.
+ *
+ * Outputs:	
+ *
+ * Description:	This starts two threads:
+ *		  *  to listen for a connection from client app.
+ *		  *  to listen for commands from client app.
+ *		so the main application doesn't block while we wait for these.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void kissnet_init (struct misc_config_s *mc)
+{
+#if __WIN32__
+	HANDLE connect_listen_th;
+	HANDLE cmd_listen_th;
+#else
+	pthread_t connect_listen_tid;
+	pthread_t cmd_listen_tid;
+#endif
+	int e;
+	int kiss_port = mc->kiss_port;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kissnet_init ( %d )\n", kiss_port);
+#endif
+
+	memset (&kf, 0, sizeof(kf));
+	
+	client_sock = -1;
+
+	if (kiss_port == 0) {
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Disabled KISS network client port.\n");
+	  return;
+	}
+	
+/*
+ * This waits for a client to connect and sets client_sock.
+ */
+#if __WIN32__
+	connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL);
+	if (connect_listen_th == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not create KISS socket connect listening thread\n");
+	  return;
+	}
+#else
+	e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)kiss_port);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Could not create KISS socket connect listening thread");
+	  return;
+	}
+#endif
+
+/*
+ * This reads messages from client when client_sock is valid.
+ */
+#if __WIN32__
+	cmd_listen_th = _beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL);
+	if (cmd_listen_th == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not create KISS socket command listening thread\n");
+	  return;
+	}
+#else
+	e = pthread_create (&cmd_listen_tid, NULL, kissnet_listen_thread, NULL);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Could not create KISS socket command listening thread");
+	  return;
+	}
+#endif
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        connect_listen_thread
+ *
+ * Purpose:     Wait for a connection request from an application.
+ *
+ * Inputs:	arg		- TCP port for server.
+ *				  Main program has default of 8001 but allows
+ *				  an alternative to be specified on the command line
+ *
+ * Outputs:	client_sock	- File descriptor for communicating with client app.
+ *
+ * Description:	Wait for connection request from client and establish
+ *		communication.
+ *		Note that the client can go away and come back again and
+ *		re-establish communication without restarting this application.
+ *
+ *--------------------------------------------------------------------*/
+
+static void * connect_listen_thread (void *arg)
+{
+#if __WIN32__
+
+	struct addrinfo hints;
+	struct addrinfo *ai = NULL;
+	int err;
+	char kiss_port_str[12];
+
+	SOCKET listen_sock;  
+	WSADATA wsadata;
+
+	snprintf (kiss_port_str, sizeof(kiss_port_str), "%d", (int)(long)arg);
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+        dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(long)arg, kiss_port_str);
+#endif
+	err = WSAStartup (MAKEWORD(2,2), &wsadata);
+	if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("WSAStartup failed: %d\n", err);
+	    return (NULL);
+	}
+
+	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Could not find a usable version of Winsock.dll\n");
+          WSACleanup();
+	  //sleep (1);
+          return (NULL);
+	}
+
+	memset (&hints, 0, sizeof(hints));
+	hints.ai_family = AF_INET;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+	hints.ai_flags = AI_PASSIVE;
+
+	err = getaddrinfo(NULL, kiss_port_str, &hints, &ai);
+	if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("getaddrinfo failed: %d\n", err);
+	    //sleep (1);
+	    WSACleanup();
+	    return (NULL);
+	}
+
+	listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+	if (listen_sock == INVALID_SOCKET) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
+	  return (NULL);
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+    	dw_printf("Binding to port %s ... \n", kiss_port_str);
+#endif
+
+	err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
+	if (err == SOCKET_ERROR) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Bind failed with error: %d\n", WSAGetLastError());		// TODO: provide corresponding text.
+	  dw_printf("Some other application is probably already using port %s.\n", kiss_port_str);
+	  dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
+          freeaddrinfo(ai);
+          closesocket(listen_sock);
+          WSACleanup();
+          return (NULL);
+        }
+
+	freeaddrinfo(ai);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+ 	dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, kiss_port_str );
+#endif
+
+ 	while (1) {
+  	 
+	  while (client_sock > 0) {
+	    SLEEP_SEC(1);			/* Already connected.  Try again later. */
+	  }
+
+#define QUEUE_SIZE 5
+
+	  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);
+	  }
+	
+	  text_color_set(DW_COLOR_INFO);
+          dw_printf("Ready to accept KISS client application on port %s ...\n", kiss_port_str);
+         
+          client_sock = 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);
+          }
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf("\nConnected to KISS client application ...\n\n");
+
+ 	}
+
+
+#else		/* End of Windows case, now Linux. */
+
+
+    	struct sockaddr_in sockaddr; /* Internet socket address stuct */
+    	socklen_t sockaddr_size = sizeof(struct sockaddr_in);
+	int kiss_port = (int)(long)arg;
+	int listen_sock;  
+	int bcopt = 1;
+
+	listen_sock= socket(AF_INET,SOCK_STREAM,0);
+	if (listen_sock == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror ("connect_listen_thread: Socket creation failed");
+	  return (NULL);
+	}
+
+	/* Version 1.3 - as suggested by G8BPQ. */
+	/* Without this, if you kill the application then try to run it */
+	/* again quickly the port number is unavailable for a while. */
+	/* Don't try doing the same thing On Windows; It has a different meaning. */
+	/* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */
+
+        setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4);
+
+    	sockaddr.sin_addr.s_addr = INADDR_ANY;
+    	sockaddr.sin_port = htons(kiss_port);
+    	sockaddr.sin_family = AF_INET;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+    	dw_printf("Binding to port %d ... \n", kiss_port);
+#endif
+
+        if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))  == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Bind failed with error: %d\n", errno);	
+          dw_printf("%s\n", strerror(errno));
+	  dw_printf("Some other application is probably already using port %d.\n", kiss_port);
+	  dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
+          return (NULL);
+	}
+
+	getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+ 	dw_printf("opened KISS socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
+#endif
+
+ 	while (1) {
+  	 
+	  while (client_sock > 0) {
+	    SLEEP_SEC(1);			/* Already connected.  Try again later. */
+	  }
+
+#define QUEUE_SIZE 5
+
+	  if(listen(listen_sock,QUEUE_SIZE) == -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 KISS client application on port %d ...\n", kiss_port);
+         
+          client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
+
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf("\nConnected to KISS client application ...\n\n");
+
+ 	}
+#endif
+}
+
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kissnet_send_rec_packet
+ *
+ * Purpose:     Send a received packet to the client app.
+ *
+ * Inputs:	chan		- Channel number where packet was received.
+ *				  0 = first, 1 = second if any.
+ *
+ *		fbuf		- Address of raw received frame buffer
+ *				  or a text string.
+ *
+ *		flen		- Number of bytes for AX.25 frame.
+ *				  or -1 for a text string.
+ *		
+ *
+ * Description:	Send message to client if connected.
+ *		Disconnect from client, and notify user, if any error.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
+{
+	unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
+	int kiss_len;
+	int j;
+	int err;
+
+
+	if (client_sock == -1) {
+	  return;
+	}
+	if (flen < 0) {
+	  flen = strlen((char*)fbuf);
+	  if (kiss_debug) {
+	    kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
+	  }
+	  strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
+	  kiss_len = strlen((char *)kiss_buff);
+	}
+	else {
+
+
+	  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 (fbuf, flen);
+	  }
+
+	  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, kiss_len);
+	  }
+	}
+
+#if __WIN32__	
+        err = send (client_sock, (char*)kiss_buff, kiss_len, 0);
+	if (err == SOCKET_ERROR)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError %d sending message to KISS client application.  Closing connection.\n\n", WSAGetLastError());
+	  closesocket (client_sock);
+	  client_sock = -1;
+	  WSACleanup();
+	}
+#else
+        err = write (client_sock, kiss_buff, kiss_len);
+	if (err <= 0)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError sending message to KISS client application.  Closing connection.\n\n");
+	  close (client_sock);
+	  client_sock = -1;    
+	}
+#endif
+	
+} /* end kissnet_send_rec_packet */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        read_from_socket
+ *
+ * Purpose:     Read from socket until we have desired number of bytes.
+ *
+ * Inputs:	fd		- file descriptor.
+ *		ptr		- address where data should be placed.
+ *		len		- desired number of bytes.
+ *
+ * Description:	Just a wrapper for the "read" system call but it should
+ *		never return fewer than the desired number of bytes.
+ *
+ * 		Not really needed for KISS because we are dealing with
+ *		a stream of bytes rather than message blocks.
+ *
+ *--------------------------------------------------------------------*/
+
+static int read_from_socket (int fd, char *ptr, int len)
+{
+	int got_bytes = 0;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
+#endif
+	while (got_bytes < len) {
+	  int n;
+
+#if __WIN32__
+
+//TODO: any flags for send/recv?
+
+	  n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
+#else
+	  n = read (fd, ptr + got_bytes, len - got_bytes);
+#endif
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("read_from_socket: n = %d\n", n);
+#endif
+	  if (n <= 0) {
+	    return (n);
+	  }
+
+	  got_bytes += n;
+	}
+	assert (got_bytes >= 0 && got_bytes <= len);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("read_from_socket: return %d\n", got_bytes);
+#endif
+	return (got_bytes);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        kissnet_listen_thread
+ *
+ * Purpose:     Wait for KISS messages from an application.
+ *
+ * Inputs:	arg		- Not used.
+ *
+ * Outputs:	client_sock	- 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
+ *		re-establish communication without restarting this application.
+ *
+ *--------------------------------------------------------------------*/
+
+
+/* Return one byte (value 0 - 255) */
+
+
+static int kiss_get (void)
+{
+	unsigned char ch;
+	int n;
+
+	while (1) {
+
+	  while (client_sock <= 0) {
+	    SLEEP_SEC(1);			/* Not connected.  Try again later. */
+	  }
+
+	  /* Just get one byte at a time. */
+
+	  n = read_from_socket (client_sock, (char *)(&ch), 1);
+
+	  if (n == 1) {
+#if DEBUG9
+	    dw_printf (log_fp, "%02x %c %c", ch, 
+			isprint(ch) ? ch : '.' , 
+			(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
+	    if (ch == FEND) fprintf (log_fp, "  FEND");
+	    if (ch == FESC) fprintf (log_fp, "  FESC");
+	    if (ch == TFEND) fprintf (log_fp, "  TFEND");
+	    if (ch == TFESC) fprintf (log_fp, "  TFESC");
+	    if (ch == '\r') fprintf (log_fp, "  CR");
+	    if (ch == '\n') fprintf (log_fp, "  LF");
+	    fprintf (log_fp, "\n");
+	    if (ch == FEND) fflush (log_fp);
+#endif
+	    return(ch);	
+	  }
+
+          text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\nError reading KISS byte from clent application.  Closing connection.\n\n");
+#if __WIN32__
+	  closesocket (client_sock);
+#else
+	  close (client_sock);
+#endif
+	  client_sock = -1;
+	}
+}
+
+
+
+static void * kissnet_listen_thread (void *arg)
+{
+	unsigned char ch;
+			
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("kissnet_listen_thread ( socket = %d )\n", client_sock);
+#endif
+
+	while (1) {
+	  ch = kiss_get();
+	  kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet);
+	}  
+
+	return (NULL);	/* to suppress compiler warning. */
+
+} /* end kissnet_listen_thread */
+
+/* end kissnet.c */
diff --git a/kissnet.h b/kissnet.h
index 2b4f9b9..361f435 100644
--- a/kissnet.h
+++ b/kissnet.h
@@ -1,21 +1,21 @@
-
-/* 
- * Name:	kissnet.h
- */
-
-
-#include "ax25_pad.h"		/* for packet_t */
-
-#include "config.h"
-
-
-
-
-void kissnet_init (struct misc_config_s *misc_config);
-
-void kissnet_send_rec_packet (int chan, unsigned char *fbuf,  int flen);
-
-void kiss_net_set_debug (int n);
-
-
-/* end kissnet.h */
+
+/* 
+ * Name:	kissnet.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+
+
+
+void kissnet_init (struct misc_config_s *misc_config);
+
+void kissnet_send_rec_packet (int chan, unsigned char *fbuf,  int flen);
+
+void kiss_net_set_debug (int n);
+
+
+/* end kissnet.h */
diff --git a/latlong.c b/latlong.c
index 238c3f2..c984ef1 100644
--- a/latlong.c
+++ b/latlong.c
@@ -1,684 +1,901 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      latlong.c
- *
- * Purpose:   	Various functions for dealing with latitude and longitude.
- *		
- * Description: Originally, these were scattered around in many places.
- *		Over time they might all be gathered into one place
- *		for consistency, reuse, and easier maintenance.
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <time.h>
-#include <math.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "latlong.h"
-#include "textcolor.h"
-
-
-/*------------------------------------------------------------------
- *
- * Name:        latitude_to_str
- *
- * Purpose:     Convert numeric latitude to string for transmission.
- *
- * Inputs:      dlat		- Floating point degrees.
- * 		ambiguity	- If 1, 2, 3, or 4, blank out that many trailing digits.
- *
- * Outputs:	slat		- String in format ddmm.mm[NS]
- *
- * Returns:     None
- *
- *----------------------------------------------------------------*/
-
-void latitude_to_str (double dlat, int ambiguity, char *slat)
-{
-	char hemi;	/* Hemisphere: N or S */
-	int ideg;	/* whole number of degrees. */
-	double dmin;	/* Minutes after removing degrees. */
-	char smin[8];	/* Minutes in format mm.mm */
-	
-	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);
-	  hemi = 'S';
-	}
-	else {
-	  hemi = 'N';
-	}
-
-	ideg = (int)dlat;
-	dmin = (dlat - ideg) * 60.;
-
-	sprintf (smin, "%05.2f", dmin);
-	/* Due to roundoff, 59.9999 could come out as "60.00" */
-	if (smin[0] == '6') {
-	  smin[0] = '0';
-	  ideg++;
-	}
-
-	sprintf (slat, "%02d%s%c", ideg, smin, hemi);
-
-	if (ambiguity >= 1) {
-	  slat[6] = ' ';
-	  if (ambiguity >= 2) {
-	    slat[5] = ' ';
-	    if (ambiguity >= 3) {
-	      slat[3] = ' ';
-	      if (ambiguity >= 4) {
-	        slat[2] = ' ';
-	      }
-	    }
-	  }
-	}
-
-} /* end latitude_to_str */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        longitude_to_str
- *
- * Purpose:     Convert numeric longitude to string for transmission.
- *
- * Inputs:      dlong		- Floating point degrees.
- * 		ambiguity	- If 1, 2, 3, or 4, blank out that many trailing digits.
- *
- * Outputs:	slat		- String in format dddmm.mm[NS]
- *
- * Returns:     None
- *
- *----------------------------------------------------------------*/
-
-void longitude_to_str (double dlong, int ambiguity, char *slong)
-{
-	char hemi;	/* Hemisphere: N or S */
-	int ideg;	/* whole number of degrees. */
-	double dmin;	/* Minutes after removing degrees. */
-	char smin[8];	/* Minutes in format mm.mm */
-	
-	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);
-	  hemi = 'W';
-	}
-	else {
-	  hemi = 'E';
-	}
-
-	ideg = (int)dlong;
-	dmin = (dlong - ideg) * 60.;
-
-	sprintf (smin, "%05.2f", dmin);
-	/* Due to roundoff, 59.9999 could come out as "60.00" */
-	if (smin[0] == '6') {
-	  smin[0] = '0';
-	  ideg++;
-	}
-
-	sprintf (slong, "%03d%s%c", ideg, smin, hemi);
-/*
- * The spec says position ambiguity in latitude also
- * applies to longitude automatically.  
- * Blanking longitude digits is not necessary but I do it
- * because it makes things clearer.
- */
-	if (ambiguity >= 1) {
-	  slong[7] = ' ';
-	  if (ambiguity >= 2) {
-	    slong[6] = ' ';
-	    if (ambiguity >= 3) {
-	      slong[4] = ' ';
-	      if (ambiguity >= 4) {
-	        slong[3] = ' ';
-	      }
-	    }
-	  }
-	}
-
-} /* end longitude_to_str */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        latitude_to_comp_str
- *
- * Purpose:     Convert numeric latitude to compressed string for transmission.
- *
- * Inputs:      dlat		- Floating point degrees.
- *
- * Outputs:	slat		- String in format yyyy.
- *
- *----------------------------------------------------------------*/
-
-void latitude_to_comp_str (double dlat, char *clat)
-{
-	int y, y0, y1, y2, y3;
-
-	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.;
-	}
-
-	y = (int)round(380926. * (90. - dlat));
-	
-	y0 = y / (91*91*91);
-	y -= y0 * (91*91*91);
-
-	y1 = y / (91*91);
-	y -= y1 * (91*91);
-
-	y2 = y / (91);
-	y -= y2 * (91);
-
-	y3 = y;
-
-	clat[0] = y0 + 33;
-	clat[1] = y1 + 33;
-	clat[2] = y2 + 33;
-	clat[3] = y3 + 33;
-}
-
-/*------------------------------------------------------------------
- *
- * Name:        longitude_to_comp_str
- *
- * Purpose:     Convert numeric longitude to compressed string for transmission.
- *
- * Inputs:      dlong		- Floating point degrees.
- *
- * Outputs:	slat		- String in format xxxx.
- *
- *----------------------------------------------------------------*/
-
-void longitude_to_comp_str (double dlong, char *clon)
-{
-	int x, x0, x1, x2, x3;
-
-	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.;
-	}
-
-	x = (int)round(190463. * (180. + dlong));
-	
-	x0 = x / (91*91*91);
-	x -= x0 * (91*91*91);
-
-	x1 = x / (91*91);
-	x -= x1 * (91*91);
-
-	x2 = x / (91);
-	x -= x2 * (91);
-
-	x3 = x;
-
-	clon[0] = x0 + 33;
-	clon[1] = x1 + 33;
-	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);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ll_distance_km
- *
- * Purpose:	Calculate distance between two locations.
- *
- * Inputs:	lat1, lon1	- One location, in degrees.
- *		lat2, lon2	- other location
- *
- * Returns:	Distance in km.
- *
- * Description:	The Ubiquitous Haversine formula.
- *
- *------------------------------------------------------------------*/
-
-#define R 6371
-
-double ll_distance_km (double lat1, double lon1, double lat2, double lon2)
-{
-	double a;
-
-	lat1 *= M_PI / 180;
-	lon1 *= M_PI / 180;	
-	lat2 *= M_PI / 180;	
-	lon2 *= M_PI / 180;	
-	
-	a = pow(sin((lat2-lat1)/2),2) + cos(lat1) * cos(lat2) * pow(sin((lon2-lon1)/2),2);
-
-	return (R * 2 *atan2(sqrt(a), sqrt(1-a)));
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	ll_from_grid_square
- *
- * Purpose:	Convert Maidenhead locator to latitude and longitude.
- *
- * Inputs:	maidenhead	- 4 or 6 character grid square.
- *
- * Outputs:	dlat, dlon	- Latitude and longitude.  
- *				  Original values unchanged if error.
- *
- * Returns:	1 for success, 0 if error.
- *
- * Bug: 	This does not check for invalid values.
- *
- * Reference:	A good converter for spot checking:  
- *		http://home.arcor.de/waldemar.kebsch/The_Makrothen_Contest/fmaidenhead.html
- *
- * Rambling:	What sort of resolution does this provide?
- *		For 8 character form, each latitude unit is 0.25 minute.
- *		(Longitude can be up to twice that around the equator.)
- *		6371 km * 2 * pi * 0.25 / 60 / 360 = 0.463 km.  Is that right?
- *
- *		Using this calculator, http://www.earthpoint.us/Convert.aspx
- *
- *		FN42MA00  -->  19T 334361mE 4651711mN
- *		FN42MA11  -->  19T 335062mE 4652157mN
- *				   ------   -------
- *				      701       446    meters difference.
- *
- *------------------------------------------------------------------*/
-
-int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon)
-{
-	double lat, lon;
-	char mh[16];
-
-
-	if (strlen(maidenhead) != 4 &&  strlen(maidenhead) != 6) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("Maidenhead locator \"%s\" must 4 or 6 characters in this context.\n", maidenhead);
-	  return (0);
-	}
-
-	strcpy (mh, maidenhead);
-	if (islower(mh[0])) mh[0] = toupper(mh[0]);
-	if (islower(mh[1])) mh[1] = toupper(mh[1]);
-	
-	if (mh[0] < 'A' || mh[0] > 'R' || mh[1] < 'A' || mh[1] > 'R') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("The first pair of characters in Maidenhead locator \"%s\" must be in range of A thru R.\n", maidenhead);
-	  return (0);
-	}
-
-	if ( ! isdigit(mh[2]) || ! isdigit(mh[3]) ) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf("The second pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead);
-	  return (0);
-	}
-
-	/* Lon:  360 deg / 18 squares = 20 deg / square */
-	/* Lat:  180 deg / 18 squares = 10 deg / square */
-
-	lon = (mh[0] - 'A') * 20 - 180;
-	lat = (mh[1] - 'A') * 10 - 90;
-
-	/* Lon:  20 deg / 10 squares = 2 deg / square */
-	/* Lat:  10 deg / 10 squares = 1 deg / square */
-
-	lon += (mh[2] - '0') * 2;
-	lat += (mh[3] - '0');
-
-
-	if (strlen(mh) >=6) {
-
-	  if (islower(mh[4])) mh[4] = toupper(mh[4]);
-	  if (islower(mh[5])) mh[5] = toupper(mh[5]);
-
-	  if (mh[4] < 'A' || mh[4] > 'X' || mh[5] < 'A' || mh[5] > 'X') {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("The third pair of characters in Maidenhead locator \"%s\" must be in range of A thru X.\n", maidenhead);
-	    return (0);
-	  }
-
-	  /* Lon:  2 deg / 24 squares = 5 minutes / square */
-	  /* Lat:  1 deg / 24 squares = 2.5 minutes / square */
-
-	  lon += (mh[4] - 'A') * 5.0 / 60.0;
-	  lat += (mh[5] - 'A') * 2.5 / 60.0;
-
-	  if (strlen(mh) >= 8) {
-
-	    if ( ! isdigit(mh[6]) || ! isdigit(mh[7]) ) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("The fourth pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead);
-	      return (0);
-	    }
-
-	    /* Lon:  5   min / 10 squares = 0.5 minutes / square */
-	    /* Lat:  2.5 min / 10 squares = 0.25 minutes / square */
-
-	    lon += (mh[6] - '0') * 0.50 / 60.0;
-	    lat += (mh[7] - '0') * 0.25 / 60.0;
-
-	    lon += 0.250 / 60.0;	/* Move from corner to center of square */
-	    lat += 0.125 / 60.0;
-	  }
-	  else {
-	    lon += 2.5  / 60.0;	/* Move from corner to center of square */
-	    lat += 1.25 / 60.0;
-	  }
-	}
-	else {
-	  lon += 1.0;	/* Move from corner to center of square */
-	  lat += 0.5;
-	}
-
- 	//text_color_set(DW_COLOR_DEBUG);
-	//dw_printf("DEBUG: Maidenhead conversion \"%s\" -> %.6f %.6f\n", maidenhead, lat, lon);
-
-	*dlat = lat;
-	*dlon = lon;
-
-	return (1);
-}
-
-/* end ll_from_grid_square */
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      latlong.c
+ *
+ * Purpose:   	Various functions for dealing with latitude and longitude.
+ *		
+ * Description: Originally, these were scattered around in many places.
+ *		Over time they might all be gathered into one place
+ *		for consistency, reuse, and easier maintenance.
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <math.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "latlong.h"
+#include "textcolor.h"
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        latitude_to_str
+ *
+ * Purpose:     Convert numeric latitude to string for transmission.
+ *
+ * Inputs:      dlat		- Floating point degrees.
+ * 		ambiguity	- If 1, 2, 3, or 4, blank out that many trailing digits.
+ *
+ * Outputs:	slat		- String in format ddmm.mm[NS]
+ *				  Should always be exactly 8 characters + NUL.
+ *
+ * Returns:     None
+ *
+ *----------------------------------------------------------------*/
+
+void latitude_to_str (double dlat, int ambiguity, char *slat)
+{
+	char hemi;	/* Hemisphere: N or S */
+	int ideg;	/* whole number of degrees. */
+	double dmin;	/* Minutes after removing degrees. */
+	char smin[8];	/* Minutes in format mm.mm */
+	
+	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);
+	  hemi = 'S';
+	}
+	else {
+	  hemi = 'N';
+	}
+
+	ideg = (int)dlat;
+	dmin = (dlat - ideg) * 60.;
+
+	snprintf (smin, sizeof(smin), "%05.2f", dmin);
+	/* Due to roundoff, 59.9999 could come out as "60.00" */
+	if (smin[0] == '6') {
+	  smin[0] = '0';
+	  ideg++;
+	}
+
+	sprintf (slat, "%02d%s%c", ideg, smin, hemi);
+
+	if (ambiguity >= 1) {
+	  slat[6] = ' ';
+	  if (ambiguity >= 2) {
+	    slat[5] = ' ';
+	    if (ambiguity >= 3) {
+	      slat[3] = ' ';
+	      if (ambiguity >= 4) {
+	        slat[2] = ' ';
+	      }
+	    }
+	  }
+	}
+
+} /* end latitude_to_str */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        longitude_to_str
+ *
+ * Purpose:     Convert numeric longitude to string for transmission.
+ *
+ * Inputs:      dlong		- Floating point degrees.
+ * 		ambiguity	- If 1, 2, 3, or 4, blank out that many trailing digits.
+ *
+ * Outputs:	slat		- String in format dddmm.mm[NS]
+ *				  Should always be exactly 9 characters + NUL.
+ *
+ * Returns:     None
+ *
+ *----------------------------------------------------------------*/
+
+void longitude_to_str (double dlong, int ambiguity, char *slong)
+{
+	char hemi;	/* Hemisphere: N or S */
+	int ideg;	/* whole number of degrees. */
+	double dmin;	/* Minutes after removing degrees. */
+	char smin[8];	/* Minutes in format mm.mm */
+	
+	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);
+	  hemi = 'W';
+	}
+	else {
+	  hemi = 'E';
+	}
+
+	ideg = (int)dlong;
+	dmin = (dlong - ideg) * 60.;
+
+	snprintf (smin, sizeof(smin), "%05.2f", dmin);
+	/* Due to roundoff, 59.9999 could come out as "60.00" */
+	if (smin[0] == '6') {
+	  smin[0] = '0';
+	  ideg++;
+	}
+
+	sprintf (slong, "%03d%s%c", ideg, smin, hemi);
+/*
+ * The spec says position ambiguity in latitude also
+ * applies to longitude automatically.  
+ * Blanking longitude digits is not necessary but I do it
+ * because it makes things clearer.
+ */
+	if (ambiguity >= 1) {
+	  slong[7] = ' ';
+	  if (ambiguity >= 2) {
+	    slong[6] = ' ';
+	    if (ambiguity >= 3) {
+	      slong[4] = ' ';
+	      if (ambiguity >= 4) {
+	        slong[3] = ' ';
+	      }
+	    }
+	  }
+	}
+
+} /* end longitude_to_str */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        latitude_to_comp_str
+ *
+ * Purpose:     Convert numeric latitude to compressed string for transmission.
+ *
+ * Inputs:      dlat		- Floating point degrees.
+ *
+ * Outputs:	slat		- String in format yyyy.
+ *				  Exactly 4 bytes, no nul terminator.
+ *
+ *----------------------------------------------------------------*/
+
+void latitude_to_comp_str (double dlat, char *clat)
+{
+	int y, y0, y1, y2, y3;
+
+	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.;
+	}
+
+	y = (int)round(380926. * (90. - dlat));
+	
+	y0 = y / (91*91*91);
+	y -= y0 * (91*91*91);
+
+	y1 = y / (91*91);
+	y -= y1 * (91*91);
+
+	y2 = y / (91);
+	y -= y2 * (91);
+
+	y3 = y;
+
+	clat[0] = y0 + 33;
+	clat[1] = y1 + 33;
+	clat[2] = y2 + 33;
+	clat[3] = y3 + 33;
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name:        longitude_to_comp_str
+ *
+ * Purpose:     Convert numeric longitude to compressed string for transmission.
+ *
+ * Inputs:      dlong		- Floating point degrees.
+ *
+ * Outputs:	slat		- String in format xxxx.
+ *				  Exactly 4 bytes, no nul terminator.
+ *
+ *----------------------------------------------------------------*/
+
+void longitude_to_comp_str (double dlong, char *clon)
+{
+	int x, x0, x1, x2, x3;
+
+	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.;
+	}
+
+	x = (int)round(190463. * (180. + dlong));
+	
+	x0 = x / (91*91*91);
+	x -= x0 * (91*91*91);
+
+	x1 = x / (91*91);
+	x -= x1 * (91*91);
+
+	x2 = x / (91);
+	x -= x2 * (91);
+
+	x3 = x;
+
+	clon[0] = x0 + 33;
+	clon[1] = x1 + 33;
+	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.;
+
+	snprintf (smin, sizeof(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.;
+
+	snprintf (smin, sizeof(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.
+ *
+ *------------------------------------------------------------------*/
+
+
+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.
+ *
+ *------------------------------------------------------------------*/
+
+
+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);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ll_distance_km
+ *
+ * Purpose:	Calculate distance between two locations.
+ *
+ * Inputs:	lat1, lon1	- One location, in degrees.
+ *		lat2, lon2	- other location
+ *
+ * Returns:	Distance in km.
+ *
+ * Description:	The Ubiquitous Haversine formula.
+ *
+ *------------------------------------------------------------------*/
+
+#define R 6371
+
+double ll_distance_km (double lat1, double lon1, double lat2, double lon2)
+{
+	double a;
+
+	lat1 *= M_PI / 180;
+	lon1 *= M_PI / 180;	
+	lat2 *= M_PI / 180;	
+	lon2 *= M_PI / 180;	
+	
+	a = pow(sin((lat2-lat1)/2),2) + cos(lat1) * cos(lat2) * pow(sin((lon2-lon1)/2),2);
+
+	return (R * 2 *atan2(sqrt(a), sqrt(1-a)));
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	ll_from_grid_square
+ *
+ * Purpose:	Convert Maidenhead locator to latitude and longitude.
+ *
+ * Inputs:	maidenhead	- 2, 4, 6, 8, 10, or 12 character grid square locator.
+ *
+ * Outputs:	dlat, dlon	- Latitude and longitude.  
+ *				  Original values unchanged if error.
+ *
+ * Returns:	1 for success, 0 if error.
+ *
+ * Reference:	A good converter for spot checking.  Only handles 4 or 6 characters :-(
+ *		http://home.arcor.de/waldemar.kebsch/The_Makrothen_Contest/fmaidenhead.html
+ *
+ * Rambling:	What sort of resolution does this provide?
+ *		For 8 character form, each latitude unit is 0.25 minute.
+ *		(Longitude can be up to twice that around the equator.)
+ *		6371 km * 2 * pi * 0.25 / 60 / 360 = 0.463 km.  Is that right?
+ *
+ *		Using this calculator, http://www.earthpoint.us/Convert.aspx
+ *		It gives lower left corner of square rather than the middle.  :-(
+ *
+ *		FN42MA00  -->  19T 334361mE 4651711mN
+ *		FN42MA11  -->  19T 335062mE 4652157mN
+ *				   ------   -------
+ *				      701       446    meters difference.
+ *
+ *		With another two pairs, we are down around 2 meters for latitude.
+ *
+ *------------------------------------------------------------------*/
+
+#define MH_MIN_PAIR 1
+#define MH_MAX_PAIR 6
+#define MH_UNITS ( 18 * 10 * 24 * 10 * 24 * 10 * 2 )
+
+static const struct {
+	char *position;
+	char min_ch;
+	char max_ch;
+	int value;
+} mh_pair[MH_MAX_PAIR] = {
+	{ "first",    'A', 'R',  10 * 24 * 10 * 24 * 10 * 2 },
+	{ "second",   '0', '9',       24 * 10 * 24 * 10 * 2 },
+	{ "third",    'A', 'X',            10 * 24 * 10 * 2 },
+	{ "fourth",   '0', '9',                 24 * 10 * 2 },
+	{ "fifth",    'A', 'X',                      10 * 2 },
+	{ "sixth",    '0', '9',                           2 } };  // Even so we can get center of square.
+
+
+
+#if 1
+
+int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon)
+{
+	char mh[16];			/* Local copy, changed to upper case. */
+	int ilat = 0, ilon = 0;		/* In units in table above. */
+	char *p;
+	int n;
+
+	int np = strlen(maidenhead) / 2;	/* Number of pairs of characters. */
+
+	if (strlen(maidenhead) %2 != 0 || np < MH_MIN_PAIR || np > MH_MAX_PAIR) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Maidenhead locator \"%s\" must from 1 to %d pairs of characters.\n", maidenhead, MH_MAX_PAIR);
+	  return (0);
+	}
+
+	strlcpy (mh, maidenhead, sizeof(mh));
+	for (p = mh; *p != '\0'; p++) {
+	  if (islower(*p)) *p = toupper(*p);
+	}
+
+	for (n = 0; n < np; n++) {
+
+	  if (mh[2*n]   < mh_pair[n].min_ch || mh[2*n]   > mh_pair[n].max_ch || 
+	      mh[2*n+1] < mh_pair[n].min_ch || mh[2*n+1] > mh_pair[n].max_ch) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("The %s pair of characters in Maidenhead locator \"%s\" must be in range of %c thru %c.\n", 
+			mh_pair[n].position, maidenhead, mh_pair[n].min_ch, mh_pair[n].max_ch);
+	    return (0);
+	  }
+
+	  ilon +=  ( mh[2*n]   - mh_pair[n].min_ch ) * mh_pair[n].value;
+	  ilat +=  ( mh[2*n+1] - mh_pair[n].min_ch ) * mh_pair[n].value;
+
+	  if (n == np-1) {	// If last pair, take center of square.
+	    ilon += mh_pair[n].value / 2;
+	    ilat += mh_pair[n].value / 2;
+	  }
+	}
+
+	*dlat = (double)ilat / MH_UNITS * 180. - 90.;
+	*dlon = (double)ilon / MH_UNITS * 360. - 180.;
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf("DEBUG: Maidenhead conversion \"%s\" -> %.6f %.6f\n", maidenhead, *dlat, *dlon);
+
+	return (1);
+}
+#else
+
+int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon)
+{
+	double lat, lon;
+	char mh[16];
+
+
+	if (strlen(maidenhead) != 2 &&  strlen(maidenhead) != 4 &&  strlen(maidenhead) != 6 &&  strlen(maidenhead) != 8) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("Maidenhead locator \"%s\" must 2, 4, 6, or 8 characters.\n", maidenhead);
+	  return (0);
+	}
+
+	strcpy (mh, maidenhead);
+	if (islower(mh[0])) mh[0] = toupper(mh[0]);
+	if (islower(mh[1])) mh[1] = toupper(mh[1]);
+	
+	if (mh[0] < 'A' || mh[0] > 'R' || mh[1] < 'A' || mh[1] > 'R') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf("The first pair of characters in Maidenhead locator \"%s\" must be in range of A thru R.\n", maidenhead);
+	  return (0);
+	}
+
+
+	/* Lon:  360 deg / 18 squares = 20 deg / square */
+	/* Lat:  180 deg / 18 squares = 10 deg / square */
+
+	lon = (mh[0] - 'A') * 20 - 180;
+	lat = (mh[1] - 'A') * 10 - 90;
+
+	if (strlen(mh) >= 4) {
+
+	  if ( ! isdigit(mh[2]) || ! isdigit(mh[3]) ) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("The second pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead);
+	    return (0);
+	  }
+
+	  /* Lon:  20 deg / 10 squares = 2 deg / square */
+	  /* Lat:  10 deg / 10 squares = 1 deg / square */
+
+	  lon += (mh[2] - '0') * 2;
+	  lat += (mh[3] - '0');
+
+
+	  if (strlen(mh) >=6) {
+
+	    if (islower(mh[4])) mh[4] = toupper(mh[4]);
+	    if (islower(mh[5])) mh[5] = toupper(mh[5]);
+
+	    if (mh[4] < 'A' || mh[4] > 'X' || mh[5] < 'A' || mh[5] > 'X') {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("The third pair of characters in Maidenhead locator \"%s\" must be in range of A thru X.\n", maidenhead);
+	      return (0);
+	    }
+
+	    /* Lon:  2 deg / 24 squares = 5 minutes / square */
+	    /* Lat:  1 deg / 24 squares = 2.5 minutes / square */
+
+	    lon += (mh[4] - 'A') * 5.0 / 60.0;
+	    lat += (mh[5] - 'A') * 2.5 / 60.0;
+
+	    if (strlen(mh) >= 8) {
+
+	      if ( ! isdigit(mh[6]) || ! isdigit(mh[7]) ) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf("The fourth pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead);
+	        return (0);
+	      }
+
+	      /* Lon:  5   min / 10 squares = 0.5 minutes / square */
+	      /* Lat:  2.5 min / 10 squares = 0.25 minutes / square */
+
+	      lon += (mh[6] - '0') * 0.50 / 60.0;
+	      lat += (mh[7] - '0') * 0.25 / 60.0;
+
+	      lon += 0.250 / 60.0;	/* Move from corner to center of square */
+	      lat += 0.125 / 60.0;
+	    }
+	    else {
+	      lon += 2.5  / 60.0;	/* Move from corner to center of square */
+	      lat += 1.25 / 60.0;
+	    }
+	  }
+	  else {
+	    lon += 1.0;	/* Move from corner to center of square */
+	    lat += 0.5;
+	  }
+	}
+	else {
+	  lon += 10;	/* Move from corner to center of square */
+	  lat += 5;
+	}
+
+ 	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf("DEBUG: Maidenhead conversion \"%s\" -> %.6f %.6f\n", maidenhead, lat, lon);
+
+	*dlat = lat;
+	*dlon = lon;
+
+	return (1);
+}
+
+#endif
+
+/* end ll_from_grid_square */
+
+
+#if LLTEST
+
+/* gcc -o lltest -DLLTEST latlong.c textcolor.o misc.a && lltest */
+
+
+int main (int argc, char *argv[])
+{
+	char result[20];
+	int errors = 0;
+	int ok;
+	double dlat, dlon;
+
+/* Latitude to APRS format. */
+
+	latitude_to_str (45.25, 0, result);
+	if (strcmp(result, "4515.00N") != 0) { errors++; dw_printf ("Error 1.1: Did not expect \"%s\"\n", result); }
+
+	latitude_to_str (-45.25, 0, result);
+	if (strcmp(result, "4515.00S") != 0) { errors++; dw_printf ("Error 1.2: Did not expect \"%s\"\n", result); }
+
+
+	latitude_to_str (45.999830, 0, result);
+	if (strcmp(result, "4559.99N") != 0) { errors++; dw_printf ("Error 1.3: Did not expect \"%s\"\n", result); }
+
+	latitude_to_str (45.99999, 0, result);
+	if (strcmp(result, "4600.00N") != 0) { errors++; dw_printf ("Error 1.4: Did not expect \"%s\"\n", result); }
+
+
+	latitude_to_str (45.999830, 1, result);
+	if (strcmp(result, "4559.9 N") != 0) { errors++; dw_printf ("Error 1.5: Did not expect \"%s\"\n", result); }
+
+	latitude_to_str (45.999830, 2, result);
+	if (strcmp(result, "4559.  N") != 0) { errors++; dw_printf ("Error 1.6: Did not expect \"%s\"\n", result); }
+
+	latitude_to_str (45.999830, 3, result);
+	if (strcmp(result, "455 .  N") != 0) { errors++; dw_printf ("Error 1.7: Did not expect \"%s\"\n", result); }
+
+	latitude_to_str (45.999830, 4, result);
+	if (strcmp(result, "45  .  N") != 0) { errors++; dw_printf ("Error 1.8: Did not expect \"%s\"\n", result); }
+
+/* Longitude to APRS format. */
+
+	longitude_to_str (45.25, 0, result);
+	if (strcmp(result, "04515.00E") != 0) { errors++; dw_printf ("Error 2.1: Did not expect \"%s\"\n", result); }
+
+	longitude_to_str (-45.25, 0, result);
+	if (strcmp(result, "04515.00W") != 0) { errors++; dw_printf ("Error 2.2: Did not expect \"%s\"\n", result); }
+
+
+	longitude_to_str (45.999830, 0, result);
+	if (strcmp(result, "04559.99E") != 0) { errors++; dw_printf ("Error 2.3: Did not expect \"%s\"\n", result); }
+
+	longitude_to_str (45.99999, 0, result);
+	if (strcmp(result, "04600.00E") != 0) { errors++; dw_printf ("Error 2.4: Did not expect \"%s\"\n", result); }
+
+
+	longitude_to_str (45.999830, 1, result);
+	if (strcmp(result, "04559.9 E") != 0) { errors++; dw_printf ("Error 2.5: Did not expect \"%s\"\n", result); }
+
+	longitude_to_str (45.999830, 2, result);
+	if (strcmp(result, "04559.  E") != 0) { errors++; dw_printf ("Error 2.6: Did not expect \"%s\"\n", result); }
+
+	longitude_to_str (45.999830, 3, result);
+	if (strcmp(result, "0455 .  E") != 0) { errors++; dw_printf ("Error 2.7: Did not expect \"%s\"\n", result); }
+
+	longitude_to_str (45.999830, 4, result);
+	if (strcmp(result, "045  .  E") != 0) { errors++; dw_printf ("Error 2.8: Did not expect \"%s\"\n", result); }
+
+/* Compressed format. */
+/* Protocol spec example has <*e7 but I got <*e8 due to rounding rather than truncation to integer. */
+
+	memset(result, 0, sizeof(result));
+
+	latitude_to_comp_str (-90.0, result);
+	if (strcmp(result, "{{!!") != 0) { errors++; dw_printf ("Error 3.1: Did not expect \"%s\"\n", result); }
+
+	latitude_to_comp_str (49.5, result);
+	if (strcmp(result, "5L!!") != 0) { errors++; dw_printf ("Error 3.2: Did not expect \"%s\"\n", result); }
+
+	latitude_to_comp_str (90.0, result);
+	if (strcmp(result, "!!!!") != 0) { errors++; dw_printf ("Error 3.3: Did not expect \"%s\"\n", result); }
+
+
+	longitude_to_comp_str (-180.0, result);
+	if (strcmp(result, "!!!!") != 0) { errors++; dw_printf ("Error 3.4: Did not expect \"%s\"\n", result); }
+
+	longitude_to_comp_str (-72.75, result);
+	if (strcmp(result, "<*e8") != 0) { errors++; dw_printf ("Error 3.5: Did not expect \"%s\"\n", result); }
+
+	longitude_to_comp_str (180.0, result);
+	if (strcmp(result, "{{!!") != 0) { errors++; dw_printf ("Error 3.6: Did not expect \"%s\"\n", result); }
+
+// to be continued for others...  NMEA...
+
+
+/* Maidenhead locator to lat/long. */
+
+
+	ok = ll_from_grid_square ("BL11", &dlat, &dlon);
+	if (!ok || dlat < 20.4999999 || dlat > 21.5000001 || dlon < -157.0000001 || dlon > -156.9999999) { errors++; dw_printf ("Error 7.1: Did not expect %.6f %.6f\n", dlat, dlon); }
+
+	ok = ll_from_grid_square ("BL11BH", &dlat, &dlon);
+	if (!ok || dlat < 21.31249 || dlat > 21.31251 || dlon < -157.87501 || dlon > -157.87499) { errors++; dw_printf ("Error 7.2: Did not expect %.6f %.6f\n", dlat, dlon); }
+
+#if 0		// TODO: add more test cases after comparing results with other cconverters.
+		// Many other converters are limited to smaller number of characters,
+		// or return corner rather than center of square, or return 3 decimal places for degrees.
+
+	ok = ll_from_grid_square ("BL11BH16", &dlat, &dlon);
+	if (!ok || dlat < 21.? || dlat > 21.? || dlon < -157.? || dlon > -157.?) { errors++; dw_printf ("Error 7.3: Did not expect %.6f %.6f\n", dlat, dlon); }
+
+	ok = ll_from_grid_square ("BL11BH16oo", &dlat, &dlon);
+	if (!ok || dlat < 21.? || dlat > 21.? || dlon < -157.? || dlon > -157.?) { errors++; dw_printf ("Error 7.4: Did not expect %.6f %.6f\n", dlat, dlon); }
+
+	ok = ll_from_grid_square ("BL11BH16oo66", &dlat, &dlon);
+	if (!ok || dlat < 21.? || dlat > 21.? || dlon < -157.? || dlon > -157.?) { errors++; dw_printf ("Error 7.5: Did not expect %.6f %.6f\n", dlat, dlon); }
+#endif
+        if (errors > 0) {
+          text_color_set (DW_COLOR_ERROR);
+          dw_printf ("\nLocation Coordinate Conversion Test - FAILED!\n");
+          exit (EXIT_FAILURE);
+        }
+        text_color_set (DW_COLOR_REC);
+        dw_printf ("\nLocation Coordinate Conversion Test - SUCCESS!\n");
+        exit (EXIT_SUCCESS);
+
+}
+
+
+
+#endif
+
+
 /* end latlong.c */
\ No newline at end of file
diff --git a/latlong.h b/latlong.h
index 58c1071..ae98fe6 100644
--- a/latlong.h
+++ b/latlong.h
@@ -1,24 +1,24 @@
-
-/* latlong.h */
-
-
-/* Use this value for unknown latitude/longitude or other values. */
-
-#define G_UNKNOWN (-999999)
-
-
-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);
-
-double ll_distance_km (double lat1, double lon1, double lat2, double lon2);
-
+
+/* latlong.h */
+
+
+/* Use this value for unknown latitude/longitude or other values. */
+
+#define G_UNKNOWN (-999999)
+
+
+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);
+
+double ll_distance_km (double lat1, double lon1, double lat2, double lon2);
+
 int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon);
\ No newline at end of file
diff --git a/ll2utm.c b/ll2utm.c
index ec91a26..3ff663d 100644
--- a/ll2utm.c
+++ b/ll2utm.c
@@ -1,114 +1,114 @@
-/* Latitude / Longitude to UTM conversion */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-
-
-#include "utm.h"
-#include "mgrs.h"
-#include "usng.h"
-#include "error_string.h"
-
-
-#define D2R(d) ((d) * M_PI / 180.)
-#define R2D(r) ((r) * 180. / M_PI)
-
-
-static void usage();
-
-
-int main (int argc, char *argv[]) 
-{
-	double easting;
-	double northing;
-	double lat, lon;
-	char mgrs[32];
-	char usng[32];
-	char hemisphere;
-	long lzone;
-	long err;
-	char message[300];
-
-
-	if (argc != 3) usage();
-
-        lat = atof(argv[1]);
-
-        lon = atof(argv[2]);
-
-
-// UTM
-
-	err = Convert_Geodetic_To_UTM (D2R(lat), D2R(lon), &lzone, &hemisphere, &easting, &northing);
-        if (err == 0) {                      
-	  printf ("UTM zone = %ld, hemisphere = %c, easting = %.0f, northing = %.0f\n", lzone, hemisphere, easting, northing);
-	}
-	else {
-	  utm_error_string (err, message);
-	  fprintf (stderr, "Conversion to UTM failed:\n%s\n\n", message);
-	
-	  // Others could still succeed, keep going.
-	}
-
-
-// Practice run with MGRS to see if it will succeed
-
-        err = Convert_Geodetic_To_MGRS (D2R(lat), D2R(lon), 5L, mgrs);
-	if (err == 0) {
-
-// OK, hope changing precision doesn't make a difference.
-
-	  long precision;
-
-	  printf ("MGRS =");
-          for (precision = 1; precision <= 5; precision++) {
-            Convert_Geodetic_To_MGRS (D2R(lat), D2R(lon), precision, mgrs);
-            printf ("  %s", mgrs);
-          }
-	  printf ("\n");
-	}
-	else {
-	  mgrs_error_string (err, message);
-	  fprintf (stderr, "Conversion to MGRS failed:\n%s\n", message);
-	}
-
-// Same for USNG.
-
-        err = Convert_Geodetic_To_USNG (D2R(lat), D2R(lon), 5L, usng);
-	if (err == 0) {
-
-	  long precision;
-
-	  printf ("USNG =");
-          for (precision = 1; precision <= 5; precision++) {
-            Convert_Geodetic_To_USNG (D2R(lat), D2R(lon), precision, usng);
-            printf ("  %s", usng);
-          }
-	  printf ("\n");
-	}
-	else {
-	  usng_error_string (err, message);
-	  fprintf (stderr, "Conversion to USNG failed:\n%s\n", message);
-	}
-
-	exit (0);
-}
-
-
-static void usage (void)
-{
-	fprintf (stderr, "Latitude / Longitude to UTM conversion\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "Usage:\n");
-	fprintf (stderr, "\tll2utm  latitude  longitude\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "where,\n");
-	fprintf (stderr, "\tLatitude and longitude are in decimal degrees.\n");
-	fprintf (stderr, "\t   Use negative for south or west.\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "Example:\n");
-	fprintf (stderr, "\tll2utm 42.662139 -71.365553\n");
-
-	exit (1);
+/* Latitude / Longitude to UTM conversion */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+
+#include "utm.h"
+#include "mgrs.h"
+#include "usng.h"
+#include "error_string.h"
+
+
+#define D2R(d) ((d) * M_PI / 180.)
+#define R2D(r) ((r) * 180. / M_PI)
+
+
+static void usage();
+
+
+int main (int argc, char *argv[]) 
+{
+	double easting;
+	double northing;
+	double lat, lon;
+	char mgrs[32];
+	char usng[32];
+	char hemisphere;
+	long lzone;
+	long err;
+	char message[300];
+
+
+	if (argc != 3) usage();
+
+        lat = atof(argv[1]);
+
+        lon = atof(argv[2]);
+
+
+// UTM
+
+	err = Convert_Geodetic_To_UTM (D2R(lat), D2R(lon), &lzone, &hemisphere, &easting, &northing);
+        if (err == 0) {                      
+	  printf ("UTM zone = %ld, hemisphere = %c, easting = %.0f, northing = %.0f\n", lzone, hemisphere, easting, northing);
+	}
+	else {
+	  utm_error_string (err, message);
+	  fprintf (stderr, "Conversion to UTM failed:\n%s\n\n", message);
+	
+	  // Others could still succeed, keep going.
+	}
+
+
+// Practice run with MGRS to see if it will succeed
+
+        err = Convert_Geodetic_To_MGRS (D2R(lat), D2R(lon), 5L, mgrs);
+	if (err == 0) {
+
+// OK, hope changing precision doesn't make a difference.
+
+	  long precision;
+
+	  printf ("MGRS =");
+          for (precision = 1; precision <= 5; precision++) {
+            Convert_Geodetic_To_MGRS (D2R(lat), D2R(lon), precision, mgrs);
+            printf ("  %s", mgrs);
+          }
+	  printf ("\n");
+	}
+	else {
+	  mgrs_error_string (err, message);
+	  fprintf (stderr, "Conversion to MGRS failed:\n%s\n", message);
+	}
+
+// Same for USNG.
+
+        err = Convert_Geodetic_To_USNG (D2R(lat), D2R(lon), 5L, usng);
+	if (err == 0) {
+
+	  long precision;
+
+	  printf ("USNG =");
+          for (precision = 1; precision <= 5; precision++) {
+            Convert_Geodetic_To_USNG (D2R(lat), D2R(lon), precision, usng);
+            printf ("  %s", usng);
+          }
+	  printf ("\n");
+	}
+	else {
+	  usng_error_string (err, message);
+	  fprintf (stderr, "Conversion to USNG failed:\n%s\n", message);
+	}
+
+	exit (0);
+}
+
+
+static void usage (void)
+{
+	fprintf (stderr, "Latitude / Longitude to UTM conversion\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "Usage:\n");
+	fprintf (stderr, "\tll2utm  latitude  longitude\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "where,\n");
+	fprintf (stderr, "\tLatitude and longitude are in decimal degrees.\n");
+	fprintf (stderr, "\t   Use negative for south or west.\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "Example:\n");
+	fprintf (stderr, "\tll2utm 42.662139 -71.365553\n");
+
+	exit (1);
 }
\ No newline at end of file
diff --git a/log.c b/log.c
index 3b2dabb..f5448e7 100644
--- a/log.c
+++ b/log.c
@@ -1,368 +1,370 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2014, 2015  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, alevel_t 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];
-	  char alevel_text[32];
-
-
-
-	  // 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, "?");
-	    }
-	  }
-
-	  ax25_alevel_to_text (alevel, alevel_text);
-
-
-	  // 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,%s,%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_text, (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 */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014, 2015  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, size_t outsize, 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;
+	  }
+	}
+
+// BUG: need to avoid buffer overflow on "out".   *strcpy*
+
+	if (need_quote) {
+	  *q++ = '"';
+	  for (p = in; *p != '\0'; p++) {
+	    if (*p == '"') {	
+	      *q++ = *p;
+	    }
+	    *q++ = *p;
+	  }
+	  *q++ = '"';
+	  *q = '\0';
+	}
+	else {
+	  strlcpy (out, in, outsize);
+	}
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * 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;
+
+	strlcpy (g_log_dir, "", sizeof(g_log_dir));
+	g_log_fp = NULL;
+	strlcpy (g_open_fname, "", sizeof(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.
+	    strlcpy (g_log_dir, path, sizeof(g_log_dir));
+	  }
+	  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");
+	    strlcpy (g_log_dir, ".", sizeof(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);
+	    strlcpy (g_log_dir, path, sizeof(g_log_dir));
+	  }
+	  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");
+	    strlcpy (g_log_dir, ".", sizeof(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, alevel_t 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);
+	(void)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;
+
+	  strlcpy (full_path, g_log_dir, sizeof(full_path));
+#if __WIN32__
+	  strlcat (full_path, "\\", sizeof(full_path));
+#else
+	  strlcat (full_path, "/", sizeof(full_path));
+#endif
+	  strlcat (full_path, fname, sizeof(full_path));
+
+	  // 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) {
+	    strlcpy (g_open_fname, fname, sizeof(g_open_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));
+	    strlcpy (g_open_fname, "", sizeof(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];
+	  char alevel_text[32];
+
+
+
+	  // 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? */
+
+	  strlcpy(heard, "", sizeof(heard));
+	  if (pp != NULL) {
+	    if (ax25_get_num_addr(pp) == 0) {
+	      /* Not AX.25. No station to display below. */
+	      h = -1;
+	      strlcpy (heard, "", sizeof(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);
+	      strlcat (heard, "?", sizeof(heard));
+	    }
+	  }
+
+	  ax25_alevel_to_text (alevel, alevel_text);
+
+
+	  // Might need to quote anything that could contain comma or quote.
+
+	  strlcpy(sdti, "", sizeof(sdti));
+	  if (pp != NULL) {
+	    stemp[0] = ax25_get_dti(pp);
+	    stemp[1] = '\0';
+	    quote_for_csv (sdti, sizeof(sdti), stemp);
+	  }
+
+	  quote_for_csv (sname, sizeof(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, sizeof(ssymbol), stemp);
+
+	  quote_for_csv (smfr, sizeof(smfr), A->g_mfr);
+	  quote_for_csv (sstatus, sizeof(sstatus), A->g_mic_e_status);
+	  quote_for_csv (stelemetry, sizeof(stelemetry), A->g_telemetry);
+	  quote_for_csv (scomment, sizeof(scomment), A->g_comment);
+
+	  strlcpy (slat, "", sizeof(slat));  if (A->g_lat != G_UNKNOWN)         snprintf (slat, sizeof(slat), "%.6f", A->g_lat);
+	  strlcpy (slon, "", sizeof(slon));  if (A->g_lon != G_UNKNOWN)         snprintf (slon, sizeof(slon), "%.6f", A->g_lon);
+	  strlcpy (sspd, "", sizeof(sspd));  if (A->g_speed_mph != G_UNKNOWN)   snprintf (sspd, sizeof(sspd), "%.1f", DW_MPH_TO_KNOTS(A->g_speed_mph));
+	  strlcpy (scse, "", sizeof(scse));  if (A->g_course != G_UNKNOWN)      snprintf (scse, sizeof(scse), "%.1f", A->g_course);
+	  strlcpy (salt, "", sizeof(salt));  if (A->g_altitude_ft != G_UNKNOWN) snprintf (salt, sizeof(salt), "%.1f", DW_FEET_TO_METERS(A->g_altitude_ft));
+
+	  strlcpy (sfreq, "", sizeof(sfreq));  if (A->g_freq   != G_UNKNOWN) snprintf (sfreq, sizeof(sfreq), "%.3f", A->g_freq);
+	  strlcpy (soffs, "", sizeof(soffs));  if (A->g_offset != G_UNKNOWN) snprintf (soffs, sizeof(soffs), "%+d", A->g_offset);
+	  strlcpy (stone, "", sizeof(stone));  if (A->g_tone   != G_UNKNOWN) snprintf (stone, sizeof(stone), "%.1f", A->g_tone);
+	                       if (A->g_dcs    != G_UNKNOWN) snprintf (stone, sizeof(stone), "D%03o", A->g_dcs);
+
+	  fprintf (g_log_fp, "%d,%d,%s,%s,%s,%s,%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_text, (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;
+	  strlcpy (g_open_fname, "", sizeof(g_open_fname));
+	}
+
+} /* end log_term */
+
+
+/* end log.c */
diff --git a/log.h b/log.h
index d43880d..5cc0264 100644
--- a/log.h
+++ b/log.h
@@ -1,17 +1,17 @@
-
-/* log.h */
-
-
-#include "hdlc_rec2.h"		// for retry_t
-
-#include "decode_aprs.h"	// for decode_aprs_t
-
-#include "ax25_pad.h"
-
-
-
-void log_init (char *path);	
-
-void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
-
+
+/* log.h */
+
+
+#include "hdlc_rec2.h"		// for retry_t
+
+#include "decode_aprs.h"	// for decode_aprs_t
+
+#include "ax25_pad.h"
+
+
+
+void log_init (char *path);	
+
+void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
+
 void log_term (void); 	
\ No newline at end of file
diff --git a/log2gpx.c b/log2gpx.c
index 5570120..c8f47e0 100644
--- a/log2gpx.c
+++ b/log2gpx.c
@@ -1,535 +1,551 @@
-
-//
-//    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");
+
+//
+//    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
+
+#include "direwolf.h"
+
+
+/*
+ * 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");
+
+	  /* Suppress the 'set but not used' warnings. */
+	  /* Alternatively, we might use __attribute__((unused)) */
+
+	  (void)(ptelemetry);
+	  (void)(psystem);
+	  (void)(psymbol);
+	  (void)(pdti);
+	  (void)(perror);
+	  (void)(plevel);
+	  (void)(pheard);
+	  (void)(psource);
+	  (void)(putime);
+
+
+/*
+ * 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);
+	      snprintf (desc, sizeof(desc), "%.3f MHz", freq);
+	    }
+	    else {
+	      strlcpy (desc, "", sizeof(desc));
+	    }
+
+	    if (poffset != NULL && strlen(poffset) > 0) {
+	      offset = atoi(poffset);
+	      if (offset != 0 && offset % 1000 == 0) {
+	        snprintf (stemp, sizeof(stemp), "%+dM", offset / 1000);
+	      }
+	      else {
+	        snprintf (stemp, sizeof(stemp), "%+dk", offset);
+	      }
+	      if (strlen(desc) > 0) strlcat (desc, " ", sizeof(desc));
+	      strlcat (desc, stemp, sizeof(desc));
+	    }
+
+	    if (ptone != NULL && strlen(ptone) > 0) {
+	      if (*ptone == 'D') {
+	        snprintf (stemp, sizeof(stemp), "DCS %s", ptone+1);
+	      }
+	      else {
+	        snprintf (stemp, sizeof(stemp), "PL %s", ptone);
+	      }
+	      if (strlen(desc) > 0) strlcat (desc, " ", sizeof(desc));
+	      strlcat (desc, stemp, sizeof(desc));
+	    }
+
+	    strlcpy (comment, "", sizeof(comment));
+	    if (pstatus != NULL && strlen(pstatus) > 0) {
+	      strlcpy (comment, pstatus, sizeof(comment));
+	    }
+	    if (pcomment != NULL && strlen(pcomment) > 0) {
+	      if (strlen(comment) > 0) strlcat (comment, ", ", sizeof(comment));
+	      strlcat (comment, pcomment, sizeof(comment));
+	    }
+	    
+	    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/man1/aclients.1 b/man1/aclients.1
index 38762a1..578c1f3 100644
--- a/man1/aclients.1
+++ b/man1/aclients.1
@@ -9,12 +9,13 @@ aclients \- Test program for side-by-side TNC performance comparison.
 .I tnc ...
 .RS
 .P
-Each \fItnc\fR reference is either a serial port or TCP network port followed by = and a comment.
+Each \fItnc\fR reference is a port,  =, and a description.
 .P
 .RE
 
 .SH DESCRIPTION
 \fBaclients\fR is used to compare how well different TNCs decode AS.25 frames.
+The port can be a serial port name, host_name:tcp_port, ip_addr:port, or simply tcp_port.
 .P
 
 
@@ -26,7 +27,7 @@ None.
 
 .SH EXAMPLES
 
-.B aclients  /dev/ttyS0=KPC3+  /dev/ttyUSB0=D710A  8000=DireWolf
+.B aclients  /dev/ttyS0=KPC3+  /dev/ttyUSB0=D710A  8000=DireWolf 192.168.1.64:8002=other
 .P
 Serial port /dev/ttyS0 is connected to a KPC3+ with monitor mode turned on.
 .P
@@ -34,6 +35,8 @@ Serial port /dev/ttyUSB0 is connected to a TM-D710A with monitor mode turned on.
 .P
 The Dire Wolf software TNC is available on network port 8000.
 .P
+Some other software TNC is available on network port 8002 on host 192.168.1.64.
+.P
 Packets from each are displayed in columns so it is easy to see how well each decodes 
 the received signals.
 .P
diff --git a/man1/direwolf.1 b/man1/direwolf.1
index 69859af..b81b8a2 100644
--- a/man1/direwolf.1
+++ b/man1/direwolf.1
@@ -77,9 +77,15 @@ u = Display non-ASCII text in hexadecimal.
 .P
 p = Packet dump in hexadecimal.
 .P
-t = GPS Tracker.
+g = GPS interface.
+.P
+t = Tracker beacon.
 .P
 o = Output controls such as PTT and DCD.
+.P
+i = IGate
+.P
+h = Hamlib verbose level.  Repeat for more.
 .RE
 .RE
 .PD
@@ -113,6 +119,14 @@ Send Xmit level calibration tones.
 .B "-U " 
 Print UTF-8 test string and exit.
 
+.TP
+.B "-S " 
+Print Symbol tables and exit.
+
+.TP
+.BI "-a " "n"
+Report audio device statistics each n seconds.
+
 
 .SH EXAMPLES
 gqrx (2.3 and later) has the ability to send streaming audio through a UDP socket to another application for further processing. 
diff --git a/man1/gen_packets.1 b/man1/gen_packets.1
index b07e7f6..f1782aa 100644
--- a/man1/gen_packets.1
+++ b/man1/gen_packets.1
@@ -98,10 +98,14 @@ Both of these are equivalent.  "-B 9600" automatically selects scrambled baseban
 Both of these generate 200 Hz shift, 300 baud, suitable for HF SSB transceiver.
 .RE
 .P
-.B echo -n "WB2OSZ>WORLD:Hello, world!" | gen_packets -a 25 -o x.wav -
+.B echo -n 'WB2OSZ>WORLD:Hello, world!' | gen_packets -a 25 -o x.wav -
+.PD 0
+.P
+.PD
+.B atest x.wav
 .P
 .RS
-Read message from stdin and put quarter volume sound into the file x.wav.
+Read message from stdin and put quarter volume sound into the file x.wav.  Decode the sound file.
 .RE
 .P
 
diff --git a/mgn_icon.h b/mgn_icon.h
index e97005c..3c96aab 100644
--- a/mgn_icon.h
+++ b/mgn_icon.h
@@ -1,267 +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
-
+
+
+/* 
+ * 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
old mode 100755
new mode 100644
index f824e25..140f960
--- a/misc/README-dire-wolf.txt
+++ b/misc/README-dire-wolf.txt
@@ -1,9 +1,34 @@
-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.
-
-They were copied from Cygwin source:
-
-	/usr/src/cygwin-1.7.10-1/newlib/libc/string/strsep.c
-	/usr/src/cygwin-1.7.10-1/newlib/libc/string/strtok_r.c
-
+
+Files in this directory fill in the gaps missing for some operating systems.
+
+
+--------------------------------------
+
+These are part of the standard C library for Linux and similar operating systems.
+For the Windows version we need to include our own copy.
+
+They were copied from Cygwin source.
+/usr/src/cygwin-1.7.10-1/newlib/libc/string/...
+
+	strsep.c
+	strtok_r.c
+
+--------------------------------------
+
+This was also missing on Windows but available everywhere else.
+
+	strcasestr.c
+
+--------------------------------------
+
+
+The are used for the Linux and Windows versions.
+They should be part of the standard C library for OpenBSD, FreeBSD, Mac OS X.
+These are from OpenBSD.
+http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcpy.c
+http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcat.c
+
+
+	strlcpy.c
+	strlcat.c
+	
\ No newline at end of file
diff --git a/misc/strcasestr.c b/misc/strcasestr.c
old mode 100755
new mode 100644
index 6cadc2a..a418549
--- a/misc/strcasestr.c
+++ b/misc/strcasestr.c
@@ -1,64 +1,64 @@
-/*-
- * Copyright (c) 1990, 1993
- *	The Regents of the University of California.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Chris Torek.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *	This product includes software developed by the University of
- *	California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-//#include <sys/cdefs.h>
-
-#include <ctype.h>
-#include <string.h>
-
-/*
- * Find the first occurrence of find in s, ignore case.
- */
-char *
-strcasestr(s, find)
-	const char *s, *find;
-{
-	char c, sc;
-	size_t len;
-
-	if ((c = *find++) != 0) {
-		c = tolower((unsigned char)c);
-		len = strlen(find);
-		do {
-			do {
-				if ((sc = *s++) == 0)
-					return (NULL);
-			} while ((char)tolower((unsigned char)sc) != c);
-		} while (strncasecmp(s, find, len) != 0);
-		s--;
-	}
-	return ((char *)s);
-}
+/*-
+ * Copyright (c) 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+//#include <sys/cdefs.h>
+
+#include <ctype.h>
+#include <string.h>
+
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
+char *
+strcasestr(s, find)
+	const char *s, *find;
+{
+	char c, sc;
+	size_t len;
+
+	if ((c = *find++) != 0) {
+		c = tolower((unsigned char)c);
+		len = strlen(find);
+		do {
+			do {
+				if ((sc = *s++) == 0)
+					return (NULL);
+			} while ((char)tolower((unsigned char)sc) != c);
+		} while (strncasecmp(s, find, len) != 0);
+		s--;
+	}
+	return ((char *)s);
+}
diff --git a/misc/strlcat.c b/misc/strlcat.c
new file mode 100644
index 0000000..c08c62d
--- /dev/null
+++ b/misc/strlcat.c
@@ -0,0 +1,127 @@
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      strlcat.c
+ *
+ * Purpose:   	Safe string functions to guard against buffer overflow.
+ *			
+ * Description:	The size of character strings, especially when coming from the 
+ *		outside, can sometimes exceed a fixed size storage area.  
+ *
+ *		There was one case where a MIC-E format packet had an enormous
+ *		comment that exceeded an internal buffer of 256 characters,
+ *		resulting in a crash.
+ *
+ *		We are not always meticulous about checking sizes to avoid overflow.
+ *		Use of these functions, instead of strcpy and strcat, should
+ *		help avoid issues.
+ *
+ * Orgin:	From OpenBSD as the copyright notice indicates.
+ *		The GNU folks didn't think it was appropriate for inclusion 
+ *		in glibc.     https://lwn.net/Articles/507319/
+ *
+ * Modifications:	Added extra debug output when strings are truncated.
+ *			Not sure if I will leave this in the release version
+ *			or just let it happen silently.		
+ *
+ *---------------------------------------------------------------*/
+
+
+
+#include <stdlib.h>
+#include <string.h>
+#include "direwolf.h"
+#include "textcolor.h"
+
+
+/*	$NetBSD: strlcat.c,v 1.5 2014/10/31 18:59:32 spz Exp $	*/
+/*	from	NetBSD: strlcat.c,v 1.16 2003/10/27 00:12:42 lukem Exp	*/
+/*	from OpenBSD: strlcat.c,v 1.10 2003/04/12 21:56:39 millert Exp	*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller at courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left).  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+
+#if DEBUG_STRL
+size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line)
+#else
+size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz)
+#endif
+{
+	char *d = dst;
+	const char *s = src;
+	size_t n = siz;
+	size_t dlen;
+	size_t retval;
+
+#if DEBUG_STRL
+	if (dst == NULL) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("ERROR: strlcat dst is NULL.  (%s %s %d)\n", file, func, line);
+		return (0);
+	}
+	if (src == NULL) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("ERROR: strlcat src is NULL.  (%s %s %d)\n", file, func, line);
+		return (0);
+	}
+	if (siz == 1 || siz == 4) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("Suspicious strlcat siz.  Is it using sizeof a pointer variable?  (%s %s %d)\n", file, func, line);
+	}
+#endif
+
+	/* Find the end of dst and adjust bytes left but don't go past end */
+	while (n-- != 0 && *d != '\0')
+		d++;
+	dlen = d - dst;
+	n = siz - dlen;
+
+	if (n == 0) {
+		retval = dlen + strlen(s);
+		goto the_end;
+	}
+	while (*s != '\0') {
+		if (n != 1) {
+			*d++ = *s;
+			n--;
+		}
+		s++;
+	}
+	*d = '\0';
+
+	retval = dlen + (s - src);	/* count does not include NUL */
+the_end:
+
+#if DEBUG_STRL
+	if (retval >= siz) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("WARNING: strlcat result length %d exceeds maximum length %d.  (%s %s %d)\n",
+				(int)retval, (int)(siz-1), file, func, line);
+	}
+#endif
+	return (retval);
+}
\ No newline at end of file
diff --git a/misc/strlcpy.c b/misc/strlcpy.c
new file mode 100644
index 0000000..64a18c1
--- /dev/null
+++ b/misc/strlcpy.c
@@ -0,0 +1,119 @@
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      strlcpy.c
+ *
+ * Purpose:   	Safe string functions to guard against buffer overflow.
+ *			
+ * Description:	The size of character strings, especially when coming from the 
+ *		outside, can sometimes exceed a fixed size storage area.  
+ *
+ *		There was one case where a MIC-E format packet had an enormous
+ *		comment that exceeded an internal buffer of 256 characters,
+ *		resulting in a crash.
+ *
+ *		We are not always meticulous about checking sizes to avoid overflow.
+ *		Use of these functions, instead of strcpy and strcat, should
+ *		help avoid issues.
+ *
+ * Orgin:	From OpenBSD as the copyright notice indicates.
+ *		The GNU folks didn't think it was appropriate for inclusion 
+ *		in glibc.     https://lwn.net/Articles/507319/
+ *
+ * Modifications:	Added extra debug output when strings are truncated.
+ *			Not sure if I will leave this in the release version
+ *			or just let it happen silently.		
+ *
+ *---------------------------------------------------------------*/
+
+
+
+/*	$NetBSD: strlcpy.c,v 1.5 2014/10/31 18:59:32 spz Exp $	*/
+/*	from	NetBSD: strlcpy.c,v 1.14 2003/10/27 00:12:42 lukem Exp	*/
+/*	from OpenBSD: strlcpy.c,v 1.7 2003/04/12 21:56:39 millert Exp	*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller at courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include "direwolf.h"
+#include "textcolor.h"
+
+/*
+ * Copy src to string dst of size siz.  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+
+#if DEBUG_STRL
+size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line)
+#else
+size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz)
+#endif
+{
+	char *d = dst;
+	const char *s = src;
+	size_t n = siz;
+	size_t retval;
+
+#if DEBUG_STRL
+	if (dst == NULL) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("ERROR: strlcpy dst is NULL.  (%s %s %d)\n", file, func, line);
+		return (0);
+	}
+	if (src == NULL) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("ERROR: strlcpy src is NULL.  (%s %s %d)\n", file, func, line);
+		return (0);
+	}
+	if (siz == 1 || siz == 4) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("Suspicious strlcpy siz.  Is it using sizeof a pointer variable?  (%s %s %d)\n", file, func, line);
+	}
+#endif
+
+	/* Copy as many bytes as will fit */
+	if (n != 0 && --n != 0) {
+		do {
+			if ((*d++ = *s++) == 0)
+				break;
+		} while (--n != 0);
+	}
+
+	/* Not enough room in dst, add NUL and traverse rest of src */
+	if (n == 0) {
+		if (siz != 0)
+			*d = '\0';		/* NUL-terminate dst */
+		while (*s++)
+			;
+	}
+
+	retval = s - src - 1;	/* count does not include NUL */
+
+#if DEBUG_STRL
+	if (retval >= siz) {
+		text_color_set (DW_COLOR_ERROR);
+		dw_printf ("WARNING: strlcpy result length %d exceeds maximum length %d.  (%s %s %d)\n",
+				(int)retval, (int)(siz-1), file, func, line);
+	}
+#endif
+	return (retval);
+}
+
diff --git a/misc/strsep.c b/misc/strsep.c
old mode 100755
new mode 100644
index 37181b0..7d764d0
--- a/misc/strsep.c
+++ b/misc/strsep.c
@@ -1,22 +1,22 @@
-/* BSD strsep function */
-
-/* Copyright 2002, Red Hat Inc. */
-
-/* undef STRICT_ANSI so that strsep prototype will be defined */
-#undef  __STRICT_ANSI__
-#include <string.h>
-//#include <_ansi.h>
-//#include <reent.h>
-
-#define _DEFUN(name,arglist,args) name(args)
-#define _AND ,
-
-extern char *__strtok_r (char *, const char *, char **, int);
-
-char *
-_DEFUN (strsep, (source_ptr, delim),
-	register char **source_ptr _AND
-	register const char *delim)
-{
-	return __strtok_r (*source_ptr, delim, source_ptr, 0);
-}
+/* BSD strsep function */
+
+/* Copyright 2002, Red Hat Inc. */
+
+/* undef STRICT_ANSI so that strsep prototype will be defined */
+#undef  __STRICT_ANSI__
+#include <string.h>
+//#include <_ansi.h>
+//#include <reent.h>
+
+#define _DEFUN(name,arglist,args) name(args)
+#define _AND ,
+
+extern char *__strtok_r (char *, const char *, char **, int);
+
+char *
+_DEFUN (strsep, (source_ptr, delim),
+	register char **source_ptr _AND
+	register const char *delim)
+{
+	return __strtok_r (*source_ptr, delim, source_ptr, 0);
+}
diff --git a/misc/strtok_r.c b/misc/strtok_r.c
old mode 100755
new mode 100644
index d56a4e4..a86de79
--- a/misc/strtok_r.c
+++ b/misc/strtok_r.c
@@ -1,102 +1,102 @@
-/*
- * Copyright (c) 1988 Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <string.h>
-
-#define _DEFUN(name,arglist,args) name(args)
-#define _AND ,
-
-char *
-_DEFUN (__strtok_r, (s, delim, lasts, skip_leading_delim),
-	register char *s _AND
-	register const char *delim _AND
-	char **lasts _AND
-	int skip_leading_delim)
-{
-	register char *spanp;
-	register int c, sc;
-	char *tok;
-
-
-	if (s == NULL && (s = *lasts) == NULL)
-		return (NULL);
-
-	/*
-	 * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
-	 */
-cont:
-	c = *s++;
-	for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
-		if (c == sc) {
-			if (skip_leading_delim) {
-				goto cont;
-			}
-			else {
-				*lasts = s;
-				s[-1] = 0;
-				return (s - 1);
-			}
-		}
-	}
-
-	if (c == 0) {		/* no non-delimiter characters */
-		*lasts = NULL;
-		return (NULL);
-	}
-	tok = s - 1;
-
-	/*
-	 * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
-	 * Note that delim must have one NUL; we stop if we see that, too.
-	 */
-	for (;;) {
-		c = *s++;
-		spanp = (char *)delim;
-		do {
-			if ((sc = *spanp++) == c) {
-				if (c == 0)
-					s = NULL;
-				else
-					s[-1] = 0;
-				*lasts = s;
-				return (tok);
-			}
-		} while (sc != 0);
-	}
-	/* NOTREACHED */
-}
-
-char *
-_DEFUN (strtok_r, (s, delim, lasts),
-	register char *s _AND
-	register const char *delim _AND
-	char **lasts)
-{
-	return __strtok_r (s, delim, lasts, 1);
-}
+/*
+ * Copyright (c) 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#define _DEFUN(name,arglist,args) name(args)
+#define _AND ,
+
+char *
+_DEFUN (__strtok_r, (s, delim, lasts, skip_leading_delim),
+	register char *s _AND
+	register const char *delim _AND
+	char **lasts _AND
+	int skip_leading_delim)
+{
+	register char *spanp;
+	register int c, sc;
+	char *tok;
+
+
+	if (s == NULL && (s = *lasts) == NULL)
+		return (NULL);
+
+	/*
+	 * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
+	 */
+cont:
+	c = *s++;
+	for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
+		if (c == sc) {
+			if (skip_leading_delim) {
+				goto cont;
+			}
+			else {
+				*lasts = s;
+				s[-1] = 0;
+				return (s - 1);
+			}
+		}
+	}
+
+	if (c == 0) {		/* no non-delimiter characters */
+		*lasts = NULL;
+		return (NULL);
+	}
+	tok = s - 1;
+
+	/*
+	 * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
+	 * Note that delim must have one NUL; we stop if we see that, too.
+	 */
+	for (;;) {
+		c = *s++;
+		spanp = (char *)delim;
+		do {
+			if ((sc = *spanp++) == c) {
+				if (c == 0)
+					s = NULL;
+				else
+					s[-1] = 0;
+				*lasts = s;
+				return (tok);
+			}
+		} while (sc != 0);
+	}
+	/* NOTREACHED */
+}
+
+char *
+_DEFUN (strtok_r, (s, delim, lasts),
+	register char *s _AND
+	register const char *delim _AND
+	char **lasts)
+{
+	return __strtok_r (s, delim, lasts, 1);
+}
diff --git a/morse.c b/morse.c
index df67e3d..8598fcc 100644
--- a/morse.c
+++ b/morse.c
@@ -1,381 +1,543 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      morse.c
- *
- * Purpose:   	Generate audio for morse code.
- *		
- * Description:	
- *
- * Reference:	
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <sys/time.h>
-
-#if __WIN32__
-#include <windows.h>
-#else
-#include <sys/termios.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#endif
-
-#include "direwolf.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "ptt.h"
-
-
-#define WPM 10
-#define TIME_UNITS_TO_MS(tu,wpm)  (((tu)*1200)/(wpm))
-
-
-// TODO : should be in .h file.
-
-/*
- * Delay from PTT on to start of first character.
- * Currently the only anticipated use for this is 
- * APRStt responses.  In this case, we want an adequate
- * delay for someone to press the # button, release 
- * the PTT button, and start listening for a response.
- */
-#define MORSE_TXDELAY_MS  1500
-
-/*
- * Delay from end of last character to PTT off.
- * Avoid chopping off the last element.
- */
-#define MORSE_TXTAIL_MS   200
-
-
-static const struct morse_s {
-	char ch;
-	char enc[7];
-} morse[] = {
-	{ 'A', ".-" },
-	{ 'B', "-..." },
-	{ 'C', "-.-." },
-	{ 'D', "-.." },
-	{ 'E', "." },
-	{ 'F', "..-." },
-	{ 'G', "--." },
-	{ 'H', "...." },
-	{ 'I', "." },
-	{ 'J', ".---" },
-	{ 'K', "-.-" },
-	{ 'L', ".-.." },
-	{ 'M', "--" },
-	{ 'N', "-." },
-	{ 'O', "---" },
-	{ 'P', ".--." },
-	{ 'Q', "--.-" },
-	{ 'R', ".-." },
-	{ 'S', "..." },
-	{ 'T', "-" },
-	{ 'U', "..-" },
-	{ 'V', "...-" },
-	{ 'W', ".--" },
-	{ 'X', "-..-" },
-	{ 'Y', "-.--" },
-	{ 'Z', "--.." },
-	{ '1', ".----" },
-	{ '2', "..---" },
-	{ '3', "...--" },
-	{ '4', "....-" },
-	{ '5', "....." },
-	{ '6', "-...." },
-	{ '7', "--..." },
-	{ '8', "---.." },
-	{ '9', "----." },
-	{ '0', "-----" },
-	{ '-', "-...-" },
-	{ '.', ".-.-.-" },
-	{ ',', "--..--" },
-	{ '?', "..--.." },
-	{ '/', "-..-." }
-};
-
-#define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s))
-
-static void morse_tone (int tu);
-static void morse_quiet (int tu);
-static int morse_lookup (int ch);
-static int morse_units_ch (int ch);
-static int morse_units_str (char *str);
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        morse_send
- *
- * Purpose:    	Given a string, generate appropriate lengths of
- *		tone and silence.
- *
- * Inputs:	chan	- Radio channel number.
- *		str	- Character string to send.
- *		wpm	- Speed in words per minute.
- *		txdelay	- Delay (ms) from PTT to first character.
- *		txtail	- Delay (ms) from last character to PTT off.	
- *		
- *
- * Returns:	Total number of milliseconds to activate PTT.
- *		This includes delays before the first character
- *		and after the last to avoid chopping off part of it.
- *
- * Description:	xmit_thread calls this instead of the usual hdlc_send
- *		when we have a special packet that means send morse
- *		code.
- *
- *--------------------------------------------------------------------*/
-
-int morse_send (int chan, char *str, int wpm, int txdelay, int txtail)
-{
-	int time_units;
-	char *p;
-
-	time_units = 0;
-	for (p = str; *p != '\0'; p++) {
-	  int i;
-
-	  i = morse_lookup (*p);
-	  if (i >= 0) {
-	    const char *e;
-
-	    for (e = morse[i].enc; *e != '\0'; e++) {
-	      if (*e == '.') {
-	        morse_tone (1);
-	        time_units++;
-	      }
-	      else {
-	        morse_tone (3);
-	        time_units += 3;
-	      }
-	      if (e[1] != '\0') {
-	        morse_quiet (1);
-	        time_units++;
-	      }
-	    }
-	  }
-	  else {
-	    morse_quiet (1);
-	    time_units++;
-	  }
-	  if (p[1] != '\0') {
-	    morse_quiet (3);
-	    time_units += 3;
-	  }
-	}
-
-	if (time_units != morse_units_str(str)) {
-	  dw_printf ("morse: Internal error.  Inconsistent length, %d vs. %d calculated.\n", 
-		time_units, morse_units_str(str));
-	}
-
-
-	return (txdelay +
-		TIME_UNITS_TO_MS(time_units, wpm) +
-		txtail);
-
-}  /* end morse_send */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        morse_tone
- *
- * Purpose:    	Generate tone for specified number of time units.
- *
- * Inputs:	tu	- Number of time units.
- *
- *--------------------------------------------------------------------*/
-
-static void morse_tone (int tu) {
-	int num_cycles;
-	int n;
-
-	for (n=0; n<tu; n++) {
-	  dw_printf ("#");
-	}
-
-	
-
-} /* end morse_tone */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        morse_quiet
- *
- * Purpose:    	Generate silence for specified number of time units.
- *
- * Inputs:	tu	- Number of time units.
- *
- *--------------------------------------------------------------------*/
-
-static void morse_quiet (int tu) {
-	int n;
-
-	for (n=0; n<tu; n++) {
-	  dw_printf (".");
-	}
-
-} /* end morse_quiet */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        morse_lookup
- *
- * Purpose:    	Given a character, find index in table above.
- *
- * Inputs:	ch
- *
- * Returns:	Index into table above or -1 if not found.
- *		Notice that space is not in the table.
- *		Any unusual character, that is not in the table, 
- *		ends up being treated like space.
- *
- *--------------------------------------------------------------------*/
-
-static int morse_lookup (int ch)
-{
-	int i;
-
-	if (islower(ch)) {
-	  ch = toupper(ch);
-	}
-
-	for (i=0; i<NUM_MORSE; i++) {
-	  if (ch == morse[i].ch) {
-	    return (i);
-	  }
-	}
-	return (-1);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        morse_units_ch
- *
- * Purpose:    	Find number of time units for a character.
- *
- * Inputs:	ch
- *
- * Returns:	1 for E (.)
- *		3 for T (-)
- *		3 for I.= (..)
- *		etc.
- *	
- *		The one unexpected result is 1 for space.  Why not 7?
- *		When a space appears between two other characters,
- *		we already have 3 before and after so only 1 more is needed.
- *
- *--------------------------------------------------------------------*/
-
-static int morse_units_ch (int ch)
-{
-	int i;
-	int len;
-	int k;
-	int units;
-
-	i = morse_lookup (ch);
-	if (i < 0) {
-	  return (1);	/* space or any invalid character */
-	}
-
-	
-	len = strlen(morse[i].enc);
-	units = len - 1;
-
-	for (k = 0; k < len; k++) {
-	  switch (morse[i].enc[k]) {
-	    case '.':  units++; break;
-	    case '-':  units += 3; break;
-	    default:  dw_printf ("ERROR: morse_units_ch: should not be here.\n"); break;
-	  }
-	}
-	
-	return (units); 
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        morse_units_str
- *
- * Purpose:    	Find number of time units for a string of characters.
- *
- * Inputs:	str
- *
- * Returns:	1 for E 	
- *		5 for EE	(1 + 3 + 1)
- *		9 for E E	(1 + 7 + 1)
- *		etc.
- *
- *--------------------------------------------------------------------*/
-
-static int morse_units_str (char *str)
-{
-	int i;
-	int len;
-	int k;
-	int units;
-
-	len = strlen(str);
-	units = (len - 1) * 3;
-
-	for (k = 0; k < len; k++) {
-	  units += morse_units_ch(str[k]);
-	}
-	
-	return (units); 
-}
-
-
-int main (int argc, char *argv[]) {
-
-	dw_printf ("CQ DX\n");
-	morse_send (0, "CQ DX", 10, 10, 10);
-	dw_printf ("\n\n");
-
-	dw_printf ("wb2osz/9\n");
-	morse_send (0, "wb2osz/9", 10, 10, 10);
-	dw_printf ("\n\n");
-
-} 
-
-
-/* end morse.c */
-
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      morse.c
+ *
+ * Purpose:   	Generate audio for morse code.
+ *		
+ * Description:	
+ *
+ * Reference:	
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <math.h>
+
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "ptt.h"
+#include "gen_tone.h"		/* for gen_tone_put_sample */
+#include "morse.h"
+
+/*
+ * Might get ambitious and make this adjustable some day.
+ * Good enough for now.
+ */
+
+#define MORSE_TONE 800
+
+#define TIME_UNITS_TO_MS(tu,wpm)  (((tu)*1200.0)/(wpm))
+
+
+
+static const struct morse_s {
+	char ch;
+	char enc[8];	/* $ has 7 elements */
+} morse[] = {
+	{ 'A', ".-" },
+	{ 'B', "-..." },
+	{ 'C', "-.-." },
+	{ 'D', "-.." },
+	{ 'E', "." },
+	{ 'F', "..-." },
+	{ 'G', "--." },
+	{ 'H', "...." },
+	{ 'I', "." },
+	{ 'J', ".---" },
+	{ 'K', "-.-" },
+	{ 'L', ".-.." },
+	{ 'M', "--" },
+	{ 'N', "-." },
+	{ 'O', "---" },
+	{ 'P', ".--." },
+	{ 'Q', "--.-" },
+	{ 'R', ".-." },
+	{ 'S', "..." },
+	{ 'T', "-" },
+	{ 'U', "..-" },
+	{ 'V', "...-" },
+	{ 'W', ".--" },
+	{ 'X', "-..-" },
+	{ 'Y', "-.--" },
+	{ 'Z', "--.." },
+	{ '1', ".----" },
+	{ '2', "..---" },
+	{ '3', "...--" },
+	{ '4', "....-" },
+	{ '5', "....." },
+	{ '6', "-...." },
+	{ '7', "--..." },
+	{ '8', "---.." },
+	{ '9', "----." },
+	{ '0', "-----" },
+	{ '-', "-...-" },
+	{ '.', ".-.-.-" },
+	{ ',', "--..--" },
+	{ '?', "..--.." },
+	{ '/', "-..-." },
+
+	{ '=', "-...-" },	/* from ARRL */
+	{ '-', "-....-" },
+	{ ')', "-.--.-" },	/* does not distinguish open/close */
+	{ ':', "---..." },
+	{ ';', "-.-.-." },
+	{ '"', ".-..-." },
+	{ '\'', ".----." },
+	{ '$', "...-..-" },
+
+	{ '!', "-.-.--" },	/* more from wikipedia */
+	{ '(', "-.--." },
+	{ '&', ".-..." },
+	{ '+', ".-.-." },
+	{ '_', "..--.-" },
+	{ '@', ".--.-." },
+
+};
+
+#define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s))
+
+static void morse_tone (int chan, int tu, int wpm);
+static void morse_quiet (int chan, int tu, int wpm);
+static void morse_quiet_ms (int chan, int ms);
+static int morse_lookup (int ch);
+static int morse_units_ch (int ch);
+static int morse_units_str (char *str);
+
+
+
+/*
+ *  Properties of the digitized sound stream.
+ */
+
+static struct audio_s *save_audio_config_p;
+
+
+/* Constants after initialization. */
+
+#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
+
+static short sine_table[256];
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        morse_init
+ *
+ * Purpose:     Initialize for tone generation.
+ *
+ * Inputs:      audio_config_p		- Pointer to audio configuration structure.
+ *
+ *				The fields we care about are:
+ *
+ *					samples_per_sec
+ *
+ *		amp		- Signal amplitude on scale of 0 .. 100.
+ *
+ *				  100 will produce maximum amplitude of +-32k samples. 
+ *
+ * Returns:     0 for success.
+ *              -1 for failure.
+ *
+ * Description:	 Precompute a sine wave table.
+ *
+ *----------------------------------------------------------------*/
+
+
+int morse_init (struct audio_s *audio_config_p, int amp)  
+{
+	int j;
+	
+/* 
+ * Save away modem parameters for later use. 
+ */
+
+	save_audio_config_p = audio_config_p;
+	
+        for (j=0; j<256; j++) {
+	  double a;
+	  int s;
+
+	  a = ((double)(j) / 256.0) * (2 * M_PI);
+	  s = (int) (sin(a) * 32767.0 * amp / 100.0);
+
+	  /* 16 bit sound sample is in range of -32768 .. +32767. */
+	  assert (s >= -32768 && s <= 32767);
+	  sine_table[j] = s;
+        }
+
+	return (0);
+
+} /* end morse_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_send
+ *
+ * Purpose:    	Given a string, generate appropriate lengths of
+ *		tone and silence.
+ *
+ * Inputs:	chan	- Radio channel number.
+ *		str	- Character string to send.
+ *		wpm	- Speed in words per minute.
+ *		txdelay	- Delay (ms) from PTT to first character.
+ *		txtail	- Delay (ms) from last character to PTT off.	
+ *		
+ *
+ * Returns:	Total number of milliseconds to activate PTT.
+ *		This includes delays before the first character
+ *		and after the last to avoid chopping off part of it.
+ *
+ * Description:	xmit_thread calls this instead of the usual hdlc_send
+ *		when we have a special packet that means send morse
+ *		code.
+ *
+ *--------------------------------------------------------------------*/
+
+int morse_send (int chan, char *str, int wpm, int txdelay, int txtail)
+{
+	int time_units;
+	char *p;
+
+	
+	time_units = 0;
+
+	morse_quiet_ms (chan, txdelay);
+
+	for (p = str; *p != '\0'; p++) {
+	  int i;
+
+	  i = morse_lookup (*p);
+	  if (i >= 0) {
+	    const char *e;
+
+	    for (e = morse[i].enc; *e != '\0'; e++) {
+	      if (*e == '.') {
+	        morse_tone (chan,1,wpm);
+	        time_units++;
+	      }
+	      else {
+	        morse_tone (chan,3,wpm);
+	        time_units += 3;
+	      }
+	      if (e[1] != '\0') {
+	        morse_quiet (chan,1,wpm);
+	        time_units++;
+	      }
+	    }
+	  }
+	  else {
+	    morse_quiet (chan,1,wpm);
+	    time_units++;
+	  }
+	  if (p[1] != '\0') {
+	    morse_quiet (chan,3,wpm);
+	    time_units += 3;
+	  }
+	}
+
+	morse_quiet_ms (chan, txtail);
+
+	if (time_units != morse_units_str(str)) {
+	  dw_printf ("morse: Internal error.  Inconsistent length, %d vs. %d calculated.\n", 
+		time_units, morse_units_str(str));
+	}
+
+	return (txdelay +
+		(int) (TIME_UNITS_TO_MS(time_units, wpm) + 0.5) +
+		txtail);
+
+}  /* end morse_send */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_tone
+ *
+ * Purpose:    	Generate tone for specified number of time units.
+ *
+ * Inputs:	chan	- Radio channel.
+ *		tu	- Number of time units.  Should be 1 or 3.
+ *		wpm	- Speed in WPM.
+ *
+ *--------------------------------------------------------------------*/
+
+static void morse_tone (int chan, int tu, int wpm) {
+
+#if MTEST1
+	int n;
+	for (n=0; n<tu; n++) {
+	  dw_printf ("#");
+	}
+#else
+
+	int a = ACHAN2ADEV(chan);	/* device for channel. */
+	int sam;
+	int nsamples;
+	int j;
+	unsigned int tone_phase; // Phase accumulator for tone generation.
+				 // Upper bits are used as index into sine table.
+
+	int f1_change_per_sample;  // How much to advance phase for each audio sample.
+
+	assert (save_audio_config_p->achan[chan].valid);
+
+	tone_phase = 0;
+
+	f1_change_per_sample = (int) (((double)MORSE_TONE * TICKS_PER_CYCLE / (double)save_audio_config_p->adev[a].samples_per_sec ) + 0.5);
+
+	nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5);
+
+	for (j=0; j<nsamples; j++)  {
+
+	  tone_phase += f1_change_per_sample;
+          sam = sine_table[(tone_phase >> 24) & 0xff];
+	  gen_tone_put_sample (chan, a, sam);
+
+        };
+
+#endif
+	
+
+} /* end morse_tone */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_quiet
+ *
+ * Purpose:    	Generate silence for specified number of time units.
+ *
+ * Inputs:	chan	- Radio channel.
+ *		tu	- Number of time units.
+ *		wpm	- Speed in WPM.
+ *
+ *--------------------------------------------------------------------*/
+
+static void morse_quiet (int chan, int tu, int wpm) {
+
+
+#if MTEST1
+	int n;
+	for (n=0; n<tu; n++) {
+	  dw_printf (".");
+	}
+#else
+	int a = ACHAN2ADEV(chan);	/* device for channel. */
+	int sam = 0;
+	int nsamples;
+	int j;
+
+	assert (save_audio_config_p->achan[chan].valid);
+
+	nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5);
+
+	for (j=0; j<nsamples; j++)  {
+
+	  gen_tone_put_sample (chan, a, sam);
+
+        };
+#endif
+
+} /* end morse_quiet */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_quiet
+ *
+ * Purpose:    	Generate silence for specified number of milliseconds.
+ *		This is used for the txdelay and txtail times.
+ *
+ * Inputs:	chan	- Radio channel.
+ *		tu	- Number of time units.
+ *
+ *--------------------------------------------------------------------*/
+
+static void morse_quiet_ms (int chan, int ms) {
+
+#if MTEST1
+#else
+	int a = ACHAN2ADEV(chan);	/* device for channel. */
+	int sam = 0;
+	int nsamples;
+	int j;
+
+	assert (save_audio_config_p->achan[chan].valid);
+
+	nsamples = (int) ((ms * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5);
+
+	for (j=0; j<nsamples; j++)  {
+
+	  gen_tone_put_sample (chan, a, sam);
+
+        };
+
+#endif
+
+} /* end morse_quiet_ms */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_lookup
+ *
+ * Purpose:    	Given a character, find index in table above.
+ *
+ * Inputs:	ch
+ *
+ * Returns:	Index into table above or -1 if not found.
+ *		Notice that space is not in the table.
+ *		Any unusual character, that is not in the table, 
+ *		ends up being treated like space.
+ *
+ *--------------------------------------------------------------------*/
+
+static int morse_lookup (int ch)
+{
+	int i;
+
+	if (islower(ch)) {
+	  ch = toupper(ch);
+	}
+
+	for (i=0; i<NUM_MORSE; i++) {
+	  if (ch == morse[i].ch) {
+	    return (i);
+	  }
+	}
+	return (-1);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_units_ch
+ *
+ * Purpose:    	Find number of time units for a character.
+ *
+ * Inputs:	ch
+ *
+ * Returns:	1 for E (.)
+ *		3 for T (-)
+ *		3 for I.= (..)
+ *		etc.
+ *	
+ *		The one unexpected result is 1 for space.  Why not 7?
+ *		When a space appears between two other characters,
+ *		we already have 3 before and after so only 1 more is needed.
+ *
+ *--------------------------------------------------------------------*/
+
+static int morse_units_ch (int ch)
+{
+	int i;
+	int len;
+	int k;
+	int units;
+
+	i = morse_lookup (ch);
+	if (i < 0) {
+	  return (1);	/* space or any invalid character */
+	}
+
+	
+	len = strlen(morse[i].enc);
+	units = len - 1;
+
+	for (k = 0; k < len; k++) {
+	  switch (morse[i].enc[k]) {
+	    case '.':  units++; break;
+	    case '-':  units += 3; break;
+	    default:  dw_printf ("ERROR: morse_units_ch: should not be here.\n"); break;
+	  }
+	}
+	
+	return (units); 
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        morse_units_str
+ *
+ * Purpose:    	Find number of time units for a string of characters.
+ *
+ * Inputs:	str
+ *
+ * Returns:	1 for E 	
+ *		5 for EE	(1 + 3 + 1)
+ *		9 for E E	(1 + 7 + 1)
+ *		etc.
+ *
+ *--------------------------------------------------------------------*/
+
+static int morse_units_str (char *str)
+{
+	//int i;
+	int len;
+	int k;
+	int units;
+
+	len = strlen(str);
+	units = (len - 1) * 3;
+
+	for (k = 0; k < len; k++) {
+	  units += morse_units_ch(str[k]);
+	}
+	
+	return (units); 
+}
+
+
+#if MTEST1
+
+int main (int argc, char *argv[]) {
+
+	dw_printf ("CQ DX\n");
+	morse_send (0, "CQ DX", 10, 10, 10);
+	dw_printf ("\n\n");
+
+	dw_printf ("wb2osz/9\n");
+	morse_send (0, "wb2osz/9", 10, 10, 10);
+	dw_printf ("\n\n");
+
+} 
+
+#endif
+
+
+/* end morse.c */
+
+
+
diff --git a/morse.h b/morse.h
new file mode 100644
index 0000000..e34dd7b
--- /dev/null
+++ b/morse.h
@@ -0,0 +1,8 @@
+/* morse.h */
+
+int morse_init (struct audio_s *audio_config_p, int amp) ;
+
+int morse_send (int chan, char *str, int wpm, int txdelay, int txtail);
+
+#define MORSE_DEFAULT_WPM 10
+
diff --git a/multi_modem.c b/multi_modem.c
index 2a92a07..f9d57fd 100644
--- a/multi_modem.c
+++ b/multi_modem.c
@@ -1,673 +1,660 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Name:	multi_modem.c
- *
- * Purpose:	Use multiple modems in parallel to increase chances
- *		of decoding less than ideal signals.
- *
- * Description:	The initial motivation was for HF SSB where mistuning
- *		causes a shift in the audio frequencies.  Here, we can
- * 		have multiple modems tuned to staggered pairs of tones
- *		in hopes that one will be close enough.
- *
- *		The overall structure opens the door to other approaches
- *		as well.  For VHF FM, the tones should always have the
- *		right frequencies but we might want to tinker with other
- *		modem parameters instead of using a single compromise.
- *
- * Originally:	The the interface application is in 3 places:
- *
- *		(a) Main program (direwolf.c or atest.c) calls 
- *		    demod_init to set up modem properties and
- *		    hdlc_rec_init for the HDLC decoders.
- *
- *		(b) demod_process_sample is called for each audio sample
- *		    from the input audio stream.
- *
- *	   	(c) When a valid AX.25 frame is found, process_rec_frame,
- *		    provided by the application, in direwolf.c or atest.c,
- *		    is called.  Normally this comes from hdlc_rec.c but
- *		    there are a couple other special cases to consider.
- *		    It can be called from hdlc_rec2.c if it took a long 
- *  		    time to "fix" corrupted bits.  aprs_tt.c constructs 
- * 		    a fake packet when a touch tone message is received.
- *
- * New in version 0.9:
- *
- *		Put an extra layer in between which potentially uses
- *		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
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdio.h>
-#include <sys/unistd.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "multi_modem.h"
-#include "demod.h"
-#include "hdlc_rec.h"
-#include "hdlc_rec2.h"
-#include "dlq.h"
-
-
-// Properties of the radio channels.
-
-static struct audio_s          *save_audio_config_p;
-
-
-// Candidates for further processing.
-
-static struct {
-
-	packet_t packet_p;
-	alevel_t alevel;
-	retry_t retries;
-	int age;
-	unsigned int crc;
-	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 crc_t crc_queue_of_last_to_app[MAX_CHANS];
-
-#define PROCESS_AFTER_BITS 2
-
-static int process_age[MAX_CHANS];
-
-static void pick_best_candidate (int chan);
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	multi_modem_init
- * 
- * Purpose:	Called at application start up to initialize appropriate
- *		modems and HDLC decoders.
- *
- * Input:	Modem properties structure as filled in from the configuration file.
- *		
- * Outputs:	
- *		
- * Description:	Called once at application startup time.
- *
- *------------------------------------------------------------------------------*/
-
-void multi_modem_init (struct audio_s *pa) 
-{
-	int chan;
-
-
-/*
- * Save audio configuration for later use.
- */
-
-	save_audio_config_p = pa;
-
-	memset (candidate, 0, sizeof(candidate));
-
-	demod_init (save_audio_config_p);
-	hdlc_rec_init (save_audio_config_p);
-
-	for (chan=0; chan<MAX_CHANS; chan++) {
-	  if (save_audio_config_p->achan[chan].valid) { 
-	    if (save_audio_config_p->achan[chan].baud <= 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__);
-	      save_audio_config_p->achan[chan].baud = DEFAULT_BAUD;
-	    }
-	    process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].baud;
-	    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;
-}
-
-/*------------------------------------------------------------------------------
- *
- * Name:	multi_modem_process_sample
- * 
- * Purpose:	Feed the sample into the proper modem(s) for the channel.	
- *
- * Inputs:	chan	- Radio channel number
- *
- *		audio_sample 
- *
- * Description:	In earlier versions we always had a one-to-one mapping with
- *		demodulators and HDLC decoders.
- *		This was added so we could have multiple modems running in
- *		parallel with different mark/space tones to compensate for 
- *		mistuning of HF SSB signals.
- * 		It was also possible to run multiple filters, for the same
- *		tones, in parallel (e.g. ABC).
- *
- * Version 1.2:	Let's try something new for an experiment.
- *		We will have a single mark/space demodulator but multiple
- *		slicers, using different levels, each with its own HDLC decoder.
- *		We now have a separate variable, num_demod, which could be 1
- *		while num_subchan is larger.
- *		
- *------------------------------------------------------------------------------*/
-
-
-__attribute__((hot))
-void multi_modem_process_sample (int chan, int audio_sample) 
-{
-	int d;
-	int subchan;
-
-	/* Formerly one loop. */
-	/* 1.2: We can feed one demodulator but end up with multiple outputs. */
-
-
-	for (d = 0; d < save_audio_config_p->achan[chan].num_demod; d++) {
-
-	  demod_process_sample(chan, d, audio_sample);
-	}
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-
-	  if (candidate[chan][subchan].packet_p != NULL) {
-	    candidate[chan][subchan].age++;
-	    if (candidate[chan][subchan].age > process_age[chan]) {
-	      pick_best_candidate (chan);
-	    }
-	  }  
-	}}
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        multi_modem_process_rec_frame
- *
- * Purpose:     This is called when we receive a frame with a valid 
- *		FCS and acceptable size.
- *
- * Inputs:	chan	- Audio channel number, 0 or 1.
- *		subchan	- Which modem/decoder found it.
- *		fbuf	- Pointer to first byte in HDLC frame.
- *		flen	- Number of bytes excluding the FCS.
- *		alevel	- Audio level, range of 0 - 100.
- *				(Special case, use negative to skip
- *				 display of audio level line.
- *				 Use -2 to indicate DTMF message.)
- *		retries	- Level of bit correction used.
- *
- *
- * Description:	Add to list of candidates.  Best one will be picked later.
- *
- *--------------------------------------------------------------------*/
-
-/*
- 
-	It gets a little more complicated when we try fixing frames
-	with imperfect CRCs.   
-
-	Changing of adjacent bits is quick and done immediately.  These
-	all come in at nearly the same time.  The processing of two 
-	separated bits can take a long time and is handled in the 
-	background by another thread.  These could come in seconds later.
-
-	We need a way to remove duplicates.  I think these are the
-	two cases we need to consider.
-
-	(1) Same result as earlier no error or adjacent bit errors.
-
-		____||||_
-		0.0: ptr=00000000
-		0.1: ptr=00000000
-		0.2: ptr=00000000
-		0.3: ptr=00000000
-		0.4: ptr=009E5540, retry=0, age=295, crc=9458, score=5024
-		0.5: ptr=0082F008, retry=0, age=294, crc=9458, score=5026  ***
-		0.6: ptr=009CE560, retry=0, age=293, crc=9458, score=5026
-		0.7: ptr=009CEE08, retry=0, age=293, crc=9458, score=5024
-		0.8: ptr=00000000
-
-		___._____
-		0.0: ptr=00000000
-		0.1: ptr=00000000
-		0.2: ptr=00000000
-		0.3: ptr=009E5540, retry=4, age=295, crc=9458, score=1000  ***
-		0.4: ptr=00000000
-		0.5: ptr=00000000
-		0.6: ptr=00000000
-		0.7: ptr=00000000
-		0.8: ptr=00000000
-
-	(2) Only results from adjusting two non-adjacent bits.
-
-
-		||||||||_
-		0.0: ptr=022EBA08, retry=0, age=289, crc=5acd, score=5042
-		0.1: ptr=022EA8B8, retry=0, age=290, crc=5acd, score=5048
-		0.2: ptr=022EB160, retry=0, age=290, crc=5acd, score=5052
-		0.3: ptr=05BD0048, retry=0, age=291, crc=5acd, score=5054  ***
-		0.4: ptr=04FE0048, retry=0, age=292, crc=5acd, score=5054
-		0.5: ptr=05E10048, retry=0, age=294, crc=5acd, score=5052
-		0.6: ptr=053D0048, retry=0, age=294, crc=5acd, score=5048
-		0.7: ptr=02375558, retry=0, age=295, crc=5acd, score=5042
-		0.8: ptr=00000000
-
-		_______._
-		0.0: ptr=00000000
-		0.1: ptr=00000000
-		0.2: ptr=00000000
-		0.3: ptr=00000000
-		0.4: ptr=00000000
-		0.5: ptr=00000000
-		0.6: ptr=00000000
-		0.7: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000  ***
-		0.8: ptr=00000000
-
-		________.
-		0.0: ptr=00000000
-		0.1: ptr=00000000
-		0.2: ptr=00000000
-		0.3: ptr=00000000
-		0.4: ptr=00000000
-		0.5: ptr=00000000
-		0.6: ptr=00000000
-		0.7: ptr=00000000
-		0.8: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000  ***
-
-
-	These can both be covered by keepin the last CRC and dropping 
-	duplicates.  In theory we could get another frame in between with
-	a slow computer so the complete solution would be to remember more
-	than one.
-*/
-
-void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries)  
-{	
-	packet_t pp;
-
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-	pp = ax25_from_frame (fbuf, flen, alevel);
-
-	if (pp == NULL) {
-	  return;	/* oops!  why would it fail? */
-	}
-
-
-/*
- * If single modem/deocder, push it thru and forget about all this foolishness.
- */
-	if (save_audio_config_p->achan[chan].num_subchan == 1) {
-	  dlq_append (DLQ_REC_FRAME, chan, subchan, pp, alevel, retries, "");
-	  return;
-	}
-
-/*
- * Special handing for two separated bit errors.
- * See description earlier.
- *
- * Not combined with others to find the best score.
- * Either pass it along or drop if duplicate.
- */
-
-	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)save_audio_config_p->achan[chan].num_subchan);
-	  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=  , dropped =%d\n", 
-		spectrum, chan, subchan, pp, (int)retries, mycrc,dropped);
-#endif	   
-	  if (dropped) {
-	     /* Same as last one.  Drop it. */
-	     ax25_delete (pp);
-#if DEBUG
-	     dw_printf ("Drop duplicate.\n");
-#endif
-	     return;
-	   }
-
-#if DEBUG
-	  dw_printf ("Send the best one along.\n");
-#endif
-	  dlq_append (DLQ_REC_FRAME, chan, subchan, pp, alevel, retries, spectrum);
-	  if (crc_queue_append(mycrc, chan) > MAX_STORED_CRC)
-	    crc_queue_remove(chan);
-	  return;
-	}
-
-
-/*
- * Otherwise, save them up for a few bit times so we can pick the best.
- */
-	if (candidate[chan][subchan].packet_p != NULL) {
-	  /* Oops!  Didn't expect it to be there. */
-	  ax25_delete (candidate[chan][subchan].packet_p);
-	  candidate[chan][subchan].packet_p = NULL;
-	}
-
-	candidate[chan][subchan].packet_p = pp;
-	candidate[chan][subchan].alevel = alevel;
-	candidate[chan][subchan].retries = retries;
-	candidate[chan][subchan].age = 0;
-	candidate[chan][subchan].crc = ax25_m_m_crc(pp);
-}
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        pick_best_candidate
- *
- * Purpose:     This is called when we have one or more candidates
- *		available for a certain amount of time.
- *
- * Description:	Pick the best one and send it up to the application.
- *		Discard the others.
- *		
- * Rules:	We prefer one received perfectly but will settle for
- *		one where some bits had to be flipped to get a good CRC.
- *
- *--------------------------------------------------------------------*/
-
-
-static void pick_best_candidate (int chan) 
-{
-	int subchan;
-	int best_subchan, best_score;
-	char spectrum[MAX_SUBCHANS+1];
-	int k;
-
-	memset (spectrum, 0, sizeof(spectrum));
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-
-	  /* Build the spectrum display. */
-
-	  if (candidate[chan][subchan].packet_p == NULL) {
-	    spectrum[subchan] = '_';
-	  }
-	  else if (candidate[chan][subchan].retries == RETRY_NONE) {
-	    spectrum[subchan] = '|';
-	  }
-	  else if (candidate[chan][subchan].retries == RETRY_SWAP_SINGLE) {
-	    spectrum[subchan] = ':';
-	  }
-	  else  {
-	    spectrum[subchan] = '.';
-	  }
-
-	  /* Begining score depends on effort to get a valid frame CRC. */
-
-	  candidate[chan][subchan].score = RETRY_MAX * 1000 - ((int)candidate[chan][subchan].retries * 1000);
-
-	  /* Bump it up slightly if others nearby have the same CRC. */
-	  
-	  for (k = 0; k < save_audio_config_p->achan[chan].num_subchan; k++) {
-	    if (k != subchan && candidate[chan][k].packet_p != NULL) {
-	      if (candidate[chan][k].crc == candidate[chan][subchan].crc) {
-	        candidate[chan][subchan].score += (MAX_SUBCHANS+1) - abs(subchan-k);
-	      }
-	    }
-	  }
-	}
-  
-	best_subchan = 0;
-	best_score = 0;
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-	  if (candidate[chan][subchan].packet_p != NULL) {
-	    if (candidate[chan][subchan].score > best_score) {
-	       best_score = candidate[chan][subchan].score;
-	       best_subchan = subchan;
-	    }
-	  }
-	}
-
-#if DEBUG	
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("\n%s\n", spectrum);
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-
-	  if (candidate[chan][subchan].packet_p == NULL) {
-	    dw_printf ("%d.%d: ptr=%p\n", chan, subchan,
-		candidate[chan][subchan].packet_p);
-	  }
-	  else {
-	    dw_printf ("%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d  %s\n", chan, subchan,
-		candidate[chan][subchan].packet_p, 
-		(int)(candidate[chan][subchan].retries), 
-		candidate[chan][subchan].age,
-		candidate[chan][subchan].crc,
-		candidate[chan][subchan].score,
-		subchan == best_subchan ? "***" : "");
-	  }
-	}
-#endif
-
-/*
- * send the best one along.
- */
-
-#if 1		// v1.2 dev F, Reverse original order.  Delete rejects THEN process the best one.
-
-
-	/* Delete those not chosen. */
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-	  if (subchan != best_subchan && candidate[chan][subchan].packet_p != NULL) {
-	    ax25_delete (candidate[chan][subchan].packet_p);
-	    candidate[chan][subchan].packet_p = NULL;
-	  }
-	}
-
-	/* Pass along one. */
-
-	dlq_append (DLQ_REC_FRAME, chan, best_subchan, 
-		candidate[chan][best_subchan].packet_p, 
-		candidate[chan][best_subchan].alevel, 
-		(int)(candidate[chan][best_subchan].retries), 
-		spectrum);
-	if (crc_queue_append(candidate[chan][best_subchan].crc, chan) > MAX_STORED_CRC)
-	    crc_queue_remove(chan);
-
-	/* Someone else owns it now and will delete it later. */
-	candidate[chan][best_subchan].packet_p = NULL;
-
-	/* Clear in preparation for next time. */
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-
-	  candidate[chan][subchan].alevel.rec = 0;
-	  candidate[chan][subchan].alevel.mark = 0;
-	  candidate[chan][subchan].alevel.space = 0;
-
-	  candidate[chan][subchan].retries = 0;
-	  candidate[chan][subchan].age = 0;
-	  candidate[chan][subchan].crc = 0;
-	}
-#else
-
-	dlq_append (DLQ_REC_FRAME, chan, best_subchan, 
-		candidate[chan][best_subchan].packet_p, 
-		candidate[chan][best_subchan].alevel, 
-		(int)(candidate[chan][best_subchan].retries), 
-		spectrum);
-	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;
-
-	/* Clear out in preparation for next time. */
-
-	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
-	  if (candidate[chan][subchan].packet_p != NULL) {
-	    ax25_delete (candidate[chan][subchan].packet_p);
-	    candidate[chan][subchan].packet_p = NULL;
-	  }
-
-	  candidate[chan][subchan].alevel.rec = 0;
-	  candidate[chan][subchan].alevel.mark = 0;
-	  candidate[chan][subchan].alevel.space = 0;
-
-	  candidate[chan][subchan].retries = 0;
-	  candidate[chan][subchan].age = 0;
-	  candidate[chan][subchan].crc = 0;
-	}
-
-#endif
-
-}
-
-
-/* end multi_modem.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:	multi_modem.c
+ *
+ * Purpose:	Use multiple modems in parallel to increase chances
+ *		of decoding less than ideal signals.
+ *
+ * Description:	The initial motivation was for HF SSB where mistuning
+ *		causes a shift in the audio frequencies.  Here, we can
+ * 		have multiple modems tuned to staggered pairs of tones
+ *		in hopes that one will be close enough.
+ *
+ *		The overall structure opens the door to other approaches
+ *		as well.  For VHF FM, the tones should always have the
+ *		right frequencies but we might want to tinker with other
+ *		modem parameters instead of using a single compromise.
+ *
+ * Originally:	The the interface application is in 3 places:
+ *
+ *		(a) Main program (direwolf.c or atest.c) calls 
+ *		    demod_init to set up modem properties and
+ *		    hdlc_rec_init for the HDLC decoders.
+ *
+ *		(b) demod_process_sample is called for each audio sample
+ *		    from the input audio stream.
+ *
+ *	   	(c) When a valid AX.25 frame is found, process_rec_frame,
+ *		    provided by the application, in direwolf.c or atest.c,
+ *		    is called.  Normally this comes from hdlc_rec.c but
+ *		    there are a couple other special cases to consider.
+ *		    It can be called from hdlc_rec2.c if it took a long 
+ *  		    time to "fix" corrupted bits.  aprs_tt.c constructs 
+ * 		    a fake packet when a touch tone message is received.
+ *
+ * New in version 0.9:
+ *
+ *		Put an extra layer in between which potentially uses
+ *		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
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <sys/unistd.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "multi_modem.h"
+#include "demod.h"
+#include "hdlc_rec.h"
+#include "hdlc_rec2.h"
+#include "dlq.h"
+
+
+// Properties of the radio channels.
+
+static struct audio_s          *save_audio_config_p;
+
+
+// Candidates for further processing.
+
+static struct {
+	packet_t packet_p;
+	alevel_t alevel;
+	retry_t retries;
+	int age;
+	unsigned int crc;
+	int score;
+} candidate[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
+
+
+
+#define PROCESS_AFTER_BITS 2
+
+static int process_age[MAX_CHANS];
+
+static void pick_best_candidate (int chan);
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	multi_modem_init
+ * 
+ * Purpose:	Called at application start up to initialize appropriate
+ *		modems and HDLC decoders.
+ *
+ * Input:	Modem properties structure as filled in from the configuration file.
+ *		
+ * Outputs:	
+ *		
+ * Description:	Called once at application startup time.
+ *
+ *------------------------------------------------------------------------------*/
+
+void multi_modem_init (struct audio_s *pa) 
+{
+	int chan;
+
+
+/*
+ * Save audio configuration for later use.
+ */
+
+	save_audio_config_p = pa;
+
+	memset (candidate, 0, sizeof(candidate));
+
+	demod_init (save_audio_config_p);
+	hdlc_rec_init (save_audio_config_p);
+
+	for (chan=0; chan<MAX_CHANS; chan++) {
+	  if (save_audio_config_p->achan[chan].valid) { 
+	    if (save_audio_config_p->achan[chan].baud <= 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__);
+	      save_audio_config_p->achan[chan].baud = DEFAULT_BAUD;
+	    }
+	    process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].baud;
+	    //crc_queue_of_last_to_app[chan] = NULL;
+	  }
+	}
+
+}
+
+#if 0
+
+//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;
+}
+#endif /* if 0 */
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	multi_modem_process_sample
+ * 
+ * Purpose:	Feed the sample into the proper modem(s) for the channel.	
+ *
+ * Inputs:	chan	- Radio channel number
+ *
+ *		audio_sample 
+ *
+ * Description:	In earlier versions we always had a one-to-one mapping with
+ *		demodulators and HDLC decoders.
+ *		This was added so we could have multiple modems running in
+ *		parallel with different mark/space tones to compensate for 
+ *		mistuning of HF SSB signals.
+ * 		It was also possible to run multiple filters, for the same
+ *		tones, in parallel (e.g. ABC).
+ *
+ * Version 1.2:	Let's try something new for an experiment.
+ *		We will have a single mark/space demodulator but multiple
+ *		slicers, using different levels, each with its own HDLC decoder.
+ *		We now have a separate variable, num_demod, which could be 1
+ *		while num_subchan is larger.
+ *
+ * Version 1.3:	Go back to num_subchan with single meaning of number of demodulators.
+ *		We now have separate independent variable, num_slicers, for the
+ *		mark/space imbalance compensation.
+ *		num_demod, while probably more descriptive, should not exist anymore.
+ *
+ *------------------------------------------------------------------------------*/
+
+
+__attribute__((hot))
+void multi_modem_process_sample (int chan, int audio_sample) 
+{
+	int d;
+	int subchan;
+	static int i = 0;	/* for interleaving among multiple demodulators. */
+
+// TODO: temp debug, remove this.
+
+	assert (save_audio_config_p->achan[chan].num_subchan > 0 && save_audio_config_p->achan[chan].num_subchan <= MAX_SUBCHANS);
+	assert (save_audio_config_p->achan[chan].num_slicers > 0 && save_audio_config_p->achan[chan].num_slicers <= MAX_SLICERS);
+
+
+	/* Formerly one loop. */
+	/* 1.2: We can feed one demodulator but end up with multiple outputs. */
+
+
+	if (save_audio_config_p->achan[chan].interleave > 1) {
+
+// TODO: temp debug, remove this.
+
+	  assert (save_audio_config_p->achan[chan].interleave == save_audio_config_p->achan[chan].num_subchan);
+	  demod_process_sample(chan, i, audio_sample);
+	  i++;
+	  if (i >= save_audio_config_p->achan[chan].interleave) i = 0;
+	}
+	else {
+	  /* Send same thing to all. */
+	  for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
+	    demod_process_sample(chan, d, audio_sample);
+	  }
+	}
+
+	for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) {
+	  int slice;
+
+	  for (slice = 0; slice < save_audio_config_p->achan[chan].num_slicers; slice++) {
+
+	    if (candidate[chan][subchan][slice].packet_p != NULL) {
+	      candidate[chan][subchan][slice].age++;
+	      if (candidate[chan][subchan][slice].age > process_age[chan]) {
+	        pick_best_candidate (chan);
+	      }
+	    }
+	  }
+	}
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        multi_modem_process_rec_frame
+ *
+ * Purpose:     This is called when we receive a frame with a valid 
+ *		FCS and acceptable size.
+ *
+ * Inputs:	chan	- Audio channel number, 0 or 1.
+ *		subchan	- Which modem found it.
+ *		slice	- Which slice found it.
+ *		fbuf	- Pointer to first byte in HDLC frame.
+ *		flen	- Number of bytes excluding the FCS.
+ *		alevel	- Audio level, range of 0 - 100.
+ *				(Special case, use negative to skip
+ *				 display of audio level line.
+ *				 Use -2 to indicate DTMF message.)
+ *		retries	- Level of bit correction used.
+ *
+ *
+ * Description:	Add to list of candidates.  Best one will be picked later.
+ *
+ *--------------------------------------------------------------------*/
+
+/*
+ 
+	It gets a little more complicated when we try fixing frames
+	with imperfect CRCs.   
+
+	Changing of adjacent bits is quick and done immediately.  These
+	all come in at nearly the same time.  The processing of two 
+	separated bits can take a long time and is handled in the 
+	background by another thread.  These could come in seconds later.
+
+	We need a way to remove duplicates.  I think these are the
+	two cases we need to consider.
+
+	(1) Same result as earlier no error or adjacent bit errors.
+
+		____||||_
+		0.0: ptr=00000000
+		0.1: ptr=00000000
+		0.2: ptr=00000000
+		0.3: ptr=00000000
+		0.4: ptr=009E5540, retry=0, age=295, crc=9458, score=5024
+		0.5: ptr=0082F008, retry=0, age=294, crc=9458, score=5026  ***
+		0.6: ptr=009CE560, retry=0, age=293, crc=9458, score=5026
+		0.7: ptr=009CEE08, retry=0, age=293, crc=9458, score=5024
+		0.8: ptr=00000000
+
+		___._____
+		0.0: ptr=00000000
+		0.1: ptr=00000000
+		0.2: ptr=00000000
+		0.3: ptr=009E5540, retry=4, age=295, crc=9458, score=1000  ***
+		0.4: ptr=00000000
+		0.5: ptr=00000000
+		0.6: ptr=00000000
+		0.7: ptr=00000000
+		0.8: ptr=00000000
+
+	(2) Only results from adjusting two non-adjacent bits.
+
+
+		||||||||_
+		0.0: ptr=022EBA08, retry=0, age=289, crc=5acd, score=5042
+		0.1: ptr=022EA8B8, retry=0, age=290, crc=5acd, score=5048
+		0.2: ptr=022EB160, retry=0, age=290, crc=5acd, score=5052
+		0.3: ptr=05BD0048, retry=0, age=291, crc=5acd, score=5054  ***
+		0.4: ptr=04FE0048, retry=0, age=292, crc=5acd, score=5054
+		0.5: ptr=05E10048, retry=0, age=294, crc=5acd, score=5052
+		0.6: ptr=053D0048, retry=0, age=294, crc=5acd, score=5048
+		0.7: ptr=02375558, retry=0, age=295, crc=5acd, score=5042
+		0.8: ptr=00000000
+
+		_______._
+		0.0: ptr=00000000
+		0.1: ptr=00000000
+		0.2: ptr=00000000
+		0.3: ptr=00000000
+		0.4: ptr=00000000
+		0.5: ptr=00000000
+		0.6: ptr=00000000
+		0.7: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000  ***
+		0.8: ptr=00000000
+
+		________.
+		0.0: ptr=00000000
+		0.1: ptr=00000000
+		0.2: ptr=00000000
+		0.3: ptr=00000000
+		0.4: ptr=00000000
+		0.5: ptr=00000000
+		0.6: ptr=00000000
+		0.7: ptr=00000000
+		0.8: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000  ***
+
+
+	These can both be covered by keepin the last CRC and dropping 
+	duplicates.  In theory we could get another frame in between with
+	a slow computer so the complete solution would be to remember more
+	than one.
+*/
+
+void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries)
+{
+	packet_t pp;
+
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+	assert (slice >= 0 && slice < MAX_SUBCHANS);
+
+	pp = ax25_from_frame (fbuf, flen, alevel);
+
+	if (pp == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__);
+	  return;	/* oops!  why would it fail? */
+	}
+
+
+/*
+ * If only one demodulator/slicer, push it thru and forget about all this foolishness.
+ */
+	if (save_audio_config_p->achan[chan].num_subchan == 1 &&
+	    save_audio_config_p->achan[chan].num_slicers == 1) {
+
+	  dlq_append (DLQ_REC_FRAME, chan, subchan, slice, pp, alevel, retries, "");
+	  return;
+	}
+
+
+/*
+ * Otherwise, save them up for a few bit times so we can pick the best.
+ */
+	if (candidate[chan][subchan][slice].packet_p != NULL) {
+	  /* Oops!  Didn't expect it to be there. */
+	  ax25_delete (candidate[chan][subchan][slice].packet_p);
+	  candidate[chan][subchan][slice].packet_p = NULL;
+	}
+
+	candidate[chan][subchan][slice].packet_p = pp;
+	candidate[chan][subchan][slice].alevel = alevel;
+	candidate[chan][subchan][slice].retries = retries;
+	candidate[chan][subchan][slice].age = 0;
+	candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp);
+}
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        pick_best_candidate
+ *
+ * Purpose:     This is called when we have one or more candidates
+ *		available for a certain amount of time.
+ *
+ * Description:	Pick the best one and send it up to the application.
+ *		Discard the others.
+ *		
+ * Rules:	We prefer one received perfectly but will settle for
+ *		one where some bits had to be flipped to get a good CRC.
+ *
+ *--------------------------------------------------------------------*/
+
+/* This is a suitable order for interleaved "G" demodulators. */
+/* Opposite order would be suitable for multi-frequency although */
+/* multiple slicers are of questionable value for HF SSB. */
+
+#define subchan_from_n(x) ((x) % save_audio_config_p->achan[chan].num_subchan)
+#define slice_from_n(x)   ((x) / save_audio_config_p->achan[chan].num_subchan)
+
+
+static void pick_best_candidate (int chan) 
+{
+	int best_n, best_score;
+	char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];
+	int n, j, k;
+	int num_bars = save_audio_config_p->achan[chan].num_slicers * save_audio_config_p->achan[chan].num_subchan;
+
+	memset (spectrum, 0, sizeof(spectrum));
+
+	for (n = 0; n < num_bars; n++) {
+	  j = subchan_from_n(n);
+	  k = slice_from_n(n);
+
+	  /* Build the spectrum display. */
+
+	  if (candidate[chan][j][k].packet_p == NULL) {
+	    spectrum[n] = '_';
+	  }
+	  else if (candidate[chan][j][k].retries == RETRY_NONE) {
+	    spectrum[n] = '|';
+	  }
+	  else if (candidate[chan][j][k].retries == RETRY_INVERT_SINGLE) {
+	    spectrum[n] = ':';
+	  }
+	  else  {
+	    spectrum[n] = '.';
+	  }
+
+	  /* Begining score depends on effort to get a valid frame CRC. */
+
+	  if (candidate[chan][j][k].packet_p == NULL) {
+	    candidate[chan][j][k].score = 0;
+	  }
+	  else {
+	    /* Originally, this produced 0 for the PASSALL case. */
+	    /* This didn't work so well when looking for the best score. */
+	    /* Around 1.3 dev H, we add an extra 1 in here so the minimum */
+	    /* score should now be 1 for anything received.  */
+
+	    candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1;
+	  }
+	}
+
+	/* Bump it up slightly if others nearby have the same CRC. */
+
+	for (n = 0; n < num_bars; n++) {
+	  int m;
+
+	  j = subchan_from_n(n);
+	  k = slice_from_n(n);
+
+	  if (candidate[chan][j][k].packet_p != NULL) {
+
+	    for (m = 0; m < num_bars; m++) {
+
+	      int mj = subchan_from_n(m);
+	      int mk = slice_from_n(m);
+
+	      if (m != n && candidate[chan][mj][mk].packet_p != NULL) {
+	        if (candidate[chan][j][k].crc == candidate[chan][mj][mk].crc) {
+	          candidate[chan][j][k].score += (num_bars+1) - abs(m-n);
+	        }
+	      }
+	    }
+	  }
+	}
+  
+	best_n = 0;
+	best_score = 0;
+
+	for (n = 0; n < num_bars; n++) {
+	  j = subchan_from_n(n);
+	  k = slice_from_n(n);
+
+	  if (candidate[chan][j][k].packet_p != NULL) {
+	    if (candidate[chan][j][k].score > best_score) {
+	       best_score = candidate[chan][j][k].score;
+	       best_n = n;
+	    }
+	  }
+	}
+
+#if DEBUG	
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("\n%s\n", spectrum);
+
+	for (n = 0; n < num_bars; n++) {
+	  j = subchan_from_n(n);
+	  k = slice_from_n(n);
+
+	  if (candidate[chan][j][k].packet_p == NULL) {
+	    dw_printf ("%d.%d.%d: ptr=%p\n", chan, j, k,
+		candidate[chan][j][k].packet_p);
+	  }
+	  else {
+	    dw_printf ("%d.%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d  %s\n", chan, j, k,
+		candidate[chan][j][k].packet_p,
+		(int)(candidate[chan][j][k].retries),
+		candidate[chan][j][k].age,
+		candidate[chan][j][k].crc,
+		candidate[chan][j][k].score,
+		(n == best_n) ? "***" : "");
+	  }
+	}
+#endif
+
+	if (best_score == 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Unexpected internal problem, %s %d.  How can best score be zero?\n", __FILE__, __LINE__);
+	}
+
+/*
+ * send the best one along.
+ */
+
+	/* Delete those not chosen. */
+
+	for (n = 0; n < num_bars; n++) {
+	  j = subchan_from_n(n);
+	  k = slice_from_n(n);
+	  if (n != best_n && candidate[chan][j][k].packet_p != NULL) {
+	    ax25_delete (candidate[chan][j][k].packet_p);
+	    candidate[chan][j][k].packet_p = NULL;
+	  }
+	}
+
+	/* Pass along one. */
+
+
+	j = subchan_from_n(best_n);
+	k = slice_from_n(best_n);
+
+	dlq_append (DLQ_REC_FRAME, chan, j, k,
+		candidate[chan][j][k].packet_p,
+		candidate[chan][j][k].alevel,
+		(int)(candidate[chan][j][k].retries),
+		spectrum);
+
+	/* Someone else owns it now and will delete it later. */
+	candidate[chan][j][k].packet_p = NULL;
+
+	/* Clear in preparation for next time. */
+
+	memset (candidate[chan], 0, sizeof(candidate[chan]));
+
+} /* end pick_best_candidate */
+
+
+/* end multi_modem.c */
diff --git a/multi_modem.h b/multi_modem.h
index 9e99d7f..77c5b6d 100644
--- a/multi_modem.h
+++ b/multi_modem.h
@@ -1,20 +1,19 @@
-/* multi_modem.h */
-
-#ifndef MULTI_MODEM_H
-#define MULTI_MODEM 1
-
-/* Needed for typedef retry_t. */
-#include "hdlc_rec2.h"
-
-/* Needed for struct audio_s */
-#include "audio.h"
-
-
-void multi_modem_init (struct audio_s *pmodem); 
-
-void multi_modem_process_sample (int c, int audio_sample);
-
-void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries);
-
-
-#endif
+/* multi_modem.h */
+
+#ifndef MULTI_MODEM_H
+#define MULTI_MODEM 1
+
+/* Needed for typedef retry_t. */
+#include "hdlc_rec2.h"
+
+/* Needed for struct audio_s */
+#include "audio.h"
+
+
+void multi_modem_init (struct audio_s *pmodem); 
+
+void multi_modem_process_sample (int c, int audio_sample);
+
+void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries);
+
+#endif
diff --git a/nmea.c b/nmea.c
index bcd58e6..32e487e 100644
--- a/nmea.c
+++ b/nmea.c
@@ -1,1037 +1,465 @@
-//
-//    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 */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014, 2015  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
+
+
+// TODO: rename this to waypoint & integrate.
+
+/*------------------------------------------------------------------
+ *
+ * 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 */
+#include "serial_port.h"
+
+
+// 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);
+
+
+
+//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.
+ *
+ *---------------------------------------------------------------*/
+
+
+void nmea_init (struct misc_config_s *mc)
+{
+	
+/*
+ * Open serial port connection.
+ * 4800 baud is standard for GPS.
+ * Should add an option to allow changing someday.
+ */
+	if (strlen(mc->nmea_port) > 0) {
+
+	  nmea_port_fd = serial_port_open (mc->nmea_port, 4800);
+
+
+	}
+
+
+#if DEBUG
+	text_color_set (DW_COLOR_DEBUG);
+
+	dw_printf ("end of nmea_init: nmea_port_fd = %d\n", nmea_port_fd);
+#endif
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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[5];		/* 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
+ */
+
+	snprintf (sentence, sizeof(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 {
+	  snprintf (salt, sizeof(salt), "%.1f", alt);
+	}
+	grm_sym = 0x1234; 	// TODO
+
+	snprintf (sentence, sizeof(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
+
+	snprintf (sicon, sizeof(sicon), "??");
+	snprintf (sentence, sizeof(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 {
+	  snprintf (sspeed, sizeof(sspeed), "%.1f", speed);
+	}
+	if (course == G_UNKNOWN) {
+	  strcpy (scourse, "");
+	} 
+	else {
+	  snprintf (scourse, sizeof(scourse), "%.1f", course);
+	}
+
+// TODO:  how to handle time & date ???
+
+	strcpy (stime, "123456");
+	strcpy (sdate, "123456");
+
+	snprintf (sentence, sizeof(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
+
+   snprintf(obuf, sizeof(), "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.
+
+	serial_port_write (nmea_port_fd, sent, len);
+
+} /* nmea_send_sentence */
+
+
+
+
+
+/* end nmea.c */
diff --git a/nmea.h b/nmea.h
index 470353d..9ff8553 100644
--- a/nmea.h
+++ b/nmea.h
@@ -1,19 +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 */
+
+/* 
+ * 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/pfilter.c b/pfilter.c
index 8b62d20..3305949 100644
--- a/pfilter.c
+++ b/pfilter.c
@@ -1,1069 +1,1141 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2015  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/>.
-//
-
-
-
-/*------------------------------------------------------------------
- *
- * Module:      pfilter.c
- *
- * Purpose:   	Packet filtering based on characteristics.
- *		
- * Description:	Sometimes it is desirable to digipeat or drop packets based on rules.
- *		For example, you might want to pass only weather information thru
- *		a cross band digipeater or you might want to drop all packets from
- *		an abusive user that is overloading the channel.
- *
- *		The filter specifications are loosely modeled after the IGate Server-side Filter
- *		Commands:   http://www.aprs-is.net/javaprsfilter.aspx
- *
- *		We add AND, OR, NOT, and ( ) to allow very flexible control.
- *
- *---------------------------------------------------------------*/
-
-
-#include <unistd.h>
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <ctype.h>
-
-#if __WIN32__
-char *strsep(char **stringp, const char *delim);
-#endif
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "decode_aprs.h"
-#include "latlong.h"
-#include "pfilter.h"
-
-
-
-typedef enum token_type_e { TOKEN_AND, TOKEN_OR, TOKEN_NOT, TOKEN_LPAREN, TOKEN_RPAREN, TOKEN_FILTER_SPEC, TOKEN_EOL } token_type_t;
-
-
-#define MAX_FILTER_LEN 1024
-#define MAX_TOKEN_LEN 1024
-
-typedef struct pfstate_s {
-
-	int from_chan;				/* From and to channels.   MAX_CHANS is used for IGate. */
-	int to_chan;				/* Used only for debug and error messages. */
-
-
-// TODO: might want to put channels and packet here so we only pass one thing around.
-
-/*
- * Original filter string from config file.
- * All control characters should be replaced by spaces.
- */
-	char filter_str[MAX_FILTER_LEN];
-	int nexti;				/* Next available character index. */
-
-/*
- * Packet object.
- */
-	packet_t pp;
-
-/*
- * Packet split into separate parts.
- * Most interesting fields are:
- *		g_src		- source address
- *		g_symbol_table	- / \ or overlay
- *		g_symbol_code
- *		g_lat, g_lon	- Location
- *		g_name		- for object or item
- *		g_comment
- */
-	decode_aprs_t decoded;
-
-/*
- * These are set by next_token.
- */
-	token_type_t token_type;
-	char token_str[MAX_TOKEN_LEN];		/* Printable string representation for use in error messages. */
-	int tokeni;				/* Index in original string for enhanced error messages. */
-
-} pfstate_t;
-
-
-
-static int parse_expr (pfstate_t *pf);
-static int parse_or_expr (pfstate_t *pf);
-static int parse_and_expr (pfstate_t *pf);
-static int parse_primary (pfstate_t *pf);
-static int parse_filter_spec (pfstate_t *pf);
-
-static void next_token (pfstate_t *pf);
-static void print_error (pfstate_t *pf, char *msg);
-
-static int filt_bodgu (pfstate_t *pf, char *pattern);
-static int filt_t (pfstate_t *pf);
-static int filt_r (pfstate_t *pf);
-static int filt_s (pfstate_t *pf);
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        pfilter.c
- *
- * Purpose:     Decide whether a packet should be allowed thru.
- *
- * Inputs:	from_chan - Channel packet is coming from.  
- *		to_chan	  - Channel packet is going to.
- *				Both are 0 .. MAX_CHANS-1 or MAX_CHANS for IGate.  
- *			 	For debug/error messages only.
- *
- *		filter	- Filter specs and logical operators to combine them.
- *
- *		pp	- Packet object handle.
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- * Description:	This might be running in multiple threads at the same time so
- *		no static data allowed and take other thread-safe precautions.
- *
- *--------------------------------------------------------------------*/
-
-int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
-{
-	pfstate_t pfstate;
-	char *p;
-	int result;
-
-	pfstate.from_chan = from_chan;
-	pfstate.to_chan = to_chan;
-
-	/* Copy filter string, removing any control characters. */
-	strncpy (pfstate.filter_str, filter, MAX_FILTER_LEN-1);
-	pfstate.filter_str[MAX_FILTER_LEN-1] = '\0';
-	pfstate.nexti = 0;
-	for (p = pfstate.filter_str; *p != '\0'; p++) {
-	  if (iscntrl(*p)) {
-	    *p = ' ';
-	  }
-	}
-
-	pfstate.pp = pp;
-	decode_aprs (&pfstate.decoded, pp, 1);	
-
-	next_token(&pfstate);
-	
-	if (pfstate.token_type == TOKEN_EOL) {
-	  /* Empty filter means reject all. */
-	  result = 0;
-	}
-	else {
-	  result = parse_expr (&pfstate);
-
-	  if (pfstate.token_type != TOKEN_AND && 
-		pfstate.token_type != TOKEN_OR && 
-		pfstate.token_type != TOKEN_EOL) {
-
-	    print_error (&pfstate, "Expected logical operator or end of line here.");
-	    result = -1;
-	  }
-	}
-	return (result);
-
-} /* end pfilter */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:   	next_token     
- *
- * Purpose:     Extract the next token from input string.
- *
- * Inputs:	pf	- Pointer to current state information.	
- *
- * Outputs:	See definition of the structure.
- *
- * Description:	Look for these special operators:   & | ! ( ) end-of-line
- *		Anything else is considered a filter specification.
- *		Note that a filter-spec must be followed by space or 
- *		end of line.  This is so the magic characters can appear in one.
- *
- * Future:	Allow words like 'OR' as alternatives to symbols like '|'.
- *
- * Unresolved Issue:
- *
- *		Adding the special operattors adds a new complication.
- *		How do we handle the case where we want those characters in
- *		a filter specification?   For example how do we know if the
- *		last character of /#& means HF gateway or AND the next part
- *		of the expression.
- *		
- *		Approach 1:  Require white space after all filter specifications.
- *			     Currently implemented.
- *			     Simple. Easy to explain. 
- *			     More readable than having everything squashed together.
- *		
- *		Approach 2:  Use escape character to get literal value.  e.g.  s/#\&
- *			     Linux people would be comfortable with this but 
- *			     others might have a problem with it.
- *		
- *		Approach 3:  use quotation marks if it contains special characters or space.
- *			     "s/#&"  Simple.  Allows embedded space but I'm not sure
- *		 	     that's useful.  Doesn't hurt to always put the quotes there
- *			     if you can't remember which characters are special.
- *
- *--------------------------------------------------------------------*/
-
-static void next_token (pfstate_t *pf) 
-{
-	while (pf->filter_str[pf->nexti] ==  ' ') {
-	  pf->nexti++;
-	}
-
-	pf->tokeni = pf->nexti;
-
-	if (pf->filter_str[pf->nexti] == '\0') {
-	  pf->token_type = TOKEN_EOL;
-	  strcpy (pf->token_str, "end-of-line");
-	}
-	else if (pf->filter_str[pf->nexti] == '&') {
-	  pf->nexti++;
-	  pf->token_type = TOKEN_AND;
-	  strcpy (pf->token_str, "\"&\"");
-	}
-	else if (pf->filter_str[pf->nexti] == '|') {
-	  pf->nexti++;
-	  pf->token_type = TOKEN_OR;
-	  strcpy (pf->token_str, "\"|\"");
-	}
-	else if (pf->filter_str[pf->nexti] == '!') {
-	  pf->nexti++;
-	  pf->token_type = TOKEN_NOT;
-	  strcpy (pf->token_str, "\"!\"");
-	}
-	else if (pf->filter_str[pf->nexti] == '(') {
-	  pf->nexti++;
-	  pf->token_type = TOKEN_LPAREN;
-	  strcpy (pf->token_str, "\"(\"");
-	}
-	else if (pf->filter_str[pf->nexti] == ')') {
-	  pf->nexti++;
-	  pf->token_type = TOKEN_RPAREN;
-	  strcpy (pf->token_str, "\")\"");
-	}
-	else {
-	  char *p = pf->token_str;
-	  pf->token_type = TOKEN_FILTER_SPEC;
-	  do {
-	    *p++ = pf->filter_str[pf->nexti++];
-	  } while (pf->filter_str[pf->nexti] != ' ' && pf->filter_str[pf->nexti] != '\0');
-	  *p = '\0';
-	}
-
-} /* end next_token */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:   	parse_expr
- *		parse_or_expr
- *		parse_and_expr
- *		parse_primary
- *    
- * Purpose:     Recursive descent parser to evaluate filter specifications
- *		contained within expressions with & | ! ( ).
- *
- * Inputs:	pf	- Pointer to current state information.	
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- *--------------------------------------------------------------------*/
-
-
-static int parse_expr (pfstate_t *pf)
-{
-	int result;
-
-	result = parse_or_expr (pf);
-
-	return (result);
-}
-
-/* or_expr::	and_expr [ | and_expr ] ... */
-
-static int parse_or_expr (pfstate_t *pf)
-{
-	int result;
-
-	result = parse_and_expr (pf);
-	if (result < 0) return (-1);
-	
-	while (pf->token_type == TOKEN_OR) {
-	  int e;
-
-	  next_token (pf);
-	  e = parse_and_expr (pf);
-	  if (e < 0) return (-1);
-	  result |= e;
-	}
-
-	return (result);
-}
-
-/* and_expr::	primary [ & primary ] ... */
- 
-static int parse_and_expr (pfstate_t *pf)
-{
-	int result;
-
-	result = parse_primary (pf);
-	if (result < 0) return (-1);
-
-	while (pf->token_type == TOKEN_AND) {
-	  int e;
-
-	  next_token (pf);
-	  e = parse_primary (pf);
-	  if (e < 0) return (-1);
-	  result &= e;
-	}
-
-	return (result);
-}
-
-/* primary::	( expr )	*/
-/* 		! primary	*/
-/*		filter_spec	*/
-
-static int parse_primary (pfstate_t *pf)
-{
-	int result;
-
-	if (pf->token_type == TOKEN_LPAREN) {
-
-	  next_token (pf);
-	  result = parse_expr (pf);
-	  	  
-	  if (pf->token_type == TOKEN_RPAREN) {
-	    next_token (pf);
-	  }
-	  else {
-	    print_error (pf, "Expected \")\" here.\n");
-	    result = -1;
-	  }
-	}
-	else if (pf->token_type == TOKEN_NOT) {
-	  int e;
-
-	  next_token (pf);
-	  e = parse_primary (pf);
-	  if (e < 0) result = -1;
-	  else result = ! e;
-	}
-	else if (pf->token_type == TOKEN_FILTER_SPEC) {
-	  result = parse_filter_spec (pf);
-	}
-	else {
-	  print_error (pf, "Expected filter specification, (, or ! here.");
-	  result = -1;
-	}
-
-	return (result);
-}
-
-/*-------------------------------------------------------------------
- *
- * Name:   	parse_filter_spec
- *    
- * Purpose:     Parse and evaluate filter specification.
- *
- * Inputs:	pf	- Pointer to current state information.	
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- *--------------------------------------------------------------------*/
-
-
-
-static int parse_filter_spec (pfstate_t *pf)
-{
-	int result = -1;
-	char addr[AX25_MAX_ADDR_LEN];
-
-/* undocumented: can use 0 or 1 for testing. */
-
-	if (strcmp(pf->token_str, "0") == 0) {
-	  result = 0;
-	}
-	else if (strcmp(pf->token_str, "1") == 0) {
-	  result = 1;
-	}
-
-/* simple string matching */
-
-	else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) {
-	  /* Budlist - source address */
-	  result = filt_bodgu (pf, pf->decoded.g_src);
-	}
-	else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) {
-	  /* Object or item name */
-	  result = filt_bodgu (pf, pf->decoded.g_name);
-	}
-	else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) {
-	  int n;
-	  // loop on used digipeaters
-	  result = 0;
-	  for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
-	    if (ax25_get_h (pf->pp, n)) {
-	      ax25_get_addr_with_ssid (pf->pp, n, addr);
-	      result = filt_bodgu (pf, addr);
-	    }
-	  }
-	}
-	else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) {
-	  /* Addressee of message. */
-	  if (ax25_get_dti(pf->pp) == ':') {
-	    result = filt_bodgu (pf, pf->decoded.g_addressee);
-	  }
-	  else {
-	    result = 0;
-	  }
-	}
-	else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
-	  /* Unproto (destination) - probably want to exclude mic-e types */
-	  /* because destintation is used for part of location. */
-
-	  if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') {
-	    ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
-	    result = filt_bodgu (pf, addr);
-	  }
-	  else {
-	    result = 0;
-	  }
-	}
-
-/* type: position, weather, etc. */
-
-	else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) {
-	  
-	  ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
-	  result = filt_t (pf);
-	}
-
-/* range */
-
-	else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) {
-	  /* range */
-	  result = filt_r (pf);
-	}
-
-/* symbol */
-
-	else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) {
-	  /* symbol */
-	  result = filt_s (pf);
-	}
-
-	else  {
-	  char stemp[80];
-	  sprintf (stemp, "Unrecognized filter type '%c'", pf->token_str[0]);
-	  print_error (pf, stemp);
-	  result = -1;
-	}
-
-	next_token (pf);
-
-	return (result);
-}
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	filt_bodgu
- * 
- * Purpose:	Filter with text pattern matching
- *
- * Inputs:	pf	- Pointer to current state information.	
- *			  token_str should have one of these filter specs:
- *
- * 				Budlist		b/call1/call2...  
- * 				Object		o/obj1/obj2...  
- * 				Digipeater	d/digi1/digi2...  
- * 				Group Msg	g/call1/call2...  
- * 				Unproto		u/unproto1/unproto2...  
- *
- *		arg	- Value to match from source addr, destination,
- *			  used digipeater, object name, etc.
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- * Description:	Same function is used for all of these because they are so similar.
- *		Look for exact match to any of the specifed strings.
- *		All of them allow wildcarding with single * at the end.
- *
- *------------------------------------------------------------------------------*/
-
-static int filt_bodgu (pfstate_t *pf, char *arg)
-{
-	char str[MAX_TOKEN_LEN];
-	char *cp;
-	char sep[2];
-	char *v;
-	int result = 0;
-
-	strcpy (str, pf->token_str);
-	sep[0] = str[1];
-	sep[1] = '\0';
-	cp = str + 2;
-
-	while (result == 0 && (v = strsep (&cp, sep)) != NULL) {
-
-	  int mlen;
-	  char *w;
-
-	  if ((w = strchr(v,'*')) != NULL) {
-	    /* Wildcarding.  Should have single * on end. */
-
-	    mlen = w - v;
-	    if (mlen != strlen(v) - 1) {
-	      print_error (pf, "Any wildcard * must be at the end of pattern.\n");
-	      return (-1);
-	    }
-	    if (strncmp(v,arg,mlen) == 0) result = 1;
-	  } 
-	  else {
-	    /* Try for exact match. */
-	    if (strcmp(v,arg) == 0) result = 1;
-	  }
-	}
-
-	return (result);
-}
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	filt_t
- * 
- * Purpose:	Filter by packet type.
- *
- * Inputs:	pf	- Pointer to current state information.	
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- * Description:	The filter is based the type filtering described here:
- *		http://www.aprs-is.net/javAPRSFilter.aspx
- *
- *		Most of these simply check the first byte of the information part.
- *		Trying to detect NWS information is a little trickier.
- *		http://www.aprs-is.net/WX/
- *		http://wxsvr.aprs.net.au/protocol-new.html	
- *		
- *------------------------------------------------------------------------------*/
-
-static int filt_t (pfstate_t *pf) 
-{
-	char src[MAX_TOKEN_LEN];
-	char *infop;
-	char *f;
-
-	ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src);
-	(void) ax25_get_info (pf->pp, (unsigned char **)(&infop));
-
-	for (f = pf->token_str + 2; *f != '\0'; f++) {
-	  switch (*f) {
-	
-	    case 'p':				/* Position */
-	      if (*infop == '!') return (1);
-	      if (*infop == '\'') return (1);
-	      if (*infop == '/') return (1);
-	      if (*infop == '=') return (1);
-	      if (*infop == '@') return (1);
-	      if (*infop == '`') return (1);
-	      break;
-
-	    case 'o':				/* Object */
-	      if (*infop == ';') return (1);
-	      break;
-
-	    case 'i':				/* Item */
-	      if (*infop == ')') return (1);
-	      break;
-
-	    case 'm':				/* Message */
-	      if (*infop == ':') return (1);
-	      break;
-
-	    case 'q':				/* Query */
-	      if (*infop == '?') return (1);
-	      break;
-
-	    case 's':				/* Status */
-	      if (*infop == '>') return (1);
-	      break;
-
-	    case 't':				/* Telemetry */
-	      if (*infop == 'T') return (1);
-	      break;
-
-	    case 'u':				/* User-defined */
-	      if (*infop == '{') return (1);
-	      break;
-
-	    case 'w':				/* Weather */
-	      if (*infop == '@') return (1);
-	      if (*infop == '*') return (1);
-	      if (*infop == '_') return (1);
-
-	      /* '$' is normally raw GPS. Check for special case. */
-	      if (strncmp(infop, "$ULTW", 5) == 0) return (1);
-
-	      /* TODO: Positions !=/@ can be weather. */
-	      /* Need to check for _ symbol. */
-	      break;
-
-	    case 'n':				/* NWS format */
-/*
- * This is the interesting case.
- * The source must be exactly 6 upper case letters, no SSID.
- */
-	      if (strlen(src) != 6) break;
-	      if (! isupper(src[0])) break;
-	      if (! isupper(src[1])) break;
-	      if (! isupper(src[2])) break;
-	      if (! isupper(src[3])) break;
-	      if (! isupper(src[4])) break;
-	      if (! isupper(src[5])) break;
-/*
- * We can have a "message" with addressee starting with NWS, SKY, or BOM (Australian version.)
- */
-	      if (strncmp(infop, ":NWS", 4) == 0) return (1);
-	      if (strncmp(infop, ":SKY", 4) == 0) return (1);
-	      if (strncmp(infop, ":BOM", 4) == 0) return (1);
-/*
- * Or we can have an object.
- * It's not exactly clear how to distiguish this from other objects.
- * It looks like the first 3 characters of the source should be the same
- * as the first 3 characters of the addressee.
- */
-	      if (infop[0] == ';' &&
-		  infop[1] == src[0] &&
-		  infop[2] == src[1] &&
-		  infop[3] == src[2]) return (1);
-	      break;
-	  }
-	}
-	return (0);			/* Didn't match anything.  Reject */
-
-} /* end filt_t */
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	filt_r
- * 
- * Purpose:	Is it in range of given location.
- *
- * Inputs:	pf	- Pointer to current state information.	
- *			  token_str should contain something of format:
- *
- *				r/lat/lon/dist
- *
- *			  We also need to know the location (if any) from the packet.
- *
- *				decoded.g_lat & decoded.g_lon
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- * Description:	
- *
- *------------------------------------------------------------------------------*/
-
-static int filt_r (pfstate_t *pf)
-{
-	char str[MAX_TOKEN_LEN];
-	char *cp;
-	char sep[2];
-	char *v;
-	double dlat, dlon, ddist, km;
-
-
-	strcpy (str, pf->token_str);
-	sep[0] = str[1];
-	sep[1] = '\0';
-	cp = str + 2;
-
-	if (pf->decoded.g_lat == G_UNKNOWN || pf->decoded.g_lon == G_UNKNOWN) {
-	  return (0);
-	}
-
-	v = strsep (&cp, sep);
-	if (v == NULL) {
-	  print_error (pf, "Missing latitude for Range filter.");
-	  return (-1);
-	}
-	dlat = atof(v);
-
-	v = strsep (&cp, sep);
-	if (v == NULL) {
-	  print_error (pf, "Missing longitude for Range filter.");
-	  return (-1);
-	}
-	dlon = atof(v);
-
-	v = strsep (&cp, sep);
-	if (v == NULL) {
-	  print_error (pf, "Missing distance for Range filter.");
-	  return (-1);
-	}
-	ddist = atof(v);
-
-	km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon);
-
-
-	text_color_set (DW_COLOR_DEBUG);
-
-	dw_printf ("Calculated distance = %.3f km\n", km);
-
-	if (km <= ddist) {
-	  return (1);
-	}
-
-	return (0);
-}
-
-
-
-/*------------------------------------------------------------------------------
- *
- * Name:	filt_s
- * 
- * Purpose:	Filter by symbol.
- *
- * Inputs:	pf	- Pointer to current state information.	
- *			  token_str should contain something of format:
- *
- *				s/pri/alt/over
- *
- * Returns:	 1 = yes
- *		 0 = no
- *		-1 = error detected
- *
- * Description:	
- *		  
- *		�pri� is zero or more symbols from the primary symbol set.
- *		�alt� is one or more symbols from the alternate symbol set.
- *		�over� is overlay characters.  Overlays apply only to the alternate symbol set.
- *		
- *		Examples:
- *			s/->		Allow house and car from primary symbol table.
- *			s//#		Allow alternate table digipeater, with or without overlay.
- *			s//#/\		Allow alternate table digipeater, only if no overlay.
- *			s//#/SL1	Allow alternate table digipeater, with overlay S, L, or 1
- * 
- *------------------------------------------------------------------------------*/
-
-static int filt_s (pfstate_t *pf)
-{
-	char str[MAX_TOKEN_LEN];
-	char *cp;
-	char sep[2];
-	char *pri, *alt, *over;
-
-
-	strcpy (str, pf->token_str);
-	sep[0] = str[1];
-	sep[1] = '\0';
-	cp = str + 2;
-
-	pri = strsep (&cp, sep);
-	if (pri == NULL) {
-	  print_error (pf, "Missing arguments for Symbol filter.");
-	  return (-1);
-	}
-
-	if (pf->decoded.g_symbol_table == '/' && strchr(pri, pf->decoded.g_symbol_code) != NULL) {
-	  /* Found in primary symbols. All done. */
-	  return (1);
-	}
-
-	alt = strsep (&cp, sep);
-	if (alt == NULL) {
-	  return (0);
-	}
-	if (strlen(alt) == 0) {
-	  /* We have s/.../ */
-	  print_error (pf, "Missing alternate symbols for Symbol filter.");
-	  return (-1);
-	}
-
-	//printf ("alt=\"%s\"  sym='%c'\n", alt, pf->decoded.g_symbol_code);
-
-	if (strchr(alt, pf->decoded.g_symbol_code) == NULL) {
-	  /* Not found in alternate symbols. Reject. */
-	  return (0);
-	}
-
-	over = strsep (&cp, sep);
-	if (over == NULL) {
-	  /* alternate, with or without overlay. */
-	  return (pf->decoded.g_symbol_table != '/');
-	}
-
-	// printf ("over=\"%s\"  table='%c'\n", over, pf->decoded.g_symbol_table);
-
-	if (strlen(over) == 0) {
-	  return (pf->decoded.g_symbol_table == '\\');
-	}
-
-	return (strchr(over, pf->decoded.g_symbol_table) != NULL);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:   	print_error
- *    
- * Purpose:     Print error message with context so someone can figure out what caused it.
- *
- * Inputs:	pf	- Pointer to current state information.	
- *
- *		str	- Specific error message.
- *
- *--------------------------------------------------------------------*/
-
-static void print_error (pfstate_t *pf, char *msg)
-{
-	char intro[50];
-
-	if (pf->from_chan == MAX_CHANS) {
-
-	  if (pf->to_chan == MAX_CHANS) {
-	    sprintf (intro, "filter[IG,IG]: ");
-	  }
-	  else {
-	    sprintf (intro, "filter[IG,%d]: ", pf->to_chan);
-	  }
-	}
-	else {
-
-	  if (pf->to_chan == MAX_CHANS) {
-	    sprintf (intro, "filter[%d,IG]: ", pf->from_chan);
-	  }
-	  else {
-	    sprintf (intro, "filter[%d,%d]: ", pf->from_chan, pf->to_chan);
-	  }
-	}
-
-	text_color_set (DW_COLOR_ERROR);
-
-	dw_printf ("%s%s\n", intro, pf->filter_str);
-	dw_printf ("%*s\n", (int)(strlen(intro) + pf->tokeni + 1), "^");
-	dw_printf ("%s\n", msg);
-}
-
-
-
-#if TEST
-
-
-/*-------------------------------------------------------------------
- *
- * Name:   	main & pftest
- *    
- * Purpose:     Unit test for packet filtering.
- *
- * Usage:	gcc -Wall -o pftest -DTEST pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o latlong.o symbols.o telemetry.o misc.a regex.a && ./pftest
- *		
- *
- *--------------------------------------------------------------------*/
-
-
-static int error_count = 0;
-static void pftest (int test_num, char *filter, char *packet, int expected);
-
-int main ()
-{
-
-	pftest (1, "", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (2, "0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (3, "1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-
-	pftest (10, "0 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (11, "0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (12, "1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (13, "1 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (14, "0 | 0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-
-	pftest (20, "0 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (21, "0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (22, "1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (23, "1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (24, "1 & 1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (24, "1 & 0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (24, "1 & 1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-
-	pftest (30, "0 | ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (31, "! 1 | ! 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (32, "! ! 1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (33, "1 | ! ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-
-	pftest (40, "1 &(!0 |0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (41, "0 |(!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (42, "1 |(!!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (42, "(!(1 ) & (1 ))", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-
-	pftest (50, "b/W2UB/WB2OSZ-5/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (51, "b/W2UB/WB2OSZ-14/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (52, "b#W2UB#WB2OSZ-5#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (53, "b#W2UB#WB2OSZ-14#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-
-	pftest (60, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 0);
-	pftest (61, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 1);
-	pftest (62, "o/HOME", "HOME>APDW12,WIDE1-1,WIDE2-1:;AWAY     *111111z4237.14N/07120.83W-Chelmsford MA", 0);
-	pftest (63, "o/WB2OSZ-5", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (64, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 0);
-	pftest (65, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 1);
-
-	pftest (70, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (71, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (72, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (73, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (74, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (75, "d/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-
-	pftest (80, "g/W2UB",        "WB2OSZ-5>APDW12::W2UB     :text", 1);
-	pftest (81, "g/W2UB/W2UB-*", "WB2OSZ-5>APDW12::W2UB-9   :text", 1);
-	pftest (82, "g/W2UB/*",      "WB2OSZ-5>APDW12::XXX      :text", 1);
-	pftest (83, "g/W2UB/W*UB",   "WB2OSZ-5>APDW12::W2UB-9   :text", -1);
-	pftest (84, "g/W2UB*",       "WB2OSZ-5>APDW12::W2UB-9   :text", 1);
-	pftest (85, "g/W2UB*",       "WB2OSZ-5>APDW12::W2UBZZ   :text", 1);
-	pftest (86, "g/W2UB",        "WB2OSZ-5>APDW12::W2UB-9   :text", 0);
-	pftest (87, "g/*",           "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (88, "g/W*",          "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-
-	pftest (90, "u/APWW10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 1);
-	pftest (91, "u/TRSY3T", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 0);
-	pftest (92, "u/APDW11/APDW12", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (93, "u/APDW", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-
-	// rather sparse coverage of the cases
-	pftest (100, "t/mqt", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (101, "t/mqtp", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (102, "t/mqtp", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 0);
-	pftest (103, "t/mqop", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 1);
-	pftest (104, "t/p", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 1);
-	pftest (104, "t/s", "KB1CHU-13>APWW10,W1CLA-1*,WIDE2-1:>FN42pb/_DX: W1MHL 36.0mi 306<0xb0> 13:24 4223.32N 07115.23W", 1);
-
-	pftest (110, "t/p", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 0);
-	pftest (111, "t/w", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 1);
-	pftest (112, "t/t", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 0);
-	pftest (113, "t/w", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 1);
-
-	pftest (120, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 0);
-	pftest (122, "t/p", "CWAPID>APRS::SKYCWA   :DDHHMMz,ADVISETYPE,zcs{seq#", 0);
-	pftest (123, "t/p", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
-	pftest (124, "t/n", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 1);
-	pftest (125, "t/n", "CWAPID>APRS::SKYCWA   :DDHHMMz,ADVISETYPE,zcs{seq#", 1);
-	pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1);
-	pftest (127, "t/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
-
-	pftest (130, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (131, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
-
-	pftest (140, "( t/t & b/WB2OSZ ) | ( t/o & ! r/42.6/-71.3/1 )", "WB2OSZ>APDW12:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 1);
-
-	pftest (150, "s/->", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (151, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W-PHG7140Chelmsford MA", 1);
-	pftest (152, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W>PHG7140Chelmsford MA", 1);
-	pftest (153, "s/->", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W>PHG7140Chelmsford MA", 0);
-
-	pftest (154, "s//#", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (155, "s//#", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (156, "s//#", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
-
-	pftest (157, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (158, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (159, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
-
-	pftest (160, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
-	pftest (161, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 0);
-	pftest (162, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
-
-	/* Test error reporting. */
-
-	pftest (200, "x/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
-	pftest (201, "t/w & ( t/w | t/w ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
-	pftest (202, "t/w ) ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
-	pftest (203, "!", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
-	pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
-	pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1);
-
-
-	if (error_count > 0) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Packet Filtering Test - FAILED!\n");
-	  exit (1);
-	}
-	text_color_set (DW_COLOR_DEBUG );
-	dw_printf ("Packet Filtering Test - SUCCESS!\n");
-	exit (0);
-
-}
-
-static void pftest (int test_num, char *filter, char *monitor, int expected)
-{
-	int result;
-	packet_t pp;
-
-	text_color_set (DW_COLOR_DEBUG);
-	dw_printf ("test number %d\n", test_num);
-	
-	pp = ax25_from_text (monitor, 1);
-	assert (pp != NULL);
-
-	result = pfilter (0, 0, filter, pp);
-	if (result != expected) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Unexpected result for test number %d\n", test_num);
-	  error_count++;
-	}
-
-	ax25_delete (pp);
-}
-
-#endif /* if TEST */
-
-/* end pfilter.c */
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2015, 2016  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/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      pfilter.c
+ *
+ * Purpose:   	Packet filtering based on characteristics.
+ *		
+ * Description:	Sometimes it is desirable to digipeat or drop packets based on rules.
+ *		For example, you might want to pass only weather information thru
+ *		a cross band digipeater or you might want to drop all packets from
+ *		an abusive user that is overloading the channel.
+ *
+ *		The filter specifications are loosely modeled after the IGate Server-side Filter
+ *		Commands:   http://www.aprs-is.net/javaprsfilter.aspx
+ *
+ *		We add AND, OR, NOT, and ( ) to allow very flexible control.
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+#endif
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "decode_aprs.h"
+#include "latlong.h"
+#include "pfilter.h"
+
+
+
+typedef enum token_type_e { TOKEN_AND, TOKEN_OR, TOKEN_NOT, TOKEN_LPAREN, TOKEN_RPAREN, TOKEN_FILTER_SPEC, TOKEN_EOL } token_type_t;
+
+
+#define MAX_FILTER_LEN 1024
+#define MAX_TOKEN_LEN 1024
+
+typedef struct pfstate_s {
+
+	int from_chan;				/* From and to channels.   MAX_CHANS is used for IGate. */
+	int to_chan;				/* Used only for debug and error messages. */
+
+
+// TODO: might want to put channels and packet here so we only pass one thing around.
+
+/*
+ * Original filter string from config file.
+ * All control characters should be replaced by spaces.
+ */
+	char filter_str[MAX_FILTER_LEN];
+	int nexti;				/* Next available character index. */
+
+/*
+ * Packet object.
+ */
+	packet_t pp;
+
+/*
+ * Packet split into separate parts.
+ * Most interesting fields are:
+ *		g_src		- source address
+ *		g_symbol_table	- / \ or overlay
+ *		g_symbol_code
+ *		g_lat, g_lon	- Location
+ *		g_name		- for object or item
+ *		g_comment
+ */
+	decode_aprs_t decoded;
+
+/*
+ * These are set by next_token.
+ */
+	token_type_t token_type;
+	char token_str[MAX_TOKEN_LEN];		/* Printable string representation for use in error messages. */
+	int tokeni;				/* Index in original string for enhanced error messages. */
+
+} pfstate_t;
+
+
+
+static int parse_expr (pfstate_t *pf);
+static int parse_or_expr (pfstate_t *pf);
+static int parse_and_expr (pfstate_t *pf);
+static int parse_primary (pfstate_t *pf);
+static int parse_filter_spec (pfstate_t *pf);
+
+static void next_token (pfstate_t *pf);
+static void print_error (pfstate_t *pf, char *msg);
+
+static int filt_bodgu (pfstate_t *pf, char *pattern);
+static int filt_t (pfstate_t *pf);
+static int filt_r (pfstate_t *pf);
+static int filt_s (pfstate_t *pf);
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        pfilter.c
+ *
+ * Purpose:     Decide whether a packet should be allowed thru.
+ *
+ * Inputs:	from_chan - Channel packet is coming from.  
+ *		to_chan	  - Channel packet is going to.
+ *				Both are 0 .. MAX_CHANS-1 or MAX_CHANS for IGate.  
+ *			 	For debug/error messages only.
+ *
+ *		filter	- String of filter specs and logical operators to combine them.
+ *
+ *		pp	- Packet object handle.
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ * Description:	This might be running in multiple threads at the same time so
+ *		no static data allowed and take other thread-safe precautions.
+ *
+ *--------------------------------------------------------------------*/
+
+int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
+{
+	pfstate_t pfstate;
+	char *p;
+	int result;
+
+	assert (from_chan >= 0 && from_chan <= MAX_CHANS);
+	assert (to_chan >= 0 && to_chan <= MAX_CHANS);
+
+	if (pp == NULL) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n");
+	  return (-1);
+	}
+	if (filter == NULL) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n");
+	  return (-1);
+	}
+
+	pfstate.from_chan = from_chan;
+	pfstate.to_chan = to_chan;
+
+	/* Copy filter string, removing any control characters. */
+
+	memset (pfstate.filter_str, 0, sizeof(pfstate.filter_str));
+	strncpy (pfstate.filter_str, filter, MAX_FILTER_LEN-1);
+
+	pfstate.nexti = 0;
+	for (p = pfstate.filter_str; *p != '\0'; p++) {
+	  if (iscntrl(*p)) {
+	    *p = ' ';
+	  }
+	}
+
+	pfstate.pp = pp;
+	decode_aprs (&pfstate.decoded, pp, 1);	
+
+	next_token(&pfstate);
+	
+	if (pfstate.token_type == TOKEN_EOL) {
+	  /* Empty filter means reject all. */
+	  result = 0;
+	}
+	else {
+	  result = parse_expr (&pfstate);
+
+	  if (pfstate.token_type != TOKEN_AND && 
+		pfstate.token_type != TOKEN_OR && 
+		pfstate.token_type != TOKEN_EOL) {
+
+	    print_error (&pfstate, "Expected logical operator or end of line here.");
+	    result = -1;
+	  }
+	}
+	return (result);
+
+} /* end pfilter */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:   	next_token     
+ *
+ * Purpose:     Extract the next token from input string.
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *
+ * Outputs:	See definition of the structure.
+ *
+ * Description:	Look for these special operators:   & | ! ( ) end-of-line
+ *		Anything else is considered a filter specification.
+ *		Note that a filter-spec must be followed by space or 
+ *		end of line.  This is so the magic characters can appear in one.
+ *
+ * Future:	Maybe allow words like 'OR' as alternatives to symbols like '|'.
+ *
+ * Unresolved Issue:
+ *
+ *		Adding the special operators adds a new complication.
+ *		How do we handle the case where we want those characters in
+ *		a filter specification?   For example how do we know if the
+ *		last character of /#& means HF gateway or AND the next part
+ *		of the expression.
+ *		
+ *		Approach 1:  Require white space after all filter specifications.
+ *			     Currently implemented.
+ *			     Simple. Easy to explain. 
+ *			     More readable than having everything squashed together.
+ *		
+ *		Approach 2:  Use escape character to get literal value.  e.g.  s/#\&
+ *			     Linux people would be comfortable with this but 
+ *			     others might have a problem with it.
+ *		
+ *		Approach 3:  use quotation marks if it contains special characters or space.
+ *			     "s/#&"  Simple.  Allows embedded space but I'm not sure
+ *		 	     that's useful.  Doesn't hurt to always put the quotes there
+ *			     if you can't remember which characters are special.
+ *
+ *--------------------------------------------------------------------*/
+
+static void next_token (pfstate_t *pf) 
+{
+	while (pf->filter_str[pf->nexti] ==  ' ') {
+	  pf->nexti++;
+	}
+
+	pf->tokeni = pf->nexti;
+
+	if (pf->filter_str[pf->nexti] == '\0') {
+	  pf->token_type = TOKEN_EOL;
+	  strlcpy (pf->token_str, "end-of-line", sizeof(pf->token_str));
+	}
+	else if (pf->filter_str[pf->nexti] == '&') {
+	  pf->nexti++;
+	  pf->token_type = TOKEN_AND;
+	  strlcpy (pf->token_str, "\"&\"", sizeof(pf->token_str));
+	}
+	else if (pf->filter_str[pf->nexti] == '|') {
+	  pf->nexti++;
+	  pf->token_type = TOKEN_OR;
+	  strlcpy (pf->token_str, "\"|\"", sizeof(pf->token_str));
+	}
+	else if (pf->filter_str[pf->nexti] == '!') {
+	  pf->nexti++;
+	  pf->token_type = TOKEN_NOT;
+	  strlcpy (pf->token_str, "\"!\"", sizeof(pf->token_str));
+	}
+	else if (pf->filter_str[pf->nexti] == '(') {
+	  pf->nexti++;
+	  pf->token_type = TOKEN_LPAREN;
+	  strlcpy (pf->token_str, "\"(\"", sizeof(pf->token_str));
+	}
+	else if (pf->filter_str[pf->nexti] == ')') {
+	  pf->nexti++;
+	  pf->token_type = TOKEN_RPAREN;
+	  strlcpy (pf->token_str, "\")\"", sizeof(pf->token_str));
+	}
+	else {
+	  char *p = pf->token_str;
+	  pf->token_type = TOKEN_FILTER_SPEC;
+	  do {
+	    *p++ = pf->filter_str[pf->nexti++];
+	  } while (pf->filter_str[pf->nexti] != ' ' && pf->filter_str[pf->nexti] != '\0');
+	  *p = '\0';
+	}
+
+} /* end next_token */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:   	parse_expr
+ *		parse_or_expr
+ *		parse_and_expr
+ *		parse_primary
+ *    
+ * Purpose:     Recursive descent parser to evaluate filter specifications
+ *		contained within expressions with & | ! ( ).
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ *--------------------------------------------------------------------*/
+
+
+static int parse_expr (pfstate_t *pf)
+{
+	int result;
+
+	result = parse_or_expr (pf);
+
+	return (result);
+}
+
+/* or_expr::	and_expr [ | and_expr ] ... */
+
+static int parse_or_expr (pfstate_t *pf)
+{
+	int result;
+
+	result = parse_and_expr (pf);
+	if (result < 0) return (-1);
+	
+	while (pf->token_type == TOKEN_OR) {
+	  int e;
+
+	  next_token (pf);
+	  e = parse_and_expr (pf);
+	  if (e < 0) return (-1);
+	  result |= e;
+	}
+
+	return (result);
+}
+
+/* and_expr::	primary [ & primary ] ... */
+ 
+static int parse_and_expr (pfstate_t *pf)
+{
+	int result;
+
+	result = parse_primary (pf);
+	if (result < 0) return (-1);
+
+	while (pf->token_type == TOKEN_AND) {
+	  int e;
+
+	  next_token (pf);
+	  e = parse_primary (pf);
+	  if (e < 0) return (-1);
+	  result &= e;
+	}
+
+	return (result);
+}
+
+/* primary::	( expr )	*/
+/* 		! primary	*/
+/*		filter_spec	*/
+
+static int parse_primary (pfstate_t *pf)
+{
+	int result;
+
+	if (pf->token_type == TOKEN_LPAREN) {
+
+	  next_token (pf);
+	  result = parse_expr (pf);
+	  	  
+	  if (pf->token_type == TOKEN_RPAREN) {
+	    next_token (pf);
+	  }
+	  else {
+	    print_error (pf, "Expected \")\" here.\n");
+	    result = -1;
+	  }
+	}
+	else if (pf->token_type == TOKEN_NOT) {
+	  int e;
+
+	  next_token (pf);
+	  e = parse_primary (pf);
+	  if (e < 0) result = -1;
+	  else result = ! e;
+	}
+	else if (pf->token_type == TOKEN_FILTER_SPEC) {
+	  result = parse_filter_spec (pf);
+	}
+	else {
+	  print_error (pf, "Expected filter specification, (, or ! here.");
+	  result = -1;
+	}
+
+	return (result);
+}
+
+/*-------------------------------------------------------------------
+ *
+ * Name:   	parse_filter_spec
+ *    
+ * Purpose:     Parse and evaluate filter specification.
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ *--------------------------------------------------------------------*/
+
+
+
+static int parse_filter_spec (pfstate_t *pf)
+{
+	int result = -1;
+	char addr[AX25_MAX_ADDR_LEN];
+
+/* undocumented: can use 0 or 1 for testing. */
+
+	if (strcmp(pf->token_str, "0") == 0) {
+	  result = 0;
+	}
+	else if (strcmp(pf->token_str, "1") == 0) {
+	  result = 1;
+	}
+
+/* simple string matching */
+
+	else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) {
+	  /* Budlist - source address */
+	  result = filt_bodgu (pf, pf->decoded.g_src);
+	}
+	else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) {
+	  /* Object or item name */
+	  result = filt_bodgu (pf, pf->decoded.g_name);
+	}
+	else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) {
+	  int n;
+	  // loop on all digipeaters
+	  result = 0;
+	  for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
+	    // Consider only those with the H (has-been-used) bit set.
+	    if (ax25_get_h (pf->pp, n)) {
+	      ax25_get_addr_with_ssid (pf->pp, n, addr);
+	      result = filt_bodgu (pf, addr);
+	    }
+	  }
+	}
+	else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) {
+	  int n;
+	  // loop on all digipeaters (mnemonic Via)
+	  result = 0;
+	  for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
+	    // This is different than the previous "d" filter.
+	    // Consider only those where the the H (has-been-used) bit is NOT set.
+	    if ( ! ax25_get_h (pf->pp, n)) {
+	      ax25_get_addr_with_ssid (pf->pp, n, addr);
+	      result = filt_bodgu (pf, addr);
+	    }
+	  }
+	}
+	else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) {
+	  /* Addressee of message. */
+	  if (ax25_get_dti(pf->pp) == ':') {
+	    result = filt_bodgu (pf, pf->decoded.g_addressee);
+	  }
+	  else {
+	    result = 0;
+	  }
+	}
+	else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
+	  /* Unproto (destination) - probably want to exclude mic-e types */
+	  /* because destintation is used for part of location. */
+
+	  if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') {
+	    ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
+	    result = filt_bodgu (pf, addr);
+	  }
+	  else {
+	    result = 0;
+	  }
+	}
+
+/* type: position, weather, etc. */
+
+	else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) {
+	  
+	  ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
+	  result = filt_t (pf);
+	}
+
+/* range */
+
+	else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) {
+	  /* range */
+	  result = filt_r (pf);
+	}
+
+/* symbol */
+
+	else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) {
+	  /* symbol */
+	  result = filt_s (pf);
+	}
+
+	else  {
+	  char stemp[80];
+	  snprintf (stemp, sizeof(stemp), "Unrecognized filter type '%c'", pf->token_str[0]);
+	  print_error (pf, stemp);
+	  result = -1;
+	}
+
+	next_token (pf);
+
+	return (result);
+}
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	filt_bodgu
+ * 
+ * Purpose:	Filter with text pattern matching
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *			  token_str should have one of these filter specs:
+ *
+ * 				Budlist		b/call1/call2...  
+ * 				Object		o/obj1/obj2...  
+ * 				Digipeater	d/digi1/digi2...  
+ * 				Group Msg	g/call1/call2...  
+ * 				Unproto		u/unproto1/unproto2...
+ *				Via-not-yet	v/digi1/digi2...
+ *
+ *		arg	- Value to match from source addr, destination,
+ *			  used digipeater, object name, etc.
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ * Description:	Same function is used for all of these because they are so similar.
+ *		Look for exact match to any of the specifed strings.
+ *		All of them allow wildcarding with single * at the end.
+ *
+ *------------------------------------------------------------------------------*/
+
+static int filt_bodgu (pfstate_t *pf, char *arg)
+{
+	char str[MAX_TOKEN_LEN];
+	char *cp;
+	char sep[2];
+	char *v;
+	int result = 0;
+
+	strlcpy (str, pf->token_str, sizeof(str));
+	sep[0] = str[1];
+	sep[1] = '\0';
+	cp = str + 2;
+
+	while (result == 0 && (v = strsep (&cp, sep)) != NULL) {
+
+	  int mlen;
+	  char *w;
+
+	  if ((w = strchr(v,'*')) != NULL) {
+	    /* Wildcarding.  Should have single * on end. */
+
+	    mlen = w - v;
+	    if (mlen != strlen(v) - 1) {
+	      print_error (pf, "Any wildcard * must be at the end of pattern.\n");
+	      return (-1);
+	    }
+	    if (strncmp(v,arg,mlen) == 0) result = 1;
+	  } 
+	  else {
+	    /* Try for exact match. */
+	    if (strcmp(v,arg) == 0) result = 1;
+	  }
+	}
+
+	return (result);
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	filt_t
+ * 
+ * Purpose:	Filter by packet type.
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ * Description:	The filter is based the type filtering described here:
+ *		http://www.aprs-is.net/javAPRSFilter.aspx
+ *
+ *		Most of these simply check the first byte of the information part.
+ *		Trying to detect NWS information is a little trickier.
+ *		http://www.aprs-is.net/WX/
+ *		http://wxsvr.aprs.net.au/protocol-new.html	
+ *		
+ *------------------------------------------------------------------------------*/
+
+/* Telemetry metadata is a special case of message. */
+/* We want to categorize it as telemetry rather than message. */
+
+static int is_telem_metadata (char *infop)
+{
+	if (*infop != ':') return (0);
+	if (strlen(infop) < 16) return (0);
+	if (strncmp(infop+10, ":PARM.", 6) == 0) return (1);
+	if (strncmp(infop+10, ":UNIT.", 6) == 0) return (1);
+	if (strncmp(infop+10, ":EQNS.", 6) == 0) return (1);
+	if (strncmp(infop+10, ":BITS.", 6) == 0) return (1);
+	return (0);
+}
+
+
+static int filt_t (pfstate_t *pf) 
+{
+	char src[AX25_MAX_ADDR_LEN];
+	char *infop = NULL;
+	char *f;
+
+	memset (src, 0, sizeof(src));
+	ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src);
+	(void) ax25_get_info (pf->pp, (unsigned char **)(&infop));
+
+	assert (infop != NULL);
+
+	for (f = pf->token_str + 2; *f != '\0'; f++) {
+	  switch (*f) {
+	
+	    case 'p':				/* Position */
+	      if (*infop == '!') return (1);
+	      if (*infop == '\'') return (1);
+	      if (*infop == '/') return (1);
+	      if (*infop == '=') return (1);
+	      if (*infop == '@') return (1);
+	      if (*infop == '`') return (1);
+	      break;
+
+	    case 'o':				/* Object */
+	      if (*infop == ';') return (1);
+	      break;
+
+	    case 'i':				/* Item */
+	      if (*infop == ')') return (1);
+	      break;
+
+	    case 'm':				/* Message */
+	      if (*infop == ':' && ! is_telem_metadata(infop)) return (1);
+	      break;
+
+	    case 'q':				/* Query */
+	      if (*infop == '?') return (1);
+	      break;
+
+	    case 's':				/* Status */
+	      if (*infop == '>') return (1);
+	      break;
+
+	    case 't':				/* Telemetry */
+	      if (*infop == 'T') return (1);
+	      if (is_telem_metadata(infop)) return (1);
+	      break;
+
+	    case 'u':				/* User-defined */
+	      if (*infop == '{') return (1);
+	      break;
+
+	    case 'w':				/* Weather */
+	      if (*infop == '@') return (1);
+	      if (*infop == '*') return (1);
+	      if (*infop == '_') return (1);
+
+	      /* '$' is normally raw GPS. Check for special case. */
+	      if (strncmp(infop, "$ULTW", 5) == 0) return (1);
+
+	      /* TODO: Positions !=/@ can be weather. */
+	      /* Need to check for _ symbol. */
+	      break;
+
+	    case 'n':				/* NWS format */
+/*
+ * This is the interesting case.
+ * The source must be exactly 6 upper case letters, no SSID.
+ */
+	      if (strlen(src) != 6) break;
+	      if (! isupper(src[0])) break;
+	      if (! isupper(src[1])) break;
+	      if (! isupper(src[2])) break;
+	      if (! isupper(src[3])) break;
+	      if (! isupper(src[4])) break;
+	      if (! isupper(src[5])) break;
+/*
+ * We can have a "message" with addressee starting with NWS, SKY, or BOM (Australian version.)
+ */
+	      if (strncmp(infop, ":NWS", 4) == 0) return (1);
+	      if (strncmp(infop, ":SKY", 4) == 0) return (1);
+	      if (strncmp(infop, ":BOM", 4) == 0) return (1);
+/*
+ * Or we can have an object.
+ * It's not exactly clear how to distiguish this from other objects.
+ * It looks like the first 3 characters of the source should be the same
+ * as the first 3 characters of the addressee.
+ */
+	      if (infop[0] == ';' &&
+		  infop[1] == src[0] &&
+		  infop[2] == src[1] &&
+		  infop[3] == src[2]) return (1);
+	      break;
+
+	    default:
+
+	      print_error (pf, "Invalid letter in t/ filter.\n");
+	      return (-1);
+	      break;
+	  }
+	}
+	return (0);			/* Didn't match anything.  Reject */
+
+} /* end filt_t */
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	filt_r
+ * 
+ * Purpose:	Is it in range (kilometers) of given location.
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *			  token_str should contain something of format:
+ *
+ *				r/lat/lon/dist
+ *
+ *			  We also need to know the location (if any) from the packet.
+ *
+ *				decoded.g_lat & decoded.g_lon
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ * Description:	
+ *
+ *------------------------------------------------------------------------------*/
+
+static int filt_r (pfstate_t *pf)
+{
+	char str[MAX_TOKEN_LEN];
+	char *cp;
+	char sep[2];
+	char *v;
+	double dlat, dlon, ddist, km;
+
+
+	strlcpy (str, pf->token_str, sizeof(str));
+	sep[0] = str[1];
+	sep[1] = '\0';
+	cp = str + 2;
+
+	if (pf->decoded.g_lat == G_UNKNOWN || pf->decoded.g_lon == G_UNKNOWN) {
+	  return (0);
+	}
+
+	v = strsep (&cp, sep);
+	if (v == NULL) {
+	  print_error (pf, "Missing latitude for Range filter.");
+	  return (-1);
+	}
+	dlat = atof(v);
+
+	v = strsep (&cp, sep);
+	if (v == NULL) {
+	  print_error (pf, "Missing longitude for Range filter.");
+	  return (-1);
+	}
+	dlon = atof(v);
+
+	v = strsep (&cp, sep);
+	if (v == NULL) {
+	  print_error (pf, "Missing distance for Range filter.");
+	  return (-1);
+	}
+	ddist = atof(v);
+
+	km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon);
+
+
+	text_color_set (DW_COLOR_DEBUG);
+
+	dw_printf ("Calculated distance = %.3f km\n", km);
+
+	if (km <= ddist) {
+	  return (1);
+	}
+
+	return (0);
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name:	filt_s
+ * 
+ * Purpose:	Filter by symbol.
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *			  token_str should contain something of format:
+ *
+ *				s/pri/alt/over
+ *
+ * Returns:	 1 = yes
+ *		 0 = no
+ *		-1 = error detected
+ *
+ * Description:	
+ *		  
+ *		"pri" is zero or more symbols from the primary symbol set.
+ *		"alt" is one or more symbols from the alternate symbol set.
+ *		"over" is overlay characters.  Overlays apply only to the alternate symbol set.
+ *		
+ *		Examples:
+ *			s/->		Allow house and car from primary symbol table.
+ *			s//#		Allow alternate table digipeater, with or without overlay.
+ *			s//#/\		Allow alternate table digipeater, only if no overlay.
+ *			s//#/SL1	Allow alternate table digipeater, with overlay S, L, or 1
+ * 
+ *------------------------------------------------------------------------------*/
+
+static int filt_s (pfstate_t *pf)
+{
+	char str[MAX_TOKEN_LEN];
+	char *cp;
+	char sep[2];
+	char *pri, *alt, *over;
+
+
+	strlcpy (str, pf->token_str, sizeof(str));
+	sep[0] = str[1];
+	sep[1] = '\0';
+	cp = str + 2;
+
+	pri = strsep (&cp, sep);
+	if (pri == NULL) {
+	  print_error (pf, "Missing arguments for Symbol filter.");
+	  return (-1);
+	}
+
+	if (pf->decoded.g_symbol_table == '/' && strchr(pri, pf->decoded.g_symbol_code) != NULL) {
+	  /* Found in primary symbols. All done. */
+	  return (1);
+	}
+
+	alt = strsep (&cp, sep);
+	if (alt == NULL) {
+	  return (0);
+	}
+	if (strlen(alt) == 0) {
+	  /* We have s/.../ */
+	  print_error (pf, "Missing alternate symbols for Symbol filter.");
+	  return (-1);
+	}
+
+	//printf ("alt=\"%s\"  sym='%c'\n", alt, pf->decoded.g_symbol_code);
+
+	if (strchr(alt, pf->decoded.g_symbol_code) == NULL) {
+	  /* Not found in alternate symbols. Reject. */
+	  return (0);
+	}
+
+	over = strsep (&cp, sep);
+	if (over == NULL) {
+	  /* alternate, with or without overlay. */
+	  return (pf->decoded.g_symbol_table != '/');
+	}
+
+	// printf ("over=\"%s\"  table='%c'\n", over, pf->decoded.g_symbol_table);
+
+	if (strlen(over) == 0) {
+	  return (pf->decoded.g_symbol_table == '\\');
+	}
+
+	return (strchr(over, pf->decoded.g_symbol_table) != NULL);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:   	print_error
+ *    
+ * Purpose:     Print error message with context so someone can figure out what caused it.
+ *
+ * Inputs:	pf	- Pointer to current state information.	
+ *
+ *		str	- Specific error message.
+ *
+ *--------------------------------------------------------------------*/
+
+static void print_error (pfstate_t *pf, char *msg)
+{
+	char intro[50];
+
+	if (pf->from_chan == MAX_CHANS) {
+
+	  if (pf->to_chan == MAX_CHANS) {
+	    snprintf (intro, sizeof(intro), "filter[IG,IG]: ");
+	  }
+	  else {
+	    snprintf (intro, sizeof(intro), "filter[IG,%d]: ", pf->to_chan);
+	  }
+	}
+	else {
+
+	  if (pf->to_chan == MAX_CHANS) {
+	    snprintf (intro, sizeof(intro), "filter[%d,IG]: ", pf->from_chan);
+	  }
+	  else {
+	    snprintf (intro, sizeof(intro), "filter[%d,%d]: ", pf->from_chan, pf->to_chan);
+	  }
+	}
+
+	text_color_set (DW_COLOR_ERROR);
+
+	dw_printf ("%s%s\n", intro, pf->filter_str);
+	dw_printf ("%*s\n", (int)(strlen(intro) + pf->tokeni + 1), "^");
+	dw_printf ("%s\n", msg);
+}
+
+
+
+#if PFTEST
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:   	main & pftest
+ *    
+ * Purpose:     Unit test for packet filtering.
+ *
+ * Usage:	gcc -Wall -o pftest -DPFTEST pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o latlong.o symbols.o telemetry.o tt_text.c misc.a regex.a && ./pftest
+ *		
+ *
+ *--------------------------------------------------------------------*/
+
+
+static int error_count = 0;
+static void pftest (int test_num, char *filter, char *packet, int expected);
+
+int main ()
+{
+
+	dw_printf ("Quick test for packet filtering.\n");
+	dw_printf ("Some error messages are normal.  Look at the final success/fail message.\n");
+
+	pftest (1, "", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (2, "0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (3, "1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+
+	pftest (10, "0 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (11, "0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (12, "1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (13, "1 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (14, "0 | 0 | 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+
+	pftest (20, "0 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (21, "0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (22, "1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (23, "1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (24, "1 & 1 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (24, "1 & 0 & 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (24, "1 & 1 & 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (30, "0 | ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (31, "! 1 | ! 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (32, "! ! 1 | 0", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (33, "1 | ! ! 1", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+
+	pftest (40, "1 &(!0 |0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (41, "0 |(!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (42, "1 |(!!0 )", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (42, "(!(1 ) & (1 ))", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (50, "b/W2UB/WB2OSZ-5/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (51, "b/W2UB/WB2OSZ-14/N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (52, "b#W2UB#WB2OSZ-5#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (53, "b#W2UB#WB2OSZ-14#N2GH", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (60, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 0);
+	pftest (61, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 1);
+	pftest (62, "o/HOME", "HOME>APDW12,WIDE1-1,WIDE2-1:;AWAY     *111111z4237.14N/07120.83W-Chelmsford MA", 0);
+	pftest (63, "o/WB2OSZ-5", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (64, "o/HOME", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 0);
+	pftest (65, "o/home", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:)home!4237.14N/07120.83W-Chelmsford MA", 1);
+
+	pftest (70, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (71, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (72, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (73, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (74, "d/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (75, "d/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+
+	pftest (80, "g/W2UB",        "WB2OSZ-5>APDW12::W2UB     :text", 1);
+	pftest (81, "g/W2UB/W2UB-*", "WB2OSZ-5>APDW12::W2UB-9   :text", 1);
+	pftest (82, "g/W2UB/*",      "WB2OSZ-5>APDW12::XXX      :text", 1);
+	pftest (83, "g/W2UB/W*UB",   "WB2OSZ-5>APDW12::W2UB-9   :text", -1);
+	pftest (84, "g/W2UB*",       "WB2OSZ-5>APDW12::W2UB-9   :text", 1);
+	pftest (85, "g/W2UB*",       "WB2OSZ-5>APDW12::W2UBZZ   :text", 1);
+	pftest (86, "g/W2UB",        "WB2OSZ-5>APDW12::W2UB-9   :text", 0);
+	pftest (87, "g/*",           "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (88, "g/W*",          "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (90, "u/APWW10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 1);
+	pftest (91, "u/TRSY3T", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 0);
+	pftest (92, "u/APDW11/APDW12", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (93, "u/APDW", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+
+	// rather sparse coverage of the cases
+	pftest (100, "t/mqt", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (101, "t/mqtp", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (102, "t/mqtp", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 0);
+	pftest (103, "t/mqop", "WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 1);
+	pftest (104, "t/p", "W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 1);
+	pftest (104, "t/s", "KB1CHU-13>APWW10,W1CLA-1*,WIDE2-1:>FN42pb/_DX: W1MHL 36.0mi 306<0xb0> 13:24 4223.32N 07115.23W", 1);
+
+	pftest (110, "t/p", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 0);
+	pftest (111, "t/w", "N8VIM>APN391,AB1OC-10,W1MRA*,WIDE2:$ULTW0000000001110B6E27F4FFF3897B0001035E004E04DD00030000<0x0d><0x0a>", 1);
+	pftest (112, "t/t", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 0);
+	pftest (113, "t/w", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 1);
+
+	/* Telemetry metadata is a special case of message. */
+	pftest (114, "t/t", "KJ4SNT>APMI04::KJ4SNT   :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 1);
+	pftest (115, "t/m", "KJ4SNT>APMI04::KJ4SNT   :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 0);
+	pftest (116, "t/t", "KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1);
+
+
+	pftest (120, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 0);
+	pftest (122, "t/p", "CWAPID>APRS::SKYCWA   :DDHHMMz,ADVISETYPE,zcs{seq#", 0);
+	pftest (123, "t/p", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
+	pftest (124, "t/n", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 1);
+	pftest (125, "t/n", "CWAPID>APRS::SKYCWA   :DDHHMMz,ADVISETYPE,zcs{seq#", 1);
+	pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1);
+	pftest (127, "t/",  "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
+
+	pftest (130, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (131, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
+
+	pftest (140, "( t/t & b/WB2OSZ ) | ( t/o & ! r/42.6/-71.3/1 )", "WB2OSZ>APDW12:;home     *111111z4237.14N/07120.83W-Chelmsford MA", 1);
+
+	pftest (150, "s/->", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (151, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W-PHG7140Chelmsford MA", 1);
+	pftest (152, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W>PHG7140Chelmsford MA", 1);
+	pftest (153, "s/->", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W>PHG7140Chelmsford MA", 0);
+
+	pftest (154, "s//#", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (155, "s//#", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (156, "s//#", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (157, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (158, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (159, "s//#/\\", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (160, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (161, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (162, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
+
+	pftest (170, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (171, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (172, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+	pftest (173, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (174, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+	pftest (175, "v/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
+
+	/* Test error reporting. */
+
+	pftest (200, "x/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
+	pftest (201, "t/w & ( t/w | t/w ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
+	pftest (202, "t/w ) ", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
+	pftest (203, "!", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
+	pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
+	pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1);
+
+
+	if (error_count > 0) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("\nPacket Filtering Test - FAILED!\n");
+	  exit (EXIT_FAILURE);
+	}
+	text_color_set (DW_COLOR_REC);
+	dw_printf ("\nPacket Filtering Test - SUCCESS!\n");
+	exit (EXIT_SUCCESS);
+
+}
+
+static void pftest (int test_num, char *filter, char *monitor, int expected)
+{
+	int result;
+	packet_t pp;
+
+	text_color_set (DW_COLOR_DEBUG);
+	dw_printf ("test number %d\n", test_num);
+	
+	pp = ax25_from_text (monitor, 1);
+	assert (pp != NULL);
+
+	result = pfilter (0, 0, filter, pp);
+	if (result != expected) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Unexpected result for test number %d\n", test_num);
+	  error_count++;
+	}
+
+	ax25_delete (pp);
+}
+
+#endif /* if TEST */
+
+/* end pfilter.c */
+
+
diff --git a/pfilter.h b/pfilter.h
index c83d3dc..1171280 100644
--- a/pfilter.h
+++ b/pfilter.h
@@ -1,4 +1,4 @@
-
-/* pfilter.h */
-
+
+/* pfilter.h */
+
 int pfilter (int from_chan, int to_chan, char *filter, packet_t pp);
\ No newline at end of file
diff --git a/ptt.c b/ptt.c
index 3e6dcbd..749ada6 100644
--- a/ptt.c
+++ b/ptt.c
@@ -1,973 +1,1260 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
-
-
-/*------------------------------------------------------------------
- *
- * Module:      ptt.c
- *
- * Purpose:   	Activate the output control lines for push to talk (PTT) and other purposes.
- *		
- * Description:	Traditionally this is done with the RTS signal of the serial port.
- *
- *		If we have two radio channels and only one serial port, DTR
- *		can be used for the second channel.
- *
- *		If __WIN32__ is defined, we use the Windows interface.
- *		Otherwise we use the Linux interface.
- *
- * Version 0.9:	Add ability to use GPIO pins on Linux.
- *
- * Version 1.1: Add parallel printer port for x86 Linux only.
- *
- * Version 1.2: More than two radio channels.
- *		Generalize for additional signals besides PTT.
- *
- * References:	http://www.robbayer.com/files/serial-win.pdf
- *
- *		https://www.kernel.org/doc/Documentation/gpio.txt
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <sys/time.h>
-
-#if __WIN32__
-#include <windows.h>
-#else
-#include <sys/termios.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-/* So we can have more common code for fd. */
-typedef int HANDLE;
-#define INVALID_HANDLE_VALUE (-1)
-
-#endif
-
-#include "direwolf.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "ptt.h"
-
-
-#if __WIN32__
-
-#define RTS_ON(fd) 	EscapeCommFunction(fd,SETRTS);
-#define RTS_OFF(fd) 	EscapeCommFunction(fd,CLRRTS);
-#define DTR_ON(fd)    	EscapeCommFunction(fd,SETDTR);
-#define DTR_OFF(fd)	EscapeCommFunction(fd,CLRDTR);
-
-#else
-
-#define RTS_ON(fd) 	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_RTS;  ioctl (fd, TIOCMSET, &stuff); }
-#define RTS_OFF(fd) 	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); }
-#define DTR_ON(fd)    	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_DTR;  ioctl (fd, TIOCMSET, &stuff); }
-#define DTR_OFF(fd)	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_DTR;	ioctl (fd, TIOCMSET, &stuff); }
-
-#define LPT_IO_ADDR 0x378
-
-#endif
-
-
-#if TEST
-#define dw_printf printf
-#endif
-
-
-static int ptt_debug_level = 0;
-
-void ptt_set_debug(int debug)
-{
-	ptt_debug_level = debug;
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        ptt_init
- *
- * Purpose:    	Open serial port(s) used for PTT signals and set to proper state.
- *
- * Inputs:	audio_config_p		- Structure with communication parameters.
- *
- *		    for each channel we have:
- *
- *			ptt_method	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. 
- *			
- *			ptt_device	Name of serial port device.  
- *					 e.g. COM1 or /dev/ttyS0. 
- *			
- *			ptt_line	RTS or DTR when using serial port. 
- *			
- *			ptt_gpio	GPIO number.  Only used for Linux. 
- *					 Valid only when ptt_method is PTT_METHOD_GPIO. 
- *					
- *			ptt_lpt_bit	Bit number for parallel printer port.  
- *					 Bit 0 = pin 2, ..., bit 7 = pin 9. 
- *					 Valid only when ptt_method is PTT_METHOD_LPT. 
- *			
- *			ptt_invert	Invert the signal.  
- *					 Normally higher voltage means transmit or LED on. 
- *
- * Outputs:	Remember required information for future use.
- *
- * Description:	
- *
- *--------------------------------------------------------------------*/
-
-
-
-static struct audio_s *save_audio_config_p;	/* Save config information for later use. */
-
-static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES];	
-					/* Serial port handle or fd.  */
-					/* Could be the same for two channels */	
-					/* if using both RTS and DTR. */
-
-
-static char otnames[NUM_OCTYPES][8];
-
-void ptt_init (struct audio_s *audio_config_p)
-{
-	int ch;
-	HANDLE fd;
-#if __WIN32__
-#else
-	int using_gpio;
-#endif
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("ptt_init ( ... )\n");
-#endif
-
-	save_audio_config_p = audio_config_p;
-
-	strcpy (otnames[OCTYPE_PTT], "PTT");
-	strcpy (otnames[OCTYPE_DCD], "DCD");
-	strcpy (otnames[OCTYPE_FUTURE], "FUTURE");
-
-
-	for (ch = 0; ch < MAX_CHANS; ch++) {
-	  int ot;
-
-	  for (ot = 0; ot < NUM_OCTYPES; ot++) {
-
-	    ptt_fd[ch][ot] = INVALID_HANDLE_VALUE;
-
-	    if (ptt_debug_level >= 2) {
-
-	      text_color_set(DW_COLOR_DEBUG);
-              dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n",
-		ch,
-		otnames[ot],
-		audio_config_p->achan[ch].octrl[ot].ptt_method, 
-		audio_config_p->achan[ch].octrl[ot].ptt_device,
-		audio_config_p->achan[ch].octrl[ot].ptt_line,
-		audio_config_p->achan[ch].octrl[ot].ptt_gpio,
-		audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit,
-		audio_config_p->achan[ch].octrl[ot].ptt_invert);
-	    }
-	  }
-	}
-
-/*
- * Set up serial ports.
- */
-
-	for (ch = 0; ch < MAX_CHANS; ch++) {
-
-	  if (audio_config_p->achan[ch].valid) {
-	    int ot;
-
-	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
-
-	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_SERIAL) {
-
-#if __WIN32__
-#else
-	        /* Translate Windows device name into Linux name. */
-	        /* COM1 -> /dev/ttyS0, etc. */
-
-	        if (strncasecmp(audio_config_p->achan[ch].octrl[ot].ptt_device, "COM", 3) == 0) {
-	          int n = atoi (audio_config_p->achan[ch].octrl[ot].ptt_device + 3);
-	          text_color_set(DW_COLOR_INFO);
-	          dw_printf ("Converted %s device '%s'", audio_config_p->achan[ch].octrl[ot].ptt_device, otnames[ot]);
-	          if (n < 1) n = 1;
-	          sprintf (audio_config_p->achan[ch].octrl[ot].ptt_device, "/dev/ttyS%d", n-1);
-	          dw_printf (" to Linux equivalent '%s'\n", audio_config_p->achan[ch].octrl[ot].ptt_device);
-	        }
-#endif
-	        /* Can't open the same device more than once so we */
-	        /* need more logic to look for the case of multiple radio */
-	        /* channels using different pins of the same COM port. */
-
-	        /* Did some earlier channel use the same device name? */
-
-	        int same_device_used = 0;
-	        int j, k;
-
-	        for (j = ch; j >= 0; j--) {
-	          if (audio_config_p->achan[j].valid) {
-		    for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) {
-	              if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) {
-	                fd = ptt_fd[j][k];
-	                same_device_used = 1;
-	              }
-	            }
-	          }
-	        }
-
-	        if ( ! same_device_used) {
-	
-#if __WIN32__
-	          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, audio_config_p->achan[ch].octrl[ot].ptt_device);
-	          if (strncasecmp(bettername, "COM", 3) == 0) {
-	            int n;
-	            n = atoi(bettername+3);
-	            if (n >= 10) {
-	              strcpy (bettername, "\\\\.\\");
-	              strcat (bettername, audio_config_p->achan[ch].octrl[ot].ptt_device);
-	            }
-	          }
-	          fd = CreateFile(bettername,
-			GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
-#else
-
-		  /* O_NONBLOCK added in version 0.9. */
-		  /* Was hanging with some USB-serial adapters. */
-		  /* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */
-
-	          fd = open (audio_config_p->achan[ch].octrl[ot].ptt_device, O_RDONLY | O_NONBLOCK);
-#endif
-                }
-
-	        if (fd != INVALID_HANDLE_VALUE) {
-	          ptt_fd[ch][ot] = fd;
-	        }
-	        else {
-#if __WIN32__
-#else
-	          int e = errno;
-#endif
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("ERROR can't open device %s for channel %d PTT control.\n",
-			audio_config_p->achan[ch].octrl[ot].ptt_device, ch);
-#if __WIN32__
-#else
-	          dw_printf ("%s\n", strerror(errno));
-#endif
-	          /* Don't try using it later if device open failed. */
-
-	          audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE;
-	        }
-
-/*
- * Set initial state off.
- * ptt_set will invert output signal if appropriate.
- */	  
-	        ptt_set (ot, ch, 0);
-
-	      }    /* if serial method. */
-	    }	 /* for each output type. */
-	  }    /* if channel valid. */
-	}    /* For each channel. */
-
-
-/* 
- * Set up GPIO - for Linux only.
- */
-
-#if __WIN32__
-#else
-
-/*
- * Does any of them use GPIO?
- */
-
-	using_gpio = 0;
-	for (ch=0; ch<MAX_CHANS; ch++) {
-	  if (save_audio_config_p->achan[ch].valid) {
-	    int ot;
-	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
-	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
-	        using_gpio = 1;
-	      }
-	    }
-	  }
-	}
-
-	if (using_gpio) {
-	
-	  struct stat finfo;
-/*
- * Normally the device nodes are set up for access 
- * only by root.  Try to change it here so we don't
- * burden user with another configuration step.
- *
- * Does /sys/class/gpio/export even exist?
- */
-
-	  if (stat("/sys/class/gpio/export", &finfo) < 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("This system is not configured with the GPIO user interface.\n");
-	    dw_printf ("Use a different method for PTT control.\n");
-	    exit (1);
-	  }
-
-/*
- * Do we have permission to access it?
- *
- *	pi at raspberrypi /sys/class/gpio $ ls -l
- *	total 0
- *	--w------- 1 root root 4096 Aug 20 07:59 export
- *	lrwxrwxrwx 1 root root    0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
- *	--w------- 1 root root 4096 Aug 20 07:59 unexport
- */
-	  if (geteuid() != 0) {
-	    if ( ! (finfo.st_mode & S_IWOTH)) {
-	      int err;
-
-	      /* Try to change protection. */
-	      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. */
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("This system is not configured with the GPIO user interface.\n");
-	        dw_printf ("Use a different method for PTT control.\n");
-	        exit (1);
-	      }
-
-	      /* Did we succeed in changing the protection? */
-	      if ( ! (finfo.st_mode & S_IWOTH)) {
-	        text_color_set(DW_COLOR_ERROR);
-	        dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
-	        dw_printf ("Log in as root and type this command:\n");
-	        dw_printf ("    chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
-	        exit (1);
-	      }	   
-	    }
-	  }
-	}
-/*
- * We should now be able to create the device nodes for 
- * the pins we want to use.
- */
-	    
-	for (ch = 0; ch < MAX_CHANS; ch++) {
-	  if (save_audio_config_p->achan[ch].valid) {
-	    int ot;
-	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
-	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
-	        char stemp[80];
-	        struct stat finfo;
-	        int err;
-
-	        fd = open("/sys/class/gpio/export", O_WRONLY);
-	        if (fd < 0) {
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
-	          dw_printf ("Log in as root and type this command:\n");
-	          dw_printf ("    chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
-	          exit (1);
-	        }
-	        sprintf (stemp, "%d", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-	        if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
-	          int e = errno;
-	          /* Ignore EBUSY error which seems to mean */
-	          /* the device node already exists. */
-	          if (e != EBUSY) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e);
-	            dw_printf ("%s\n", strerror(e));
-	            exit (1);
-	          }
-	        }
-	        close (fd);
-
-/*
- * We will have the same permission problem if not root.
- * We only care about "direction" and "value".
- */
-	        sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-	        err = system (stemp);
-	        sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/value", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-	        err = system (stemp);
-
-	        sprintf (stemp, "/sys/class/gpio/gpio%d/value", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-
-	        if (stat(stemp, &finfo) < 0) {
-	          int e = errno;
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Failed to get status for %s \n", stemp);
-	          dw_printf ("%s\n", strerror(e));
-	          exit (1);
-	        }
-
-	        if (geteuid() != 0) {
-	          if ( ! (finfo.st_mode & S_IWOTH)) {
-	            text_color_set(DW_COLOR_ERROR);
-	            dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
-	            dw_printf ("Log in as root and type these commands:\n");
-	            dw_printf ("    chmod go+rw /sys/class/gpio/gpio%d/direction", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-	            dw_printf ("    chmod go+rw /sys/class/gpio/gpio%d/value", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-	            exit (1);
-	          }
-	        }
-
-/*
- * Set output direction with initial state off.
- */
-
-	        sprintf (stemp, "/sys/class/gpio/gpio%d/direction", audio_config_p->achan[ch].octrl[ot].ptt_gpio);
-	        fd = open(stemp, O_WRONLY);
-	        if (fd < 0) {
-	          int e = errno;
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Error opening %s\n", stemp);
-	          dw_printf ("%s\n", strerror(e));
-	          exit (1);
-	        }
-
-	        char hilo[8];
-	        if (audio_config_p->achan[ch].octrl[ot].ptt_invert) {
-	          strcpy (hilo, "high");
-	        }
-	        else {
-	          strcpy (hilo, "low");
-	        }
-	        if (write (fd, hilo, strlen(hilo)) != strlen(hilo)) {
-	          int e = errno;
-	          text_color_set(DW_COLOR_ERROR);
-	          dw_printf ("Error writing initial state to %s\n", stemp);
-	          dw_printf ("%s\n", strerror(e));
-	          exit (1);
-	        }
-	        close (fd);
-	      }
-	    }
-	  }
-	}
-#endif
-
-
-
-/*
- * Set up parallel printer port.
- * 
- * Restrictions:
- * 	Only the primary printer port.
- * 	For x86 Linux only.
- */
-
-#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
-
-	for (ch = 0; ch < MAX_CHANS; ch++) {
-	  if (save_audio_config_p->achan[ch].valid) {
-	    int ot;
-	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
-	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_LPT) {
-
-	        /* Can't open the same device more than once so we */
-	        /* need more logic to look for the case of mutiple radio */
-	        /* channels using different pins of the LPT port. */
-
-	        /* Did some earlier channel use the same ptt device name? */
-
-	        int same_device_used = 0;
-	        int j, k;
-	
-	        for (j = ch; j >= 0; j--) {
-	          if (audio_config_p->achan[j].valid) {
-		    for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) {
-	              if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) {
-	                fd = ptt_fd[j][k];
-	                same_device_used = 1;
-	              }
-	            }
-	          }
-	        }
-
-	        if ( ! same_device_used) {
-	          fd = open ("/dev/port", O_RDWR | O_NDELAY);
-	        }
-
-	        if (fd != INVALID_HANDLE_VALUE) {
-	          ptt_fd[ch][ot] = 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. */
-
-	          audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE;
-	        }
-	    
-
-/*
- * Set initial state off.
- * ptt_set will invert output signal if appropriate.
- */	  
-	        ptt_set (ot, ch, 0);
-
-	      }       /* if parallel printer port method. */
-	    }       /* for each output type */
-	  }       /* if valid channel. */
-	}	/* For each channel. */
-
-
-
-#endif /* x86 Linux */
-
-
-/* Why doesn't it transmit?  Probably forgot to specify PTT option. */
-
-	for (ch=0; ch<MAX_CHANS; ch++) {
-	  if (audio_config_p->achan[ch].valid) {
-	    if(audio_config_p->achan[ch].octrl[OCTYPE_PTT].ptt_method == PTT_METHOD_NONE) {
-	      text_color_set(DW_COLOR_INFO);
-	      dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch);
-	    }
-	  }
-	}
-
-} /* end ptt_init */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        ptt_set
- *
- * Purpose:    	Turn output control line on or off.
- *		Originally this was just for PTT, hence the name.
- *		Now that it is more general purpose, it should
- *		probably be renamed something like octrl_set.
- *
- * Inputs:	ot		- Output control type:
- *				   OCTYPE_PTT, OCTYPE_DCD, OCTYPE_FUTURE
- *
- *		chan		- channel, 0 .. (number of channels)-1
- *
- *		ptt_signal	- 1 for transmit, 0 for receive.
- *
- *
- * Assumption:	ptt_init was called first.
- *
- * Description:	Set the RTS or DTR line or GPIO pin.
- *		More positive output corresponds to 1 unless invert is set.
- *
- *--------------------------------------------------------------------*/
-
-
-void ptt_set (int ot, int chan, int ptt_signal)
-{
-
-	int ptt = ptt_signal;
-	int ptt2 = ptt_signal;
-
-	assert (ot >= 0 && ot < NUM_OCTYPES);
-	assert (chan >= 0 && chan < MAX_CHANS);
-
-	if (ptt_debug_level >= 1) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("%s %d = %d\n", otnames[ot], chan, ptt_signal);
-	}
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-
-	if ( ! save_audio_config_p->achan[chan].valid) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Internal error, ptt_set ( %s, %d, %d ), did not expect invalid channel.\n", otnames[ot], chan, ptt);
-	  return;
-	}
-
-/* 
- * Inverted output? 
- */
-
-	if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert) {
-	  ptt = ! ptt;
-	}
-	if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert2) {
-	  ptt2 = ! ptt2;
-	}
-
-/*
- * Using serial port?
- */
-	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_SERIAL && 
-		ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) {
-
-	  if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_RTS) {
-
-	    if (ptt) {
-	      RTS_ON(ptt_fd[chan][ot]);
-	    }
-	    else {
-	      RTS_OFF(ptt_fd[chan][ot]);
-	    }
-	  }
-	  else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_DTR) {
-
-	    if (ptt) {
-	      DTR_ON(ptt_fd[chan][ot]);
-	    }
-	    else {
-	      DTR_OFF(ptt_fd[chan][ot]);
-	    }
-	  }
-
-/* 
- * Second serial port control line?  Typically driven with opposite phase but could be in phase.
- */
-
-	  if (save_audio_config_p->achan[chan].octrl[ot].ptt_line2 == PTT_LINE_RTS) {
-
-	    if (ptt2) {
-	      RTS_ON(ptt_fd[chan][ot]);
-	    }
-	    else {
-	      RTS_OFF(ptt_fd[chan][ot]);
-	    }
-	  }
-	  else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line2 == PTT_LINE_DTR) {
-
-	    if (ptt2) {
-	      DTR_ON(ptt_fd[chan][ot]);
-	    }
-	    else {
-	      DTR_OFF(ptt_fd[chan][ot]);
-	    }
-	  }
-	  /* else neither one */
-
-	}
-
-/*
- * Using GPIO? 
- */
-
-#if __WIN32__
-#else
-
-	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
-	  int fd;
-	  char stemp[80];
-
-	  sprintf (stemp, "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio);
-
-	  fd = open(stemp, O_WRONLY);
-	  if (fd < 0) {
-	    int e = errno;
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Error opening %s to set %s signal.\n", stemp, otnames[ot]);
-	    dw_printf ("%s\n", strerror(e));
-	    return;
-	  }
-
-	  sprintf (stemp, "%d", ptt);
-
-	  if (write (fd, stemp, 1) != 1) {
-	    int e = errno;
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio, otnames[ot]);
-	    dw_printf ("%s\n", strerror(e));
-	  }
-	  close (fd);
-
-	}
-#endif
-	
-/*
- * Using parallel printer port?
- */
-
-#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
-
-	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_LPT && 
-		ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) {
-
-	  char lpt_data;
-	  ssize_t n;		
-
-	  lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET);
-	  if (read (ptt_fd[chan][ot], &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 %s\n", chan, otnames[ot]);
-	    dw_printf ("%s\n", strerror(e));
-	  }
-
-	  if (ptt) {
-	    lpt_data |= ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit );
-	  }
-	  else {
-	    lpt_data &= ~ ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit );
-	  }
-
-	  lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET);
-	  if (write (ptt_fd[chan][ot], &lpt_data, (size_t)1) != 1) {
-	    int e = errno;
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Error writing to LPT for channel %d %s\n", chan, otnames[ot]);
-	    dw_printf ("%s\n", strerror(e));
-	  }
-	}
-
-#endif /* x86 Linux */
-
-
-} /* end ptt_set */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        ptt_term
- *
- * Purpose:    	Make sure PTT and others are turned off when we exit.
- *
- * Inputs:	none
- *
- * Description:	
- *
- *--------------------------------------------------------------------*/
-
-void ptt_term (void)
-{
-	int n;
-
-	for (n = 0; n < MAX_CHANS; n++) {
-	  if (save_audio_config_p->achan[n].valid) {
-	    int ot;
-	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
-	      ptt_set (ot, n, 0);
-	    }
-	  }
-	}
-
-	for (n = 0; n < MAX_CHANS; n++) {
-	  if (save_audio_config_p->achan[n].valid) {
-	    int ot;
-	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
-	      if (ptt_fd[n][ot] != INVALID_HANDLE_VALUE) {
-#if __WIN32__
-	        CloseHandle (ptt_fd[n][ot]);
-#else
-	        close(ptt_fd[n][ot]);
-#endif
-	        ptt_fd[n][ot] = INVALID_HANDLE_VALUE;
-	      }
-	    }
-	  }
-	}
-}
-
-
-
-
-/*
- * Quick stand-alone test for above.
- *
- *    gcc -DTEST -o ptest ptt.c ; ./ptest
- *
- */
-
-
-#if TEST
-
-void text_color_set (dw_color_t c)  {  }
-
-#define dw_printf printf
-
-main ()
-{
-	struct audio_s my_audio_config;
-	int n;
-	int chan;
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-
-	my_audio_config.adev[0].num_channels = 2;
-
-	my_audio_config.valid[0] = 1;
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
-	//strcpy (my_audio_config.ptt_device, "COM1");
-	strcpy (my_audio_config.ptt_device, "/dev/ttyUSB0");
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_RTS;
-
-	my_audio_config.valid[1] = 1;
-	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
-	//strcpy (my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device, "COM1");
-	strcpy (my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0");
-	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_DTR;
-
-
-/* initialize - both off */
-
-	ptt_init (&my_audio_config);
-
-	SLEEP_SEC(2);
-
-/* flash each a few times. */
-
-	dw_printf ("turn on RTS a few times...\n");
-
-	chan = 0;
-	for (n=0; n<3; n++) {
-	  ptt_set (OCTYPE_PTT, chan, 1);
-	  SLEEP_SEC(1);
-	  ptt_set (OCTYPE_PTT, chan, 0);
-	  SLEEP_SEC(1);
-	}
-
-	dw_printf ("turn on DTR a few times...\n");
-
-	chan = 1;
-	for (n=0; n<3; n++) {
-	  ptt_set (OCTYPE_PTT, chan, 1);
-	  SLEEP_SEC(1);
-	  ptt_set (OCTYPE_PTT, chan, 0);
-	  SLEEP_SEC(1);
-	}
-
-	ptt_term();
-
-/* Same thing again but invert RTS. */
-
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_invert = 1;
-
-	ptt_init (&my_audio_config);
-
-	SLEEP_SEC(2);
-
-	dw_printf ("INVERTED -  RTS a few times...\n");
-
-	chan = 0;
-	for (n=0; n<3; n++) {
-	  ptt_set (OCTYPE_PTT, chan, 1);
-	  SLEEP_SEC(1);
-	  ptt_set (OCTYPE_PTT, chan, 0);
-	  SLEEP_SEC(1);
-	}
-
-	dw_printf ("turn on DTR a few times...\n");
-
-	chan = 1;
-	for (n=0; n<3; n++) {
-	  ptt_set (OCTYPE_PTT, chan, 1);
-	  SLEEP_SEC(1);
-	  ptt_set (OCTYPE_PTT, chan, 0);
-	  SLEEP_SEC(1);
-	}
-
-	ptt_term ();
-
-
-/* Test GPIO */
-
-#if __arm__
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-	my_audio_config.adev[0].num_channels = 1;
-	my_audio_config.valid[0] = 1;
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO;
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_gpio = 25;
-
-	dw_printf ("Try GPIO %d a few times...\n", my_audio_config.ptt_gpio[0]);
-
-	ptt_init (&my_audio_config);
-
-	SLEEP_SEC(2);
-	chan = 0;
-	for (n=0; n<3; n++) {
-	  ptt_set (OCTYPE_PTT, chan, 1);
-	  SLEEP_SEC(1);
-	  ptt_set (OCTYPE_PTT, chan, 0);
-	  SLEEP_SEC(1);
-	}
-
-	ptt_term ();
-#endif
-
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-	my_audio_config.num_channels = 2;
-	my_audio_config.valid[0] = 1;
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT;
-	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_lpt_bit = 0;
-	my_audio_config.valid[1] = 1;
-	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT;
-	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_lpt_bit = 1;
-
-	dw_printf ("Try LPT bits 0 & 1 a few times...\n");
-
-	ptt_init (&my_audio_config);
-
-	for (n=0; n<8; n++) {
-	  ptt_set (OCTYPE_PTT, 0, n & 1);
-	  ptt_set (OCTYPE_PTT, 1, (n>>1) & 1);
-	  SLEEP_SEC(1);
-	}
-
-	ptt_term ();	
-
-/* Parallel printer port. */
-
-#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
-
-	// TODO
-
-#endif
-
-
-}
-
-#endif  /* TEST */
-
-/* end ptt.c */
-
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013, 2014, 2015, 2016  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/>.
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      ptt.c
+ *
+ * Purpose:   	Activate the output control lines for push to talk (PTT) and other purposes.
+ *		
+ * Description:	Traditionally this is done with the RTS signal of the serial port.
+ *
+ *		If we have two radio channels and only one serial port, DTR
+ *		can be used for the second channel.
+ *
+ *		If __WIN32__ is defined, we use the Windows interface.
+ *		Otherwise we use the Linux interface.
+ *
+ * Version 0.9:	Add ability to use GPIO pins on Linux.
+ *
+ * Version 1.1: Add parallel printer port for x86 Linux only.
+ *
+ *		This is hardcoded to use the primary motherboard parallel
+ *		printer port at I/O address 0x378.  This might work with
+ *		a PCI card configured to use the same address if the 
+ *		motherboard does not have a built in parallel port.
+ *		It won't work with a USB-to-parallel-printer-port adapter.
+ *
+ * Version 1.2: More than two radio channels.
+ *		Generalize for additional signals besides PTT.
+ *
+ * Version 1.3:	HAMLIB support.
+ *
+ * References:	http://www.robbayer.com/files/serial-win.pdf
+ *
+ *		https://www.kernel.org/doc/Documentation/gpio.txt
+ *
+ *---------------------------------------------------------------*/
+
+/*
+	Idea for future enhancement:
+
+	A growing number of people have been asking about support for the DMK URI.
+	This uses a C-Media CM108/CM119 with one interesting addition, a GPIO
+	pin is used to drive PTT.  Here is some related information.
+
+	DMK URI:
+
+		http://www.dmkeng.com/URI_Order_Page.htm
+		http://dmkeng.com/images/URI%20Schematic.pdf
+		http://www.repeater-builder.com/voip/pdf/cm119-datasheet.pdf
+
+	Homebrew versions of the same idea:
+
+		http://images.ohnosec.org/usbfob.pdf
+		http://www.qsl.net/kb9mwr/projects/voip/usbfob-119.pdf
+		http://rtpdir.weebly.com/uploads/1/6/8/7/1687703/usbfob.pdf
+		http://www.repeater-builder.com/projects/fob/USB-Fob-Construction.pdf
+
+	Applications that have support for this:
+
+		http://docs.allstarlink.org/drupal/
+		http://soundmodem.sourcearchive.com/documentation/0.16-1/ptt_8c_source.html
+		https://github.com/N0NB/hamlib/blob/master/src/cm108.c#L190
+
+	Information about the "hidraw" device:
+
+		http://unix.stackexchange.com/questions/85379/dev-hidraw-read-permissions
+		http://www.signal11.us/oss/udev/
+		http://www.signal11.us/oss/hidapi/
+		https://github.com/signal11/hidapi/blob/master/libusb/hid.c
+		http://stackoverflow.com/questions/899008/howto-write-to-the-gpio-pin-of-the-cm108-chip-in-linux
+		https://www.kernel.org/doc/Documentation/hid/hidraw.txt
+
+	In version 1.3, we add HAMLIB support which should be able to do this.
+ 	(Linux only & haven't verified that it actually works yet!)
+	
+	Might want to have CM108 GPIO support built in, someday, for simpler building & configuration.
+	Maybe even for Windows.  ;-)
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+
+#if __WIN32__
+#include <windows.h>
+#else
+#include <sys/termios.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef USE_HAMLIB
+#include <hamlib/rig.h>
+#endif
+
+/* So we can have more common code for fd. */
+typedef int HANDLE;
+#define INVALID_HANDLE_VALUE (-1)
+
+#endif
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "ptt.h"
+
+
+#if __WIN32__
+
+#define RTS_ON(fd) 	EscapeCommFunction(fd,SETRTS);
+#define RTS_OFF(fd) 	EscapeCommFunction(fd,CLRRTS);
+#define DTR_ON(fd)    	EscapeCommFunction(fd,SETDTR);
+#define DTR_OFF(fd)	EscapeCommFunction(fd,CLRDTR);
+
+#else
+
+#define RTS_ON(fd) 	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_RTS;  ioctl (fd, TIOCMSET, &stuff); }
+#define RTS_OFF(fd) 	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); }
+#define DTR_ON(fd)    	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_DTR;  ioctl (fd, TIOCMSET, &stuff); }
+#define DTR_OFF(fd)	{ int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_DTR;	ioctl (fd, TIOCMSET, &stuff); }
+
+#define LPT_IO_ADDR 0x378
+
+#endif
+
+
+#if TEST
+#define dw_printf printf
+#endif
+
+
+static int ptt_debug_level = 0;
+
+void ptt_set_debug(int debug)
+{
+	ptt_debug_level = debug;
+}
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	export_gpio
+ *
+ * Purpose:	Tell the GPIO subsystem to export a GPIO line for
+ * 	us to use, and set the initial state of the GPIO.
+ *
+ * Inputs:	gpio		- GPIO line to export
+ *		invert:		- Is the GPIO active low?
+ *		direction:	- 0 for input, 1 for output
+ *
+ * Outputs:	None.
+ *
+ *------------------------------------------------------------------*/
+
+#ifndef __WIN32__
+
+void export_gpio(int gpio, int invert, int direction)
+{
+	HANDLE fd;
+	char stemp[80];
+	struct stat finfo;
+	int err;
+
+	fd = open("/sys/class/gpio/export", O_WRONLY);
+	if (fd < 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
+	  dw_printf ("Log in as root and type this command:\n");
+	  dw_printf ("    chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
+	  exit (1);
+	}
+	snprintf (stemp, sizeof(stemp), "%d", gpio);
+	if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
+	  int e = errno;
+	  /* Ignore EBUSY error which seems to mean */
+	  /* the device node already exists. */
+	  if (e != EBUSY) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e);
+	    dw_printf ("%s\n", strerror(e));
+	    exit (1);
+	  }
+	}
+	close (fd);
+
+/*
+	Idea for future:
+
+	On the RPi, the device path for GPIO number XX is /sys/class/gpio/gpioXX.
+	There was a report that it is different for the Cubieboard.  For instance
+	GPIO 61 has gpio61_pi13 in the path.  This indicates connector "i" pin 13.
+
+	For another similar single board computer, we find the same thing:
+	https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux
+
+	How should we deal with this?  Some possibilities:
+
+	(1) The user might explicitly mention the name in direwolf.conf.
+	(2) We might be able to find the names in some system device config file.
+	(3) Get a directory listing of /sys/class/gpio then search for a 
+		matching name.  Suppose we wanted GPIO 61.  First look for an exact
+		match to "gpio61".  If that is not found, look for something
+		matching the pattern "gpio61_*".
+*/
+
+/*
+ * We will have the same permission problem if not root.
+ * We only care about "direction" and "value".
+ */
+	snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", gpio);
+	err = system (stemp);
+	snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/value", gpio);
+	err = system (stemp);
+	(void)err;
+
+	snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", gpio);
+
+	if (stat(stemp, &finfo) < 0) {
+	  int e = errno;
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Failed to get status for %s \n", stemp);
+	  dw_printf ("%s\n", strerror(e));
+	  exit (1);
+	}
+
+	if (geteuid() != 0) {
+	  if ( ! (finfo.st_mode & S_IWOTH)) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
+	    dw_printf ("Log in as root and type these commands:\n");
+	    dw_printf ("    chmod go+rw /sys/class/gpio/gpio%d/direction", gpio);
+	    dw_printf ("    chmod go+rw /sys/class/gpio/gpio%d/value", gpio);
+	    exit (1);
+	  }
+	}
+
+/*
+ * Set output direction and initial state
+ */
+
+	snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/direction", gpio);
+	fd = open(stemp, O_WRONLY);
+	if (fd < 0) {
+	  int e = errno;
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Error opening %s\n", stemp);
+	  dw_printf ("%s\n", strerror(e));
+	  exit (1);
+	}
+
+	char gpio_val[8];
+	if (direction) {
+	  if (invert) {
+	    strlcpy (gpio_val, "high", sizeof(gpio_val));
+	  }
+	  else {
+	    strlcpy (gpio_val, "low", sizeof(gpio_val));
+	  }
+	}
+	else {
+	  strlcpy (gpio_val, "in", sizeof(gpio_val));
+	}
+	if (write (fd, gpio_val, strlen(gpio_val)) != strlen(gpio_val)) {
+	  int e = errno;
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Error writing initial state to %s\n", stemp);
+	  dw_printf ("%s\n", strerror(e));
+	  exit (1);
+	}
+	close (fd);
+}
+
+#endif   /* not __WIN32__ */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ptt_init
+ *
+ * Purpose:    	Open serial port(s) used for PTT signals and set to proper state.
+ *
+ * Inputs:	audio_config_p		- Structure with communication parameters.
+ *
+ *		    for each channel we have:
+ *
+ *			ptt_method	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. 
+ *                  			PTT_METHOD_HAMLIB - HAMLib rig control.
+ *			
+ *			ptt_device	Name of serial port device.  
+ *					 e.g. COM1 or /dev/ttyS0. 
+ *					 HAMLIB can also use hostaddr:port.
+ *			
+ *			ptt_line	RTS or DTR when using serial port. 
+ *			
+ *			ptt_gpio	GPIO number.  Only used for Linux. 
+ *					 Valid only when ptt_method is PTT_METHOD_GPIO. 
+ *					
+ *			ptt_lpt_bit	Bit number for parallel printer port.  
+ *					 Bit 0 = pin 2, ..., bit 7 = pin 9. 
+ *					 Valid only when ptt_method is PTT_METHOD_LPT. 
+ *			
+ *			ptt_invert	Invert the signal.  
+ *					 Normally higher voltage means transmit or LED on. 
+ *
+ *			ptt_model	Only for HAMLIB.
+ *					2 to communicate with rigctld.
+ *					>= 3 for specific radio model.
+ *					-1 guess at what is out there.  (AUTO option in config file.)
+ *
+ * Outputs:	Remember required information for future use.
+ *
+ * Description:	
+ *
+ *--------------------------------------------------------------------*/
+
+
+
+static struct audio_s *save_audio_config_p;	/* Save config information for later use. */
+
+static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES];	
+					/* Serial port handle or fd.  */
+					/* Could be the same for two channels */	
+					/* if using both RTS and DTR. */
+#if USE_HAMLIB
+static RIG *rig[MAX_CHANS][NUM_OCTYPES];
+#endif
+
+static char otnames[NUM_OCTYPES][8];
+
+void ptt_init (struct audio_s *audio_config_p)
+{
+	int ch;
+	HANDLE fd = INVALID_HANDLE_VALUE;
+#if __WIN32__
+#else
+	int using_gpio;
+#endif
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("ptt_init ( ... )\n");
+#endif
+
+	save_audio_config_p = audio_config_p;
+
+	strlcpy (otnames[OCTYPE_PTT], "PTT", sizeof(otnames[OCTYPE_PTT]));
+	strlcpy (otnames[OCTYPE_DCD], "DCD", sizeof(otnames[OCTYPE_DCD]));
+	strlcpy (otnames[OCTYPE_FUTURE], "FUTURE", sizeof(otnames[OCTYPE_FUTURE]));
+
+
+	for (ch = 0; ch < MAX_CHANS; ch++) {
+	  int ot;
+
+	  for (ot = 0; ot < NUM_OCTYPES; ot++) {
+
+	    ptt_fd[ch][ot] = INVALID_HANDLE_VALUE;
+#if USE_HAMLIB
+	    rig[ch][ot] = NULL;
+#endif
+	    if (ptt_debug_level >= 2) {
+
+	      text_color_set(DW_COLOR_DEBUG);
+              dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n",
+		ch,
+		otnames[ot],
+		audio_config_p->achan[ch].octrl[ot].ptt_method, 
+		audio_config_p->achan[ch].octrl[ot].ptt_device,
+		audio_config_p->achan[ch].octrl[ot].ptt_line,
+		audio_config_p->achan[ch].octrl[ot].ptt_gpio,
+		audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit,
+		audio_config_p->achan[ch].octrl[ot].ptt_invert);
+	    }
+	  }
+	}
+
+/*
+ * Set up serial ports.
+ */
+
+	for (ch = 0; ch < MAX_CHANS; ch++) {
+
+	  if (audio_config_p->achan[ch].valid) {
+	    int ot;
+
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+
+	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_SERIAL) {
+
+#if __WIN32__
+#else
+	        /* Translate Windows device name into Linux name. */
+	        /* COM1 -> /dev/ttyS0, etc. */
+
+	        if (strncasecmp(audio_config_p->achan[ch].octrl[ot].ptt_device, "COM", 3) == 0) {
+	          int n = atoi (audio_config_p->achan[ch].octrl[ot].ptt_device + 3);
+	          text_color_set(DW_COLOR_INFO);
+	          dw_printf ("Converted %s device '%s'", audio_config_p->achan[ch].octrl[ot].ptt_device, otnames[ot]);
+	          if (n < 1) n = 1;
+	          snprintf (audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(audio_config_p->achan[ch].octrl[ot].ptt_device), "/dev/ttyS%d", n-1);
+	          dw_printf (" to Linux equivalent '%s'\n", audio_config_p->achan[ch].octrl[ot].ptt_device);
+	        }
+#endif
+	        /* Can't open the same device more than once so we */
+	        /* need more logic to look for the case of multiple radio */
+	        /* channels using different pins of the same COM port. */
+
+	        /* Did some earlier channel use the same device name? */
+
+	        int same_device_used = 0;
+	        int j, k;
+
+	        for (j = ch; j >= 0; j--) {
+	          if (audio_config_p->achan[j].valid) {
+		    for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) {
+	              if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) {
+	                fd = ptt_fd[j][k];
+	                same_device_used = 1;
+	              }
+	            }
+	          }
+	        }
+
+	        if ( ! same_device_used) {
+	
+#if __WIN32__
+	          char bettername[50];
+	          // Bug fix in release 1.1 - Need to munge name for COM10 and up.
+	          // http://support.microsoft.com/kb/115831
+
+	          strlcpy (bettername, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(bettername));
+	          if (strncasecmp(bettername, "COM", 3) == 0) {
+	            int n;
+	            n = atoi(bettername+3);
+	            if (n >= 10) {
+	              strlcpy (bettername, "\\\\.\\", sizeof(bettername));
+	              strlcat (bettername, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(bettername));
+	            }
+	          }
+	          fd = CreateFile(bettername,
+			GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+#else
+
+		  /* O_NONBLOCK added in version 0.9. */
+		  /* Was hanging with some USB-serial adapters. */
+		  /* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */
+
+	          fd = open (audio_config_p->achan[ch].octrl[ot].ptt_device, O_RDONLY | O_NONBLOCK);
+#endif
+                }
+
+	        if (fd != INVALID_HANDLE_VALUE) {
+	          ptt_fd[ch][ot] = fd;
+	        }
+	        else {
+#if __WIN32__
+#else
+	          int e = errno;
+#endif
+	          text_color_set(DW_COLOR_ERROR);
+	          dw_printf ("ERROR can't open device %s for channel %d PTT control.\n",
+			audio_config_p->achan[ch].octrl[ot].ptt_device, ch);
+#if __WIN32__
+#else
+	          dw_printf ("%s\n", strerror(e));
+#endif
+	          /* Don't try using it later if device open failed. */
+
+	          audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE;
+	        }
+
+/*
+ * Set initial state off.
+ * ptt_set will invert output signal if appropriate.
+ */	  
+	        ptt_set (ot, ch, 0);
+
+	      }    /* if serial method. */
+	    }	 /* for each output type. */
+	  }    /* if channel valid. */
+	}    /* For each channel. */
+
+
+/* 
+ * Set up GPIO - for Linux only.
+ */
+
+#if __WIN32__
+#else
+
+/*
+ * Does any of them use GPIO?
+ */
+
+	using_gpio = 0;
+	for (ch=0; ch<MAX_CHANS; ch++) {
+	  if (save_audio_config_p->achan[ch].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
+	        using_gpio = 1;
+	      }
+	    }
+	    for (ot = 0; ot < NUM_ICTYPES; ot++) {
+	      if (audio_config_p->achan[ch].ictrl[ot].method == PTT_METHOD_GPIO) {
+	        using_gpio = 1;
+	      }
+	    }
+	  }
+	}
+
+	if (using_gpio) {
+	
+	  struct stat finfo;
+/*
+ * Normally the device nodes are set up for access 
+ * only by root.  Try to change it here so we don't
+ * burden user with another configuration step.
+ *
+ * Does /sys/class/gpio/export even exist?
+ */
+
+	  if (stat("/sys/class/gpio/export", &finfo) < 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("This system is not configured with the GPIO user interface.\n");
+	    dw_printf ("Use a different method for PTT control.\n");
+	    exit (1);
+	  }
+
+/*
+ * Do we have permission to access it?
+ *
+ *	pi at raspberrypi /sys/class/gpio $ ls -l
+ *	total 0
+ *	--w------- 1 root root 4096 Aug 20 07:59 export
+ *	lrwxrwxrwx 1 root root    0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
+ *	--w------- 1 root root 4096 Aug 20 07:59 unexport
+ */
+	  if (geteuid() != 0) {
+	    if ( ! (finfo.st_mode & S_IWOTH)) {
+	      int err;
+
+	      /* Try to change protection. */
+	      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. */
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("This system is not configured with the GPIO user interface.\n");
+	        dw_printf ("Use a different method for PTT control.\n");
+	        exit (1);
+	      }
+
+	      /* Did we succeed in changing the protection? */
+	      if ( ! (finfo.st_mode & S_IWOTH)) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
+	        dw_printf ("Log in as root and type this command:\n");
+	        dw_printf ("    chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
+	        exit (1);
+	      }	   
+	    }
+	  }
+	}
+/*
+ * We should now be able to create the device nodes for 
+ * the pins we want to use.
+ */
+	    
+	for (ch = 0; ch < MAX_CHANS; ch++) {
+	  if (save_audio_config_p->achan[ch].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
+	        export_gpio(audio_config_p->achan[ch].octrl[ot].ptt_gpio, audio_config_p->achan[ch].octrl[ot].ptt_invert, 1);
+	      }
+	    }
+	    for (ot = 0; ot < NUM_ICTYPES; ot++) {
+	      if (audio_config_p->achan[ch].ictrl[ot].method == PTT_METHOD_GPIO) {
+	        export_gpio(audio_config_p->achan[ch].ictrl[ot].gpio, audio_config_p->achan[ch].ictrl[ot].invert, 0);
+	      }
+	    }
+	  }
+	}
+#endif
+
+
+
+/*
+ * Set up parallel printer port.
+ * 
+ * Restrictions:
+ * 	Only the primary printer port.
+ * 	For x86 Linux only.
+ */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+	for (ch = 0; ch < MAX_CHANS; ch++) {
+	  if (save_audio_config_p->achan[ch].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_LPT) {
+
+	        /* Can't open the same device more than once so we */
+	        /* need more logic to look for the case of mutiple radio */
+	        /* channels using different pins of the LPT port. */
+
+	        /* Did some earlier channel use the same ptt device name? */
+
+	        int same_device_used = 0;
+	        int j, k;
+	
+	        for (j = ch; j >= 0; j--) {
+	          if (audio_config_p->achan[j].valid) {
+		    for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) {
+	              if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) {
+	                fd = ptt_fd[j][k];
+	                same_device_used = 1;
+	              }
+	            }
+	          }
+	        }
+
+	        if ( ! same_device_used) {
+	          fd = open ("/dev/port", O_RDWR | O_NDELAY);
+	        }
+
+	        if (fd != INVALID_HANDLE_VALUE) {
+	          ptt_fd[ch][ot] = 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(e));
+	          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. */
+
+	          audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE;
+	        }
+	    
+
+/*
+ * Set initial state off.
+ * ptt_set will invert output signal if appropriate.
+ */	  
+	        ptt_set (ot, ch, 0);
+
+	      }       /* if parallel printer port method. */
+	    }       /* for each output type */
+	  }       /* if valid channel. */
+	}	/* For each channel. */
+
+
+
+#endif /* x86 Linux */
+
+#ifdef USE_HAMLIB
+	for (ch = 0; ch < MAX_CHANS; ch++) {
+	  if (save_audio_config_p->achan[ch].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) {
+	        if (ot == OCTYPE_PTT) {
+
+	          /* For "AUTO" model, try to guess what is out there. */
+
+	          if (audio_config_p->achan[ch].octrl[ot].ptt_model == -1) {
+	            hamlib_port_t hport;
+
+	            memset (&hport, 0, sizeof(hport));
+	            strlcpy (hport.pathname, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(hport.pathname));
+	            rig_load_all_backends();
+                    audio_config_p->achan[ch].octrl[ot].ptt_model = rig_probe(&hport);
+
+	            if (audio_config_p->achan[ch].octrl[ot].ptt_model == RIG_MODEL_NONE) {
+	              text_color_set(DW_COLOR_ERROR);
+	              dw_printf ("Couldn't guess rig model number for AUTO option.  Run \"rigctl --list\" for a list of model numbers.\n");
+	              continue;
+	            }
+
+	            text_color_set(DW_COLOR_INFO);
+	            dw_printf ("Hamlib AUTO option detected rig model %d.  Run \"rigctl --list\" for a list of model numbers.\n",
+							audio_config_p->achan[ch].octrl[ot].ptt_model);
+	          }
+
+	          rig[ch][ot] = rig_init(audio_config_p->achan[ch].octrl[ot].ptt_model);
+	          if (rig[ch][ot] == NULL) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Unknown rig model %d for hamlib.  Run \"rigctl --list\" for a list of model numbers.\n", 
+	                          audio_config_p->achan[ch].octrl[ot].ptt_model);
+	            continue;
+	          }
+
+	          strlcpy (rig[ch][ot]->state.rigport.pathname, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(rig[ch][ot]->state.rigport.pathname));
+	          int err = rig_open(rig[ch][ot]);
+	          if (err != RIG_OK) {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("Hamlib Rig open error %d: %s\n", err, rigerror(err));
+	            rig_cleanup (rig[ch][ot]);
+	            rig[ch][ot] = NULL;
+	            continue;
+	          }
+
+       		  /* Successful.  Later code should check for rig[ch][ot] not NULL. */
+	        }
+	        else {
+                  text_color_set(DW_COLOR_ERROR);
+                  dw_printf ("HAMLIB can only be used for PTT.  Not DCD or other output.\n");
+	        }
+	      }
+	    }
+	  }
+	}
+
+#endif
+
+
+/* Why doesn't it transmit?  Probably forgot to specify PTT option. */
+
+	for (ch=0; ch<MAX_CHANS; ch++) {
+	  if (audio_config_p->achan[ch].valid) {
+	    if(audio_config_p->achan[ch].octrl[OCTYPE_PTT].ptt_method == PTT_METHOD_NONE) {
+	      text_color_set(DW_COLOR_INFO);
+	      dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch);
+	    }
+	  }
+	}
+
+} /* end ptt_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ptt_set
+ *
+ * Purpose:    	Turn output control line on or off.
+ *		Originally this was just for PTT, hence the name.
+ *		Now that it is more general purpose, it should
+ *		probably be renamed something like octrl_set.
+ *
+ * Inputs:	ot		- Output control type:
+ *				   OCTYPE_PTT, OCTYPE_DCD, OCTYPE_HAMLIB, OCTYPE_FUTURE
+ *
+ *		chan		- channel, 0 .. (number of channels)-1
+ *
+ *		ptt_signal	- 1 for transmit, 0 for receive.
+ *
+ *
+ * Assumption:	ptt_init was called first.
+ *
+ * Description:	Set the RTS or DTR line or GPIO pin.
+ *		More positive output corresponds to 1 unless invert is set.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void ptt_set (int ot, int chan, int ptt_signal)
+{
+
+	int ptt = ptt_signal;
+	int ptt2 = ptt_signal;
+
+	assert (ot >= 0 && ot < NUM_OCTYPES);
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+	if (ptt_debug_level >= 1) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("%s %d = %d\n", otnames[ot], chan, ptt_signal);
+	}
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+	if ( ! save_audio_config_p->achan[chan].valid) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error, ptt_set ( %s, %d, %d ), did not expect invalid channel.\n", otnames[ot], chan, ptt);
+	  return;
+	}
+
+/* 
+ * Inverted output? 
+ */
+
+	if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert) {
+	  ptt = ! ptt;
+	}
+	if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert2) {
+	  ptt2 = ! ptt2;
+	}
+
+/*
+ * Using serial port?
+ */
+	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_SERIAL && 
+		ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) {
+
+	  if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_RTS) {
+
+	    if (ptt) {
+	      RTS_ON(ptt_fd[chan][ot]);
+	    }
+	    else {
+	      RTS_OFF(ptt_fd[chan][ot]);
+	    }
+	  }
+	  else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_DTR) {
+
+	    if (ptt) {
+	      DTR_ON(ptt_fd[chan][ot]);
+	    }
+	    else {
+	      DTR_OFF(ptt_fd[chan][ot]);
+	    }
+	  }
+
+/* 
+ * Second serial port control line?  Typically driven with opposite phase but could be in phase.
+ */
+
+	  if (save_audio_config_p->achan[chan].octrl[ot].ptt_line2 == PTT_LINE_RTS) {
+
+	    if (ptt2) {
+	      RTS_ON(ptt_fd[chan][ot]);
+	    }
+	    else {
+	      RTS_OFF(ptt_fd[chan][ot]);
+	    }
+	  }
+	  else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line2 == PTT_LINE_DTR) {
+
+	    if (ptt2) {
+	      DTR_ON(ptt_fd[chan][ot]);
+	    }
+	    else {
+	      DTR_OFF(ptt_fd[chan][ot]);
+	    }
+	  }
+	  /* else neither one */
+
+	}
+
+/*
+ * Using GPIO? 
+ */
+
+#if __WIN32__
+#else
+
+	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
+	  int fd;
+	  char stemp[80];
+
+	  snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio);
+
+	  fd = open(stemp, O_WRONLY);
+	  if (fd < 0) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error opening %s to set %s signal.\n", stemp, otnames[ot]);
+	    dw_printf ("%s\n", strerror(e));
+	    return;
+	  }
+
+	  snprintf (stemp, sizeof(stemp), "%d", ptt);
+
+	  if (write (fd, stemp, 1) != 1) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio, otnames[ot]);
+	    dw_printf ("%s\n", strerror(e));
+	  }
+	  close (fd);
+
+	}
+#endif
+	
+/*
+ * Using parallel printer port?
+ */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_LPT && 
+		ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) {
+
+	  char lpt_data;
+	  //ssize_t n;		
+
+	  lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET);
+	  if (read (ptt_fd[chan][ot], &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 %s\n", chan, otnames[ot]);
+	    dw_printf ("%s\n", strerror(e));
+	  }
+
+	  if (ptt) {
+	    lpt_data |= ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit );
+	  }
+	  else {
+	    lpt_data &= ~ ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit );
+	  }
+
+	  lseek (ptt_fd[chan][ot], (off_t)LPT_IO_ADDR, SEEK_SET);
+	  if (write (ptt_fd[chan][ot], &lpt_data, (size_t)1) != 1) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error writing to LPT for channel %d %s\n", chan, otnames[ot]);
+	    dw_printf ("%s\n", strerror(e));
+	  }
+	}
+
+#endif /* x86 Linux */
+
+#ifdef USE_HAMLIB
+/*
+ * Using hamlib?
+ */
+
+	if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) {
+
+	  if (rig[chan][ot] != NULL) {
+
+	    int retcode = rig_set_ptt(rig[chan][ot], RIG_VFO_CURR, ptt ? RIG_PTT_ON : RIG_PTT_OFF);
+
+	    if (retcode != RIG_OK) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Error sending rig_set_ptt command for channel %d %s\n", chan, otnames[ot]);
+	      dw_printf ("%s\n", rigerror(retcode));
+	    }
+	  }
+	  else {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Can't use rig_set_ptt for channel %d %s because rig_open failed.\n", chan, otnames[ot]);
+	  }
+	}
+#endif
+
+
+} /* end ptt_set */
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	get_input
+ *
+ * Purpose:	Read the value of an input line
+ *
+ * Inputs:	it	- Input type (ICTYPE_TCINH supported so far)
+ * 		chan	- Audio channel number
+ * 
+ * Outputs:	0 = inactive, 1 = active, -1 = error
+ *
+ * ------------------------------------------------------------------*/
+
+int get_input (int it, int chan)
+{
+	assert (it >= 0 && it < NUM_ICTYPES);
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+	if ( ! save_audio_config_p->achan[chan].valid) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Internal error, get_input ( %d, %d ), did not expect invalid channel.\n", it, chan);
+	  return -1;
+	}
+	
+#if __WIN32__
+#else
+	if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) {
+	  int fd;
+	  char stemp[80];
+
+	  snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].ictrl[it].gpio);
+
+	  fd = open(stemp, O_RDONLY);
+	  if (fd < 0) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error opening %s to check input.\n", stemp);
+	    dw_printf ("%s\n", strerror(e));
+	    return -1;
+	  }
+
+	  char vtemp[2];
+	  if (read (fd, vtemp, 1) != 1) {
+	    int e = errno;
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error getting GPIO %d value\n", save_audio_config_p->achan[chan].ictrl[it].gpio);
+	    dw_printf ("%s\n", strerror(e));
+	  }
+	  close (fd);
+
+	  if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) {
+	    return 1;
+	  }
+	  else {
+	    return 0;
+	  }
+	}
+#endif
+
+	return -1;	/* Method was none, or something went wrong */
+}
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ptt_term
+ *
+ * Purpose:    	Make sure PTT and others are turned off when we exit.
+ *
+ * Inputs:	none
+ *
+ * Description:	
+ *
+ *--------------------------------------------------------------------*/
+
+void ptt_term (void)
+{
+	int n;
+
+	for (n = 0; n < MAX_CHANS; n++) {
+	  if (save_audio_config_p->achan[n].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      ptt_set (ot, n, 0);
+	    }
+	  }
+	}
+
+	for (n = 0; n < MAX_CHANS; n++) {
+	  if (save_audio_config_p->achan[n].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      if (ptt_fd[n][ot] != INVALID_HANDLE_VALUE) {
+#if __WIN32__
+	        CloseHandle (ptt_fd[n][ot]);
+#else
+	        close(ptt_fd[n][ot]);
+#endif
+	        ptt_fd[n][ot] = INVALID_HANDLE_VALUE;
+	      }
+	    }
+	  }
+	}
+
+#ifdef USE_HAMLIB
+
+	for (n = 0; n < MAX_CHANS; n++) {
+	  if (save_audio_config_p->achan[n].valid) {
+	    int ot;
+	    for (ot = 0; ot < NUM_OCTYPES; ot++) {
+	      if (rig[n][ot] != NULL) {
+
+	        rig_close(rig[n][ot]);
+	        rig_cleanup(rig[n][ot]);
+	        rig[n][ot] = NULL;
+	      }
+	    }
+	  }
+	}
+#endif
+}
+
+
+
+
+/*
+ * Quick stand-alone test for above.
+ *
+ *    gcc -DTEST -o ptest ptt.c ; ./ptest
+ *
+ */
+
+
+#if TEST
+
+void text_color_set (dw_color_t c)  {  }
+
+#define dw_printf printf
+
+main ()
+{
+	struct audio_s my_audio_config;
+	int n;
+	int chan;
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+
+	my_audio_config.adev[0].num_channels = 2;
+
+	my_audio_config.valid[0] = 1;
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
+	//strlcpy (my_audio_config.ptt_device, "COM1", sizeof(my_audio_config.ptt_device));
+	strlcpy (my_audio_config.ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.ptt_device));
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_RTS;
+
+	my_audio_config.valid[1] = 1;
+	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
+	//strlcpy (my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device, "COM1", sizeof(my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device));
+	strlcpy (my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device));
+	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_DTR;
+
+
+/* initialize - both off */
+
+	ptt_init (&my_audio_config);
+
+	SLEEP_SEC(2);
+
+/* flash each a few times. */
+
+	dw_printf ("turn on RTS a few times...\n");
+
+	chan = 0;
+	for (n=0; n<3; n++) {
+	  ptt_set (OCTYPE_PTT, chan, 1);
+	  SLEEP_SEC(1);
+	  ptt_set (OCTYPE_PTT, chan, 0);
+	  SLEEP_SEC(1);
+	}
+
+	dw_printf ("turn on DTR a few times...\n");
+
+	chan = 1;
+	for (n=0; n<3; n++) {
+	  ptt_set (OCTYPE_PTT, chan, 1);
+	  SLEEP_SEC(1);
+	  ptt_set (OCTYPE_PTT, chan, 0);
+	  SLEEP_SEC(1);
+	}
+
+	ptt_term();
+
+/* Same thing again but invert RTS. */
+
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_invert = 1;
+
+	ptt_init (&my_audio_config);
+
+	SLEEP_SEC(2);
+
+	dw_printf ("INVERTED -  RTS a few times...\n");
+
+	chan = 0;
+	for (n=0; n<3; n++) {
+	  ptt_set (OCTYPE_PTT, chan, 1);
+	  SLEEP_SEC(1);
+	  ptt_set (OCTYPE_PTT, chan, 0);
+	  SLEEP_SEC(1);
+	}
+
+	dw_printf ("turn on DTR a few times...\n");
+
+	chan = 1;
+	for (n=0; n<3; n++) {
+	  ptt_set (OCTYPE_PTT, chan, 1);
+	  SLEEP_SEC(1);
+	  ptt_set (OCTYPE_PTT, chan, 0);
+	  SLEEP_SEC(1);
+	}
+
+	ptt_term ();
+
+
+/* Test GPIO */
+
+#if __arm__
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+	my_audio_config.adev[0].num_channels = 1;
+	my_audio_config.valid[0] = 1;
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO;
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_gpio = 25;
+
+	dw_printf ("Try GPIO %d a few times...\n", my_audio_config.ptt_gpio[0]);
+
+	ptt_init (&my_audio_config);
+
+	SLEEP_SEC(2);
+	chan = 0;
+	for (n=0; n<3; n++) {
+	  ptt_set (OCTYPE_PTT, chan, 1);
+	  SLEEP_SEC(1);
+	  ptt_set (OCTYPE_PTT, chan, 0);
+	  SLEEP_SEC(1);
+	}
+
+	ptt_term ();
+#endif
+
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+	my_audio_config.num_channels = 2;
+	my_audio_config.valid[0] = 1;
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT;
+	my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_lpt_bit = 0;
+	my_audio_config.valid[1] = 1;
+	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT;
+	my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_lpt_bit = 1;
+
+	dw_printf ("Try LPT bits 0 & 1 a few times...\n");
+
+	ptt_init (&my_audio_config);
+
+	for (n=0; n<8; n++) {
+	  ptt_set (OCTYPE_PTT, 0, n & 1);
+	  ptt_set (OCTYPE_PTT, 1, (n>>1) & 1);
+	  SLEEP_SEC(1);
+	}
+
+	ptt_term ();	
+
+/* Parallel printer port. */
+
+#if  ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
+
+	// TODO
+
+#endif
+
+
+}
+
+#endif  /* TEST */
+
+/* end ptt.c */
+
+
+
diff --git a/ptt.h b/ptt.h
index fa1cf9e..6e75253 100644
--- a/ptt.h
+++ b/ptt.h
@@ -1,25 +1,26 @@
-
-
-#ifndef PTT_H
-#define PTT_H 1
-
-
-#include "audio.h"	/* for struct audio_s and definitions for octype values */
-
-
-void ptt_set_debug(int debug);
-
-void ptt_init (struct audio_s *p_modem);
-
-void ptt_set (int octype, int chan, int ptt); 
-
-void ptt_term (void);
-
-
-#endif
-
-
-/* end ptt.h */
-
-
-
+
+
+#ifndef PTT_H
+#define PTT_H 1
+
+
+#include "audio.h"	/* for struct audio_s and definitions for octype values */
+
+
+void ptt_set_debug(int debug);
+
+void ptt_init (struct audio_s *p_modem);
+
+void ptt_set (int octype, int chan, int ptt); 
+
+void ptt_term (void);
+
+int get_input (int it, int chan);
+
+#endif
+
+
+/* end ptt.h */
+
+
+
diff --git a/rdq.c b/rdq.c
index 2dcd86e..cbfc2cf 100644
--- a/rdq.c
+++ b/rdq.c
@@ -1,374 +1,374 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2015  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/>.
-//
-
-
-
-/*------------------------------------------------------------------
- *
- * Module:      rdq.c
- *
- * Purpose:   	Retry later decode queue for frames with bad FCS.
- *		
- * Description:	
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "rdq.h"
-#include "dedupe.h"
-
-
-
-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 */
-
-
-
-static dw_mutex_t rdq_mutex;			/* Critical section for updating queues. */
-
-
-#if __WIN32__
-
-static HANDLE wake_up_event;			/* Notify try decode again thread when queue not empty. */
-
-#else
-
-static pthread_cond_t wake_up_cond;		/* Notify try decode again thread when queue not empty. */
-
-static dw_mutex_t wake_up_mutex;		/* Required by cond_wait. */
-
-#endif
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        rdq_init
- *
- * Purpose:     Initialize the receive decode again queue.
- *
- * Inputs:	None.  Only single queue for all channels.
- *
- * Outputs:	
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *--------------------------------------------------------------------*/
-
-
-void rdq_init (void)
-{
-	//int c, p;
-#if __WIN32__
-#else
-	int err;
-#endif
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_init (  )\n");
-	dw_printf ("rdq_init: pthread_mutex_init...\n");
-#endif
-
-	dw_mutex_init (&rdq_mutex);
-
-#if __WIN32__
-#else
-	dw_mutex_init (&wake_up_mutex);
-#endif
-
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_init: pthread_cond_init...\n");
-#endif
-
-#if __WIN32__
-
-	wake_up_event = CreateEvent (NULL, 0, 0, NULL);
-
-	if (wake_up_event == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("rdq_init: pthread_cond_init: can't create decode wake up event");
-	  exit (1);
-	}
-
-#else
-	err = pthread_cond_init (&wake_up_cond, NULL);
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_init: pthread_cond_init returns %d\n", err);
-#endif
-
-
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("rdq_init: pthread_cond_init err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-#endif
-
-
-} /* end rdq_init */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        rdq_append
- *
- * Purpose:     Add a packet to the end of the queue.
- *
- * Inputs:	pp	- Address of raw received bit buffer.
- *				Caller should NOT make any references to
- *				it after this point because it could
- *				be deleted at any time.
- *
- * Outputs:	
- *
- * Description:	Add buffer to end of linked list.
- *		Signal the decode thread if the queue was formerly empty.
- *
- *--------------------------------------------------------------------*/
-
-void rdq_append (rrbb_t rrbb)
-{
-	//int was_empty;
-	rrbb_t plast;
-	rrbb_t pnext;
-#ifndef __WIN32__
-	int err;
-#endif
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_append (rrbb=%p)\n", rrbb);
-	dw_printf ("rdq_append: enter critical section\n");
-#endif
-
-
-	dw_mutex_lock (&rdq_mutex);
-
-	//was_empty = 1;
-	//if (queue_head != NULL) {
-	       //was_empty = 0;
-	//}
-	if (queue_head == NULL) {
-	  queue_head = rrbb;
-	}
-	else {
-	  plast = queue_head;
-	  while ((pnext = rrbb_get_nextp(plast)) != NULL) {
-	    plast = pnext;
-	  }
-	  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);
-	}
-
-	dw_mutex_unlock (&rdq_mutex);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_append: left critical section\n");
-	dw_printf ("rdq_append (): about to wake up retry decode thread.\n");
-#endif
-
-#if __WIN32__
-	SetEvent (wake_up_event);
-#else
-	dw_mutex_lock (&wake_up_mutex);
-
-	err = pthread_cond_signal (&wake_up_cond);
-	if (err != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("rdq_append: pthread_cond_signal err=%d", err);
-	  perror ("");
-	  exit (1);
-	}
-
-	dw_mutex_unlock (&wake_up_mutex);
-#endif
-
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        rdq_wait_while_empty
- *
- * Purpose:     Sleep while the queue is empty rather than
- *		polling periodically.
- *
- * Inputs:	None.
- *		
- *--------------------------------------------------------------------*/
-
-
-void rdq_wait_while_empty (void)
-{
-	int is_empty;
-#ifndef __WIN32__
-	int err;
-#endif
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_wait_while_empty () : enter critical section\n");
-#endif
-
-	dw_mutex_lock (&rdq_mutex);
-
-#if DEBUG
-	//text_color_set(DW_COLOR_DEBUG);
-	//dw_printf ("rdq_wait_while_empty (): after pthread_mutex_lock\n");
-#endif
-	is_empty = 1;
-        if (queue_head != NULL)
-	       is_empty = 0;
-
-	dw_mutex_unlock (&rdq_mutex);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_wait_while_empty () : left critical section\n");
-#endif
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_wait_while_empty (): is_empty = %d\n", is_empty);
-#endif
-
-	if (is_empty) {
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("rdq_wait_while_empty (): SLEEP - about to call cond wait\n");
-#endif
-
-
-#if __WIN32__
-	  WaitForSingleObject (wake_up_event, INFINITE);
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("rdq_wait_while_empty (): returned from wait\n");
-#endif
-
-#else
-	  dw_mutex_lock (&wake_up_mutex);
-
-	  err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex);
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("rdq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err);
-#endif
-
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("rdq_wait_while_empty: pthread_cond_wait err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  dw_mutex_unlock (&wake_up_mutex);
-
-#endif
-	}
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_wait_while_empty () returns (%d buffers remaining)\n", rdq_len);
-#endif
-
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        rdq_remove
- *
- * Purpose:     Remove raw bit buffer from the head of the queue.
- *
- * Inputs:	none
- *
- * Returns:	Pointer to rrbb object.
- *		Caller should destroy it with rrbb_delete when finished with it.	
- *
- *--------------------------------------------------------------------*/
-
-rrbb_t rdq_remove (void)
-{
-
-	rrbb_t result_p;
-#ifndef __WIN32__
-	int err;
-#endif
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_remove() enter critical section\n");
-#endif
-
-	dw_mutex_lock (&rdq_mutex);
-
-        rdq_len--;
-#if DEBUG
-	dw_printf ("-rdq_len: %d\n", rdq_len);
-#endif
-	if (queue_head == NULL) {
-	  result_p = NULL;
-	}
-	else {
-
-	  result_p = queue_head;
-	  queue_head = rrbb_get_nextp(result_p);
-	  rrbb_set_nextp (result_p, NULL);
-	}
-
-	dw_mutex_unlock (&rdq_mutex);
-	 
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("rdq_remove() leave critical section, returns %p\n", result_p);
-#endif
-	return (result_p);
-}
-
-/* end rdq.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2015  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/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      rdq.c
+ *
+ * Purpose:   	Retry later decode queue for frames with bad FCS.
+ *		
+ * Description:	
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "rdq.h"
+#include "dedupe.h"
+
+
+
+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 */
+
+
+
+static dw_mutex_t rdq_mutex;			/* Critical section for updating queues. */
+
+
+#if __WIN32__
+
+static HANDLE wake_up_event;			/* Notify try decode again thread when queue not empty. */
+
+#else
+
+static pthread_cond_t wake_up_cond;		/* Notify try decode again thread when queue not empty. */
+
+static dw_mutex_t wake_up_mutex;		/* Required by cond_wait. */
+
+#endif
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        rdq_init
+ *
+ * Purpose:     Initialize the receive decode again queue.
+ *
+ * Inputs:	None.  Only single queue for all channels.
+ *
+ * Outputs:	
+ *
+ * Description:	Initialize the queue to be empty and set up other
+ *		mechanisms for sharing it between different threads.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void rdq_init (void)
+{
+	//int c, p;
+#if __WIN32__
+#else
+	int err;
+#endif
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_init (  )\n");
+	dw_printf ("rdq_init: pthread_mutex_init...\n");
+#endif
+
+	dw_mutex_init (&rdq_mutex);
+
+#if __WIN32__
+#else
+	dw_mutex_init (&wake_up_mutex);
+#endif
+
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_init: pthread_cond_init...\n");
+#endif
+
+#if __WIN32__
+
+	wake_up_event = CreateEvent (NULL, 0, 0, NULL);
+
+	if (wake_up_event == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("rdq_init: pthread_cond_init: can't create decode wake up event");
+	  exit (1);
+	}
+
+#else
+	err = pthread_cond_init (&wake_up_cond, NULL);
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_init: pthread_cond_init returns %d\n", err);
+#endif
+
+
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("rdq_init: pthread_cond_init err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+#endif
+
+
+} /* end rdq_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        rdq_append
+ *
+ * Purpose:     Add a packet to the end of the queue.
+ *
+ * Inputs:	pp	- Address of raw received bit buffer.
+ *				Caller should NOT make any references to
+ *				it after this point because it could
+ *				be deleted at any time.
+ *
+ * Outputs:	
+ *
+ * Description:	Add buffer to end of linked list.
+ *		Signal the decode thread if the queue was formerly empty.
+ *
+ *--------------------------------------------------------------------*/
+
+void rdq_append (rrbb_t rrbb)
+{
+	//int was_empty;
+	rrbb_t plast;
+	rrbb_t pnext;
+#ifndef __WIN32__
+	int err;
+#endif
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_append (rrbb=%p)\n", rrbb);
+	dw_printf ("rdq_append: enter critical section\n");
+#endif
+
+
+	dw_mutex_lock (&rdq_mutex);
+
+	//was_empty = 1;
+	//if (queue_head != NULL) {
+	       //was_empty = 0;
+	//}
+	if (queue_head == NULL) {
+	  queue_head = rrbb;
+	}
+	else {
+	  plast = queue_head;
+	  while ((pnext = rrbb_get_nextp(plast)) != NULL) {
+	    plast = pnext;
+	  }
+	  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);
+	}
+
+	dw_mutex_unlock (&rdq_mutex);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_append: left critical section\n");
+	dw_printf ("rdq_append (): about to wake up retry decode thread.\n");
+#endif
+
+#if __WIN32__
+	SetEvent (wake_up_event);
+#else
+	dw_mutex_lock (&wake_up_mutex);
+
+	err = pthread_cond_signal (&wake_up_cond);
+	if (err != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("rdq_append: pthread_cond_signal err=%d", err);
+	  perror ("");
+	  exit (1);
+	}
+
+	dw_mutex_unlock (&wake_up_mutex);
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        rdq_wait_while_empty
+ *
+ * Purpose:     Sleep while the queue is empty rather than
+ *		polling periodically.
+ *
+ * Inputs:	None.
+ *		
+ *--------------------------------------------------------------------*/
+
+
+void rdq_wait_while_empty (void)
+{
+	int is_empty;
+#ifndef __WIN32__
+	int err;
+#endif
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_wait_while_empty () : enter critical section\n");
+#endif
+
+	dw_mutex_lock (&rdq_mutex);
+
+#if DEBUG
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("rdq_wait_while_empty (): after pthread_mutex_lock\n");
+#endif
+	is_empty = 1;
+        if (queue_head != NULL)
+	       is_empty = 0;
+
+	dw_mutex_unlock (&rdq_mutex);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_wait_while_empty () : left critical section\n");
+#endif
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_wait_while_empty (): is_empty = %d\n", is_empty);
+#endif
+
+	if (is_empty) {
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("rdq_wait_while_empty (): SLEEP - about to call cond wait\n");
+#endif
+
+
+#if __WIN32__
+	  WaitForSingleObject (wake_up_event, INFINITE);
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("rdq_wait_while_empty (): returned from wait\n");
+#endif
+
+#else
+	  dw_mutex_lock (&wake_up_mutex);
+
+	  err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex);
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("rdq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err);
+#endif
+
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("rdq_wait_while_empty: pthread_cond_wait err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  dw_mutex_unlock (&wake_up_mutex);
+
+#endif
+	}
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_wait_while_empty () returns (%d buffers remaining)\n", rdq_len);
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        rdq_remove
+ *
+ * Purpose:     Remove raw bit buffer from the head of the queue.
+ *
+ * Inputs:	none
+ *
+ * Returns:	Pointer to rrbb object.
+ *		Caller should destroy it with rrbb_delete when finished with it.	
+ *
+ *--------------------------------------------------------------------*/
+
+rrbb_t rdq_remove (void)
+{
+
+	rrbb_t result_p;
+#ifndef __WIN32__
+	int err;
+#endif
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_remove() enter critical section\n");
+#endif
+
+	dw_mutex_lock (&rdq_mutex);
+
+        rdq_len--;
+#if DEBUG
+	dw_printf ("-rdq_len: %d\n", rdq_len);
+#endif
+	if (queue_head == NULL) {
+	  result_p = NULL;
+	}
+	else {
+
+	  result_p = queue_head;
+	  queue_head = rrbb_get_nextp(result_p);
+	  rrbb_set_nextp (result_p, NULL);
+	}
+
+	dw_mutex_unlock (&rdq_mutex);
+	 
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("rdq_remove() leave critical section, returns %p\n", result_p);
+#endif
+	return (result_p);
+}
+
+/* end rdq.c */
diff --git a/rdq.h b/rdq.h
index dcb2896..b0e430c 100644
--- a/rdq.h
+++ b/rdq.h
@@ -1,28 +1,28 @@
-
-/*------------------------------------------------------------------
- *
- * Module:      rdq.h
- *
- * Purpose:   	Retry decode queue - Hold raw received frames with errors
- *		for retrying the decoding later.
- *		
- *---------------------------------------------------------------*/
-
-#ifndef RDQ_H
-#define RDQ_H 1
-
-#include "rrbb.h"
-//#include "audio.h"
-
-void rdq_init (void);
-
-void rdq_append (rrbb_t rrbb);
-
-void rdq_wait_while_empty (void);
-
-rrbb_t rdq_remove (void);
-
-
-#endif
-
-/* end rdq.h */
+
+/*------------------------------------------------------------------
+ *
+ * Module:      rdq.h
+ *
+ * Purpose:   	Retry decode queue - Hold raw received frames with errors
+ *		for retrying the decoding later.
+ *		
+ *---------------------------------------------------------------*/
+
+#ifndef RDQ_H
+#define RDQ_H 1
+
+#include "rrbb.h"
+//#include "audio.h"
+
+void rdq_init (void);
+
+void rdq_append (rrbb_t rrbb);
+
+void rdq_wait_while_empty (void);
+
+rrbb_t rdq_remove (void);
+
+
+#endif
+
+/* end rdq.h */
diff --git a/recv.c b/recv.c
index dea6c8e..a2dba4d 100644
--- a/recv.c
+++ b/recv.c
@@ -1,328 +1,327 @@
-
-// 
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      recv.c
- *
- * Purpose:   	Process audio input for receiving.
- *
- *		This is for all platforms.
- *
- *
- * Description:	In earlier versions, we supported a single audio device
- *		and the main program looped around processing the 
- *		audio samples.  The structure looked like this:
- *
- *		main in direwolf.c:
- *
- *			audio_init()
- *			various other *_init()
- *
- *			loop forever:
- *				s = demod_get_sample.
- *				multi_modem_process_sample(s)
- *				
- *
- *		When a packet is succesfully decoded, somebody calls
- *		app_process_rec_frame, also in direwolf.c
- *
- *
- *		Starting in version 1.2, we support multiple audio 
- *		devices at the same time.  We now have a separate
- *		thread for each audio device.   Decoded frames are
- *		sent to a single queue for serial processing.
- *
- *		The new flow looks like this:
- *
- *		main in direwolf.c:
- *
- *			audio_init()
- *			various other *_init()
- *			recv_init()
- *			recv_process()  -- does not return
- *
- *			
- *		recv_init()		This starts up a separate thread
- *					for each audio device.
- *					Each thread reads audio samples and
- *					passes them to multi_modem_process_sample.
- *
- *					The difference is that app_process_rec_frame
- *					is no longer called directly.  Instead
- *					the frame is appended to a queue with dlq_append.
- *
- *					Received frames can now be processed one at 
- *					a time and we don't need to worry about later
- *					processing being reentrant.
- *				
- *		recv_process()  	This simply waits for something to show up
- *					in the dlq queue and calls app_process_rec_frame
- *					for each.
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-//#include <sys/stat.h>
-//#include <sys/ioctl.h>
-//#include <fcntl.h>
-#include <assert.h>
-
-#ifdef __FreeBSD__
-#include <errno.h>
-#endif
-
-#include "direwolf.h"
-#include "audio.h"
-#include "demod.h"
-#include "multi_modem.h"
-#include "textcolor.h"
-#include "dlq.h"
-#include "recv.h"
-#include "dtmf.h"
-#include "aprs_tt.h"
-
-
-#if __WIN32__
-static unsigned __stdcall recv_adev_thread (void *arg);
-#else
-static void * recv_adev_thread (void *arg);
-#endif
-
-
-static struct audio_s *save_pa;		/* Keep pointer to audio configuration */
-					/* for later use. */
-
-/*------------------------------------------------------------------
- *
- * Name:        recv_init
- *
- * Purpose:     Start up a thread for each audio device.
- *
- *
- * Inputs:      pa		- Address of structure of type audio_s.
- *				
- *              
- * Returns:     None.
- *
- * Errors:	Exit if error.
- *		No point in going on if we can't get audio.
- *		
- *----------------------------------------------------------------*/
-
-
-
-void recv_init (struct audio_s *pa)
-{
-#if __WIN32__
-	HANDLE xmit_th[MAX_ADEVS];
-#else
-	pthread_t xmit_tid[MAX_ADEVS];
-#endif
-	int a;
-
-	save_pa = pa;
-
-	for (a=0; a<MAX_ADEVS; a++) {
-
-	  if (pa->adev[a].defined) {
-
-#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("recv_init: start up thread, a=%d\n", a);
-#endif
-
-#if __WIN32__
-	    xmit_th[a] = (HANDLE)_beginthreadex (NULL, 0, recv_adev_thread, (void*)(long)a, 0, NULL);
-	    if (xmit_th[a] == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a);
-	      exit(1);
-	    }
-#else
-	    int e;
-	    e = pthread_create (&xmit_tid[a], NULL, recv_adev_thread, (void *)(long)a);
-
-	    if (e != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a);
-	      exit(1);
-	    }
-#endif
-	  }
-
-#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("recv_init: all done\n");
-#endif
-	}
-
-
-} /* end recv_init */
-
-
-
-
-/* Try using "hot" attribute for all functions */
-/* which are used for each audio sample. */
-/* Compiler & linker might gather */
-/* them together to improve memory cache performance. */
-/* Or maybe it won't make any difference. */
-
-__attribute__((hot))
-#if __WIN32__
-static unsigned __stdcall recv_adev_thread (void *arg)
-#else
-static void * recv_adev_thread (void *arg)
-#endif
-{
-	int a = (int)(long)arg;	// audio device number.
-	int eof;
-	
-	/* This audio device can have one (mono) or two (stereo) channels. */
-	/* Find number of the first channel. */
-
-	int first_chan =  ADEVFIRSTCHAN(a); 
-	int num_chan = save_pa->adev[a].num_channels;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("recv_adev_thread is now running for a=%d\n", a);
-#endif
-/*
- * Get sound samples and decode them.
- */
-	eof = 0;
-	while ( ! eof) 
-	{
-
-	  int audio_sample;
-	  int c;
-	  char tt;
-
-	  for (c=0; c<num_chan; c++)
-	  {
-	    audio_sample = demod_get_sample (a);
-	  
- 	    if (audio_sample >= 256 * 256) 
-	      eof = 1;
-
-	    multi_modem_process_sample(first_chan + c, audio_sample);
-
-
-	    /* Originally, the DTMF decoder was always active. */
-	    /* It took very little CPU time and the thinking was that an */
-	    /* attached application might be interested in this even when */
-	    /* the APRStt gateway was not being used.  */
-
-	    /* Unfortunately it resulted in too many false detections of */
-	    /* touch tones when hearing other types of digital communications */
-	    /* on HF.  Starting in version 1.0, the DTMF decoder is active */
-	    /* only when the APRStt gateway is configured. */
-
-	    /* The test below allows us to listen to only a single channel for */
-	    /* for touch tone sequences.  The DTMF decoder and the accumulation */
-	    /* of digits into a sequence maintain separate data for each channel. */
-	    /* We should be able to accept touch tone sequences concurrently on */
-	    /* all channels.  The only issue is when a complete sequence is */
-	    /* sent to aprs_tt_sequence which doesn't have separate data for each */
-	    /* channel.  This shouldn't be a problem unless we have multiple */
-	    /* sequences arriving at the same instant. */
-
-	    if (save_pa->achan[first_chan + c].dtmf_decode != DTMF_DECODE_OFF) {
-	      tt = dtmf_sample (first_chan + c, audio_sample/16384.);
-	      if (tt != ' ') {
-	        aprs_tt_button (first_chan + c, tt);
-	      }
-	    }
-	  }
-
-		/* When a complete frame is accumulated, */
-		/* dlq_append, is called. */
-
-		/* recv_process, below, drains the queue. */
-
-	}
-
-// What should we do now?
-// Seimply terminate the application?  
-// Try to re-init the audio device a couple times before giving up?
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Terminating after audio input failure.\n");
-	exit (1);
-}
-
-
-
-
-
-void recv_process (void) 
-{
-
-	int ok;
-	dlq_type_t type;
-	int chan;
-	int subchan;
-	packet_t pp;
-	alevel_t alevel;
-	retry_t retries;
-	char spectrum[MAX_SUBCHANS+1];
-
-
-	while (1) {
-
-	  dlq_wait_while_empty ();
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("recv_process: woke up\n");
-#endif
-	  
-
-	  ok = dlq_remove (&type, &chan, &subchan, &pp, &alevel, &retries, spectrum);
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("recv_process: dlq_remove() returned ok=%d, type=%d, chan=%d, pp=%p\n", 
-				ok, (int)type, chan, pp);
-#endif
-	  if (ok) {
-	   
-		app_process_rec_packet (chan, subchan, pp, alevel, retries, spectrum);  
-	  }
-#if DEBUG
-	  else {
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n");
-	  }
-#endif
-	}
-
-} /* end recv_process */
-
-
-
-/* end recv.c */
-
+
+// 
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      recv.c
+ *
+ * Purpose:   	Process audio input for receiving.
+ *
+ *		This is for all platforms.
+ *
+ *
+ * Description:	In earlier versions, we supported a single audio device
+ *		and the main program looped around processing the 
+ *		audio samples.  The structure looked like this:
+ *
+ *		main in direwolf.c:
+ *
+ *			audio_init()
+ *			various other *_init()
+ *
+ *			loop forever:
+ *				s = demod_get_sample.
+ *				multi_modem_process_sample(s)
+ *				
+ *
+ *		When a packet is succesfully decoded, somebody calls
+ *		app_process_rec_frame, also in direwolf.c
+ *
+ *
+ *		Starting in version 1.2, we support multiple audio 
+ *		devices at the same time.  We now have a separate
+ *		thread for each audio device.   Decoded frames are
+ *		sent to a single queue for serial processing.
+ *
+ *		The new flow looks like this:
+ *
+ *		main in direwolf.c:
+ *
+ *			audio_init()
+ *			various other *_init()
+ *			recv_init()
+ *			recv_process()  -- does not return
+ *
+ *			
+ *		recv_init()		This starts up a separate thread
+ *					for each audio device.
+ *					Each thread reads audio samples and
+ *					passes them to multi_modem_process_sample.
+ *
+ *					The difference is that app_process_rec_frame
+ *					is no longer called directly.  Instead
+ *					the frame is appended to a queue with dlq_append.
+ *
+ *					Received frames can now be processed one at 
+ *					a time and we don't need to worry about later
+ *					processing being reentrant.
+ *				
+ *		recv_process()  	This simply waits for something to show up
+ *					in the dlq queue and calls app_process_rec_frame
+ *					for each.
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+//#include <sys/stat.h>
+//#include <sys/ioctl.h>
+//#include <fcntl.h>
+#include <assert.h>
+
+#ifdef __FreeBSD__
+#include <errno.h>
+#endif
+
+#include "direwolf.h"
+#include "audio.h"
+#include "demod.h"
+#include "multi_modem.h"
+#include "textcolor.h"
+#include "dlq.h"
+#include "recv.h"
+#include "dtmf.h"
+#include "aprs_tt.h"
+
+
+#if __WIN32__
+static unsigned __stdcall recv_adev_thread (void *arg);
+#else
+static void * recv_adev_thread (void *arg);
+#endif
+
+
+static struct audio_s *save_pa;		/* Keep pointer to audio configuration */
+					/* for later use. */
+
+/*------------------------------------------------------------------
+ *
+ * Name:        recv_init
+ *
+ * Purpose:     Start up a thread for each audio device.
+ *
+ *
+ * Inputs:      pa		- Address of structure of type audio_s.
+ *				
+ *              
+ * Returns:     None.
+ *
+ * Errors:	Exit if error.
+ *		No point in going on if we can't get audio.
+ *		
+ *----------------------------------------------------------------*/
+
+
+
+void recv_init (struct audio_s *pa)
+{
+#if __WIN32__
+	HANDLE xmit_th[MAX_ADEVS];
+#else
+	pthread_t xmit_tid[MAX_ADEVS];
+#endif
+	int a;
+
+	save_pa = pa;
+
+	for (a=0; a<MAX_ADEVS; a++) {
+
+	  if (pa->adev[a].defined) {
+
+#if DEBUG
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("recv_init: start up thread, a=%d\n", a);
+#endif
+
+#if __WIN32__
+	    xmit_th[a] = (HANDLE)_beginthreadex (NULL, 0, recv_adev_thread, (void*)(long)a, 0, NULL);
+	    if (xmit_th[a] == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a);
+	      exit(1);
+	    }
+#else
+	    int e;
+	    e = pthread_create (&xmit_tid[a], NULL, recv_adev_thread, (void *)(long)a);
+
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a);
+	      exit(1);
+	    }
+#endif
+	  }
+
+#if DEBUG
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("recv_init: all done\n");
+#endif
+	}
+
+
+} /* end recv_init */
+
+
+
+
+/* Try using "hot" attribute for all functions */
+/* which are used for each audio sample. */
+/* Compiler & linker might gather */
+/* them together to improve memory cache performance. */
+/* Or maybe it won't make any difference. */
+
+__attribute__((hot))
+#if __WIN32__
+static unsigned __stdcall recv_adev_thread (void *arg)
+#else
+static void * recv_adev_thread (void *arg)
+#endif
+{
+	int a = (int)(long)arg;	// audio device number.
+	int eof;
+	
+	/* This audio device can have one (mono) or two (stereo) channels. */
+	/* Find number of the first channel. */
+
+	int first_chan =  ADEVFIRSTCHAN(a); 
+	int num_chan = save_pa->adev[a].num_channels;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("recv_adev_thread is now running for a=%d\n", a);
+#endif
+/*
+ * Get sound samples and decode them.
+ */
+	eof = 0;
+	while ( ! eof) 
+	{
+
+	  int audio_sample;
+	  int c;
+	  char tt;
+
+	  for (c=0; c<num_chan; c++)
+	  {
+	    audio_sample = demod_get_sample (a);
+	  
+ 	    if (audio_sample >= 256 * 256) 
+	      eof = 1;
+
+	    multi_modem_process_sample(first_chan + c, audio_sample);
+
+
+	    /* Originally, the DTMF decoder was always active. */
+	    /* It took very little CPU time and the thinking was that an */
+	    /* attached application might be interested in this even when */
+	    /* the APRStt gateway was not being used.  */
+
+	    /* Unfortunately it resulted in too many false detections of */
+	    /* touch tones when hearing other types of digital communications */
+	    /* on HF.  Starting in version 1.0, the DTMF decoder is active */
+	    /* only when the APRStt gateway is configured. */
+
+	    /* The test below allows us to listen to only a single channel for */
+	    /* for touch tone sequences.  The DTMF decoder and the accumulation */
+	    /* of digits into a sequence maintain separate data for each channel. */
+	    /* We should be able to accept touch tone sequences concurrently on */
+	    /* all channels.  The only issue is when a complete sequence is */
+	    /* sent to aprs_tt_sequence which doesn't have separate data for each */
+	    /* channel.  This shouldn't be a problem unless we have multiple */
+	    /* sequences arriving at the same instant. */
+
+	    if (save_pa->achan[first_chan + c].dtmf_decode != DTMF_DECODE_OFF) {
+	      tt = dtmf_sample (first_chan + c, audio_sample/16384.);
+	      if (tt != ' ') {
+	        aprs_tt_button (first_chan + c, tt);
+	      }
+	    }
+	  }
+
+		/* When a complete frame is accumulated, */
+		/* dlq_append, is called. */
+
+		/* recv_process, below, drains the queue. */
+
+	}
+
+// What should we do now?
+// Seimply terminate the application?  
+// Try to re-init the audio device a couple times before giving up?
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Terminating after audio input failure.\n");
+	exit (1);
+}
+
+
+
+
+
+void recv_process (void) 
+{
+
+	int ok;
+	dlq_type_t type;
+	int chan;
+	int subchan;
+	int slice;
+	packet_t pp;
+	alevel_t alevel;
+	retry_t retries;
+	char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];
+
+	while (1) {
+
+	  dlq_wait_while_empty ();
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("recv_process: woke up\n");
+#endif 
+
+	  ok = dlq_remove (&type, &chan, &subchan, &slice, &pp, &alevel, &retries, spectrum, sizeof(spectrum));
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("recv_process: dlq_remove() returned ok=%d, type=%d, chan=%d, pp=%p\n", 
+				ok, (int)type, chan, pp);
+#endif
+	  if (ok) {
+		app_process_rec_packet (chan, subchan, slice, pp, alevel, retries, spectrum);
+	  }
+#if DEBUG
+	  else {
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n");
+	  }
+#endif
+	}
+
+} /* end recv_process */
+
+
+
+/* end recv.c */
+
diff --git a/recv.h b/recv.h
index d10fa85..3201991 100644
--- a/recv.h
+++ b/recv.h
@@ -1,6 +1,6 @@
-
-/* recv.h */
-
-void recv_init (struct audio_s *pa);
-
+
+/* recv.h */
+
+void recv_init (struct audio_s *pa);
+
 void recv_process (void);
\ No newline at end of file
diff --git a/redecode.c b/redecode.c
index 90a4912..7c779ad 100644
--- a/redecode.c
+++ b/redecode.c
@@ -1,252 +1,255 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      redecode.c
- *
- * Purpose:   	Retry decoding frames that have a bad FCS.
- *		
- * Description:	
- *
- *
- * Usage:	(1) The main application calls redecode_init.
- *
- *			This will initialize the retry decoding queue
- *			and create a thread to work on contents of the queue.
- *
- *		(2) The application queues up frames by calling rdq_append.
- *
- *
- *		(3) redecode_thread removes raw frames from the queue and 
- *			tries to recover from errors.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-//#include <sys/time.h>
-#include <time.h>
-
-#if __WIN32__
-#include <windows.h>
-#endif
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "rdq.h"
-#include "redecode.h"
-#include "hdlc_send.h"
-#include "hdlc_rec2.h"
-#include "ptt.h"
-
-
-/* Audio configuration for the fix_bits / passall optiions. */
-
-static struct audio_s          *save_audio_config_p;
-
-
-
-#if __WIN32__
-static unsigned redecode_thread (void *arg);
-#else
-static void * redecode_thread (void *arg);
-#endif
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        redecode_init
- *
- * Purpose:     Initialize the process to try fixing bits in frames with bad FCS.
- *
- * Inputs:	none.
- *
- * Outputs:	none.
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *		Start up redecode_thread to actually process the
- *		raw frames from the queue.
- *
- *--------------------------------------------------------------------*/
-
-
-void redecode_init (struct audio_s *p_audio_config)
-{
-
-#if __WIN32__
-	HANDLE redecode_th;
-#else
-	pthread_t redecode_tid;
-	int e;
-#endif
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("redecode_init ( ... )\n");
-#endif
-
-	save_audio_config_p = p_audio_config;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("redecode_init: about to call rdq_init \n");
-#endif
-	rdq_init ();
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("redecode_init: about to create thread \n");
-#endif
-
-
-#if __WIN32__
-	redecode_th = _beginthreadex (NULL, 0, redecode_thread, NULL, 0, NULL);
-	if (redecode_th == NULL) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Could not create redecode thread\n");
-	  return;
-	}
-#else
-
-//TODO: Give thread lower priority.
-
-	e = pthread_create (&redecode_tid, NULL, redecode_thread, (void *)0);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Could not create redecode thread");
-	  return;
-	}
-#endif
-
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("redecode_init: finished \n");
-#endif
-
-
-} /* end redecode_init */
-
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        redecode_thread
- *
- * Purpose:     Try to decode frames with a bad FCS.
- *
- * Inputs:	None.
- *
- * Outputs:	
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *
- *--------------------------------------------------------------------*/
-
-#if __WIN32__
-static unsigned redecode_thread (void *arg)
-#else
-static void * redecode_thread (void *arg)
-#endif
-{
-#if __WIN32__
-	HANDLE tid = GetCurrentThread();
-	//int tp;
-
-	//tp = GetThreadPriority (tid);
-	//text_color_set(DW_COLOR_DEBUG);
-	//dw_printf ("Starting redecode thread priority=%d\n", tp);
-	SetThreadPriority (tid, THREAD_PRIORITY_LOWEST);
-	//tp = GetThreadPriority (tid);
-	//dw_printf ("New redecode thread priority=%d\n", tp);
-#endif
-
-	while (1) {
-	  rrbb_t block;
-
-	  rdq_wait_while_empty ();
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("redecode_thread: woke up\n");
-#endif
-	  
-	  block = rdq_remove ();
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("redecode_thread: rdq_remove() returned %p\n", block);
-#endif
-
-/* Don't expect null ever but be safe. */
-
-	  if (block != NULL) {
-
-	    int chan = rrbb_get_chan(block);
-	    int subchan = rrbb_get_subchan(block);
-	    int blen = rrbb_get_len(block);
-	    alevel_t alevel = rrbb_get_audio_level(block);
-	    //retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
-	    //int passall = save_audio_config_p->achan[chan].passall;
-
-	    int ok;
-
-#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen);
-#endif
-
-	    ok = hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel);
-
-#if DEBUG
-	    text_color_set(DW_COLOR_DEBUG);
-	    dw_printf ("redecode_thread: finished processing %p\n", block);
-#endif
-	    rrbb_delete (block);
-	  }
-
-	}
-
-	return 0;
-
-} /* end redecode_thread */
-
-
-
-
-
-/* end redecode.c */
-
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      redecode.c
+ *
+ * Purpose:   	Retry decoding frames that have a bad FCS.
+ *		
+ * Description:	
+ *
+ *
+ * Usage:	(1) The main application calls redecode_init.
+ *
+ *			This will initialize the retry decoding queue
+ *			and create a thread to work on contents of the queue.
+ *
+ *		(2) The application queues up frames by calling rdq_append.
+ *
+ *
+ *		(3) redecode_thread removes raw frames from the queue and 
+ *			tries to recover from errors.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include <time.h>
+
+#if __WIN32__
+#include <windows.h>
+#endif
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "rdq.h"
+#include "redecode.h"
+#include "hdlc_send.h"
+#include "hdlc_rec2.h"
+#include "ptt.h"
+
+
+/* Audio configuration for the fix_bits / passall optiions. */
+
+static struct audio_s          *save_audio_config_p;
+
+
+
+#if __WIN32__
+static unsigned redecode_thread (void *arg);
+#else
+static void * redecode_thread (void *arg);
+#endif
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        redecode_init
+ *
+ * Purpose:     Initialize the process to try fixing bits in frames with bad FCS.
+ *
+ * Inputs:	none.
+ *
+ * Outputs:	none.
+ *
+ * Description:	Initialize the queue to be empty and set up other
+ *		mechanisms for sharing it between different threads.
+ *
+ *		Start up redecode_thread to actually process the
+ *		raw frames from the queue.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void redecode_init (struct audio_s *p_audio_config)
+{
+
+#if 0
+
+#if __WIN32__
+	HANDLE redecode_th;
+#else
+	pthread_t redecode_tid;
+	int e;
+#endif
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("redecode_init ( ... )\n");
+#endif
+
+	save_audio_config_p = p_audio_config;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("redecode_init: about to call rdq_init \n");
+#endif
+	rdq_init ();
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("redecode_init: about to create thread \n");
+#endif
+
+
+#if __WIN32__
+	redecode_th = _beginthreadex (NULL, 0, redecode_thread, NULL, 0, NULL);
+	if (redecode_th == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Could not create redecode thread\n");
+	  return;
+	}
+#else
+
+//TODO: Give thread lower priority.
+
+	e = pthread_create (&redecode_tid, NULL, redecode_thread, (void *)0);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Could not create redecode thread");
+	  return;
+	}
+#endif
+
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("redecode_init: finished \n");
+#endif
+#endif
+
+} /* end redecode_init */
+
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        redecode_thread
+ *
+ * Purpose:     Try to decode frames with a bad FCS.
+ *
+ * Inputs:	None.
+ *
+ * Outputs:	
+ *
+ * Description:	Initialize the queue to be empty and set up other
+ *		mechanisms for sharing it between different threads.
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+#if 0
+
+#if __WIN32__
+static unsigned redecode_thread (void *arg)
+#else
+static void * redecode_thread (void *arg)
+#endif
+{
+#if __WIN32__
+	HANDLE tid = GetCurrentThread();
+	//int tp;
+
+	//tp = GetThreadPriority (tid);
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("Starting redecode thread priority=%d\n", tp);
+	SetThreadPriority (tid, THREAD_PRIORITY_LOWEST);
+	//tp = GetThreadPriority (tid);
+	//dw_printf ("New redecode thread priority=%d\n", tp);
+#endif
+
+	while (1) {
+	  rrbb_t block;
+
+	  rdq_wait_while_empty ();
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("redecode_thread: woke up\n");
+#endif
+	  
+	  block = rdq_remove ();
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("redecode_thread: rdq_remove() returned %p\n", block);
+#endif
+
+/* Don't expect null ever but be safe. */
+
+	  if (block != NULL) {
+
+	    int chan = rrbb_get_chan(block);
+	    int subchan = rrbb_get_subchan(block);
+	    int blen = rrbb_get_len(block);
+	    alevel_t alevel = rrbb_get_audio_level(block);
+	    //retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
+	    //int passall = save_audio_config_p->achan[chan].passall;
+
+	    int ok;
+
+#if DEBUG
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen);
+#endif
+
+	    ok = hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel);
+
+#if DEBUG
+	    text_color_set(DW_COLOR_DEBUG);
+	    dw_printf ("redecode_thread: finished processing %p\n", block);
+#endif
+	    rrbb_delete (block);
+	  }
+
+	}
+
+	return 0;
+
+} /* end redecode_thread */
+
+#endif
+
+
+
+/* end redecode.c */
+
+
+
diff --git a/redecode.h b/redecode.h
index 4f5ab42..ffd9a95 100644
--- a/redecode.h
+++ b/redecode.h
@@ -1,15 +1,15 @@
-
-
-#ifndef REDECODE_H
-#define REDECODE_H 1
-
-#include "rrbb.h"	
-
-
-extern void redecode_init (struct audio_s *p_audio_config);
-
-
-#endif
-
-/* end redecode.h */
-
+
+
+#ifndef REDECODE_H
+#define REDECODE_H 1
+
+#include "rrbb.h"	
+
+
+extern void redecode_init (struct audio_s *p_audio_config);
+
+
+#endif
+
+/* end redecode.h */
+
diff --git a/regex/COPYING b/regex/COPYING
old mode 100755
new mode 100644
diff --git a/regex/COPYING.LIB b/regex/COPYING.LIB
deleted file mode 100755
index cf9b6b9..0000000
--- a/regex/COPYING.LIB
+++ /dev/null
@@ -1,510 +0,0 @@
-
-                  GNU LESSER GENERAL PUBLIC LICENSE
-                       Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL.  It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
-  This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it.  You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations
-below.
-
-  When we speak of free software, we are referring to freedom of use,
-not price.  Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
-  To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights.  These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
-
-  We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
-  To protect each distributor, we want to make it very clear that
-there is no warranty for the free library.  Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-^L
-  Finally, software patents pose a constant threat to the existence of
-any free program.  We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder.  Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
-  Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License.  This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License.  We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
-  When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library.  The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom.  The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
-  We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License.  It also provides other free software developers Less
-of an advantage over competing non-free programs.  These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries.  However, the Lesser license provides advantages in certain
-special circumstances.
-
-  For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it
-becomes a de-facto standard.  To achieve this, non-free programs must
-be allowed to use the library.  A more frequent case is that a free
-library does the same job as widely used non-free libraries.  In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
-  In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software.  For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
-  Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-^L
-                  GNU LESSER GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
-
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control
-compilation and installation of the library.
-
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
-  1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
-  You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-

-  2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
-
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-^L
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
-  If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-^L
-  6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Use a suitable shared library mechanism for linking with the
-    Library.  A suitable mechanism is one that (1) uses at run time a
-    copy of the library already present on the user's computer system,
-    rather than copying library functions into the executable, and (2)
-    will operate properly with a modified version of the library, if
-    the user installs one, as long as the modified version is
-    interface-compatible with the version that the work was made with.
-
-    c) Accompany the work with a written offer, valid for at least
-    three years, to give the same user the materials specified in
-    Subsection 6a, above, for a charge no more than the cost of
-    performing this distribution.
-
-    d) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    e) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-^L
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License.  However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
-  9. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Library or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-^L
-  11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply, and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License
-may add an explicit geographical distribution limitation excluding those
-countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation.  If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-^L
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission.  For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this.  Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
-                            NO WARRANTY
-
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-^L
-           How to Apply These Terms to Your New Libraries
-
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms
-of the ordinary General Public License).
-
-  To apply these terms, attach the following notices to the library.
-It is safest to attach them to the start of each source file to most
-effectively convey the exclusion of warranty; and each file should
-have at least the "copyright" line and a pointer to where the full
-notice is found.
-
-
-    <one line to give the library's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library 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
-    Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public
-    License along with this library; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or
-your school, if any, to sign a "copyright disclaimer" for the library,
-if necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James
-  Random Hacker.
-
-  <signature of Ty Coon>, 1 April 1990
-  Ty Coon, President of Vice
-
-That's all there is to it!
-
-
diff --git a/regex/INSTALL b/regex/INSTALL
old mode 100755
new mode 100644
diff --git a/regex/LICENSES b/regex/LICENSES
old mode 100755
new mode 100644
diff --git a/regex/NEWS b/regex/NEWS
old mode 100755
new mode 100644
diff --git a/regex/README b/regex/README
old mode 100755
new mode 100644
diff --git a/regex/README-dire-wolf.txt b/regex/README-dire-wolf.txt
old mode 100755
new mode 100644
index dea8f72..cc081c4
--- a/regex/README-dire-wolf.txt
+++ b/regex/README-dire-wolf.txt
@@ -1,7 +1,7 @@
-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.
-
-The source was obtained from:
-
+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.
+
+The source was obtained from:
+
 	http://gnuwin32.sourceforge.net/packages/regex.htm
\ No newline at end of file
diff --git a/regex/re_comp.h b/regex/re_comp.h
old mode 100755
new mode 100644
diff --git a/regex/regcomp.c b/regex/regcomp.c
old mode 100755
new mode 100644
diff --git a/regex/regex.c b/regex/regex.c
old mode 100755
new mode 100644
diff --git a/regex/regex.h b/regex/regex.h
old mode 100755
new mode 100644
diff --git a/regex/regex_internal.c b/regex/regex_internal.c
old mode 100755
new mode 100644
diff --git a/regex/regex_internal.h b/regex/regex_internal.h
old mode 100755
new mode 100644
diff --git a/regex/regexec.c b/regex/regexec.c
old mode 100755
new mode 100644
diff --git a/rpack.h b/rpack.h
index c830eb8..d972a54 100644
--- a/rpack.h
+++ b/rpack.h
@@ -1,94 +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 */
-	
+
+/*------------------------------------------------------------------
+ *
+ * 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 77777f1..5651de7 100644
--- a/rrbb.c
+++ b/rrbb.c
@@ -1,567 +1,488 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	rrbb.c
- *
- * Purpose:	Raw Received Bit Buffer.
- *		Implementation of an array of bits used to hold data out of
- *		the demodulator before feeding it into the HLDC decoding.
- *
- * Version 1.0:	Let's try something new.
- *		Rather than storing a single bit from the demodulator
- *		output, let's store a value which we can try later
- *		comparing to threshold values besides 0.
- *
- * Version 1.2: Save initial state of 9600 baud descrambler so we can
- *		attempt bit fix up on G3RUH/K9NG scrambled data.
- *
- *******************************************************************************/
-
-#define RRBB_C
-
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "textcolor.h"
-#include "ax25_pad.h"
-#include "rrbb.h"
-
-
-
-
-#define MAGIC1 0x12344321
-#define MAGIC2 0x56788765
-
-static const unsigned int masks[SOI] = {
-	0x00000001,
-	0x00000002,
-	0x00000004,
-	0x00000008,
-	0x00000010,
-	0x00000020,
-	0x00000040,
-	0x00000080,
-	0x00000100,
-	0x00000200,
-	0x00000400,
-	0x00000800,
-	0x00001000,
-	0x00002000,
-	0x00004000,
-	0x00008000,
-	0x00010000,
-	0x00020000,
-	0x00040000,
-	0x00080000,
-	0x00100000,
-	0x00200000,
-	0x00400000,
-	0x00800000,
-	0x01000000,
-	0x02000000,
-	0x04000000,
-	0x08000000,
-	0x10000000,
-	0x20000000,
-	0x40000000,
-	0x80000000 };
-
-static int new_count = 0;
-static int delete_count = 0;
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_new	
- *
- * Purpose:	Allocate space for an array of samples.
- *
- * Inputs:	chan	- Radio channel from whence it came.
- *
- *		subchan	- Which demodulator of the channel.
- *
- *		is_scrambled - Is data scrambled? (true, false)
- *
- *		descram_state - State of data descrambler.
- *
- *		prev_descram - Previous descrambled bit.
- *
- * Returns:	Handle to be used by other functions.
- *		
- * Description:	
- *
- ***********************************************************************************/
-
-rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state, int prev_descram)
-{
-	rrbb_t result;
-
-	assert (SOI == 8 * sizeof(unsigned int));
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-
-	result = malloc(sizeof(struct rrbb_s));
-
-	result->magic1 = MAGIC1;
-	result->chan = chan;
-	result->subchan = subchan;
-	result->magic2 = MAGIC2;
-
-	new_count++;
-
-	if (new_count > delete_count + 100) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("MEMORY LEAK, rrbb_new, new_count=%d, delete_count=%d\n", new_count, delete_count);
-	}
-
-	rrbb_clear (result, is_scrambled, descram_state, prev_descram);
-
-	return (result);
-}
-
-/***********************************************************************************
- *
- * Name:	rrbb_clear	
- *
- * Purpose:	Clear by setting length to zero, etc.
- *
- * Inputs:	b 		-Handle for sample array.
- *
- *		is_scrambled 	- Is data scrambled? (true, false)
- *
- *		descram_state 	- State of data descrambler.
- *
- *		prev_descram 	- Previous descrambled bit.
- *
- ***********************************************************************************/
-
-void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	assert (is_scrambled == 0 || is_scrambled == 1);
-	assert (prev_descram == 0 || prev_descram == 1);
-
-	b->nextp = NULL;
-
-	b->alevel.rec = 9999;	// TODO: was there some reason for this instead of 0 or -1?
-	b->alevel.mark = 9999;
-	b->alevel.space = 9999;
-
-	b->len = 0;
-
-	b->is_scrambled = is_scrambled;
-	b->descram_state = descram_state;
-	b->prev_descram = prev_descram;
-}
-
-/***********************************************************************************
- *
- * Name:	rrbb_append_bit	
- *
- * Purpose:	Append another bit to the end.
- *
- * Inputs:	Handle for sample array.
- *		Value for the sample.
- *
- ***********************************************************************************/
-
-
-// TODO:  Forget about bit packing and just use bytes.
-//  We have hundreds of MB.  Why waste time to save a couple KB?
-
-
-void rrbb_append_bit (rrbb_t b, int val)
-{
-	unsigned int di, mi;
-	
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	if (b->len >= MAX_NUM_BITS) {
-	  return;	/* Silently discard if full. */
-	}
-
-	di = b->len / SOI;
-	mi = b->len % SOI;
-
-	if (val) {
-	  b->data[di] |= masks[mi];
-	}
-	else {
-	  b->data[di] &= ~ masks[mi];
-	}
-
-	b->len++;
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_chop8	
- *
- * Purpose:	Remove 8 from the length.
- *
- * Inputs:	Handle for bit array.
- *		
- * Description:	Back up after appending the flag sequence.
- *
- ***********************************************************************************/
-
-void rrbb_chop8 (rrbb_t b)
-{
-	
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	if (b->len >= 8) {
-	  b->len -= 8;
-	}
-}
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_len	
- *
- * Purpose:	Get number of bits in the array.
- *
- * Inputs:	Handle for bit array.
- *		
- ***********************************************************************************/
-
-int rrbb_get_len (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	return (b->len);
-}
-
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_bit	
- *
- * Purpose:	Get value of bit in specified position.
- *
- * Inputs:	Handle for sample array.
- *		Index into array.
- *		
- ***********************************************************************************/
-
-int rrbb_get_bit (rrbb_t b, unsigned int ind)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	assert (ind < b->len);
-
-	if (b->data[ind / SOI] & masks[ind % SOI]) {
-	  return 1;
-	}
-	else {
-	  return 0;
-	}
-}
-
-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;
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_flip_bit	
- *
- * Purpose:	Complement the value of bit in specified position.
- *
- * Inputs:	Handle for bit array.
- *		Index into array.
- *		
- ***********************************************************************************/
-
-//void rrbb_flip_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;
-//
-//	b->data[di] ^= masks[mi];
-//}
-
-/***********************************************************************************
- *
- * Name:	rrbb_delete	
- *
- * Purpose:	Free the storage associated with the bit array.
- *
- * Inputs:	Handle for bit array.
- *		
- ***********************************************************************************/
-
-void rrbb_delete (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	b->magic1 = 0;
-	b->magic2 = 0;
-	
-	free (b);
-
-	delete_count++;
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_set_netxp	
- *
- * Purpose:	Set the nextp field, used to maintain a queue.
- *
- * Inputs:	b	Handle for bit array.
- *		np	New value for nextp.
- *		
- ***********************************************************************************/
-
-void rrbb_set_nextp (rrbb_t b, rrbb_t np)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	b->nextp = np;
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_netxp	
- *
- * Purpose:	Get value of nextp field.
- *
- * Inputs:	b	Handle for bit array.
- *		
- ***********************************************************************************/
-
-rrbb_t rrbb_get_nextp (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	return (b->nextp);
-}
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_chan	
- *
- * Purpose:	Get channel from which bit buffer was received.
- *
- * Inputs:	b	Handle for bit array.
- *		
- ***********************************************************************************/
-
-int rrbb_get_chan (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	assert (b->chan >= 0 && b->chan < MAX_CHANS);
-
-	return (b->chan);
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_subchan	
- *
- * Purpose:	Get subchannel from which bit buffer was received.
- *
- * Inputs:	b	Handle for bit array.
- *		
- ***********************************************************************************/
-
-int rrbb_get_subchan (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	assert (b->subchan >= 0 && b->subchan < MAX_SUBCHANS);
-
-	return (b->subchan);
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_set_audio_level	
- *
- * Purpose:	Set audio level at time the frame was received.
- *
- * Inputs:	b	Handle for bit array.
- *		alevel	Audio level.
- *		
- ***********************************************************************************/
-
-void rrbb_set_audio_level (rrbb_t b, alevel_t alevel)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	b->alevel = alevel;
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_audio_level	
- *
- * Purpose:	Get audio level at time the frame was received.
- *
- * Inputs:	b	Handle for bit array.
- *		
- ***********************************************************************************/
-
-alevel_t rrbb_get_audio_level (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	return (b->alevel);
-}
-
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_is_scrambled	
- *
- * Purpose:	Find out if using scrambled data.
- *
- * Inputs:	b	Handle for bit array.
- *
- * Returns:	True (for 9600 baud) or false (for slower AFSK).
- *		
- ***********************************************************************************/
-
-int rrbb_get_is_scrambled (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	return (b->is_scrambled);
-}
-
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_descram_state	
- *
- * Purpose:	Get data descrambler state before first data bit of frame.
- *
- * Inputs:	b	Handle for bit array.
- *		
- ***********************************************************************************/
-
-int rrbb_get_descram_state (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	return (b->descram_state);
-}
-
-
-/***********************************************************************************
- *
- * Name:	rrbb_get_prev_descram	
- *
- * Purpose:	Get previous descrambled bit before first data bit of frame.
- *
- * Inputs:	b	Handle for bit array.
- *		
- ***********************************************************************************/
-
-int rrbb_get_prev_descram (rrbb_t b)
-{
-	assert (b != NULL);
-	assert (b->magic1 == MAGIC1);
-	assert (b->magic2 == MAGIC2);
-
-	return (b->prev_descram);
-}
-
-
-/* end rrbb.c */
-
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	rrbb.c
+ *
+ * Purpose:	Raw Received Bit Buffer.
+ *		An array of bits used to hold data out of
+ *		the demodulator before feeding it into the HLDC decoding.
+ *
+ * Version 1.2: Save initial state of 9600 baud descrambler so we can
+ *		attempt bit fix up on G3RUH/K9NG scrambled data.
+ *
+ * Version 1.3:	Store as bytes rather than packing 8 bits per byte.
+ *
+ *******************************************************************************/
+
+#define RRBB_C
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "ax25_pad.h"
+#include "rrbb.h"
+
+
+#define MAGIC1 0x12344321
+#define MAGIC2 0x56788765
+
+
+static int new_count = 0;
+static int delete_count = 0;
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_new	
+ *
+ * Purpose:	Allocate space for an array of samples.
+ *
+ * Inputs:	chan	- Radio channel from whence it came.
+ *
+ *		subchan	- Which demodulator of the channel.
+ *
+ *		slice	- multiple thresholds per demodulator.
+ *
+ *		is_scrambled - Is data scrambled? (true, false)
+ *
+ *		descram_state - State of data descrambler.
+ *
+ *		prev_descram - Previous descrambled bit.
+ *
+ * Returns:	Handle to be used by other functions.
+ *		
+ * Description:	
+ *
+ ***********************************************************************************/
+
+rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram)
+{
+	rrbb_t result;
+
+	assert (chan >= 0 && chan < MAX_CHANS);
+	assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+	assert (slice >= 0 && slice < MAX_SLICERS);
+
+	result = malloc(sizeof(struct rrbb_s));
+
+	result->magic1 = MAGIC1;
+	result->chan = chan;
+	result->subchan = subchan;
+	result->slice = slice;
+	result->magic2 = MAGIC2;
+
+	new_count++;
+
+	if (new_count > delete_count + 100) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("MEMORY LEAK, rrbb_new, new_count=%d, delete_count=%d\n", new_count, delete_count);
+	}
+
+	rrbb_clear (result, is_scrambled, descram_state, prev_descram);
+
+	return (result);
+}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_clear	
+ *
+ * Purpose:	Clear by setting length to zero, etc.
+ *
+ * Inputs:	b 		-Handle for sample array.
+ *
+ *		is_scrambled 	- Is data scrambled? (true, false)
+ *
+ *		descram_state 	- State of data descrambler.
+ *
+ *		prev_descram 	- Previous descrambled bit.
+ *
+ ***********************************************************************************/
+
+void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	assert (is_scrambled == 0 || is_scrambled == 1);
+	assert (prev_descram == 0 || prev_descram == 1);
+
+	b->nextp = NULL;
+
+	b->alevel.rec = 9999;	// TODO: was there some reason for this instead of 0 or -1?
+	b->alevel.mark = 9999;
+	b->alevel.space = 9999;
+
+	b->len = 0;
+
+	b->is_scrambled = is_scrambled;
+	b->descram_state = descram_state;
+	b->prev_descram = prev_descram;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_append_bit	
+ *
+ * Purpose:	Append another bit to the end.
+ *
+ * Inputs:	Handle for sample array.
+ *		Value for the sample.
+ *
+ ***********************************************************************************/
+
+/* Definition in header file so it can be inlined. */
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_chop8	
+ *
+ * Purpose:	Remove 8 from the length.
+ *
+ * Inputs:	Handle for bit array.
+ *		
+ * Description:	Back up after appending the flag sequence.
+ *
+ ***********************************************************************************/
+
+void rrbb_chop8 (rrbb_t b)
+{
+	
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	if (b->len >= 8) {
+	  b->len -= 8;
+	}
+}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_len	
+ *
+ * Purpose:	Get number of bits in the array.
+ *
+ * Inputs:	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_len (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->len);
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_bit	
+ *
+ * Purpose:	Get value of bit in specified position.
+ *
+ * Inputs:	Handle for sample array.
+ *		Index into array.
+ *		
+ ***********************************************************************************/
+
+/* Definition in header file so it can be inlined. */
+
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_flip_bit	
+ *
+ * Purpose:	Complement the value of bit in specified position.
+ *
+ * Inputs:	Handle for bit array.
+ *		Index into array.
+ *		
+ ***********************************************************************************/
+
+//void rrbb_flip_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;
+//
+//	b->data[di] ^= masks[mi];
+//}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_delete	
+ *
+ * Purpose:	Free the storage associated with the bit array.
+ *
+ * Inputs:	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+void rrbb_delete (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	b->magic1 = 0;
+	b->magic2 = 0;
+	
+	free (b);
+
+	delete_count++;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_set_netxp	
+ *
+ * Purpose:	Set the nextp field, used to maintain a queue.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		np	New value for nextp.
+ *		
+ ***********************************************************************************/
+
+void rrbb_set_nextp (rrbb_t b, rrbb_t np)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	b->nextp = np;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_netxp	
+ *
+ * Purpose:	Get value of nextp field.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+rrbb_t rrbb_get_nextp (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->nextp);
+}
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_chan	
+ *
+ * Purpose:	Get channel from which bit buffer was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_chan (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	assert (b->chan >= 0 && b->chan < MAX_CHANS);
+
+	return (b->chan);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_subchan	
+ *
+ * Purpose:	Get subchannel from which bit buffer was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_subchan (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	assert (b->subchan >= 0 && b->subchan < MAX_SUBCHANS);
+
+	return (b->subchan);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_slice	
+ *
+ * Purpose:	Get slice number from which bit buffer was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_slice (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	assert (b->slice >= 0 && b->slice < MAX_SLICERS);
+
+	return (b->slice);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_set_audio_level	
+ *
+ * Purpose:	Set audio level at time the frame was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		alevel	Audio level.
+ *		
+ ***********************************************************************************/
+
+void rrbb_set_audio_level (rrbb_t b, alevel_t alevel)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	b->alevel = alevel;
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_audio_level	
+ *
+ * Purpose:	Get audio level at time the frame was received.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+alevel_t rrbb_get_audio_level (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->alevel);
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_is_scrambled	
+ *
+ * Purpose:	Find out if using scrambled data.
+ *
+ * Inputs:	b	Handle for bit array.
+ *
+ * Returns:	True (for 9600 baud) or false (for slower AFSK).
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_is_scrambled (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->is_scrambled);
+}
+
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_descram_state	
+ *
+ * Purpose:	Get data descrambler state before first data bit of frame.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_descram_state (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->descram_state);
+}
+
+
+/***********************************************************************************
+ *
+ * Name:	rrbb_get_prev_descram	
+ *
+ * Purpose:	Get previous descrambled bit before first data bit of frame.
+ *
+ * Inputs:	b	Handle for bit array.
+ *		
+ ***********************************************************************************/
+
+int rrbb_get_prev_descram (rrbb_t b)
+{
+	assert (b != NULL);
+	assert (b->magic1 == MAGIC1);
+	assert (b->magic2 == MAGIC2);
+
+	return (b->prev_descram);
+}
+
+
+/* end rrbb.c */
+
+
diff --git a/rrbb.h b/rrbb.h
index 89236b6..4b28372 100644
--- a/rrbb.h
+++ b/rrbb.h
@@ -1,90 +1,92 @@
-
-#ifndef RRBB_H
-
-#define RRBB_H
-
-
-typedef short slice_t;
-
-
-#ifdef RRBB_C
-
-/* 
- * Maximum size (in bytes) of an AX.25 frame including the 2 octet FCS. 
- */
-
-#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
-
-/*
- * Maximum number of bits in AX.25 frame excluding the flags.
- * Adequate for extreme case of bit stuffing after every 5 bits
- * which could never happen.
- */
-
-#define MAX_NUM_BITS (MAX_FRAME_LEN * 8 * 6 / 5)
-
-#define SOI 32
-
-typedef struct rrbb_s {
-	int magic1;
-	struct rrbb_s* nextp;	/* Next pointer to maintain a queue. */
-	int chan;		/* Radio channel from which it was received. */
-	int subchan;		/* Which modem when more than one per channel. */
-	alevel_t alevel;	/* Received audio level at time of frame capture. */
-	unsigned int len;	/* Current number of samples in array. */
-
-	int is_scrambled;	/* Is data scrambled G3RUH / K9NG style? */
-	int descram_state;	/* Descrambler state before first data bit of frame. */
-	int prev_descram;	/* Previous descrambled bit. */
-
-	unsigned int data[(MAX_NUM_BITS+SOI-1)/SOI];
-	unsigned int computed_data[MAX_NUM_BITS];
-
-	int magic2;
-} *rrbb_t;
-
-#else
-
-/* Hide the implementation. */
-
-typedef void *rrbb_t;
-
-#endif
-
-
-
-rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state, int prev_descram);
-
-void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram);
-
-
-void rrbb_append_bit (rrbb_t b, int val);
-
-
-void rrbb_chop8 (rrbb_t b);
-
-int rrbb_get_len (rrbb_t b);
-
-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);
-
-void rrbb_delete (rrbb_t b);
-
-void rrbb_set_nextp (rrbb_t b, rrbb_t np);
-rrbb_t rrbb_get_nextp (rrbb_t b);
-
-int rrbb_get_chan (rrbb_t b);
-int rrbb_get_subchan (rrbb_t b);
-
-void rrbb_set_audio_level (rrbb_t b, alevel_t alevel);
-alevel_t rrbb_get_audio_level (rrbb_t b);
-
-int rrbb_get_is_scrambled (rrbb_t b);
-int rrbb_get_descram_state (rrbb_t b);
-int rrbb_get_prev_descram (rrbb_t b);
-
-
-#endif
+
+#ifndef RRBB_H
+
+#define RRBB_H
+
+
+#define FASTER13 1		// Don't pack 8 samples per byte.
+
+
+//typedef short slice_t;
+
+
+/* 
+ * Maximum size (in bytes) of an AX.25 frame including the 2 octet FCS. 
+ */
+
+#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)	
+
+/*
+ * Maximum number of bits in AX.25 frame excluding the flags.
+ * Adequate for extreme case of bit stuffing after every 5 bits
+ * which could never happen.
+ */
+
+#define MAX_NUM_BITS (MAX_FRAME_LEN * 8 * 6 / 5)
+
+typedef struct rrbb_s {
+	int magic1;
+	struct rrbb_s* nextp;	/* Next pointer to maintain a queue. */
+
+	int chan;		/* Radio channel from which it was received. */
+	int subchan;		/* Which modem when more than one per channel. */
+	int slice;		/* Which slicer. */
+
+	alevel_t alevel;	/* Received audio level at time of frame capture. */
+	unsigned int len;	/* Current number of samples in array. */
+
+	int is_scrambled;	/* Is data scrambled G3RUH / K9NG style? */
+	int descram_state;	/* Descrambler state before first data bit of frame. */
+	int prev_descram;	/* Previous descrambled bit. */
+
+	unsigned char fdata[MAX_NUM_BITS];
+
+	int magic2;
+} *rrbb_t;
+
+
+
+rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram);
+
+void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram);
+
+
+static inline /*__attribute__((always_inline))*/ void rrbb_append_bit (rrbb_t b, const unsigned char val)
+{
+	if (b->len >= MAX_NUM_BITS) {
+	  return;	/* Silently discard if full. */
+	}
+	b->fdata[b->len] = val;
+	b->len++;
+}
+
+static inline /*__attribute__((always_inline))*/ unsigned char rrbb_get_bit (const rrbb_t b, const int ind)
+{
+	return (b->fdata[ind]);
+}
+
+
+void rrbb_chop8 (rrbb_t b);
+
+int rrbb_get_len (rrbb_t b);
+
+//void rrbb_flip_bit (rrbb_t b, unsigned int ind);
+
+void rrbb_delete (rrbb_t b);
+
+void rrbb_set_nextp (rrbb_t b, rrbb_t np);
+rrbb_t rrbb_get_nextp (rrbb_t b);
+
+int rrbb_get_chan (rrbb_t b);
+int rrbb_get_subchan (rrbb_t b);
+int rrbb_get_slice (rrbb_t b);
+
+void rrbb_set_audio_level (rrbb_t b, alevel_t alevel);
+alevel_t rrbb_get_audio_level (rrbb_t b);
+
+int rrbb_get_is_scrambled (rrbb_t b);
+int rrbb_get_descram_state (rrbb_t b);
+int rrbb_get_prev_descram (rrbb_t b);
+
+
+#endif
diff --git a/sdr.conf b/sdr.conf
new file mode 100644
index 0000000..e506e52
--- /dev/null
+++ b/sdr.conf
@@ -0,0 +1,30 @@
+#
+# Sample configuration for SDR read-only IGate.
+#
+
+# We might not have an audio output device so set to null.
+# We will override the input half on the command line.
+ADEVICE null null
+CHANNEL 0
+MYCALL xxx
+
+# First you need to specify the name of a Tier 2 server.  
+# The current preferred way is to use one of these regional rotate addresses:
+
+#	noam.aprs2.net 		- for North America
+#	soam.aprs2.net		- for South America
+#	euro.aprs2.net		- for Europe and Africa
+#	asia.aprs2.net 		- for Asia
+#	aunz.aprs2.net		- for Oceania 
+
+IGSERVER noam.aprs2.net
+
+# You also need to specify your login name and passcode. 
+# Contact the author if you can't figure out how to generate the passcode.
+ 
+IGLOGIN xxx 123456
+
+# That's all you need for a receive only IGate which relays
+# messages from the local radio channel to the global servers.
+
+
diff --git a/search_sdks.sh b/search_sdks.sh
new file mode 100644
index 0000000..3ddac3a
--- /dev/null
+++ b/search_sdks.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+#
+# This file is part of Dire Wolf, an amateur radio packet TNC.
+#
+# Bash script to search for SDKs on various MacOSX versions.
+#
+# Copyright (C) 2015 Robert Stiles
+#
+# 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/>.
+#
+
+FILENAME="./use_this_sdk"
+selected_sdk=""
+valid_flag=0
+system_sdk=""
+
+if [ -f $FILENAME ]; then
+    selected_sdk=`cat $FILENAME`
+    if [ -d $selected_sdk ]; then
+        valid_flag=1
+    fi
+fi
+
+if [ $valid_flag -eq "0" ]; then
+    echo " " >&2
+    echo " " >&2
+    echo "Searching for SDKs.... (Wait for results)" >&2
+    echo " " >&2
+    echo "Enter the number and press Enter/Return Key" >&2
+    echo " " >&2
+    echo " " >&2
+
+    prompt="Select SDK to use:"
+
+    loc1=( $(find /Applications/XCode.app -type d -name "MacOSX10.*.sdk") )
+    loc2=( $(find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.*.sdk") )
+
+    options=("${loc1[@]}" "${loc2[@]}")
+
+    if [ "${#options[@]}" -lt "2" ]; then
+        echo "$options"
+    fi
+
+    PS3="$prompt "
+    select opt in "${options[@]}" "Do not use any SDK" ; do
+        if (( REPLY == 1 + ${#options[@]} )) ; then
+            echo " "
+            break
+        elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then
+            selected_sdk="$opt"
+            break
+        fi
+    done
+
+    if [ ! -z "$selected_sdk" ]; then
+        echo "$selected_sdk" > $FILENAME
+    else
+        echo " " > $FILENAME
+    fi
+fi
+
+if [ ! -z "$selected_sdk" ]; then
+    temp_str="$selected_sdk"
+    min_str=""
+    flag=true
+
+    # Search for the last MacOSX in the string.
+    while [ "${#temp_str}" -gt 4 ]; do
+        temp_str="${temp_str#*MacOSX}"
+        temp_str="${temp_str%%.sdk}"
+        min_str="$temp_str"
+        temp_str="${temp_str:1}"
+    done
+
+    # Remove the "u" if 10.4u Universal SDK is used.
+    min_str="${min_str%%u}"
+
+    system_sdk="-isystem ${selected_sdk} -mmacosx-version-min=${min_str}"
+else
+    system_sdk=" "
+fi
+
+echo " " >&2
+echo "*******************************************************************" >&2
+
+if [ -z "${system_sdk}" ]; then
+    echo "SDK Selected: None" >&2
+else
+    echo "SDK Selected: ${system_sdk}" >&2
+fi
+
+echo "To change SDK version execute 'make clean' followed by 'make'." >&2
+echo "*******************************************************************" >&2
+echo " " >&2
+
+echo ${system_sdk}
+
+
diff --git a/serial_port.c b/serial_port.c
new file mode 100644
index 0000000..2a71010
--- /dev/null
+++ b/serial_port.c
@@ -0,0 +1,451 @@
+
+// TODO: Needs more clean up and testing of error conditions.
+
+// TODO: use this in place of other similar code.
+
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014, 2015  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:      serial.c
+ *
+ * Purpose:   	Interface to serial port, hiding operating system differences.
+ *		
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+
+#if __WIN32__
+
+#include <stdlib.h>
+#include <windows.h>
+
+#else
+
+#define __USE_XOPEN2KXSI 1
+#define __USE_XOPEN 1
+#include <stdlib.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "serial_port.h"
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	serial_port_open
+ *
+ * Purpose:	Open serial port.
+ *
+ * Inputs:	devicename	- For Windows, usually like COM5.
+ *				  For Linux, usually /dev/tty...
+ *				  "COMn" also allowed and converted to /dev/ttyS(n-1)
+ *
+ *		baud		- Speed.  4800, 9600, etc.
+ *
+ * Returns 	Handle for serial port or MYFDERROR for error.
+ *
+ *---------------------------------------------------------------*/
+
+
+MYFDTYPE serial_port_open (char *devicename, int baud)
+{
+
+#if __WIN32__
+
+	MYFDTYPE fd;
+	DCB dcb;
+	int ok;
+	char bettername[50];
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("serial_port_open ( '%s', %d )\n", devicename, baud);
+#endif
+
+	
+// Reference:	http://www.robbayer.com/files/serial-win.pdf
+
+// 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
+
+	strlcpy (bettername, devicename, sizeof(bettername));
+	if (strncasecmp(devicename, "COM", 3) == 0) {
+	  int n;
+	  n = atoi(devicename+3);
+	  if (n >= 10) {
+	    strlcpy (bettername, "\\\\.\\", sizeof(bettername));
+	    strlcat (bettername, devicename, sizeof(bettername));
+	  }
+	}
+
+	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 serial 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 ("serial_port_open: GetCommState failed.\n");
+	}
+
+	/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
+
+	dcb.DCBlength = sizeof(DCB);
+
+	switch (baud) {
+
+	  case 1200:	dcb.BaudRate = CBR_1200;	break;
+	  case 2400:	dcb.BaudRate = CBR_2400;	break;
+	  case 4800:	dcb.BaudRate = CBR_4800;	break;
+	  case 9600:	dcb.BaudRate = CBR_9600;	break;
+	  case 19200:	dcb.BaudRate = CBR_19200;	break;
+	  case 38400:	dcb.BaudRate = CBR_38400;	break;
+	  case 57600:	dcb.BaudRate = CBR_57600;	break;
+	  case 115200:	dcb.BaudRate = CBR_115200;	break;
+
+	  default:	text_color_set(DW_COLOR_ERROR);
+	  		dw_printf ("serial_port_open: Unsupported speed %d.  Using 4800.\n", baud);
+			dcb.BaudRate = CBR_4800;	
+			break;
+	}
+
+	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 ("serial_port_open: SetCommState failed.\n");
+	}
+
+	//text_color_set(DW_COLOR_INFO);
+	//dw_printf("Successful serial port open on %s.\n", devicename);
+
+#else
+
+/* Linux version. */
+
+	int fd;
+	struct termios ts;
+	int e;
+	char linuxname[50];
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("serial_port_open ( '%s' )\n", devicename);
+#endif
+
+	/* Translate Windows device name into Linux name. */
+	/* COM1 -> /dev/ttyS0, etc. */
+	
+	strlcpy (linuxname, devicename, sizeof(linuxname));
+
+	if (strncasecmp(devicename, "COM", 3) == 0) {
+	  int n = atoi (devicename + 3);
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Converted serial port name '%s'", devicename);
+	  if (n < 1) n = 1;
+	  snprintf (linuxname, sizeof(linuxname), "/dev/ttyS%d", n-1);
+	  dw_printf (" to Linux equivalent '%s'\n", linuxname);
+	}
+
+	fd = open (linuxname, O_RDWR);
+
+	if (fd == MYFDERROR) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Could not open serial port %s.\n", linuxname);
+	  return (MYFDERROR);
+	}
+
+	e = tcgetattr (fd, &ts);
+	if (e != 0) { perror ("tcgetattr"); }
+
+	cfmakeraw (&ts);
+	
+	ts.c_cc[VMIN] = 1;	/* wait for at least one character */
+	ts.c_cc[VTIME] = 0;	/* no fancy timing. */
+
+	switch (baud) {
+
+	  case 1200:	cfsetispeed (&ts, B1200);	cfsetospeed (&ts, B1200); 	break;
+	  case 2400:	cfsetispeed (&ts, B2400);	cfsetospeed (&ts, B2400); 	break;
+	  case 4800:	cfsetispeed (&ts, B4800);	cfsetospeed (&ts, B4800); 	break;
+	  case 9600:	cfsetispeed (&ts, B9600);	cfsetospeed (&ts, B9600); 	break;
+	  case 19200:	cfsetispeed (&ts, B19200);	cfsetospeed (&ts, B19200); 	break;
+	  case 38400:	cfsetispeed (&ts, B38400);	cfsetospeed (&ts, B38400); 	break;
+	  case 57600:	cfsetispeed (&ts, B57600);	cfsetospeed (&ts, B57600); 	break;
+	  case 115200:	cfsetispeed (&ts, B115200);	cfsetospeed (&ts, B115200); 	break;
+
+	  default:	text_color_set(DW_COLOR_ERROR);
+	  		dw_printf ("serial_port_open: Unsupported speed %d.  Using 4800.\n", baud);
+			cfsetispeed (&ts, B4800);	cfsetospeed (&ts, B4800);
+		 	break;
+	}
+
+	e = tcsetattr (fd, TCSANOW, &ts);
+	if (e != 0) { perror ("tcsetattr"); }
+
+	//text_color_set(DW_COLOR_INFO);
+	//dw_printf("Successfully opened serial port %s.\n", devicename);
+
+#endif
+
+	return (fd);
+}
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:	serial_port_write
+ *
+ * Purpose:	Send characters to serial port.
+ *
+ * Inputs:	fd	- Handle from open.
+ *		str	- Pointer to array of bytes.
+ *		len	- Number of bytes to write.
+ *
+ * Returns 	Number of bytes written.  Should be the same as len.
+ *		-1 if error.
+ *
+ *---------------------------------------------------------------*/
+
+
+int serial_port_write (MYFDTYPE fd, char *str, int len)
+{
+
+	if (fd == MYFDERROR) {
+	  return (-1);
+	}
+
+#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 (fd, str, len, &nwritten, &ov_wr))
+	{
+	  int err = GetLastError();
+
+	  if (err != ERROR_IO_PENDING) 
+	  {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Error writing to serial port.  Error %d.\n\n", err);
+	  }
+	}
+	else if (nwritten != len) 
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Error writing to serial port.  Only %d of %d written.\n\n", (int)nwritten, len);
+	}
+
+	return (nwritten);
+
+#else
+	int written;
+
+        written = write (fd, str, (size_t)len);
+	if (written != len)
+	{
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Error writing to serial port. err=%d\n\n", written);
+	  return (-1);
+	}
+
+	return (written);
+#endif
+
+	
+} /* serial_port_write */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        serial_port_get1
+ *
+ * Purpose:     Get one byte from the serial port.  Wait if not ready.
+ *
+ * Inputs:	fd	- Handle from open.
+ *
+ * Returns:	Value of byte in range of 0 to 255.
+ *		-1 if error.
+ *
+ *--------------------------------------------------------------------*/
+
+int serial_port_get1 (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 ("Serial Port GetOverlappedResult error %d.\n\n", err3);
+	        }
+	        else 
+	        {
+		  /* Success!  n should be 1 */
+	        }
+	      }
+	    }
+	    else
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Serial port read error %d.\n", err1);
+	      return (-1);
+	    }
+	  }
+
+	}	/* end while n==0 */
+
+	CloseHandle(ov_rd.hEvent); 
+
+	if (n != 1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Serial port failed to get one byte. n=%d.\n\n", (int)n);
+	}
+
+
+#else		/* Linux version */
+
+	int n;
+
+	n = read(fd, &ch, (size_t)1);
+
+	if (n != 1) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("serial_port_get1(%d) returns -1 for error.\n", fd);
+	  return (-1);
+	}
+
+#endif
+
+#if DEBUGx
+	text_color_set(DW_COLOR_DEBUG);
+	if (isprint(ch)) {
+	  dw_printf ("serial_port_get1(%d) returns 0x%02x = '%c'\n", fd, ch, ch);
+	}
+	else {
+	  dw_printf ("serial_port_get1(%d) returns 0x%02x\n", fd, ch);
+	}
+#endif
+
+	return (ch);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        serial_port_close
+ *
+ * Purpose:     Close the device.
+ *
+ * Inputs:	fd	- Handle from open.
+ *
+ * Returns:	None.
+ *
+ *--------------------------------------------------------------------*/
+
+
+// TODO: 
+
+
+/* end serial_port.c */
diff --git a/serial_port.h b/serial_port.h
new file mode 100644
index 0000000..6c3287a
--- /dev/null
+++ b/serial_port.h
@@ -0,0 +1,27 @@
+/* serial_port.h */
+
+
+
+#if __WIN32__
+
+#include <stdlib.h>
+#include <windows.h>
+
+typedef HANDLE MYFDTYPE;
+#define MYFDERROR INVALID_HANDLE_VALUE
+
+#else
+
+typedef int MYFDTYPE;
+#define MYFDERROR (-1)
+
+#endif
+
+
+extern MYFDTYPE serial_port_open (char *devicename, int baud);
+
+extern int serial_port_write (MYFDTYPE fd, char *str, int len);
+
+extern int serial_port_get1 (MYFDTYPE fd);
+
+extern void serial_port_close (MYFDTYPE fd);
\ No newline at end of file
diff --git a/server.c b/server.c
index 685fcd1..13e7980 100644
--- a/server.c
+++ b/server.c
@@ -1,1477 +1,1785 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      server.c
- *
- * Purpose:   	Provide service to other applications via "AGW TCPIP Socket Interface".
- *		
- * Input:	
- *
- * Outputs:	  
- *
- * Description:	This provides a TCP socket for communication with a client application.
- *		It implements a subset of the AGW socket interface.
- *
- *		Commands from application recognized:
- *
- *			'R'	Request for version number.
- *				(See below for response.)
- *
- *			'G'	Ask about radio ports.
- *				(See below for response.)
- *
- *			'g'	Capabilities of a port.  (new in 0.8)
- *				(See below for response.)
- *
- *			'k'	Ask to start receiving RAW AX25 frames.
- *
- *			'm'	Ask to start receiving Monitor AX25 frames.
- *
- *			'V'	Transmit UI data frame.
- *				Generate audio for transmission.
- *
- *			'H'	Report recently heard stations.  Not implemented yet.
- *
- *			'K'	Transmit raw AX.25 frame.
- *		
- *			'X'	Register CallSign 
- *		
- *			'x'	Unregister CallSign 
- *		
- *			'y'	Ask Outstanding frames waiting on a Port   (new in 1.2)
- *		
- *			A message is printed if any others are received.
- *
- *			TODO: Should others be implemented?
- *				
- *
- *		Messages sent to client application:
- *
- *			'R'	Reply to Request for version number.
- *				Currently responds with major 1, minor 0.
- *
- *			'G'	Reply to Ask about radio ports.
- *
- *			'g'	Reply to capabilities of a port.  (new in 0.8)
- *
- *			'K'	Received AX.25 frame in raw format.
- *				(Enabled with 'k' command.)
- *
- *			'U'	Received AX.25 frame in monitor format.
- *				(Enabled with 'm' command.)
- *
- *			'y'	Outstanding frames waiting on a Port   (new in 1.2)
- *		
- *
- *
- * References:	AGWPE TCP/IP API Tutorial
- *		http://uz7ho.org.ua/includes/agwpeapi.htm
- *
- * 		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.
- *
- *---------------------------------------------------------------*/
-
-
-/*
- * Native Windows:	Use the Winsock interface.
- * Linux:		Use the BSD socket interface.
- * Cygwin:		Can use either one.
- */
-
-
-#if __WIN32__
-#include <winsock2.h>
-#define _WIN32_WINNT 0x0501
-#include <ws2tcpip.h>
-#else 
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#ifdef __OpenBSD__
-#include <errno.h>
-#else
-#include <sys/errno.h>
-#endif
-#endif
-
-#include <unistd.h>
-#include <stdio.h>
-#include <assert.h>
-#include <string.h>
-#include <time.h>
-#include <ctype.h>
-
-#include "direwolf.h"
-#include "tq.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#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 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_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. */
-
-
-
-
-// TODO:  define in one place, use everywhere.
-// TODO:  Macro to terminate thread when no point to go on.
-
-#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.
- * Assuming little endian such as x86 or ARM.
- * Byte swapping would be required for big endian cpu.
- */
-
-#if __GNUC__
-#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
-#error This needs to be more portable to work on big endian.
-#endif
-#endif
-
-struct agwpe_s {	
-  short portx;			/* 0 for first, 1 for second, etc. */
-  short port_hi_reserved;	
-  short kind_lo;		/* message type */
-  short kind_hi;
-  char call_from[10];
-  char call_to[10];
-  int data_len;			/* Number of data bytes following. */
-  int user_reserved;
-};
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        debug_print 
- *
- * 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;		/* Debug option: Print information flowing from and to client. */
-
-void server_set_debug (int n) 
-{	
-	debug_client = n;
-}
-
-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]);
-	  }
-	  for (i=n; i<16; i++) {
-	    dw_printf ("   ");
-	  }
-	  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;
-	}
-}
-
-typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
-
-static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int msg_len)
-{
-	char direction [10];
-	char datakind[80];
-	const char *prefix [2] = { "<<<", ">>>" };
-
-	switch (fromto) {
-
-	  case FROM_CLIENT:
-	    strcpy (direction, "from");		/* from the client application */
-
-	    switch (pmsg->kind_lo) {
-	      case 'P': strcpy (datakind, "Application Login"); break;
-	      case 'X': strcpy (datakind, "Register CallSign"); break;
-	      case 'x': strcpy (datakind, "Unregister CallSign"); break;
-	      case 'G': strcpy (datakind, "Ask Port Information"); break;
-	      case 'm': strcpy (datakind, "Enable Reception of Monitoring Frames"); break;
-	      case 'R': strcpy (datakind, "AGWPE Version Info"); break;
-	      case 'g': strcpy (datakind, "Ask Port Capabilities"); break;
-	      case 'H': strcpy (datakind, "Callsign Heard on a Port"); break;
-	      case 'y': strcpy (datakind, "Ask Outstanding frames waiting on a Port"); break;
-	      case 'Y': strcpy (datakind, "Ask Outstanding frames waiting for a connection"); break;
-	      case 'M': strcpy (datakind, "Send UNPROTO Information"); break;
-	      case 'C': strcpy (datakind, "Connect, Start an AX.25 Connection"); break;
-	      case 'D': strcpy (datakind, "Send Connected Data"); break;
-	      case 'd': strcpy (datakind, "Disconnect, Terminate an AX.25 Connection"); break;
-	      case 'v': strcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters"); break;
-	      case 'V': strcpy (datakind, "Send UNPROTO VIA"); break;
-	      case 'c': strcpy (datakind, "Non-Standard Connections, Connection with PID"); break;
-	      case 'K': strcpy (datakind, "Send data in raw AX.25 format"); break;
-	      case 'k': strcpy (datakind, "Activate reception of Frames in raw format"); break;
-	      default:  strcpy (datakind, "**INVALID**"); break;
-	    }
-	    break;
-
-	  case TO_CLIENT:
-	  default:
-	    strcpy (direction, "to");	/* sent to the client application. */
-
-	    switch (pmsg->kind_lo) {
-	      case 'R': strcpy (datakind, "Version Number"); break;
-	      case 'X': strcpy (datakind, "Callsign Registration"); break;
-	      case 'G': strcpy (datakind, "Port Information"); break;
-	      case 'g': strcpy (datakind, "Capabilities of a Port"); break;
-	      case 'y': strcpy (datakind, "Frames Outstanding on a Port"); break;
-	      case 'Y': strcpy (datakind, "Frames Outstanding on a Connection"); break;
-	      case 'H': strcpy (datakind, "Heard Stations on a Port"); break;
-	      case 'C': strcpy (datakind, "AX.25 Connection Received"); break;
-	      case 'D': strcpy (datakind, "Connected AX.25 Data"); break;
-	      case 'M': strcpy (datakind, "Monitored Connected Information"); break;
-	      case 'S': strcpy (datakind, "Monitored Supervisory Information"); break;
-	      case 'U': strcpy (datakind, "Monitored Unproto Information"); break;
-	      case 'T': strcpy (datakind, "Monitoring Own Information"); break;
-	      case 'K': strcpy (datakind, "Monitored Information in Raw Format"); break;
-	      default:  strcpy (datakind, "**INVALID**"); break;
-	    }
-	}
-
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("\n");
-
-	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 ((unsigned char*)pmsg + sizeof(struct agwpe_s), pmsg->data_len);
-
-	if (msg_len < 36) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("AGWPE message length, %d, is shorter than minumum 36.\n", msg_len);
-	}
-	if (msg_len != pmsg->data_len + 36) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("AGWPE message length, %d, inconsistent with data length %d.\n", msg_len, pmsg->data_len);
-	}
-
-}
-
-/*-------------------------------------------------------------------
- *
- * Name:        server_init
- *
- * Purpose:     Set up a server to listen for connection requests from
- *		an application such as Xastir.
- *
- * Inputs:	mc->agwpe_port	- TCP port for server.
- *				  Main program has default of 8000 but allows
- *				  an alternative to be specified on the command line
- *
- *				0 means disable.  New in version 1.2.
- *
- * Outputs:	
- *
- * 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.
- *
- *--------------------------------------------------------------------*/
-
-static struct audio_s *save_audio_config_p;
-
-
-void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc)
-{
-	int client;
-
-#if __WIN32__
-	HANDLE connect_listen_th;
-	HANDLE cmd_listen_th[MAX_NET_CLIENTS];
-#else
-	pthread_t connect_listen_tid;
-	pthread_t cmd_listen_tid[MAX_NET_CLIENTS];
-#endif
-	int e;
-	int server_port = mc->agwpe_port;		/* Usually 8000 but can be changed. */
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("server_init ( %d )\n", server_port);
-	debug_a = 1;
-#endif
-
-	save_audio_config_p = audio_config_p;
-
-	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;
-	}
-
-	if (server_port == 0) {
-	  text_color_set(DW_COLOR_INFO);
-	  dw_printf ("Disabled AGW network client port.\n");
-	  return;
-	}
-
-
-/*
- * This waits for a client to connect and sets an available client_sock[n].
- */
-#if __WIN32__
-	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");
-	  return;
-	}
-#else
-	e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)server_port);
-	if (e != 0) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror("Could not create AGW connect listening thread");
-	  return;
-	}
-#endif
-
-/*
- * 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[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[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
-	}
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        connect_listen_thread
- *
- * Purpose:     Wait for a connection request from an application.
- *
- * Inputs:	arg		- TCP port for server.
- *				  Main program has default of 8000 but allows
- *				  an alternative to be specified on the command line
- *
- * Outputs:	client_sock	- File descriptor for communicating with client app.
- *
- * Description:	Wait for connection request from client and establish
- *		communication.
- *		Note that the client can go away and come back again and
- *		re-establish communication without restarting this application.
- *
- *--------------------------------------------------------------------*/
-
-static THREAD_F connect_listen_thread (void *arg)
-{
-#if __WIN32__
-
-	struct addrinfo hints;
-	struct addrinfo *ai = NULL;
-	int err;
-	char server_port_str[12];
-
-	SOCKET listen_sock;  
-	WSADATA wsadata;
-
-	sprintf (server_port_str, "%d", (int)(long)arg);
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-        dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(long)arg, server_port_str);
-#endif
-	err = WSAStartup (MAKEWORD(2,2), &wsadata);
-	if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("WSAStartup failed: %d\n", err);
-	    return (NULL);		// TODO: what should this be for Windows?
-	}
-
-	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Could not find a usable version of Winsock.dll\n");
-          WSACleanup();
-	  //sleep (1);
-          return (NULL);		// TODO: what should this be for Windows?
-	}
-
-	memset (&hints, 0, sizeof(hints));
-	hints.ai_family = AF_INET;
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_protocol = IPPROTO_TCP;
-	hints.ai_flags = AI_PASSIVE;
-
-	err = getaddrinfo(NULL, server_port_str, &hints, &ai);
-	if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf("getaddrinfo failed: %d\n", err);
-	    //sleep (1);
-	    WSACleanup();
-	    return (NULL);		// TODO: what should this be for Windows?
-	}
-
-	listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-	if (listen_sock == INVALID_SOCKET) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
-	  return (NULL);		// TODO: what should this be for Windows?
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-    	dw_printf("Binding to port %s ... \n", server_port_str);
-#endif
-
-	err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
-	if (err == SOCKET_ERROR) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Bind failed with error: %d\n", WSAGetLastError());		// TODO: translate number to text?
-	  dw_printf("Some other application is probably already using port %s.\n", server_port_str);
-	  dw_printf("Try using a different port number with AGWPORT in the configuration file.\n");
-          freeaddrinfo(ai);
-          closesocket(listen_sock);
-          WSACleanup();
-          return (NULL);		// TODO: what should this be for Windows?
-        }
-
-	freeaddrinfo(ai);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
- 	dw_printf("opened socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, server_port_str );
-#endif
-
- 	while (1) {
-
-	  int client;
-	  int c;
-	  
-	  client = -1;
-	  for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
-	    if (client_sock[c] <= 0) {
-	      client = c;
-	    }
-	  }
-
-/*
- * Listen for connection if we have not reached maximum.
- */
-	  if (client >= 0) {
-
-	    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);		// TODO: what should this be for Windows?
-	    }
-	
-	    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[client] = accept(listen_sock, NULL, 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);		// TODO: what should this be for Windows?
-            }
-
-	    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[client] = 0;
-	    enable_send_monitor_to_client[client] = 0;
-	  }
-	  else {
-	    SLEEP_SEC(1);	/* wait then check again if more clients allowed. */
-	  }
- 	}
-
-#else		/* End of Windows case, now Linux */
-
-
-    	struct sockaddr_in sockaddr; /* Internet socket address stuct */
-    	socklen_t sockaddr_size = sizeof(struct sockaddr_in);
-	int server_port = (int)(long)arg;
-	int listen_sock;  
-
-	listen_sock= socket(AF_INET,SOCK_STREAM,0);
-	if (listen_sock == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-	  perror ("connect_listen_thread: Socket creation failed");
-	  return (NULL);
-	}
-
-    	sockaddr.sin_addr.s_addr = INADDR_ANY;
-    	sockaddr.sin_port = htons(server_port);
-    	sockaddr.sin_family = AF_INET;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-    	dw_printf("Binding to port %d ... \n", server_port);
-#endif
-
-        if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))  == -1) {
-	  text_color_set(DW_COLOR_ERROR);
-          dw_printf("Bind failed with error: %d\n", errno);
-          dw_printf("%s\n", strerror(errno));
-	  dw_printf("Some other application is probably already using port %d.\n", server_port);
-	  dw_printf("Try using a different port number with AGWPORT in the configuration file.\n");
-          return (NULL);
-	}
-
-	getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
- 	dw_printf("opened socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
-#endif
-
- 	while (1) {
-
-	  int client;
-	  int c;
-	  
-	  client = -1;
-	  for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
-	    if (client_sock[c] <= 0) {
-	      client = c;
-	    }
-	  }
-
-	  if (client >= 0) {
-
-	    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 %d on port %d ...\n", client, server_port);
-         
-            client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
-
-	    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[client] = 0;
-	    enable_send_monitor_to_client[client] = 0;
-	  }
-	  else {
-	    SLEEP_SEC(1);	/* wait then check again if more clients allowed. */
-	  }
- 	}
-#endif
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        server_send_rec_packet
- *
- * Purpose:     Send a received packet to the client app.
- *
- * Inputs:	chan		- Channel number where packet was received.
- *				  0 = first, 1 = second if any.
- *
- *		pp		- Identifier for packet object.
- *
- *		fbuf		- Address of raw received frame buffer.
- *		flen		- Length of raw received frame.
- *		
- *
- * Description:	Send message to client if connected.
- *		Disconnect from client, and notify user, if any error.
- *
- *		There are two different formats:
- *			RAW - the original received frame.
- *			MONITOR - just the information part.
- *
- *--------------------------------------------------------------------*/
-
-
-void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int flen)
-{
-	struct {	
-	  struct agwpe_s hdr;
-	  char data[1+AX25_MAX_PACKET_LEN];		
-	} agwpe_msg;
-
-	int err;
-	int info_len;
-	unsigned char *pinfo;
-	int client;
-
-
-/*
- * RAW format
- */
-	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));
-
-	    agwpe_msg.hdr.portx = chan;
-
-	    agwpe_msg.hdr.kind_lo = 'K';
-
-	    ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from);
-
-	    ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to);
-
-	    agwpe_msg.hdr.data_len = flen + 1;
-
-	    /* Stick in extra byte for the "TNC" to use. */
-
-	    agwpe_msg.data[0] = 0;
-	    memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen);
-
-	    if (debug_client) {
-	      debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
-	    }
-
-#if __WIN32__	
-            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]);
-	      client_sock[client] = -1;
-	      WSACleanup();
-	    }
-#else
-            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]);
-	      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] && client_sock[client] > 0 
-			&& ax25_get_control(pp) == AX25_UI_FRAME){
-
-	    time_t clock;
-	    struct tm *tm;
-
-	    clock = time(NULL);
-	    tm = localtime(&clock);
-
-	    memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr));
-
-	    agwpe_msg.hdr.portx = chan;
-
-	    agwpe_msg.hdr.kind_lo = 'U';
-
-	    ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from);
-
-	    ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to);
-
-	    info_len = ax25_get_info (pp, &pinfo);
-
-	    /* http://uz7ho.org.ua/includes/agwpeapi.htm#_Toc500723812 */
-
-	    /* Description mentions one CR character after timestamp but example has two. */
-	    /* Actual observed cases have only one. */
-	    /* Also need to add extra CR, CR, null at end. */
-	    /* The documentation example includes these 3 extra in the Len= value */
-	    /* but actual observed data uses only the packet info length. */
-
-	    sprintf (agwpe_msg.data, " %d:Fm %s To %s <UI pid=%02X Len=%d >[%02d:%02d:%02d]\r%s\r\r",
-			chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to,
-			ax25_get_pid(pp), info_len, 
-			tm->tm_hour, tm->tm_min, tm->tm_sec,
-			pinfo);
-
-	    agwpe_msg.hdr.data_len = strlen(agwpe_msg.data) + 1 /* include null */ ;
-
-	    if (debug_client) {
-	      debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
-	    }
-
-#if __WIN32__	
-            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 %d.  Closing connection.\n\n", WSAGetLastError(), client);
-	      closesocket (client_sock[client]);
-	      client_sock[client] = -1;
-	      WSACleanup();
-	    }
-#else
-            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 %d.  Closing connection.\n\n", client);
-	      close (client_sock[client]);
-	      client_sock[client] = -1;    
-	    }
-#endif
-	  }
-	}
-
-} /* server_send_rec_packet */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        read_from_socket
- *
- * Purpose:     Read from socket until we have desired number of bytes.
- *
- * Inputs:	fd		- file descriptor.
- *		ptr		- address where data should be placed.
- *		len		- desired number of bytes.
- *
- * Description:	Just a wrapper for the "read" system call but it should
- *		never return fewer than the desired number of bytes.
- *
- *--------------------------------------------------------------------*/
-
-static int read_from_socket (int fd, char *ptr, int len)
-{
-	int got_bytes = 0;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
-#endif
-	while (got_bytes < len) {
-	  int n;
-
-#if __WIN32__
-
-//TODO: any flags for send/recv?
-
-	  n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
-#else
-	  n = read (fd, ptr + got_bytes, len - got_bytes);
-#endif
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("read_from_socket: n = %d\n", n);
-#endif
-	  if (n <= 0) {
-	    return (n);
-	  }
-
-	  got_bytes += n;
-	}
-	assert (got_bytes >= 0 && got_bytes <= len);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("read_from_socket: return %d\n", got_bytes);
-#endif
-	return (got_bytes);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        cmd_listen_thread
- *
- * Purpose:     Wait for command messages from an application.
- *
- * Inputs:	arg		- client number, 0 .. MAX_NET_CLIENTS-1
- *
- * 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
- *		re-establish communication without restarting this application.
- *
- *--------------------------------------------------------------------*/
-
-
-static void send_to_client (int client, void *reply_p)
-{
-	struct agwpe_s *ph;
-	int len;
-#if __WIN32__     
-#else
-	int err;
-#endif
-
-	ph = (struct agwpe_s *) reply_p;	// Replies are often hdr + other stuff.
-
-	len = sizeof(struct agwpe_s) + ph->data_len;
-
-	/* Not sure what max data length might be. */
-
-	if (ph->data_len < 0 || ph->data_len > 4096) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Invalid data length %d for AGW protocol message to client %d.\n", ph->data_len, client);
-	  debug_print (TO_CLIENT, client, ph, len);
-	}
-
-	if (debug_client) {
-	  debug_print (TO_CLIENT, client, ph, len);
-	}
-
-#if __WIN32__     
-	send (client_sock[client], (char*)(ph), len, 0);
-#else
-	err = write (client_sock[client], ph, len);
-#endif
-}
-
-
-static THREAD_F cmd_listen_thread (void *arg)
-{
-	int n;
-
-	struct {
-	  struct agwpe_s hdr;		/* Command header. */
-	
-	  char data[512];		/* Additional data used by some commands. */
-					/* Maximum for 'V': 1 + 8*10 + 256 */
-	} cmd;
-
-	int client = (int)(long)arg;
-
-	assert (client >= 0 && client < MAX_NET_CLIENTS);
-
-	while (1) {
-
-	  while (client_sock[client] <= 0) {
-	    SLEEP_SEC(1);			/* Not connected.  Try again later. */
-	  }
-
-	  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 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[client]);
-#else
-	    close (client_sock[client]);
-#endif
-	    client_sock[client] = -1;
-	    continue;
-	  }
-
-/*
- * 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;		// TODO: what should this be for Windows?
-	  }
-
-	  cmd.data[0] = '\0';
-
-	  if (cmd.hdr.data_len > 0) {
-	    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 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[client]);
-#else
-	      close (client_sock[client]);
-#endif
-	      client_sock[client] = -1;
-	      return NULL;
-	    }
-	    if (n > 0) {
-		cmd.data[cmd.hdr.data_len] = '\0';
-	    }
-	  }
-
-/*
- * print & process message from client.
- */
-
-	  if (debug_client) {
-	    debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
-	  }
-
-	  switch (cmd.hdr.kind_lo) {
-
-	    case 'R':				/* Request for version number */
-	      {
-		struct {
-		  struct agwpe_s hdr;
-	 	  int major_version;
-	 	  int minor_version;
-		} reply;
-
-
-	        memset (&reply, 0, sizeof(reply));
-	        reply.hdr.kind_lo = 'R';
-	        reply.hdr.data_len = sizeof(reply.major_version) + sizeof(reply.minor_version);
-		assert (reply.hdr.data_len == 8);
-
-		// Xastir only prints this and doesn't care otherwise.
-		// APRSIS32 doesn't seem to care.
-		// UI-View32 wants on 2000.15 or later.
-
-	        reply.major_version = 2005;
-	        reply.minor_version = 127;
-
-		assert (sizeof(reply) == 44);
-
-	        send_to_client (client, &reply);
-
-	      }
-	      break;
-
-	    case 'G':				/* Ask about radio ports */
-
-	      {
-		struct {
-		  struct agwpe_s hdr;
-	 	  char info[200];
-		} reply;
-
-
-		int j, count;
-
-
-	        memset (&reply, 0, sizeof(reply));
-	        reply.hdr.kind_lo = 'G';
-
-
-		// Xastir only prints this and doesn't care otherwise.
-		// YAAC uses this to identify available channels.
-
-		// The interface manual wants the first to be "Port1" 
-		// so channel 0 corresponds to "Port1."
-		// We can have gaps in the numbering.
-		// I wonder what applications will think about that.
-
-#if 1
-		// No other place cares about total number.
-
-		count = 0;
-		for (j=0; j<MAX_CHANS; j++) {
-	 	  if (save_audio_config_p->achan[j].valid) {
-		    count++;
-		  }
-		}
-		sprintf (reply.info, "%d;", count);
-
-		for (j=0; j<MAX_CHANS; j++) {
-	 	  if (save_audio_config_p->achan[j].valid) {
-		    char stemp[100];
-		    int a = ACHAN2ADEV(j);
-		    // If I was really ambitious, some description could be provided.
-		    static const char *names[8] = { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth" };
-
-		    if (save_audio_config_p->adev[a].num_channels == 1) {
-		      sprintf (stemp, "Port%d %s soundcard mono;", j+1, names[a]);
-		      strcat (reply.info, stemp);
-		    }
-		    else {
-		      sprintf (stemp, "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left");
-		      strcat (reply.info, stemp);
-		    }
-		  }
-		}
-
-#else
-		if (num_channels == 1) {
-		  sprintf (reply.info, "1;Port1 Single channel;");
-		}
-		else {
-		  sprintf (reply.info, "2;Port1 Left channel;Port2 Right Channel;");
-		}
-#endif
-	        reply.hdr.data_len = strlen(reply.info) + 1;
-
-	        send_to_client (client, &reply);
-
-	      }
-	      break;
-
-
-	    case 'g':				/* Ask about capabilities of a port. */
-
-	      {
-		struct {
-		  struct agwpe_s hdr;
-	 	  unsigned char on_air_baud_rate; 	/* 0=1200, 3=9600 */
-		  unsigned char traffic_level;		/* 0xff if not in autoupdate mode */
-		  unsigned char tx_delay;
-		  unsigned char tx_tail;
-		  unsigned char persist;
-		  unsigned char slottime;
-		  unsigned char maxframe;
-		  unsigned char active_connections;
-		  int how_many_bytes;
-		} reply;
-
-
-	        memset (&reply, 0, sizeof(reply));
-
-		reply.hdr.portx = cmd.hdr.portx;	/* Reply with same port number ! */
-	        reply.hdr.kind_lo = 'g';
-	        reply.hdr.data_len = 12;
-
-		// YAAC asks for this.
-		// Fake it to keep application happy.
-
-	        reply.on_air_baud_rate = 0;
-		reply.traffic_level = 1;
-		reply.tx_delay = 0x19;
-		reply.tx_tail = 4;
-		reply.persist = 0xc8;
-		reply.slottime = 4;
-		reply.maxframe = 7;
-		reply.active_connections = 0;
-		reply.how_many_bytes = 1;
-
-		assert (sizeof(reply) == 48);
-
-	        send_to_client (client, &reply);
-
-	      }
-	      break;
-
-
-	    case 'H':				/* Ask about recently heard stations. */
-
-	      {
-#if 0
-		struct {
-		  struct agwpe_s hdr;
-	 	  char info[100];
-		} reply;
-
-
-	        memset (&reply.hdr, 0, sizeof(reply.hdr));
-	        reply.hdr.kind_lo = 'H';
-
-		// TODO:  Implement properly.  
-
-	        reply.hdr.portx = cmd.hdr.portx
-
-	        strcpy (reply.hdr.call_from, "WB2OSZ-15");
-
-	        strcpy (agwpe_msg.data, ...);
-
-	        reply.hdr.data_len = strlen(reply.info);
-
-	        send_to_client (client, &reply);
-#endif
-	      }
-	      break;
-	    
-
-
-
-	    case 'k':				/* Ask to start receiving RAW AX25 frames */
-
-	      // Actually it is a toggle so we must be sure to clear it for a new connection.
-
-	      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[client] = ! enable_send_monitor_to_client[client];
-	      break;
-
-
-	    case 'V':				/* Transmit UI data frame */
-	      {
-	      	// Data format is:
-	      	//	1 byte for number of digipeaters.
-	      	//	10 bytes for each digipeater.
-	      	//	data part of message.
-
-	      	char stemp[512];
-		char *p;
-		int ndigi;
-		int k;
-	      
-		packet_t pp;
-    		//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);
-
-		cmd.data[cmd.hdr.data_len] = '\0';
-		ndigi = cmd.data[0];
-		p = cmd.data + 1;
-
-		for (k=0; k<ndigi; k++) {
-		  strcat (stemp, ",");
-		  strcat (stemp, p);
-		  p += 10;
-	        }
-		strcat (stemp, ":");
-		strcat (stemp, p);
-
-	        //text_color_set(DW_COLOR_DEBUG);
-		//dw_printf ("Transmit '%s'\n", stemp);
-
-		pp = ax25_from_text (stemp, 1);
-
-
-		if (pp == NULL) {
-	          text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Failed to create frame from AGW 'V' message.\n");
-		}
-		else {
-
-		  /* This goes into the low priority queue because it is an original. */
-
-		  /* Note that the protocol has no way to set the "has been used" */
-		  /* bits in the digipeater fields. */
-
-		  /* This explains why the digipeating option is grayed out in */
-		  /* xastir when using the AGW interface.  */
-		  /* The current version uses only the 'V' message, not 'K' for transmitting. */
-
-		  tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
-
-		}
-	      }
-	      
-	      break;
-
-	    case 'K':				/* Transmit raw AX.25 frame */
-	      {
-	      	// Message contains:
-	      	//	port number for transmission.
-	      	//	data length
-	      	//	data which is raw ax.25 frame.
-		//		
-	      
-		packet_t pp;
-		alevel_t alevel;
-
-		// 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.
-
-		memset (&alevel, 0xff, sizeof(alevel));
-		pp = ax25_from_frame ((unsigned char *)cmd.data+1, cmd.hdr.data_len - 1, alevel);
-
-		if (pp == NULL) {
-	          text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Failed to create frame from AGW 'K' message.\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. */
-
-		  if (ax25_get_num_repeaters(pp) >= 1 &&
-		      ax25_get_h(pp,AX25_REPEATER_1)) {
-		    tq_append (cmd.hdr.portx, TQ_PRIO_0_HI, pp);
-		  }
-		  else {
-		    tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
-		  }
-		}
-	      }
-	      
-	      break;
-
-	    case 'X':				/* Register CallSign  */
-
-	      /* Send success status. */
-
-	      {
-		struct {
-		  struct agwpe_s hdr;
-		  char data;
-		} reply;
-
-
-	        memset (&reply, 0, sizeof(reply));
-	        reply.hdr.kind_lo = 'X';
-		memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from));
-	        reply.hdr.data_len = 1;
-		reply.data = 1;		/* success */
-	
-		// Version 1.0.
-		// Previously used sizeof(reply) but compiler rounded it up to next byte boundary.
-		// That's why more cumbersome size expression is used.
-
-	        send_to_client (client, &reply);
-
-	      }
-	      break;
-
-	    case 'x':				/* Unregister CallSign  */
-	      /* No reponse is expected. */
-	      break;
-
-	    case 'C':				/* Connect, Start an AX.25 Connection  */
-	    case 'v':	      			/* Connect VIA, Start an AX.25 circuit thru digipeaters */
-	    case 'D': 				/* Send Connected Data */
-	    case 'd': 				/* Disconnect, Terminate an AX.25 Connection */
-
-	      // Version 1.0.  Better message instead of generic unexpected command.
-
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("\n");
-	      dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.kind_lo, client);
-	      dw_printf ("Connected packet mode is not implemented.\n");
-
-	      break;
-
-#if 0
-	    case 'M': 				/* Send UNPROTO Information */
-
-		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 0, total length = 253
-		        portx = 0, port_hi_reserved = 0
-		        kind_lo = 77 = 'M', kind_hi = 0
-		        call_from = "SV2AGW-1", call_to = "BEACON"
-		        data_len = 217, user_reserved = 588, data =
-		  000:  54 68 69 73 20 76 65 72 73 69 6f 6e 20 75 73 65  This version use
-		  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 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
-
-
-	    case 'y':				/* Ask Outstanding frames waiting on a Port  */
-
-	      {
-		struct {
-		  struct agwpe_s hdr;
-		  int data;			// Assuming little-endian architecture.
-		} reply;
-
-
-	        memset (&reply, 0, sizeof(reply));
-		reply.hdr.portx = cmd.hdr.portx;	/* Reply with same port number */
-	        reply.hdr.kind_lo = 'y';
-	        reply.hdr.data_len = 4;
-		reply.data = 0;		
-	
-	        if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) {
-		  reply.data = tq_count (cmd.hdr.portx, TQ_PRIO_0_HI) + tq_count (cmd.hdr.portx, TQ_PRIO_1_LO);
-		}
-
-	        send_to_client (client, &reply);
-	      }
-	      break;
-
-	    default:
-
-	      text_color_set(DW_COLOR_ERROR);
-	      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;
-	  }
-	
-
-	}
-
-}
-
-/* end server.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      server.c
+ *
+ * Purpose:   	Provide service to other applications via "AGW TCPIP Socket Interface".
+ *		
+ * Input:	
+ *
+ * Outputs:	  
+ *
+ * Description:	This provides a TCP socket for communication with a client application.
+ *		It implements a subset of the AGW socket interface.
+ *
+ *		Commands from application recognized:
+ *
+ *			'R'	Request for version number.
+ *				(See below for response.)
+ *
+ *			'G'	Ask about radio ports.
+ *				(See below for response.)
+ *
+ *			'g'	Capabilities of a port.  (new in 0.8)
+ *				(See below for response.)
+ *
+ *			'k'	Ask to start receiving RAW AX25 frames.
+ *
+ *			'm'	Ask to start receiving Monitor AX25 frames.
+ *
+ *			'V'	Transmit UI data frame.
+ *				Generate audio for transmission.
+ *
+ *			'H'	Report recently heard stations.  Not implemented yet.
+ *
+ *			'K'	Transmit raw AX.25 frame.
+ *		
+ *			'X'	Register CallSign 
+ *		
+ *			'x'	Unregister CallSign 
+ *		
+ *			'y'	Ask Outstanding frames waiting on a Port   (new in 1.2)
+ *		
+ *			A message is printed if any others are received.
+ *
+ *			TODO: Should others be implemented?
+ *				
+ *
+ *		Messages sent to client application:
+ *
+ *			'R'	Reply to Request for version number.
+ *				Currently responds with major 1, minor 0.
+ *
+ *			'G'	Reply to Ask about radio ports.
+ *
+ *			'g'	Reply to capabilities of a port.  (new in 0.8)
+ *
+ *			'K'	Received AX.25 frame in raw format.
+ *				(Enabled with 'k' command.)
+ *
+ *			'U'	Received AX.25 frame in monitor format.
+ *				(Enabled with 'm' command.)
+ *
+ *			'y'	Outstanding frames waiting on a Port   (new in 1.2)
+ *		
+ *
+ *
+ * References:	AGWPE TCP/IP API Tutorial
+ *		http://uz7ho.org.ua/includes/agwpeapi.htm
+ *
+ * 		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.
+ *
+ *---------------------------------------------------------------*/
+
+
+/*
+ * Native Windows:	Use the Winsock interface.
+ * Linux:		Use the BSD socket interface.
+ * Cygwin:		Can use either one.
+ */
+
+
+#if __WIN32__
+#include <winsock2.h>
+#define _WIN32_WINNT 0x0501
+#include <ws2tcpip.h>
+#else 
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#ifdef __OpenBSD__
+#include <errno.h>
+#else
+#include <sys/errno.h>
+#endif
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "direwolf.h"
+#include "tq.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#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 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_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. */
+
+
+/*
+ * Registered callsigns from 'X' command.
+ * For simplicity just use a fixed size array until there
+ * is evidence that a larger number would be needed.
+ *
+ * Also keep track of which client did the registration.
+ * For example client 0 might register the callsign ABC
+ * and client 1 register DEF.   If something comes addressed
+ * to DEF, we would want it going only to client 1.
+ */
+
+#define MAX_REG_CALLSIGNS 20
+
+static char registered_callsigns[MAX_REG_CALLSIGNS][AX25_MAX_ADDR_LEN];
+static int registered_by_client[MAX_REG_CALLSIGNS];
+
+
+// TODO:  define in one place, use everywhere.
+// TODO:  Macro to terminate thread when no point to go on.
+
+#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.
+ * Multibyte numeric values require rearranging for big endian cpu.
+ */
+
+/*
+ * With MinGW version 4.6, obviously x86.
+ * or Linux gcc version 4.9, Linux ARM.
+ *
+ *	$ gcc -E -dM - < /dev/null | grep END
+ *	#define __ORDER_LITTLE_ENDIAN__ 1234
+ *	#define __FLOAT_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__
+ *	#define __ORDER_PDP_ENDIAN__ 3412
+ *	#define __ORDER_BIG_ENDIAN__ 4321
+ *	#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
+ *
+ * This is for standard OpenWRT on MIPS.
+ *
+ *	#define __ORDER_LITTLE_ENDIAN__ 1234
+ *	#define __FLOAT_WORD_ORDER__ __ORDER_BIG_ENDIAN__
+ *	#define __ORDER_PDP_ENDIAN__ 3412
+ *	#define __ORDER_BIG_ENDIAN__ 4321
+ *	#define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__
+ *
+ * This was reported for an old Mac with PowerPC processor.
+ * (Newer versions have x86.)
+ *
+ *	$ gcc -E -dM - < /dev/null | grep END
+ *	#define __BIG_ENDIAN__ 1
+ *	#define _BIG_ENDIAN 1
+ */
+
+
+#if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+
+// gcc >= 4.2 has __builtin_swap32() but be compatible with older versions.
+
+#define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) )
+#define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) )
+
+#else
+
+#define host2netle(x) (x)
+#define netle2host(x) (x)
+
+#endif
+
+
+struct agwpe_s {	
+  unsigned char portx;		/* 0 for first, 1 for second, etc. */
+  unsigned char reserved1;
+  unsigned char reserved2;
+  unsigned char reserved3;
+
+  unsigned char datakind;	/* message type, usually written as a letter. */
+  unsigned char reserved4;
+  unsigned char pid;
+  unsigned char reserved5;
+
+  char call_from[10];
+
+  char call_to[10];
+
+  int data_len_NETLE;		/* Number of data bytes following. */
+				/* _NETLE suffix is reminder to convert for network byte order. */
+
+  int user_reserved_NETLE;
+};
+
+
+static void send_to_client (int client, void *reply_p);
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        debug_print 
+ *
+ * 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;		/* Debug option: Print information flowing from and to client. */
+
+void server_set_debug (int n) 
+{	
+	debug_client = n;
+}
+
+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]);
+	  }
+	  for (i=n; i<16; i++) {
+	    dw_printf ("   ");
+	  }
+	  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;
+	}
+}
+
+typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
+
+static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int msg_len)
+{
+	char direction [10];
+	char datakind[80];
+	const char *prefix [2] = { "<<<", ">>>" };
+
+	switch (fromto) {
+
+	  case FROM_CLIENT:
+	    strlcpy (direction, "from", sizeof(direction));		/* from the client application */
+
+	    switch (pmsg->datakind) {
+	      case 'P': strlcpy (datakind, "Application Login",				sizeof(datakind)); break;
+	      case 'X': strlcpy (datakind, "Register CallSign",				sizeof(datakind)); break;
+	      case 'x': strlcpy (datakind, "Unregister CallSign",			sizeof(datakind)); break;
+	      case 'G': strlcpy (datakind, "Ask Port Information",			sizeof(datakind)); break;
+	      case 'm': strlcpy (datakind, "Enable Reception of Monitoring Frames",	sizeof(datakind)); break;
+	      case 'R': strlcpy (datakind, "AGWPE Version Info",			sizeof(datakind)); break;
+	      case 'g': strlcpy (datakind, "Ask Port Capabilities",			sizeof(datakind)); break;
+	      case 'H': strlcpy (datakind, "Callsign Heard on a Port",			sizeof(datakind)); break;
+	      case 'y': strlcpy (datakind, "Ask Outstanding frames waiting on a Port",	sizeof(datakind)); break;
+	      case 'Y': strlcpy (datakind, "Ask Outstanding frames waiting for a connection", sizeof(datakind)); break;
+	      case 'M': strlcpy (datakind, "Send UNPROTO Information",			sizeof(datakind)); break;
+	      case 'C': strlcpy (datakind, "Connect, Start an AX.25 Connection",	sizeof(datakind)); break;
+	      case 'D': strlcpy (datakind, "Send Connected Data",			sizeof(datakind)); break;
+	      case 'd': strlcpy (datakind, "Disconnect, Terminate an AX.25 Connection",	sizeof(datakind)); break;
+	      case 'v': strlcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters", sizeof(datakind)); break;
+	      case 'V': strlcpy (datakind, "Send UNPROTO VIA",				sizeof(datakind)); break;
+	      case 'c': strlcpy (datakind, "Non-Standard Connections, Connection with PID", sizeof(datakind)); break;
+	      case 'K': strlcpy (datakind, "Send data in raw AX.25 format",		sizeof(datakind)); break;
+	      case 'k': strlcpy (datakind, "Activate reception of Frames in raw format", sizeof(datakind)); break;
+	      default:  strlcpy (datakind, "**INVALID**",				sizeof(datakind)); break;
+	    }
+	    break;
+
+	  case TO_CLIENT:
+	  default:
+	    strlcpy (direction, "to", sizeof(direction));	/* sent to the client application. */
+
+	    switch (pmsg->datakind) {
+	      case 'R': strlcpy (datakind, "Version Number",				sizeof(datakind)); break;
+	      case 'X': strlcpy (datakind, "Callsign Registration",			sizeof(datakind)); break;
+	      case 'G': strlcpy (datakind, "Port Information",				sizeof(datakind)); break;
+	      case 'g': strlcpy (datakind, "Capabilities of a Port",			sizeof(datakind)); break;
+	      case 'y': strlcpy (datakind, "Frames Outstanding on a Port",		sizeof(datakind)); break;
+	      case 'Y': strlcpy (datakind, "Frames Outstanding on a Connection",	sizeof(datakind)); break;
+	      case 'H': strlcpy (datakind, "Heard Stations on a Port",			sizeof(datakind)); break;
+	      case 'C': strlcpy (datakind, "AX.25 Connection Received",			sizeof(datakind)); break;
+	      case 'D': strlcpy (datakind, "Connected AX.25 Data",			sizeof(datakind)); break;
+	      case 'd': strlcpy (datakind, "Disconnected",				sizeof(datakind)); break;
+	      case 'M': strlcpy (datakind, "Monitored Connected Information",		sizeof(datakind)); break;
+	      case 'S': strlcpy (datakind, "Monitored Supervisory Information",		sizeof(datakind)); break;
+	      case 'U': strlcpy (datakind, "Monitored Unproto Information",		sizeof(datakind)); break;
+	      case 'T': strlcpy (datakind, "Monitoring Own Information",		sizeof(datakind)); break;
+	      case 'K': strlcpy (datakind, "Monitored Information in Raw Format",	sizeof(datakind)); break;
+	      default:  strlcpy (datakind, "**INVALID**",				sizeof(datakind)); break;
+	    }
+	}
+
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("\n");
+
+	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, datakind = '%c', pid = 0x%02x\n", pmsg->portx, pmsg->datakind, pmsg->pid);
+	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", netle2host(pmsg->data_len_NETLE), netle2host(pmsg->user_reserved_NETLE));
+
+	hex_dump ((unsigned char*)pmsg + sizeof(struct agwpe_s), netle2host(pmsg->data_len_NETLE));
+
+	if (msg_len < 36) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("AGWPE message length, %d, is shorter than minumum 36.\n", msg_len);
+	}
+	if (msg_len != netle2host(pmsg->data_len_NETLE) + 36) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("AGWPE message length, %d, inconsistent with data length %d.\n", msg_len, netle2host(pmsg->data_len_NETLE));
+	}
+
+}
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        server_init
+ *
+ * Purpose:     Set up a server to listen for connection requests from
+ *		an application such as Xastir.
+ *
+ * Inputs:	mc->agwpe_port	- TCP port for server.
+ *				  Main program has default of 8000 but allows
+ *				  an alternative to be specified on the command line
+ *
+ *				0 means disable.  New in version 1.2.
+ *
+ * Outputs:	
+ *
+ * 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.
+ *
+ *--------------------------------------------------------------------*/
+
+static struct audio_s *save_audio_config_p;
+
+
+void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc)
+{
+	int client;
+
+#if __WIN32__
+	HANDLE connect_listen_th;
+	HANDLE cmd_listen_th[MAX_NET_CLIENTS];
+#else
+	pthread_t connect_listen_tid;
+	pthread_t cmd_listen_tid[MAX_NET_CLIENTS];
+	int e;
+#endif
+	int server_port = mc->agwpe_port;		/* Usually 8000 but can be changed. */
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("server_init ( %d )\n", server_port);
+	debug_a = 1;
+#endif
+
+	save_audio_config_p = audio_config_p;
+
+	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;
+	}
+
+	memset (registered_callsigns, 0, sizeof(registered_callsigns));
+
+	if (server_port == 0) {
+	  text_color_set(DW_COLOR_INFO);
+	  dw_printf ("Disabled AGW network client port.\n");
+	  return;
+	}
+
+
+/*
+ * This waits for a client to connect and sets an available client_sock[n].
+ */
+#if __WIN32__
+	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");
+	  return;
+	}
+#else
+	e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)server_port);
+	if (e != 0) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror("Could not create AGW connect listening thread");
+	  return;
+	}
+#endif
+
+/*
+ * 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[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[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
+	}
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        connect_listen_thread
+ *
+ * Purpose:     Wait for a connection request from an application.
+ *
+ * Inputs:	arg		- TCP port for server.
+ *				  Main program has default of 8000 but allows
+ *				  an alternative to be specified on the command line
+ *
+ * Outputs:	client_sock	- File descriptor for communicating with client app.
+ *
+ * Description:	Wait for connection request from client and establish
+ *		communication.
+ *		Note that the client can go away and come back again and
+ *		re-establish communication without restarting this application.
+ *
+ *--------------------------------------------------------------------*/
+
+static THREAD_F connect_listen_thread (void *arg)
+{
+#if __WIN32__
+
+	struct addrinfo hints;
+	struct addrinfo *ai = NULL;
+	int err;
+	char server_port_str[12];
+
+	SOCKET listen_sock;  
+	WSADATA wsadata;
+
+	snprintf (server_port_str, sizeof(server_port_str), "%d", (int)(long)arg);
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+        dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(long)arg, server_port_str);
+#endif
+	err = WSAStartup (MAKEWORD(2,2), &wsadata);
+	if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("WSAStartup failed: %d\n", err);
+	    return (0);
+	}
+
+	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Could not find a usable version of Winsock.dll\n");
+          WSACleanup();
+	  //sleep (1);
+          return (0);
+	}
+
+	memset (&hints, 0, sizeof(hints));
+	hints.ai_family = AF_INET;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+	hints.ai_flags = AI_PASSIVE;
+
+	err = getaddrinfo(NULL, server_port_str, &hints, &ai);
+	if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf("getaddrinfo failed: %d\n", err);
+	    //sleep (1);
+	    WSACleanup();
+	    return (0);
+	}
+
+	listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+	if (listen_sock == INVALID_SOCKET) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
+	  return (0);
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+    	dw_printf("Binding to port %s ... \n", server_port_str);
+#endif
+
+	err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
+	if (err == SOCKET_ERROR) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Bind failed with error: %d\n", WSAGetLastError());		// TODO: translate number to text?
+	  dw_printf("Some other application is probably already using port %s.\n", server_port_str);
+	  dw_printf("Try using a different port number with AGWPORT in the configuration file.\n");
+          freeaddrinfo(ai);
+          closesocket(listen_sock);
+          WSACleanup();
+          return (0);
+        }
+
+	freeaddrinfo(ai);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+ 	dw_printf("opened socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, server_port_str );
+#endif
+
+ 	while (1) {
+
+	  int client;
+	  int c;
+	  
+	  client = -1;
+	  for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
+	    if (client_sock[c] <= 0) {
+	      client = c;
+	    }
+	  }
+
+/*
+ * Listen for connection if we have not reached maximum.
+ */
+	  if (client >= 0) {
+
+	    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 (0);
+	    }
+	
+	    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[client] = accept(listen_sock, NULL, 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 (0);
+            }
+
+	    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[client] = 0;
+	    enable_send_monitor_to_client[client] = 0;
+	  }
+	  else {
+	    SLEEP_SEC(1);	/* wait then check again if more clients allowed. */
+	  }
+ 	}
+
+#else		/* End of Windows case, now Linux */
+
+
+    	struct sockaddr_in sockaddr; /* Internet socket address stuct */
+    	socklen_t sockaddr_size = sizeof(struct sockaddr_in);
+	int server_port = (int)(long)arg;
+	int listen_sock;  
+	int bcopt = 1;
+
+	listen_sock= socket(AF_INET,SOCK_STREAM,0);
+	if (listen_sock == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+	  perror ("connect_listen_thread: Socket creation failed");
+	  return (NULL);
+	}
+
+	/* Version 1.3 - as suggested by G8BPQ. */
+	/* Without this, if you kill the application then try to run it */
+	/* again quickly the port number is unavailable for a while. */
+	/* Don't try doing the same thing On Windows; It has a different meaning. */
+	/* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */
+
+        setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4);
+
+
+    	sockaddr.sin_addr.s_addr = INADDR_ANY;
+    	sockaddr.sin_port = htons(server_port);
+    	sockaddr.sin_family = AF_INET;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+    	dw_printf("Binding to port %d ... \n", server_port);
+#endif
+
+        if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))  == -1) {
+	  text_color_set(DW_COLOR_ERROR);
+          dw_printf("Bind failed with error: %d\n", errno);
+          dw_printf("%s\n", strerror(errno));
+	  dw_printf("Some other application is probably already using port %d.\n", server_port);
+	  dw_printf("Try using a different port number with AGWPORT in the configuration file.\n");
+          return (NULL);
+	}
+
+	getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+ 	dw_printf("opened socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
+#endif
+
+ 	while (1) {
+
+	  int client;
+	  int c;
+	  
+	  client = -1;
+	  for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
+	    if (client_sock[c] <= 0) {
+	      client = c;
+	    }
+	  }
+
+	  if (client >= 0) {
+
+	    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 %d on port %d ...\n", client, server_port);
+         
+            client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
+
+	    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[client] = 0;
+	    enable_send_monitor_to_client[client] = 0;
+	  }
+	  else {
+	    SLEEP_SEC(1);	/* wait then check again if more clients allowed. */
+	  }
+ 	}
+#endif
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        server_send_rec_packet
+ *
+ * Purpose:     Send a received packet to the client app.
+ *
+ * Inputs:	chan		- Channel number where packet was received.
+ *				  0 = first, 1 = second if any.
+ *
+ *		pp		- Identifier for packet object.
+ *
+ *		fbuf		- Address of raw received frame buffer.
+ *		flen		- Length of raw received frame.
+ *		
+ *
+ * Description:	Send message to client if connected.
+ *		Disconnect from client, and notify user, if any error.
+ *
+ *		There are two different formats:
+ *			RAW - the original received frame.
+ *			MONITOR - just the information part.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int flen)
+{
+	struct {	
+	  struct agwpe_s hdr;
+	  char data[1+AX25_MAX_PACKET_LEN];		
+	} agwpe_msg;
+
+	int err;
+	int info_len;
+	unsigned char *pinfo;
+	int client;
+
+
+/*
+ * RAW format
+ */
+	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));
+
+	    agwpe_msg.hdr.portx = chan;
+
+	    agwpe_msg.hdr.datakind = 'K';
+
+	    ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from);
+
+	    ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to);
+
+	    agwpe_msg.hdr.data_len_NETLE = host2netle(flen + 1);
+
+	    /* Stick in extra byte for the "TNC" to use. */
+
+	    agwpe_msg.data[0] = 0;
+	    memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen);
+
+	    if (debug_client) {
+	      debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
+	    }
+
+#if __WIN32__	
+            err = send (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE), 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]);
+	      client_sock[client] = -1;
+	      WSACleanup();
+	    }
+#else
+            err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
+	    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]);
+	      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] && client_sock[client] > 0 
+			&& ax25_get_control(pp) == AX25_UI_FRAME){
+
+	    time_t clock;
+	    struct tm *tm;
+
+	    clock = time(NULL);
+	    tm = localtime(&clock);	// TODO: should use localtime_r
+
+	    memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr));
+
+	    agwpe_msg.hdr.portx = chan;
+
+	    agwpe_msg.hdr.datakind = 'U';
+
+	    ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from);
+
+	    ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to);
+
+	    info_len = ax25_get_info (pp, &pinfo);
+
+	    /* http://uz7ho.org.ua/includes/agwpeapi.htm#_Toc500723812 */
+
+	    /* Description mentions one CR character after timestamp but example has two. */
+	    /* Actual observed cases have only one. */
+	    /* Also need to add extra CR, CR, null at end. */
+	    /* The documentation example includes these 3 extra in the Len= value */
+	    /* but actual observed data uses only the packet info length. */
+
+	    snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s <UI pid=%02X Len=%d >[%02d:%02d:%02d]\r%s\r\r",
+			chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to,
+			ax25_get_pid(pp), info_len, 
+			tm->tm_hour, tm->tm_min, tm->tm_sec,
+			pinfo);
+
+	    agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* include null */ ;
+
+	    if (debug_client) {
+	      debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
+	    }
+
+#if __WIN32__	
+            err = send (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE), 0);
+	    if (err == SOCKET_ERROR)
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      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[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
+	    if (err <= 0)
+	    {
+	      text_color_set(DW_COLOR_ERROR);
+	      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 */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        server_link_established
+ *
+ * Purpose:     Send notification to client app when a link has
+ *		been established with another station.
+ *
+ * Inputs:	chan		- Which radio channel.
+ *
+ * 		client		- Which one of potentially several clients.
+ *
+ *		remote_call	- Callsign[-ssid] of remote station.
+ *
+ *		own_call	- Callsign[-ssid] of my end.
+ *
+ *		incoming	- true if connection was initiated from other end.
+ *				  false if this end started it.
+ *
+ *--------------------------------------------------------------------*/
+
+void server_link_established (int chan, int client, char *remote_call, char *own_call, int incoming)
+{
+
+	struct {
+	  struct agwpe_s hdr;
+	  char info[100];
+	} reply;
+
+
+	memset (&reply, 0, sizeof(reply));
+	reply.hdr.portx = chan;
+	reply.hdr.datakind = 'C';
+
+	strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from));
+	strlcpy (reply.hdr.call_to,   own_call,    sizeof(reply.hdr.call_to));
+
+	if (incoming) {
+	  // Other end initiated the connection.
+	  snprintf (reply.info, sizeof(reply.info), "*** CONNECTED To Station %s\r", remote_call);
+	}
+	else {
+	  // We started the connection.
+	  snprintf (reply.info, sizeof(reply.info), "*** CONNECTED With Station %s\r", remote_call);
+	}
+	reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1);
+
+	send_to_client (client, &reply);
+
+} /* end server_link_established */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        server_link_terminated
+ *
+ * Purpose:     Send notification to client app when a link with
+ *		another station has been terminated or a connection
+ *		attempt failed.
+ *
+ * Inputs:	chan		- Which radio channel.
+ *
+ * 		client		- Which one of potentially several clients.
+ *
+ *		remote_call	- Callsign[-ssid] of remote station.
+ *
+ *		own_call	- Callsign[-ssid] of my end.
+ *
+ *		timeout		- true when no answer from other station.
+ *				  How do we distinguish who asked for the
+ *				  termination of an existing linkn?
+ *
+ *--------------------------------------------------------------------*/
+
+void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout)
+{
+
+	struct {
+	  struct agwpe_s hdr;
+	  char info[100];
+	} reply;
+
+
+	memset (&reply, 0, sizeof(reply));
+	reply.hdr.portx = chan;
+	reply.hdr.datakind = 'd';
+	strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from));  /* right order */
+	strlcpy (reply.hdr.call_to,   own_call,    sizeof(reply.hdr.call_to));
+
+	if (timeout) {
+	  snprintf (reply.info, sizeof(reply.info), "*** DISCONNECTED RETRYOUT With %s\r", remote_call);
+	}
+	else {
+	  snprintf (reply.info, sizeof(reply.info), "*** DISCONNECTED From Station %s\r", remote_call);
+	}
+	reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1);
+
+	send_to_client (client, &reply);
+
+
+} /* end server_link_terminated */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        read_from_socket
+ *
+ * Purpose:     Read from socket until we have desired number of bytes.
+ *
+ * Inputs:	fd		- file descriptor.
+ *		ptr		- address where data should be placed.
+ *		len		- desired number of bytes.
+ *
+ * Description:	Just a wrapper for the "read" system call but it should
+ *		never return fewer than the desired number of bytes.
+ *
+ *--------------------------------------------------------------------*/
+
+static int read_from_socket (int fd, char *ptr, int len)
+{
+	int got_bytes = 0;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
+#endif
+	while (got_bytes < len) {
+	  int n;
+
+#if __WIN32__
+
+//TODO: any flags for send/recv?
+
+	  n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
+#else
+	  n = read (fd, ptr + got_bytes, len - got_bytes);
+#endif
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("read_from_socket: n = %d\n", n);
+#endif
+	  if (n <= 0) {
+	    return (n);
+	  }
+
+	  got_bytes += n;
+	}
+	assert (got_bytes >= 0 && got_bytes <= len);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("read_from_socket: return %d\n", got_bytes);
+#endif
+	return (got_bytes);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        cmd_listen_thread
+ *
+ * Purpose:     Wait for command messages from an application.
+ *
+ * Inputs:	arg		- client number, 0 .. MAX_NET_CLIENTS-1
+ *
+ * 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
+ *		re-establish communication without restarting this application.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static void send_to_client (int client, void *reply_p)
+{
+	struct agwpe_s *ph;
+	int len;
+#if __WIN32__     
+#else
+	int err;
+#endif
+
+	ph = (struct agwpe_s *) reply_p;	// Replies are often hdr + other stuff.
+
+	len = sizeof(struct agwpe_s) + netle2host(ph->data_len_NETLE);
+
+	/* Not sure what max data length might be. */
+
+	if (netle2host(ph->data_len_NETLE) < 0 || netle2host(ph->data_len_NETLE) > 4096) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Invalid data length %d for AGW protocol message to client %d.\n", netle2host(ph->data_len_NETLE), client);
+	  debug_print (TO_CLIENT, client, ph, len);
+	}
+
+	if (debug_client) {
+	  debug_print (TO_CLIENT, client, ph, len);
+	}
+
+#if __WIN32__     
+	send (client_sock[client], (char*)(ph), len, 0);
+#else
+	err = write (client_sock[client], ph, len);
+#endif
+}
+
+
+static THREAD_F cmd_listen_thread (void *arg)
+{
+	int n;
+
+	struct {
+	  struct agwpe_s hdr;		/* Command header. */
+	
+	  char data[512];		/* Additional data used by some commands. */
+					/* Maximum for 'V': 1 + 8*10 + 256 */
+	} cmd;
+
+	int client = (int)(long)arg;
+
+	assert (client >= 0 && client < MAX_NET_CLIENTS);
+
+	while (1) {
+
+	  while (client_sock[client] <= 0) {
+	    SLEEP_SEC(1);			/* Not connected.  Try again later. */
+	  }
+
+	  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 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[client]);
+#else
+	    close (client_sock[client]);
+#endif
+	    client_sock[client] = -1;
+	    continue;
+	  }
+
+/*
+ * 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.
+ */
+
+	  int data_len = netle2host(cmd.hdr.data_len_NETLE);
+
+	  if (data_len < 0 || 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", 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 (0);
+	  }
+
+	  cmd.data[0] = '\0';
+
+	  if (data_len > 0) {
+	    n = read_from_socket (client_sock[client], cmd.data, data_len);
+	    if (n != data_len) {
+	      text_color_set(DW_COLOR_ERROR);
+	      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", data_len, n);
+	      dw_printf ("Closing connection.\n\n");
+#if __WIN32__
+	      closesocket (client_sock[client]);
+#else
+	      close (client_sock[client]);
+#endif
+	      client_sock[client] = -1;
+	      return (0);
+	    }
+	    if (n >= 0) {
+		cmd.data[n] = '\0';	// Tidy if we print for debug.
+	    }
+	  }
+
+/*
+ * print & process message from client.
+ */
+
+	  if (debug_client) {
+	    debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + data_len);
+	  }
+
+	  switch (cmd.hdr.datakind) {
+
+	    case 'R':				/* Request for version number */
+	      {
+		struct {
+		  struct agwpe_s hdr;
+	          int major_version_NETLE;
+	          int minor_version_NETLE;
+		} reply;
+
+
+	        memset (&reply, 0, sizeof(reply));
+	        reply.hdr.datakind = 'R';
+	        reply.hdr.data_len_NETLE = host2netle(sizeof(reply.major_version_NETLE) + sizeof(reply.minor_version_NETLE));
+		assert (netle2host(reply.hdr.data_len_NETLE) == 8);
+
+		// Xastir only prints this and doesn't care otherwise.
+		// APRSIS32 doesn't seem to care.
+		// UI-View32 wants on 2000.15 or later.
+
+	        reply.major_version_NETLE = host2netle(2005);
+	        reply.minor_version_NETLE = host2netle(127);
+
+		assert (sizeof(reply) == 44);
+
+	        send_to_client (client, &reply);
+
+	      }
+	      break;
+
+	    case 'G':				/* Ask about radio ports */
+
+	      {
+		struct {
+		  struct agwpe_s hdr;
+	 	  char info[200];
+		} reply;
+
+
+		int j, count;
+
+
+	        memset (&reply, 0, sizeof(reply));
+	        reply.hdr.datakind = 'G';
+
+
+		// Xastir only prints this and doesn't care otherwise.
+		// YAAC uses this to identify available channels.
+
+		// The interface manual wants the first to be "Port1" 
+		// so channel 0 corresponds to "Port1."
+		// We can have gaps in the numbering.
+		// I wonder what applications will think about that.
+
+#if 1
+		// No other place cares about total number.
+
+		count = 0;
+		for (j=0; j<MAX_CHANS; j++) {
+	 	  if (save_audio_config_p->achan[j].valid) {
+		    count++;
+		  }
+		}
+		snprintf (reply.info, sizeof(reply.info), "%d;", count);
+
+		for (j=0; j<MAX_CHANS; j++) {
+	 	  if (save_audio_config_p->achan[j].valid) {
+		    char stemp[100];
+		    int a = ACHAN2ADEV(j);
+		    // If I was really ambitious, some description could be provided.
+		    static const char *names[8] = { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth" };
+
+		    if (save_audio_config_p->adev[a].num_channels == 1) {
+		      snprintf (stemp, sizeof(stemp), "Port%d %s soundcard mono;", j+1, names[a]);
+		      strlcat (reply.info, stemp, sizeof(reply.info));
+		    }
+		    else {
+		      snprintf (stemp, sizeof(stemp), "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left");
+		      strlcat (reply.info, stemp, sizeof(reply.info));
+		    }
+		  }
+		}
+
+#else
+		if (num_channels == 1) {
+		  snprintf (reply.info, sizeof(reply.info), "1;Port1 Single channel;");
+		}
+		else {
+		  snprintf (reply.info, sizeof(reply.info), "2;Port1 Left channel;Port2 Right Channel;");
+		}
+#endif
+	        reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1);
+
+	        send_to_client (client, &reply);
+
+	      }
+	      break;
+
+
+	    case 'g':				/* Ask about capabilities of a port. */
+
+	      {
+		struct {
+		  struct agwpe_s hdr;
+	 	  unsigned char on_air_baud_rate; 	/* 0=1200, 3=9600 */
+		  unsigned char traffic_level;		/* 0xff if not in autoupdate mode */
+		  unsigned char tx_delay;
+		  unsigned char tx_tail;
+		  unsigned char persist;
+		  unsigned char slottime;
+		  unsigned char maxframe;
+		  unsigned char active_connections;
+		  int how_many_bytes_NETLE;
+		} reply;
+
+
+	        memset (&reply, 0, sizeof(reply));
+
+		reply.hdr.portx = cmd.hdr.portx;	/* Reply with same port number ! */
+	        reply.hdr.datakind = 'g';
+	        reply.hdr.data_len_NETLE = host2netle(12);
+
+		// YAAC asks for this.
+		// Fake it to keep application happy.
+
+	        reply.on_air_baud_rate = 0;
+		reply.traffic_level = 1;
+		reply.tx_delay = 0x19;
+		reply.tx_tail = 4;
+		reply.persist = 0xc8;
+		reply.slottime = 4;
+		reply.maxframe = 7;
+		reply.active_connections = 0;
+		reply.how_many_bytes_NETLE = host2netle(1);
+
+		assert (sizeof(reply) == 48);
+
+	        send_to_client (client, &reply);
+
+	      }
+	      break;
+
+
+	    case 'H':				/* Ask about recently heard stations. */
+
+	      {
+#if 0						/* This information is not being collected. */
+		struct {
+		  struct agwpe_s hdr;
+	 	  char info[100];
+		} reply;
+
+
+	        memset (&reply.hdr, 0, sizeof(reply.hdr));
+	        reply.hdr.datakind = 'H';
+
+		// TODO:  Implement properly.  
+
+	        reply.hdr.portx = cmd.hdr.portx
+
+	        strlcpy (reply.hdr.call_from, "WB2OSZ-15", sizeof(reply.hdr.call_from));
+
+	        strlcpy (agwpe_msg.data, ..., sizeof(agwpe_msg.data));
+
+	        reply.hdr.data_len_NETLE = host2netle(strlen(reply.info));
+
+	        send_to_client (client, &reply);
+#endif
+	      }
+	      break;
+	    
+
+
+
+	    case 'k':				/* Ask to start receiving RAW AX25 frames */
+
+	      // Actually it is a toggle so we must be sure to clear it for a new connection.
+
+	      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[client] = ! enable_send_monitor_to_client[client];
+	      break;
+
+
+	    case 'V':				/* Transmit UI data frame (with digipeater path) */
+	      {
+	      	// Data format is:
+	      	//	1 byte for number of digipeaters.
+	      	//	10 bytes for each digipeater.
+	      	//	data part of message.
+
+	      	char stemp[AX25_MAX_PACKET_LEN+2];
+		char *p;
+		int ndigi;
+		int k;
+	      
+		packet_t pp;
+
+	      	strlcpy (stemp, cmd.hdr.call_from, sizeof(stemp));
+	      	strlcat (stemp, ">", sizeof(stemp));
+	      	strlcat (stemp, cmd.hdr.call_to, sizeof(stemp));
+
+		cmd.data[data_len] = '\0';
+		ndigi = cmd.data[0];
+		p = cmd.data + 1;
+
+		for (k=0; k<ndigi; k++) {
+		  strlcat (stemp, ",", sizeof(stemp));
+		  strlcat (stemp, p, sizeof(stemp));
+		  p += 10;
+	        }
+		strlcat (stemp, ":", sizeof(stemp));
+		strlcat (stemp, p, sizeof(stemp));
+
+	        //text_color_set(DW_COLOR_DEBUG);
+		//dw_printf ("Transmit '%s'\n", stemp);
+
+		pp = ax25_from_text (stemp, 1);
+
+
+		if (pp == NULL) {
+	          text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Failed to create frame from AGW 'V' message.\n");
+		}
+		else {
+
+		  /* This goes into the low priority queue because it is an original. */
+
+		  /* Note that the protocol has no way to set the "has been used" */
+		  /* bits in the digipeater fields. */
+
+		  /* This explains why the digipeating option is grayed out in */
+		  /* xastir when using the AGW interface.  */
+		  /* The current version uses only the 'V' message, not 'K' for transmitting. */
+
+		  tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
+
+		}
+	      }
+	      
+	      break;
+
+	    case 'K':				/* Transmit raw AX.25 frame */
+	      {
+	      	// Message contains:
+	      	//	port number for transmission.
+	      	//	data length
+	      	//	data which is raw ax.25 frame.
+		//		
+	      
+		packet_t pp;
+		alevel_t alevel;
+
+		// 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.
+
+		memset (&alevel, 0xff, sizeof(alevel));
+		pp = ax25_from_frame ((unsigned char *)cmd.data+1, data_len - 1, alevel);
+
+		if (pp == NULL) {
+	          text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Failed to create frame from AGW 'K' message.\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. */
+
+		  if (ax25_get_num_repeaters(pp) >= 1 &&
+		      ax25_get_h(pp,AX25_REPEATER_1)) {
+		    tq_append (cmd.hdr.portx, TQ_PRIO_0_HI, pp);
+		  }
+		  else {
+		    tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
+		  }
+		}
+	      }
+	      
+	      break;
+
+	    case 'X':				/* Register CallSign  */
+
+	      {
+		struct {
+		  struct agwpe_s hdr;
+		  char data;
+		} reply;
+
+		int j, ok;
+
+	        memset (&reply, 0, sizeof(reply));
+	        reply.hdr.datakind = 'X';
+		memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from));
+	        reply.hdr.data_len_NETLE = host2netle(1);
+	
+		// Version 1.0.
+		// Previously used sizeof(reply) but compiler rounded it up to next byte boundary.
+		// That's why more cumbersome size expression is used.
+
+		// The protocol spec says it is an error to register the same one more than once.
+	        // First make sure is it not already in there.  Add if space available.
+
+	        if (server_callsign_registered_by_client(cmd.hdr.call_from) >= 0) {
+	          ok = 0;
+	        }
+	        else {
+	          ok = 0;
+	          for (j = 0; j < MAX_REG_CALLSIGNS && ok == 0; j++) {
+	            if (registered_callsigns[j][0] == '\0') {
+	              strlcpy (registered_callsigns[j], cmd.hdr.call_from, sizeof(registered_callsigns[j]));
+	              registered_by_client[j] = client;
+	              ok = 1;
+	            }
+	          }
+	        }
+
+		reply.data = ok;		/* 1 = success, 0 = failure */
+	        send_to_client (client, &reply);
+	      }
+	      break;
+
+	    case 'x':				/* Unregister CallSign  */
+
+	      {
+	        int j;
+
+	        for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
+	          if (strcmp(registered_callsigns[j], cmd.hdr.call_from) == 0) {
+	            registered_callsigns[j][0] = '\0';
+	            registered_by_client[j] = -1;
+	          }
+	        }
+	      }
+	      /* No reponse is expected. */
+	      break;
+
+	    case 'C':				/* Connect, Start an AX.25 Connection  */
+	    case 'v':	      			/* Connect VIA, Start an AX.25 circuit thru digipeaters */
+	    case 'c':	      			/* Connection with non-standard PID */
+
+	      {
+	        struct via_info {
+	          unsigned char num_digi;	/* Expect to be in range 1 to 7.  Why not up to 8? */
+		  char dcall[7][10];
+	        } *v = (struct via_info *)cmd.data;
+
+	        char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+	        int num_calls = 2;	/* 2 plus any digipeaters. */
+	        int pid = 0xf0;		/* normal for AX.25 I frames. */
+		int j;
+	        char stemp[256];
+
+	        strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
+	        strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
+
+	        if (cmd.hdr.datakind == 'c') {
+	          pid = cmd.hdr.pid;		/* non standard for NETROM, TCP/IP, etc. */
+	        }
+
+	        if (cmd.hdr.datakind == 'v') {
+	          if (v->num_digi >= 1 && v->num_digi <= 7) {
+
+	            if (data_len != v->num_digi * 10 + 1 && data_len != v->num_digi * 10 + 2) {
+	              // I'm getting 1 more than expected from AGWterminal.
+	              text_color_set(DW_COLOR_ERROR);
+	              dw_printf ("AGW client, connect via, has data len, %d when %d expected.\n", data_len, v->num_digi * 10 + 1);
+	            }
+
+	            for (j = 0; j < v->num_digi; j++) {
+	              strlcpy (callsigns[AX25_REPEATER_1 + j], v->dcall[j], sizeof(callsigns[AX25_REPEATER_1 + j]));
+	              num_calls++;
+	            }
+	          }
+	          else {
+	            text_color_set(DW_COLOR_ERROR);
+	            dw_printf ("\n");
+	            dw_printf ("AGW client, connect via, has invalid number of digipeaters = %d\n", v->num_digi);
+	          }
+	        }
+
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("\n");
+	        dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
+	        dw_printf ("Connected packet mode is not implemented.\n");
+	      }
+	      break;
+
+	    case 'D': 				/* Send Connected Data */
+
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("\n");
+	      dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
+	      dw_printf ("Connected packet mode is not implemented.\n");
+	      break;
+
+	    case 'd': 				/* Disconnect, Terminate an AX.25 Connection */
+
+	      {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf ("\n");
+	        dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
+	        dw_printf ("Connected packet mode is not implemented.\n");
+	      }
+	      break;
+
+
+	    case 'M': 				/* Send UNPROTO Information (no digipeater path) */
+
+		/* 
+		Added in version 1.3.
+		This is the same as 'V' except there is no provision for digipeaters.
+		TODO: combine 'V' and 'M' into one case.
+		AGWterminal sends this for beacon or ask QRA.
+
+		<<< Send UNPROTO Information from AGWPE client application 0, total length = 253
+		        portx = 0, datakind = 'M', pid = 0x00
+		        call_from = "WB2OSZ-15", call_to = "BEACON"
+		        data_len = 217, user_reserved = 556, data =
+		  000:  54 68 69 73 20 76 65 72 73 69 6f 6e 20 75 73 65  This version use
+		   ...
+
+		<<< Send UNPROTO Information from AGWPE client application 0, total length = 37
+		        portx = 0, datakind = 'M', pid = 0x00
+		        call_from = "WB2OSZ-15", call_to = "QRA"
+		        data_len = 1, user_reserved = 31759424, data =
+		  000:  0d                                               .
+                                          .
+
+		There is also a report of it coming from UISS.
+
+		<<< Send UNPROTO Information from AGWPE client application 0, total length = 50
+			portx = 0, port_hi_reserved = 0
+			datakind = 77 = 'M', kind_hi = 0
+			call_from = "JH4XSY", call_to = "APRS"
+			data_len = 14, user_reserved = 0, data =
+		  000:  21 22 3c 43 2e 74 71 6c 48 72 71 21 21 5f        !"<C.tqlHrq!!_
+		*/
+	      {
+	      
+		int pid = cmd.hdr.pid;
+		(void)(pid);
+			/* The AGW protocol spec says, */
+			/* "AX.25 PID 0x00 or 0xF0 for AX.25 0xCF NETROM and others" */
+
+			/* BUG: In theory, the AX.25 PID octet should be set from this. */
+			/* All examples seen (above) have 0. */
+			/* The AX.25 protocol spec doesn't list 0 as a valid value. */
+			/* We always send 0xf0, meaning no layer 3. */
+			/* Maybe we should have an ax25_set_pid function for cases when */
+			/* it is neither 0 nor 0xf0. */
+
+	      	char stemp[AX25_MAX_PACKET_LEN];
+		packet_t pp;
+
+	      	strlcpy (stemp, cmd.hdr.call_from, sizeof(stemp));
+	      	strlcat (stemp, ">", sizeof(stemp));
+	      	strlcat (stemp, cmd.hdr.call_to, sizeof(stemp));
+
+		cmd.data[data_len] = '\0';
+
+		strlcat (stemp, ":", sizeof(stemp));
+		strlcat (stemp, cmd.data, sizeof(stemp));
+
+	        //text_color_set(DW_COLOR_DEBUG);
+		//dw_printf ("Transmit '%s'\n", stemp);
+
+		pp = ax25_from_text (stemp, 1);
+
+		if (pp == NULL) {
+	          text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Failed to create frame from AGW 'M' message.\n");
+		}
+		else {
+		  tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
+		}
+	      }
+	      break;
+
+
+	    case 'y':				/* Ask Outstanding frames waiting on a Port  */
+
+	      {
+		struct {
+		  struct agwpe_s hdr;
+		  int data_NETLE;			// Little endian order.
+		} reply;
+
+
+	        memset (&reply, 0, sizeof(reply));
+		reply.hdr.portx = cmd.hdr.portx;	/* Reply with same port number */
+	        reply.hdr.datakind = 'y';
+	        reply.hdr.data_len_NETLE = host2netle(4);
+
+	        int n = 0;
+	        if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) {
+		  n = tq_count (cmd.hdr.portx, TQ_PRIO_0_HI) + tq_count (cmd.hdr.portx, TQ_PRIO_1_LO);
+		}
+		reply.data_NETLE = host2netle(n);
+
+	        send_to_client (client, &reply);
+	      }
+	      break;
+
+	    default:
+
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("--- Unexpected Command from application %d using AGW protocol:\n", client);
+	      debug_print (FROM_CLIENT, client, &cmd.hdr, sizeof(cmd.hdr) + data_len);
+
+	      break;
+	  }
+	}
+
+} /* end send_to_client */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        server_callsign_registered_by_client
+ *
+ * Purpose:     See if given callsign was registered.
+ *
+ * Inputs:	callsign
+ *
+ * Returns:	>= 0 for the client number.
+ *		-1 for not found.
+ *
+ *--------------------------------------------------------------------*/
+
+int server_callsign_registered_by_client (char *callsign)
+{
+	int j;
+
+	for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
+	  if (strcmp(registered_callsigns[j], callsign) == 0) {
+	    return (registered_by_client[j]);
+	  }
+	}
+	return (-1);
+
+} /* end server_callsign_registered_by_client */
+
+/* end server.c */
diff --git a/server.h b/server.h
index 3e32885..94ad068 100644
--- a/server.h
+++ b/server.h
@@ -1,19 +1,22 @@
-
-/* 
- * Name:	server.h
- */
-
-
-#include "ax25_pad.h"		/* for packet_t */
-
-#include "config.h"
-
-
-void server_set_debug (int n);
-
-void server_init (struct audio_s *audio_config_p, struct misc_config_s *misc_config);
-
-void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int flen);
-
-
-/* end server.h */
+
+/* 
+ * Name:	server.h
+ */
+
+
+#include "ax25_pad.h"		/* for packet_t */
+
+#include "config.h"
+
+
+void server_set_debug (int n);
+
+void server_init (struct audio_s *audio_config_p, struct misc_config_s *misc_config);
+
+void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf,  int flen);
+
+int server_callsign_registered_by_client (char *callsign);
+
+
+
+/* end server.h */
diff --git a/symbols-new.txt b/symbols-new.txt
index e8856f6..d9f50b4 100644
--- a/symbols-new.txt
+++ b/symbols-new.txt
@@ -1,459 +1,487 @@
-APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2      28 Aug 2014
----------------------------------------------------------------------
-
-BACKGROUND:  This file addresses new additions proposals (OVERLAYS) 
-to the APRS symbol set after 1 October 2007.  The master symbol 
-document remains on the www.aprs.org/symbols/symbolsX.txt page.
-
-
-NOTE:  There was confusion with different copies of this file on 
-different web pages and links.  THIS file is now assumed to be the
-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)"
-07 Oct 13  Added new overlays to ships such as Jet Ski, Js
-           Added Ham Club symbol as a C overlay on House, C-
-19 Sep 11  Added T and 2 overlays for TX 1 and 2 hop IGates
-           Added overlays to (;) portable, to show event types
-23 Mar 11  Added Radiation Detector (RH)
-20 Apr 10  Byonics requested (BY)
-04 Jan 10  added #A to the table (correcting earlier omission)
-12 Oct 09  Added W0 for Yaesu WIRES nodes
-09 Apr 09  Changed APRStt symbol to overlayed BOX (#A)
-21 Aug 08  Added RFID R=, Stroller B], Radios#Y, & skull&Xbones (XH)
-27 Apr 08  Added some definitions of the numbered circle #0.          
-25 Mar 08  Added these new definitions of overlays:
-
-Original Alternate Symbol codes being modified for new Overlay Use:
-
-\A - (BOX symbol) APRStt(DTMF), RFID users, XO (OLPC) 
-\' - Was Crash Site.  Now expanded to be INCIDENT sites
-\% - is an overlayed Powerplant.  See definitions below
-\H - \H is HAZE but other H overlays are HAZARDs. WH is "H.Waste"
-\Y - Overlays for Radios and other APRS devices
-\k - Overlay Special vehicles.  A = ATV for example
-\u - Overlay Trucks.  "Tu" is a tanker. "Gu" is a gas truck, etc
-\< - Advisories may now have overlays
-\8 - Nodes with overlays. "G8" would be 802.11G
-\[ - \[ is wall cloud, but overlays are humans. S[ is a skier.
-\h - Buildings. \h is a Ham store, "Hh" is Home Depot, etc.
-
-Previous edition was 4 Oct 2007.
-
-In April 2007, a proposal to expand the use of overlay bytes for 
-the extension of the APRS symbol set was added to the draft APRS1.2 
-addendum web page.  The following document addresses that proposal:
-
-www.aprs.org/symbols/symbols-overlays.txt
-
-For details on Upgrading your symbol set, please see the background
-information on Symbols prepared by Stephen Smith, WA8LMF:
-
-www.aprs.org/symbols/symbols-background.txt
-
-CONSISTANCY:  Since the objective of APRS is consistent, reliable 
-communications at the local level, there has been a hesitance to 
-making significant changes to the APRS symbol set.  The Integrity 
-of APRS depends on everyone seeing the same information at the 
-same time.  Frequent changes to the symbol sets can actually
-undermine that integrity and operational utility of APRS and end up
-with worse outcomes due to miss-communications than the lack of any 
-particular symbol might suggest.
-
-OVERLAY HISTORY:  When the overlay symbol set was first defined for 
-the original APRS back in 1995, it had the potential to expand the 
-APRS symbol set from the 94 original primary symbols to a secondary 
-set that could each have as many as 36 diffeernt overlays on each of 
-those secondary symbols up to almost 3500 combinations.  But some 
-authors then could not easily implement these overlays, except by 
-one-by-one exceptions to their code.
-
-For this reason, a compromise was made with those authors and then
-eventually written into the APRS spec to limit overlays to only a 
-small subset of alternate symbols.  Those original overlayable
-alternate symbols were labeled with a "#" and called "numbered"
-symbols.   (UIview requires "No." in the symbols.ini file) 
-
-STATUS OF OVERLAYS 1 OCTOBER 2007:  the APRS symbol set only had a 
-few remaining unused symbol codes that had not yet been defined:
-
-OF THE 94 Primary Symbols.  The following were available:
-   10 symbols (/0 - /9) that mostly look like billiard balls now
-    4 symbols /D, /J, /Q, /z were undefined or TBD
-    2 were reserved
-
-OF THE 94 Alternate  Symbols.  The following were available:
-   3 undefined series \=, \Y, \Z which could do 36 overlays
-   8 series \1 through \8  that can support 36 overlays each
-   3 reserved series.
-
-ADDITIONAL OVERLAY PROPOSAL:  But any of the other 79 alternate 
-symbols could all have multiple (36) overlays if they can make sense 
-with the existing underlying basic symbol that we have been using for 
-that basic alternate symbol.  That is, any new definition of a 
-previously unused overlay character will have undefined results on all 
-prior APRS systems and should be used with caution.  But the symbol 
-set is extensible with these cautions.  (See the Proposal that would 
-expand the APRS symbol set to over 3200 at the bottom of this 
-document.)
-
-
-SYMBOL OVERLAY TABLES:  This document will keep track of all
-definitions of overlays on all ALTERNATE symbols.  Although these 
-overlays were originally intended to just overlay a displayable 
-single character on a basic symbol, there is no prohibition against
-taking the combination of a symbol and specific overlay, and then 
-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
-D^ = Drone   (new may 2014)
-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
-\$ = Bank or ATM (generic)
-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
-
-GATEWAYS: #&
-/& = HF Gateway  <= the original primary table definition
-I& = Igate Generic (please use more specific overlay)
-R& = Receive only IGate (do not send msgs back to RF)
-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: #'
-/' = Small Aircraft (original primary symbol)
-\' = Airplane Crash Site  <= the original alternate deifinition
-A' = Automobile crash site
-H' = Hazardous incident
-M' = Multi-Vehicle crash site
-P' = Pileup
-T' = Truck wreck
-
-HUMAN SYMBOL: #[
-/[ = Human
-\[ = Wall Cloud (the original definition)
-B[ = Baby on board (stroller, pram etc)
-S[ = Skier      * <= Recommend Special Symbol
-R[ = Runner
-H[ = Hiker
-
-HOUSE: #-
-/- = House
-\- = (was HF)
-5- = 50 Hz mains power
-6- = 60 Hz mains power
-B- = Backup Battery Power
-C- = Club, as in Ham club
-E- = Emergency power
-G- = Geothermal
-H- = Hydro powered
-O- = Operator Present
-S- = Solar Powered
-W- = Wind powered
-
-NUMBERED CIRCLES: #0
-E0 = Echolink Node (E0)
-I0 = IRLP repeater (I0)
-S0 = Staging Area  (S0)
-W0 = WIRES (Yaesu VOIP)
-
-NETWORK NODES: #8
-88 = 802.11 network node (88)
-G8 = 802.11G  (G8)
-
-PORTABLE SYMBOL: #;
-/; = Portable operation (tent)
-\; = Park or Picnic
-F; = Field Day
-I; = Islands on the air
-S; = Summits on the air
-W; = WOTA
-
-ADVISORIES: #<  (new expansion possibilities)
-/< = motorcycle
-\< = Advisory (single gale flag)
-
-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
-V> = GM Volt
-
-BOX SYMBOL: #A (and other system inputted symbols)
-/A = Aid station
-\A = numbered box
-9A = Mobile DTMF user
-7A = HT DTMF user
-HA = House DTMF user
-EA = Echolink DTMF report
-IA = IRLP DTMF report
-RA = RFID report
-AA = AllStar DTMF report
-DA = D-Star report
-XA = OLPC Laptop XO
-etc
-
-EYEBALL and VISIBILITY  #E
-/E = Eyeball for special live events
-\E = (existing smoke) the symbol with no overlay
-HE = (H overlay) Haze
-SE = (S overlay) Smoke
-BE = (B overlay) Blowing Snow         was \B
-DE = (D overlay) blowing Dust or sand was \b
-FE = (F overlay) Fog                  was \{
-
-HAZARDS: #H
-/H = hotel
-\H = Haze
-RH = Radiation detector (new mar 2011)
-WH = Hazardous Waste
-XH = Skull&Crossbones
-
-RESTAURANTS: #R 
-\R = Restaurant (generic)
-7R = 7/11
-KR = KFC
-MR = McDonalds
-TR = Taco Bell
-
-RADIOS and APRS DEVICES: #Y
-/Y = Yacht  <= the original primary symbol
-\Y =        <= the original alternate was undefined
-AY = Alinco
-BY = Byonics
-IY = Icom
-KY = Kenwood       * <= Recommend special symbol
-YY = Yaesu/Standard* <= Recommend special symbol
-
-GPS devices: #\
-/\ = Triangle DF primary symbol
-\\ = was undefined alternate symbol
-A\ = Avmap G5      * <= Recommend special symbol
-
-ARRL or DIAMOND: #a
-/a = Ambulance
-Aa = ARES
-Da = DSTAR (had been ARES Dutch)
-Ga = RSGB Radio Society of Great Brittan
-Ra = RACES
-Sa = SATERN Salvation Army
-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
-\k = SUV
-4k = 4x4
-Ak = ATV (all terrain vehicle)
-
-SHIPS: #s 
-/s = Power boat (ship) side view
-\s = Overlay Boat (Top view)
-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
-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
-
-
-Anyone can use any overlay on any of the overlayable symbols for any
-special purpose.  We are not trying to document all possible such
-overlays.  The purpose of this document is to help keep a list of 
-more common such definitions that have more universal use and for
-which multiple definitions would lead to confusion.
-
-Future APRS code writers should be aware of where we are going:
-
-********************************************************************
-PROPOSAL TO ASSIGN MANY MORE BLOCKS OF SYMBOL SETS        April 2007
---------------------------------------------------------------------
-www.aprs.org/symbols/symbols-overlays.txt
-
-In the initiative to upgrade APRS symbols, we are wasting some
-very valuable OVERLAYABLE symbol subgroups with a few nailed
-down legacy weather symbols.  We are proposing to consolidate
-some of these Weather symbols to open up much more space.  Since
-this is the first time we have considered actually CHANGING some
-symbol definitions, this can cause problems out there for some
-users of some legacy systems.
-
-That is why I am posting this plan to the APRS community.  If we
-do this, XASTIR and UIVIEW will be able to download new symbol
-definitions.  But some legacy clients that do not operate from
-external symbol files will show the wrong symbols for these. If
-users of those systems forsee some serious problems with the
-re-arrangement of these symbols, let us know the specific impact
-and your ideas for a workaround.. 
-
-The symbols that would be impacted are as follows:
-
-First, consolidate all of the visibility symbols into the old
-SMOKE symbol and change its meaning to "VISIBILITY", and then
-differentiate them with the overlay characters.  
-
-"\E"  (existing smoke) the symbol with no overlay
-"HE"  (an H overlay) will mean Haze
-"SE"  (an S overlay) will mean Smoke
-"BE"  (a  B overlay) will mean Blowing Snow         was \B
-"DE"  (a  D overlay) will mean blowing Dust or sand was \b
-"FE"  (an F overlay) will mean Fog                  was \{
-
-Another category is to expand the RAIN symbol to make it kinda
-like lots of angled dots coming from the sky, but with an open
-center so that we can use overlays for a number of common
-PRECIPITATIONS.  The consolidations would be:
-
-"\`" (existing Rain) would be the symbol with no overlay
-"R`" (an R overlay) would mean Rain
-"F`" (an F overlay) would mean Freezing Rain  was \F
-"H`" (an H overlay) would mean Hail           was\:
-"D`" (an D overlay) would mean Drizzle        was \D
-"E`" (an E overlay) would mean slEEt          was \e
-"S`" (an S overlay) would mean Snow           was \*
-Etc. and other particulates coming from the sky
-
-Next, I propose expanding the existing RAIN SHOWER "\I" symbol
-to look like some kind of cloud symbol with specks in it that
-can be overlayed. (It needs to look different from the next
-CLOUD symbol). It can then consolidate these symbols:
-
-"RI" (an R overlay) would mean Rain Shower
-"SI" (an S overlay) would mean Snow shower    was \G
-"LI" (an L overlay) would mean Lightening     was \J
-Etc. and other things related to clouds
-
-Next, I propose expanding the existing CLOUD symbol to allow
-definition of any number of different types of cloud.  This
-needs to also look like a cloud but a different shape and allow
-for overlays  (Maybe this cloud is clear):
-
-"\(" is the existing cloud symbol (would have no overlay)
-"P(" with P overlay would mean partly cloudy        was \p
-"W(" with W overlay would be a wall cloud           was \[
-"F(" would be Funnel cloud, but the original "\f" will also be
-retained for now
-
-All of these initiative will free up a lot of overlayable symbol 
-GROUPS each of which can suport up to 36 different overlays in 
-each group for the future:
-
-#H for 36 new Hazards (was only Hail)
-#[ for 36 new human symbols (was only Wall Cloud)
-#\ for 36 new GPS or navigation equipment
-#B TBD. \B was only blowing snow         now is BE
-#b TBD. \b was only blowing dust/sand    now is DE
-#{ TBD. \{ was only fog                  now is FE
-#* TBD. \* was snow only                 now is S`
-#: TBD. \: was hail only                 now is H`
-#D TBD. \D was drizzle only              now is D`
-#F TBD. \F was freezing rain only        now is F`
-#e TBD. \e was sleet only                now is E`
-#G TBD. \G was only Snow shower          now is SI
-#J TBD. \J was only Lightening           now is LI
-#p TBD. \p was only partly cloudy        now is P(
-
-Of course future code can fully draw each of these overlays as
-distinct special symbols in any way they want. 
-
-I especially want to hear from Dale Hugley who
-is a resource for weather, and Stephen Smith who will have to
-draw them for Uiview. And others with a stake in this...
-
-Bob Bruninga, WB4APR
+APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2      29 Oct 2015
+---------------------------------------------------------------------
+
+BACKGROUND:  This file addresses new additions proposals (OVERLAYS) 
+to the APRS symbol set after 1 October 2007.  The master symbol 
+document remains on the www.aprs.org/symbols/symbolsX.txt page, but
+only has one line per symbol character.  Since each of the symbols
+can have up to 36 overlays, this gives us thousands of symbols codes.
+
+
+Update 29 Oct 2015:  Reorgainized list to Alphabetical Order.
+  + Added many new Balloons (due to lost DoD radar Blimp yesterday)
+  + Confirmed D^ for Drones was already in there since 2014
+  + Added R^ type aircraft for remotely piloted
+  + Added S^ Solar Powered Aircraft
+  + Noticed all new category \= is availalbe.  Had been shown ast
+    APRStt, but that was changed in 2009 to overlay BOX symbols.
+
+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)"
+07 Oct 13  Added new overlays to ships such as Jet Ski, Js
+           Added Ham Club symbol as a C overlay on House, C-
+19 Sep 11  Added T and 2 overlays for TX 1 and 2 hop IGates
+           Added overlays to (;) portable, to show event types
+23 Mar 11  Added Radiation Detector (RH)
+20 Apr 10  Byonics requested (BY)
+04 Jan 10  added #A to the table (correcting earlier omission)
+12 Oct 09  Added W0 for Yaesu WIRES nodes
+09 Apr 09  Changed APRStt symbol to overlayed BOX (#A)
+21 Aug 08  Added RFID R=, Babystroller B], Radio#Y, skull&Xbones XH
+
+
+25 Mar 08 Modified these Alternate Symbol codes for expanded Overlays.
+Prior to this, each Alternate Table basic symbol had a unique defini-
+tion, but this was every restrictive.  SO the following alternate 
+base symbols were redefined so that the basic symbol could take on
+dozens of unique overlay definitions:
+
+\= - Had been undefined
+\0 - Several overlays for the numbered Circle
+\A - (BOX symbol) APRStt(DTMF), RFID users, XO (OLPC) 
+\' - Was Crash Site.  Now expanded to be INCIDENT sites
+\% - is an overlayed Powerplant.  See definitions below
+\H - \H is HAZE but other H overlays are HAZARDs. WH is "H.Waste"
+\Y - Overlays for Radios and other APRS devices
+\k - Overlay Special vehicles.  A = ATV for example
+\u - Overlay Trucks.  "Tu" is a tanker. "Gu" is a gas truck, etc
+\< - Advisories may now have overlays
+\8 - Nodes with overlays. "G8" would be 802.11G
+\[ - \[ is wall cloud, but overlays are humans. S[ is a skier.
+\h - Buildings. \h is a Ham store, "Hh" is Home Depot, etc.
+
+Previous edition was 4 Oct 2007.
+
+In April 2007, a proposal to expand the use of overlay bytes for 
+the extension of the APRS symbol set was added to the draft APRS1.2 
+addendum web page.  The following document addresses that proposal:
+
+www.aprs.org/symbols/symbols-overlays.txt
+
+For details on Upgrading your symbol set, please see the background
+information on Symbols prepared by Stephen Smith, WA8LMF:
+
+www.aprs.org/symbols/symbols-background.txt
+
+CONSISTANCY:  Since the objective of APRS is consistent, reliable 
+communications at the local level, there has been a hesitance to 
+making significant changes to the APRS symbol set.  The Integrity 
+of APRS depends on everyone seeing the same information at the 
+same time.  Frequent changes to the symbol sets can actually
+undermine that integrity and operational utility of APRS and end up
+with worse outcomes due to miss-communications than the lack of any 
+particular symbol might suggest.
+
+OVERLAY HISTORY:  When the overlay symbol set was first defined for 
+the original APRS back in 1995, it had the potential to expand the 
+APRS symbol set from the 94 original primary symbols to a secondary 
+set that could each have as many as 36 diffeernt overlays on each of 
+those secondary symbols up to almost 3500 combinations.  But some 
+authors then could not easily implement these overlays, except by 
+one-by-one exceptions to their code.
+
+For this reason, a compromise was made with those authors and then
+eventually written into the APRS spec to limit overlays to only a 
+small subset of alternate symbols.  Those original overlayable
+alternate symbols were labeled with a "#" and called "numbered"
+symbols.   (UIview requires "No." in the symbols.ini file) 
+
+STATUS OF OVERLAYS 1 OCTOBER 2007:  the APRS symbol set only had a 
+few remaining unused symbol codes that had not yet been defined:
+
+OF THE 94 Primary Symbols.  The following were available:
+   10 symbols (/0 - /9) that mostly look like billiard balls now
+    4 symbols /D, /J, /Q, /z were undefined or TBD
+    2 were reserved
+
+OF THE 94 Alternate  Symbols.  The following were available:
+   3 undefined series \=, \Y, \Z which could do 36 overlays
+   8 series \1 through \8  that can support 36 overlays each
+   3 reserved series.
+
+ADDITIONAL OVERLAY PROPOSAL:  But any of the other 79 alternate 
+symbols could all have multiple (36) overlays if they can make sense 
+with the existing underlying basic symbol that we have been using for 
+that basic alternate symbol.  That is, any new definition of a 
+previously unused overlay character will have undefined results on all 
+prior APRS systems and should be used with caution.  But the symbol 
+set is extensible with these cautions.  (See the Proposal that would 
+expand the APRS symbol set to over 3200 at the bottom of this 
+document.)
+
+
+SYMBOL OVERLAY TABLES:  This document will keep track of all
+definitions of overlays on all ALTERNATE symbols.  Although these 
+overlays were originally intended to just overlay a displayable 
+single character on a basic symbol, there is no prohibition against
+taking the combination of a symbol and specific overlay, and then 
+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.
+
+AMPLIFIED some existing ALTERNATE SYMBOL Overlays: (new Aug 2014)
+change Flooding #W to include Avalanche, Mudslide/Landslide
+Update #' name to crash & incident sites
+Update \D (was available) to DEPOT family
+change overlayed car to generic Vehicle with (1-9 overlays)
+
+ADVISORIES: #<  (new expansion possibilities)
+/< = motorcycle
+\< = Advisory (single gale flag)
+
+AIRCRAFT
+/^ = LARGE Aircraft
+\^ = top-view originally intended to point in direction of flight
+A^ = Autonomous (2015)
+D^ = Drone   (new may 2014)
+E^ = Electric aircraft (2015)
+H^ = Hovercraft    (new may 2014)
+J^ = JET     (new may 2014)
+M^ = Missle   (new may 2014)
+P^ = Prop (new Aug 2014)
+R^ = Remotely Piloted (new 2015)
+S^ = Solar Powered  (new 2015)
+V^ = Vertical takeoff   (new may 2014)
+X^ = Experimental (new Aug 2014)
+
+ATM Machine or CURRENCY:  #$ 
+/$ = original primary Phone
+\$ = Bank or ATM (generic)
+U$ = US dollars
+L$ = Brittish Pound
+Y$ = Japanese Yen
+
+ARRL or DIAMOND: #a
+/a = Ambulance
+Aa = ARES
+Da = DSTAR (had been ARES Dutch)
+Ga = RSGB Radio Society of Great Brittan
+Ra = RACES
+Sa = SATERN Salvation Army
+Wa = WinLink
+
+BALLOONS and lighter than air #O (All new Oct 2015)
+/O = Original Balloon (think Ham balloon)
+\O = ROCKET (amateur)(2007)
+BO = Blimp           (2015)
+MO = Manned Balloon  (2015)
+TO = Teathered       (2015)
+CO = Constant Pressure - Long duration (2015)
+RO = Rocket bearing Balloon (Rockoon)  (2015)
+
+BOX SYMBOL: #A (and other system inputted symbols)
+/A = Aid station
+\A = numbered box
+9A = Mobile DTMF user
+7A = HT DTMF user
+HA = House DTMF user
+EA = Echolink DTMF report
+IA = IRLP DTMF report
+RA = RFID report
+AA = AllStar DTMF report
+DA = D-Star report
+XA = OLPC Laptop XO
+etc
+
+BUILDINGS: #h
+/h = Hospital
+\h = Ham Store       ** <= now used for HAMFESTS
+Fh = HamFest (new Aug 2014)
+Hh = Home Dept etc..
+
+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
+V> = GM Volt
+
+CIVIL DEFENSE or TRIANGLE: #c
+/c = Incident Command Post
+\c = Civil Defense
+Dc = Decontamination (new Aug 2014)
+Rc = RACES
+Sc = SATERN mobile canteen
+
+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)
+
+EYEBALL (EVENT) and VISIBILITY  #E
+/E = Eyeball for special live events
+\E = (existing smoke) the symbol with no overlay
+HE = (H overlay) Haze
+SE = (S overlay) Smoke
+BE = (B overlay) Blowing Snow         was \B
+DE = (D overlay) blowing Dust or sand was \b
+FE = (F overlay) Fog                  was \{
+
+GATEWAYS: #&
+/& = HF Gateway  <= the original primary table definition
+I& = Igate Generic (please use more specific overlay)
+R& = Receive only IGate (do not send msgs back to RF)
+T& = TX igate with path set to 1 hop only)
+2& = TX igate with path set to 2 hops (not generally good idea)
+
+GPS devices: #\
+/\ = Triangle DF primary symbol
+\\ = was undefined alternate symbol
+A\ = Avmap G5      * <= Recommend special symbol
+
+HAZARDS: #H
+/H = hotel
+\H = Haze
+RH = Radiation detector (new mar 2011)
+WH = Hazardous Waste
+XH = Skull&Crossbones
+
+HUMAN SYMBOL: #[
+/[ = Human
+\[ = Wall Cloud (the original definition)
+B[ = Baby on board (stroller, pram etc)
+S[ = Skier      * <= Recommend Special Symbol
+R[ = Runner
+H[ = Hiker
+
+HOUSE: #-
+/- = House
+\- = (was HF)
+5- = 50 Hz mains power
+6- = 60 Hz mains power
+B- = Backup Battery Power
+C- = Club, as in Ham club
+E- = Emergency power
+G- = Geothermal
+H- = Hydro powered
+O- = Operator Present
+S- = Solar Powered
+W- = Wind powered
+
+INCIDENT SITES: #'
+/' = Small Aircraft (original primary symbol)
+\' = Airplane Crash Site  <= the original alternate deifinition
+A' = Automobile crash site
+H' = Hazardous incident
+M' = Multi-Vehicle crash site
+P' = Pileup
+T' = Truck wreck
+
+NUMBERED CIRCLES: #0
+E0 = Echolink Node (E0)
+I0 = IRLP repeater (I0)
+S0 = Staging Area  (S0)
+W0 = WIRES (Yaesu VOIP)
+
+NETWORK NODES: #8
+88 = 802.11 network node (88)
+G8 = 802.11G  (G8)
+
+PORTABLE SYMBOL: #;
+/; = Portable operation (tent)
+\; = Park or Picnic
+F; = Field Day
+I; = Islands on the air
+S; = Summits on the air
+W; = WOTA
+
+POWER or ENERGY: #%  
+/% = 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
+
+RESTAURANTS: #R 
+\R = Restaurant (generic)
+7R = 7/11
+KR = KFC
+MR = McDonalds
+TR = Taco Bell
+
+RADIOS and APRS DEVICES: #Y
+/Y = Yacht  <= the original primary symbol
+\Y =        <= the original alternate was undefined
+AY = Alinco
+BY = Byonics
+IY = Icom
+KY = Kenwood       * <= Recommend special symbol
+YY = Yaesu/Standard* <= Recommend special symbol
+
+
+SPECIAL VEHICLES: #k
+/k = truck
+\k = SUV
+4k = 4x4
+Ak = ATV (all terrain vehicle)
+
+SHELTERS: #z
+/z = was available
+\z = overlayed shelter
+Cz = Clinic (new Aug 2014)
+Gz = Government building  (new Aug 2014)
+Mz = Morgue (new Aug 2014)
+Tz = Triage (new Aug 2014)
+
+SHIPS: #s 
+/s = Power boat (ship) side view
+\s = Overlay Boat (Top view)
+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
+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/Backhoe  (new Aug 2014)
+Gu = Gas
+Pu = Plow or SnowPlow (new Aug 2014)
+Tu = Tanker
+Cu = Chlorine Tanker
+Hu = Hazardous
+
+
+Anyone can use any overlay on any of the overlayable symbols for any
+special purpose.  We are not trying to document all possible such
+overlays.  The purpose of this document is to help keep a list of 
+more common such definitions that have more universal use and for
+which multiple definitions would lead to confusion.
+
+Future APRS code writers should be aware of where we are going:
+
+********************************************************************
+PROPOSAL TO ASSIGN MANY MORE BLOCKS OF SYMBOL SETS        April 2007
+--------------------------------------------------------------------
+www.aprs.org/symbols/symbols-overlays.txt
+
+In the initiative to upgrade APRS symbols, we are wasting some
+very valuable OVERLAYABLE symbol subgroups with a few nailed
+down legacy weather symbols.  We are proposing to consolidate
+some of these Weather symbols to open up much more space.  Since
+this is the first time we have considered actually CHANGING some
+symbol definitions, this can cause problems out there for some
+users of some legacy systems.
+
+That is why I am posting this plan to the APRS community.  If we
+do this, XASTIR and UIVIEW will be able to download new symbol
+definitions.  But some legacy clients that do not operate from
+external symbol files will show the wrong symbols for these. If
+users of those systems forsee some serious problems with the
+re-arrangement of these symbols, let us know the specific impact
+and your ideas for a workaround.. 
+
+The symbols that would be impacted are as follows:
+
+First, consolidate all of the visibility symbols into the old
+SMOKE symbol and change its meaning to "VISIBILITY", and then
+differentiate them with the overlay characters.  
+
+"\E"  (existing smoke) the symbol with no overlay
+"HE"  (an H overlay) will mean Haze
+"SE"  (an S overlay) will mean Smoke
+"BE"  (a  B overlay) will mean Blowing Snow         was \B
+"DE"  (a  D overlay) will mean blowing Dust or sand was \b
+"FE"  (an F overlay) will mean Fog                  was \{
+
+Another category is to expand the RAIN symbol to make it kinda
+like lots of angled dots coming from the sky, but with an open
+center so that we can use overlays for a number of common
+PRECIPITATIONS.  The consolidations would be:
+
+"\`" (existing Rain) would be the symbol with no overlay
+"R`" (an R overlay) would mean Rain
+"F`" (an F overlay) would mean Freezing Rain  was \F
+"H`" (an H overlay) would mean Hail           was\:
+"D`" (an D overlay) would mean Drizzle        was \D
+"E`" (an E overlay) would mean slEEt          was \e
+"S`" (an S overlay) would mean Snow           was \*
+Etc. and other particulates coming from the sky
+
+Next, I propose expanding the existing RAIN SHOWER "\I" symbol
+to look like some kind of cloud symbol with specks in it that
+can be overlayed. (It needs to look different from the next
+CLOUD symbol). It can then consolidate these symbols:
+
+"RI" (an R overlay) would mean Rain Shower
+"SI" (an S overlay) would mean Snow shower    was \G
+"LI" (an L overlay) would mean Lightening     was \J
+Etc. and other things related to clouds
+
+Next, I propose expanding the existing CLOUD symbol to allow
+definition of any number of different types of cloud.  This
+needs to also look like a cloud but a different shape and allow
+for overlays  (Maybe this cloud is clear):
+
+"\(" is the existing cloud symbol (would have no overlay)
+"P(" with P overlay would mean partly cloudy        was \p
+"W(" with W overlay would be a wall cloud           was \[
+"F(" would be Funnel cloud, but the original "\f" will also be
+retained for now
+
+All of these initiative will free up a lot of overlayable symbol 
+GROUPS each of which can suport up to 36 different overlays in 
+each group for the future:
+
+#H for 36 new Hazards (was only Hail)
+#[ for 36 new human symbols (was only Wall Cloud)
+#\ for 36 new GPS or navigation equipment
+#B TBD. \B was only blowing snow         now is BE
+#b TBD. \b was only blowing dust/sand    now is DE
+#{ TBD. \{ was only fog                  now is FE
+#* TBD. \* was snow only                 now is S`
+#: TBD. \: was hail only                 now is H`
+#D TBD. \D was drizzle only              now is D`
+#F TBD. \F was freezing rain only        now is F`
+#e TBD. \e was sleet only                now is E`
+#G TBD. \G was only Snow shower          now is SI
+#J TBD. \J was only Lightening           now is LI
+#p TBD. \p was only partly cloudy        now is P(
+
+Of course future code can fully draw each of these overlays as
+distinct special symbols in any way they want. 
+
+I especially want to hear from Dale Hugley who
+is a resource for weather, and Stephen Smith who will have to
+draw them for Uiview. And others with a stake in this...
+
+Bob Bruninga, WB4APR
diff --git a/symbols.c b/symbols.c
index c833ec0..e4d51af 100644
--- a/symbols.c
+++ b/symbols.c
@@ -1,910 +1,1056 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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:	symbols.c
- *
- * Purpose:	Functions related to the APRS symbols
- *
- *------------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>	
-#include <string.h>	
-#include <ctype.h>	
-#include "textcolor.h"
-#include "symbols.h"
-
-//#if __WIN32__
-	char *strcasestr(const char *S, const char *FIND);
-//#endif
-
-/*
- * APRS symbol tables.
- *
- * Derived from http://www.aprs.org/symbols/symbolsX.txt
- * version of 20 Oct 2009.
- */
-
-/*
- * Primary symbol table.
- */
-
-#define SYMTAB_SIZE 95
-
-
-static const struct {
-		char xy[3];
-		char *description;
-	} primary_symtab[SYMTAB_SIZE] = {
-
-	/*     00  */	{ "~~", "--no-symbol--" },
-	/*  !  01  */	{ "BB", "Police, Sheriff" },
-	/*  "  02  */	{ "BC", "reserved  (was rain)" },
-	/*  #  03  */	{ "BD", "DIGI (white center)" },
-	/*  $  04  */	{ "BE", "PHONE" },
-	/*  %  05  */	{ "BF", "DX CLUSTER" },
-	/*  &  06  */	{ "BG", "HF GATEway" },
-	/*  '  07  */	{ "BH", "Small AIRCRAFT" },
-	/*  (  08  */	{ "BI", "Mobile Satellite Station" },
-	/*  )  09  */	{ "BJ", "Wheelchair (handicapped)" },
-	/*  *  10  */	{ "BK", "SnowMobile" },
-	/*  +  11  */	{ "BL", "Red Cross" },
-	/*  ,  12  */	{ "BM", "Boy Scouts" },
-	/*  -  13  */	{ "BN", "House QTH (VHF)" },
-	/*  .  14  */	{ "BO", "X" },
-	/*  /  15  */	{ "BP", "Red Dot" },
-	/*  0  16  */	{ "P0", "# circle (obsolete)" },
-	/*  1  17  */	{ "P1", "TBD" },
-	/*  2  18  */	{ "P2", "TBD" },
-	/*  3  19  */	{ "P3", "TBD" },
-	/*  4  20  */	{ "P4", "TBD" },
-	/*  5  21  */	{ "P5", "TBD" },
-	/*  6  22  */	{ "P6", "TBD" },
-	/*  7  23  */	{ "P7", "TBD" },
-	/*  8  24  */	{ "P8", "TBD" },
-	/*  9  25  */	{ "P9", "TBD" },
-	/*  :  26  */	{ "MR", "FIRE" },
-	/*  ;  27  */	{ "MS", "Campground (Portable ops)" },
-	/*  <  28  */	{ "MT", "Motorcycle" },
-	/*  =  29  */	{ "MU", "RAILROAD ENGINE" },
-	/*  >  30  */	{ "MV", "CAR" },
-	/*  ?  31  */	{ "MW", "SERVER for Files" },
-	/*  @  32  */	{ "MX", "HC FUTURE predict (dot)" },
-	/*  A  33  */	{ "PA", "Aid Station" },
-	/*  B  34  */	{ "PB", "BBS or PBBS" },
-	/*  C  35  */	{ "PC", "Canoe" },
-	/*  D  36  */	{ "PD", "" },
-	/*  E  37  */	{ "PE", "EYEBALL (Eye catcher!)" },
-	/*  F  38  */	{ "PF", "Farm Vehicle (tractor)" },
-	/*  G  39  */	{ "PG", "Grid Square (6 digit)" },
-	/*  H  40  */	{ "PH", "HOTEL (blue bed symbol)" },
-	/*  I  41  */	{ "PI", "TcpIp on air network stn" },
-	/*  J  42  */	{ "PJ", "" },
-	/*  K  43  */	{ "PK", "School" },
-	/*  L  44  */	{ "PL", "PC user" },
-	/*  M  45  */	{ "PM", "MacAPRS" },
-	/*  N  46  */	{ "PN", "NTS Station" },
-	/*  O  47  */	{ "PO", "BALLOON" },
-	/*  P  48  */	{ "PP", "Police" },
-	/*  Q  49  */	{ "PQ", "TBD" },
-	/*  R  50  */	{ "PR", "REC. VEHICLE" },
-	/*  S  51  */	{ "PS", "SHUTTLE" },
-	/*  T  52  */	{ "PT", "SSTV" },
-	/*  U  53  */	{ "PU", "BUS" },
-	/*  V  54  */	{ "PV", "ATV" },
-	/*  W  55  */	{ "PW", "National WX Service Site" },
-	/*  X  56  */	{ "PX", "HELO" },
-	/*  Y  57  */	{ "PY", "YACHT (sail)" },
-	/*  Z  58  */	{ "PZ", "WinAPRS" },
-	/*  [  59  */	{ "HS", "Human/Person (HT)" },
-	/*  \  60  */	{ "HT", "TRIANGLE(DF station)" },
-	/*  ]  61  */	{ "HU", "MAIL/PostOffice(was PBBS)" },
-	/*  ^  62  */	{ "HV", "LARGE AIRCRAFT" },
-	/*  _  63  */	{ "HW", "WEATHER Station (blue)" },
-	/*  `  64  */	{ "HX", "Dish Antenna" },
-	/*  a  65  */	{ "LA", "AMBULANCE" },
-	/*  b  66  */	{ "LB", "BIKE" },
-	/*  c  67  */	{ "LC", "Incident Command Post" },
-	/*  d  68  */	{ "LD", "Fire dept" },
-	/*  e  69  */	{ "LE", "HORSE (equestrian)" },
-	/*  f  70  */	{ "LF", "FIRE TRUCK" },
-	/*  g  71  */	{ "LG", "Glider" },
-	/*  h  72  */	{ "LH", "HOSPITAL" },
-	/*  i  73  */	{ "LI", "IOTA (islands on the air)" },
-	/*  j  74  */	{ "LJ", "JEEP" },
-	/*  k  75  */	{ "LK", "TRUCK" },
-	/*  l  76  */	{ "LL", "Laptop" },
-	/*  m  77  */	{ "LM", "Mic-E Repeater" },
-	/*  n  78  */	{ "LN", "Node (black bulls-eye)" },
-	/*  o  79  */	{ "LO", "EOC" },
-	/*  p  80  */	{ "LP", "ROVER (puppy, or dog)" },
-	/*  q  81  */	{ "LQ", "GRID SQ shown above 128 m" },
-	/*  r  82  */	{ "LR", "Repeater" },
-	/*  s  83  */	{ "LS", "SHIP (pwr boat)" },
-	/*  t  84  */	{ "LT", "TRUCK STOP" },
-	/*  u  85  */	{ "LU", "TRUCK (18 wheeler)" },
-	/*  v  86  */	{ "LV", "VAN" },
-	/*  w  87  */	{ "LW", "WATER station" },
-	/*  x  88  */	{ "LX", "xAPRS (Unix)" },
-	/*  y  89  */	{ "LY", "YAGI @ QTH" },
-	/*  z  90  */	{ "LZ", "TBD" },
-	/*  {  91  */	{ "J1", "" },
-	/*  |  92  */	{ "J2", "TNC Stream Switch" },
-	/*  }  93  */	{ "J3", "" },
-	/*  ~  94  */	{ "J3", "TNC Stream Switch" } };
-
-/*
- * Alternate symbol table.
- */
-
-static const struct {
-		char xy[3];
-		char *description;
-	} alternate_symtab[SYMTAB_SIZE] = {
-
-	/*     00  */	{ "~~", "--no-symbol--" },
-	/*  !  01  */	{ "OB", "EMERGENCY (!)" },
-	/*  "  02  */	{ "OC", "reserved" },
-	/*  #  03  */	{ "OD", "OVERLAY DIGI (green star)" },
-	/*  $  04  */	{ "OE", "Bank or ATM  (green box)" },
-	/*  %  05  */	{ "OF", "Power Plant with overlay" },
-	/*  &  06  */	{ "OG", "I=Igte IGate R=RX T=1hopTX 2=2hopTX" },
-	/*  '  07  */	{ "OH", "Crash (& now Incident sites)" },
-	/*  (  08  */	{ "OI", "CLOUDY (other clouds w ovrly)" },
-	/*  )  09  */	{ "OJ", "Firenet MEO, MODIS Earth Obs." },
-	/*  *  10  */	{ "OK", "SNOW (& future ovrly codes)" },
-	/*  +  11  */	{ "OL", "Church" },
-	/*  ,  12  */	{ "OM", "Girl Scouts" },
-	/*  -  13  */	{ "ON", "House (H=HF) (O = Op Present)" },
-	/*  .  14  */	{ "OO", "Ambiguous (Big Question mark)" },
-	/*  /  15  */	{ "OP", "Waypoint Destination" },
-	/*  0  16  */	{ "A0", "CIRCLE (E/I/W=IRLP/Echolink/WIRES)" },
-	/*  1  17  */	{ "A1", "" },
-	/*  2  18  */	{ "A2", "" },
-	/*  3  19  */	{ "A3", "" },
-	/*  4  20  */	{ "A4", "" },
-	/*  5  21  */	{ "A5", "" },
-	/*  6  22  */	{ "A6", "" },
-	/*  7  23  */	{ "A7", "" },
-	/*  8  24  */	{ "A8", "802.11 or other network node" },
-	/*  9  25  */	{ "A9", "Gas Station (blue pump)" },
-	/*  :  26  */	{ "NR", "Hail (& future ovrly codes)" },
-	/*  ;  27  */	{ "NS", "Park/Picnic area" },
-	/*  <  28  */	{ "NT", "ADVISORY (one WX flag)" },
-	/*  =  29  */	{ "NU", "APRStt Touchtone (DTMF users)" },
-	/*  >  30  */	{ "NV", "OVERLAYED CAR" },
-	/*  ?  31  */	{ "NW", "INFO Kiosk  (Blue box with ?)" },
-	/*  @  32  */	{ "NX", "HURICANE/Trop-Storm" },
-	/*  A  33  */	{ "AA", "overlayBOX DTMF & RFID & XO" },
-	/*  B  34  */	{ "AB", "Blwng Snow (& future codes)" },
-	/*  C  35  */	{ "AC", "Coast Guard" },
-	/*  D  36  */	{ "AD", "Drizzle (proposed APRStt)" },
-	/*  E  37  */	{ "AE", "Smoke (& other vis codes)" },
-	/*  F  38  */	{ "AF", "Freezng rain (&future codes)" },
-	/*  G  39  */	{ "AG", "Snow Shwr (& future ovrlys)" },
-	/*  H  40  */	{ "AH", "Haze (& Overlay Hazards)" },
-	/*  I  41  */	{ "AI", "Rain Shower" },
-	/*  J  42  */	{ "AJ", "Lightening (& future ovrlys)" },
-	/*  K  43  */	{ "AK", "Kenwood HT (W)" },
-	/*  L  44  */	{ "AL", "Lighthouse" },
-	/*  M  45  */	{ "AM", "MARS (A=Army,N=Navy,F=AF)" },
-	/*  N  46  */	{ "AN", "Navigation Buoy" },
-	/*  O  47  */	{ "AO", "Rocket" },
-	/*  P  48  */	{ "AP", "Parking" },
-	/*  Q  49  */	{ "AQ", "QUAKE" },
-	/*  R  50  */	{ "AR", "Restaurant" },
-	/*  S  51  */	{ "AS", "Satellite/Pacsat" },
-	/*  T  52  */	{ "AT", "Thunderstorm" },
-	/*  U  53  */	{ "AU", "SUNNY" },
-	/*  V  54  */	{ "AV", "VORTAC Nav Aid" },
-	/*  W  55  */	{ "AW", "# NWS site (NWS options)" },
-	/*  X  56  */	{ "AX", "Pharmacy Rx (Apothicary)" },
-	/*  Y  57  */	{ "AY", "Radios and devices" },
-	/*  Z  58  */	{ "AZ", "" },
-	/*  [  59  */	{ "DS", "W.Cloud (& humans w Ovrly)" },
-	/*  \  60  */	{ "DT", "New overlayable GPS symbol" },
-	/*  ]  61  */	{ "DU", "" },
-	/*  ^  62  */	{ "DV", "# Aircraft (shows heading)" },
-	/*  _  63  */	{ "DW", "# WX site (green digi)" },
-	/*  `  64  */	{ "DX", "Rain (all types w ovrly)" },
-	/*  a  65  */	{ "SA", "ARRL, ARES, WinLINK" },
-	/*  b  66  */	{ "SB", "Blwng Dst/Snd (& others)" },
-	/*  c  67  */	{ "SC", "CD triangle RACES/SATERN/etc" },
-	/*  d  68  */	{ "SD", "DX spot by callsign" },
-	/*  e  69  */	{ "SE", "Sleet (& future ovrly codes)" },
-	/*  f  70  */	{ "SF", "Funnel Cloud" },
-	/*  g  71  */	{ "SG", "Gale Flags" },
-	/*  h  72  */	{ "SH", "Store. or HAMFST Hh=HAM store" },
-	/*  i  73  */	{ "SI", "BOX or points of Interest" },
-	/*  j  74  */	{ "SJ", "WorkZone (Steam Shovel)" },
-	/*  k  75  */	{ "SK", "Special Vehicle SUV,ATV,4x4" },
-	/*  l  76  */	{ "SL", "Areas      (box,circles,etc)" },
-	/*  m  77  */	{ "SM", "Value Sign (3 digit display)" },
-	/*  n  78  */	{ "SN", "OVERLAY TRIANGLE" },
-	/*  o  79  */	{ "SO", "small circle" },
-	/*  p  80  */	{ "SP", "Prtly Cldy (& future ovrlys)" },
-	/*  q  81  */	{ "SQ", "" },
-	/*  r  82  */	{ "SR", "Restrooms" },
-	/*  s  83  */	{ "SS", "OVERLAY SHIP/boat (top view)" },
-	/*  t  84  */	{ "ST", "Tornado" },
-	/*  u  85  */	{ "SU", "OVERLAYED TRUCK" },
-	/*  v  86  */	{ "SV", "OVERLAYED Van" },
-	/*  w  87  */	{ "SW", "Flooding" },
-	/*  x  88  */	{ "SX", "Wreck or Obstruction ->X<-" },
-	/*  y  89  */	{ "SY", "Skywarn" },
-	/*  z  90  */	{ "SZ", "OVERLAYED Shelter" },
-	/*  {  91  */	{ "Q1", "Fog (& future ovrly codes)" },
-	/*  |  92  */	{ "Q2", "TNC Stream Switch" },
-	/*  }  93  */	{ "Q3", "" },
-	/*  ~  94  */	{ "Q4", "TNC Stream Switch" } };
-
-
-/*------------------------------------------------------------------
- *
- * Function:	symbols_init
- *
- * Purpose:	Initialization for functions related to symbols.
- *
- * Inputs:	
- *
- * Global output:
- *		new_sym_ptr
- *		new_sym_size
- *		new_sym_len
- *
- * Description:	The primary and alternate symbol tables are constant
- *		so they are hardcoded.
- *		However the "new" sysmbols, which give new meanings to
- *		overlayed symbols, are always evolving.
- *		For maximum flexibility, we will read the
- *		data file at run time rather than compiling it in.
- *
- *		For the most recent version, download from:
- *
- *		http://www.aprs.org/symbols/symbols-new.txt
- *
- *		Windows version:  File must be in current working directory.
- *
- *		Linux version: Search order is current working directory
- *			then /usr/share/direwolf directory.
- *
- *------------------------------------------------------------------*/
-
-#define NEW_SYM_INIT_SIZE 20
-#define NEW_SYM_DESC_LEN 29
-
-typedef struct new_sym_s {
-	char overlay;
-	char symbol;
-	char description[NEW_SYM_DESC_LEN+1];
-} new_sym_t;
-
-static new_sym_t *new_sym_ptr = NULL;	/* Dynamically allocated array. */
-static int new_sym_size = 0;		/* Number of elements allocated. */
-static int new_sym_len = 0;			/* Number of elements used. */
-
-
-void symbols_init (void)
-{
-	FILE *fp;
-
-/*
- * 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. */
-	}
-
-// If search strategy changes, be sure to keep decode_tocall in sync.
-
-
-	fp = fopen("symbols-new.txt", "r");
-#ifndef __WIN32__
-	if (fp == NULL) {
-	  fp = fopen("/usr/share/direwolf/symbols-new.txt", "r");
-	}
-#endif
-	if (fp == NULL) {
-
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Warning: Could not open 'symbols-new.txt'.\n");
-	  dw_printf ("The \"new\" overlayed character information will not be available.\n");
-
-	  new_sym_size = 1;	
-	  new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t));  /* Don't try again. */
-	  new_sym_len = 0;	
-	  return;
-	}
-
-/*
- * Count number of interesting lines and allocate storage.
- */
-	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
-	  if (GOOD_LINE(stuff)) {
-	    new_sym_size++;
-	  }
-	}
-
-	new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t));
-
-/*
- * Rewind, read again, and save contents of interesting lines. 
- */
-	rewind (fp);
-
-	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
-
-	  if (GOOD_LINE(stuff)) {
-	    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[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++;
-	  }
-	}
-	fclose (fp);
-
-	assert (new_sym_len == new_sym_size);
-
-#if 0
-	for (j=0; j<new_sym_len; j++) {
-	  dw_printf ("%02d: %c %c '%s'\n", j, new_sym_ptr[j].overlay,
-		new_sym_ptr[j].symbol, new_sym_ptr[j].description);
-	}
-#endif
-
-} /* end symbols_init */
-
-
-/*------------------------------------------------------------------
- *
- * Function:	symbols_from_dest_or_src
- *
- * Purpose:	Extract symbol from destination or source.
- *
- * Inputs:	dti	- Data type indicator.
- *
- *		src	- Source address with SSID.
- *		
- *		dest	- Destination address.
- *			  Don't care if SSID is present or not.
- *
- * Outputs:	*symtab
- *		*symbol
- *
- * Description:	There are 3 different ways to specify the symbol,
- *		in this order of precedence:
- *	
- *		- Information field.  This was done already in the
- *		  separate decoders for different message types.
- *
- *		If not set already,
- *
- *		- The destination address if it has certain formats
- *		  starting with GPS, SPC, or SYM which are equivalent
- *		  for our purposes.
- *		  (Not for MIC-E where destination has a special use.)
- *
- *		If all else fails,
- *
- *		- SSID of the source address.
- *
- *
- *------------------------------------------------------------------*/
-
-const static char ssid_to_sym[16] = {
-	  ' ',	/* 0 - No icon. */
-	  'a',	/* 1 - Ambulance */
-	  'U',	/* 2 - Bus */
-	  'f',	/* 3 - Fire Truck */
-	  'b',	/* 4 - Bicycle */
-	  'Y',	/* 5 - Yacht */
-	  'X',	/* 6 - Helicopter */
-	  '\'',	/* 7 - Small Aircraft */
-	  's',	/* 8 - Ship */
-	  '>',	/* 9 - Car */
-	  '<',	/* 10 - Motorcycle */
-	  'O',	/* 11 - Ballon */
-	  'j',	/* 12 - Jeep */
-	  'R',	/* 13 - Recreational Vehicle */
-	  'k',	/* 14 - Truck */
-	  'v' 	/* 15 - Van */
-	};
-
-void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol)
-{
-	char *p;
-
-
-/*
- * This part does not apply to MIC-E format because the destination
- * is used to encode latitude and other information.
- */
-	if (dti != '\'' && dti != '`') {
-
-/* 
- * For GPSCnn, nn is the index into the primary symbol table.
- */
-
-	  if (strncmp(dest, "GPSC", 4) == 0)
-	  {
-	    int nn;
-	  
-	    nn = atoi(dest+4);
-	    if (nn >= 1 && nn <= 94) {
-	      *symtab = '/';		/* Primary */
-	      *symbol = ' ' + nn;	
-	      return;
-	    }
-	  }
-
-/* 
- * For GPSEnn, nn is the index into the primary symbol table.
- */
-
-	  if (strncmp(dest, "GPSE", 4) == 0)
-	  {
-	    int nn;
-	  
-	    nn = atoi(dest+4);
-	    if (nn >= 1 && nn <= 94) {
-	      *symtab = '\\';		/* Alternate. */
-	      *symbol = ' ' + nn;	
-	      return;
-	    }
-	  }
-
-/*
- * For GPSxy or SPCxy or SYMxy, look up xy in the translation tables.
- * First search the primary table.
- */
-
-	  if (strncmp(dest, "GPS", 3) == 0 ||
-	      strncmp(dest, "SPC", 3) == 0 ||
-	      strncmp(dest, "SYM", 3) == 0) 
-	  {
-	    char xy[3];
-	    int nn;
-	  
-	    xy[0] = dest[3];
-	    xy[1] = dest[4];
-	    xy[2] = '\0';
-
-	    for (nn = 1; nn <= 94; nn++) {
-	      if (strcmp(xy, primary_symtab[nn].xy) == 0) {
-	        *symtab = '/';		/* Primary. */
-	        *symbol = ' ' + nn;
-	        return;
-	      }
-	    }
-	  }			
-
-/*
- * Next, search the alternate table.
- * This time, we can have the format ...xyz, where z is an overlay character.
- * Only upper case letters and digits are valid overlay characters.
- */
-
-	  if (strncmp(dest, "GPS", 3) == 0 ||
-	      strncmp(dest, "SPC", 3) == 0 ||
-	      strncmp(dest, "SYM", 3) == 0) 
-	  {
-	    char xy[3];
-	    char z;
-	    int nn;
-	  
-	    xy[0] = dest[3];
-	    xy[1] = dest[4];
-	    xy[2] = '\0';
-	    z = dest[5];
-
-	    for (nn = 1; nn <= 94; nn++) {
-	      if (strcmp(xy, alternate_symtab[nn].xy) == 0) {
-	        *symtab = '\\';		/* Alternate. */
-	        *symbol = ' ' + nn;
-		if (isupper((int)z) || isdigit((int)z)) {
-	          *symtab = z;
-	        }
-	        return;
-	      }
-	    }
-	  }
-
-	}  /* end not MIC-E */
-
-/*
- * When all else fails, use source SSID.
- */
-
-	p = strchr (src, '-');
-	if (p != NULL) 
-	{
-	  int ssid;
-
-	  ssid = atoi(p+1);
-	  if (ssid >= 1 && ssid <= 15) {
-	    *symtab = '/';		/* All in Primary table. */
-	    *symbol = ssid_to_sym[ssid];
-	    return;
-	  }
-	}
-
-} /* symbols_from_dest_or_src */
-
-
-
-/*------------------------------------------------------------------
- *
- * Function:	symbols_into_dest
- *
- * Purpose:	Encode symbol for destination field.
- *
- * Inputs:	symtab		/, \, 0-9, A-Z
- *		symbol		any printable character ! to ~ 
- *
- * Outputs:	dest		6 character "destination" of the forms 
- *					GPSCnn  - primary table.
- *					GPSEnn  - alternate table.
- *					GPSxyz  - alternate with overlay.
- *
- * Returns:	0 for success, 1 for error.
- *
- *------------------------------------------------------------------*/
-
-int symbols_into_dest (char symtab, char symbol, char *dest)
-{
-
-	if (symbol >= '!' && symbol <= '~' && symtab == '/') {
-	  
-	  /* Primary Symbol table. */
-	  sprintf (dest, "GPSC%02d", symbol - ' ');
-	  return (0);
-	}
-	else if (symbol >= '!' && symbol <= '~' && symtab == '\\') {
-	  
-	  /* Alternate Symbol table. */
-	  sprintf (dest, "GPSE%02d", symbol - ' ');
-	  return (0);
-	}
-	else if (symbol >= '!' && symbol <= '~' && (isupper(symtab) || isdigit(symtab))) {
-
-	  /* Alternate Symbol table with overlay. */
-	  sprintf (dest, "GPS%s%c", alternate_symtab[symbol - ' '].xy, symtab);
-	  return (0);
-	}
-
-
-	text_color_set(DW_COLOR_ERROR);
-	dw_printf ("Could not convert symbol \"%c%c\" to GPSxyz destination format.\n",
-			symtab, symbol);
-
-	strcpy (dest, "GPS???");	/* Error. */
-	return (1);
-}
-
-
-/*------------------------------------------------------------------
- *
- * Function:	symbols_get_description
- *
- * Purpose:	Get description for given symbol table/code/overlay.
- *
- * Inputs:	symtab		/, \, 0-9, A-Z
- *		symbol		any printable character ! to ~ 
- *
- * Outputs:	description	Text description.
- *				"--no-symbol--"  if error.
- *
- *
- * Description:	This is used for the monitoring and the 
- *		decode_aprs utility.
- *
- *------------------------------------------------------------------*/
-
-void symbols_get_description (char symtab, char symbol, char *description)
-{
-	char tmp2[2];
-	int j;
-
-	symbols_init();
-
-
-// The symbol table identifier should be 
-//	/	for symbol from primary table
-//	\	for symbol from alternate table
-//	0-9,A-Z	for alternate symbol table with overlay character
-
-	if (symtab != '/' &&
-	    symtab != '\\' &&
-	    ! isdigit(symtab) &&
-	    ! isupper(symtab)) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Symbol table identifier is not '/' (primary), '\\' (alternate), or valid overlay character.\n");
-	
-	  /* Possibilities: */
-	  /* Select primary table and keep going, or */
-	  /* report no symbol.  It IS an error. */
-	  /* We do the latter. */
-
-	  symbol = ' ';
-	  strcpy (description, primary_symtab[symbol-' '].description);
-	  return;
-	}
-
-// Bounds check before using symbol as index into table.
-
-	if (symbol < ' ' || symbol > '~') {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Symbol code is not a printable character.\n");
-	  symbol = ' ';		/* Avoid subscript out of bounds. */	  
-	}
-
-// First try to match with the "new" symbols.
-
-	for (j=0; j<new_sym_len; j++) {
-	  if (symtab == new_sym_ptr[j].overlay && symbol == new_sym_ptr[j].symbol) {
-	    strcpy (description, new_sym_ptr[j].description);
-	    return;
-	  }
-	}  
-
-// Otherwise use the original symbol tables.
-
-	if (symtab == '/') {
-	  strcpy (description, primary_symtab[symbol-' '].description);
-	}
-	else {
-	  strcpy (description, alternate_symtab[symbol-' '].description);
-	  if (symtab != '\\') {
-	    strcat (description, " w/overlay ");
-	    tmp2[0] = symtab;
-	    tmp2[1] = '\0';
-	    strcat (description, tmp2);
-	  }
-	}
-
-} /* end symbols_get_description */
-
-
-/*------------------------------------------------------------------
- *
- * Function:	symbols_code_from_description
- *
- * Purpose:	Find a suitable table/symbol based on given description.
- *
- * Inputs:	overlay		Explicit overlay or space.
- *		description	Substring of one of the descriptions.
- *				Example: dog
- *
- * Outputs:	symtab		/, \, 0-9, A-Z
- *		symbol		any printable character ! to ~ 
- *
- * Returns:	1 for success, 0 for failure.
- *
- *------------------------------------------------------------------*/
-
-int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol)
-{
-	int j;
-
-	symbols_init();
-
-/*
- * If user specified a particular overlay (i.e. for config file BEACON), 
- * first try the alternate symbol table.
- * If that fails should we give up or ignore the overlay and keep trying?
- */
-
-	if (isupper(overlay) || isdigit(overlay)) {
-
-	  for (j=0; j<SYMTAB_SIZE; j++) {
-	    if (strcasestr(alternate_symtab[j].description, description) != NULL) {
-	      *symtab = overlay;
-	      *symbol = j + ' ';
-	      return (1);
-	    }
-	  }
-	  /* If that fails should we give up or ignore the overlay and keep trying? */
-	}
-
-/*
- * Search primary table.
- */
-	for (j=0; j<SYMTAB_SIZE; j++) {
-	  if (strcasestr(primary_symtab[j].description, description) != NULL) {
-	    *symtab = '/';
-	    *symbol = j + ' ';
-	    return (1);
-	  }
-	}
-
-/*
- * Search alternate table.
- */
-	for (j=0; j<SYMTAB_SIZE; j++) {
-	  if (strcasestr(alternate_symtab[j].description, description) != NULL) {
-	    *symtab = '\\';
-	    *symbol = j + ' ';
-	    return (1);
-	  }
-	}
-
-/*
- * Finally, the "new" symbols.
- * Probably want this last so get get the most standard and
- * generic for queries such as "house" or ...
- */
-	for (j=0; j<new_sym_len; j++) {
-	  if (strcasestr(new_sym_ptr[j].description, description) != NULL) {
-	    *symtab = new_sym_ptr[j].overlay;
-	    *symbol = new_sym_ptr[j].symbol;
-	    return (1);
-	  }
-	}
-
-/*
- * Default to something generic like house.  
- * Caller is responsible for issuing error message.
- */
-	*symtab = '/';
-	*symbol = '-';
-	return (0);
-
-} /* end symbols_code_from_description */
-
-
-#if 0
-
-/* Quick, incomplete, unit test. */
-/* gcc -g symbols.c textcolor.c misc.a */
-
-int main (int argc, char *argv[])
-{
-	char symtab;
-	char symbol;
-	char dest[8];
-	char description[50];
-
-	symbols_init ();
-
-
-
-	symbols_from_dest_or_src ('T', "W1ABC", "GPSC43", &symtab, &symbol);
-	if (symtab != '/' || symbol != 'K') dw_printf ("ERROR 1-1\n");
-
-	symbols_from_dest_or_src ('T', "W1ABC", "GPSE87", &symtab, &symbol);
-	if (symtab != '\\' || symbol != 'w') dw_printf ("ERROR 1-2\n");
-
-	symbols_from_dest_or_src ('T', "W1ABC", "SPCBL", &symtab, &symbol);
-	if (symtab != '/' || symbol != '+') dw_printf ("ERROR 1-3\n");
-
-	symbols_from_dest_or_src ('T', "W1ABC", "SYMST", &symtab, &symbol);
-	if (symtab != '\\' || symbol != 't') dw_printf ("ERROR 1-4\n");
-
-	symbols_from_dest_or_src ('T', "W1ABC", "GPSOD9", &symtab, &symbol);
-	if (symtab != '9' || symbol != '#') dw_printf ("ERROR 1-5\n");
-
-	symbols_from_dest_or_src ('T', "W1ABC-14", "XXXXXX", &symtab, &symbol);
-	if (symtab != '/' || symbol != 'k') dw_printf ("ERROR 1-6\n");
-
-	symbols_from_dest_or_src ('T', "W1ABC", "GPS???", &symtab, &symbol);
-	/* Outputs are left alone if symbol can't be determined. */
-	if (symtab != '/' || symbol != 'k') dw_printf ("ERROR 1-7\n");
-
-
-	symbols_into_dest ('/', 'K', dest);
-	if (strcmp(dest, "GPSC43") != 0) dw_printf ("ERROR 2-1\n");
-
-	symbols_into_dest ('\\', 'w', dest);
-	if (strcmp(dest, "GPSE87") != 0) dw_printf ("ERROR 2-2\n");
-
-	symbols_into_dest ('3', 'A', dest);
-	if (strcmp(dest, "GPSAA3") != 0) dw_printf ("ERROR 2-3\n");
-
-// Expect to see this:
-//   Could not convert symbol " A" to GPSxyz destination format.
-//   Could not convert symbol "/ " to GPSxyz destination format.
-
-	symbols_into_dest (' ', 'A', dest);
-	if (strcmp(dest, "GPS???") != 0) dw_printf ("ERROR 2-4\n");
-
-	symbols_into_dest ('/', ' ', dest);
-	if (strcmp(dest, "GPS???") != 0) dw_printf ("ERROR 2-5\n");
-
-
-
-	symbols_get_description ('J', 's', description);
-	if (strcmp(description, "Jet Ski") != 0) dw_printf ("ERROR 3-1\n");
-
-	symbols_get_description ('/', 'O', description);
-	if (strcmp(description, "BALLOON") != 0) dw_printf ("ERROR 3-2\n");
-
-	symbols_get_description ('\\', 'T', description);
-	if (strcmp(description, "Thunderstorm") != 0) dw_printf ("ERROR 3-3\n");
-
-	symbols_get_description ('5', 'T', description);
-	if (strcmp(description, "Thunderstorm w/overlay 5") != 0) dw_printf ("ERROR 3-4\n");
-
-// Expect to see this:
-//   Symbol table identifier is not '/' (primary), '\' (alternate), or valid overlay character.
-
-	symbols_get_description (' ', 'T', description);
-	if (strcmp(description, "--no-symbol--") != 0) dw_printf ("ERROR 3-5\n");
-
-	symbols_get_description ('/', ' ', description);
-	if (strcmp(description, "--no-symbol--") != 0) dw_printf ("ERROR 3-6\n");
-
-
-
-	symbols_code_from_description ('5', "girl scouts", &symtab, &symbol);
-	if (symtab != '5' || symbol != ',') dw_printf ("ERROR 4-1\n");
-
-	symbols_code_from_description (' ', "scouts", &symtab, &symbol);
-	if (symtab != '/' || symbol != ',') dw_printf ("ERROR 4-2\n");
-
-	symbols_code_from_description (' ', "girl scouts", &symtab, &symbol);
-	if (symtab != '\\' || symbol != ',') dw_printf ("ERROR 4-3\n");
-
-	symbols_code_from_description (' ', "jet ski", &symtab, &symbol);
-	if (symtab != 'J' || symbol != 's') dw_printf ("ERROR 4-4\n");
-
-	symbols_code_from_description (' ', "girl scouts", &symtab, &symbol);
-	if (symtab != '\\' || symbol != ',') dw_printf ("ERROR 4-5\n");
-
-	symbols_code_from_description (' ', "yen", &symtab, &symbol);
-	if (symtab != 'Y' || symbol != '$') dw_printf ("ERROR 4-6\n");
-
-	symbols_code_from_description (' ', "taco bell", &symtab, &symbol);
-	if (symtab != 'T' || symbol != 'R') dw_printf ("ERROR 4-7\n");
-
-
-} /* end main */
-
-#endif
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2013, 2014, 2015  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:	symbols.c
+ *
+ * Purpose:	Functions related to the APRS symbols
+ *
+ *------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>	
+#include <string.h>	
+#include <ctype.h>	
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "symbols.h"
+#include "tt_text.h"
+
+
+//#if __WIN32__
+	char *strcasestr(const char *S, const char *FIND);
+//#endif
+
+/*
+ * APRS symbol tables.
+ *
+ * Derived from http://www.aprs.org/symbols/symbolsX.txt
+ * version of 20 Oct 2009.
+ */
+
+/*
+ * Primary symbol table.
+ */
+
+#define SYMTAB_SIZE 95
+
+
+static const struct {
+		char xy[3];
+		char *description;
+	} primary_symtab[SYMTAB_SIZE] = {
+
+	/*     00  */	{ "~~", "--no-symbol--" },
+	/*  !  01  */	{ "BB", "Police, Sheriff" },
+	/*  "  02  */	{ "BC", "reserved  (was rain)" },
+	/*  #  03  */	{ "BD", "DIGI (white center)" },
+	/*  $  04  */	{ "BE", "PHONE" },
+	/*  %  05  */	{ "BF", "DX CLUSTER" },
+	/*  &  06  */	{ "BG", "HF GATEway" },
+	/*  '  07  */	{ "BH", "Small AIRCRAFT" },
+	/*  (  08  */	{ "BI", "Mobile Satellite Station" },
+	/*  )  09  */	{ "BJ", "Wheelchair (handicapped)" },
+	/*  *  10  */	{ "BK", "SnowMobile" },
+	/*  +  11  */	{ "BL", "Red Cross" },
+	/*  ,  12  */	{ "BM", "Boy Scouts" },
+	/*  -  13  */	{ "BN", "House QTH (VHF)" },
+	/*  .  14  */	{ "BO", "X" },
+	/*  /  15  */	{ "BP", "Red Dot" },
+	/*  0  16  */	{ "P0", "# circle (obsolete)" },
+	/*  1  17  */	{ "P1", "TBD" },
+	/*  2  18  */	{ "P2", "TBD" },
+	/*  3  19  */	{ "P3", "TBD" },
+	/*  4  20  */	{ "P4", "TBD" },
+	/*  5  21  */	{ "P5", "TBD" },
+	/*  6  22  */	{ "P6", "TBD" },
+	/*  7  23  */	{ "P7", "TBD" },
+	/*  8  24  */	{ "P8", "TBD" },
+	/*  9  25  */	{ "P9", "TBD" },
+	/*  :  26  */	{ "MR", "FIRE" },
+	/*  ;  27  */	{ "MS", "Campground (Portable ops)" },
+	/*  <  28  */	{ "MT", "Motorcycle" },
+	/*  =  29  */	{ "MU", "RAILROAD ENGINE" },
+	/*  >  30  */	{ "MV", "CAR" },
+	/*  ?  31  */	{ "MW", "SERVER for Files" },
+	/*  @  32  */	{ "MX", "HC FUTURE predict (dot)" },
+	/*  A  33  */	{ "PA", "Aid Station" },
+	/*  B  34  */	{ "PB", "BBS or PBBS" },
+	/*  C  35  */	{ "PC", "Canoe" },
+	/*  D  36  */	{ "PD", "" },
+	/*  E  37  */	{ "PE", "EYEBALL (Eye catcher!)" },
+	/*  F  38  */	{ "PF", "Farm Vehicle (tractor)" },
+	/*  G  39  */	{ "PG", "Grid Square (6 digit)" },
+	/*  H  40  */	{ "PH", "HOTEL (blue bed symbol)" },
+	/*  I  41  */	{ "PI", "TcpIp on air network stn" },
+	/*  J  42  */	{ "PJ", "" },
+	/*  K  43  */	{ "PK", "School" },
+	/*  L  44  */	{ "PL", "PC user" },
+	/*  M  45  */	{ "PM", "MacAPRS" },
+	/*  N  46  */	{ "PN", "NTS Station" },
+	/*  O  47  */	{ "PO", "BALLOON" },
+	/*  P  48  */	{ "PP", "Police" },
+	/*  Q  49  */	{ "PQ", "TBD" },
+	/*  R  50  */	{ "PR", "REC. VEHICLE" },
+	/*  S  51  */	{ "PS", "SHUTTLE" },
+	/*  T  52  */	{ "PT", "SSTV" },
+	/*  U  53  */	{ "PU", "BUS" },
+	/*  V  54  */	{ "PV", "ATV" },
+	/*  W  55  */	{ "PW", "National WX Service Site" },
+	/*  X  56  */	{ "PX", "HELO" },
+	/*  Y  57  */	{ "PY", "YACHT (sail)" },
+	/*  Z  58  */	{ "PZ", "WinAPRS" },
+	/*  [  59  */	{ "HS", "Human/Person (HT)" },
+	/*  \  60  */	{ "HT", "TRIANGLE(DF station)" },
+	/*  ]  61  */	{ "HU", "MAIL/PostOffice(was PBBS)" },
+	/*  ^  62  */	{ "HV", "LARGE AIRCRAFT" },
+	/*  _  63  */	{ "HW", "WEATHER Station (blue)" },
+	/*  `  64  */	{ "HX", "Dish Antenna" },
+	/*  a  65  */	{ "LA", "AMBULANCE" },
+	/*  b  66  */	{ "LB", "BIKE" },
+	/*  c  67  */	{ "LC", "Incident Command Post" },
+	/*  d  68  */	{ "LD", "Fire dept" },
+	/*  e  69  */	{ "LE", "HORSE (equestrian)" },
+	/*  f  70  */	{ "LF", "FIRE TRUCK" },
+	/*  g  71  */	{ "LG", "Glider" },
+	/*  h  72  */	{ "LH", "HOSPITAL" },
+	/*  i  73  */	{ "LI", "IOTA (islands on the air)" },
+	/*  j  74  */	{ "LJ", "JEEP" },
+	/*  k  75  */	{ "LK", "TRUCK" },
+	/*  l  76  */	{ "LL", "Laptop" },
+	/*  m  77  */	{ "LM", "Mic-E Repeater" },
+	/*  n  78  */	{ "LN", "Node (black bulls-eye)" },
+	/*  o  79  */	{ "LO", "EOC" },
+	/*  p  80  */	{ "LP", "ROVER (puppy, or dog)" },
+	/*  q  81  */	{ "LQ", "GRID SQ shown above 128 m" },
+	/*  r  82  */	{ "LR", "Repeater" },
+	/*  s  83  */	{ "LS", "SHIP (pwr boat)" },
+	/*  t  84  */	{ "LT", "TRUCK STOP" },
+	/*  u  85  */	{ "LU", "TRUCK (18 wheeler)" },
+	/*  v  86  */	{ "LV", "VAN" },
+	/*  w  87  */	{ "LW", "WATER station" },
+	/*  x  88  */	{ "LX", "xAPRS (Unix)" },
+	/*  y  89  */	{ "LY", "YAGI @ QTH" },
+	/*  z  90  */	{ "LZ", "TBD" },
+	/*  {  91  */	{ "J1", "" },
+	/*  |  92  */	{ "J2", "TNC Stream Switch" },
+	/*  }  93  */	{ "J3", "" },
+	/*  ~  94  */	{ "J3", "TNC Stream Switch" } };
+
+/*
+ * Alternate symbol table.
+ */
+
+static const struct {
+		char xy[3];
+		char *description;
+	} alternate_symtab[SYMTAB_SIZE] = {
+
+	/*     00  */	{ "~~", "--no-symbol--" },
+	/*  !  01  */	{ "OB", "EMERGENCY (!)" },
+	/*  "  02  */	{ "OC", "reserved" },
+	/*  #  03  */	{ "OD", "OVERLAY DIGI (green star)" },
+	/*  $  04  */	{ "OE", "Bank or ATM  (green box)" },
+	/*  %  05  */	{ "OF", "Power Plant with overlay" },
+	/*  &  06  */	{ "OG", "I=Igte IGate R=RX T=1hopTX 2=2hopTX" },
+	/*  '  07  */	{ "OH", "Crash (& now Incident sites)" },
+	/*  (  08  */	{ "OI", "CLOUDY (other clouds w ovrly)" },
+	/*  )  09  */	{ "OJ", "Firenet MEO, MODIS Earth Obs." },
+	/*  *  10  */	{ "OK", "SNOW (& future ovrly codes)" },
+	/*  +  11  */	{ "OL", "Church" },
+	/*  ,  12  */	{ "OM", "Girl Scouts" },
+	/*  -  13  */	{ "ON", "House (H=HF) (O = Op Present)" },
+	/*  .  14  */	{ "OO", "Ambiguous (Big Question mark)" },
+	/*  /  15  */	{ "OP", "Waypoint Destination" },
+	/*  0  16  */	{ "A0", "CIRCLE (E/I/W=IRLP/Echolink/WIRES)" },
+	/*  1  17  */	{ "A1", "" },
+	/*  2  18  */	{ "A2", "" },
+	/*  3  19  */	{ "A3", "" },
+	/*  4  20  */	{ "A4", "" },
+	/*  5  21  */	{ "A5", "" },
+	/*  6  22  */	{ "A6", "" },
+	/*  7  23  */	{ "A7", "" },
+	/*  8  24  */	{ "A8", "802.11 or other network node" },
+	/*  9  25  */	{ "A9", "Gas Station (blue pump)" },
+	/*  :  26  */	{ "NR", "Hail (& future ovrly codes)" },
+	/*  ;  27  */	{ "NS", "Park/Picnic area" },
+	/*  <  28  */	{ "NT", "ADVISORY (one WX flag)" },
+	/*  =  29  */	{ "NU", "APRStt Touchtone (DTMF users)" },
+	/*  >  30  */	{ "NV", "OVERLAYED CAR" },
+	/*  ?  31  */	{ "NW", "INFO Kiosk  (Blue box with ?)" },
+	/*  @  32  */	{ "NX", "HURICANE/Trop-Storm" },
+	/*  A  33  */	{ "AA", "overlayBOX DTMF & RFID & XO" },
+	/*  B  34  */	{ "AB", "Blwng Snow (& future codes)" },
+	/*  C  35  */	{ "AC", "Coast Guard" },
+	/*  D  36  */	{ "AD", "Drizzle (proposed APRStt)" },
+	/*  E  37  */	{ "AE", "Smoke (& other vis codes)" },
+	/*  F  38  */	{ "AF", "Freezng rain (&future codes)" },
+	/*  G  39  */	{ "AG", "Snow Shwr (& future ovrlys)" },
+	/*  H  40  */	{ "AH", "Haze (& Overlay Hazards)" },
+	/*  I  41  */	{ "AI", "Rain Shower" },
+	/*  J  42  */	{ "AJ", "Lightening (& future ovrlys)" },
+	/*  K  43  */	{ "AK", "Kenwood HT (W)" },
+	/*  L  44  */	{ "AL", "Lighthouse" },
+	/*  M  45  */	{ "AM", "MARS (A=Army,N=Navy,F=AF)" },
+	/*  N  46  */	{ "AN", "Navigation Buoy" },
+	/*  O  47  */	{ "AO", "Rocket" },
+	/*  P  48  */	{ "AP", "Parking" },
+	/*  Q  49  */	{ "AQ", "QUAKE" },
+	/*  R  50  */	{ "AR", "Restaurant" },
+	/*  S  51  */	{ "AS", "Satellite/Pacsat" },
+	/*  T  52  */	{ "AT", "Thunderstorm" },
+	/*  U  53  */	{ "AU", "SUNNY" },
+	/*  V  54  */	{ "AV", "VORTAC Nav Aid" },
+	/*  W  55  */	{ "AW", "# NWS site (NWS options)" },
+	/*  X  56  */	{ "AX", "Pharmacy Rx (Apothicary)" },
+	/*  Y  57  */	{ "AY", "Radios and devices" },
+	/*  Z  58  */	{ "AZ", "" },
+	/*  [  59  */	{ "DS", "W.Cloud (& humans w Ovrly)" },
+	/*  \  60  */	{ "DT", "New overlayable GPS symbol" },
+	/*  ]  61  */	{ "DU", "" },
+	/*  ^  62  */	{ "DV", "# Aircraft (shows heading)" },
+	/*  _  63  */	{ "DW", "# WX site (green digi)" },
+	/*  `  64  */	{ "DX", "Rain (all types w ovrly)" },
+	/*  a  65  */	{ "SA", "ARRL, ARES, WinLINK" },
+	/*  b  66  */	{ "SB", "Blwng Dst/Snd (& others)" },
+	/*  c  67  */	{ "SC", "CD triangle RACES/SATERN/etc" },
+	/*  d  68  */	{ "SD", "DX spot by callsign" },
+	/*  e  69  */	{ "SE", "Sleet (& future ovrly codes)" },
+	/*  f  70  */	{ "SF", "Funnel Cloud" },
+	/*  g  71  */	{ "SG", "Gale Flags" },
+	/*  h  72  */	{ "SH", "Store. or HAMFST Hh=HAM store" },
+	/*  i  73  */	{ "SI", "BOX or points of Interest" },
+	/*  j  74  */	{ "SJ", "WorkZone (Steam Shovel)" },
+	/*  k  75  */	{ "SK", "Special Vehicle SUV,ATV,4x4" },
+	/*  l  76  */	{ "SL", "Areas      (box,circles,etc)" },
+	/*  m  77  */	{ "SM", "Value Sign (3 digit display)" },
+	/*  n  78  */	{ "SN", "OVERLAY TRIANGLE" },
+	/*  o  79  */	{ "SO", "small circle" },
+	/*  p  80  */	{ "SP", "Prtly Cldy (& future ovrlys)" },
+	/*  q  81  */	{ "SQ", "" },
+	/*  r  82  */	{ "SR", "Restrooms" },
+	/*  s  83  */	{ "SS", "OVERLAY SHIP/boat (top view)" },
+	/*  t  84  */	{ "ST", "Tornado" },
+	/*  u  85  */	{ "SU", "OVERLAYED TRUCK" },
+	/*  v  86  */	{ "SV", "OVERLAYED Van" },
+	/*  w  87  */	{ "SW", "Flooding" },
+	/*  x  88  */	{ "SX", "Wreck or Obstruction ->X<-" },
+	/*  y  89  */	{ "SY", "Skywarn" },
+	/*  z  90  */	{ "SZ", "OVERLAYED Shelter" },
+	/*  {  91  */	{ "Q1", "Fog (& future ovrly codes)" },
+	/*  |  92  */	{ "Q2", "TNC Stream Switch" },
+	/*  }  93  */	{ "Q3", "" },
+	/*  ~  94  */	{ "Q4", "TNC Stream Switch" } };
+
+
+// Make sure the array is null terminated.
+static const char *search_locations[] = {
+	(const char *) "symbols-new.txt",
+#ifndef __WIN32__
+	(const char *) "/usr/share/direwolf/symbols-new.txt",
+	(const char *) "/usr/local/share/direwolf/symbols-new.txt",
+#endif
+	(const char *) NULL
+};
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_init
+ *
+ * Purpose:	Initialization for functions related to symbols.
+ *
+ * Inputs:	
+ *
+ * Global output:
+ *		new_sym_ptr
+ *		new_sym_size
+ *		new_sym_len
+ *
+ * Description:	The primary and alternate symbol tables are constant
+ *		so they are hardcoded.
+ *		However the "new" sysmbols, which give new meanings to
+ *		overlayed symbols, are always evolving.
+ *		For maximum flexibility, we will read the
+ *		data file at run time rather than compiling it in.
+ *
+ *		For the most recent version, download from:
+ *
+ *		http://www.aprs.org/symbols/symbols-new.txt
+ *
+ *		Windows version:  File must be in current working directory.
+ *
+ *		Linux version: Search order is current working directory
+ *			then /usr/share/direwolf directory.
+ *
+ *------------------------------------------------------------------*/
+
+#define NEW_SYM_INIT_SIZE 20
+#define NEW_SYM_DESC_LEN 29
+
+typedef struct new_sym_s {
+	char overlay;
+	char symbol;
+	char description[NEW_SYM_DESC_LEN+1];
+} new_sym_t;
+
+static new_sym_t *new_sym_ptr = NULL;	/* Dynamically allocated array. */
+static int new_sym_size = 0;		/* Number of elements allocated. */
+static int new_sym_len = 0;			/* Number of elements used. */
+
+
+void symbols_init (void)
+{
+	FILE *fp = NULL;
+
+/*
+ * 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. */
+	}
+
+// If search strategy changes, be sure to keep decode_tocall in sync.
+
+	fp = NULL;
+	j = 0;
+	do {
+	  if (search_locations[j] == NULL) break;
+	  fp = fopen(search_locations[j++], "r");
+	} while (fp == NULL);
+
+	if (fp == NULL) {
+
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Warning: Could not open 'symbols-new.txt'.\n");
+	  dw_printf ("The \"new\" overlayed character information will not be available.\n");
+
+	  new_sym_size = 1;	
+	  new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t));  /* Don't try again. */
+	  new_sym_len = 0;	
+	  return;
+	}
+
+/*
+ * Count number of interesting lines and allocate storage.
+ */
+	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
+	  if (GOOD_LINE(stuff)) {
+	    new_sym_size++;
+	  }
+	}
+
+	new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t));
+
+/*
+ * Rewind, read again, and save contents of interesting lines. 
+ */
+	rewind (fp);
+
+	while (fgets(stuff, sizeof(stuff), fp) != NULL) {
+
+	  if (GOOD_LINE(stuff)) {
+	    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[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++;
+	  }
+	}
+	fclose (fp);
+
+	assert (new_sym_len == new_sym_size);
+
+#if 0
+	for (j=0; j<new_sym_len; j++) {
+	  dw_printf ("%02d: %c %c '%s'\n", j, new_sym_ptr[j].overlay,
+		new_sym_ptr[j].symbol, new_sym_ptr[j].description);
+	}
+#endif
+
+} /* end symbols_init */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_list
+ *
+ * Purpose:	Print a list of all the symbols.
+ *
+ * Inputs:	none
+ *
+ *------------------------------------------------------------------*/
+
+void symbols_list (void) 
+{
+	int n;
+
+	dw_printf ("\n");
+
+	dw_printf ("\tPRIMARY SYMBOL TABLE\n");
+	dw_printf ("\n");
+	dw_printf ("sym  GPSxy  GPSCnn  APRStt  Icon\n");
+	dw_printf ("---  -----  ------  ------  ----\n");
+	for (n = 1; n < SYMTAB_SIZE; n++) {
+	  dw_printf (" /%c     %s      %02d  AB1%02d   %s\n", n + ' ', primary_symtab[n].xy, n, n, primary_symtab[n].description);
+	}
+
+	dw_printf ("\n");
+	dw_printf ("\tALTERNATE SYMBOL TABLE\n");
+	dw_printf ("\n");
+	dw_printf ("sym  GPSxy  GPSEnn  APRStt  Icon\n");
+	dw_printf ("---  -----  ------  ------  ----\n");
+	for (n = 1; n < SYMTAB_SIZE; n++) {
+	  dw_printf (" \\%c     %s      %02d  AB2%02d   %s\n", n + ' ', alternate_symtab[n].xy, n, n, alternate_symtab[n].description);
+	}
+
+	dw_printf ("\n");
+	dw_printf ("\tNEW SYMBOLS from symbols-new.txt\n");
+	dw_printf ("\n");
+	dw_printf ("sym  GPSxyz  GPSxnn  APRStt   Icon\n");
+	dw_printf ("---  ------  ------  ------   ----\n");
+
+
+	for (n = 0; n < new_sym_len; n++) {
+
+	  int overlay = new_sym_ptr[n].overlay;
+	  int symbol = new_sym_ptr[n].symbol;
+	  char tones[12];
+
+	  symbols_to_tones (overlay, symbol, tones, sizeof(tones));
+
+	  if (overlay == '/') {
+
+	    dw_printf (" %c%c     %s%c     C%02d  %-7s  %s\n", overlay, symbol, 
+								primary_symtab[symbol - ' '].xy, ' ',
+								symbol - ' ', tones,
+								new_sym_ptr[n].description);
+	  }
+	  else if (isupper(overlay) || isdigit(overlay)) {
+
+	    dw_printf (" %c%c     %s%c          %-7s  %s\n", overlay, symbol, 
+								alternate_symtab[symbol - ' '].xy, overlay,
+								tones,
+								new_sym_ptr[n].description);
+	  }
+	  else {
+
+	    dw_printf (" %c%c     %s%c     E%02d  %-7s  %s\n", overlay, symbol, 
+								alternate_symtab[symbol - ' '].xy, ' ', 
+								symbol - ' ', tones,
+								new_sym_ptr[n].description);
+	  }
+	}
+	dw_printf ("\n");
+
+
+} /* end symbols_list */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_from_dest_or_src
+ *
+ * Purpose:	Extract symbol from destination or source.
+ *
+ * Inputs:	dti	- Data type indicator.
+ *
+ *		src	- Source address with SSID.
+ *		
+ *		dest	- Destination address.
+ *			  Don't care if SSID is present or not.
+ *
+ * Outputs:	*symtab
+ *		*symbol
+ *
+ * Description:	There are 3 different ways to specify the symbol,
+ *		in this order of precedence:
+ *	
+ *		- Information field.  This was done already in the
+ *		  separate decoders for different message types.
+ *
+ *		If not set already,
+ *
+ *		- The destination address if it has certain formats
+ *		  starting with GPS, SPC, or SYM which are equivalent
+ *		  for our purposes.
+ *		  (Not for MIC-E where destination has a special use.)
+ *
+ *		If all else fails,
+ *
+ *		- SSID of the source address.
+ *
+ *
+ *------------------------------------------------------------------*/
+
+const static char ssid_to_sym[16] = {
+	  ' ',	/* 0 - No icon. */
+	  'a',	/* 1 - Ambulance */
+	  'U',	/* 2 - Bus */
+	  'f',	/* 3 - Fire Truck */
+	  'b',	/* 4 - Bicycle */
+	  'Y',	/* 5 - Yacht */
+	  'X',	/* 6 - Helicopter */
+	  '\'',	/* 7 - Small Aircraft */
+	  's',	/* 8 - Ship */
+	  '>',	/* 9 - Car */
+	  '<',	/* 10 - Motorcycle */
+	  'O',	/* 11 - Ballon */
+	  'j',	/* 12 - Jeep */
+	  'R',	/* 13 - Recreational Vehicle */
+	  'k',	/* 14 - Truck */
+	  'v' 	/* 15 - Van */
+	};
+
+void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol)
+{
+	char *p;
+
+
+/*
+ * This part does not apply to MIC-E format because the destination
+ * is used to encode latitude and other information.
+ */
+	if (dti != '\'' && dti != '`') {
+
+/* 
+ * For GPSCnn, nn is the index into the primary symbol table.
+ */
+
+	  if (strncmp(dest, "GPSC", 4) == 0)
+	  {
+	    int nn;
+	  
+	    nn = atoi(dest+4);
+	    if (nn >= 1 && nn <= 94) {
+	      *symtab = '/';		/* Primary */
+	      *symbol = ' ' + nn;	
+	      return;
+	    }
+	  }
+
+/* 
+ * For GPSEnn, nn is the index into the primary symbol table.
+ */
+
+	  if (strncmp(dest, "GPSE", 4) == 0)
+	  {
+	    int nn;
+	  
+	    nn = atoi(dest+4);
+	    if (nn >= 1 && nn <= 94) {
+	      *symtab = '\\';		/* Alternate. */
+	      *symbol = ' ' + nn;	
+	      return;
+	    }
+	  }
+
+/*
+ * For GPSxy or SPCxy or SYMxy, look up xy in the translation tables.
+ * First search the primary table.
+ */
+
+	  if (strncmp(dest, "GPS", 3) == 0 ||
+	      strncmp(dest, "SPC", 3) == 0 ||
+	      strncmp(dest, "SYM", 3) == 0) 
+	  {
+	    char xy[3];
+	    int nn;
+	  
+	    xy[0] = dest[3];
+	    xy[1] = dest[4];
+	    xy[2] = '\0';
+
+	    for (nn = 1; nn <= 94; nn++) {
+	      if (strcmp(xy, primary_symtab[nn].xy) == 0) {
+	        *symtab = '/';		/* Primary. */
+	        *symbol = ' ' + nn;
+	        return;
+	      }
+	    }
+	  }			
+
+/*
+ * Next, search the alternate table.
+ * This time, we can have the format ...xyz, where z is an overlay character.
+ * Only upper case letters and digits are valid overlay characters.
+ */
+
+	  if (strncmp(dest, "GPS", 3) == 0 ||
+	      strncmp(dest, "SPC", 3) == 0 ||
+	      strncmp(dest, "SYM", 3) == 0) 
+	  {
+	    char xy[3];
+	    char z;
+	    int nn;
+	  
+	    xy[0] = dest[3];
+	    xy[1] = dest[4];
+	    xy[2] = '\0';
+	    z = dest[5];
+
+	    for (nn = 1; nn <= 94; nn++) {
+	      if (strcmp(xy, alternate_symtab[nn].xy) == 0) {
+	        *symtab = '\\';		/* Alternate. */
+	        *symbol = ' ' + nn;
+		if (isupper((int)z) || isdigit((int)z)) {
+	          *symtab = z;
+	        }
+	        return;
+	      }
+	    }
+	  }
+
+	}  /* end not MIC-E */
+
+/*
+ * When all else fails, use source SSID.
+ */
+
+	p = strchr (src, '-');
+	if (p != NULL) 
+	{
+	  int ssid;
+
+	  ssid = atoi(p+1);
+	  if (ssid >= 1 && ssid <= 15) {
+	    *symtab = '/';		/* All in Primary table. */
+	    *symbol = ssid_to_sym[ssid];
+	    return;
+	  }
+	}
+
+} /* symbols_from_dest_or_src */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_into_dest
+ *
+ * Purpose:	Encode symbol for destination field.
+ *
+ * Inputs:	symtab		/, \, 0-9, A-Z
+ *		symbol		any printable character ! to ~ 
+ *
+ * Outputs:	dest		6 character "destination" of the forms 
+ *					GPSCnn  - primary table.
+ *					GPSEnn  - alternate table.
+ *					GPSxyz  - alternate with overlay.
+ *
+ * Returns:	0 for success, 1 for error.
+ *
+ *------------------------------------------------------------------*/
+
+int symbols_into_dest (char symtab, char symbol, char *dest)
+{
+
+	if (symbol >= '!' && symbol <= '~' && symtab == '/') {
+	  
+	  /* Primary Symbol table. */
+	  snprintf (dest, 7, "GPSC%02d", symbol - ' ');
+	  return (0);
+	}
+	else if (symbol >= '!' && symbol <= '~' && symtab == '\\') {
+	  
+	  /* Alternate Symbol table. */
+	  snprintf (dest, 7, "GPSE%02d", symbol - ' ');
+	  return (0);
+	}
+	else if (symbol >= '!' && symbol <= '~' && (isupper(symtab) || isdigit(symtab))) {
+
+	  /* Alternate Symbol table with overlay. */
+	  snprintf (dest, 7, "GPS%s%c", alternate_symtab[symbol - ' '].xy, symtab);
+	  return (0);
+	}
+
+
+	text_color_set(DW_COLOR_ERROR);
+	dw_printf ("Could not convert symbol \"%c%c\" to GPSxyz destination format.\n",
+			symtab, symbol);
+
+	strlcpy (dest, "GPS???", sizeof(dest));	/* Error. */
+	return (1);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_get_description
+ *
+ * Purpose:	Get description for given symbol table/code/overlay.
+ *
+ * Inputs:	symtab		/, \, 0-9, A-Z
+ *		symbol		any printable character ! to ~ 
+ *
+ *		desc_size	Size of description provided by caller
+ *				so we can avoid buffer overflow.
+ *
+ * Outputs:	description	Text description.
+ *				"--no-symbol--"  if error.
+ *
+ *	 
+ * Description:	This is used for the monitoring and the 
+ *		decode_aprs utility.
+ *
+ *------------------------------------------------------------------*/
+
+void symbols_get_description (char symtab, char symbol, char *description, size_t desc_size)
+{
+	char tmp2[2];
+	int j;
+
+	symbols_init();
+
+
+// The symbol table identifier should be 
+//	/	for symbol from primary table
+//	\	for symbol from alternate table
+//	0-9,A-Z	for alternate symbol table with overlay character
+
+	if (symtab != '/' &&
+	    symtab != '\\' &&
+	    ! isdigit(symtab) &&
+	    ! isupper(symtab)) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Symbol table identifier is not '/' (primary), '\\' (alternate), or valid overlay character.\n");
+	
+	  /* Possibilities: */
+	  /* Select primary table and keep going, or */
+	  /* report no symbol.  It IS an error. */
+	  /* We do the latter. */
+
+	  symbol = ' ';
+	  strlcpy (description, primary_symtab[symbol-' '].description, desc_size);
+	  return;
+	}
+
+// Bounds check before using symbol as index into table.
+
+	if (symbol < ' ' || symbol > '~') {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Symbol code is not a printable character.\n");
+	  symbol = ' ';		/* Avoid subscript out of bounds. */	  
+	}
+
+// First try to match with the "new" symbols.
+
+	for (j=0; j<new_sym_len; j++) {
+	  if (symtab == new_sym_ptr[j].overlay && symbol == new_sym_ptr[j].symbol) {
+	    strlcpy (description, new_sym_ptr[j].description, desc_size);
+	    return;
+	  }
+	}  
+
+// Otherwise use the original symbol tables.
+
+	if (symtab == '/') {
+	  strlcpy (description, primary_symtab[symbol-' '].description, desc_size);
+	}
+	else {
+	  strlcpy (description, alternate_symtab[symbol-' '].description, desc_size);
+	  if (symtab != '\\') {
+	    strlcat (description, " w/overlay ", desc_size);
+	    tmp2[0] = symtab;
+	    tmp2[1] = '\0';
+	    strlcat (description, tmp2, desc_size);
+	  }
+	}
+
+} /* end symbols_get_description */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_code_from_description
+ *
+ * Purpose:	Find a suitable table/symbol based on given description.
+ *
+ * Inputs:	overlay		Explicit overlay or space.
+ *		description	Substring of one of the descriptions.
+ *				Example: dog
+ *
+ * Outputs:	symtab		/, \, 0-9, A-Z
+ *		symbol		any printable character ! to ~ 
+ *
+ * Returns:	1 for success, 0 for failure.
+ *
+ *------------------------------------------------------------------*/
+
+int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol)
+{
+	int j;
+
+	symbols_init();
+
+/*
+ * If user specified a particular overlay (i.e. for config file BEACON), 
+ * first try the alternate symbol table.
+ * If that fails should we give up or ignore the overlay and keep trying?
+ */
+
+	if (isupper(overlay) || isdigit(overlay)) {
+
+	  for (j=0; j<SYMTAB_SIZE; j++) {
+	    if (strcasestr(alternate_symtab[j].description, description) != NULL) {
+	      *symtab = overlay;
+	      *symbol = j + ' ';
+	      return (1);
+	    }
+	  }
+	  /* If that fails should we give up or ignore the overlay and keep trying? */
+	}
+
+/*
+ * Search primary table.
+ */
+	for (j=0; j<SYMTAB_SIZE; j++) {
+	  if (strcasestr(primary_symtab[j].description, description) != NULL) {
+	    *symtab = '/';
+	    *symbol = j + ' ';
+	    return (1);
+	  }
+	}
+
+/*
+ * Search alternate table.
+ */
+	for (j=0; j<SYMTAB_SIZE; j++) {
+	  if (strcasestr(alternate_symtab[j].description, description) != NULL) {
+	    *symtab = '\\';
+	    *symbol = j + ' ';
+	    return (1);
+	  }
+	}
+
+/*
+ * Finally, the "new" symbols.
+ * Probably want this last so get get the most standard and
+ * generic for queries such as "house" or ...
+ */
+	for (j=0; j<new_sym_len; j++) {
+	  if (strcasestr(new_sym_ptr[j].description, description) != NULL) {
+	    *symtab = new_sym_ptr[j].overlay;
+	    *symbol = new_sym_ptr[j].symbol;
+	    return (1);
+	  }
+	}
+
+/*
+ * Default to something generic like house.  
+ * Caller is responsible for issuing error message.
+ */
+	*symtab = '/';
+	*symbol = '-';
+	return (0);
+
+} /* end symbols_code_from_description */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function:	symbols_to_tones
+ *
+ * Purpose:	Convert symbol to APRStt tone sequence.
+ *
+ * Inputs:	symtab/overlay
+ *		symbol
+ *		tonessiz	- Amount of space available for result.
+ *
+ * Output:	tones	- string of AB...		
+ *		
+ * Description: 
+ *
+ *		Primary: 	AB1nn		nn = same number as GPSCnn
+ *		Alternate:	AB2nn 		nn = same number as GPSEnn
+ *		with overlay:	AB0nntt   	nn = same as with alternate
+ *						tt = one or two tones from two key method.
+ *
+ *------------------------------------------------------------------*/
+
+void symbols_to_tones (char symtab, char symbol, char *tones, size_t tonessiz)
+{
+
+	if (symtab == '/') {
+
+	  snprintf (tones, tonessiz, "AB1%02d", symbol - ' ');
+	}
+	else if (isupper(symtab) || isdigit(symtab)) {
+
+	  char text[2];
+	  char tt[8];
+
+	  text[0] = symtab;
+	  text[1] = '\0';
+
+	  tt_text_to_two_key (text, 0, tt);
+
+	  snprintf (tones, tonessiz, "AB0%02d%s", symbol - ' ', tt);
+	}
+	else {
+	 
+	  snprintf (tones, tonessiz, "AB2%02d", symbol - ' ');
+	}
+
+}  /* end symbols_to_tones */
+
+
+
+
+#if 0
+
+/* Quick, incomplete, unit test. */
+/* gcc -g symbols.c textcolor.c misc.a */
+
+int main (int argc, char *argv[])
+{
+	char symtab;
+	char symbol;
+	char dest[8];
+	char description[50];
+
+	symbols_init ();
+
+
+
+	symbols_from_dest_or_src ('T', "W1ABC", "GPSC43", &symtab, &symbol);
+	if (symtab != '/' || symbol != 'K') dw_printf ("ERROR 1-1\n");
+
+	symbols_from_dest_or_src ('T', "W1ABC", "GPSE87", &symtab, &symbol);
+	if (symtab != '\\' || symbol != 'w') dw_printf ("ERROR 1-2\n");
+
+	symbols_from_dest_or_src ('T', "W1ABC", "SPCBL", &symtab, &symbol);
+	if (symtab != '/' || symbol != '+') dw_printf ("ERROR 1-3\n");
+
+	symbols_from_dest_or_src ('T', "W1ABC", "SYMST", &symtab, &symbol);
+	if (symtab != '\\' || symbol != 't') dw_printf ("ERROR 1-4\n");
+
+	symbols_from_dest_or_src ('T', "W1ABC", "GPSOD9", &symtab, &symbol);
+	if (symtab != '9' || symbol != '#') dw_printf ("ERROR 1-5\n");
+
+	symbols_from_dest_or_src ('T', "W1ABC-14", "XXXXXX", &symtab, &symbol);
+	if (symtab != '/' || symbol != 'k') dw_printf ("ERROR 1-6\n");
+
+	symbols_from_dest_or_src ('T', "W1ABC", "GPS???", &symtab, &symbol);
+	/* Outputs are left alone if symbol can't be determined. */
+	if (symtab != '/' || symbol != 'k') dw_printf ("ERROR 1-7\n");
+
+
+	symbols_into_dest ('/', 'K', dest);
+	if (strcmp(dest, "GPSC43") != 0) dw_printf ("ERROR 2-1\n");
+
+	symbols_into_dest ('\\', 'w', dest);
+	if (strcmp(dest, "GPSE87") != 0) dw_printf ("ERROR 2-2\n");
+
+	symbols_into_dest ('3', 'A', dest);
+	if (strcmp(dest, "GPSAA3") != 0) dw_printf ("ERROR 2-3\n");
+
+// Expect to see this:
+//   Could not convert symbol " A" to GPSxyz destination format.
+//   Could not convert symbol "/ " to GPSxyz destination format.
+
+	symbols_into_dest (' ', 'A', dest);
+	if (strcmp(dest, "GPS???") != 0) dw_printf ("ERROR 2-4\n");
+
+	symbols_into_dest ('/', ' ', dest);
+	if (strcmp(dest, "GPS???") != 0) dw_printf ("ERROR 2-5\n");
+
+
+
+	symbols_get_description ('J', 's', description);
+	if (strcmp(description, "Jet Ski") != 0) dw_printf ("ERROR 3-1\n");
+
+	symbols_get_description ('/', 'O', description);
+	if (strcmp(description, "BALLOON") != 0) dw_printf ("ERROR 3-2\n");
+
+	symbols_get_description ('\\', 'T', description);
+	if (strcmp(description, "Thunderstorm") != 0) dw_printf ("ERROR 3-3\n");
+
+	symbols_get_description ('5', 'T', description);
+	if (strcmp(description, "Thunderstorm w/overlay 5") != 0) dw_printf ("ERROR 3-4\n");
+
+// Expect to see this:
+//   Symbol table identifier is not '/' (primary), '\' (alternate), or valid overlay character.
+
+	symbols_get_description (' ', 'T', description);
+	if (strcmp(description, "--no-symbol--") != 0) dw_printf ("ERROR 3-5\n");
+
+	symbols_get_description ('/', ' ', description);
+	if (strcmp(description, "--no-symbol--") != 0) dw_printf ("ERROR 3-6\n");
+
+
+
+	symbols_code_from_description ('5', "girl scouts", &symtab, &symbol);
+	if (symtab != '5' || symbol != ',') dw_printf ("ERROR 4-1\n");
+
+	symbols_code_from_description (' ', "scouts", &symtab, &symbol);
+	if (symtab != '/' || symbol != ',') dw_printf ("ERROR 4-2\n");
+
+	symbols_code_from_description (' ', "girl scouts", &symtab, &symbol);
+	if (symtab != '\\' || symbol != ',') dw_printf ("ERROR 4-3\n");
+
+	symbols_code_from_description (' ', "jet ski", &symtab, &symbol);
+	if (symtab != 'J' || symbol != 's') dw_printf ("ERROR 4-4\n");
+
+	symbols_code_from_description (' ', "girl scouts", &symtab, &symbol);
+	if (symtab != '\\' || symbol != ',') dw_printf ("ERROR 4-5\n");
+
+	symbols_code_from_description (' ', "yen", &symtab, &symbol);
+	if (symtab != 'Y' || symbol != '$') dw_printf ("ERROR 4-6\n");
+
+	symbols_code_from_description (' ', "taco bell", &symtab, &symbol);
+	if (symtab != 'T' || symbol != 'R') dw_printf ("ERROR 4-7\n");
+
+
+} /* end main */
+
+#endif
+
 /* end symbols.c */
\ No newline at end of file
diff --git a/symbols.h b/symbols.h
index cf2eca3..5ed91ad 100644
--- a/symbols.h
+++ b/symbols.h
@@ -1,14 +1,19 @@
-
-/* symbols.h */
-
-void symbols_init (void);
-
-void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol);
-
-int symbols_into_dest (char symtab, char symbol, char *dest);
-
-void symbols_get_description (char symtab, char symbol, char *description);
-
-int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol);
-
-/* end symbols.h */
+
+/* symbols.h */
+
+void symbols_init (void);
+
+void symbols_list (void);
+
+void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol);
+
+int symbols_into_dest (char symtab, char symbol, char *dest);
+
+void symbols_get_description (char symtab, char symbol, char *description, size_t desc_size);
+
+int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol);
+
+void symbols_to_tones (char symtab, char symbol, char *tones, size_t tonessize);
+
+
+/* end symbols.h */
diff --git a/symbolsX.txt b/symbolsX.txt
index 6f086fa..2b54b15 100644
--- a/symbolsX.txt
+++ b/symbolsX.txt
@@ -1,384 +1,387 @@
-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 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 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"
-added to indicate that additonal definitions exist in the above 
-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://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
-20 Oct 09: Added WIRES to overlayed circles
-05 Dec 08: Clarified symbols classified as MOBILE.  
-           Added Primary: 123456789\ and ALT: Y[\
-04 Nov 08: Added notes to match file with New Overlays noted above
-           Added list of "just-mobile-symbols"
-25 Mar 08: Added \% Power plants with Overlays.  Changed
-           \i from "BOXn digi" to generic "BOX with overlay".
-           An indoor BOXn digi can use an overlay on existing digi.
-13 Feb 08: Added overlay "S" on \a and \c for SATERN
-10 Jan 08: Added Overlay "X" on BOX for the XO OLPC laptop
-01 Oct 07: Added the above paragraphs about Overlay byte extensions 
-           of the APRS symbol set and defined two new symbols.  
-           Added "S[" Skier and "O-" for House with Operator Present.
-03 Jul 07: Began defining some overlay common usages.
-09 Apr 07: First proposed expanding the alternate symbols overlays.
-           And added to the APRS1.2 addendum:
-           http://www.ew.usna.edu/~bruninga/aprs/symbols-overlays.txt
-02 Feb 07: Cleaned up some  names for consistency with Icon tables
-18 Oct 06: Suggest \x for car wreck or road obstruction. Looks 
-           like a big X like this:   ->X<-   sort-of...
-16 Jun 06: Suggest I for 2-way IGate and R overlay for RX only.
-28 Sep 05: Added /F for Farm vehicle (looks like a tractor)
-18 Jan 05: Added C overlay for CERTS to "\c" symbol
- 3 Jan 05: Added W overlay to "\a" symbol to indicate WinLINK.
- 7 Dec 04: the /] PBBS symbol (typically a blue Mail Box) is renamed 
-             as "MAIL/P.O.". PBBS users should use the BBS symbol.
- 8 Sep 04: Added Military Affiliate MARS symbol as \M with overlays
-
-SYMBOLS for APRS 1.1 ADDENDUM are as below.  As of 26 July 04, the
-symbols below were approved and became part of the APRS1.1 addendum.
-
-JunJul 04 to add a Rocket "\O" and an SUV as "\k"
-06 May 04 to move Shelter(overlay) from PRI to ALT table
- 5 Jan 04 to add Destination Waypoint "\/")
-29 Oct 03 to add 802.11, firenet, IncidentCommandPost & Shelter
-
-
-04 Feb 04:  Unassigned symbols should display the international symbol
-            of a circle with a slash through it.  Meaning "not"...
-
-29 Jan 04:  Reviewed ALL symbols in the spec.  Here are all additions:
-   \& = is not just HF, but ANY GATEWAY with overlay character            
-   /) = Wheelchair (Handicapped) useful in Marathons (blue and white)
-   \) = Firenet MEO symbol (MODIS Earth observation)
-   \/ = Waypoint (destination) a RED dot showing intended destination. 
-        Uses special processing to draw a line from a mobile to his
-        destination.  This was proposed 5 Jan 2004
-   /L = Logged-ON user.  (A PC symbol showing someone on APRS-IS)
-   /l = Laptop (lower case L) (looks like a laptop)
-   /c = Incident Command Post
-   \y = Skywarn (black tornado, orange background with white surround)
-   \z = Shelter (with overlay) (A red house with peaked roof)
-
-JUST-MOBILE-SYMBOLS:  The following two lists of symbols were defined
-as "mobile" symbols for the purposess of filtering etc.  This list
-has been published in APRS1.1 for over a decade.  As of Nov08, this
-list was reviewd and updated:
-
-WAS:
-Pri:  '<=>()*0COPRSUXY[^abefgjkpsuv
-Alt:  /0>AKOS[^knsuv
-
-IS NOW:
-Pri:  !'<=>()*0123456789CFOPRSUXY[\^abefgjkpsuv  <== [added !F\ ]
-Alt:  >KOSY[^ksuv\                               <==[removed /0An]
-
-
-
-SYMBOLS.TXT      APRS DISPLAY SYMBOLS             APRSdos    ORIGINAL
-======================================================================
-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>
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The character after the latitude "N/S" in an APRS position report is a 
-TABLE character and the character following the longitude "E/W" is the 
-APRS symbol character.  The TABLE character selects one of two symbol 
-tables or may be used as an alphanumeric overlay over some symbols:
-
-        TABLE    RESULT            
-         &       RESERVED for possible AUXILLIARY tables (Aug 09)
-         /       Primary   symbol Table  (Mostly stations)
-         \       Alternate symbol table  (Mostly Objects)
-         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 "/"
-and symbol character "$".
-
-Press F1-S in APRSdos to see these symbols.  Some symbols may have a
-numeric overlay character 0-9 or A-Z.  These are shown on the F1-S
-display with the numeral "3" overlayed.  The original overlayable 
-symbols through Oct 2007 were:
-
-  CIRCLE, SQUARE, CAR, TRUCK, VAN, DIGIS, GATES
-  Civil-Defense(RACES), NWS sites, WX stations, Triangle
-
-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
-unique application, the symbol can be designated in the UNPROTO 
-TOADDRESS of the form GPSxyz.  The X points to a subgroup table and 
-the Y is the actual symbol.  Z is an overlay character if used.   
-Actually there are four TOCALLS that will provide the same symbol.
-
-   GPSxyz is for stand alone trackers
-   SPCxyz is for stand alone trackers at SPECIAL events in SPCL mode
-   SYMxyz is for other TNC-only stations such as WX stations
-
-Notice that both the /$ method and GPSxyz method have a one-for-one
-corrspondence for all numeric and alphabetic symbols of both upper 
-and lower case which should make them easy to remember.  For the 
-GPSxyz method, we have broken the PRIMARY and ALTERNATE tables into 
-sub tables with different names to make them easier to remember.  For 
-example, "/C" is a CANOE in the PRIMARY table which can be referred to 
-as PC in the XYZ format and the "\C" ALTERNATE table symbol is for 
-Coast Guard which could also be referred to in the GPSxyz format as 
-AC.   Simillarly, you can think  of the lower case symbols /c or \c as 
-being LC for lower case C and SC for "secondary" table "c".
-
-The Following Symbol table shows the two types of identifiers for all
-APRS ICONS.  The primary symbols are on the left and the alternate 
-table is on the right.  The first column of each is labeled /$ and \$ 
-for the primary and alternate tables.  These are the chacacters you 
-will see in an APRS formatted position report.  The XYZ columns are 
-for the stand-alone trackers described above.
-
-/$ XYZ BASIC SYMBOL TABLE        \$  XYZ OTHER SYMBOL TABLE (\)
--- --- ------------------------  --  --- ----------------------
-/! 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  \(  OIO CLOUDY (other clouds w ovrly)
-/) BJ  Wheelchair (handicapped)  \)  OJO Firenet MEO, MODIS Earth Obs.
-/* 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)
-/. BO  X                         \.  OO  Ambiguous (Big Question mark)
-// BP  Red Dot                   \/  OP  Waypoint Destination
-                                          See APRSdos MOBILE.txt
-
-/$ XYZ PRIMARY SYMBOL TABLE      \$  XYZ ALTERNATE SYMBOL TABLE (\)
--- --- ------------------------  --  --- --------------------------
-/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  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 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  AVAIL (BlwngSnow ==> E ovly B
-/C PC  Canoe                     \C  AC  Coast Guard          
-/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  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  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                   
-/S PS  SHUTTLE                   \S  AS  Satellite/Pacsat
-/T PT  SSTV                      \T  AT  Thunderstorm        
-/U PU  BUS            (SSID = 2) \U  AU  SUNNY                       
-/V PV  ATV                       \V  AV  VORTAC Nav Aid              
-/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  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  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,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)
-/f LF  FIRE TRUCK    (SSID = 3)  \f  SF  Funnel Cloud                
-/g LG  Glider                    \g  SG  Gale Flags                     
-/h LH  HOSPITAL                  \h  SHO Store. or HAMFST Hh=HAM store
-/i LI  IOTA (islands on the air) \i  SI# BOX or points of Interest
-/j LJ  JEEP          (SSID-12)   \j  SJ  WorkZone (Steam Shovel)
-/k LK  TRUCK         (SSID = 14) \k  SKO Special Vehicle SUV,ATV,4x4
-/l LL  Laptop (Jan 03)  (Feb 07) \l  SL  Areas      (box,circles,etc)
-/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  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/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  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  AVAIL? (Fog ==> E ovly F)
-/| J2  TNC Stream Switch         \|  Q2  TNC Stream Switch
-/} 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.  Now All symbols should be oriented (if practical).  These 
-original special symbols were:
- 
-\> OVERLAYED CAR
-\s Overlayed Ship
-\^ Overlayed Aircraft
-/^ Aircraft
-/g Glider
-\n Overlayed Triangle
-
-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 
-lower point can be to the left of the beginning point.  Further, in 
-the line option you may specify a "width" either side of the central 
-line.
-
-These AREAS are useful for real-time events such as for a search-and-
-rescue, or adding a special ROAD or ROUTE for a special event.  Be 
-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: 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
-                                          add 5 to these => color-in
-                     C is color from 0 to 15.  For colors geater than
-                       9, / is replaced with a 1.
-                    yy is sqroot of the latitude offset in 1/100ths
-                    xx is sqroot of the longitude offset
-                       
-These offsets are ALWAYS positive to the right and down, except for 
-the special case of a lower right quadrant line, these are given the 
-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
-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 
-label be a # followed by the dual symbol character, followed by a hex 
-number from 1 to F that indicates the color for the symbol.  The 
-remaining 8 characters can be used for a conventional label at the 
-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:  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
-objects when traffic slows in these areas.  To avoid cluttering the 
-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 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 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: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
-in this manner.  But special overlays on some special characters
-may also be re-drawn as entirely new graphics for clarity if desired.
-
-The above NEW-Overlay document attempts to define those special
-combinations that may rate their own special symbol or where multiple
-use of an overlay character for different purposes would be confusing.
-
-Bob, WB4APR
+APRS SYMBOLS (Icons)                                        25 Nov 2015
+-----------------------------------------------------------------------
+                                                                 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 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 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"
+added to indicate that additonal definitions exist in the above 
+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://aprs.org/symbols/symbols-background.txt
+
+UPDATE CHRONOLOGY:
+
+25 Nov 15: Found APRStt symbol poorly documented  Was shown as "\=". 
+           But has been \A BOX symbol with variety of overlays
+23 Jun 15: Changed Aircraft to SSID-11 and Human to SSID-7
+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
+20 Oct 09: Added WIRES to overlayed circles
+05 Dec 08: Clarified symbols classified as MOBILE.  
+           Added Primary: 123456789\ and ALT: Y[\
+04 Nov 08: Added notes to match file with New Overlays noted above
+           Added list of "just-mobile-symbols"
+25 Mar 08: Added \% Power plants with Overlays.  Changed
+           \i from "BOXn digi" to generic "BOX with overlay".
+           An indoor BOXn digi can use an overlay on existing digi.
+13 Feb 08: Added overlay "S" on \a and \c for SATERN
+10 Jan 08: Added Overlay "X" on BOX for the XO OLPC laptop
+01 Oct 07: Added the above paragraphs about Overlay byte extensions 
+           of the APRS symbol set and defined two new symbols.  
+           Added "S[" Skier and "O-" for House with Operator Present.
+03 Jul 07: Began defining some overlay common usages.
+09 Apr 07: First proposed expanding the alternate symbols overlays.
+           And added to the APRS1.2 addendum:
+           http://www.ew.usna.edu/~bruninga/aprs/symbols-overlays.txt
+02 Feb 07: Cleaned up some  names for consistency with Icon tables
+18 Oct 06: Suggest \x for car wreck or road obstruction. Looks 
+           like a big X like this:   ->X<-   sort-of...
+16 Jun 06: Suggest I for 2-way IGate and R overlay for RX only.
+28 Sep 05: Added /F for Farm vehicle (looks like a tractor)
+18 Jan 05: Added C overlay for CERTS to "\c" symbol
+ 3 Jan 05: Added W overlay to "\a" symbol to indicate WinLINK.
+ 7 Dec 04: the /] PBBS symbol (typically a blue Mail Box) is renamed 
+             as "MAIL/P.O.". PBBS users should use the BBS symbol.
+ 8 Sep 04: Added Military Affiliate MARS symbol as \M with overlays
+
+SYMBOLS for APRS 1.1 ADDENDUM are as below.  As of 26 July 04, the
+symbols below were approved and became part of the APRS1.1 addendum.
+
+JunJul 04 to add a Rocket "\O" and an SUV as "\k"
+06 May 04 to move Shelter(overlay) from PRI to ALT table
+ 5 Jan 04 to add Destination Waypoint "\/")
+29 Oct 03 to add 802.11, firenet, IncidentCommandPost & Shelter
+
+
+04 Feb 04:  Unassigned symbols should display the international symbol
+            of a circle with a slash through it.  Meaning "not"...
+
+29 Jan 04:  Reviewed ALL symbols in the spec.  Here are all additions:
+   \& = is not just HF, but ANY GATEWAY with overlay character            
+   /) = Wheelchair (Handicapped) useful in Marathons (blue and white)
+   \) = Firenet MEO symbol (MODIS Earth observation)
+   \/ = Waypoint (destination) a RED dot showing intended destination. 
+        Uses special processing to draw a line from a mobile to his
+        destination.  This was proposed 5 Jan 2004
+   /L = Logged-ON user.  (A PC symbol showing someone on APRS-IS)
+   /l = Laptop (lower case L) (looks like a laptop)
+   /c = Incident Command Post
+   \y = Skywarn (black tornado, orange background with white surround)
+   \z = Shelter (with overlay) (A red house with peaked roof)
+
+JUST-MOBILE-SYMBOLS:  The following two lists of symbols were defined
+as "mobile" symbols for the purposess of filtering etc.  This list
+has been published in APRS1.1 for over a decade.  As of Nov08, this
+list was reviewd and updated:
+
+WAS:
+Pri:  '<=>()*0COPRSUXY[^abefgjkpsuv
+Alt:  /0>AKOS[^knsuv
+
+IS NOW:
+Pri:  !'<=>()*0123456789CFOPRSUXY[\^abefgjkpsuv  <== [added !F\ ]
+Alt:  >KOSY[^ksuv\                               <==[removed /0An]
+
+
+
+SYMBOLS.TXT      APRS DISPLAY SYMBOLS             APRSdos    ORIGINAL
+======================================================================
+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>
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The character after the latitude "N/S" in an APRS position report is a 
+TABLE character and the character following the longitude "E/W" is the 
+APRS symbol character.  The TABLE character selects one of two symbol 
+tables or may be used as an alphanumeric overlay over some symbols:
+
+        TABLE    RESULT            
+         &       RESERVED for possible AUXILLIARY tables (Aug 09)
+         /       Primary   symbol Table  (Mostly stations)
+         \       Alternate symbol table  (Mostly Objects)
+         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 "/"
+and symbol character "$".
+
+Press F1-S in APRSdos to see these symbols.  Some symbols may have a
+numeric overlay character 0-9 or A-Z.  These are shown on the F1-S
+display with the numeral "3" overlayed.  The original overlayable 
+symbols through Oct 2007 were:
+
+  CIRCLE, SQUARE, CAR, TRUCK, VAN, DIGIS, GATES
+  Civil-Defense(RACES), NWS sites, WX stations, Triangle
+
+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
+unique application, the symbol can be designated in the UNPROTO 
+TOADDRESS of the form GPSxyz.  The X points to a subgroup table and 
+the Y is the actual symbol.  Z is an overlay character if used.   
+Actually there are four TOCALLS that will provide the same symbol.
+
+   GPSxyz is for stand alone trackers
+   SPCxyz is for stand alone trackers at SPECIAL events in SPCL mode
+   SYMxyz is for other TNC-only stations such as WX stations
+
+Notice that both the /$ method and GPSxyz method have a one-for-one
+corrspondence for all numeric and alphabetic symbols of both upper 
+and lower case which should make them easy to remember.  For the 
+GPSxyz method, we have broken the PRIMARY and ALTERNATE tables into 
+sub tables with different names to make them easier to remember.  For 
+example, "/C" is a CANOE in the PRIMARY table which can be referred to 
+as PC in the XYZ format and the "\C" ALTERNATE table symbol is for 
+Coast Guard which could also be referred to in the GPSxyz format as 
+AC.   Simillarly, you can think  of the lower case symbols /c or \c as 
+being LC for lower case C and SC for "secondary" table "c".
+
+The Following Symbol table shows the two types of identifiers for all
+APRS ICONS.  The primary symbols are on the left and the alternate 
+table is on the right.  The first column of each is labeled /$ and \$ 
+for the primary and alternate tables.  These are the chacacters you 
+will see in an APRS formatted position report.  The XYZ columns are 
+for the stand-alone trackers described above.
+
+/$ XYZ BASIC SYMBOL TABLE        \$  XYZ OTHER SYMBOL TABLE (\)
+-- --- ------------------------  --  --- ----------------------
+/! 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-11)  \'  OHO Crash (& now Incident sites)
+/( BI  Mobile Satellite Station  \(  OIO CLOUDY (other clouds w ovrly)
+/) BJ  Wheelchair (handicapped)  \)  OJO Firenet MEO, MODIS Earth Obs.
+/* 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)
+/. BO  X                         \.  OO  Ambiguous (Big Question mark)
+// BP  Red Dot                   \/  OP  Waypoint Destination
+                                          See APRSdos MOBILE.txt
+
+/$ XYZ PRIMARY SYMBOL TABLE      \$  XYZ ALTERNATE SYMBOL TABLE (\)
+-- --- ------------------------  --  --- --------------------------
+/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  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 avail. symbol overlay group
+/> 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  AVAIL (BlwngSnow ==> E ovly B
+/C PC  Canoe                     \C  AC  Coast Guard          
+/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  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  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                   
+/S PS  SHUTTLE                   \S  AS  Satellite/Pacsat
+/T PT  SSTV                      \T  AT  Thunderstorm        
+/U PU  BUS            (SSID-2)   \U  AU  SUNNY                       
+/V PV  ATV                       \V  AV  VORTAC Nav Aid              
+/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  AVAIL
+/[ HS  Human/Person   (SSID-7)   \[  DSO W.Cloud (& humans w Ovrly)
+/\ HT  TRIANGLE(DF station)      \\  DTO New overlayable GPS symbol
+/] 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,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)
+/f LF  FIRE TRUCK    (SSID-3)    \f  SF  Funnel Cloud                
+/g LG  Glider                    \g  SG  Gale Flags                     
+/h LH  HOSPITAL                  \h  SHO Store. or HAMFST Hh=HAM store
+/i LI  IOTA (islands on the air) \i  SI# BOX or points of Interest
+/j LJ  JEEP          (SSID-12)   \j  SJ  WorkZone (Steam Shovel)
+/k LK  TRUCK         (SSID-14)   \k  SKO Special Vehicle SUV,ATV,4x4
+/l LL  Laptop (Jan 03)  (Feb 07) \l  SL  Areas      (box,circles,etc)
+/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  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/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  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  AVAIL? (Fog ==> E ovly F)
+/| J2  TNC Stream Switch         \|  Q2  TNC Stream Switch
+/} 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.  Now All symbols should be oriented (if practical).  These 
+original special symbols were:
+ 
+\> OVERLAYED CAR
+\s Overlayed Ship
+\^ Overlayed Aircraft
+/^ Aircraft
+/g Glider
+\n Overlayed Triangle
+
+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 
+lower point can be to the left of the beginning point.  Further, in 
+the line option you may specify a "width" either side of the central 
+line.
+
+These AREAS are useful for real-time events such as for a search-and-
+rescue, or adding a special ROAD or ROUTE for a special event.  Be 
+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: 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
+                                          add 5 to these => color-in
+                     C is color from 0 to 15.  For colors geater than
+                       9, / is replaced with a 1.
+                    yy is sqroot of the latitude offset in 1/100ths
+                    xx is sqroot of the longitude offset
+                       
+These offsets are ALWAYS positive to the right and down, except for 
+the special case of a lower right quadrant line, these are given the 
+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
+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 
+label be a # followed by the dual symbol character, followed by a hex 
+number from 1 to F that indicates the color for the symbol.  The 
+remaining 8 characters can be used for a conventional label at the 
+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:  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
+objects when traffic slows in these areas.  To avoid cluttering the 
+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 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 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: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
+in this manner.  But special overlays on some special characters
+may also be re-drawn as entirely new graphics for clarity if desired.
+
+The above NEW-Overlay document attempts to define those special
+combinations that may rate their own special symbol or where multiple
+use of an overlay character for different purposes would be confusing.
+
+Bob, WB4APR
diff --git a/telemetry-toolkit/telem-balloon.conf b/telemetry-toolkit/telem-balloon.conf
new file mode 100644
index 0000000..666bb75
--- /dev/null
+++ b/telemetry-toolkit/telem-balloon.conf
@@ -0,0 +1,55 @@
+# Sample configuration for demonstration of sending telemetry.
+# Here we try to replicate actual data heard for a balloon.
+
+CHANNEL 0
+MYCALL M0XER-3
+
+# These will send the beacons to the transmitter (which you disconnected, right?)
+
+# First the metadata.
+
+# Channel 1: Battery voltage, Volts, scaled up by 100
+# Channel 2: Solar voltage, Volts, scaled up by 100
+# Channel 3: Temperature, degrees C, sent as Kelvin x 10
+# Channel 4: Number of satellites, no units
+
+# Note: When using Strawberry perl, as specified in the example, Windows knows
+# that the .pl file type is associated with it.  When using a different implementation
+# of perl, which doesn't make this association of file type to application, it might
+# be necessary to use something like this instead:
+#
+#                          ... infocmd="c:\strawberry\perl\bin\perl.exe telem-parm.pl M0XER-3 Vbat Vsolar Temp Sat"
+
+# Here we use the generic scripts to generate the messages with metadata.
+# The "infocmd=..." option means use the result for the info part of the packet.
+
+CBEACON delay=0:10 every=1:00 infocmd="telem-parm.pl M0XER-3 Vbat Vsolar Temp Sat"
+CBEACON delay=0:12 every=1:00 infocmd="telem-unit.pl M0XER-3 V V C """" m"
+CBEACON delay=0:14 every=1:00 infocmd="telem-eqns.pl M0XER-3 0 0.001 0 0 0.001 0 0 0.1 -273.2 0 1 0 0 1 0"
+CBEACON delay=0:16 every=1:00 infocmd="telem-bits.pl M0XER-3 11111111 ""10mW research balloon"""
+
+# Now the telemetry data.
+# In a real situation, the location and telemetry data would come from sensors.
+# Here we have just hardcoded 3 sets of historical data as a demonstration.
+
+# telem-balloon.pl accumulates the data then invokes telem-data91.pl to convert
+# it to the compressed format.  This is inserted into the position comment with "commentcmd=..."
+
+PBEACON compress=1 delay=0:20 every=1:00 via=WIDE2-1 symbol=Balloon lat=61^34.2876N lon=155^40.0931W alt=12953 commentcmd="telem-balloon.pl 3307 4.383 0.436 -34.6 12"
+PBEACON compress=1 delay=0:22 every=1:00 via=WIDE2-1 symbol=Balloon lat=51^07.4402N lon=124^14.4472W alt=12563 commentcmd="telem-balloon.pl 6524 4.515 0.653 -1.3 7"
+PBEACON compress=1 delay=0:24 every=1:00 via=WIDE2-1 symbol=Balloon lat=55^58.5558N lon=122^28.5933W alt=12680 commentcmd="telem-balloon.pl 7458 4.521 0.587 -8.3 7"
+
+
+# Now we do the same thing again.
+
+# This time, add the SENDTO=R0 option to simulate reception.
+# These will be sent to any attached applications so you can see how they process the data.
+
+CBEACON SENDTO=R0 delay=0:30 every=1:00 infocmd="telem-parm.pl M0XER-3 Vbat Vsolar Temp Sat"
+CBEACON SENDTO=R0 delay=0:32 every=1:00 infocmd="telem-unit.pl M0XER-3 V V C """" m"
+CBEACON SENDTO=R0 delay=0:34 every=1:00 infocmd="telem-eqns.pl M0XER-3 0 0.001 0 0 0.001 0 0 0.1 -273.2 0 1 0 0 1 0"
+CBEACON SENDTO=R0 delay=0:36 every=1:00 infocmd="telem-bits.pl M0XER-3 11111111 ""10mW research balloon"""
+
+PBEACON SENDTO=R0 compress=1 delay=0:40 every=1:00 via=WIDE2-1 symbol=Balloon lat=61^34.2876N lon=155^40.0931W alt=12953 commentcmd="telem-balloon.pl 3307 4.383 0.436 -34.6 12"
+PBEACON SENDTO=R0 compress=1 delay=0:42 every=1:00 via=WIDE2-1 symbol=Balloon lat=51^07.4402N lon=124^14.4472W alt=12563 commentcmd="telem-balloon.pl 6524 4.515 0.653 -1.3 7"
+PBEACON SENDTO=R0 compress=1 delay=0:44 every=1:00 via=WIDE2-1 symbol=Balloon lat=55^58.5558N lon=122^28.5933W alt=12680 commentcmd="telem-balloon.pl 7458 4.521 0.587 -8.3 7"
diff --git a/telemetry-toolkit/telem-balloon.pl b/telemetry-toolkit/telem-balloon.pl
new file mode 100644
index 0000000..ed54aff
--- /dev/null
+++ b/telemetry-toolkit/telem-balloon.pl
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+# In a real situation this would obtain data from sensors.
+# For demonstration purposes we use historical data supplied on the command line.
+
+if ($#ARGV+1 != 5) { 
+	print STDERR "5 command line arguments must be provided.\n";
+	usage(); 
+}
+
+($seq,$vbat,$vsolar,$temp,$sat) = @ARGV;
+
+# Scale to integer in range of 0 to 8280.
+# This must be the inverse of the mapping in the EQNS message.
+
+$vbat = int(($vbat * 1000) + 0.5);
+$vsolar = int(($vsolar * 1000) + 0.5);
+$temp = int((($temp + 273.2) * 10) + 0.5);
+
+exit system("telem-data91.pl $seq $vbat $vsolar $temp $sat");
+
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "balloon.pl - Format data into Compressed telemetry format.\n";
+	print STDERR "\n";
+	print STDERR "In a real situation this would obtain data from sensors.\n";
+	print STDERR "For demonstration purposes we use historical data supplied on the command line.\n";
+	print STDERR "\n";
+	print STDERR "Usage:  balloon.pl  seq vbat vsolar temp sat\n";
+	print STDERR "\n";
+	print STDERR "Where,\n";
+	print STDERR "    seq     is a sequence number.\n";
+	print STDERR "    vbat    is battery voltage.\n";
+	print STDERR "    vsolar  is solar cell voltage.\n";
+	print STDERR "    temp    is temperature, degrees C.\n";
+	print STDERR "    sat     is number of GPS satellites visible.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-bits.pl b/telemetry-toolkit/telem-bits.pl
new file mode 100644
index 0000000..a3fcdd2
--- /dev/null
+++ b/telemetry-toolkit/telem-bits.pl
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+if ($#ARGV+1 < 2 || $#ARGV+1 > 3) { 
+	print STDERR "A callsign, bit sense string, and optional project title must be provided.\n";
+	usage(); 
+}
+
+# Separate out call and pad to 9 characters.
+$call = shift @ARGV;
+$call = substr($call . "         ", 0, 9);
+
+if ( ! ($ARGV[0] =~ m/^[01]{8}$/)) {
+	print STDERR "The bit-sense value must be 8 binary digits.\n";
+	usage();
+}
+	
+print ":$call:BITS." . join (',', @ARGV) . "\n";
+exit 0;
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "telem-bits.pl - Generate BITS message with bit polarity and optional project title.\n";
+	print STDERR "\n";
+	print STDERR "Usage:  telem-bits.pl  call bit-sense [ project-title ]\n";
+	print STDERR "\n";
+	print STDERR "Bit-sense is string of 8 binary digits.\n";
+	print STDERR "If project title contains any spaces, enclose it in quotes.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-data.pl b/telemetry-toolkit/telem-data.pl
new file mode 100644
index 0000000..1484f44
--- /dev/null
+++ b/telemetry-toolkit/telem-data.pl
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+if ($#ARGV+1 < 2 || $#ARGV+1 > 7) { 
+	print STDERR "2 to 7 command line arguments must be provided.\n";
+	usage(); 
+}
+
+if ($#ARGV+1 == 7) {
+	if ( ! ($ARGV[6] =~ m/^[01]{8}$/)) {
+		print STDERR "The sixth value must be 8 binary digits.\n";
+		usage(); 
+	}
+}
+	
+print "T#" . join (',', @ARGV) . "\n";
+exit 0;
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "telem-data.pl - Format data into Telemetry Report format.\n";
+	print STDERR "\n";
+	print STDERR "Usage:  telem-data.pl  sequence value1 [ value2 ... ]\n";
+	print STDERR "\n";
+	print STDERR "A sequence number and up to 5 analog values can be specified.\n";
+	print STDERR "Any sixth value must be 8 binary digits.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-data91.pl b/telemetry-toolkit/telem-data91.pl
new file mode 100644
index 0000000..54da1ad
--- /dev/null
+++ b/telemetry-toolkit/telem-data91.pl
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+# For explanation of encoding see:
+# http://he.fi/doc/aprs-base91-comment-telemetry.txt
+
+
+if ($#ARGV+1 < 2 || $#ARGV+1 > 7) { 
+	print STDERR "2 to 7 command line arguments must be provided.\n";
+	usage(); 
+}
+
+
+if ($#ARGV+1 == 7) {
+	if ( ! ($ARGV[6] =~ m/^[01]{8}$/)) {
+		print STDERR "The sixth value must be 8 binary digits.\n";
+		usage(); 
+	}
+	# Convert binary digits to value.
+	$ARGV[6] = oct("0b" . reverse($ARGV[6]));
+}
+
+$result = "|";
+
+for ($n = 0 ; $n <= $#ARGV; $n++) {
+	#print $n . " = " . $ARGV[$n] . "\n";
+	$v = $ARGV[$n];
+	if ($v != int($v) || $v < 0 || $v > 8280) {
+		print STDERR "$v is not an integer in range of 0 to 8280.\n";
+		usage(); 
+	}
+
+	$result .= base91($v);
+}
+
+$result .= "|";
+print "$result\n";
+exit 0;
+
+
+sub base91 ()
+{
+	my $x = @_[0];
+
+	my $d1 = int ($x / 91);
+	my $d2 = $x % 91;
+
+	return chr($d1+33) . chr($d2+33);
+}
+
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "telem-data91.pl - Format data into compressed base 91 telemetry.\n";
+	print STDERR "\n";
+	print STDERR "Usage:  telem-data91.pl  sequence value1 [ value2 ... ]\n";
+	print STDERR "\n";
+	print STDERR "A sequence number and up to 5 analog values can be specified.\n";
+	print STDERR "Any sixth value must be 8 binary digits.\n";
+	print STDERR "Values must be integers in range of 0 to 8280.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-eqns.pl b/telemetry-toolkit/telem-eqns.pl
new file mode 100644
index 0000000..741ad94
--- /dev/null
+++ b/telemetry-toolkit/telem-eqns.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+if ($#ARGV+1 != 4 && $#ARGV+1 != 7 && $#ARGV+1 != 10 && $#ARGV+1 != 13 && $#ARGV+1 != 16) { 
+	print STDERR "A callsign and 1 to 5 sets of 3 coefficients must be provided.\n";
+	usage(); 
+}
+
+# Separate out call and pad to 9 characters.
+$call = shift @ARGV;
+$call = substr($call . "         ", 0, 9);
+	
+print ":$call:EQNS." . join (',', @ARGV) . "\n";
+exit 0;
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "telem-eqns.pl - Generate EQNS message with scaling coefficients\n";
+	print STDERR "\n";
+	print STDERR "Usage:  telem-eqns.pl  call a1 b1 c1 [ a2 b2 c2 ... ]\n";
+	print STDERR "\n";
+	print STDERR "Specify a callsign and 1 to 5 sets of 3 coefficients.\n";
+	print STDERR "See APRS protocol reference for their meaning.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-m0xer-3.txt b/telemetry-toolkit/telem-m0xer-3.txt
new file mode 100644
index 0000000..93ce5bb
--- /dev/null
+++ b/telemetry-toolkit/telem-m0xer-3.txt
@@ -0,0 +1,7 @@
+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>+!(|
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-parm.pl b/telemetry-toolkit/telem-parm.pl
new file mode 100644
index 0000000..464fa60
--- /dev/null
+++ b/telemetry-toolkit/telem-parm.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+if ($#ARGV+1 < 2 || $#ARGV+1 > 14) { 
+	print STDERR "A callsign and 1 to 13 channel names must be provided.\n";
+	usage(); 
+}
+
+# Separate out call and pad to 9 characters.
+$call = shift @ARGV;
+$call = substr($call . "         ", 0, 9);
+	
+print ":$call:PARM." . join (',', @ARGV) . "\n";
+exit 0;
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "telem-parm.pl - Generate PARM message with channel names.\n";
+	print STDERR "\n";
+	print STDERR "Usage:  telem-parm.pl  call aname1 ... aname5 dname1 .,, dname8\n";
+	print STDERR "\n";
+	print STDERR "Specify a callsign and up to 13 names for the analog & digital channels.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-seq.sh b/telemetry-toolkit/telem-seq.sh
new file mode 100644
index 0000000..0d2a36d
--- /dev/null
+++ b/telemetry-toolkit/telem-seq.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Generate sequence number as described here:
+# https://github.com/wb2osz/direwolf/issues/9
+#
+SEQ=`cat /tmp/seq 2>/dev/null`
+SEQ=$(expr \( $SEQ + 1 \) % 1000)
+echo $SEQ | tee /tmp/seq
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-unit.pl b/telemetry-toolkit/telem-unit.pl
new file mode 100644
index 0000000..c1d999d
--- /dev/null
+++ b/telemetry-toolkit/telem-unit.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+if ($#ARGV+1 < 2 || $#ARGV+1 > 14) { 
+	print STDERR "A callsign and 1 to 13 units/labels must be provided.\n";
+	usage(); 
+}
+
+# Separate out call and pad to 9 characters.
+$call = shift @ARGV;
+$call = substr($call . "         ", 0, 9);
+	
+print ":$call:UNIT." . join (',', @ARGV) . "\n";
+exit 0;
+
+sub usage () 
+{
+	print STDERR "\n";
+	print STDERR "telem-unit.pl - Generate UNIT message with channel units/labels.\n";
+	print STDERR "\n";
+	print STDERR "Usage:  telem-unit.pl  call unit1 ... unit5 label1 .,, label8\n";
+	print STDERR "\n";
+	print STDERR "Specify a callsign and up to 13 names for the units/labels.\n";
+
+	exit 1;
+}
\ No newline at end of file
diff --git a/telemetry-toolkit/telem-volts.conf b/telemetry-toolkit/telem-volts.conf
new file mode 100644
index 0000000..dfcc08a
--- /dev/null
+++ b/telemetry-toolkit/telem-volts.conf
@@ -0,0 +1,28 @@
+# Sample configuration for demonstration of sending telemetry.
+# Here we take a voltage from an analog to digital converter (ADC).
+
+ADEVICE plughw:1,0
+
+MYCALL MYCALL-9
+
+# For demonstration purposes, the metadata will be sent each minute and
+# voltage data every 10 seconds.  In actual practice, it would be much less frequent.
+
+# First the metadata.
+
+# The "infocmd=..." option means use the result for the info part of the packet.
+
+CBEACON delay=0:10 every=1:00 via=WIDE2-1 infocmd="telem-parm.pl MYCALL-9 Supply"
+CBEACON delay=0:11 every=1:00 via=WIDE2-1 infocmd="telem-unit.pl MYCALL-9 Volts"
+
+# Now the telemetry data.
+
+# First we use telem-volts.py to read a volage from the A/D converter.
+# This is supplied to telem-data.pl as a command line argument.
+# The result is used as the info part of a custom beacon.
+
+# Sequence numbers are generated as suggested here:
+# https://github.com/wb2osz/direwolf/issues/9
+
+CBEACON delay=0:15 every=0:10 via=WIDE2-1 infocmd="telem-data.pl `telem-seq.sh` `PYTHONPATH=~/Adafruit-Raspberry-Pi-Python-Code/Adafruit_ADS1x15 telem-volts.py`"
+
diff --git a/telemetry-toolkit/telem-volts.py b/telemetry-toolkit/telem-volts.py
new file mode 100644
index 0000000..f524968
--- /dev/null
+++ b/telemetry-toolkit/telem-volts.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015
+
+# Derived from
+# https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_ADS1x15/ads1x15_ex_singleended.py
+
+import time, signal, sys
+from Adafruit_ADS1x15 import ADS1x15
+
+ADS1015 = 0x00  # 12-bit ADC
+ADS1115 = 0x01  # 16-bit ADC
+
+# Set ADC full scale to 2048 mV.
+# Can't use original 4096 with 3.3V supply.
+gain = 2048
+
+# Select the sample time, 1/sps second.
+# Longer is better to average out noise.
+sps = 64
+
+# Set this to ADS1015 or ADS1115 depending on the ADC you are using!
+adc = ADS1x15(ic=ADS1115)
+
+# Values for voltage divider on ADC input.
+r1 = 1000000.
+r2 = 100000.
+
+# Read channel 0 in single-ended mode using the settings above
+volts = adc.readADCSingleEnded(0, gain, sps) * 0.001 * (r1+r2) / r2
+
+# Calibration correction specific to my hardware.
+# (multiply by expected value, divide by uncalibrated result.)
+#volts = volts * 4.98 / 4.889
+
+print "%.3f" % (volts)
diff --git a/telemetry.c b/telemetry.c
index 02e58fb..68cd585 100644
--- a/telemetry.c
+++ b/telemetry.c
@@ -1,1096 +1,1364 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2014, 2015  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. */
-
-#if TEST
-
-#define DEBUG1 1
-#define DEBUG2 1
-#define DEBUG3 1
-#define DEBUG4 1
-
-#endif
-
-
-/*------------------------------------------------------------------
- *
- * 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.
- *		quiet	- suppress error messages.
- *
- * 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, int quiet, 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) {
-	  if ( ! quiet) {
-	    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])) {
-	      if ( ! quiet) {
-	        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) {
-	      if ( ! quiet) {
-	        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 {
-	        if ( ! quiet) {
-	          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) {
-	  if ( ! quiet) {
-	    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."
- *		quiet	- suppress error messages.
- *
- * 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 quiet) 
-{
-	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 {
-	      if ( ! quiet) {
-	        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) {
-	  if ( ! quiet) {
-	    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."
- *		quiet	- suppress error messages.
- *
- * 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 quiet) 
-{
-	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) {
-	  if ( ! quiet) {
-	    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 {
-	    if ( ! quiet) {
-	      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 */
-
-
-/*-------------------------------------------------------------------
- *
- * Unit test.   Run with:
- *
- *	make -f Makefile.? etest
- *
- *--------------------------------------------------------------------*/
-
-
-#if TEST
-
-
-
-int main ( )
-{
-	char result[256];
-	char comment[256];
-
-	strcpy (result, "");
-	strcpy (comment, "");
-
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("Unit test for telemetry decoding functions...\n");	
-
-#if DEBUG1
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("part 1\n");	
-
-	// From protocol spec.
-
-	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001", 0, result, comment);
-
-	// Try adding a comment.
-
-	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001Comment,with,commas", 0, result, comment);
-	strcpy (comment, "");
-
-	// Try shortening or omitting parts.
-
-	telemetry_data_original ("WB2OSZ", "T005,199,000,255,073,123,0110", 0, result, comment);
-	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,0110", 0, result, comment);
-	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123", 0, result, comment);
-	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,,123,01101001", 0, result, comment);
-	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101009", 0, result, comment);
-
-	// Local observation.
-
-	telemetry_data_original ("WB2OSZ", "T#491,4.9,0.3,25.0,0.0,1.0,00000000", 0, result, comment); 
-
-#endif
-
-#if DEBUG2
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("part 2\n");	
-
-	// 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
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("part 3\n");	
-
-	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", 0);
-
-	// 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", 0);
-	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,,3", 0);
-
-	telemetry_bit_sense_message ("N0QBF-11", "10110000,N0QBF's Big Balloon", 0);
-
-	// Too few and invalid digits.
-	telemetry_bit_sense_message ("N0QBF-11", "1011000", 0);
-	telemetry_bit_sense_message ("N0QBF-11", "10110008", 0);
-
-#endif
-
-	text_color_set(DW_COLOR_INFO);
-	dw_printf ("part 4\n");	
-
-	telemetry_coefficents_message ("M0XER-3", "0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0", 0);
-	telemetry_bit_sense_message ("M0XER-3", "11111111,10mW research balloon", 0);
-	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);
-
-
-	// TODO: Should return success/fail so visual inspection is not needed. 
-
-	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 */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2014, 2015  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. */
+
+#if TEST
+
+#define DEBUG1 1
+#define DEBUG2 1
+#define DEBUG3 1
+#define DEBUG4 1
+
+#endif
+
+
+/*------------------------------------------------------------------
+ *
+ * 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>
+
+#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, size_t outputsize); 
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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;
+
+#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) {
+
+	    assert (p->magic1 == MAGIC1);
+	    assert (p->magic2 == MAGIC2);
+
+	    return (p);
+	  }
+	}
+
+	p = malloc (sizeof (struct t_metadata_s));
+	memset (p, 0, sizeof (struct t_metadata_s));
+
+	p->magic1 = MAGIC1;
+	
+	strlcpy (p->station, station, sizeof(p->station));
+
+	for (n = 0; n < T_NUM_ANALOG; n++) {
+	  snprintf (p->name[n], sizeof(p->name[n]), "A%d", n+1);
+	}
+	for (n = 0; n < T_NUM_DIGITAL; n++) {
+	  snprintf (p->name[T_NUM_ANALOG+n], sizeof(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.
+ *		quiet	- suppress error messages.
+ *
+ * Outputs:	output	- Decoded telemetry in human readable format.
+ *				TODO:  How big does it need to be?  (buffer overflow?)
+ *		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, int quiet, char *output, size_t outputsize, char *comment, size_t commentsize) 
+{
+	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
+
+	strlcpy (output, "", outputsize);
+	strlcpy (comment, "", commentsize);
+
+	pm = t_get_metadata(station);
+
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	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) {
+	  if ( ! quiet) {
+	    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.
+ * Remove any trailing CR/LF.
+ */
+
+	strlcpy (stemp, info+2, sizeof(stemp));
+
+	for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) {
+	  *p = '\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);
+	    }
+	    // Version 1.3: Suppress this message.
+	    // No one pays attention to the original 000 to 255 range.
+	    // BTW, this doesn't trap values like 0.0 or 1.0
+	    //if (strlen(p) != 3 || araw[n] < 0 || araw[n] > 255 || araw[n] != (int)(araw[n])) {
+	    //  if ( ! quiet) {
+	    //    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) {
+	      if ( ! quiet) {
+	        text_color_set(DW_COLOR_ERROR);
+	        dw_printf("Expected to find 8 binary digits after \"%s\" for the digital values.\n", p);	
+	      }      
+	    }
+
+	    // TODO: test this!
+	    if (strlen(next) > 8) {
+	      strlcpy (comment, next+8, commentsize);
+	      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 {
+	        if ( ! quiet) {
+	          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) {
+	  if ( ! quiet) {
+	    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, outputsize);
+
+} /* 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]);
+	  return (G_UNKNOWN);
+	}
+
+	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 (G_UNKNOWN);
+	}
+	return (result);
+}
+
+void telemetry_data_base91 (char *station, char *cdata, char *output, size_t outputsize)
+{
+	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
+
+	strlcpy (output, "", outputsize);
+
+	pm = t_get_metadata(station);
+
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	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, outputsize);
+
+} /* 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.
+ * Remove any trailing CR LF.
+ */
+
+	strlcpy (stemp, msg, sizeof(stemp));
+
+	for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) {
+	  *p = '\0';
+	} 
+
+	pm = t_get_metadata(station);
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	next = stemp;
+
+	n = 0;
+	while ((p = strsep(&next,",")) != NULL) {
+	  if (n < T_NUM_ANALOG + T_NUM_DIGITAL) {
+	    if (strlen(p) > 0 && strcmp(p,"-") != 0) {
+	      strlcpy (pm->name[n], p, sizeof(pm->name[n]));
+	    }
+	    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.
+ * Remove any trailing CR LF.
+ */
+	
+	strlcpy (stemp, msg, sizeof(stemp));
+
+	for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) {
+	  *p = '\0';
+	} 
+
+	pm = t_get_metadata(station);
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	next = stemp;
+
+	n = 0;
+	while ((p = strsep(&next,",")) != NULL) {
+	  if (n < T_NUM_ANALOG + T_NUM_DIGITAL) {
+	    if (strlen(p) > 0) {
+	      strlcpy (pm->unit[n], p, sizeof(pm->unit[n]));
+	    }
+	    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."
+ *		quiet	- suppress error messages.
+ *
+ * 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 quiet) 
+{
+	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.
+ * Remove any trailing CR LF.
+ */
+
+	strlcpy (stemp, msg, sizeof(stemp));
+
+	for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) {
+	  *p = '\0';
+	} 
+
+	pm = t_get_metadata(station);
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	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 {
+	      if ( ! quiet) {
+	        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) {
+	  if ( ! quiet) {
+	    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."
+ *		quiet	- suppress error messages.
+ *
+ * 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 quiet) 
+{
+	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);
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	if (strlen(msg) < 8) {
+	  if ( ! quiet) {
+	    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 {
+	    if ( ! quiet) {
+	      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++;
+
+	strlcpy (pm->project, msg+n, sizeof(pm->project));
+ 
+#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.
+ *		
+ *--------------------------------------------------------------------*/
+
+#define VAL_STR_SIZE 64
+
+static void fval_to_str (float x, int ndp, char str[VAL_STR_SIZE])
+{
+	if (x == G_UNKNOWN) {
+	  strlcpy (str, "?", VAL_STR_SIZE);
+	}
+	else {
+	  snprintf (str, VAL_STR_SIZE, "%.*f", ndp, x);
+	}
+}
+
+static void ival_to_str (int x, char str[VAL_STR_SIZE])
+{
+	if (x == G_UNKNOWN) {
+	  strlcpy (str, "?", VAL_STR_SIZE);
+	}
+	else {
+	  snprintf (str, VAL_STR_SIZE, "%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, size_t outputsize) 
+{
+	int n;
+	char val_str[VAL_STR_SIZE];
+
+
+	assert (pm != NULL);
+	assert (pm->magic1 == MAGIC1);
+	assert (pm->magic2 == MAGIC2);
+
+	strlcpy (output, "", outputsize);
+
+	if (strlen(pm->project) > 0) {
+	  strlcpy (output, pm->project, outputsize);
+	  strlcat (output, ": ", outputsize);
+	}
+
+	ival_to_str (seq, val_str);
+	strlcat (output, "Seq=", outputsize);
+	strlcat (output, val_str, outputsize);
+	
+	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;
+
+	    strlcat (output, ", ", outputsize);
+
+	    strlcat (output, pm->name[n], outputsize);
+	    strlcat (output, "=", outputsize);
+	    
+	    // 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, val_str);
+	    strlcat (output, val_str, outputsize);
+	    if (strlen(pm->unit[n]) > 0) {
+	      strlcat (output, " ", outputsize);
+	      strlcat (output, pm->unit[n], outputsize);
+	    }
+	    
+	  }
+	}
+
+	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;
+
+	    strlcat (output, ", ", outputsize);
+
+	    strlcat (output, pm->name[T_NUM_ANALOG+n], outputsize);
+	    strlcat (output, "=", outputsize);
+	    
+	    // Possible inverting for bit sense.
+
+	    if (draw[n] == G_UNKNOWN) {
+	      dval = G_UNKNOWN;
+	    }
+	    else {
+	      dval = draw[n] ^ ! pm->sense[n];
+	    }
+
+	    ival_to_str (dval, val_str);
+
+	    if (strlen(pm->unit[T_NUM_ANALOG+n]) > 0) {
+	      strlcat (output, " ", outputsize);
+	      strlcat (output, pm->unit[T_NUM_ANALOG+n], outputsize);
+	    }
+	    strlcat (output, val_str, outputsize);
+	    
+	  }
+	}
+
+
+#if DEBUG4
+	text_color_set(DW_COLOR_DEBUG);
+
+	dw_printf ("%s\n", output);	
+#endif
+
+} /* end t_data_process */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Unit test.   Run with:
+ *
+ *	make  etest
+ *
+ *
+ *--------------------------------------------------------------------*/
+
+
+#if TEST
+
+
+int main ( )
+{
+	char result[120];
+	char comment[40];
+	int errors = 0;
+
+	strlcpy (result, "", sizeof(result));
+	strlcpy (comment, "", sizeof(comment));
+
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("Unit test for telemetry decoding functions...\n");	
+
+#if DEBUG1
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("part 1\n");	
+
+	// From protocol spec.
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0, D8=1") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 101\n");
+	}
+
+	// Try adding a comment.
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101001Comment,with,commas", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0, D8=1") != 0 ||
+	    strcmp(comment, "Comment,with,commas") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 102\n");
+	}
+
+
+	// Error handling - Try shortening or omitting parts.
+
+	telemetry_data_original ("WB2OSZ", "T005,199,000,255,073,123,0110", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 103\n");
+	}
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,0110", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 104\n");
+	}
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 105\n");
+	}
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,,123,01101001", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0, D8=1") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 106\n");
+	}
+
+	telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101009", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=5, A1=199, A2=0, A3=255, A4=73, A5=123, D1=0, D2=1, D3=1, D4=0, D5=1, D6=0, D7=0") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 107\n");
+	}
+
+
+	// Local observation.
+
+	telemetry_data_original ("WB2OSZ", "T#491,4.9,0.3,25.0,0.0,1.0,00000000", 0, result, sizeof(result), comment, sizeof(comment));
+
+	if (strcmp(result, "Seq=491, A1=4.9, A2=0.3, A3=25.0, A4=0.0, A5=1.0, D1=0, D2=0, D3=0, D4=0, D5=0, D6=0, D7=0, D8=0") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 108\n");
+	}
+
+#endif
+
+#if DEBUG2
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("part 2\n");	
+
+	// From protocol spec.
+
+	telemetry_data_base91 ("WB2OSZ", "ss11", result, sizeof(result));
+
+	if (strcmp(result, "Seq=7544, A1=1472") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 201\n");	
+	}
+
+	telemetry_data_base91 ("WB2OSZ", "ss11223344{{!\"", result, sizeof(result));
+
+	if (strcmp(result, "Seq=7544, A1=1472, A2=1564, A3=1656, A4=1748, A5=8280, D1=1, D2=0, D3=0, D4=0, D5=0, D6=0, D7=0, D8=0") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 202\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, sizeof(result));
+
+	if (strcmp(result, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 203\n");
+	}
+
+	telemetry_data_base91 ("WB2OSZ", "ss1", result, sizeof(result));
+
+	if (strcmp(result, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 204\n");
+	}
+
+	telemetry_data_base91 ("WB2OSZ", "ss11223344{{!", result, sizeof(result));
+
+	if (strcmp(result, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 205\n");
+	}
+
+	telemetry_data_base91 ("WB2OSZ", "s |1", result, sizeof(result));
+
+	if (strcmp(result, "Seq=?") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 206\n");
+	}
+
+#endif
+
+#if DEBUG3
+
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("part 3\n");	
+
+	telemetry_name_message ("N0QBF-11", "Battery,Btemp,ATemp,Pres,Alt,Camra,Chut,Sun,10m,ATV");
+
+	struct t_metadata_s *pm;
+	pm = t_get_metadata("N0QBF-11");
+
+	if (strcmp(pm->name[0],  "Battery") != 0 ||
+	    strcmp(pm->name[1],  "Btemp") != 0 ||
+	    strcmp(pm->name[2],  "ATemp") != 0 ||
+	    strcmp(pm->name[3],  "Pres") != 0 ||
+	    strcmp(pm->name[4],  "Alt") != 0 ||
+	    strcmp(pm->name[5],  "Camra") != 0 ||
+	    strcmp(pm->name[6],  "Chut") != 0 ||
+	    strcmp(pm->name[7],  "Sun") != 0 ||
+	    strcmp(pm->name[8],  "10m") != 0 ||
+	    strcmp(pm->name[9],  "ATV") != 0 ||
+	    strcmp(pm->name[10], "D6") != 0 ||
+	    strcmp(pm->name[11], "D7") != 0 ||
+	    strcmp(pm->name[12], "D8") != 0 ) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 301\n");
+	}
+
+	telemetry_unit_label_message ("N0QBF-11", "v/100,deg.F,deg.F,Mbar,Kft,Click,OPEN,on,on,hi");
+
+	pm = t_get_metadata("N0QBF-11");
+
+	if (strcmp(pm->unit[0],  "v/100") != 0 ||
+	    strcmp(pm->unit[1],  "deg.F") != 0 ||
+	    strcmp(pm->unit[2],  "deg.F") != 0 ||
+	    strcmp(pm->unit[3],  "Mbar") != 0 ||
+	    strcmp(pm->unit[4],  "Kft") != 0 ||
+	    strcmp(pm->unit[5],  "Click") != 0 ||
+	    strcmp(pm->unit[6],  "OPEN") != 0 ||
+	    strcmp(pm->unit[7],  "on") != 0 ||
+	    strcmp(pm->unit[8],  "on") != 0 ||
+	    strcmp(pm->unit[9],  "hi") != 0 ||
+	    strcmp(pm->unit[10], "") != 0 ||
+	    strcmp(pm->unit[11], "") != 0 ||
+	    strcmp(pm->unit[12], "") != 0 ) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 302\n");
+	}
+
+	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,2,3", 0);
+
+	pm = t_get_metadata("N0QBF-11");
+
+	if (pm->coeff[0][0] != 0   || pm->coeff[0][1] < 5.1999 || pm->coeff[0][1] > 5.2001 || pm->coeff[0][2] != 0 ||
+	    pm->coeff[1][0] != 0   || pm->coeff[1][1] < .52999 || pm->coeff[1][1] > .53001 || pm->coeff[1][2] != -32 ||
+	    pm->coeff[2][0] != 3   || pm->coeff[2][1] < 4.3899 || pm->coeff[2][1] > 4.3901 || pm->coeff[2][2] != 49 ||
+	    pm->coeff[3][0] != -32 || pm->coeff[3][1] != 3                                 || pm->coeff[3][2] != 18 ||
+            pm->coeff[4][0] != 1   || pm->coeff[4][1] != 2                                 || pm->coeff[4][2] != 3) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 303c\n");
+	}
+
+	if (pm->coeff_ndp[0][0] != 0 || pm->coeff_ndp[0][1] != 1 || pm->coeff_ndp[0][2] != 0 ||
+	    pm->coeff_ndp[1][0] != 0 || pm->coeff_ndp[1][1] != 2 || pm->coeff_ndp[1][2] != 0 ||
+	    pm->coeff_ndp[2][0] != 0 || pm->coeff_ndp[2][1] != 2 || pm->coeff_ndp[2][2] != 0 ||
+	    pm->coeff_ndp[3][0] != 0 || pm->coeff_ndp[3][1] != 0 || pm->coeff_ndp[3][2] != 0 ||
+	    pm->coeff_ndp[4][0] != 0 || pm->coeff_ndp[4][1] != 0 || pm->coeff_ndp[4][2] != 0 ) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 303n\n");
+	}
+
+	// Error if less than 15 or empty field.
+	// Notice that we keep the previous value in this case.
+
+	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,2", 0);
+
+	pm = t_get_metadata("N0QBF-11");
+
+	if (pm->coeff[0][0] != 0   || pm->coeff[0][1] < 5.1999 || pm->coeff[0][1] > 5.2001 || pm->coeff[0][2] != 0 ||
+	    pm->coeff[1][0] != 0   || pm->coeff[1][1] < .52999 || pm->coeff[1][1] > .53001 || pm->coeff[1][2] != -32 ||
+	    pm->coeff[2][0] != 3   || pm->coeff[2][1] < 4.3899 || pm->coeff[2][1] > 4.3901 || pm->coeff[2][2] != 49 ||
+	    pm->coeff[3][0] != -32 || pm->coeff[3][1] != 3                                 || pm->coeff[3][2] != 18 ||
+            pm->coeff[4][0] != 1   || pm->coeff[4][1] != 2                                 || pm->coeff[4][2] != 3) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 304c\n");
+	}
+
+	if (pm->coeff_ndp[0][0] != 0 || pm->coeff_ndp[0][1] != 1 || pm->coeff_ndp[0][2] != 0 ||
+	    pm->coeff_ndp[1][0] != 0 || pm->coeff_ndp[1][1] != 2 || pm->coeff_ndp[1][2] != 0 ||
+	    pm->coeff_ndp[2][0] != 0 || pm->coeff_ndp[2][1] != 2 || pm->coeff_ndp[2][2] != 0 ||
+	    pm->coeff_ndp[3][0] != 0 || pm->coeff_ndp[3][1] != 0 || pm->coeff_ndp[3][2] != 0 ||
+	    pm->coeff_ndp[4][0] != 0 || pm->coeff_ndp[4][1] != 0 || pm->coeff_ndp[4][2] != 0 ) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 304n\n");
+	}
+
+	telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,,3", 0);
+
+	pm = t_get_metadata("N0QBF-11");
+
+	if (pm->coeff[0][0] != 0   || pm->coeff[0][1] < 5.1999 || pm->coeff[0][1] > 5.2001 || pm->coeff[0][2] != 0 ||
+	    pm->coeff[1][0] != 0   || pm->coeff[1][1] < .52999 || pm->coeff[1][1] > .53001 || pm->coeff[1][2] != -32 ||
+	    pm->coeff[2][0] != 3   || pm->coeff[2][1] < 4.3899 || pm->coeff[2][1] > 4.3901 || pm->coeff[2][2] != 49 ||
+	    pm->coeff[3][0] != -32 || pm->coeff[3][1] != 3                                 || pm->coeff[3][2] != 18 ||
+            pm->coeff[4][0] != 1   || pm->coeff[4][1] != 2                                 || pm->coeff[4][2] != 3) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 305c\n");
+	}
+
+	if (pm->coeff_ndp[0][0] != 0 || pm->coeff_ndp[0][1] != 1 || pm->coeff_ndp[0][2] != 0 ||
+	    pm->coeff_ndp[1][0] != 0 || pm->coeff_ndp[1][1] != 2 || pm->coeff_ndp[1][2] != 0 ||
+	    pm->coeff_ndp[2][0] != 0 || pm->coeff_ndp[2][1] != 2 || pm->coeff_ndp[2][2] != 0 ||
+	    pm->coeff_ndp[3][0] != 0 || pm->coeff_ndp[3][1] != 0 || pm->coeff_ndp[3][2] != 0 ||
+	    pm->coeff_ndp[4][0] != 0 || pm->coeff_ndp[4][1] != 0 || pm->coeff_ndp[4][2] != 0 ) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 305n\n");
+	}
+
+
+	telemetry_bit_sense_message ("N0QBF-11", "10110000,N0QBF's Big Balloon", 0);
+
+	pm = t_get_metadata("N0QBF-11");
+	if (pm->sense[0] != 1 || pm->sense[1] != 0 || pm->sense[2] != 1 || pm->sense[3] != 1 ||
+	    pm->sense[4] != 0 || pm->sense[5] != 0 || pm->sense[6] != 0 || pm->sense[7] != 0 ||
+	    strcmp(pm->project, "N0QBF's Big Balloon") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 306\n");
+	}
+
+	// Too few and invalid digits.
+	telemetry_bit_sense_message ("N0QBF-11", "1011000", 0);
+
+	pm = t_get_metadata("N0QBF-11");
+	if (pm->sense[0] != 1 || pm->sense[1] != 0 || pm->sense[2] != 1 || pm->sense[3] != 1 ||
+	    pm->sense[4] != 0 || pm->sense[5] != 0 || pm->sense[6] != 0 || pm->sense[7] != 0 ||
+	    strcmp(pm->project, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 307\n");
+	}
+
+	telemetry_bit_sense_message ("N0QBF-11", "10110008", 0);
+
+	pm = t_get_metadata("N0QBF-11");
+	if (pm->sense[0] != 1 || pm->sense[1] != 0 || pm->sense[2] != 1 || pm->sense[3] != 1 ||
+	    pm->sense[4] != 0 || pm->sense[5] != 0 || pm->sense[6] != 0 || pm->sense[7] != 0 ||
+	    strcmp(pm->project, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 308\n");
+	}
+
+
+#endif
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("part 4\n");	
+
+	telemetry_coefficents_message ("M0XER-3", "0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0", 0);
+	telemetry_bit_sense_message ("M0XER-3", "11111111,10mW research balloon", 0);
+	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, sizeof(result));
+
+	if (strcmp(result, "10mW research balloon: Seq=3273, Vbat=4.472 V, Vsolar=0.516 V, Temp=-24.3 C, Sat=13") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 401\n");
+	}
+
+	telemetry_data_base91 ("M0XER-3", "cNOv'C?=!-", result, sizeof(result));
+
+	if (strcmp(result, "10mW research balloon: Seq=6051, Vbat=4.271 V, Vsolar=0.580 V, Temp=2.6 C, Sat=12") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 402\n");
+	}
+
+	telemetry_data_base91 ("M0XER-3", "n0RS(:>b!+", result, sizeof(result));
+
+	if (strcmp(result, "10mW research balloon: Seq=7022, Vbat=4.509 V, Vsolar=0.662 V, Temp=-2.8 C, Sat=10") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 403\n");
+	}
+
+	telemetry_data_base91 ("M0XER-3", "x&G=!(8s!,", result, sizeof(result));
+
+	if (strcmp(result, "10mW research balloon: Seq=7922, Vbat=3.486 V, Vsolar=0.007 V, Temp=-55.7 C, Sat=11") != 0 ||
+	    strcmp(comment, "") != 0) {
+	  errors++; text_color_set(DW_COLOR_ERROR); dw_printf ("Wrong result, test 404\n");
+	}
+
+
+/* final score. */
+
+	if (errors != 0) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("\nTEST FAILED with %d errors.\n", errors);
+	  exit (EXIT_FAILURE);
+	}
+
+	text_color_set (DW_COLOR_REC);
+	dw_printf ("\nTEST WAS SUCCESSFUL.\n");
+	exit (EXIT_SUCCESS);
+}
+
+/*
+	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
index a50f6f2..4ef9b62 100644
--- a/telemetry.h
+++ b/telemetry.h
@@ -1,15 +1,15 @@
-
-
-/* telemetry.h */
-
-void telemetry_data_original (char *station, char *info, int quiet, 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, int quiet);
-
-void telemetry_bit_sense_message (char *station, char *msg, int quiet);
+
+
+/* telemetry.h */
+
+void telemetry_data_original (char *station, char *info, int quiet, char *output, size_t outputsize, char *comment, size_t commentsize);
+ 
+void telemetry_data_base91 (char *station, char *cdata, char *output, size_t outputsize);
+ 
+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, int quiet);
+
+void telemetry_bit_sense_message (char *station, char *msg, int quiet);
diff --git a/textcolor.c b/textcolor.c
index 66a4020..90b55e3 100644
--- a/textcolor.c
+++ b/textcolor.c
@@ -1,383 +1,392 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        textcolor.c
- *
- * Purpose:     Originally this would only set color of text
- *	 	and we used printf everywhere.
- *		Now we also have a printf replacement that can
- *		be used to redirect all output to the desired place.
- *		This opens the door to using ncurses, a GUI, or
- *		running as a daemon.
- *
- * Description:	For Linux and Cygwin use the ANSI escape sequences.
- *		In earlier versions of Windows, the cmd window and ANSI.SYS
- *		could interpret this but it doesn't seem to be available
- *		anymore so we use a different interface.
- *
- * References:
- *		http://en.wikipedia.org/wiki/ANSI_escape_code
- *		http://academic.evergreen.edu/projects/biophysics/technotes/program/ansi_esc.htm
- *
- * Problem:	The ANSI escape sequences, used on Linux, allow 8 basic colors.
- *		Unfortunately, white is not one of them.  We only have dark
- *		white, also known as light gray.  To get brighter colors, 
- *		we need to apply an attribute.  On some systems, the bold 
- *		attribute produces a brighter color rather than a bold font.
- *		On other systems, we need to use the blink attribute to get 
- *		bright colors, including white.  However on others, blink
- *		does actually produce blinking characters.
- *
- *		Several people have also complained that bright green is
- *		very hard to read against a light background.  The current
- *		implementation does not allow easy user customization of colors.
- *		
- *		Currently, the only option is to put "-t 0" on the command
- *		line to disable all text color.  This is more readable but
- *		makes it harder to distinguish different types of
- *		information, e.g. received packets vs. error messages.
- *
- *		A few people have suggested ncurses.  This needs to 
- *		be investigated for a future version.   The foundation has
- *		already been put in place.  All of the printf's should have been
- *		replaced by dw_printf, defined in this file.   All of the
- *		text output is now being funneled thru this one function
- *		so it should be easy to send it to the user by some
- *		other means.
- *
- *--------------------------------------------------------------------*/
-
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-
-
-#if __WIN32__
-
-#include <windows.h>
-
-#define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY)
-
-
-
-#elif __CYGWIN__	/* Cygwin */
-
-/* For Cygwin 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";
-
-
-#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" "\e[5;47m";
-static const char green[] 	= "\e[1;32m" "\e[5;47m";
-static const char yellow[] 	= "\e[1;33m" "\e[5;47m";
-static const char blue[] 	= "\e[1;34m" "\e[5;47m";
-static const char magenta[] 	= "\e[1;35m" "\e[5;47m";
-static const char cyan[] 	= "\e[1;36m" "\e[5;47m";
-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 */
-
-#if 1		/* new in version 1.2, as suggested by IW2DHW */
-		/* Test done using gnome-terminal and xterm */
-
-static const char background_white[] = "\e[48;2;255;255;255m";
-
-/* Whenever a dark color is used, the */
-/* background is reset and needs to be set again. */
-
-
-static const char black[]	= "\e[0;30m" "\e[48;2;255;255;255m";
-static const char red[] 	= "\e[0;31m" "\e[48;2;255;255;255m";
-static const char green[] 	= "\e[0;32m" "\e[48;2;255;255;255m";
-static const char yellow[] 	= "\e[0;33m" "\e[48;2;255;255;255m";
-static const char blue[] 	= "\e[0;34m" "\e[48;2;255;255;255m";
-static const char magenta[] 	= "\e[0;35m" "\e[48;2;255;255;255m";
-static const char cyan[] 	= "\e[0;36m" "\e[48;2;255;255;255m";
-static const char dark_green[]	= "\e[0;32m" "\e[48;2;255;255;255m";
-
-
-#else 		/* from version 1.1 */
-
-
-static const char background_white[] = "\e[47;1m";
-
-/* Whenever a dark color is used, the */
-/* background is reset and needs to be set again. */
-
-static const char black[]	= "\e[0;30m" "\e[1;47m";
-static const char red[] 	= "\e[1;31m" "\e[1;47m";
-static const char green[] 	= "\e[1;32m" "\e[1;47m"; 
-static const char yellow[] 	= "\e[1;33m" "\e[1;47m";
-static const char blue[] 	= "\e[1;34m" "\e[1;47m";
-static const char magenta[] 	= "\e[1;35m" "\e[1;47m";
-static const char cyan[] 	= "\e[1;36m" "\e[1;47m";
-static const char dark_green[]	= "\e[0;32m" "\e[1;47m";
-
-
-#endif
-
-
-/* Clear from cursor to end of screen. */
-
-static const char clear_eos[]	= "\e[0J";
-
-#endif	/* end Linux */
-
-
-#include "textcolor.h"
-
-
-/*
- * g_enable_color:
- *	0 = disable text colors.
- *	1 = normal.
- *	others... future possibility.
- */
-
-static int g_enable_color = 1;
-
-
-void text_color_init (int enable_color)
-{
-
-	g_enable_color = enable_color;
-
-
-#if __WIN32__
-
-
-	if (g_enable_color) {
-
-	  HANDLE h;
-	  CONSOLE_SCREEN_BUFFER_INFO csbi;
-	  WORD attr = BACKGROUND_WHITE;
-	  DWORD length;
-	  COORD coord;
-	  DWORD nwritten;
-
-	  h = GetStdHandle(STD_OUTPUT_HANDLE);
-	  if (h != NULL && h != INVALID_HANDLE_VALUE) {
-
-	    GetConsoleScreenBufferInfo (h, &csbi);
-
-	    length = csbi.dwSize.X * csbi.dwSize.Y;
-	    coord.X = 0; 
-	    coord.Y = 0;
-	    FillConsoleOutputAttribute (h, attr, length, coord, &nwritten);
-	  }
-	}
-
-#else
-	if (g_enable_color) {
-	  //printf ("%s", clear_eos);
-	  printf ("%s", background_white);
-	  printf ("%s", clear_eos);
-	  printf ("%s", black);
-	}
-#endif
-}
-
-
-#if __WIN32__
-
-/* Seems that ANSI.SYS is no longer available. */
-
-
-void text_color_set ( enum dw_color_e c )
-{
-	WORD attr;
-	HANDLE h;
-
-	if (g_enable_color == 0) {
-	  return;
-	}
-
-	switch (c) {
-
-	  default:
-	  case DW_COLOR_INFO:
-	    attr = BACKGROUND_WHITE;
-	    break;
-
-	  case DW_COLOR_ERROR:
-	    attr = FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
-	    break;
-
-	  case DW_COLOR_REC:
-	    attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
-	    break;
-
-	  case DW_COLOR_DECODED:
-	    attr = FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
-	    break;
-
-	  case DW_COLOR_XMIT:
-	    attr = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
-	    break;
-
-	  case DW_COLOR_DEBUG:
-	    attr = FOREGROUND_GREEN | BACKGROUND_WHITE;
-	    break;
-	}
-
-	h = GetStdHandle(STD_OUTPUT_HANDLE);
-
-	if (h != NULL && h != INVALID_HANDLE_VALUE) {
-	  SetConsoleTextAttribute (h, attr);
-	}
-}
-
-#else
-
-void text_color_set ( enum dw_color_e c )
-{
-
-	if (g_enable_color == 0) {
-	  return;
-	}
-
-	switch (c) {
-
-	  default:
-	  case DW_COLOR_INFO:
-	    printf ("%s", black);
-	    break;
-
-	  case DW_COLOR_ERROR:
-	    printf ("%s", red);
-	    break;
-
-	  case DW_COLOR_REC:
-	    printf ("%s", green);
-	    break;
-
-	  case DW_COLOR_DECODED:
-	    printf ("%s", blue);
-	    break;
-
-	  case DW_COLOR_XMIT:
-	    printf ("%s", magenta);
-	    break;
-
-	  case DW_COLOR_DEBUG:
-	    printf ("%s", dark_green);
-	    break;
-	}
-}
-
-#endif
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        dw_printf 
- *
- * Purpose:     printf replacement that allows us to send all text
- *		output to stdout or other desired destination.
- *
- * Inputs:	fmt	- C language format.
- *		...	- Addtional arguments, just like printf.
- *
- *
- * Returns:	Number of characters in result.
- *
- * Bug:		Fixed size buffer.  
- *		I'd rather not do a malloc for each print.
- *
- *--------------------------------------------------------------------*/
-
-
-// TODO: replace all printf, look for stderr, perror
-// TODO:   $ grep printf *.c | grep -v dw_printf | grep -v fprintf | gawk '{ print $1 }' |  sort -u
-
-
-int dw_printf (const char *fmt, ...) 
-{
-#define BSIZE 1000
-	va_list args;
-	char buffer[BSIZE];
-	int len;
-	
-	va_start (args, fmt);
-	len = vsnprintf (buffer, BSIZE, fmt, args);
-	va_end (args);
-
-// TODO: other possible destinations...
-
-	fputs (buffer, stdout);
-	return (len);
-}
-
-
-
-#if TESTC
-main () 
-{
-	printf ("Initial condition\n");
-	text_color_init (1);
-	printf ("After text_color_init\n");
-	text_color_set(DW_COLOR_INFO); 		printf ("Info\n");
-	text_color_set(DW_COLOR_ERROR); 	printf ("Error\n");
-	text_color_set(DW_COLOR_REC); 		printf ("Rec\n");
-	text_color_set(DW_COLOR_DECODED); 	printf ("Decoded\n");
-	text_color_set(DW_COLOR_XMIT); 		printf ("Xmit\n");
-	text_color_set(DW_COLOR_DEBUG); 	printf ("Debug\n");
-}
-#endif
-	
-/* end textcolor.c */
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    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
+//    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/>.
+//
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        textcolor.c
+ *
+ * Purpose:     Originally this would only set color of text
+ *	 	and we used printf everywhere.
+ *		Now we also have a printf replacement that can
+ *		be used to redirect all output to the desired place.
+ *		This opens the door to using ncurses, a GUI, or
+ *		running as a daemon.
+ *
+ * Description:	For Linux and Cygwin use the ANSI escape sequences.
+ *		In earlier versions of Windows, the cmd window and ANSI.SYS
+ *		could interpret this but it doesn't seem to be available
+ *		anymore so we use a different interface.
+ *
+ * References:
+ *		http://en.wikipedia.org/wiki/ANSI_escape_code
+ *		http://academic.evergreen.edu/projects/biophysics/technotes/program/ansi_esc.htm
+ *
+ *
+
+>>>> READ THIS PART!!! <<<<
+
+ *
+ * 
+ * Problem:	The ANSI escape sequences, used on Linux, allow 8 basic colors.
+ *		Unfortunately, white is not one of them.  We only have dark
+ *		white, also known as light gray.  To get brighter colors, 
+ *		we need to apply an attribute.  On some systems, the bold 
+ *		attribute produces a brighter color rather than a bold font.
+ *		On other systems, we need to use the blink attribute to get 
+ *		bright colors, including white.  However on others, blink
+ *		does actually produce blinking characters.
+ *
+ *		Several people have also complained that bright green is
+ *		very hard to read against a light background.  The current
+ *		implementation does not allow easy user customization of colors.
+ *		
+ *		Currently, the only option is to put "-t 0" on the command
+ *		line to disable all text color.  This is more readable but
+ *		makes it harder to distinguish different types of
+ *		information, e.g. received packets vs. error messages.
+ *
+ *		A few people have suggested ncurses.  This needs to 
+ *		be investigated for a future version.   The foundation has
+ *		already been put in place.  All of the printf's should have been
+ *		replaced by dw_printf, defined in this file.   All of the
+ *		text output is now being funneled thru this one function
+ *		so it should be easy to send it to the user by some
+ *		other means.
+ *
+ *--------------------------------------------------------------------*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+
+#if __WIN32__
+
+#include <windows.h>
+
+#define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY)
+
+
+
+#elif __CYGWIN__	/* Cygwin */
+
+/* For Cygwin 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";
+
+
+#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. */
+
+/* If you do get blinking, remove all references to "\e[5;47m" */
+
+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" "\e[5;47m";
+static const char green[] 	= "\e[1;32m" "\e[5;47m";
+static const char yellow[] 	= "\e[1;33m" "\e[5;47m";
+static const char blue[] 	= "\e[1;34m" "\e[5;47m";
+static const char magenta[] 	= "\e[1;35m" "\e[5;47m";
+static const char cyan[] 	= "\e[1;36m" "\e[5;47m";
+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 */
+
+#if 1		/* new in version 1.2, as suggested by IW2DHW */
+		/* Test done using gnome-terminal and xterm */
+
+static const char background_white[] = "\e[48;2;255;255;255m";
+
+/* Whenever a dark color is used, the */
+/* background is reset and needs to be set again. */
+
+
+static const char black[]	= "\e[0;30m" "\e[48;2;255;255;255m";
+static const char red[] 	= "\e[0;31m" "\e[48;2;255;255;255m";
+static const char green[] 	= "\e[0;32m" "\e[48;2;255;255;255m";
+static const char yellow[] 	= "\e[0;33m" "\e[48;2;255;255;255m";
+static const char blue[] 	= "\e[0;34m" "\e[48;2;255;255;255m";
+static const char magenta[] 	= "\e[0;35m" "\e[48;2;255;255;255m";
+static const char cyan[] 	= "\e[0;36m" "\e[48;2;255;255;255m";
+static const char dark_green[]	= "\e[0;32m" "\e[48;2;255;255;255m";
+
+
+#else 		/* from version 1.1 */
+
+
+static const char background_white[] = "\e[47;1m";
+
+/* Whenever a dark color is used, the */
+/* background is reset and needs to be set again. */
+
+static const char black[]	= "\e[0;30m" "\e[1;47m";
+static const char red[] 	= "\e[1;31m" "\e[1;47m";
+static const char green[] 	= "\e[1;32m" "\e[1;47m"; 
+static const char yellow[] 	= "\e[1;33m" "\e[1;47m";
+static const char blue[] 	= "\e[1;34m" "\e[1;47m";
+static const char magenta[] 	= "\e[1;35m" "\e[1;47m";
+static const char cyan[] 	= "\e[1;36m" "\e[1;47m";
+static const char dark_green[]	= "\e[0;32m" "\e[1;47m";
+
+
+#endif
+
+
+/* Clear from cursor to end of screen. */
+
+static const char clear_eos[]	= "\e[0J";
+
+#endif	/* end Linux */
+
+
+#include "textcolor.h"
+
+
+/*
+ * g_enable_color:
+ *	0 = disable text colors.
+ *	1 = normal.
+ *	others... future possibility.
+ */
+
+static int g_enable_color = 1;
+
+
+void text_color_init (int enable_color)
+{
+
+	g_enable_color = enable_color;
+
+
+#if __WIN32__
+
+
+	if (g_enable_color) {
+
+	  HANDLE h;
+	  CONSOLE_SCREEN_BUFFER_INFO csbi;
+	  WORD attr = BACKGROUND_WHITE;
+	  DWORD length;
+	  COORD coord;
+	  DWORD nwritten;
+
+	  h = GetStdHandle(STD_OUTPUT_HANDLE);
+	  if (h != NULL && h != INVALID_HANDLE_VALUE) {
+
+	    GetConsoleScreenBufferInfo (h, &csbi);
+
+	    length = csbi.dwSize.X * csbi.dwSize.Y;
+	    coord.X = 0; 
+	    coord.Y = 0;
+	    FillConsoleOutputAttribute (h, attr, length, coord, &nwritten);
+	  }
+	}
+
+#else
+	if (g_enable_color) {
+	  //printf ("%s", clear_eos);
+	  printf ("%s", background_white);
+	  printf ("%s", clear_eos);
+	  printf ("%s", black);
+	}
+#endif
+}
+
+
+#if __WIN32__
+
+/* Seems that ANSI.SYS is no longer available. */
+
+
+void text_color_set ( enum dw_color_e c )
+{
+	WORD attr;
+	HANDLE h;
+
+	if (g_enable_color == 0) {
+	  return;
+	}
+
+	switch (c) {
+
+	  default:
+	  case DW_COLOR_INFO:
+	    attr = BACKGROUND_WHITE;
+	    break;
+
+	  case DW_COLOR_ERROR:
+	    attr = FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
+	    break;
+
+	  case DW_COLOR_REC:
+	    attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
+	    break;
+
+	  case DW_COLOR_DECODED:
+	    attr = FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
+	    break;
+
+	  case DW_COLOR_XMIT:
+	    attr = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE;
+	    break;
+
+	  case DW_COLOR_DEBUG:
+	    attr = FOREGROUND_GREEN | BACKGROUND_WHITE;
+	    break;
+	}
+
+	h = GetStdHandle(STD_OUTPUT_HANDLE);
+
+	if (h != NULL && h != INVALID_HANDLE_VALUE) {
+	  SetConsoleTextAttribute (h, attr);
+	}
+}
+
+#else
+
+void text_color_set ( enum dw_color_e c )
+{
+
+	if (g_enable_color == 0) {
+	  return;
+	}
+
+	switch (c) {
+
+	  default:
+	  case DW_COLOR_INFO:
+	    printf ("%s", black);
+	    break;
+
+	  case DW_COLOR_ERROR:
+	    printf ("%s", red);
+	    break;
+
+	  case DW_COLOR_REC:
+	    printf ("%s", green);
+	    break;
+
+	  case DW_COLOR_DECODED:
+	    printf ("%s", blue);
+	    break;
+
+	  case DW_COLOR_XMIT:
+	    printf ("%s", magenta);
+	    break;
+
+	  case DW_COLOR_DEBUG:
+	    printf ("%s", dark_green);
+	    break;
+	}
+}
+
+#endif
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        dw_printf 
+ *
+ * Purpose:     printf replacement that allows us to send all text
+ *		output to stdout or other desired destination.
+ *
+ * Inputs:	fmt	- C language format.
+ *		...	- Addtional arguments, just like printf.
+ *
+ *
+ * Returns:	Number of characters in result.
+ *
+ * Bug:		Fixed size buffer.  
+ *		I'd rather not do a malloc for each print.
+ *
+ *--------------------------------------------------------------------*/
+
+
+// TODO: replace all printf, look for stderr, perror
+// TODO:   $ grep printf *.c | grep -v dw_printf | grep -v fprintf | gawk '{ print $1 }' |  sort -u
+
+
+int dw_printf (const char *fmt, ...) 
+{
+#define BSIZE 1000
+	va_list args;
+	char buffer[BSIZE];
+	int len;
+	
+	va_start (args, fmt);
+	len = vsnprintf (buffer, BSIZE, fmt, args);
+	va_end (args);
+
+// TODO: other possible destinations...
+
+	fputs (buffer, stdout);
+	return (len);
+}
+
+
+
+#if TESTC
+main () 
+{
+	printf ("Initial condition\n");
+	text_color_init (1);
+	printf ("After text_color_init\n");
+	text_color_set(DW_COLOR_INFO); 		printf ("Info\n");
+	text_color_set(DW_COLOR_ERROR); 	printf ("Error\n");
+	text_color_set(DW_COLOR_REC); 		printf ("Rec\n");
+	text_color_set(DW_COLOR_DECODED); 	printf ("Decoded\n");
+	text_color_set(DW_COLOR_XMIT); 		printf ("Xmit\n");
+	text_color_set(DW_COLOR_DEBUG); 	printf ("Debug\n");
+}
+#endif
+	
+/* end textcolor.c */
diff --git a/textcolor.h b/textcolor.h
index 49f3fcb..4e38c83 100644
--- a/textcolor.h
+++ b/textcolor.h
@@ -1,58 +1,58 @@
-
-/*-------------------------------------------------------------------
- *
- * Name:        textcolor.h
- *
- * Purpose:     Set color of text.
- *
- *--------------------------------------------------------------------*/
-
-
-#ifndef TEXTCOLOR_H
-#define TEXTCOLOR_H 1
-
-enum dw_color_e { 	DW_COLOR_INFO,		/* black */
-			DW_COLOR_ERROR,		/* red */
-			DW_COLOR_REC,		/* green */
-			DW_COLOR_DECODED,	/* blue */
-			DW_COLOR_XMIT,		/* magenta */
-			DW_COLOR_DEBUG		/* dark_green */
-		};
-
-typedef enum dw_color_e dw_color_t;
-
-			
-void text_color_init (int enable_color);
-void text_color_set (dw_color_t c);
-void text_color_term (void);
-
-
-/* Degree symbol. */
-
-#if __WIN32__
-
-//#define CH_DEGREE "\xc2\xb0"	/* UTF-8. */
-
-#define CH_DEGREE " "
-
-
-#else
-
-/* Maybe we could change this based on LANG environment variable. */
-
-//#define CH_DEGREE "\xc2\xb0"	/* UTF-8. */
-
-#define CH_DEGREE " "
-
-#endif
-
-
-
-int dw_printf (const char *fmt, ...) 
-#if __WIN32__
-				__attribute__((format(ms_printf,1,2)));		/* Win C lib. */
-#else
-				__attribute__((format(printf,1,2)));		/* gnu C lib. */
-#endif
-
-#endif
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        textcolor.h
+ *
+ * Purpose:     Set color of text.
+ *
+ *--------------------------------------------------------------------*/
+
+
+#ifndef TEXTCOLOR_H
+#define TEXTCOLOR_H 1
+
+enum dw_color_e { 	DW_COLOR_INFO,		/* black */
+			DW_COLOR_ERROR,		/* red */
+			DW_COLOR_REC,		/* green */
+			DW_COLOR_DECODED,	/* blue */
+			DW_COLOR_XMIT,		/* magenta */
+			DW_COLOR_DEBUG		/* dark_green */
+		};
+
+typedef enum dw_color_e dw_color_t;
+
+			
+void text_color_init (int enable_color);
+void text_color_set (dw_color_t c);
+void text_color_term (void);
+
+
+/* Degree symbol. */
+
+#if __WIN32__
+
+//#define CH_DEGREE "\xc2\xb0"	/* UTF-8. */
+
+#define CH_DEGREE " "
+
+
+#else
+
+/* Maybe we could change this based on LANG environment variable. */
+
+//#define CH_DEGREE "\xc2\xb0"	/* UTF-8. */
+
+#define CH_DEGREE " "
+
+#endif
+
+
+
+int dw_printf (const char *fmt, ...) 
+#if __WIN32__
+				__attribute__((format(ms_printf,1,2)));		/* Win C lib. */
+#else
+				__attribute__((format(printf,1,2)));		/* gnu C lib. */
+#endif
+
+#endif
diff --git a/tocalls.txt b/tocalls.txt
index e788899..ac44dfd 100644
--- a/tocalls.txt
+++ b/tocalls.txt
@@ -1,225 +1,217 @@
-APRS TO-CALL VERSION NUMBERS                            27 Apr 2015
--------------------------------------------------------------------
-                                                             WB4APR
-27 Apr 15 added APZMAJ for Martyn M1MAJ DeLorme inReach Tracker
-21 Apr 15 added APB2MF & APR2MF DL2MF - MF2APRS Radiosonde   
-06 Apr 15 added APAVT5 SainSonic AP510 - a 1watt tracker 
-13 Mar 14 added APECAN Pecan Pico APRS Balloon Tracker
-02 Sep 14 added APSTMx W7QO's Balloon trackers
-21 Aug 14 added APSMSx Paul Defrusne's SMS gateway
-11 Aug 14 added APCWP8 John GM7HHB, WinphoneAPRS
-18 Dec 13 added APZWKR GM1WKR NetSked application
-22 Oct 13 added APFIxx APRS.FI OH7LZB, Hessu
-23 Aug 13 added APOxxx OSCAR satellites for AMSAT-LU by LU9DO
-22 Feb 13 added APNWxx SQ3FYK.com & SQ3PLX http://microsat.com.pl/
-            and APMIxx SQ3PLX http://microsat.com.pl/
-29 Jan 13 added APICxx for HA9MCQ Pic IGate
-23 Jan 13 added APWAxx APRSISCE Android version
-18 Jan 13 added APDGxx,APDHxx,APDOxx,APDDxx,APDKxx,APD4xx for Dstar
-13 Jan 13 added APLMxx WA0TQG transceiver controller
-17 Dec 12 added APAMxx Altus Metrum GPS trackers
-03 Dec 12 added APUDRx NW Digital Radio's UDR (APRS/Dstar)
-03 Nov 12 added APHAXn SM2APRS by PY2UEP
-17 Sep 12 added APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK)
-12 Sep 12 added APSARx for ZL4FOX's SARTRACK
-02 Jul 12 added APDGxx D-Star Gateways by G4KLX
-28 Jun 12 added APDInn DIXPRS - Bela, HA5DI
-27 jun 12 added APMGxx MiniGate - Alex, AB0TJ
-17 Feb 12 added APJYnn KA2DDO yet another APRS system
-20 Jan 12 added APDSXX SP9UOB for dsDigi and ds-tracker
-                       APBPQx  John G8BPQ Digipeater/IGate
-                       APLQRU Charlie - QRU Server
-11 Jan 12 added APYTxx for YagTracker and updated Yaesu APY008/350
-
-In APRS, the AX.25  Destination address is not used for packet 
-routing as is normally done in AX.25.  So APRS uses it for two 
-things.  The initial APxxxx is used as a group identifier to make 
-APRS packets instanantly recognizable on shared channels.  Most 
-applicaitons ignore all non APRS packets.  The remaining 4 xxxx
-bytes of the field are available to indicate the software version 
-number or application.  The following applications have requested 
-a TOCALL number series:
-
- APn  3rd digit is a number
-      AP1WWX  TAPR T-238+ WX station
-      AP1MAJ  Martyn M1MAJ DeLorme inReach Tracker
-      AP4Rxy  APRS4R software interface
-      APnnnD  Painter Engineering uSmartDigi D-Gate DSTAR Gateway
-      APnnnU  Painter Engineering uSmartDigi Digipeater
- APA  APAFxx  AFilter.
-      APAGxx  AGATE
-      APAGWx  SV2AGW's AGWtracker
-      APALxx  Alinco DR-620/635 internal TNC digis. "Hachi" ,JF1AJE
-      APAXxx  AFilterX.
-      APAHxx  AHub
-      APAND1  APRSdroid (replaced by APDRxx
-      APAMxx  Altus Metrum GPS trackers
-      APAVT5  SainSonic AP510 which is a 1watt tracker
-      APAWxx  AGWPE 
- APB  APBxxx  Beacons or Rabbit TCPIP micros?
-      APB2MF  DL2MF - MF2APRS Radiosonde for balloons
-      APBLxx  BigRedBee BeeLine  
-      APBLO   MOdel Rocketry K7RKT
-      APBPQx  John G8BPQ Digipeater/IGate 
- APC  APCxxx  Cellular applications
-      APCBBx  for VE7UDP Blackberry Applications
-      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 
-      APDFxx  Automatic DF units
-      APDGxx  D-Star Gateways by G4KLX ircDDB
-      APDHxx  WinDV (DUTCH*Star DV Node for Windows)
-      APDInn  DIXPRS - Bela, HA5DI
-      APDKxx  KI4LKF g2_ircddb Dstar gateway software
-      APDOxx  ON8JL Standalone DStar Node
-      APDPRS  D-Star originated posits
-      APDRxx  APrsDRoid replaces old APAND1.
-      APDSXX  SP9UOB for dsDigi and ds-tracker
-      APDTxx  APRStouch Tone (DTMF)
-      APDUxx  U2APRS by JA7UDE
-      APDWxx  DireWolf, WB2OSZ
- APE  APExxx  Telemetry devices
-      APECAN  Pecan Pico APRS Balloon Tracker
-      APERXQ  Experimental tracker by PE1RXQ
- APF  APFxxx  Firenet
-      APFGxx  Flood Gage (KP4DJT)
-      APFIxx  for APRS.FI OH7LZB, Hessu
- APG  APGxxx  Gates, etc
-      APGOxx  for AA3NJ PDA application
- APH  APHKxx  for LA1BR tracker/digipeater
-      APHAXn  SM2APRS by PY2UEP
- API  APICQx  for ICQ
-      APICxx  for HA9MCQ Pic IGate
- APJ  APJAxx  JavAPRS
-      APJExx  JeAPRS
-      APJIxx  jAPRSIgate
-      APJSxx  javAPRSSrvr
-      APJYnn  KA2DDO Yet another APRS system
- APK  APK0xx  Kenwood TH-D7's
-      APK003  Kenwood TH-D72
-      APK1xx  Kenwood D700's
-      APK102  Kenwood D710
-      APKRAM  KRAMstuff.com - Mark. G7LEU
- APL  APLQRU  Charlie - QRU Server
-      APLMxx  WA0TQG transceiver controller
- APM  APMxxx  MacAPRS, 
-      APMGxx  MiniGate - Alex, AB0TJ
-      APMIxx  SQ3PLX http://microsat.com.pl/
- APN  APNxxx  Network nodes, digis, etc
-      APN3xx  Kantronics KPC-3 rom versions
-      APN9xx  Kantronics KPC-9612 Roms
-      APNAxx  WB6ZSU's APRServe
-      APNDxx  DIGI_NED
-      APNK01  Kenwood D700 (APK101) type
-      APNK80  KAM version 8.0
-      APNKMP  KAM+
-      APNMxx  MJF TNC roms
-      APNPxx  Paccom TNC roms
-      APNTxx  SV2AGW's TNT tnc as a digi
-      APNUxx  UIdigi
-      APNXxx  TNC-X  (K6DBG)
-      APNWxx  SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/
- APO  APRSpoint
-      APOLUx  for OSCAR satellites for AMSAT-LU by LU9DO
-      APOAxx  OpenAPRS - Greg Carter
-      APOTxx  Open Track
-      APOD1w  Open Track with 1 wire WX
-      APOU2k  Open Track for Ultimeter
-      APOZxx  www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO
- APP  APPTxx  KetaiTracker by  JF6LZE, Takeki (msg capable)
- APQ  APQxxx  Earthquake data
- APR  APR8xx  APRSdos versions 800+
-      APR2MF  DL2MF - MF2APRS Radiosonde WX reporting
-      APRDxx  APRSdata, APRSdr
-      APRGxx  aprsg igate software, OH2GVE
-      APRHH2  HamHud 2
-      APRKxx  APRStk
-      APRNOW  W5GGW ipad application
-      APRRTx  RPC electronics
-      APRS    Generic, (obsolete. Digis should use APNxxx instead)
-      APRXxx  >40 APRSmax
-      APRXxx  <39 for OH2MQK's RX-igate
-      APRTLM  used in MIM's and Mic-lites, etc
-      APRtfc  APRStraffic
-      APRSTx  APRStt (Touch tone)
- 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
-      APT3xx  Tiny Track III
-      APTAxx  K4ATM's tiny track
-      APTWxx  Byons WXTrac
-      APTVxx  for ATV/APRN and SSTV applications
- APU  APU1xx  UIview 16 bit applications
-      APU2xx  UIview 32 bit apps
-      APU3xx  UIview terminal program
-      APUDRx  NW Digital Radio's UDR (APRS/Dstar)
- APV  APVxxx  Voice over Internet  applications
-      APVRxx  for IRLP
-      APVLxx  for I-LINK
-      APVExx  for ECHO link
- APW  APWxxx  WinAPRS, etc
-      APWAxx  APRSISCE Android version
-      APWSxx  DF4IAN's WS2300 WX station
-      APWMxx  APRSISCE KJ4ERJ
-      APWWxx  APRSISCE win32 version
- APX  APXnnn  Xastir
-      APXRnn  Xrouter
- APY  APYxxx  Yeasu
-      APY008  Yaesu VX-8 series
-      APY350  Yaesu FTM-350 series
-      APYTxx  for YagTracker
- APZ  APZxxx  Experimental
-      APZ0xx  Xastir (old versions. See APX)
-      APZMAJ  Martyn M1MAJ DeLorme inReach Tracker
-      APZMDR  for HaMDR trackers - hessu * hes.iki.fi]
-      APZPAD  Smart Palm 
-      APZTKP  TrackPoint, Nick N0LP (Balloon tracking)
-      APZWIT  MAP27 radio (Mountain Rescue) EI7IG
-      APZWKR  GM1WKR NetSked application
-
-Authors with similar alphabetic requirements are encouraged to share
-their address space with other software.  Work out agreements amongst
-yourselves and keep me informed.
-
-
-REGISTERED ALTNETS:
--------------------
-
-ALTNETS are uses of the AX-25 tocall to distinguish specialized
-traffic that may be flowing on the APRS-IS, but that are not intended
-to be part of normal APRS distribution to all normal APRS software
-operating in normal (default) modes.  Proper APRS software that
-honors this design are supposed to IGNORE all ALTNETS unless the 
-particular operator has selected an ALTNET to monitor for.    
-
-An example is when testing; an author may want to transmit objects 
-all over his map for on-air testing, but does not want these to
-clutter everyone's maps or databases.  He could use the ALTNET of
-"TEST" and client APRS software that respects the ALTNET concept
-should ignore these packets.
-
-An ALTNET is defined to be ANY AX.25 TOCALL that is NOT one of the
-normal APRS TOCALL's.  The normal TOCALL's that APRS is supposed to
-process are:  ALL, BEACON, CQ, QST, GPSxxx and of course APxxxx.
-
-The following is a list of ALTNETS that may be of interest to other
-users.  This list is by no means complete, since ANY combination of
-characters other than APxxxx are considered an ALTNET.  But this list 
-can give consisntecy to ALTNETS that may be using the global APRS-IS 
-and need some special recognition:
-
-  TEST   - A generic ALTNET for use during testing
-  PSKAPR - PSKmail .  But it is not AX.25 anyway
-
-de WB4APR, Bob
+APRS TO-CALL VERSION NUMBERS                            21 Jan 2016
+-------------------------------------------------------------------
+                                                             WB4APR
+21 Jan 16 added APDNOx for APRSduino by DO3SWW
+18 Nov 15 Added APSTPO for N0AGI
+03 Nov 15 Updated APAND1 and APDRxx for androids
+26 Oct 15 added APZ247 for UPRS
+09 Sep 15 added APHTxx for HMTracker by IU0AAC 
+06 Aug 15 added APMTxx for LZ1PPL for tracker
+27 Apr 15 added APZMAJ for Martyn M1MAJ DeLorme inReach Tracker
+21 Apr 15 added APB2MF & APR2MF DL2MF - MF2APRS Radiosonde   
+06 Apr 15 added APAVT5 SainSonic AP510 - a 1watt tracker 
+13 Mar 14 added APECAN Pecan Pico APRS Balloon Tracker
+02 Sep 14 added APSTMx W7QO's Balloon trackers
+21 Aug 14 added APSMSx Paul Defrusne's SMS gateway
+11 Aug 14 added APCWP8 John GM7HHB, WinphoneAPRS
+18 Dec 13 added APZWKR GM1WKR NetSked application
+
+11 Jan 12 added APYTxx for YagTracker and updated Yaesu APY008/350
+
+In APRS, the AX.25  Destination address is not used for packet 
+routing as is normally done in AX.25.  So APRS uses it for two 
+things.  The initial APxxxx is used as a group identifier to make 
+APRS packets instanantly recognizable on shared channels.  Most 
+applicaitons ignore all non APRS packets.  The remaining 4 xxxx
+bytes of the field are available to indicate the software version 
+number or application.  The following applications have requested 
+a TOCALL number series:
+
+ APn  3rd digit is a number
+      AP1WWX  TAPR T-238+ WX station
+      AP1MAJ  Martyn M1MAJ DeLorme inReach Tracker
+      AP4Rxy  APRS4R software interface
+      APnnnD  Painter Engineering uSmartDigi D-Gate DSTAR Gateway
+      APnnnU  Painter Engineering uSmartDigi Digipeater
+ APA  APAFxx  AFilter.
+      APAGxx  AGATE
+      APAGWx  SV2AGW's AGWtracker
+      APALxx  Alinco DR-620/635 internal TNC digis. "Hachi" ,JF1AJE
+      APAXxx  AFilterX.
+      APAHxx  AHub
+      APAND1  APRSdroid (pre-release) http://aprsdroid.org/
+      APAMxx  Altus Metrum GPS trackers
+      APAVT5  SainSonic AP510 which is a 1watt tracker
+      APAWxx  AGWPE 
+ APB  APBxxx  Beacons or Rabbit TCPIP micros?
+      APB2MF  DL2MF - MF2APRS Radiosonde for balloons
+      APBLxx  BigRedBee BeeLine  
+      APBLO   MOdel Rocketry K7RKT
+      APBPQx  John G8BPQ Digipeater/IGate 
+ APC  APCxxx  Cellular applications
+      APCBBx  for VE7UDP Blackberry Applications
+      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 
+      APDFxx  Automatic DF units
+      APDGxx  D-Star Gateways by G4KLX ircDDB
+      APDHxx  WinDV (DUTCH*Star DV Node for Windows)
+      APDInn  DIXPRS - Bela, HA5DI
+      APDKxx  KI4LKF g2_ircddb Dstar gateway software
+      APDNOx  APRSduino by DO3SWW
+      APDOxx  ON8JL Standalone DStar Node
+      APDPRS  D-Star originated posits
+      APDRxx  APRSdroid Android App http://aprsdroid.org/
+      APDSXX  SP9UOB for dsDigi and ds-tracker
+      APDTxx  APRStouch Tone (DTMF)
+      APDUxx  U2APRS by JA7UDE
+      APDWxx  DireWolf, WB2OSZ
+ APE  APExxx  Telemetry devices
+      APECAN  Pecan Pico APRS Balloon Tracker
+      APERXQ  Experimental tracker by PE1RXQ
+ APF  APFxxx  Firenet
+      APFGxx  Flood Gage (KP4DJT)
+      APFIxx  for APRS.FI OH7LZB, Hessu
+ APG  APGxxx  Gates, etc
+      APGOxx  for AA3NJ PDA application
+ APH  APHKxx  for LA1BR tracker/digipeater
+      APHAXn  SM2APRS by PY2UEP
+      APHTxx  HMTracker by IU0AAC 
+ API  APICQx  for ICQ
+      APICxx  for HA9MCQ Pic IGate
+ APJ  APJAxx  JavAPRS
+      APJExx  JeAPRS
+      APJIxx  jAPRSIgate
+      APJSxx  javAPRSSrvr
+      APJYnn  KA2DDO Yet another APRS system
+ APK  APK0xx  Kenwood TH-D7's
+      APK003  Kenwood TH-D72
+      APK1xx  Kenwood D700's
+      APK102  Kenwood D710
+      APKRAM  KRAMstuff.com - Mark. G7LEU
+ APL  APLQRU  Charlie - QRU Server
+      APLMxx  WA0TQG transceiver controller
+ APM  APMxxx  MacAPRS, 
+      APMGxx  MiniGate - Alex, AB0TJ
+      APMIxx  SQ3PLX http://microsat.com.pl/
+      APMTxx  LZ1PPL for tracker
+ APN  APNxxx  Network nodes, digis, etc
+      APN3xx  Kantronics KPC-3 rom versions
+      APN9xx  Kantronics KPC-9612 Roms
+      APNAxx  WB6ZSU's APRServe
+      APNDxx  DIGI_NED
+      APNK01  Kenwood D700 (APK101) type
+      APNK80  KAM version 8.0
+      APNKMP  KAM+
+      APNMxx  MJF TNC roms
+      APNPxx  Paccom TNC roms
+      APNTxx  SV2AGW's TNT tnc as a digi
+      APNUxx  UIdigi
+      APNXxx  TNC-X  (K6DBG)
+      APNWxx  SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/
+ APO  APRSpoint
+      APOLUx  for OSCAR satellites for AMSAT-LU by LU9DO
+      APOAxx  OpenAPRS - Greg Carter
+      APOTxx  Open Track
+      APOD1w  Open Track with 1 wire WX
+      APOU2k  Open Track for Ultimeter
+      APOZxx  www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO
+ APP  APPTxx  KetaiTracker by  JF6LZE, Takeki (msg capable)
+ APQ  APQxxx  Earthquake data
+ APR  APR8xx  APRSdos versions 800+
+      APR2MF  DL2MF - MF2APRS Radiosonde WX reporting
+      APRDxx  APRSdata, APRSdr
+      APRGxx  aprsg igate software, OH2GVE
+      APRHH2  HamHud 2
+      APRKxx  APRStk
+      APRNOW  W5GGW ipad application
+      APRRTx  RPC electronics
+      APRS    Generic, (obsolete. Digis should use APNxxx instead)
+      APRXxx  >40 APRSmax
+      APRXxx  <39 for OH2MQK's RX-igate
+      APRTLM  used in MIM's and Mic-lites, etc
+      APRtfc  APRStraffic
+      APRSTx  APRStt (Touch tone)
+ 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
+      APSTPO  for N0AGI Satellite Tracking and Operations
+ APT  APTIGR  TigerTrack
+      APTTxx  Tiny Track
+      APT2xx  Tiny Track II
+      APT3xx  Tiny Track III
+      APTAxx  K4ATM's tiny track
+      APTWxx  Byons WXTrac
+      APTVxx  for ATV/APRN and SSTV applications
+ APU  APU1xx  UIview 16 bit applications
+      APU2xx  UIview 32 bit apps
+      APU3xx  UIview terminal program
+      APUDRx  NW Digital Radio's UDR (APRS/Dstar)
+ APV  APVxxx  Voice over Internet  applications
+      APVRxx  for IRLP
+      APVLxx  for I-LINK
+      APVExx  for ECHO link
+ APW  APWxxx  WinAPRS, etc
+      APWAxx  APRSISCE Android version
+      APWSxx  DF4IAN's WS2300 WX station
+      APWMxx  APRSISCE KJ4ERJ
+      APWWxx  APRSISCE win32 version
+ APX  APXnnn  Xastir
+      APXRnn  Xrouter
+ APY  APYxxx  Yeasu
+      APY008  Yaesu VX-8 series
+      APY350  Yaesu FTM-350 series
+      APYTxx  for YagTracker
+ APZ  APZxxx  Experimental
+      APZ247  for UPRS NR0Q
+      APZ0xx  Xastir (old versions. See APX)
+      APZMAJ  Martyn M1MAJ DeLorme inReach Tracker
+      APZMDR  for HaMDR trackers - hessu * hes.iki.fi]
+      APZPAD  Smart Palm 
+      APZTKP  TrackPoint, Nick N0LP (Balloon tracking)
+      APZWIT  MAP27 radio (Mountain Rescue) EI7IG
+      APZWKR  GM1WKR NetSked application
+
+Authors with similar alphabetic requirements are encouraged to share
+their address space with other software.  Work out agreements amongst
+yourselves and keep me informed.
+
+
+REGISTERED ALTNETS:
+-------------------
+
+ALTNETS are uses of the AX-25 tocall to distinguish specialized
+traffic that may be flowing on the APRS-IS, but that are not intended
+to be part of normal APRS distribution to all normal APRS software
+operating in normal (default) modes.  Proper APRS software that
+honors this design are supposed to IGNORE all ALTNETS unless the 
+particular operator has selected an ALTNET to monitor for.    
+
+An example is when testing; an author may want to transmit objects 
+all over his map for on-air testing, but does not want these to
+clutter everyone's maps or databases.  He could use the ALTNET of
+"TEST" and client APRS software that respects the ALTNET concept
+should ignore these packets.
+
+An ALTNET is defined to be ANY AX.25 TOCALL that is NOT one of the
+normal APRS TOCALL's.  The normal TOCALL's that APRS is supposed to
+process are:  ALL, BEACON, CQ, QST, GPSxxx and of course APxxxx.
+
+The following is a list of ALTNETS that may be of interest to other
+users.  This list is by no means complete, since ANY combination of
+characters other than APxxxx are considered an ALTNET.  But this list 
+can give consisntecy to ALTNETS that may be using the global APRS-IS 
+and need some special recognition:
+
+  TEST   - A generic ALTNET for use during testing
+  PSKAPR - PSKmail .  But it is not AX.25 anyway
+
+de WB4APR, Bob
diff --git a/tq.c b/tq.c
index 54ad4b1..88ad59e 100644
--- a/tq.c
+++ b/tq.c
@@ -1,557 +1,561 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2012, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      tq.c
- *
- * Purpose:   	Transmit queue - hold packets for transmission until the channel is clear.
- *		
- * Description:	Producers of packets to be transmitted call tq_append and then
- *		go merrily on their way, unconcerned about when the packet might
- *		actually get transmitted.
- *
- *		Another thread waits until the channel is clear and then removes
- *		packets from the queue and transmits them.
- *
- * Revisions:	1.2 - Enhance for multiple audio devices.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "tq.h"
-#include "dedupe.h"
-
-
-
-
-static packet_t queue_head[MAX_CHANS][TQ_NUM_PRIO];	/* Head of linked list for each queue. */
-
-
-static dw_mutex_t tq_mutex;				/* Critical section for updating queues. */
-							/* Just one for all queues. */
-
-#if __WIN32__
-
-static HANDLE wake_up_event[MAX_CHANS];			/* Notify transmit thread when queue not empty. */
-
-#else
-
-static pthread_cond_t wake_up_cond[MAX_CHANS];		/* Notify transmit thread when queue not empty. */
-
-static pthread_mutex_t wake_up_mutex[MAX_CHANS];	/* Required by cond_wait. */
-
-static int xmit_thread_is_waiting[MAX_CHANS];
-
-#endif
-
-static int tq_is_empty (int chan);
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        tq_init
- *
- * Purpose:     Initialize the transmit queue.
- *
- * Inputs:	audio_config_p	- Audio device configuration.
- *
- * Outputs:	
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *		We have different timing rules for different types of
- *		packets so they are put into different queues.
- *
- *		High Priority -
- *
- *			Packets which are being digipeated go out first.
- *			Latest recommendations are to retransmit these
- *			immdediately (after no one else is heard, of course)
- *			rather than waiting random times to avoid collisions.
- *			The KPC-3 configuration option for this is "UIDWAIT OFF".
- *
- *		Low Priority - 
- *
- *			Other packets are sent after a random wait time
- *			(determined by PERSIST & SLOTTIME) to help avoid
- *			collisions.	
- *
- *		Each audio channel has its own queue.
- *	
- *--------------------------------------------------------------------*/
-
-
-static struct audio_s *save_audio_config_p;
-
-
-
-void tq_init (struct audio_s *audio_config_p)
-{
-	int c, p;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_init ( %d )\n", nchan);
-#endif
-
-	save_audio_config_p = audio_config_p;
-
-	for (c=0; c<MAX_CHANS; c++) {
-	  for (p=0; p<TQ_NUM_PRIO; p++) {
-	    queue_head[c][p] = NULL;
-	  }
-	}
-
-/*
- * Mutex to coordinate access to the queue.
- */
-
-	dw_mutex_init(&tq_mutex);
-
-/*
- * Windows and Linux have different wake up methods.
- * Put a wrapper around this someday to hide the details.
- */
-
-#if __WIN32__
-
-	for (c = 0; c < MAX_CHANS; c++) {
-
-	  if (audio_config_p->achan[c].valid) {
-
-	    wake_up_event[c] = CreateEvent (NULL, 0, 0, NULL);
-
-	    if (wake_up_event[c] == NULL) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("tq_init: CreateEvent: can't create transmit wake up event, c=%d", c);
-	      exit (1);
-	    }	
-	  }
-	}
-
-#else
-	int err;
-
-	for (c = 0; c < MAX_CHANS; c++) {
-
-	  xmit_thread_is_waiting[c] = 0;
-
-	  if (audio_config_p->achan[c].valid) {
-	    err = pthread_cond_init (&(wake_up_cond[c]), NULL);
-	    if (err != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("tq_init: pthread_cond_init c=%d err=%d", c, err);
-	      perror ("");
-	      exit (1);
-	    }
-
-	    dw_mutex_init(&(wake_up_mutex[c]));
-	  }
-	}
-
-#endif
-
-} /* end tq_init */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        tq_append
- *
- * Purpose:     Add a packet to the end of the specified transmit queue.
- *
- * Inputs:	chan	- Channel, 0 is first.
- *
- *		prio	- Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
- *
- *		pp	- Address of packet object.
- *				Caller should NOT make any references to
- *				it after this point because it could
- *				be deleted at any time.
- *
- * Outputs:	
- *
- * Description:	Add packet to end of linked list.
- *		Signal the transmit thread if the queue was formerly empty.
- *
- *		Note that we have a transmit thread each audio channel.
- *		Two channels can share one audio output device.
- *
- * IMPORTANT!	Don't make an further references to the packet object after
- *		giving it to tq_append.
- *
- *--------------------------------------------------------------------*/
-
-void tq_append (int chan, int prio, packet_t pp)
-{
-	packet_t plast;
-	packet_t pnext;
-	//int a = ACHAN2ADEV(chan);		/* Audio device for channel. */
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_append (chan=%d, prio=%d, pp=%p)\n", chan, prio, pp);
-#endif
-
-
-
-	assert (chan >= 0 && chan < MAX_CHANS);
-	assert (prio >= 0 && prio < TQ_NUM_PRIO);
-
-#if AX25MEMDEBUG
-
-	if (ax25memdebug_get()) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("tq_append (chan=%d, prio=%d, seq=%d)\n", chan, prio, ax25memdebug_seq(pp));
-	}
-#endif
-
-	if ( ! save_audio_config_p->achan[chan].valid) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
-	  ax25_delete(pp);
-	  return;
-	}
-
-/* 
- * Is transmit queue out of control? 
- *
- * There is no technical reason to limit the transmit packet queue length, it just seemed like a good 
- * warning that something wasn�t right.
- * When this was written, I was mostly concerned about APRS where packets would only be sent 
- * occasionally and they can be discarded if they can�t be sent out in a reasonable amount of time.
- *
- * If a large file is being sent, with TCP/IP, it is perfectly reasonable to have a large number 
- * of packets waiting for transmission.
- *
- * Ideally, the application should be able to throttle the transmissions so the queue doesn't get too long.
- * If using the KISS interface, there is no way to get this information from the TNC back to the client app.
- * The AGW network interface does have a command �y� to query about the number of frames waiting for transmission.
- * This was implemented in version 1.2.
- *
- * I'd rather not take out the queue length check because it is a useful sanity check for something going wrong.
- * Maybe the check should be performed only for APRS packets.  
- * The check would allow an unlimited number of other types.
- *
- * Limit was 20.  Changed to 100 in version 1.2 as a workaround.
- *
- * Implementing the 6PACK protocol is probably the proper solution.
- */
-
-	if (ax25_is_aprs(pp) && tq_count(chan,prio) > 100) {
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Transmit packet queue for channel %d is too long.  Discarding packet.\n", chan);
-	  dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n");
-	  ax25_delete(pp);
-	  return;
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_append: enter critical section\n");
-#endif
-
-	dw_mutex_lock (&tq_mutex);
-
-//	was_empty = 1;
-//	for (c=0; c<tq_num_channels; c++) {
-//	  for (p=0; p<TQ_NUM_PRIO; p++) {
-//	    if (queue_head[c][p] != NULL)
-//	       was_empty = 0;
-//	  }
-//	}
-
-	if (queue_head[chan][prio] == NULL) {
-	  queue_head[chan][prio] = pp;
-	}
-	else {
-	  plast = queue_head[chan][prio];
-	  while ((pnext = ax25_get_nextp(plast)) != NULL) {
-	    plast = pnext;
-	  }
-	  ax25_set_nextp (plast, pp);
-	}
-
-	dw_mutex_unlock (&tq_mutex);
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_append: left critical section\n");
-	dw_printf ("tq_append (): about to wake up xmit thread.\n");
-#endif
-
-#if __WIN32__
-	SetEvent (wake_up_event[chan]);
-#else
-	if (xmit_thread_is_waiting[chan]) {
-	  int err;
-
-	  dw_mutex_lock (&(wake_up_mutex[chan]));
-
-	  err = pthread_cond_signal (&(wake_up_cond[chan]));
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("tq_append: pthread_cond_signal err=%d", err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  dw_mutex_unlock (&(wake_up_mutex[chan]));
-	}
-#endif
-
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        tq_wait_while_empty
- *
- * Purpose:     Sleep while the transmit queue is empty rather than
- *		polling periodically.
- *
- * Inputs:	chan	- Audio device number.  
- *
- * Description:	We have one transmit thread for each audio device.
- *		This handles 1 or 2 channels.
- *		
- *--------------------------------------------------------------------*/
-
-
-void tq_wait_while_empty (int chan)
-{
-	int is_empty;
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_wait_while_empty (%d) : enter critical section\n", chan);
-#endif
-	assert (chan >= 0 && chan < MAX_CHANS);
-
-	dw_mutex_lock (&tq_mutex);
-
-#if DEBUG
-	//text_color_set(DW_COLOR_DEBUG);
-	//dw_printf ("tq_wait_while_empty (%d): after pthread_mutex_lock\n", chan);
-#endif
-	is_empty = tq_is_empty(chan);
-
-	dw_mutex_unlock (&tq_mutex);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_wait_while_empty (%d) : left critical section\n", chan);
-#endif
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_wait_while_empty (%d): is_empty = %d\n", chan, is_empty);
-#endif
-
-	if (is_empty) {
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("tq_wait_while_empty (%d): SLEEP - about to call cond wait\n", chan);
-#endif
-
-
-#if __WIN32__
-	  WaitForSingleObject (wake_up_event[chan], INFINITE);
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("tq_wait_while_empty (): returned from wait\n");
-#endif
-
-#else
-	  dw_mutex_lock (&(wake_up_mutex[chan]));
-
-	  xmit_thread_is_waiting[chan] = 1;
-	  int err;
-	  err = pthread_cond_wait (&(wake_up_cond[chan]), &(wake_up_mutex[chan]));
-	  xmit_thread_is_waiting[chan] = 0;
-
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("tq_wait_while_empty (%d): WOKE UP - returned from cond wait, err = %d\n", chan, err);
-#endif
-
-	  if (err != 0) {
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("tq_wait_while_empty (%d): pthread_cond_wait err=%d", chan, err);
-	    perror ("");
-	    exit (1);
-	  }
-
-	  dw_mutex_unlock (&(wake_up_mutex[chan]));
-#endif
-	}
-
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_wait_while_empty (%d) returns\n", chan);
-#endif
-
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        tq_remove
- *
- * Purpose:     Remove a packet from the head of the specified transmit queue.
- *
- * Inputs:	chan	- Channel, 0 is first.
- *
- *		prio	- Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
- *
- * Returns:	Pointer to packet object.
- *		Caller should destroy it with ax25_delete when finished with it.	
- *
- *--------------------------------------------------------------------*/
-
-packet_t tq_remove (int chan, int prio)
-{
-
-	packet_t result_p;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_remove(%d,%d) enter critical section\n", chan, prio);
-#endif
-
-	dw_mutex_lock (&tq_mutex);
-
-	if (queue_head[chan][prio] == NULL) {
-
-	  result_p = NULL;
-	}
-	else {
-
-	  result_p = queue_head[chan][prio];
-	  queue_head[chan][prio] = ax25_get_nextp(result_p);
-	  ax25_set_nextp (result_p, NULL);
-	}
-	 
-	dw_mutex_unlock (&tq_mutex);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_remove(%d,%d) leave critical section, returns %p\n", chan, prio, result_p);
-#endif
-
-#if AX25MEMDEBUG
-
-	if (ax25memdebug_get() && result_p != NULL) {
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("tq_remove (chan=%d, prio=%d)  seq=%d\n", chan, prio, ax25memdebug_seq(result_p));
-	}
-#endif
-	return (result_p);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        tq_is_empty
- *
- * Purpose:     Test if queues for specified channel are empty.
- *		
- * Inputs:	chan		Channel 
- *
- * Returns:	True if nothing in the queue.	
- *
- *--------------------------------------------------------------------*/
-
-static int tq_is_empty (int chan)
-{
-	int p;
-	
-	assert (chan >= 0 && chan < MAX_CHANS);
-
-
-	for (p=0; p<TQ_NUM_PRIO; p++) {
-
-	  assert (p >= 0 && p < TQ_NUM_PRIO);
-
-	  if (queue_head[chan][p] != NULL)
-	     return (0);
-	}
-
-	return (1);
-
-} /* end tq_is_empty */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        tq_count
- *
- * Purpose:     Return count of the number of packets in the specified transmit queue.
- *
- * Inputs:	chan	- Channel, 0 is first.
- *
- *		prio	- Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
- *
- * Returns:	Number of items in specified queue.	
- *
- *--------------------------------------------------------------------*/
-
-int tq_count (int chan, int prio)
-{
-
-	packet_t p;
-	int n;
-
-
-/* Don't bother with critical section. */
-/* Only used for debugging a problem. */
-
-	n = 0;
-	p = queue_head[chan][prio];
-	while (p != NULL) {
-	  n++;
-	  p = ax25_get_nextp(p);
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("tq_count(%d,%d) returns %d\n", chan, prio, n);
-#endif
-
-	return (n);
-
-} /* end tq_count */
-
-/* end tq.c */
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2012, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      tq.c
+ *
+ * Purpose:   	Transmit queue - hold packets for transmission until the channel is clear.
+ *		
+ * Description:	Producers of packets to be transmitted call tq_append and then
+ *		go merrily on their way, unconcerned about when the packet might
+ *		actually get transmitted.
+ *
+ *		Another thread waits until the channel is clear and then removes
+ *		packets from the queue and transmits them.
+ *
+ * Revisions:	1.2 - Enhance for multiple audio devices.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "tq.h"
+#include "dedupe.h"
+
+
+
+
+static packet_t queue_head[MAX_CHANS][TQ_NUM_PRIO];	/* Head of linked list for each queue. */
+
+
+static dw_mutex_t tq_mutex;				/* Critical section for updating queues. */
+							/* Just one for all queues. */
+
+#if __WIN32__
+
+static HANDLE wake_up_event[MAX_CHANS];			/* Notify transmit thread when queue not empty. */
+
+#else
+
+static pthread_cond_t wake_up_cond[MAX_CHANS];		/* Notify transmit thread when queue not empty. */
+
+static pthread_mutex_t wake_up_mutex[MAX_CHANS];	/* Required by cond_wait. */
+
+static int xmit_thread_is_waiting[MAX_CHANS];
+
+#endif
+
+static int tq_is_empty (int chan);
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tq_init
+ *
+ * Purpose:     Initialize the transmit queue.
+ *
+ * Inputs:	audio_config_p	- Audio device configuration.
+ *
+ * Outputs:	
+ *
+ * Description:	Initialize the queue to be empty and set up other
+ *		mechanisms for sharing it between different threads.
+ *
+ *		We have different timing rules for different types of
+ *		packets so they are put into different queues.
+ *
+ *		High Priority -
+ *
+ *			Packets which are being digipeated go out first.
+ *			Latest recommendations are to retransmit these
+ *			immdediately (after no one else is heard, of course)
+ *			rather than waiting random times to avoid collisions.
+ *			The KPC-3 configuration option for this is "UIDWAIT OFF".
+ *
+ *		Low Priority - 
+ *
+ *			Other packets are sent after a random wait time
+ *			(determined by PERSIST & SLOTTIME) to help avoid
+ *			collisions.	
+ *
+ *		Each audio channel has its own queue.
+ *	
+ *--------------------------------------------------------------------*/
+
+
+static struct audio_s *save_audio_config_p;
+
+
+
+void tq_init (struct audio_s *audio_config_p)
+{
+	int c, p;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_init ( %d )\n", nchan);
+#endif
+
+	save_audio_config_p = audio_config_p;
+
+	for (c=0; c<MAX_CHANS; c++) {
+	  for (p=0; p<TQ_NUM_PRIO; p++) {
+	    queue_head[c][p] = NULL;
+	  }
+	}
+
+/*
+ * Mutex to coordinate access to the queue.
+ */
+
+	dw_mutex_init(&tq_mutex);
+
+/*
+ * Windows and Linux have different wake up methods.
+ * Put a wrapper around this someday to hide the details.
+ */
+
+#if __WIN32__
+
+	for (c = 0; c < MAX_CHANS; c++) {
+
+	  if (audio_config_p->achan[c].valid) {
+
+	    wake_up_event[c] = CreateEvent (NULL, 0, 0, NULL);
+
+	    if (wake_up_event[c] == NULL) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("tq_init: CreateEvent: can't create transmit wake up event, c=%d", c);
+	      exit (1);
+	    }	
+	  }
+	}
+
+#else
+	int err;
+
+	for (c = 0; c < MAX_CHANS; c++) {
+
+	  xmit_thread_is_waiting[c] = 0;
+
+	  if (audio_config_p->achan[c].valid) {
+	    err = pthread_cond_init (&(wake_up_cond[c]), NULL);
+	    if (err != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("tq_init: pthread_cond_init c=%d err=%d", c, err);
+	      perror ("");
+	      exit (1);
+	    }
+
+	    dw_mutex_init(&(wake_up_mutex[c]));
+	  }
+	}
+
+#endif
+
+} /* end tq_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tq_append
+ *
+ * Purpose:     Add a packet to the end of the specified transmit queue.
+ *
+ * Inputs:	chan	- Channel, 0 is first.
+ *
+ *		prio	- Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
+ *
+ *		pp	- Address of packet object.
+ *				Caller should NOT make any references to
+ *				it after this point because it could
+ *				be deleted at any time.
+ *
+ * Outputs:	
+ *
+ * Description:	Add packet to end of linked list.
+ *		Signal the transmit thread if the queue was formerly empty.
+ *
+ *		Note that we have a transmit thread each audio channel.
+ *		Two channels can share one audio output device.
+ *
+ * IMPORTANT!	Don't make an further references to the packet object after
+ *		giving it to tq_append.
+ *
+ *--------------------------------------------------------------------*/
+
+void tq_append (int chan, int prio, packet_t pp)
+{
+	packet_t plast;
+	packet_t pnext;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_append (chan=%d, prio=%d, pp=%p)\n", chan, prio, pp);
+#endif
+
+
+	assert (prio >= 0 && prio < TQ_NUM_PRIO);
+
+	if (pp == NULL) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("INTERNAL ERROR:  tq_append NULL packet pointer. Please report this!\n");
+	  return;
+	}
+
+#if AX25MEMDEBUG
+
+	if (ax25memdebug_get()) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("tq_append (chan=%d, prio=%d, seq=%d)\n", chan, prio, ax25memdebug_seq(pp));
+	}
+#endif
+
+	if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
+	  ax25_delete(pp);
+	  return;
+	}
+
+/* 
+ * Is transmit queue out of control? 
+ *
+ * There is no technical reason to limit the transmit packet queue length, it just seemed like a good 
+ * warning that something wasn't right.
+ * When this was written, I was mostly concerned about APRS where packets would only be sent 
+ * occasionally and they can be discarded if they can't be sent out in a reasonable amount of time.
+ *
+ * If a large file is being sent, with TCP/IP, it is perfectly reasonable to have a large number 
+ * of packets waiting for transmission.
+ *
+ * Ideally, the application should be able to throttle the transmissions so the queue doesn't get too long.
+ * If using the KISS interface, there is no way to get this information from the TNC back to the client app.
+ * The AGW network interface does have a command 'y' to query about the number of frames waiting for transmission.
+ * This was implemented in version 1.2.
+ *
+ * I'd rather not take out the queue length check because it is a useful sanity check for something going wrong.
+ * Maybe the check should be performed only for APRS packets.  
+ * The check would allow an unlimited number of other types.
+ *
+ * Limit was 20.  Changed to 100 in version 1.2 as a workaround.
+ *
+ * Implementing the 6PACK protocol is probably the proper solution.
+ */
+
+	if (ax25_is_aprs(pp) && tq_count(chan,prio) > 100) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Transmit packet queue for channel %d is too long.  Discarding packet.\n", chan);
+	  dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n");
+	  ax25_delete(pp);
+	  return;
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_append: enter critical section\n");
+#endif
+
+	dw_mutex_lock (&tq_mutex);
+
+//	was_empty = 1;
+//	for (c=0; c<tq_num_channels; c++) {
+//	  for (p=0; p<TQ_NUM_PRIO; p++) {
+//	    if (queue_head[c][p] != NULL)
+//	       was_empty = 0;
+//	  }
+//	}
+
+	if (queue_head[chan][prio] == NULL) {
+	  queue_head[chan][prio] = pp;
+	}
+	else {
+	  plast = queue_head[chan][prio];
+	  while ((pnext = ax25_get_nextp(plast)) != NULL) {
+	    plast = pnext;
+	  }
+	  ax25_set_nextp (plast, pp);
+	}
+
+	dw_mutex_unlock (&tq_mutex);
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_append: left critical section\n");
+	dw_printf ("tq_append (): about to wake up xmit thread.\n");
+#endif
+
+#if __WIN32__
+	SetEvent (wake_up_event[chan]);
+#else
+	if (xmit_thread_is_waiting[chan]) {
+	  int err;
+
+	  dw_mutex_lock (&(wake_up_mutex[chan]));
+
+	  err = pthread_cond_signal (&(wake_up_cond[chan]));
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("tq_append: pthread_cond_signal err=%d", err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  dw_mutex_unlock (&(wake_up_mutex[chan]));
+	}
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tq_wait_while_empty
+ *
+ * Purpose:     Sleep while the transmit queue is empty rather than
+ *		polling periodically.
+ *
+ * Inputs:	chan	- Audio device number.  
+ *
+ * Description:	We have one transmit thread for each audio device.
+ *		This handles 1 or 2 channels.
+ *		
+ *--------------------------------------------------------------------*/
+
+
+void tq_wait_while_empty (int chan)
+{
+	int is_empty;
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_wait_while_empty (%d) : enter critical section\n", chan);
+#endif
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+	dw_mutex_lock (&tq_mutex);
+
+#if DEBUG
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("tq_wait_while_empty (%d): after pthread_mutex_lock\n", chan);
+#endif
+	is_empty = tq_is_empty(chan);
+
+	dw_mutex_unlock (&tq_mutex);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_wait_while_empty (%d) : left critical section\n", chan);
+#endif
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_wait_while_empty (%d): is_empty = %d\n", chan, is_empty);
+#endif
+
+	if (is_empty) {
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("tq_wait_while_empty (%d): SLEEP - about to call cond wait\n", chan);
+#endif
+
+
+#if __WIN32__
+	  WaitForSingleObject (wake_up_event[chan], INFINITE);
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("tq_wait_while_empty (): returned from wait\n");
+#endif
+
+#else
+	  dw_mutex_lock (&(wake_up_mutex[chan]));
+
+	  xmit_thread_is_waiting[chan] = 1;
+	  int err;
+	  err = pthread_cond_wait (&(wake_up_cond[chan]), &(wake_up_mutex[chan]));
+	  xmit_thread_is_waiting[chan] = 0;
+
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("tq_wait_while_empty (%d): WOKE UP - returned from cond wait, err = %d\n", chan, err);
+#endif
+
+	  if (err != 0) {
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("tq_wait_while_empty (%d): pthread_cond_wait err=%d", chan, err);
+	    perror ("");
+	    exit (1);
+	  }
+
+	  dw_mutex_unlock (&(wake_up_mutex[chan]));
+#endif
+	}
+
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_wait_while_empty (%d) returns\n", chan);
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tq_remove
+ *
+ * Purpose:     Remove a packet from the head of the specified transmit queue.
+ *
+ * Inputs:	chan	- Channel, 0 is first.
+ *
+ *		prio	- Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
+ *
+ * Returns:	Pointer to packet object.
+ *		Caller should destroy it with ax25_delete when finished with it.	
+ *
+ *--------------------------------------------------------------------*/
+
+packet_t tq_remove (int chan, int prio)
+{
+
+	packet_t result_p;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_remove(%d,%d) enter critical section\n", chan, prio);
+#endif
+
+	dw_mutex_lock (&tq_mutex);
+
+	if (queue_head[chan][prio] == NULL) {
+
+	  result_p = NULL;
+	}
+	else {
+
+	  result_p = queue_head[chan][prio];
+	  queue_head[chan][prio] = ax25_get_nextp(result_p);
+	  ax25_set_nextp (result_p, NULL);
+	}
+	 
+	dw_mutex_unlock (&tq_mutex);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_remove(%d,%d) leave critical section, returns %p\n", chan, prio, result_p);
+#endif
+
+#if AX25MEMDEBUG
+
+	if (ax25memdebug_get() && result_p != NULL) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("tq_remove (chan=%d, prio=%d)  seq=%d\n", chan, prio, ax25memdebug_seq(result_p));
+	}
+#endif
+	return (result_p);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tq_is_empty
+ *
+ * Purpose:     Test if queues for specified channel are empty.
+ *		
+ * Inputs:	chan		Channel 
+ *
+ * Returns:	True if nothing in the queue.	
+ *
+ *--------------------------------------------------------------------*/
+
+static int tq_is_empty (int chan)
+{
+	int p;
+	
+	assert (chan >= 0 && chan < MAX_CHANS);
+
+
+	for (p=0; p<TQ_NUM_PRIO; p++) {
+
+	  assert (p >= 0 && p < TQ_NUM_PRIO);
+
+	  if (queue_head[chan][p] != NULL)
+	     return (0);
+	}
+
+	return (1);
+
+} /* end tq_is_empty */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        tq_count
+ *
+ * Purpose:     Return count of the number of packets in the specified transmit queue.
+ *
+ * Inputs:	chan	- Channel, 0 is first.
+ *
+ *		prio	- Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
+ *
+ * Returns:	Number of items in specified queue.	
+ *
+ *--------------------------------------------------------------------*/
+
+int tq_count (int chan, int prio)
+{
+
+	packet_t p;
+	int n;
+
+
+/* Don't bother with critical section. */
+/* Only used for debugging a problem. */
+
+	n = 0;
+	p = queue_head[chan][prio];
+	while (p != NULL) {
+	  n++;
+	  p = ax25_get_nextp(p);
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("tq_count(%d,%d) returns %d\n", chan, prio, n);
+#endif
+
+	return (n);
+
+} /* end tq_count */
+
+/* end tq.c */
diff --git a/tq.h b/tq.h
index a67bac8..92ee3ba 100644
--- a/tq.h
+++ b/tq.h
@@ -1,35 +1,35 @@
-
-/*------------------------------------------------------------------
- *
- * Module:      tq.h
- *
- * Purpose:   	Transmit queue - hold packets for transmission until the channel is clear.
- *		
- *---------------------------------------------------------------*/
-
-#ifndef TQ_H
-#define TQ_H 1
-
-#include "ax25_pad.h"
-#include "audio.h"
-
-#define TQ_NUM_PRIO 2				/* Number of priorities. */
-
-#define TQ_PRIO_0_HI 0
-#define TQ_PRIO_1_LO 1
-
-
-
-void tq_init (struct audio_s *audio_config_p);
-
-void tq_append (int chan, int prio, packet_t pp);
-
-void tq_wait_while_empty (int chan);
-
-packet_t tq_remove (int chan, int prio);
-
-int tq_count (int chan, int prio);
-
-#endif
-
-/* end tq.h */
+
+/*------------------------------------------------------------------
+ *
+ * Module:      tq.h
+ *
+ * Purpose:   	Transmit queue - hold packets for transmission until the channel is clear.
+ *		
+ *---------------------------------------------------------------*/
+
+#ifndef TQ_H
+#define TQ_H 1
+
+#include "ax25_pad.h"
+#include "audio.h"
+
+#define TQ_NUM_PRIO 2				/* Number of priorities. */
+
+#define TQ_PRIO_0_HI 0
+#define TQ_PRIO_1_LO 1
+
+
+
+void tq_init (struct audio_s *audio_config_p);
+
+void tq_append (int chan, int prio, packet_t pp);
+
+void tq_wait_while_empty (int chan);
+
+packet_t tq_remove (int chan, int prio);
+
+int tq_count (int chan, int prio);
+
+#endif
+
+/* end tq.h */
diff --git a/tt_text.c b/tt_text.c
index dad6a01..f2803f2 100644
--- a/tt_text.c
+++ b/tt_text.c
@@ -1,1130 +1,1828 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013, 2015  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/>.
-//
-
-/*------------------------------------------------------------------
- *
- * Module:      tt-text.c
- *
- * Purpose:   	Translate between text and touch tone representation.
- *		
- * Description: Letters can be represented by different touch tone
- *		keypad sequences.
- *
- * References:	This is based upon APRStt (TM) documents but not 100%
- *		compliant due to ambiguities and inconsistencies in
- *		the specifications.
- *
- *		http://www.aprs.org/aprstt.html
- *
- *---------------------------------------------------------------*/
-
-/*
- * There are two different encodings called:
- *
- *   * Two-key
- *
- *		Digits are represented by a single key press.
- *		Letters (or space) are represented by the corresponding
- *		key followed by A, B, C, or D depending on the position
- *		of the letter.
- *
- *   * Multi-press
- *
- *		Letters are represented by one or more key presses
- *		depending on their position.
- *		e.g. on 5/JKL key, J = 1 press, K = 2, etc. 
- *		The digit is the number of letters plus 1.
- *		In this case, press 5 key four times to get digit 5.
- *		When two characters in a row use the same key,
- *		use the "A" key as a separator.
- *
- * Examples:
- *
- *	Character	Multipress	Two Key		Comments
- *	---------	----------	-------		--------
- *	0		00		0		Space is handled like a letter.
- *	1		1		1		No letters on 1 button.
- *	2		2222		2		3 letters -> 4 key presses
- *	9		99999		9
- *	W		9		9A
- *	X		99		9B
- *	Y		999		9C
- *	Z		9999		9D
- *	space		0		0A		0A was used in an APRStt comment example.
- *
- *
- * Note that letters can occur in callsigns and comments.
- * Everywhere else they are simply digits.
- *
- *
- *   * New fixed length callsign format
- *
- *
- * 	The "QIKcom-2" project adds a new format where callsigns are represented by
- * 	a fixed length string of only digits.  The first 6 digits are the buttons corresponding
- * 	to the letters.  The last 4 take a little calculation.  Example:
- *
- *		W B 4 A P R	original.
- *		9 2 4 2 7 7	corresponding button.
- *		1 2 0 1 1 2	character position on key.  0 for the digit.
- *
- * 	Treat the last line as a base 4 number.
- * 	Convert it to base 10 and we get 1558 for the last four digits.
- */
-
-/*
- * Everything is based on this table.
- * Changing it will change everything.
- * In other words, don't mess with it.  
- * The world will come crumbling down.
- */
-
-static const char translate[10][4] = {
-		/*	 A	 B	 C	 D  */
-		/*	---	---	---	--- */
-	/* 0 */	{	' ',	 0,	 0,	 0  },
-	/* 1 */	{	 0,	 0,	 0,	 0  },
-	/* 2 */	{	'A',	'B',	'C',	 0  },
-	/* 3 */	{	'D',	'E',	'F',	 0  },
-	/* 4 */	{	'G',	'H',	'I',	 0  },
-	/* 5 */	{	'J',	'K',	'L',	 0  },
-	/* 6 */	{	'M',	'N',	'O',	 0  },
-	/* 7 */	{	'P',	'Q',	'R',	'S' },
-	/* 8 */	{	'T',	'U',	'V',	 0  },
-	/* 9 */	{	'W',	'X',	'Y',	'Z' } };
-
-
-/*
- * This is for the new 10 character fixed length callsigns for APRStt 3.
- * Notice that it uses an old keypad layout with Q & Z on the 1 button.
- * The TH-D72A and all telephones that I could find all have 
- * four letters each on the 7 and 9 buttons.
- * This inconsistency is sure to cause confusion but the 6+4 scheme won't
- * be possible with more than 4 characters assigned to one button.
- * 4**6-1 = 4096 which fits in 4 decimal digits.
- * 5**6-1 = 15624 would not fit.
- *
- * The column is a two bit code packed into the last 4 digits.
- */
-
-static const char call10encoding[10][4] = {
-		/*	 0	 1	 2	 3  */
-		/*	---	---	---	--- */
-	/* 0 */	{	'0',	' ',	 0,	 0   },
-	/* 1 */	{	'1',	'Q',	'Z',	 0   },
-	/* 2 */	{	'2',	'A',	'B',	'C'  },
-	/* 3 */	{	'3',	'D',	'E',	'F'  },
-	/* 4 */	{	'4',	'G',	'H',	'I'  },
-	/* 5 */	{	'5',	'J',	'K',	'L'  },
-	/* 6 */	{	'6',	'M',	'N',	'O'  },
-	/* 7 */	{	'7',	'P',	'R',	'S'  },
-	/* 8 */	{	'8',	'T',	'U',	'V'  },
-	/* 9 */	{	'9',	'W',	'X',	'Y'  } };
-
-
-/*
- * 4 digit gridsquares to cover "99.99% of the world's population."
- */
-
-static const char grid[10][10][3] =      
-     {  { "AP", "BP", "AO", "BO", "CO", "DO", "EO", "FO", "GO", "OJ" },		// 0 - Canada
-        { "CN", "DN", "EN", "FN", "GN", "CM", "DM", "EM", "FM", "OI" },		// 1 - USA
-        { "DL", "EL", "FL", "DK", "EK", "FK", "EJ", "FJ", "GJ", "PI" },		// 2 - C. America
-        { "FI", "GI", "HI", "FH", "GH", "HH", "FG", "GG", "FF", "GF" },		// 3 - S. America
-        { "JP", "IO", "JO", "KO", "IN", "JN", "KN", "IM", "JM", "KM" },		// 4 - Europe
-        { "LO", "MO", "NO", "OO", "PO", "QO", "RO", "LN", "MN", "NN" },		// 5 - Russia
-        { "ON", "PN", "QN", "OM", "PM", "QM", "OL", "PL", "OK", "PK" },		// 6 - Japan, China
-        { "LM", "MM", "NM", "LL", "ML", "NL", "LK", "MK", "NK", "LJ" },		// 7 - India
-        { "PH", "QH", "OG", "PG", "QG", "OF", "PF", "QF", "RF", "RE" },		// 8 - Aus / NZ
-        { "IL", "IK", "IJ", "JJ", "JI", "JH", "JG", "KG", "JF", "KF" }  };	// 9 - Africa
-
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-#include <stdarg.h>
-
-#include "textcolor.h"
-#include "tt_text.h"
-
-
-#if defined(ENC_MAIN) || defined(DEC_MAIN)
-
-void text_color_set (dw_color_t c) { return; }
-
-int dw_printf (const char *fmt, ...) 
-{
-	va_list args;
-	int len;
-	
-	va_start (args, fmt);
-	len = vprintf (fmt, args);
-	va_end (args);
-	return (len);
-}
-
-#endif
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_text_to_multipress
- *
- * Purpose:     Convert text to the multi-press representation.
- *
- * Inputs:      text	- Input string.
- *			  Should contain only digits, letters, or space.
- *			  All other punctuation is treated as space.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	buttons	- Sequence of buttons to press.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_text_to_multipress (char *text, int quiet, char *buttons) 
-{
-	char *t = text;
-	char *b = buttons;
-	char c;
-	int row, col;
-	int errors = 0;
-	int found;
-	int n;
-
-	*b = '\0';
-	
-	while ((c = *t++) != '\0') {
-
-	  if (isdigit(c)) {
-	
-/* Count number of other characters assigned to this button. */
-/* Press that number plus one more. */
-
-	    n = 1;
-	    row = c - '0';
-	    for (col=0; col<4; col++) {
-	      if (translate[row][col] != 0) {
-	        n++;
-	      }
-	    }
-	    if (buttons[0] != '\0' && *(b-1) == row + '0') {
-	      *b++ = 'A';
-	    }
-	    while (n--) {
-	      *b++ = row + '0';
-	      *b = '\0';
-	    }
-	  }
-	  else {
-	    if (isupper(c)) {
-	      ;
-	    }	  
-	    else if (islower(c)) {
-	      c = toupper(c);
-	    }
-	    else if (c != ' ') {
-	      errors++;
-	      if (! quiet) {
-	        text_color_set (DW_COLOR_ERROR);
-		dw_printf ("Text to multi-press: Only letters, digits, and space allowed.\n");
-	      }
-	      c = ' ';
-	    }
-
-/* Search for everything else in the translation table. */
-/* Press number of times depending on column where found. */
-
-	    found = 0;
-
-	    for (row=0; row<10 && ! found; row++) {
-	      for (col=0; col<4 && ! found; col++) {
-	        if (c == translate[row][col]) {
-
-/* Stick in 'A' if previous character used same button. */
-
-	          if (buttons[0] != '\0' && *(b-1) == row + '0') {
-	            *b++ = 'A';
-	          }
-	          n = col + 1;
-	          while (n--) {
-	            *b++ = row + '0';
-	            *b = '\0';
-	            found = 1;
-	          }
-	        }
-	      }
-	    }
-	    if (! found) {
-	      errors++;
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Text to multi-press: INTERNAL ERROR.  Should not be here.\n");
-	    }
-	  }
-	}
-	return (errors);          
-
-} /* end tt_text_to_multipress */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_text_to_two_key
- *
- * Purpose:     Convert text to the two-key representation.
- *
- * Inputs:      text	- Input string.
- *			  Should contain only digits, letters, or space.
- *			  All other punctuation is treated as space.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	buttons	- Sequence of buttons to press.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_text_to_two_key (char *text, int quiet, char *buttons) 
-{
-	char *t = text;
-	char *b = buttons;
-	char c;
-	int row, col;
-	int errors = 0;
-	int found;
-
-
-	*b = '\0';
-	
-	while ((c = *t++) != '\0') {
-
-	  if (isdigit(c)) {
-	
-/* Digit is single key press. */
-	  
-	    *b++ = c;
-	    *b = '\0';
-	  }
-	  else {
-	    if (isupper(c)) {
-	      ;
-	    }	  
-	    else if (islower(c)) {
-	      c = toupper(c);
-	    }
-	    else if (c != ' ') {
-	      errors++;
-	      if (! quiet) {
-	        text_color_set (DW_COLOR_ERROR);
-		dw_printf ("Text to two key: Only letters, digits, and space allowed.\n");
-	      }
-	      c = ' ';
-	    }
-
-/* Search for everything else in the translation table. */
-
-	    found = 0;
-
-	    for (row=0; row<10 && ! found; row++) {
-	      for (col=0; col<4 && ! found; col++) {
-	        if (c == translate[row][col]) {
-		  *b++ = '0' + row;
-	          *b++ = 'A' + col;
-	          *b = '\0';
-	          found = 1;
-	        }
-	      }
-	    }
-	    if (! found) {
-	      errors++;
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Text to two-key: INTERNAL ERROR.  Should not be here.\n");
-	    }
-	  }
-	}
-	return (errors);          
-
-} /* end tt_text_to_two_key */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_text_to_call10
- *
- * Purpose:     Convert text to the 10 character callsign format.
- *
- * Inputs:      text	- Input string.
- *			  Should contain from 1 to 6 letters and digits.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	buttons	- Sequence of buttons to press.
- *			  Should be exactly 10 unless error.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_text_to_call10 (char *text, int quiet, char *buttons) 
-{
-	char *t;
-	char *b;
-	char c;
-	int packed;		/* two bits per character */
-	int row, col;
-	int errors = 0;
-	int found;
-	char padded[8];
-	char stemp[8];
-
-
-	strcpy (buttons, "");
-
-/* Quick validity check. */
-	
-	if (strlen(text) < 1 || strlen(text) > 6) {
-
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Text to callsign 6+4: Callsign \"%s\" not between 1 and 6 characters.\n", text);
-	  }
-	  errors++;
-	  return (errors);
-   	}
-
-	for (t = text; *t != '\0'; t++) {
-
-	  if (! isalnum(*t)) {
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Text to callsign 6+4: Callsign \"%s\" can contain only letters and digits.\n", text);
-	    }
-	    errors++;
-	    return (errors);
-	  }
-   	}
-
-/* Append spaces if less than 6 characters. */
-
-	strcpy (padded, text);
-	while (strlen(padded) < 6) {
-	  strcat (padded, " ");
-	}
-
-	b = buttons;
-	packed = 0;
-
-	for (t = padded; *t != '\0'; t++) {
-	
-	  c = *t;
-	  if (islower(c)) {
-	      c = toupper(c);
-	  }
-
-/* Search in the translation table. */
-
-	  found = 0;
-
-	  for (row=0; row<10 && ! found; row++) {
-	    for (col=0; col<4 && ! found; col++) {
-	      if (c == call10encoding[row][col]) {
-	        *b++ = '0' + row;
-	        *b = '\0';
-	        packed = packed * 4 + col;  /* base 4 to binary */
-	        found = 1;
-	      }
-	    }
-	  }
-
-	  if (! found) {
-	    /* Earlier check should have caught any character not in translation table. */
-	    errors++;
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Text to callsign 6+4: INTERNAL ERROR 0x%02x.  Should not be here.\n", c);
-	  }
-	}
-
-/* Binary to decimal for the columns. */
-
-	sprintf (stemp, "%04d", packed);
-	strcat (buttons, stemp);
-
-	return (errors);          
-
-} /* end tt_text_to_call10 */
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_text_to_gridsquare
- *
- * Purpose:     Convert Gridsquare to 4 digit DTMF representation.
- *
- * Inputs:      text	- Input string.
- *			  Should be two letters (A thru R) and two digits.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	buttons	- Sequence of buttons to press.
- *			  Should be 4 digits unless error.
- *
- * Returns:     Number of errors detected.
- *
- * Example:	"FM19" is converted to "1819."
- *		"AA00" is converted to empty string and error return code.
- *
- *----------------------------------------------------------------*/
-
-int tt_text_to_gridsquare (char *text, int quiet, char *buttons) 
-{
-
-	int row, col;
-	int errors = 0;
-	int found;
-	char uc[3];
-
-
-	strcpy (buttons, "");
-
-/* Quick validity check. */
-	
-	if (strlen(text) < 1 || strlen(text) > 4) {
-
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Gridsquare to DTMF: Gridsquare \"%s\" must be 4 characters.\n", text);
-	  }
-	  errors++;
-	  return (errors);
-   	}
-
-/* Changing to upper case makes things easier later. */
-
-	uc[0] = islower(text[0]) ? toupper(text[0]) : text[0];
-	uc[1] = islower(text[1]) ? toupper(text[1]) : text[1];
-	uc[2] = '\0';
-
-	if (uc[0] < 'A' || uc[0] > 'R' || uc[1] < 'A' || uc[1] > 'R') {
-
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Gridsquare to DTMF: First two characters \"%s\" must be letters in range of A to R.\n", text);
-	  }
-	  errors++;
-	  return (errors);
-	}
-
-	if (! isdigit(text[2]) || ! isdigit(text[3])) {
-
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Gridsquare to DTMF: Last two characters \"%s\" must digits.\n", text);
-	  }
-	  errors++;
-	  return (errors);
-   	}
-
-
-/* Search in the translation table. */
-
-	found = 0;
-
-	for (row=0; row<10 && ! found; row++) {
-	  for (col=0; col<10 && ! found; col++) {
-	    if (strcmp(uc,grid[row][col]) == 0) {
-	      buttons[0] = row + '0';
-	      buttons[1] = col + '0';
-	      buttons[2] = text[2];
-	      buttons[3] = text[3];
-	      buttons[4] = '\0';
-	      found = 1;
-	    }
-	  }
-	}
-
-	if (! found) {
-	  /* Sorry, Greenland, and half of Africa, and ... */
-	  errors++;
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Gridsquare to DTMF: Sorry, your location can't be converted to DTMF.\n");
-	  }
-	}
-
-	return (errors);          
-
-} /* end tt_text_to_gridsquare */
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_multipress_to_text
- *
- * Purpose:     Convert the multi-press representation to text.
- *
- * Inputs:      buttons	- Input string.
- *			  Should contain only 0123456789A.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	text	- Converted to letters, digits, space.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_multipress_to_text (char *buttons, int quiet, char *text) 
-{
-	char *b = buttons;
-	char *t = text;
-	char c;
-	int row, col;
-	int errors = 0;
-	int maxspan;
-	int n;
-
-	*t = '\0';
-	
-	while ((c = *b++) != '\0') {
-
-	  if (isdigit(c)) {
-	
-/* Determine max that can occur in a row. */
-/* = number of other characters assigned to this button + 1. */
-
-	    maxspan = 1;
-	    row = c - '0';
-	    for (col=0; col<4; col++) {
-	      if (translate[row][col] != 0) {
-	        maxspan++;
-	      }
-	    }
-
-/* Count number of consecutive same digits. */
-
-	    n = 1;
-	    while (c == *b) {
-	      b++;
-	      n++;
-	    }
-
-	    if (n < maxspan) {
-	      *t++ = translate[row][n-1];
-	      *t = '\0';
-	    }
-	    else if (n == maxspan) {
-	      *t++ = c;
-	      *t = '\0';
-	    }
-	    else {
-	      errors++;
-	      if (! quiet) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("Multi-press to text: Maximum of %d \"%c\" can occur in a row.\n", maxspan, c);
-	      }
-	      /* Treat like the maximum length. */
-	      *t++ = c;
-	      *t = '\0';
-	    }
-	  }
-	  else if (c == 'A' || c == 'a') {
-
-/* Separator should occur only if digit before and after are the same. */
-	     
-	    if (b == buttons + 1 || *b == '\0' || *(b-2) != *b) {
-	      errors++;
-	      if (! quiet) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("Multi-press to text: \"A\" can occur only between two same digits.\n");
-  	      }
-	    }
-	  }
-	  else {
-
-/* Completely unexpected character. */
-
-	    errors++;
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Multi-press to text: \"%c\" not allowed.\n", c);
-  	    }
-	  }
-	}
-	return (errors);          
-
-} /* end tt_multipress_to_text */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_two_key_to_text
- *
- * Purpose:     Convert the two key representation to text.
- *
- * Inputs:      buttons	- Input string.
- *			  Should contain only 0123456789ABCD.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	text	- Converted to letters, digits, space.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_two_key_to_text (char *buttons, int quiet, char *text) 
-{
-	char *b = buttons;
-	char *t = text;
-	char c;
-	int row, col;
-	int errors = 0;
-
-	*t = '\0';
-	
-	while ((c = *b++) != '\0') {
-
-	  if (isdigit(c)) {
-	
-/* Letter (or space) if followed by ABCD. */
-	    
-	    row = c - '0';
-	    col = -1;
-
-	    if (*b >= 'A' && *b <= 'D') {
-	      col = *b++ - 'A';
-	    }
-	    else if (*b >= 'a' && *b <= 'd') {
-	      col = *b++ - 'a';
-	    }
-
-	    if (col >= 0) {
-	      if (translate[row][col] != 0) {
-	        *t++ = translate[row][col];
-	        *t = '\0';
-	      }
-	      else {
-		errors++;
-	        if (! quiet) {
-	          text_color_set (DW_COLOR_ERROR);
-	          dw_printf ("Two key to text: Invalid combination \"%c%c\".\n", c, col+'A');
-		}
-	      }
-	    }
-	    else {
-	      *t++ = c;
-	      *t = '\0';
-	    }
-	  }
-	  else if ((c >= 'A' && c <= 'D') || (c >= 'a' && c <= 'd')) {
-
-/* ABCD not expected here. */
-	     
-	    errors++;
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Two-key to text: A, B, C, or D in unexpected location.\n");
-	    }
-	  }
-	  else {
-
-/* Completely unexpected character. */
-
-	    errors++;
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Two-key to text: Invalid character \"%c\".\n", c);
-  	    }
-	  }
-	}
-	return (errors);          
-
-} /* end tt_two_key_to_text */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_call10_to_text
- *
- * Purpose:     Convert the 10 digit callsign representation to text.
- *
- * Inputs:      buttons	- Input string.
- *			  Should contain only ten digits.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	text	- Converted to callsign with upper case letters and digits.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_call10_to_text (char *buttons, int quiet, char *text) 
-{
-	char *b;
-	char *t;
-	char c;
-	int packed;		/* from last 4 digits */
-	int row, col;
-	int errors = 0;
-	int k;
-
-	t = text;
-	*t = '\0';	/* result */
-
-/* Validity check. */
-
-	if (strlen(buttons) != 10) {
-
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Callsign 6+4 to text: Encoded Callsign \"%s\" must be exactly 10 digits.\n", buttons);
-	  }
-	  errors++;
-	  return (errors);
-   	}
-
-	for (b = buttons; *b != '\0'; b++) {
-
-	  if (! isdigit(*b)) {
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Callsign 6+4 to text: Encoded Callsign \"%s\" can contain only digits.\n", buttons);
-	    }
-	    errors++;
-	    return (errors);
-	  }
-   	}
-
-	packed = atoi(buttons+6);
-
-	for (k = 0; k < 6; k++) {
-	  c = buttons[k];
-
-	  row = c - '0';
-	  col = (packed >> ((5 - k) *2)) & 3;
-
-	  if (row < 0 || row > 9 || col < 0 || col > 3) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("Callsign 6+4 to text: INTERNAL ERROR %d %d.  Should not be here.\n", row, col);
-	    errors++;
-	    row = 0;
-	    col = 1;
-	  }
-
-	  if (call10encoding[row][col] != 0) {
-	    *t++ = call10encoding[row][col];
-	    *t = '\0';
-	  }
-	  else {
-	    errors++;
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("Callsign 6+4 to text: Invalid combination: button %d, position %d.\n", row, col);
-	    }
-	  }
-	}
-
-/* Trim any trailing spaces. */
-
-	k = strlen(text) - 1;		/* should be 6 - 1 = 5 */
-
-	while (k >= 0 && text[k] == ' ') {
-	  text[k] = '\0';
-	  k--;
-	}
-
-	return (errors);          
-
-} /* end tt_call10_to_text */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_gridsquare_to_text
- *
- * Purpose:     Convert the 4 digit DTMF gridsquare to normal 2 letters and 2 digits.
- *
- * Inputs:      buttons	- Input string.
- *			  Should contain 4 digits.
- *
- *		quiet	- True to suppress error messages.
- *	
- * Outputs:	text	- Converted to gridsquare with upper case letters and digits.
- *
- * Returns:     Number of errors detected.
- *
- *----------------------------------------------------------------*/
-
-int tt_gridsquare_to_text (char *buttons, int quiet, char *text) 
-{
-	char *b;
-	int row, col;
-	int errors = 0;
-
-	strcpy (text, "");
-
-/* Validity check. */
-
-	if (strlen(buttons) != 4) {
-
-	  if (! quiet) {
-	    text_color_set (DW_COLOR_ERROR);
-	    dw_printf ("DTMF to Gridsquare: Input \"%s\" must be exactly 4 digits.\n", buttons);
-	  }
-	  errors++;
-	  return (errors);
-   	}
-
-	for (b = buttons; *b != '\0'; b++) {
-
-	  if (! isdigit(*b)) {
-	    if (! quiet) {
-	      text_color_set (DW_COLOR_ERROR);
-	      dw_printf ("DTMF to Gridsquare: Input \"%s\" can contain only digits.\n", buttons);
-	    }
-	    errors++;
-	    return (errors);
-	  }
-   	}
-
-	row = buttons[0] - '0';
-	col = buttons[1] - '0';
-
-	strcpy (text, grid[row][col]);
-	strcat (text, buttons+2);
-
-	return (errors);          
-
-} /* end tt_gridsquare_to_text */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_guess_type
- *
- * Purpose:     Try to guess which encoding we have.
- *
- * Inputs:      buttons	- Input string.
- *			  Should contain only 0123456789ABCD.
- *
- * Returns:     TT_MULTIPRESS	- Looks like multipress.
- *		TT_TWO_KEY	- Looks like two key.
- *		TT_EITHER	- Could be either one.
- *
- *----------------------------------------------------------------*/
-
-typedef enum { TT_EITHER, TT_MULTIPRESS, TT_TWO_KEY } tt_enc_t;
-
-tt_enc_t tt_guess_type (char *buttons) 
-{
-	char text[256];
-	int err_mp;
-	int err_tk;
-	
-/* If it contains B, C, or D, it can't be multipress. */
-
-	if (strchr (buttons, 'B') != NULL || strchr (buttons, 'b') != NULL ||
-	    strchr (buttons, 'C') != NULL || strchr (buttons, 'c') != NULL ||
-	    strchr (buttons, 'D') != NULL || strchr (buttons, 'd') != NULL) {
-	  return (TT_TWO_KEY);
-	}
-
-/* Try parsing quietly and see if one gets errors and the other doesn't. */
-
-	err_mp = tt_multipress_to_text (buttons, 1, text);
-	err_tk = tt_two_key_to_text (buttons, 1, text);
-
-	if (err_mp == 0 && err_tk > 0) {
-	  return (TT_MULTIPRESS);
- 	}
-	else if (err_tk == 0 && err_mp > 0) {
-	  return (TT_TWO_KEY);
-	}
-
-/* Could be either one. */
-
-	return (TT_EITHER);
-
-} /* end tt_guess_type */
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Utility program for testing the encoding.
- *
- *----------------------------------------------------------------*/
-
-
-#if ENC_MAIN
-
-int checksum (char *tt)
-{
-	int cs = 10;	/* Assume leading 'A'. */
-			/* Doesn't matter due to mod 10 at the end. */
-	char *p;
-
-	for (p = tt; *p != '\0'; p++) {
-	  if (isdigit(*p)) {
-	    cs += *p - '0';
-	  }
-	  else if (isupper(*p)) {
-	    cs += *p - 'A' + 10;
-	  }
-	  else if (islower(*p)) {
-	    cs += *p - 'a' + 10;
-	  }
-	}
-
-	return (cs % 10);
-}
-
-int main (int argc, char *argv[])
-{
-	char text[1000], buttons[2000];
-	int n;
-	int cs;
-
-	text_color_set (DW_COLOR_INFO);
-
-	if (argc < 2) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Supply text string on command line.\n");
-	  exit (1);
-	}
-
-	strcpy (text, argv[1]);
-
-	for (n = 2; n < argc; n++) {
-	  strcat (text, " ");
-	  strcat (text, argv[n]);
-	}
-
-	dw_printf ("Push buttons for multi-press method:\n");
-	n = tt_text_to_multipress (text, 0, buttons);
-	cs = checksum (buttons);
-	dw_printf ("\"%s\"    checksum for call = %d\n", buttons, cs);
-
-	dw_printf ("Push buttons for two-key method:\n");
-	n = tt_text_to_two_key (text, 0, buttons);
-	cs = checksum (buttons);
-	dw_printf ("\"%s\"    checksum for call = %d\n", buttons, cs);
-
-	n = tt_text_to_call10 (text, 1, buttons);
-	if (n == 0) {
-	  dw_printf ("Push buttons for fixed length 10 digit callsign:\n");
-	  dw_printf ("\"%s\"\n", buttons);
-	}
-
-	n = tt_text_to_gridsquare (text, 1, buttons);
-	if (n == 0) {
-	  dw_printf ("Push buttons for gridsquare:\n");
-	  dw_printf ("\"%s\"\n", buttons);
-	}
-
-	return(0);
-
-}  /* end main */
-
-#endif		/* encoding */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Utility program for testing the decoding.
- *
- *----------------------------------------------------------------*/
-
-
-#if DEC_MAIN
-
-
-int main (int argc, char *argv[])
-{
-	char buttons[2000], text[1000];
-	int n;
-
-	text_color_set (DW_COLOR_INFO);
-
-	if (argc < 2) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("Supply button sequence on command line.\n");
-	  exit (1);
-	}
-
-	strcpy (buttons, argv[1]);
-
-	for (n = 2; n < argc; n++) {
-	  strcat (buttons, argv[n]);
-	}
-
-	switch (tt_guess_type(buttons)) {
-	  case TT_MULTIPRESS:
-	    dw_printf ("Looks like multi-press encoding.\n");
-	    break;
-	  case TT_TWO_KEY:
-	    dw_printf ("Looks like two-key encoding.\n");
-	    break;
-	  default:
-	    dw_printf ("Could be either type of encoding.\n");
-	    break;
-	}
-
-	dw_printf ("Decoded text from multi-press method:\n");
-	n = tt_multipress_to_text (buttons, 0, text);
-	dw_printf ("\"%s\"\n", text);
-
-	dw_printf ("Decoded text from two-key method:\n");
-	n = tt_two_key_to_text (buttons, 0, text);
-	dw_printf ("\"%s\"\n", text);
-
-	n = tt_call10_to_text (buttons, 1, text);
-	if (n == 0) {
-	  dw_printf ("Decoded callsign from 10 digit method:\n");
-	  dw_printf ("\"%s\"\n", text);
-	}
-
-	n = tt_gridsquare_to_text (buttons, 1, text);
-	if (n == 0) {
-	  dw_printf ("Decoded gridsquare from 4 DTMF digits:\n");
-	  dw_printf ("\"%s\"\n", text);
-	}
-
-	return(0);
-
-}  /* end main */
-
-#endif		/* decoding */
-
-
-
-/* end tt-text.c */
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2015  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/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * Module:      tt_text.c
+ *
+ * Purpose:   	Translate between text and touch tone representation.
+ *		
+ * Description: Letters can be represented by different touch tone
+ *		keypad sequences.
+ *
+ * References:	This is based upon APRStt (TM) documents but not 100%
+ *		compliant due to ambiguities and inconsistencies in
+ *		the specifications.
+ *
+ *		http://www.aprs.org/aprstt.html
+ *
+ *---------------------------------------------------------------*/
+
+/*
+ * There are two different encodings called:
+ *
+ *   * Two-key
+ *
+ *		Digits are represented by a single key press.
+ *		Letters (or space) are represented by the corresponding
+ *		key followed by A, B, C, or D depending on the position
+ *		of the letter.
+ *
+ *   * Multi-press
+ *
+ *		Letters are represented by one or more key presses
+ *		depending on their position.
+ *		e.g. on 5/JKL key, J = 1 press, K = 2, etc. 
+ *		The digit is the number of letters plus 1.
+ *		In this case, press 5 key four times to get digit 5.
+ *		When two characters in a row use the same key,
+ *		use the "A" key as a separator.
+ *
+ * Examples:
+ *
+ *	Character	Multipress	Two Key		Comments
+ *	---------	----------	-------		--------
+ *	0		00		0		Space is handled like a letter.
+ *	1		1		1		No letters on 1 button.
+ *	2		2222		2		3 letters -> 4 key presses
+ *	9		99999		9
+ *	W		9		9A
+ *	X		99		9B
+ *	Y		999		9C
+ *	Z		9999		9D
+ *	space		0		0A		0A was used in an APRStt comment example.
+ *
+ *
+ * Note that letters can occur in callsigns and comments.
+ * Everywhere else they are simply digits.
+ *
+ *
+ *   * New fixed length callsign format
+ *
+ *
+ * 	The "QIKcom-2" project adds a new format where callsigns are represented by
+ * 	a fixed length string of only digits.  The first 6 digits are the buttons corresponding
+ * 	to the letters.  The last 4 take a little calculation.  Example:
+ *
+ *		W B 4 A P R	original.
+ *		9 2 4 2 7 7	corresponding button.
+ *		1 2 0 1 1 2	character position on key.  0 for the digit.
+ *
+ * 	Treat the last line as a base 4 number.
+ * 	Convert it to base 10 and we get 1558 for the last four digits.
+ */
+
+/*
+ * Everything is based on this table.
+ * Changing it will change everything.
+ * In other words, don't mess with it.  
+ * The world will come crumbling down.
+ */
+
+static const char translate[10][4] = {
+		/*	 A	 B	 C	 D  */
+		/*	---	---	---	--- */
+	/* 0 */	{	' ',	 0,	 0,	 0  },
+	/* 1 */	{	 0,	 0,	 0,	 0  },
+	/* 2 */	{	'A',	'B',	'C',	 0  },
+	/* 3 */	{	'D',	'E',	'F',	 0  },
+	/* 4 */	{	'G',	'H',	'I',	 0  },
+	/* 5 */	{	'J',	'K',	'L',	 0  },
+	/* 6 */	{	'M',	'N',	'O',	 0  },
+	/* 7 */	{	'P',	'Q',	'R',	'S' },
+	/* 8 */	{	'T',	'U',	'V',	 0  },
+	/* 9 */	{	'W',	'X',	'Y',	'Z' } };
+
+
+/*
+ * This is for the new 10 character fixed length callsigns for APRStt 3.
+ * Notice that it uses an old keypad layout with Q & Z on the 1 button.
+ * The TH-D72A and all telephones that I could find all have 
+ * four letters each on the 7 and 9 buttons.
+ * This inconsistency is sure to cause confusion but the 6+4 scheme won't
+ * be possible with more than 4 characters assigned to one button.
+ * 4**6-1 = 4096 which fits in 4 decimal digits.
+ * 5**6-1 = 15624 would not fit.
+ *
+ * The column is a two bit code packed into the last 4 digits.
+ */
+
+static const char call10encoding[10][4] = {
+		/*	 0	 1	 2	 3  */
+		/*	---	---	---	--- */
+	/* 0 */	{	'0',	' ',	 0,	 0   },
+	/* 1 */	{	'1',	'Q',	'Z',	 0   },
+	/* 2 */	{	'2',	'A',	'B',	'C'  },
+	/* 3 */	{	'3',	'D',	'E',	'F'  },
+	/* 4 */	{	'4',	'G',	'H',	'I'  },
+	/* 5 */	{	'5',	'J',	'K',	'L'  },
+	/* 6 */	{	'6',	'M',	'N',	'O'  },
+	/* 7 */	{	'7',	'P',	'R',	'S'  },
+	/* 8 */	{	'8',	'T',	'U',	'V'  },
+	/* 9 */	{	'9',	'W',	'X',	'Y'  } };
+
+
+/*
+ * Special satellite 4 digit gridsquares to cover "99.99% of the world's population."
+ */
+
+static const char grid[10][10][3] =      
+     {  { "AP", "BP", "AO", "BO", "CO", "DO", "EO", "FO", "GO", "OJ" },		// 0 - Canada
+        { "CN", "DN", "EN", "FN", "GN", "CM", "DM", "EM", "FM", "OI" },		// 1 - USA
+        { "DL", "EL", "FL", "DK", "EK", "FK", "EJ", "FJ", "GJ", "PI" },		// 2 - C. America
+        { "FI", "GI", "HI", "FH", "GH", "HH", "FG", "GG", "FF", "GF" },		// 3 - S. America
+        { "JP", "IO", "JO", "KO", "IN", "JN", "KN", "IM", "JM", "KM" },		// 4 - Europe
+        { "LO", "MO", "NO", "OO", "PO", "QO", "RO", "LN", "MN", "NN" },		// 5 - Russia
+        { "ON", "PN", "QN", "OM", "PM", "QM", "OL", "PL", "OK", "PK" },		// 6 - Japan, China
+        { "LM", "MM", "NM", "LL", "ML", "NL", "LK", "MK", "NK", "LJ" },		// 7 - India
+        { "PH", "QH", "OG", "PG", "QG", "OF", "PF", "QF", "RF", "RE" },		// 8 - Aus / NZ
+        { "IL", "IK", "IJ", "JJ", "JI", "JH", "JG", "KG", "JF", "KF" }  };	// 9 - Africa
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#include "direwolf.h"
+#include "textcolor.h"
+#include "tt_text.h"
+
+
+#if defined(ENC_MAIN) || defined(DEC_MAIN)
+
+void text_color_set (dw_color_t c) { return; }
+
+int dw_printf (const char *fmt, ...) 
+{
+	va_list args;
+	int len;
+	
+	va_start (args, fmt);
+	len = vprintf (fmt, args);
+	va_end (args);
+	return (len);
+}
+
+#endif
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_text_to_multipress
+ *
+ * Purpose:     Convert text to the multi-press representation.
+ *
+ * Inputs:      text	- Input string.
+ *			  Should contain only digits, letters, or space.
+ *			  All other punctuation is treated as space.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	buttons	- Sequence of buttons to press.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_text_to_multipress (const char *text, int quiet, char *buttons)
+{
+	const char *t = text;
+	char *b = buttons;
+	char c;
+	int row, col;
+	int errors = 0;
+	int found;
+	int n;
+
+	*b = '\0';
+	
+	while ((c = *t++) != '\0') {
+
+	  if (isdigit(c)) {
+	
+/* Count number of other characters assigned to this button. */
+/* Press that number plus one more. */
+
+	    n = 1;
+	    row = c - '0';
+	    for (col=0; col<4; col++) {
+	      if (translate[row][col] != 0) {
+	        n++;
+	      }
+	    }
+	    if (buttons[0] != '\0' && *(b-1) == row + '0') {
+	      *b++ = 'A';
+	    }
+	    while (n--) {
+	      *b++ = row + '0';
+	      *b = '\0';
+	    }
+	  }
+	  else {
+	    if (isupper(c)) {
+	      ;
+	    }	  
+	    else if (islower(c)) {
+	      c = toupper(c);
+	    }
+	    else if (c != ' ') {
+	      errors++;
+	      if (! quiet) {
+	        text_color_set (DW_COLOR_ERROR);
+		dw_printf ("Text to multi-press: Only letters, digits, and space allowed.\n");
+	      }
+	      c = ' ';
+	    }
+
+/* Search for everything else in the translation table. */
+/* Press number of times depending on column where found. */
+
+	    found = 0;
+
+	    for (row=0; row<10 && ! found; row++) {
+	      for (col=0; col<4 && ! found; col++) {
+	        if (c == translate[row][col]) {
+
+/* Stick in 'A' if previous character used same button. */
+
+	          if (buttons[0] != '\0' && *(b-1) == row + '0') {
+	            *b++ = 'A';
+	          }
+	          n = col + 1;
+	          while (n--) {
+	            *b++ = row + '0';
+	            *b = '\0';
+	            found = 1;
+	          }
+	        }
+	      }
+	    }
+	    if (! found) {
+	      errors++;
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Text to multi-press: INTERNAL ERROR.  Should not be here.\n");
+	    }
+	  }
+	}
+	return (errors);          
+
+} /* end tt_text_to_multipress */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_text_to_two_key
+ *
+ * Purpose:     Convert text to the two-key representation.
+ *
+ * Inputs:      text	- Input string.
+ *			  Should contain only digits, letters, or space.
+ *			  All other punctuation is treated as space.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	buttons	- Sequence of buttons to press.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_text_to_two_key (const char *text, int quiet, char *buttons)
+{
+	const char *t = text;
+	char *b = buttons;
+	char c;
+	int row, col;
+	int errors = 0;
+	int found;
+
+
+	*b = '\0';
+	
+	while ((c = *t++) != '\0') {
+
+	  if (isdigit(c)) {
+	
+/* Digit is single key press. */
+	  
+	    *b++ = c;
+	    *b = '\0';
+	  }
+	  else {
+	    if (isupper(c)) {
+	      ;
+	    }	  
+	    else if (islower(c)) {
+	      c = toupper(c);
+	    }
+	    else if (c != ' ') {
+	      errors++;
+	      if (! quiet) {
+	        text_color_set (DW_COLOR_ERROR);
+		dw_printf ("Text to two key: Only letters, digits, and space allowed.\n");
+	      }
+	      c = ' ';
+	    }
+
+/* Search for everything else in the translation table. */
+
+	    found = 0;
+
+	    for (row=0; row<10 && ! found; row++) {
+	      for (col=0; col<4 && ! found; col++) {
+	        if (c == translate[row][col]) {
+		  *b++ = '0' + row;
+	          *b++ = 'A' + col;
+	          *b = '\0';
+	          found = 1;
+	        }
+	      }
+	    }
+	    if (! found) {
+	      errors++;
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Text to two-key: INTERNAL ERROR.  Should not be here.\n");
+	    }
+	  }
+	}
+	return (errors);          
+
+} /* end tt_text_to_two_key */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_letter_to_two_digits
+ *
+ * Purpose:     Convert one letter to 2 digit representation.
+ *
+ * Inputs:      c	- One letter.
+ *
+ *		quiet	- True to suppress error messages.
+ *
+ * Outputs:	buttons	- Sequence of two buttons to press.
+ *			  "00" for error because this is probably
+ *			  being used to build up a fixed length
+ *			  string where positions are signficant.
+ *			  Must be at least 3 bytes.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+
+// TODO:  need to test this.
+
+int tt_letter_to_two_digits (char c, int quiet, char buttons[3])
+{
+	int row, col;
+	int errors = 0;
+	int found;
+
+	strlcpy(buttons, "", 3);
+  
+	if (islower(c)) {
+	  c = toupper(c);
+	}
+
+	if ( ! isupper(c)) {
+	  errors++;
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Letter to two digits: \"%c\" found where a letter is required.\n", c);
+	  }
+	  strlcpy (buttons, "00", 3);
+	  return (errors);
+	}
+
+/* Search in the translation table. */
+
+	found = 0;
+
+	for (row=0; row<10 && ! found; row++) {
+	  for (col=0; col<4 && ! found; col++) {
+	    if (c == translate[row][col]) {
+	      buttons[0] = '0' + row;
+	      buttons[1] = '1' + col;
+	      buttons[2] = '\0';
+	      found = 1;
+	    }
+	  }
+	 }
+	 if (! found) {
+	  errors++;
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Letter to two digits: INTERNAL ERROR.  Should not be here.\n");
+	  strlcpy (buttons, "00", 3);
+	}
+
+	return (errors);          
+
+} /* end tt_letter_to_two_digits */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_text_to_call10
+ *
+ * Purpose:     Convert text to the 10 character callsign format.
+ *
+ * Inputs:      text	- Input string.
+ *			  Should contain from 1 to 6 letters and digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	buttons	- Sequence of buttons to press.
+ *			  Should be exactly 10 unless error.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_text_to_call10 (const char *text, int quiet, char *buttons)
+{
+	const char *t;
+	char *b;
+	char c;
+	int packed;		/* two bits per character */
+	int row, col;
+	int errors = 0;
+	int found;
+	char padded[8];
+	char stemp[8];
+
+
+	strcpy (buttons, "");
+
+/* Quick validity check. */
+	
+	if (strlen(text) < 1 || strlen(text) > 6) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Text to callsign 6+4: Callsign \"%s\" not between 1 and 6 characters.\n", text);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+	for (t = text; *t != '\0'; t++) {
+
+	  if (! isalnum(*t)) {
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Text to callsign 6+4: Callsign \"%s\" can contain only letters and digits.\n", text);
+	    }
+	    errors++;
+	    return (errors);
+	  }
+   	}
+
+/* Append spaces if less than 6 characters. */
+
+	strcpy (padded, text);
+	while (strlen(padded) < 6) {
+	  strcat (padded, " ");
+	}
+
+	b = buttons;
+	packed = 0;
+
+	for (t = padded; *t != '\0'; t++) {
+	
+	  c = *t;
+	  if (islower(c)) {
+	      c = toupper(c);
+	  }
+
+/* Search in the translation table. */
+
+	  found = 0;
+
+	  for (row=0; row<10 && ! found; row++) {
+	    for (col=0; col<4 && ! found; col++) {
+	      if (c == call10encoding[row][col]) {
+	        *b++ = '0' + row;
+	        *b = '\0';
+	        packed = packed * 4 + col;  /* base 4 to binary */
+	        found = 1;
+	      }
+	    }
+	  }
+
+	  if (! found) {
+	    /* Earlier check should have caught any character not in translation table. */
+	    errors++;
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Text to callsign 6+4: INTERNAL ERROR 0x%02x.  Should not be here.\n", c);
+	  }
+	}
+
+/* Binary to decimal for the columns. */
+
+	snprintf (stemp, sizeof(stemp), "%04d", packed);
+	strcat (buttons, stemp);
+
+	return (errors);          
+
+} /* end tt_text_to_call10 */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_text_to_satsq		
+ *
+ * Purpose:     Convert Special Satellite Gridsquare to 4 digit DTMF representation.
+ *
+ * Inputs:      text	- Input string.
+ *			  Should be two letters (A thru R) and two digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	buttons	- Sequence of buttons to press.
+ *			  Should be 4 digits unless error.
+ *
+ * Returns:     Number of errors detected.
+ *
+ * Example:	"FM19" is converted to "1819."
+ *		"AA00" is converted to empty string and error return code.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_text_to_satsq (const char *text, int quiet, char *buttons, size_t buttonsize)
+{
+
+	int row, col;
+	int errors = 0;
+	int found;
+	char uc[3];
+
+
+	strlcpy (buttons, "", buttonsize);
+
+/* Quick validity check. */
+	
+	if (strlen(text) < 1 || strlen(text) > 4) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Satellite Gridsquare to DTMF: Gridsquare \"%s\" must be 4 characters.\n", text);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+/* Changing to upper case makes things easier later. */
+
+	uc[0] = islower(text[0]) ? toupper(text[0]) : text[0];
+	uc[1] = islower(text[1]) ? toupper(text[1]) : text[1];
+	uc[2] = '\0';
+
+	if (uc[0] < 'A' || uc[0] > 'R' || uc[1] < 'A' || uc[1] > 'R') {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Satellite Gridsquare to DTMF: First two characters \"%s\" must be letters in range of A to R.\n", text);
+	  }
+	  errors++;
+	  return (errors);
+	}
+
+	if (! isdigit(text[2]) || ! isdigit(text[3])) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Satellite Gridsquare to DTMF: Last two characters \"%s\" must digits.\n", text);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+
+/* Search in the translation table. */
+
+	found = 0;
+
+	for (row=0; row<10 && ! found; row++) {
+	  for (col=0; col<10 && ! found; col++) {
+	    if (strcmp(uc,grid[row][col]) == 0) {
+
+	      char btemp[8];
+
+	      btemp[0] = row + '0';
+	      btemp[1] = col + '0';
+	      btemp[2] = text[2];
+	      btemp[3] = text[3];
+	      btemp[4] = '\0';
+
+	      strlcpy (buttons, btemp, buttonsize);
+	      found = 1;
+	    }
+	  }
+	}
+
+	if (! found) {
+	  /* Sorry, Greenland, and half of Africa, and ... */
+	  errors++;
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Satellite Gridsquare to DTMF: Sorry, your location can't be converted to DTMF.\n");
+	  }
+	}
+
+	return (errors);          
+
+} /* end tt_text_to_satsq */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_text_to_ascii2d
+ *
+ * Purpose:     Convert text to the two digit per ascii character representation.
+ *
+ * Inputs:      text	- Input string.
+ *			  Any printable ASCII characters.
+ *
+ *		quiet	- True to suppress error messages.
+ *
+ * Outputs:	buttons	- Sequence of buttons to press.
+ *
+ * Returns:     Number of errors detected.
+ *
+ * Description:	The standard comment format uses the multipress
+ *		encoding which allows only single case letters, digits,
+ *		and the space character.
+ *		This is a more flexible format that can handle all
+ *		printable ASCII characters.  We take the character code,
+ *		subtract 32 and convert to two decimal digits.  i.e.
+ *			space	= 00
+ *			!	= 01
+ *			"	= 02
+ *			...
+ *			~	= 94
+ *
+ *		This is mostly for internal use, so macros can generate
+ *		comments with all characters.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_text_to_ascii2d (const char *text, int quiet, char *buttons)
+{
+	const char *t = text;
+	char *b = buttons;
+	char c;
+	int errors = 0;
+
+
+	*b = '\0';
+
+	while ((c = *t++) != '\0') {
+
+	  int n;
+
+	  /* "isprint()" might depend on locale so use brute force. */
+
+	  if (c < ' ' || c > '~') c = '?';
+
+	  n = c - 32;
+
+	  *b++ = (n / 10) + '0';
+	  *b++ = (n % 10) + '0';
+	  *b = '\0';
+	}
+	return (errors);
+
+} /* end tt_text_to_ascii2d */
+
+
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_multipress_to_text
+ *
+ * Purpose:     Convert the multi-press representation to text.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain only 0123456789A.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	text	- Converted to letters, digits, space.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_multipress_to_text (const char *buttons, int quiet, char *text)
+{
+	const char *b = buttons;
+	char *t = text;
+	char c;
+	int row, col;
+	int errors = 0;
+	int maxspan;
+	int n;
+
+	*t = '\0';
+	
+	while ((c = *b++) != '\0') {
+
+	  if (isdigit(c)) {
+	
+/* Determine max that can occur in a row. */
+/* = number of other characters assigned to this button + 1. */
+
+	    maxspan = 1;
+	    row = c - '0';
+	    for (col=0; col<4; col++) {
+	      if (translate[row][col] != 0) {
+	        maxspan++;
+	      }
+	    }
+
+/* Count number of consecutive same digits. */
+
+	    n = 1;
+	    while (c == *b) {
+	      b++;
+	      n++;
+	    }
+
+	    if (n < maxspan) {
+	      *t++ = translate[row][n-1];
+	      *t = '\0';
+	    }
+	    else if (n == maxspan) {
+	      *t++ = c;
+	      *t = '\0';
+	    }
+	    else {
+	      errors++;
+	      if (! quiet) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("Multi-press to text: Maximum of %d \"%c\" can occur in a row.\n", maxspan, c);
+	      }
+	      /* Treat like the maximum length. */
+	      *t++ = c;
+	      *t = '\0';
+	    }
+	  }
+	  else if (c == 'A' || c == 'a') {
+
+/* Separator should occur only if digit before and after are the same. */
+	     
+	    if (b == buttons + 1 || *b == '\0' || *(b-2) != *b) {
+	      errors++;
+	      if (! quiet) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("Multi-press to text: \"A\" can occur only between two same digits.\n");
+  	      }
+	    }
+	  }
+	  else {
+
+/* Completely unexpected character. */
+
+	    errors++;
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Multi-press to text: \"%c\" not allowed.\n", c);
+  	    }
+	  }
+	}
+	return (errors);          
+
+} /* end tt_multipress_to_text */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_two_key_to_text
+ *
+ * Purpose:     Convert the two key representation to text.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain only 0123456789ABCD.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	text	- Converted to letters, digits, space.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_two_key_to_text (const char *buttons, int quiet, char *text)
+{
+	const char *b = buttons;
+	char *t = text;
+	char c;
+	int row, col;
+	int errors = 0;
+
+	*t = '\0';
+	
+	while ((c = *b++) != '\0') {
+
+	  if (isdigit(c)) {
+	
+/* Letter (or space) if followed by ABCD. */
+	    
+	    row = c - '0';
+	    col = -1;
+
+	    if (*b >= 'A' && *b <= 'D') {
+	      col = *b++ - 'A';
+	    }
+	    else if (*b >= 'a' && *b <= 'd') {
+	      col = *b++ - 'a';
+	    }
+
+	    if (col >= 0) {
+	      if (translate[row][col] != 0) {
+	        *t++ = translate[row][col];
+	        *t = '\0';
+	      }
+	      else {
+		errors++;
+	        if (! quiet) {
+	          text_color_set (DW_COLOR_ERROR);
+	          dw_printf ("Two key to text: Invalid combination \"%c%c\".\n", c, col+'A');
+		}
+	      }
+	    }
+	    else {
+	      *t++ = c;
+	      *t = '\0';
+	    }
+	  }
+	  else if ((c >= 'A' && c <= 'D') || (c >= 'a' && c <= 'd')) {
+
+/* ABCD not expected here. */
+	     
+	    errors++;
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Two-key to text: A, B, C, or D in unexpected location.\n");
+	    }
+	  }
+	  else {
+
+/* Completely unexpected character. */
+
+	    errors++;
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Two-key to text: Invalid character \"%c\".\n", c);
+  	    }
+	  }
+	}
+	return (errors);          
+
+} /* end tt_two_key_to_text */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_two_digits_to_letter
+ *
+ * Purpose:     Convert the two digit representation to one letter.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain exactly two digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *
+ *		textsiz	- Size of result storage.  Typically 2.
+ *	
+ * Outputs:	text	- Converted to string which should contain one upper case letter.
+ *			  Empty string on error.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_two_digits_to_letter (const char *buttons, int quiet, char *text, size_t textsiz)
+{
+	char c1 = buttons[0];
+	char c2 = buttons[1];
+	int row, col;
+	int errors = 0;
+	char stemp2[2];
+
+	strlcpy (text, "", textsiz);
+	
+	if (c1 >= '2' && c1 <= '9') {
+
+	  if (c2 >= '1' && c2 <= '4') {
+
+	    row = c1 - '0';
+	    col = c2 - '1';
+
+	    if (translate[row][col] != 0) {
+
+	      stemp2[0] = translate[row][col];
+	      stemp2[1] = '\0';
+	      strlcpy (text, stemp2, textsiz);
+	    }
+	    else {
+	      errors++;
+	      strlcpy (text, "", textsiz);
+	      if (! quiet) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("Two digits to letter: Invalid combination \"%c%c\".\n", c1, c2);
+	      }
+	    }
+	  }
+	  else {
+	    errors++;
+	    strlcpy (text, "", textsiz);
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Two digits to letter: Second character \"%c\" must be in range of 1 through 4.\n", c2);
+	    }
+	  }
+	}
+	else {
+	  errors++;
+	  strlcpy (text, "", textsiz);
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Two digits to letter: First character \"%c\" must be in range of 2 through 9.\n", c1);
+	  }
+	}
+
+	return (errors);     
+
+} /* end tt_two_digits_to_letter */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_call10_to_text
+ *
+ * Purpose:     Convert the 10 digit callsign representation to text.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain only ten digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	text	- Converted to callsign with upper case letters and digits.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_call10_to_text (const char *buttons, int quiet, char *text)
+{
+	const char *b;
+	char *t;
+	char c;
+	int packed;		/* from last 4 digits */
+	int row, col;
+	int errors = 0;
+	int k;
+
+	t = text;
+	*t = '\0';	/* result */
+
+/* Validity check. */
+
+	if (strlen(buttons) != 10) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Callsign 6+4 to text: Encoded Callsign \"%s\" must be exactly 10 digits.\n", buttons);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+	for (b = buttons; *b != '\0'; b++) {
+
+	  if (! isdigit(*b)) {
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Callsign 6+4 to text: Encoded Callsign \"%s\" can contain only digits.\n", buttons);
+	    }
+	    errors++;
+	    return (errors);
+	  }
+   	}
+
+	packed = atoi(buttons+6);
+
+	for (k = 0; k < 6; k++) {
+	  c = buttons[k];
+
+	  row = c - '0';
+	  col = (packed >> ((5 - k) *2)) & 3;
+
+	  if (row < 0 || row > 9 || col < 0 || col > 3) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Callsign 6+4 to text: INTERNAL ERROR %d %d.  Should not be here.\n", row, col);
+	    errors++;
+	    row = 0;
+	    col = 1;
+	  }
+
+	  if (call10encoding[row][col] != 0) {
+	    *t++ = call10encoding[row][col];
+	    *t = '\0';
+	  }
+	  else {
+	    errors++;
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Callsign 6+4 to text: Invalid combination: button %d, position %d.\n", row, col);
+	    }
+	  }
+	}
+
+/* Trim any trailing spaces. */
+
+	k = strlen(text) - 1;		/* should be 6 - 1 = 5 */
+
+	while (k >= 0 && text[k] == ' ') {
+	  text[k] = '\0';
+	  k--;
+	}
+
+	return (errors);          
+
+} /* end tt_call10_to_text */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_call5_suffix_to_text
+ *
+ * Purpose:     Convert the 5 digit APRStt 3 style callsign suffix
+ *		representation to text.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain exactly 5 digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *
+ * Outputs:	text	- Converted to 3 upper case letters and/or digits.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text)
+{
+	const char *b;
+	char *t;
+	char c;
+	int packed;		/* from last 4 digits */
+	int row, col;
+	int errors = 0;
+	int k;
+
+	t = text;
+	*t = '\0';	/* result */
+
+/* Validity check. */
+
+	if (strlen(buttons) != 5) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Callsign 3+2 suffix to text: Encoded Callsign \"%s\" must be exactly 5 digits.\n", buttons);
+	  }
+	  errors++;
+	  return (errors);
+	}
+
+	for (b = buttons; *b != '\0'; b++) {
+
+	  if (! isdigit(*b)) {
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Callsign 3+2 suffix to text: Encoded Callsign \"%s\" can contain only digits.\n", buttons);
+	    }
+	    errors++;
+	    return (errors);
+	  }
+	}
+
+	packed = atoi(buttons+3);
+
+	for (k = 0; k < 3; k++) {
+	  c = buttons[k];
+
+	  row = c - '0';
+	  col = (packed >> ((2 - k) * 2)) & 3;
+
+	  if (row < 0 || row > 9 || col < 0 || col > 3) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Callsign 3+2 suffix to text: INTERNAL ERROR %d %d.  Should not be here.\n", row, col);
+	    errors++;
+	    row = 0;
+	    col = 1;
+	  }
+
+	  if (call10encoding[row][col] != 0) {
+	    *t++ = call10encoding[row][col];
+	    *t = '\0';
+	  }
+	  else {
+	    errors++;
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("Callsign 3+2 suffix to text: Invalid combination: button %d, position %d.\n", row, col);
+	    }
+	  }
+	}
+
+	if (errors > 0) {
+	  strcpy (text, "");
+	  return (errors);
+	}
+
+	return (errors);
+
+} /* end tt_call5_suffix_to_text */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_mhead_to_text	
+ *
+ * Purpose:     Convert the DTMF representation of 
+ *		Maidenhead Grid Square Locator to normal text representation.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Must contain 4, 6, 10, or 12, 16, or 18 digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	text	- Converted to gridsquare with upper case letters and digits.
+ *			  Length should be 2, 4, 6, or 8 with alternating letter or digit pairs.
+ *			  Zero length if any error.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+#define MAXMHPAIRS 6
+
+static const struct {
+	char *position;
+	char  min_ch;
+	char  max_ch;
+} mhpair[MAXMHPAIRS] = {
+	{ "first",  'A', 'R' },
+	{ "second", '0', '9' },
+	{ "third",  'A', 'X' },
+	{ "fourth", '0', '9' },
+	{ "fifth",  'A', 'X' },
+	{ "sixth",  '0', '9' }
+};
+
+
+int tt_mhead_to_text (const char *buttons, int quiet, char *text, size_t textsiz)
+{
+	const char *b;
+	int errors = 0;
+
+	strlcpy (text, "", textsiz);
+
+/* Validity check. */
+
+	if (strlen(buttons) != 4 && strlen(buttons) != 6 &&
+	    strlen(buttons) != 10 && strlen(buttons) != 12 &&
+	    strlen(buttons) != 16 && strlen(buttons) != 18) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("DTMF to Maidenhead Gridsquare Locator: Input \"%s\" must be exactly 4, 6, 10, or 12 digits.\n", buttons);
+	  }
+	  errors++;
+	  strlcpy (text, "", textsiz);
+	  return (errors);
+   	}
+
+	for (b = buttons; *b != '\0'; b++) {
+
+	  if (! isdigit(*b)) {
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("DTMF to Maidenhead Gridsquare Locator: Input \"%s\" can contain only digits.\n", buttons);
+	    }
+	    errors++;
+	    strlcpy (text, "", textsiz);
+	    return (errors);
+	  }
+   	}
+
+
+/* Convert DTMF to normal representation. */
+
+	b = buttons;
+
+	int n;
+
+	for (n = 0; n < 6 && b < buttons+strlen(buttons); n++) {
+	  if ((n % 2) == 0) {
+
+	    /* Convert pairs of digits to letter. */
+
+	    char t2[2];
+
+	    errors += tt_two_digits_to_letter (b, quiet, t2, sizeof(t2));
+	    strlcat (text, t2, textsiz);
+	    b += 2;
+
+	    errors += tt_two_digits_to_letter (b, quiet, t2, sizeof(t2));
+	    strlcat (text, t2, textsiz);
+	    b += 2;
+	  }
+	  else {
+
+	    /* Copy the digits. */
+
+	    char d3[3];
+	    d3[0] = *b++;
+	    d3[1] = *b++;
+	    d3[2] = '\0';
+	    strlcat (text, d3, textsiz);
+	  }
+	}
+
+	if (errors != 0) {
+	  strlcpy (text, "", textsiz);
+	}
+	return (errors);          
+
+} /* end tt_mhead_to_text */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_text_to_mhead	
+ *
+ * Purpose:     Convert normal text Maidenhead Grid Square Locator to DTMF representation.
+ *
+ * Inputs:	text	- Maidenhead Grid Square locator in usual format.
+ *			  Length should be 1 to 6 pairs with alternating letter or digit pairs.
+ *
+ *		quiet	- True to suppress error messages.
+ *
+ *		buttonsize - space available for 'buttons' result.
+ *
+ * Outputs:	buttons	- Result with 4, 6, 10, 12, 16, 18 digits.
+ *			  Each letter is replaced by two digits.
+ *			  Digits are simply copied.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_text_to_mhead (const char *text, int quiet, char *buttons, size_t buttonsize)
+{
+	int errors = 0;
+	int np, i;
+
+	strlcpy (buttons, "", buttonsize);
+
+	np = strlen(text) / 2;
+
+	if ((strlen(text) % 2) != 0) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Maidenhead Gridsquare Locator to DTMF: Input \"%s\" must be even number of characters.\n", text);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+	if (np < 1 || np > MAXMHPAIRS) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("Maidenhead Gridsquare Locator to DTMF: Input \"%s\" must be 1 to %d pairs of characters.\n", text, np);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+	for (i = 0; i < np; i++) {
+
+	  char t0 = text[i*2];
+	  char t1 = text[i*2+1];
+
+	  if (toupper(t0) < mhpair[i].min_ch || toupper(t0) > mhpair[i].max_ch ||
+		toupper(t1) < mhpair[i].min_ch || toupper(t1) > mhpair[i].max_ch) {
+	    if (! quiet) {
+	      text_color_set(DW_COLOR_ERROR);
+	      dw_printf("The %s pair of characters in Maidenhead locator \"%s\" must be in range of %c thru %c.\n", 
+				mhpair[i].position, text, mhpair[i].min_ch, mhpair[i].max_ch);
+	    }
+	    strlcpy (buttons, "", buttonsize);
+	    errors++;  
+	    return(errors);
+	  }
+
+	  if (mhpair[i].min_ch == 'A') {		/* Should be letters */
+
+	    char b3[3];
+
+	    errors += tt_letter_to_two_digits (t0, quiet, b3);
+	    strlcat (buttons, b3, buttonsize);
+
+	    errors += tt_letter_to_two_digits (t1, quiet, b3);
+	    strlcat (buttons, b3, buttonsize);
+	  }
+	  else {					/* Should be digits */
+
+	    char b3[3];
+
+	    b3[0] = t0;
+	    b3[1] = t1;
+	    b3[2] = '\0';
+	    strlcat (buttons, b3, buttonsize);
+	  }
+	}
+
+	if (errors != 0) strlcpy (buttons, "", buttonsize);
+
+	return (errors);          
+
+} /* tt_text_to_mhead */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_satsq_to_text	
+ *
+ * Purpose:     Convert the 4 digit DTMF special Satellite gridsquare to normal 2 letters and 2 digits.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain 4 digits.
+ *
+ *		quiet	- True to suppress error messages.
+ *	
+ * Outputs:	text	- Converted to gridsquare with upper case letters and digits.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_satsq_to_text (const char *buttons, int quiet, char *text)
+{
+	const char *b;
+	int row, col;
+	int errors = 0;
+
+	strcpy (text, "");
+
+/* Validity check. */
+
+	if (strlen(buttons) != 4) {
+
+	  if (! quiet) {
+	    text_color_set (DW_COLOR_ERROR);
+	    dw_printf ("DTMF to Satellite Gridsquare: Input \"%s\" must be exactly 4 digits.\n", buttons);
+	  }
+	  errors++;
+	  return (errors);
+   	}
+
+	for (b = buttons; *b != '\0'; b++) {
+
+	  if (! isdigit(*b)) {
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("DTMF to Satellite Gridsquare: Input \"%s\" can contain only digits.\n", buttons);
+	    }
+	    errors++;
+	    return (errors);
+	  }
+   	}
+
+	row = buttons[0] - '0';
+	col = buttons[1] - '0';
+
+	strcpy (text, grid[row][col]);
+	strcat (text, buttons+2);
+
+	return (errors);          
+
+} /* end tt_satsq_to_text */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_ascii2d_to_text
+ *
+ * Purpose:     Convert the two digit ascii representation back to normal text.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain pairs of digits in range 00 to 94.
+ *
+ *		quiet	- True to suppress error messages.
+ *
+ * Outputs:	text	- Converted to any printable ascii characters.
+ *
+ * Returns:     Number of errors detected.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_ascii2d_to_text (const char *buttons, int quiet, char *text)
+{
+	const char *b = buttons;
+	char *t = text;
+	char c1, c2;
+	int errors = 0;
+
+
+	*t = '\0';
+
+	while (*b != '\0') {
+
+	  c1 = *b++;
+	  if (*b != '\0') {
+	    c2 = *b++;
+	  }
+	  else {
+	    c2 = ' ';
+	  }
+
+	  if (isdigit(c1) && isdigit(c2)) {
+	    int n;
+
+	    n = (c1 - '0') * 10 + (c2 - '0');
+
+           *t++ = n + 32;
+	   *t = '\0';
+	  }
+	  else {
+
+/* Unexpected character. */
+
+	    errors++;
+	    if (! quiet) {
+	      text_color_set (DW_COLOR_ERROR);
+	      dw_printf ("ASCII2D to text: Invalid character pair \"%c%c\".\n", c1, c2);
+	    }
+	  }
+	}
+	return (errors);
+
+} /* end tt_ascii2d_to_text */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_guess_type
+ *
+ * Purpose:     Try to guess which encoding we have.
+ *
+ * Inputs:      buttons	- Input string.
+ *			  Should contain only 0123456789ABCD.
+ *
+ * Returns:     TT_MULTIPRESS	- Looks like multipress.
+ *		TT_TWO_KEY	- Looks like two key.
+ *		TT_EITHER	- Could be either one.
+ *
+ *----------------------------------------------------------------*/
+
+typedef enum { TT_EITHER, TT_MULTIPRESS, TT_TWO_KEY } tt_enc_t;
+
+tt_enc_t tt_guess_type (char *buttons) 
+{
+	char text[256];
+	int err_mp;
+	int err_tk;
+	
+/* If it contains B, C, or D, it can't be multipress. */
+
+	if (strchr (buttons, 'B') != NULL || strchr (buttons, 'b') != NULL ||
+	    strchr (buttons, 'C') != NULL || strchr (buttons, 'c') != NULL ||
+	    strchr (buttons, 'D') != NULL || strchr (buttons, 'd') != NULL) {
+	  return (TT_TWO_KEY);
+	}
+
+/* Try parsing quietly and see if one gets errors and the other doesn't. */
+
+	err_mp = tt_multipress_to_text (buttons, 1, text);
+	err_tk = tt_two_key_to_text (buttons, 1, text);
+
+	if (err_mp == 0 && err_tk > 0) {
+	  return (TT_MULTIPRESS);
+ 	}
+	else if (err_tk == 0 && err_mp > 0) {
+	  return (TT_TWO_KEY);
+	}
+
+/* Could be either one. */
+
+	return (TT_EITHER);
+
+} /* end tt_guess_type */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Utility program for testing the encoding.
+ *
+ *----------------------------------------------------------------*/
+
+
+#if ENC_MAIN
+
+int checksum (char *tt)
+{
+	int cs = 10;	/* Assume leading 'A'. */
+			/* Doesn't matter due to mod 10 at the end. */
+	char *p;
+
+	for (p = tt; *p != '\0'; p++) {
+	  if (isdigit(*p)) {
+	    cs += *p - '0';
+	  }
+	  else if (isupper(*p)) {
+	    cs += *p - 'A' + 10;
+	  }
+	  else if (islower(*p)) {
+	    cs += *p - 'a' + 10;
+	  }
+	}
+
+	return (cs % 10);
+}
+
+int main (int argc, char *argv[])
+{
+	char text[1000], buttons[2000];
+	int n;
+	int cs;
+
+	text_color_set (DW_COLOR_INFO);
+
+	if (argc < 2) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Supply text string on command line.\n");
+	  exit (1);
+	}
+
+	strcpy (text, argv[1]);
+
+	for (n = 2; n < argc; n++) {
+	  strcat (text, " ");
+	  strcat (text, argv[n]);
+	}
+
+	dw_printf ("Push buttons for multi-press method:\n");
+	n = tt_text_to_multipress (text, 0, buttons);
+	cs = checksum (buttons);
+	dw_printf ("\"%s\"    checksum for call = %d\n", buttons, cs);
+
+	dw_printf ("Push buttons for two-key method:\n");
+	n = tt_text_to_two_key (text, 0, buttons);
+	cs = checksum (buttons);
+	dw_printf ("\"%s\"    checksum for call = %d\n", buttons, cs);
+
+	n = tt_text_to_call10 (text, 1, buttons);
+	if (n == 0) {
+	  dw_printf ("Push buttons for fixed length 10 digit callsign:\n");
+	  dw_printf ("\"%s\"\n", buttons);
+	}
+
+	n = tt_text_to_mhead (text, 1, buttons, sizeof(buttons));
+	if (n == 0) {
+	  dw_printf ("Push buttons for Maidenhead Grid Square Locator:\n");
+	  dw_printf ("\"%s\"\n", buttons);
+	}
+
+	n = tt_text_to_satsq (text, 1, buttons, sizeof(buttons));
+	if (n == 0) {
+	  dw_printf ("Push buttons for satellite gridsquare:\n");
+	  dw_printf ("\"%s\"\n", buttons);
+	}
+
+	return(0);
+
+}  /* end main */
+
+#endif		/* encoding */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Utility program for testing the decoding.
+ *
+ *----------------------------------------------------------------*/
+
+
+#if DEC_MAIN
+
+
+int main (int argc, char *argv[])
+{
+	char buttons[2000], text[1000];
+	int n;
+
+	text_color_set (DW_COLOR_INFO);
+
+	if (argc < 2) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("Supply button sequence on command line.\n");
+	  exit (1);
+	}
+
+	strcpy (buttons, argv[1]);
+
+	for (n = 2; n < argc; n++) {
+	  strlcat (buttons, argv[n], sizeof(buttons));
+	}
+
+	switch (tt_guess_type(buttons)) {
+	  case TT_MULTIPRESS:
+	    dw_printf ("Looks like multi-press encoding.\n");
+	    break;
+	  case TT_TWO_KEY:
+	    dw_printf ("Looks like two-key encoding.\n");
+	    break;
+	  default:
+	    dw_printf ("Could be either type of encoding.\n");
+	    break;
+	}
+
+	dw_printf ("Decoded text from multi-press method:\n");
+	n = tt_multipress_to_text (buttons, 0, text);
+	dw_printf ("\"%s\"\n", text);
+
+	dw_printf ("Decoded text from two-key method:\n");
+	n = tt_two_key_to_text (buttons, 0, text);
+	dw_printf ("\"%s\"\n", text);
+
+	n = tt_call10_to_text (buttons, 1, text);
+	if (n == 0) {
+	  dw_printf ("Decoded callsign from 10 digit method:\n");
+	  dw_printf ("\"%s\"\n", text);
+	}
+
+	n = tt_mhead_to_text (buttons, 1, text, sizeof(text));
+	if (n == 0) {
+	  dw_printf ("Decoded Maidenhead Locator from DTMF digits:\n");
+	  dw_printf ("\"%s\"\n", text);
+	}
+
+	n = tt_satsq_to_text (buttons, 1, text);
+	if (n == 0) {
+	  dw_printf ("Decoded satellite gridsquare from 4 DTMF digits:\n");
+	  dw_printf ("\"%s\"\n", text);
+	}
+
+	return(0);
+
+}  /* end main */
+
+#endif		/* decoding */
+
+
+#if TTT_TEST
+
+/* gcc -g -DTTT_TEST tt_text.c textcolor.o misc.a && ./a.exe */
+
+
+/* Quick unit test. */
+
+static int error_count;
+
+static void test_text2tt (char *text, char *expect_mp, char *expect_2k, char *expect_c10, char *expect_loc, char *expect_sat)
+{
+	char buttons[100];
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("\nConvert from text \"%s\" to tone sequence.\n", text);
+
+	tt_text_to_multipress (text, 0, buttons);
+	if (strcmp(buttons, expect_mp) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected multi-press \"%s\" but got \"%s\"\n", expect_mp, buttons); }
+
+	tt_text_to_two_key (text, 0, buttons);
+	if (strcmp(buttons, expect_2k) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected two-key \"%s\" but got \"%s\"\n", expect_2k, buttons); }
+
+	tt_text_to_call10 (text, 0, buttons);
+	if (strcmp(buttons, expect_c10) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected call 6+4 \"%s\" but got \"%s\"\n", expect_c10, buttons); }
+
+	tt_text_to_mhead (text, 0, buttons, sizeof(buttons));
+	if (strcmp(buttons, expect_loc) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Maidenhead \"%s\" but got \"%s\"\n", expect_loc, buttons); }
+
+	tt_text_to_satsq (text, 0, buttons, sizeof(buttons));
+	if (strcmp(buttons, expect_sat) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Sat Sq \"%s\" but got \"%s\"\n", expect_sat, buttons); }
+}
+
+static void test_tt2text (char *buttons, char *expect_mp, char *expect_2k, char *expect_c10, char *expect_loc, char *expect_sat)
+{
+	char text[100];
+
+	text_color_set(DW_COLOR_INFO);
+	dw_printf ("\nConvert tone sequence \"%s\" to text.\n", buttons);
+
+	tt_multipress_to_text (buttons, 0, text);
+	if (strcmp(text, expect_mp) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected multi-press \"%s\" but got \"%s\"\n", expect_mp, text); }
+
+	tt_two_key_to_text (buttons, 0, text);
+	if (strcmp(text, expect_2k) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected two-key \"%s\" but got \"%s\"\n", expect_2k, text); }
+
+	tt_call10_to_text (buttons, 0, text);
+	if (strcmp(text, expect_c10) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected call 6+4 \"%s\" but got \"%s\"\n", expect_c10, text); }
+
+	tt_mhead_to_text (buttons, 0, text, sizeof(text));
+	if (strcmp(text, expect_loc) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Maidenhead \"%s\" but got \"%s\"\n", expect_loc, text); }
+
+	tt_satsq_to_text (buttons, 0, text);
+	if (strcmp(text, expect_sat) != 0) { error_count++; text_color_set(DW_COLOR_ERROR); dw_printf ("Expected Sat Sq \"%s\" but got \"%s\"\n", expect_sat, text); }
+}
+
+
+int main (int argc, char *argv[])
+{
+
+	text_color_set (DW_COLOR_INFO);
+	dw_printf ("Test conversions between normal text and DTMF representation.\n");
+	dw_printf ("Some error messages are normal.  Just look for number of errors at end.\n");
+
+	error_count = 0;
+
+		    /* original text   multipress                         two-key                 call10        mhead         satsq */
+
+	test_text2tt ("abcdefg 0123", "2A22A2223A33A33340A00122223333",  "2A2B2C3A3B3C4A0A0123", "",           "",            "");
+
+	test_text2tt ("WB4APR",       "922444427A777",                   "9A2B42A7A7C",          "9242771558", "",            "");
+
+	test_text2tt ("EM29QE78",     "3362222999997733777778888",       "3B6A297B3B78",          "",          "326129723278", "");
+
+	test_text2tt ("FM19",         "3336199999",                      "3C6A19",                "3619003333", "336119",       "1819");
+
+
+		    /* tone_seq                          multipress       two-key                     call10        mhead         satsq */
+
+	test_tt2text ("2A22A2223A33A33340A00122223333",  "ABCDEFG 0123", "A2A222D3D3334 00122223333", "",           "",            "");
+
+	test_tt2text ("9242771558",                      "WAGAQ1KT",     "9242771558",                "WB4APR",     "",            "");
+
+	test_tt2text ("326129723278",                    "DAM1AWPADAPT", "326129723278",               "",          "EM29QE78",    "");
+
+	test_tt2text ("1819",                            "1T1W",         "1819",                       "",           "",           "FM19");
+
+
+	if (error_count > 0) {
+
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("\nERROR: %d tests failed.\n", error_count);
+	  exit (EXIT_FAILURE);
+	}
+
+	text_color_set (DW_COLOR_REC);
+	dw_printf ("\nSUCCESS!  All tests passed.\n");
+	exit (EXIT_SUCCESS);
+
+
+}  /* end main */
+
+#endif
+
+/* end tt_text.c */
+
diff --git a/tt_text.h b/tt_text.h
index 5b5a232..7cab3b8 100644
--- a/tt_text.h
+++ b/tt_text.h
@@ -1,27 +1,38 @@
-
-/* tt_text.h */
-
-
-/* Encode to DTMF representation. */
-
-int tt_text_to_multipress (char *text, int quiet, char *buttons); 
-
-int tt_text_to_two_key (char *text, int quiet, char *buttons); 
-
-int tt_text_to_call10 (char *text, int quiet, char *buttons) ;
-
-int tt_text_to_gridsquare (char *text, int quiet, char *buttons) ;
-
-
-/* Decode DTMF to normal human readable form. */
-
-int tt_multipress_to_text (char *buttons, int quiet, char *text); 
-
-int tt_two_key_to_text (char *buttons, int quiet, char *text);
-
-int tt_call10_to_text (char *buttons, int quiet, char *text);
-
-int tt_gridsquare_to_text (char *buttons, int quiet, char *text);
-
-
+
+/* tt_text.h */
+
+
+/* Encode normal human readable to DTMF representation. */
+
+int tt_text_to_multipress (const char *text, int quiet, char *buttons);
+
+int tt_text_to_two_key (const char *text, int quiet, char *buttons);
+
+int tt_text_to_call10 (const char *text, int quiet, char *buttons);
+
+int tt_text_to_mhead (const char *text, int quiet, char *buttons, size_t buttonsiz);
+
+int tt_text_to_satsq (const char *text, int quiet, char *buttons, size_t buttonsiz);
+
+int tt_text_to_ascii2d (const char *text, int quiet, char *buttons);
+
+
+/* Decode DTMF to normal human readable form. */
+
+int tt_multipress_to_text (const char *buttons, int quiet, char *text);
+
+int tt_two_key_to_text (const char *buttons, int quiet, char *text);
+
+int tt_call10_to_text (const char *buttons, int quiet, char *text);
+
+int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text);
+
+int tt_mhead_to_text (const char *buttons, int quiet, char *text, size_t textsiz);
+
+int tt_satsq_to_text (const char *buttons, int quiet, char *text);
+
+int tt_ascii2d_to_text (const char *buttons, int quiet, char *text);
+
+
+
 /* end tt_text.h */
\ No newline at end of file
diff --git a/tt_user.c b/tt_user.c
index 4ff95da..46aff3c 100644
--- a/tt_user.c
+++ b/tt_user.c
@@ -1,826 +1,1190 @@
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    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
-//    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/>.
-//
-
-/*------------------------------------------------------------------
- *
- * Module:      tt-user.c
- *
- * Purpose:   	Keep track of the APRStt users.
- *		
- * Description: This maintains a list of recently heard APRStt users
- *		and prepares "object" format packets for transmission.
- *
- * References:	This is based upon APRStt (TM) documents but not 100%
- *		compliant due to ambiguities and inconsistencies in
- *		the specifications.
- *
- *		http://www.aprs.org/aprstt.html
- *
- *---------------------------------------------------------------*/
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <time.h>
-#include <assert.h>
-
-#include "direwolf.h"
-#include "version.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "aprs_tt.h"
-#include "tt_text.h"
-#include "dedupe.h"
-#include "tq.h"
-#include "igate.h"
-#include "tt_user.h"
-#include "encode_aprs.h"
-#include "latlong.h"
-
-/* 
- * Information kept about local APRStt users.
- *
- * For now, just use a fixed size array for simplicity.
- */
-
-#if TT_MAIN
-#define MAX_TT_USERS 3	
-#else
-#define MAX_TT_USERS 100
-#endif
-
-#define MAX_CALLSIGN_LEN 9	/* "Object Report" names can be up to 9 characters. */
-				
-#define MAX_COMMENT_LEN 43	/* Max length of comment in "Object Report." */
-
-//#define G_UNKNOWN -999999	/* Should be in one place. */
-
-#define NUM_XMITS 3
-#define XMIT_DELAY_1 5
-#define XMIT_DELAY_2 8
-#define XMIT_DELAY_3 13
-
-
-static struct tt_user_s {
-
-	char callsign[MAX_CALLSIGN_LEN+1];	/* Callsign of station heard. */
-						/* Does not include the "-12" SSID added later. */
-						/* Possibly other tactical call / object label. */
-						/* Null string indicates table position is not used. */
-
-	int ssid;				/* SSID to add. */	
-						/* Default of 12 but not always. */			
-		
-	char overlay;				/* Overlay character. Should be 0-9, A-Z. */
-						/* Could be / or \ for general object. */
-
-	char symbol;				/* 'A' for traditional.  */
-						/* Can be any symbol for extended objects. */
-
-	char digit_suffix[3+1];			/* Suffix abbreviation as 3 digits. */
-
-	time_t last_heard;			/* Timestamp when last heard.  */
-						/* User information will be deleted at some */
-						/* point after last time being heard. */
-
-	int xmits;				/* Number of remaining times to transmit info */
-						/* about the user.   This is set to 3 when */
-						/* a station is heard and decremented each time */
-						/* an object packet is sent.  The idea is to send */
-						/* 3 within 30 seconds to improve chances of */
-						/* being heard while using digipeater duplicate */
-						/* removal. */
-
-	time_t next_xmit;			/* Time for next transmit.  Meaningful only */
-						/* if xmits > 0. */
-
-	int corral_slot;			/* If location is known, set this to 0. */
-						/* Otherwise, this is a display offset position */
-						/* from the gateway. */
-
-	double latitude, longitude;		/* Location either from user or generated */		
-						/* position in the corral. */
-
-	char freq[12];				/* Frequency in format 999.999MHz */
-
-	char comment[MAX_COMMENT_LEN+1];	/* Free form comment. */
-
-	char mic_e;				/* Position status. */
-
-	char dao[8];				/* Enhanced position information. */
-
-} tt_user[MAX_TT_USERS];
-
-
-static void clear_user(int i);
-
-static void xmit_object_report (int i, double c_lat, double c_long, int ambiguity, double c_offs);
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_user_init
- *
- * Purpose:     Initialize the APRStt gateway at system startup time.
- *
- * Inputs:      Configuration options gathered by config.c.
- *
- * Global out:	Make our own local copy of the structure here.
- *
- * Returns:     None
- *
- * Description:	The main program needs to call this at application
- *		start up time after reading the configuration file.
- *
- *		TT_MAIN is defined for unit testing.
- *
- *----------------------------------------------------------------*/
-
-static struct audio_s *save_audio_config_p;
-
-static struct tt_config_s *save_tt_config_p;
-
-
-void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_config)
-{
-	int i;
-
-	save_audio_config_p = p_audio_config;
-
-	save_tt_config_p = p_tt_config;
-
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  clear_user (i);
-	}
-}
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_user_search
- *
- * Purpose:     Search for user in recent history.
- *
- * Inputs:      callsign	- full or a suffix abbreviation
- *		overlay
- *
- * Returns:     Handle for refering to table position or -1 if not found.
- *		This happens to be an index into an array but
- *		the implementation could change so the caller should 
- *		not make any assumptions.
- *
- *----------------------------------------------------------------*/
-
-int tt_user_search (char *callsign, char overlay)
-{
-	int i;
-/*
- * First, look for exact match to full call and overlay.
- */
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (strcmp(callsign, tt_user[i].callsign) == 0 && 
-		overlay == tt_user[i].overlay) {
-	    return (i);
-	  }
-	}
-
-/*
- * Look for digits only suffix plus overlay.
- */
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (strcmp(callsign, tt_user[i].digit_suffix) == 0 && 
-		overlay != ' ' &&
-		overlay == tt_user[i].overlay) {
-	    return (i);
-	  }
-	}
-
-/*
- * Look for digits only suffix if no overlay was specified.
- */
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (strcmp(callsign, tt_user[i].digit_suffix) == 0 && 
-		overlay == ' ') {
-	    return (i);
-	  }
-	}
-
-/*
- * Not sure about the new spelled suffix yet...
- */
-	return (-1);
-
-}  /* end tt_user_search */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        clear_user
- *
- * Purpose:     Clear specified user table entry.
- *
- * Inputs:      handle for user table entry.
- *
- *----------------------------------------------------------------*/
-
-static void clear_user(int i)
-{
-	assert (i >= 0 && i < MAX_TT_USERS);
-
-	memset (&tt_user[i], 0, sizeof (struct tt_user_s));
-
-} /* end clear_user */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        find_avail
- *
- * Purpose:     Find an available user table location.
- *
- * Inputs:      none
- *
- * Returns:     Handle for refering to table position.
- *
- * Description:	If table is already full, this should delete the 
- *		least recently heard user to make room.		
- *
- *----------------------------------------------------------------*/
-
-static int find_avail (void)
-{
-	int i;
-	int i_oldest;
-
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (tt_user[i].callsign[0] == '\0') {
-	    clear_user (i);
-	    return (i);
-	  }
-	}
-
-/* Remove least recently heard. */
-
-	i_oldest = 0;
-
-	for (i=1; i<MAX_TT_USERS; i++) {
-	  if (tt_user[i].last_heard < tt_user[i_oldest].last_heard) {
-	    i_oldest = i;
-	  }
-	}
-
-	clear_user (i_oldest);
-	return (i_oldest);
-
-} /* end find_avail */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        corral_slot
- *
- * Purpose:     Find an available position in the corral.
- *
- * Inputs:      none
- *
- * Returns:     Small integer >= 1 not already in use.
- *
- *----------------------------------------------------------------*/
-
-static int corral_slot (void)
-{
-	int slot, i, used;
-
-	for (slot=1; ; slot++) {
-	  used = 0;;
-	  for (i=0; i<MAX_TT_USERS && ! used; i++) {
-	    if (tt_user[i].callsign[0] != '\0' && tt_user[i].corral_slot == slot) {
-	      used = 1;
-	    }
-	  }
-	  if (!used) {
-	    return (slot);
-	  }
-	}
-
-} /* end corral_slot */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        digit_suffix
- *
- * Purpose:     Find 3 digit only suffix code for given call.
- *
- * Inputs:      callsign
- *
- * Outputs:	3 digit suffix
- *
- *----------------------------------------------------------------*/
-
-static void digit_suffix (char *callsign, char *suffix)
-{
-	char two_key[50];
-	char *t;
-
-
-	strcpy (suffix, "000");
-	tt_text_to_two_key (callsign, 0, two_key);
-	for (t = two_key; *t != '\0'; t++) {
-	  if (isdigit(*t)) {
-	    suffix[0] = suffix[1];
-	    suffix[1] = suffix[2];
-	    suffix[2] = *t;
-	  }
-	}
-
-
-} /* end digit_suffix */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_user_heard
- *
- * Purpose:     Record information from an APRStt trasmission.
- *
- * Inputs:      callsign	- full or an abbreviation
- *		ssid
- *		overlay		- or symbol table identifier
- *		symbol
- *		latitude
- *		longitude
- *		freq
- *		comment
- *		mic_e
- *		dao
- *
- * Outputs:	Information is stored in table above.
- *		Last heard time is updated.
- *		Object Report transmission is scheduled.
- *
- * Returns:	0 for success or one of the TT_ERROR_... codes.
- *
- *----------------------------------------------------------------*/
-
-int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double latitude, 
-		double longitude, char *freq, char *comment, char mic_e, char *dao)
-{
-	int i;
-	
-/*
- * At this time all messages are expected to contain a callsign.
- * Other types of messages, not related to a particular person/object
- * are a future possibility. 
- */
-	if (callsign[0] == '\0') {
-	  text_color_set(DW_COLOR_ERROR);
-	  printf ("APRStt message did not include callsign.\n");
-	  return (TT_ERROR_NO_CALL);
-	}
-
-/*
- * Is it someone new or a returning user?
- */
-	i = tt_user_search (callsign, overlay);
-	if (i == -1) {
-
-/*
- * New person.  Create new table entry with all available information.
- */
-	  i = find_avail ();
-
-	  assert (i >= 0 && i < MAX_TT_USERS);
-	  strncpy (tt_user[i].callsign, callsign, MAX_CALLSIGN_LEN);
-	  tt_user[i].callsign[MAX_CALLSIGN_LEN] = '\0';
-	  tt_user[i].ssid = ssid;
-	  tt_user[i].overlay = overlay;
-	  tt_user[i].symbol = symbol;
-	  digit_suffix(tt_user[i].callsign, tt_user[i].digit_suffix);
-	  if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) {
-	    /* We have specific location. */
-	    tt_user[i].corral_slot = 0;
-	    tt_user[i].latitude = latitude;
-	    tt_user[i].longitude = longitude;
-	  }
-	  else {
-	    /* Unknown location, put it in the corral. */
-	    tt_user[i].corral_slot = corral_slot();
-	  }
-
-	  strcpy (tt_user[i].freq, freq);
-	  strncpy (tt_user[i].comment, comment, MAX_COMMENT_LEN);
-	  tt_user[i].comment[MAX_COMMENT_LEN] = '\0';
-	  tt_user[i].mic_e = mic_e;
-	  strncpy(tt_user[i].dao, dao, 6);
-	}
-	else {
-/*
- * Known user.  Update with any new information.
- */
-	  assert (i >= 0 && i < MAX_TT_USERS);
-
-	  /* Any reason to look at ssid here? */
-
-	  if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) {
-	    /* We have specific location. */
-	    tt_user[i].corral_slot = 0;
-	    tt_user[i].latitude = latitude;
-	    tt_user[i].longitude = longitude;
-	  }
-
-	  if (freq[0] != '\0') {
-	    strcpy (tt_user[i].freq, freq);
-	  }
-
-	  if (comment[0] != '\0') {
-	    strncpy (tt_user[i].comment, comment, MAX_COMMENT_LEN);
-	    tt_user[i].comment[MAX_COMMENT_LEN] = '\0';
-	  }
-
-	  if (mic_e != ' ') {
-	    tt_user[i].mic_e = mic_e;
-	  }
-	  if (strlen(dao) > 0) {
-	    strncpy(tt_user[i].dao, dao, 6);
-	    tt_user[i].dao[5] = '\0';
-	  }
-	}
-
-/*
- * In both cases, note last time heard and schedule object report transmission. 
- */
-	tt_user[i].last_heard = time(NULL);
-	tt_user[i].xmits = 0;
-	tt_user[i].next_xmit = tt_user[i].last_heard + save_tt_config_p->xmit_delay[0];
-
-	return (0);	/* Success! */
-
-} /* end tt_user_heard */
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_user_background
- *
- * Purpose:     
- *
- * Inputs:      
- *
- * Outputs:	Append to transmit queue.
- *
- * Returns:     None
- *
- * Description:	...... TBD
- *
- *----------------------------------------------------------------*/
-
-void tt_user_background (void)
-{
-	time_t now = time(NULL);
-	int i;
-
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (tt_user[i].callsign[0] != '\0') {
-	    if (tt_user[i].xmits < save_tt_config_p->num_xmits && tt_user[i].next_xmit <= now) {
-
-	      xmit_object_report (i, save_tt_config_p->corral_lat, save_tt_config_p->corral_lon,
-			save_tt_config_p->corral_ambiguity, save_tt_config_p->corral_offset);	
- 
-	      /* Increase count of number times this one was sent. */
-	      tt_user[i].xmits++;
-	      if (tt_user[i].xmits < save_tt_config_p->num_xmits) {
-	        /* Schedule next one. */
-	        tt_user[i].next_xmit += save_tt_config_p->xmit_delay[tt_user[i].xmits];    
-	      }
-	    }
-	  }
-	}
-
-/*
- * Purge if too old.
- */
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (tt_user[i].callsign[0] != '\0') {
-	    if (tt_user[i].last_heard + save_tt_config_p->retain_time < now) {
-
-		 // debug - dw_printf ("debug: purging expired user %d\n", i);
-
-	      clear_user (i);
-	    }
-	  }
-	}
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        xmit_object_report
- *
- * Purpose:     Create object report packet and put into transmit queue.
- *
- * Inputs:      i	- Index into user table.
- *		c_lat	- Corral latitude.
- *		c_long	- Corral longitude.
- *		ambiguity - Number of amibiguity digits: 0, 1, 2, or 3.
- *		c_offs	- Corral (latitude) offset.
- *
- * Outputs:	Append to transmit queue.
- *
- * Returns:     None
- *
- * Description:	Details for specified user are converted to
- *		"Object Report Format" and added to the transmit queue.
- *
- *		If the user did not report a position, we have to make 
- *		up something so the corresponding object will show up on
- *		the map or other list of nearby stations.
- *
- *		The traditional approach is to put them in different 
- *		positions in the "corral" by applying increments of an
- *		offset from the starting position.  This has two 
- *		unfortunate properties.  It gives the illusion we know
- *		where the person is located.   Being in the ,,,
- *
- *----------------------------------------------------------------*/
-
-static const char *mic_e_position_comment[10] = {
-	"",
-	"/off duty  ",
-	"/enroute   ",
-	"/in service",
-	"/returning ",
-	"/committed ",
-	"/special   ",
-	"/priority  ",
-	"/emergency ",
-	"/custom 1  " };
-
-static void xmit_object_report (int i, double c_lat, double c_long, int ambiguity, double c_offs)
-{
-	char object_name[20];
-	char object_info[50];
-	char info_comment[100];
-	double olat, olong;
-	char stemp[200];
-	packet_t pp;
-	unsigned char fbuf[AX25_MAX_PACKET_LEN];
-	int flen;
-	char c4[4];
-
-
-	assert (i >= 0 && i < MAX_TT_USERS);
-
-/*
- * Prepare the object name.  
- * Tack on "-12" if it is a callsign.
- */
-	strcpy (object_name, tt_user[i].callsign);
-
-	if (strlen(object_name) <= 6 && tt_user[i].ssid != 0) {
-	  char stemp8[8];
-	  sprintf (stemp8, "-%d", tt_user[i].ssid);
-	  strcat (object_name, stemp8);
-	}
-
-	if (tt_user[i].corral_slot == 0) {
-/* 
- * Known location.
- */
-	  olat = tt_user[i].latitude;
-	  olong = tt_user[i].longitude;
-	}
-	else {
-/*
- * Use made up position in the corral.
- */
-	  olat = c_lat - (tt_user[i].corral_slot - 1) * c_offs;
-	  olong = c_long;
-	}
-
-/*
- * Build comment field from various information.
- */
-	strcpy (info_comment, "");
-
-	if (strlen(tt_user[i].comment) != 0) {
-	  strcat (info_comment, tt_user[i].comment);
-	}
-	if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') {
-	  strcat (info_comment, mic_e_position_comment[tt_user[i].mic_e - '0']);
-	}
-	if (strlen(tt_user[i].dao) > 0) {
-	  strcat (info_comment, tt_user[i].dao);
-	}
-
-	/* Official limit is 43 characters. */
-	info_comment[MAX_COMMENT_LEN] = '\0';
-	
-/*
- * Packet header is built from mycall (of transmit channel) and software version.
- */
-
-	strcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall);
-	strcat (stemp, ">");
-	strcat (stemp, APP_TOCALL);
-	c4[0] = '0' + MAJOR_VERSION;
-	c4[1] = '0' + MINOR_VERSION;
-	c4[2] = '\0';
-	strcat (stemp, c4);
-
-/*
- * Append via path if specified. 
- */
-
-	if (save_tt_config_p->obj_xmit_via[0] != '\0') {
-	  strcat (stemp, ",");
-	  strcat (stemp, save_tt_config_p->obj_xmit_via);
-	}
-
-	strcat (stemp, ":");
-
-	encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, 
-		tt_user[i].overlay, tt_user[i].symbol, 
-		0,0,0,NULL, 0,0,	/* PHGD, C/S */
-		atof(tt_user[i].freq), 0, 0, info_comment, object_info);
-
-	strcat (stemp, object_info);
-
-	//text_color_set(DW_COLOR_ERROR);
-	//printf ("\nDEBUG: %s\n\n", stemp);
-
-
-#if TT_MAIN
-
-	printf ("---> %s\n\n", stemp);
-
-#else
-
-/*
- * Convert to packet and append to transmit queue.
- */
-	pp = ax25_from_text (stemp, 1);
-
-	flen = ax25_pack (pp, fbuf);
-
-/*
- * Process as if we heard ourself.
- */
-	// TODO:  We need radio channel where this came from.
-	// It would make a difference if running two radios
-	// and they have different station identifiers.
- 
-	int chan = 0;
-        igate_send_rec_packet (chan, pp);
-
-	/* Remember it so we don't digipeat our own. */
-
-	dedupe_remember (pp, save_tt_config_p->obj_xmit_chan);
-
-	tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp);
-#endif 
-	
-}
-
-
-
-/*------------------------------------------------------------------
- *
- * Name:        tt_user_dump
- *
- * Purpose:     Print information about known users for debugging.
- *
- * Inputs:      None.
- *
- * Description:	Timestamps displayed relative to current time.
- *
- *----------------------------------------------------------------*/
-
-void tt_user_dump (void)
-{
-	int i;
-	time_t now = time(NULL);
-	
-	printf ("call  ov suf lsthrd xmit nxt cor  lat    long freq       m comment\n");
-	for (i=0; i<MAX_TT_USERS; i++) {
-	  if (tt_user[i].callsign[0] != '\0') {
-	    printf ("%-6s %c%c %-3s %6d %d %+6d %d %6.2f %7.2f %-10s %c %s\n",
-	    	tt_user[i].callsign,
-	    	tt_user[i].overlay,
-	    	tt_user[i].symbol,
-	    	tt_user[i].digit_suffix,
-	    	(int)(tt_user[i].last_heard - now),
-	    	tt_user[i].xmits,
-	    	(int)(tt_user[i].next_xmit - now),
-	    	tt_user[i].corral_slot,
-	    	tt_user[i].latitude,
-	    	tt_user[i].longitude,
-	    	tt_user[i].freq,
-	    	tt_user[i].mic_e,
-	    	tt_user[i].comment);
-	  }
-	}
-			
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:     Quick test for some functions in this file.
- *
- * Description:	Just a smattering, not an organized test.
- *
- * 		$ rm a.exe ; gcc -DTT_MAIN -Iregex tt_user.c tt_text.c encode_aprs.c latlong.c textcolor.c ; ./a.exe
- *
- *----------------------------------------------------------------*/
-
-
-#if TT_MAIN
-
-
-static struct audio_s my_audio_config;
-
-static struct tt_config_s my_tt_config;
-
-
-int main (int argc, char *argv[])
-{
-	int n;
-
-/* Fake audio config - All we care about is mycall for constructing object report packet. */
-
-	memset (&my_audio_config, 0, sizeof(my_audio_config));
-
-	strcpy (my_audio_config.achan[0].mycall, "WB2OSZ-15");
-
-/* Fake TT gateway config. */
-
-	memset (&my_tt_config, 0, sizeof(my_tt_config));	
-
-	/* Don't care about the location translation here. */
-
-	my_tt_config.retain_time = 20;		/* Normally 80 minutes. */
-	my_tt_config.num_xmits = 3;
-	assert (my_tt_config.num_xmits <= TT_MAX_XMITS);
-	my_tt_config.xmit_delay[0] = 3;		/* Before initial transmission. */
-	my_tt_config.xmit_delay[1] = 5;
-	my_tt_config.xmit_delay[2] = 5;
-
-	my_tt_config.corral_lat = 42.61900;
-	my_tt_config.corral_lon = -71.34717;
-	my_tt_config.corral_offset = 0.02 / 60;
-	my_tt_config.corral_ambiguity = 0;
-
-
-	tt_user_init(&my_audio_config, &my_tt_config);
-
-	tt_user_heard ("TEST1",  12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
-	SLEEP_SEC (1);
-	tt_user_heard ("TEST2",  12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
-	SLEEP_SEC (1);
-	tt_user_heard ("TEST3",  12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
-	SLEEP_SEC (1);
-	tt_user_heard ("TEST4",  12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
-	SLEEP_SEC (1);
-	tt_user_heard ("WB2OSZ", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
-	tt_user_heard ("K2H",    12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
-	tt_user_dump ();
-
-	tt_user_heard ("679",    12, 'J', 'A', 37.25,     -71.75,    " ", " ", ' ', "!T99!");
-	tt_user_heard ("WB2OSZ", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "146.520MHz", "", ' ', "!T99!");
-	tt_user_heard ("679",    12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "Hello, world", '9', "!T99!");
-	tt_user_dump ();
-	
-	for (n=0; n<30; n++) {
-	  SLEEP_SEC(1);
-	  tt_user_background ();
-	}
-
-	return(0);
-
-}  /* end main */
-
-#endif		/* unit test */
-
-
-/* end tt-user.c */
-
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2014, 2015  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/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * Module:      tt-user.c
+ *
+ * Purpose:   	Keep track of the APRStt users.
+ *		
+ * Description: This maintains a list of recently heard APRStt users
+ *		and prepares "object" format packets for transmission.
+ *
+ * References:	This is based upon APRStt (TM) documents but not 100%
+ *		compliant due to ambiguities and inconsistencies in
+ *		the specifications.
+ *
+ *		http://www.aprs.org/aprstt.html
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <assert.h>
+
+#include "direwolf.h"
+#include "version.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "aprs_tt.h"
+#include "tt_text.h"
+#include "dedupe.h"
+#include "tq.h"
+#include "igate.h"
+#include "tt_user.h"
+#include "encode_aprs.h"
+#include "latlong.h"
+
+#include "server.h"
+#include "kiss.h"
+#include "kissnet.h"
+
+
+/* 
+ * Information kept about local APRStt users.
+ *
+ * For now, just use a fixed size array for simplicity.
+ */
+
+#if TT_MAIN
+#define MAX_TT_USERS 3	
+#else
+#define MAX_TT_USERS 100
+#endif
+
+#define MAX_CALLSIGN_LEN 9	/* "Object Report" names can be up to 9 characters. */
+				
+#define MAX_COMMENT_LEN 43	/* Max length of comment in "Object Report." */
+
+//#define G_UNKNOWN -999999	/* Should be in one place. */
+
+#define NUM_XMITS 3
+#define XMIT_DELAY_1 5
+#define XMIT_DELAY_2 8
+#define XMIT_DELAY_3 13
+
+
+static struct tt_user_s {
+
+	char callsign[MAX_CALLSIGN_LEN+1];	/* Callsign of station heard. */
+						/* Does not include the "-12" SSID added later. */
+						/* Possibly other tactical call / object label. */
+						/* Null string indicates table position is not used. */
+
+	int count;				/* Number of times we received information for this object. */
+						/* Value 1 means first time and could be used to send */
+						/* a welcome greeting. */
+
+	int ssid;				/* SSID to add. */	
+						/* Default of 12 but not always. */			
+		
+	char overlay;				/* Overlay character. Should be 0-9, A-Z. */
+						/* Could be / or \ for general object. */
+
+	char symbol;				/* 'A' for traditional.  */
+						/* Can be any symbol for extended objects. */
+
+	char digit_suffix[3+1];			/* Suffix abbreviation as 3 digits. */
+
+	time_t last_heard;			/* Timestamp when last heard.  */
+						/* User information will be deleted at some */
+						/* point after last time being heard. */
+
+	int xmits;				/* Number of remaining times to transmit info */
+						/* about the user.   This is set to 3 when */
+						/* a station is heard and decremented each time */
+						/* an object packet is sent.  The idea is to send */
+						/* 3 within 30 seconds to improve chances of */
+						/* being heard while using digipeater duplicate */
+						/* removal. */
+						// TODO:  I think implementation is different.
+
+	time_t next_xmit;			/* Time for next transmit.  Meaningful only */
+						/* if xmits > 0. */
+
+	int corral_slot;			/* If location is known, set this to 0. */
+						/* Otherwise, this is a display offset position */
+						/* from the gateway. */
+
+	char loc_text[24];			/* Text representation of location when a single */
+						/* lat/lon point would be deceptive.  e.g.  */
+						/* 32TPP8049 */
+						/* 32TPP8179549363 */
+						/* 32T 681795 4849363 */
+						/* EM29QE78 */
+
+	double latitude, longitude;		/* Location either from user or generated */		
+						/* position in the corral. */
+
+	int ambiguity;				/* Number of digits to omit from location. */
+						/* Default 0, max. 4. */
+
+	char freq[12];				/* Frequency in format 999.999MHz */
+
+	char ctcss[5];				/* CTCSS tone.  Exactly 3 digits for integer part. */
+						/* For example 74.4 Hz becomes "074". */
+
+	char comment[MAX_COMMENT_LEN+1];	/* Free form comment from user. */
+						/* Comment sent in final object report includes */
+						/* other information besides this. */
+
+	char mic_e;				/* Position status. */
+						/* Should be a character in range of '1' to '9' for */
+						/* the predefined status strings or '0' for none. */
+
+	char dao[8];				/* Enhanced position information. */
+						
+
+} tt_user[MAX_TT_USERS];
+
+
+static void clear_user(int i);
+
+static void xmit_object_report (int i, int first_time);
+
+static void tt_setenv (int i);
+
+
+#if __WIN32__
+
+// setenv is missing on Windows!
+
+int setenv(const char *name, const char *value, int overwrite)
+{
+	char etemp[1000];
+
+	snprintf (etemp, sizeof(etemp), "%s=%s", name, value);
+	putenv (etemp);
+	return (0);
+}
+
+#endif
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_user_init
+ *
+ * Purpose:     Initialize the APRStt gateway at system startup time.
+ *
+ * Inputs:      Configuration options gathered by config.c.
+ *
+ * Global out:	Make our own local copy of the structure here.
+ *
+ * Returns:     None
+ *
+ * Description:	The main program needs to call this at application
+ *		start up time after reading the configuration file.
+ *
+ *		TT_MAIN is defined for unit testing.
+ *
+ *----------------------------------------------------------------*/
+
+static struct audio_s *save_audio_config_p;
+
+static struct tt_config_s *save_tt_config_p;
+
+
+void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_config)
+{
+	int i;
+
+	save_audio_config_p = p_audio_config;
+
+	save_tt_config_p = p_tt_config;
+
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  clear_user (i);
+	}
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_user_search
+ *
+ * Purpose:     Search for user in recent history.
+ *
+ * Inputs:      callsign	- full or a old style 3 DIGIT suffix abbreviation
+ *		overlay
+ *
+ * Returns:     Handle for refering to table position or -1 if not found.
+ *		This happens to be an index into an array but
+ *		the implementation could change so the caller should
+ *		not make any assumptions.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_user_search (char *callsign, char overlay)
+{
+	int i;
+/*
+ * First, look for exact match to full call and overlay.
+ */
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  if (strcmp(callsign, tt_user[i].callsign) == 0 && 
+		overlay == tt_user[i].overlay) {
+	    return (i);
+	  }
+	}
+
+/*
+ * Look for digits only suffix plus overlay.
+ */
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  if (strcmp(callsign, tt_user[i].digit_suffix) == 0 && 
+		overlay != ' ' &&
+		overlay == tt_user[i].overlay) {
+	    return (i);
+	  }
+	}
+
+/*
+ * Look for digits only suffix if no overlay was specified.
+ */
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  if (strcmp(callsign, tt_user[i].digit_suffix) == 0 && 
+		overlay == ' ') {
+	    return (i);
+	  }
+	}
+
+/*
+ * Not sure about the new spelled suffix yet...
+ */
+	return (-1);
+
+}  /* end tt_user_search */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_3char_suffix_search
+ *
+ * Purpose:     Search for new style 3 CHARACTER (vs. 3 digit) suffix in recent history.
+ *
+ * Inputs:      suffix	- full or a old style 3 DIGIT suffix abbreviation
+ *
+ * Outputs:	callsign - corresponding full callsign or empty string.
+ *
+ * Returns:     Handle for refering to table position (>= 0) or -1 if not found.
+ *		This happens to be an index into an array but
+ *		the implementation could change so the caller should
+ *		not make any assumptions.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_3char_suffix_search (char *suffix, char *callsign)
+{
+	int i;
+
+
+/*
+ * Look for suffix in list of known calls.
+ */
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  int len = strlen(tt_user[i].callsign);
+
+	  if (len >= 3 && len <= 6 && strcmp(tt_user[i].callsign + len - 3, suffix) == 0) {
+	    strlcpy (callsign, tt_user[i].callsign, MAX_CALLSIGN_LEN+1);
+	    return (i);
+	  }
+	}
+
+/*
+ * Not found.
+ */
+	strlcpy (callsign, "", MAX_CALLSIGN_LEN+1);
+	return (-1);
+
+}  /* end tt_3char_suffix_search */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        clear_user
+ *
+ * Purpose:     Clear specified user table entry.
+ *
+ * Inputs:      handle for user table entry.
+ *
+ *----------------------------------------------------------------*/
+
+static void clear_user(int i)
+{
+	assert (i >= 0 && i < MAX_TT_USERS);
+
+	memset (&(tt_user[i]), 0, sizeof (struct tt_user_s));
+
+} /* end clear_user */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        find_avail
+ *
+ * Purpose:     Find an available user table location.
+ *
+ * Inputs:      none
+ *
+ * Returns:     Handle for refering to table position.
+ *
+ * Description:	If table is already full, this should delete the 
+ *		least recently heard user to make room.		
+ *
+ *----------------------------------------------------------------*/
+
+static int find_avail (void)
+{
+	int i;
+	int i_oldest;
+
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  if (tt_user[i].callsign[0] == '\0') {
+	    clear_user (i);
+	    return (i);
+	  }
+	}
+
+/* Remove least recently heard. */
+
+	i_oldest = 0;
+
+	for (i=1; i<MAX_TT_USERS; i++) {
+	  if (tt_user[i].last_heard < tt_user[i_oldest].last_heard) {
+	    i_oldest = i;
+	  }
+	}
+
+	clear_user (i_oldest);
+	return (i_oldest);
+
+} /* end find_avail */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        corral_slot
+ *
+ * Purpose:     Find an available position in the corral.
+ *
+ * Inputs:      none
+ *
+ * Returns:     Small integer >= 1 not already in use.
+ *
+ *----------------------------------------------------------------*/
+
+static int corral_slot (void)
+{
+	int slot, i, used;
+
+	for (slot=1; ; slot++) {
+	  used = 0;;
+	  for (i=0; i<MAX_TT_USERS && ! used; i++) {
+	    if (tt_user[i].callsign[0] != '\0' && tt_user[i].corral_slot == slot) {
+	      used = 1;
+	    }
+	  }
+	  if (!used) {
+	    return (slot);
+	  }
+	}
+
+} /* end corral_slot */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        digit_suffix
+ *
+ * Purpose:     Find 3 digit only suffix code for given call.
+ *
+ * Inputs:      callsign
+ *
+ * Outputs:	3 digit suffix
+ *
+ *----------------------------------------------------------------*/
+
+static void digit_suffix (char *callsign, char *suffix)
+{
+	char two_key[50];
+	char *t;
+
+
+	strlcpy (suffix, "000", 5);			// TODO: should have proper size
+	tt_text_to_two_key (callsign, 0, two_key);
+	for (t = two_key; *t != '\0'; t++) {
+	  if (isdigit(*t)) {
+	    suffix[0] = suffix[1];
+	    suffix[1] = suffix[2];
+	    suffix[2] = *t;
+	  }
+	}
+
+
+} /* end digit_suffix */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_user_heard
+ *
+ * Purpose:     Record information from an APRStt trasmission.
+ *
+ * Inputs:      callsign	- full or an abbreviation
+ *		ssid
+ *		overlay		- or symbol table identifier
+ *		symbol
+ *		loc_text	- Original text for non lat/lon location
+ *		latitude
+ *		longitude
+ *		ambiguity
+ *		freq
+ *		ctcss
+ *		comment
+ *		mic_e
+ *		dao
+ *
+ * Outputs:	Information is stored in table above.
+ *		Last heard time is updated.
+ *		Object Report transmission is scheduled.
+ *
+ * Returns:	0 for success or one of the TT_ERROR_... codes.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude, 
+		double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao)
+{
+	int i;
+
+
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("tt_user_heard (%s, %d, %c, %c, %s, ...)\n", callsign, ssid, overlay, symbol, loc_text);
+
+/*
+ * At this time all messages are expected to contain a callsign.
+ * Other types of messages, not related to a particular person/object
+ * are a future possibility. 
+ */
+	if (callsign[0] == '\0') {
+	  text_color_set(DW_COLOR_ERROR);
+	  printf ("APRStt tone sequence did not include callsign / object name.\n");
+	  return (TT_ERROR_NO_CALL);
+	}
+
+/*
+ * Is it someone new or a returning user?
+ */
+	i = tt_user_search (callsign, overlay);
+	if (i == -1) {
+
+/*
+ * New person.  Create new table entry with all available information.
+ */
+	  i = find_avail ();
+
+	  assert (i >= 0 && i < MAX_TT_USERS);
+	  strlcpy (tt_user[i].callsign, callsign, sizeof(tt_user[i].callsign));
+	  tt_user[i].count = 1;
+	  tt_user[i].ssid = ssid;
+	  tt_user[i].overlay = overlay;
+	  tt_user[i].symbol = symbol;
+	  digit_suffix(tt_user[i].callsign, tt_user[i].digit_suffix);
+	  strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text));
+
+	  if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) {
+	    /* We have specific location. */
+	    tt_user[i].corral_slot = 0;
+	    tt_user[i].latitude = latitude;
+	    tt_user[i].longitude = longitude;
+	  }
+	  else {
+	    /* Unknown location, put it in the corral. */
+	    tt_user[i].corral_slot = corral_slot();
+	  }
+
+	  tt_user[i].ambiguity = ambiguity;
+
+	  strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq));
+	  strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss));
+	  strlcpy (tt_user[i].comment, comment, sizeof(tt_user[i].comment));
+	  tt_user[i].mic_e = mic_e;
+	  strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao));
+	}
+	else {
+/*
+ * Known user.  Update with any new information.
+ * Keep any old values where not being updated.
+ */
+	  assert (i >= 0 && i < MAX_TT_USERS);
+
+	  tt_user[i].count++;
+
+	  /* Any reason to look at ssid here? */
+
+	  /* Update the symbol if not the default. */
+
+	  if (overlay != APRSTT_DEFAULT_SYMTAB || symbol != APRSTT_DEFAULT_SYMBOL) {
+	    tt_user[i].overlay = overlay;
+	    tt_user[i].symbol = symbol;
+	  }
+
+	  if (strlen(loc_text) > 0) {
+	    strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text));
+	  }
+
+	  if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) {
+	    /* We have specific location. */
+	    tt_user[i].corral_slot = 0;
+	    tt_user[i].latitude = latitude;
+	    tt_user[i].longitude = longitude;
+	  }
+
+	  if (ambiguity != G_UNKNOWN) {
+	    tt_user[i].ambiguity = ambiguity;
+	  }
+
+	  if (freq[0] != '\0') {
+	    strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq));
+	  }
+
+	  if (ctcss[0] != '\0') {
+	    strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss));
+	  }
+
+	  if (comment[0] != '\0') {
+	    strlcpy (tt_user[i].comment, comment, MAX_COMMENT_LEN);
+	    tt_user[i].comment[MAX_COMMENT_LEN] = '\0';
+	  }
+
+	  if (mic_e != ' ') {
+	    tt_user[i].mic_e = mic_e;
+	  }
+
+	  if (strlen(dao) > 0) {
+	    strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao));
+	  }
+	}
+
+/*
+ * In both cases, note last time heard and schedule object report transmission. 
+ */
+	tt_user[i].last_heard = time(NULL);
+	tt_user[i].xmits = 0;
+	tt_user[i].next_xmit = tt_user[i].last_heard + save_tt_config_p->xmit_delay[0];
+
+/*
+ * Send to applications and IGate immediately.
+ */
+
+	xmit_object_report (i, 1);	
+
+/*
+ * Put properties into environment variables in preparation
+ * for calling a user-specified script.
+ */
+
+	tt_setenv (i);
+
+	return (0);	/* Success! */
+
+} /* end tt_user_heard */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_user_background
+ *
+ * Purpose:     
+ *
+ * Inputs:      
+ *
+ * Outputs:	Append to transmit queue.
+ *
+ * Returns:     None
+ *
+ * Description:	...... TBD
+ *
+ *----------------------------------------------------------------*/
+
+void tt_user_background (void)
+{
+	time_t now = time(NULL);
+	int i;
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("tt_user_background()  now = %d\n", (int)now);
+
+
+	for (i=0; i<MAX_TT_USERS; i++) {
+
+	  assert (i >= 0 && i < MAX_TT_USERS);
+
+	  if (tt_user[i].callsign[0] != '\0') {
+	    if (tt_user[i].xmits < save_tt_config_p->num_xmits && tt_user[i].next_xmit <= now) {
+
+
+	      //text_color_set(DW_COLOR_DEBUG);
+	      //dw_printf ("tt_user_background()  now = %d\n", (int)now);
+	      //tt_user_dump ();
+
+	      xmit_object_report (i, 0);	
+ 
+	      /* Increase count of number times this one was sent. */
+	      tt_user[i].xmits++;
+	      if (tt_user[i].xmits < save_tt_config_p->num_xmits) {
+	        /* Schedule next one. */
+	        tt_user[i].next_xmit += save_tt_config_p->xmit_delay[tt_user[i].xmits];    
+	      }
+
+	      //tt_user_dump ();
+	    }
+	  }
+	}
+
+/*
+ * Purge if too old.
+ */
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  if (tt_user[i].callsign[0] != '\0') {
+	    if (tt_user[i].last_heard + save_tt_config_p->retain_time < now) {
+
+	     //dw_printf ("debug: purging expired user %d\n", i);
+
+	      clear_user (i);
+	    }
+	  }
+	}
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        xmit_object_report
+ *
+ * Purpose:     Create object report packet and put into transmit queue.
+ *
+ * Inputs:      i	   - Index into user table.
+ *
+ *		first_time - Is this being called immediately after the tone sequence
+ *			 	was received or after some delay?
+ *				For the former, we send to any attached applications
+ *				and the IGate.
+ *				For the latter, we transmit over radio.
+ *
+ * Outputs:	Append to transmit queue.
+ *
+ * Returns:     None
+ *
+ * Description:	Details for specified user are converted to
+ *		"Object Report Format" and added to the transmit queue.
+ *
+ *		If the user did not report a position, we have to make 
+ *		up something so the corresponding object will show up on
+ *		the map or other list of nearby stations.
+ *
+ *		The traditional approach is to put them in different 
+ *		positions in the "corral" by applying increments of an
+ *		offset from the starting position.  This has two 
+ *		unfortunate properties.  It gives the illusion we know
+ *		where the person is located.   Being in the ,,,
+ *
+ *----------------------------------------------------------------*/
+
+static void xmit_object_report (int i, int first_time)
+{
+	char object_name[20];		// xxxxxxxxx or xxxxxx-nn
+	char info_comment[200];		// usercomment [locationtext] /status !DAO!
+	char object_info[250];		// info part of Object Report packet
+	char stemp[300];		// src>dest,path:object_info
+
+	double olat, olong;
+	int oambig;			// Position ambiguity.
+	packet_t pp;
+	char c4[4];
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//printf ("xmit_object_report (index = %d, first_time = %d) rx = %d, tx = %d\n", i, first_time, 
+	//			save_tt_config_p->obj_recv_chan, save_tt_config_p->obj_xmit_chan);
+
+	assert (i >= 0 && i < MAX_TT_USERS);
+
+/*
+ * Prepare the object name.  
+ * Tack on "-12" if it is a callsign.
+ */
+	strlcpy (object_name, tt_user[i].callsign, sizeof(object_name));
+
+	if (strlen(object_name) <= 6 && tt_user[i].ssid != 0) {
+	  char stemp8[8];
+	  snprintf (stemp8, sizeof(stemp8), "-%d", tt_user[i].ssid);
+	  strlcat (object_name, stemp8, sizeof(object_name));
+	}
+
+	if (tt_user[i].corral_slot == 0) {
+/* 
+ * Known location.
+ */
+	  olat = tt_user[i].latitude;
+	  olong = tt_user[i].longitude;
+	  oambig = tt_user[i].ambiguity;
+	  if (oambig == G_UNKNOWN) oambig = 0;
+	}
+	else {
+/*
+ * Use made up position in the corral.
+ */
+	  double c_lat = save_tt_config_p->corral_lat;		// Corral latitude.
+	  double c_long = save_tt_config_p->corral_lon;		// Corral longitude.
+	  double c_offs =  save_tt_config_p->corral_offset;	// Corral (latitude) offset.
+
+	  olat = c_lat - (tt_user[i].corral_slot - 1) * c_offs;
+	  olong = c_long;
+	  oambig = 0;
+	}
+
+/*
+ * Build comment field from various information.
+ *
+ * 	usercomment [locationtext] /status !DAO!
+ *
+ * Any frequency is inserted at beginning later.
+ */
+	strlcpy (info_comment, "", sizeof(info_comment));
+
+	if (strlen(tt_user[i].comment) != 0) {
+	  strlcat (info_comment, tt_user[i].comment, sizeof(info_comment));
+	}
+
+	if (strlen(tt_user[i].loc_text) > 0) {
+	  if (strlen(info_comment) > 0) {
+	    strlcat (info_comment, " ", sizeof(info_comment));
+	  }
+	  strlcat (info_comment, "[", sizeof(info_comment));
+	  strlcat (info_comment, tt_user[i].loc_text, sizeof(info_comment));
+	  strlcat (info_comment, "]", sizeof(info_comment));
+	}
+
+	if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') {
+	  
+	  if (strlen(info_comment) > 0) {
+	    strlcat (info_comment, " ", sizeof(info_comment));
+	  }
+
+	  // Insert "/" if status does not already begin with it.
+	  if (save_tt_config_p->status[tt_user[i].mic_e - '0'][0] != '/') {
+	    strlcat (info_comment, "/", sizeof(info_comment));
+	  }
+	  strlcat (info_comment, save_tt_config_p->status[tt_user[i].mic_e - '0'], sizeof(info_comment));
+	}
+
+	if (strlen(tt_user[i].dao) > 0) {
+	  if (strlen(info_comment) > 0) {
+	    strlcat (info_comment, " ", sizeof(info_comment));
+	  }
+	  strlcat (info_comment, tt_user[i].dao, sizeof(info_comment));
+	}
+
+	/* Official limit is 43 characters. */
+	//info_comment[MAX_COMMENT_LEN] = '\0';
+	
+/*
+ * Packet header is built from mycall (of transmit channel) and software version.
+ */
+
+	if (save_tt_config_p->obj_xmit_chan >= 0) {
+	  strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall, sizeof(stemp));
+	}
+	else {
+	  strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_recv_chan].mycall, sizeof(stemp));
+	}
+	strlcat (stemp, ">", sizeof(stemp));
+	strlcat (stemp, APP_TOCALL, sizeof(stemp));
+	c4[0] = '0' + MAJOR_VERSION;
+	c4[1] = '0' + MINOR_VERSION;
+	c4[2] = '\0';
+	strlcat (stemp, c4, sizeof(stemp));
+
+/*
+ * Append via path, for transmission, if specified. 
+ */
+
+	if ( ! first_time && save_tt_config_p->obj_xmit_via[0] != '\0') {
+	  strlcat (stemp, ",", sizeof(stemp));
+	  strlcat (stemp, save_tt_config_p->obj_xmit_via, sizeof(stemp));
+	}
+
+	strlcat (stemp, ":", sizeof(stemp));
+
+	encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, oambig,
+		tt_user[i].overlay, tt_user[i].symbol, 
+		0,0,0,NULL, G_UNKNOWN, G_UNKNOWN,	/* PHGD, Course/Speed */
+		strlen(tt_user[i].freq) > 0 ? atof(tt_user[i].freq) : G_UNKNOWN,
+		strlen(tt_user[i].ctcss) > 0 ? atof(tt_user[i].ctcss) : G_UNKNOWN,
+		G_UNKNOWN,	/* CTCSS */
+		info_comment, object_info, sizeof(object_info));
+
+	strlcat (stemp, object_info, sizeof(stemp));
+
+#if TT_MAIN
+
+	printf ("---> %s\n\n", stemp);
+
+#else
+
+	if (first_time) {
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("[APRStt] %s\n", stemp);
+	}
+
+/*
+ * Convert text to packet.
+ */
+	pp = ax25_from_text (stemp, 1);
+
+	if (pp == NULL) {
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("\"%s\"\n", stemp);
+	  return;
+	}
+
+
+/* 
+ * Send to one or more of the following depending on configuration:
+ *	Transmit queue.
+ *	Any attached application(s).
+ * 	IGate.
+ *
+ * When transmitting over the radio, it gets sent multipe times, to help
+ * probablity of being heard, with increasing delays between.
+ *
+ * The other methods are reliable so we only want to send it once.
+ */
+
+	if (first_time && save_tt_config_p->obj_send_to_app)  {
+	  unsigned char fbuf[AX25_MAX_PACKET_LEN];
+	  int flen;
+
+ 	  // TODO1.3:  Put a wrapper around this so we only call one function to send by all methods.
+
+	  flen = ax25_pack(pp, fbuf);
+
+	  server_send_rec_packet (save_tt_config_p->obj_recv_chan, pp, fbuf, flen);
+	  kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, fbuf, flen);
+	  kiss_send_rec_packet (save_tt_config_p->obj_recv_chan, fbuf, flen);
+	}
+
+	if (first_time && save_tt_config_p->obj_send_to_ig)  {
+
+	  //text_color_set(DW_COLOR_DEBUG);
+	  //dw_printf ("xmit_object_report (): send to IGate\n");
+
+          igate_send_rec_packet (save_tt_config_p->obj_recv_chan, pp);
+
+	}
+
+	if ( ! first_time && save_tt_config_p->obj_xmit_chan >= 0) {
+
+	  /* Remember it so we don't digipeat our own. */
+
+	  dedupe_remember (pp, save_tt_config_p->obj_xmit_chan);
+
+	  tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp);
+	}
+	else {
+	  ax25_delete (pp);
+	}
+
+#endif 
+	
+
+}
+
+static const char *letters[26] = {
+        "Alpha",
+        "Bravo",
+        "Charlie",
+        "Delta",
+        "Echo",
+        "Foxtrot",
+        "Golf",
+        "Hotel",
+        "India",
+        "Juliet",
+        "Kilo",
+        "Lima",
+        "Mike",
+        "November",
+        "Oscar",
+        "Papa",
+        "Quebec",
+        "Romeo",
+        "Sierra",
+        "Tango",
+        "Uniform",
+        "Victor",
+        "Whiskey",
+        "X-ray",
+        "Yankee",
+        "Zulu"
+};
+
+static const char *digits[10] = {
+	"Zero",
+	"One",
+	"Two",
+	"Three",
+	"Four",
+	"Five",
+	"Six",
+	"Seven",
+	"Eight",
+	"Nine"		
+};
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_setenv
+ *
+ * Purpose:     Put information in environment variables in preparation
+ *		for calling a user-supplied script for custom processing.
+ *
+ * Inputs:      i	- Index into tt_user table.
+ *
+ * Description:	Timestamps displayed relative to current time.
+ *
+ *----------------------------------------------------------------*/
+
+
+static void tt_setenv (int i)
+{
+	char stemp[256];
+	char t2[2];
+	char *p;
+
+	assert (i >= 0 && i < MAX_TT_USERS);
+
+	setenv ("TTCALL", tt_user[i].callsign, 1);
+
+	strlcpy (stemp, "", sizeof(stemp));
+	t2[1] = '\0';
+	for (p = tt_user[i].callsign; *p != '\0'; p++) {
+	  t2[0] = *p;
+	  strlcat (stemp, t2, sizeof(stemp));
+	  if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp));
+	}
+	setenv ("TTCALLSP", stemp, 1);
+
+	strlcpy (stemp, "", sizeof(stemp));
+	for (p = tt_user[i].callsign; *p != '\0'; p++) {
+	  if (isupper(*p)) {
+	    strlcat (stemp, letters[*p - 'A'], sizeof(stemp));
+	  }
+	  else if (islower(*p)) {
+	    strlcat (stemp, letters[*p - 'a'], sizeof(stemp));
+	  }
+	  else if (isdigit(*p)) {
+	    strlcat (stemp, digits[*p - '0'], sizeof(stemp));
+	  }
+	  else {
+	    t2[0] = *p;
+	    strlcat (stemp, t2, sizeof(stemp));
+	  }
+	  if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp));
+	}
+	setenv ("TTCALLPH", stemp, 1);
+
+	snprintf (stemp, sizeof(stemp), "%d", tt_user[i].ssid);
+	setenv ("TTSSID",stemp , 1);
+
+	snprintf (stemp, sizeof(stemp), "%d", tt_user[i].count);
+	setenv ("TTCOUNT",stemp , 1);
+
+	snprintf (stemp, sizeof(stemp), "%c%c", tt_user[i].overlay, tt_user[i].symbol);
+	setenv ("TTSYMBOL",stemp , 1);
+
+	snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].latitude);
+	setenv ("TTLAT",stemp , 1);
+
+	snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].longitude);
+	setenv ("TTLON",stemp , 1);
+
+	setenv ("TTFREQ", tt_user[i].freq, 1);
+
+	// TODO: Should convert to actual frequency. e.g.  074 becomes 74.4
+	// There is some code for this in decode_aprs.c but not broken out
+	// into a function that we could use from here.
+	// TODO: Document this environment variable after converting.
+
+	setenv ("TTCTCSS", tt_user[i].ctcss, 1);
+
+	setenv ("TTCOMMENT", tt_user[i].comment, 1);
+
+	setenv ("TTLOC", tt_user[i].loc_text, 1);
+
+	if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') {
+	  setenv ("TTSTATUS", save_tt_config_p->status[tt_user[i].mic_e - '0'], 1);
+	}
+	else {
+	  setenv ("TTSTATUS", "", 1);
+	}
+
+	setenv ("TTDAO", tt_user[i].dao, 1);
+
+} /* end tt_setenv */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        tt_user_dump
+ *
+ * Purpose:     Print information about known users for debugging.
+ *
+ * Inputs:      None.
+ *
+ * Description:	Timestamps displayed relative to current time.
+ *
+ *----------------------------------------------------------------*/
+
+void tt_user_dump (void)
+{
+	int i;
+	time_t now = time(NULL);
+	
+	printf ("call   ov suf lsthrd xmit nxt cor  lat    long freq     ctcss m comment\n");
+	for (i=0; i<MAX_TT_USERS; i++) {
+	  if (tt_user[i].callsign[0] != '\0') {
+	    printf ("%-6s %c%c %-3s %6d %d %+6d %d %6.2f %7.2f %-10s %-3s %c %s\n",
+	    	tt_user[i].callsign,
+	    	tt_user[i].overlay,
+	    	tt_user[i].symbol,
+	    	tt_user[i].digit_suffix,
+	    	(int)(tt_user[i].last_heard - now),
+	    	tt_user[i].xmits,
+	    	(int)(tt_user[i].next_xmit - now),
+	    	tt_user[i].corral_slot,
+	    	tt_user[i].latitude,
+	    	tt_user[i].longitude,
+	    	tt_user[i].freq,
+		tt_user[i].ctcss,
+	    	tt_user[i].mic_e,
+	    	tt_user[i].comment);
+	  }
+	}
+			
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:     Quick test for some functions in this file.
+ *
+ * Description:	Just a smattering, not an organized test.
+ *
+ * 		$ rm a.exe ; gcc -DTT_MAIN -Iregex tt_user.c tt_text.c encode_aprs.c latlong.c textcolor.c misc.a ; ./a.exe
+ *
+ *----------------------------------------------------------------*/
+
+
+#if TT_MAIN
+
+
+static struct audio_s my_audio_config;
+
+static struct tt_config_s my_tt_config;
+
+
+int main (int argc, char *argv[])
+{
+	int n;
+
+/* Fake audio config - All we care about is mycall for constructing object report packet. */
+
+	memset (&my_audio_config, 0, sizeof(my_audio_config));
+
+	strlcpy (my_audio_config.achan[0].mycall, "WB2OSZ-15", sizeof(my_audio_config.achan[0].mycall));
+
+/* Fake TT gateway config. */
+
+	memset (&my_tt_config, 0, sizeof(my_tt_config));	
+
+	/* Don't care about the location translation here. */
+
+	my_tt_config.retain_time = 20;		/* Normally 80 minutes. */
+	my_tt_config.num_xmits = 3;
+	assert (my_tt_config.num_xmits <= TT_MAX_XMITS);
+	my_tt_config.xmit_delay[0] = 3;		/* Before initial transmission. */
+	my_tt_config.xmit_delay[1] = 5;
+	my_tt_config.xmit_delay[2] = 5;
+
+	my_tt_config.corral_lat = 42.61900;
+	my_tt_config.corral_lon = -71.34717;
+	my_tt_config.corral_offset = 0.02 / 60;
+	my_tt_config.corral_ambiguity = 0;
+
+
+	tt_user_init(&my_audio_config, &my_tt_config);
+
+// tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude,
+//              double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao);
+
+	tt_user_heard ("TEST1",  12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
+	SLEEP_SEC (1);
+	tt_user_heard ("TEST2",  12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
+	SLEEP_SEC (1);
+	tt_user_heard ("TEST3",  12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
+	SLEEP_SEC (1);
+	tt_user_heard ("TEST4",  12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
+	SLEEP_SEC (1);
+	tt_user_heard ("WB2OSZ", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
+	tt_user_heard ("K2H",    12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
+	tt_user_dump ();
+
+	tt_user_heard ("679",    12, 'J', 'A', "", 37.25,     -71.75,    0, "", " ", " ", ' ', "!T99!");
+	tt_user_heard ("WB2OSZ", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "146.520MHz", "", "", ' ', "!T99!");
+	tt_user_heard ("WB1GOF", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "146.955MHz", "074", "", ' ', "!T99!");
+	tt_user_heard ("679",    12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "Hello, world", '9', "!T99!");
+	tt_user_dump ();
+	
+	for (n=0; n<30; n++) {
+	  SLEEP_SEC(1);
+	  tt_user_background ();
+	}
+
+	return(0);
+
+}  /* end main */
+
+#endif		/* unit test */
+
+
+/* end tt-user.c */
+
diff --git a/tt_user.h b/tt_user.h
index 2e0b256..4ff2ec8 100644
--- a/tt_user.h
+++ b/tt_user.h
@@ -1,12 +1,15 @@
-
-/* tt_user.h */
-
-
-#include "audio.h"
-
-void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p);
-
-int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double latitude, 
-		double longitude, char *freq, char *comment, char mic_e, char *dao);
-
-void tt_user_background (void);
\ No newline at end of file
+
+/* tt_user.h */
+
+
+#include "audio.h"
+
+void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p);
+
+int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude, 
+		double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao);
+
+int tt_3char_suffix_search (char *suffix, char *callsign);
+
+void tt_user_background (void);
+void tt_user_dump (void);
\ No newline at end of file
diff --git a/ttcalc.c b/ttcalc.c
index fe9924f..17538e3 100644
--- a/ttcalc.c
+++ b/ttcalc.c
@@ -1,544 +1,545 @@
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2013, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      ttcalc.c
- *
- * Purpose:   	Simple Touch Tone to Speech calculator.
- *		
- * Description:	Demonstration of how Dire Wolf can be used
- *		as a DTMF / Speech interface for ham radio applications.
- *
- * Usage:	Start up direwolf with configuration:
- *			- DTMF decoder enabled.
- *			- Text-to-speech enabled.
- *			- Listening to standard port 8000 for a client application.
- *
- *		Run this in a different window.
- *
- *		User sends formulas such as:
- *
- *			2 * 3 * 4 #
- *
- *		with the touch tone pad.
- *		The result is sent back with speech, e.g. "Twenty Four."
- *		
- *---------------------------------------------------------------*/
-
-
-#if __WIN32__
-
-#include <winsock2.h>
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0501	/* Minimum OS version is XP. */
-#include <ws2tcpip.h>
-#else 
-#include <stdlib.h>
-#include <netdb.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <sys/errno.h>
-#endif
-
-#include <unistd.h>
-#include <stdio.h>
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-
-
-struct agwpe_s {	
-  short portx;			/* 0 for first, 1 for second, etc. */
-  short port_hi_reserved;	
-  short kind_lo;		/* message type */
-  short kind_hi;
-  char call_from[10];
-  char call_to[10];
-  int data_len;			/* Number of data bytes following. */
-  int user_reserved;
-};
-
-
-static int calculator (char *str);
-static int connect_to_server (char *hostname, char *port);
-static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
-
-
-
-/*------------------------------------------------------------------
- *
- * Name: 	main
- *
- *---------------------------------------------------------------*/
-
-
-
-int main (int argc, char *argv[])
-{
-
-	int server_sock = -1;
-	struct agwpe_s mon_cmd;
-	char data[1024];
-	char hostname[30] = "localhost";
-	char port[10] = "8000";
-
-#if __WIN32__
-#else
-	int err;
-
- 	setlinebuf (stdout);
-#endif
-
-	assert (calculator("12a34#") == 46);
-	assert (calculator("2*3A4#") == 10);
-	assert (calculator("5*100A3#") == 503);
-	assert (calculator("6a4*5#") == 50);
-
-/*
- * Try to attach to Dire Wolf.
- */
-
-	server_sock = connect_to_server (hostname, port);
-
-
-	if (server_sock == -1) {
-	  exit (1);
-	}
-
-/*
- * Send command to toggle reception of frames in raw format.
- *
- * Note: Monitor format is only for UI frames.
- */
-
-	memset (&mon_cmd, 0, sizeof(mon_cmd));
-
-	mon_cmd.kind_lo = 'k';
-
-#if __WIN32__	      
-	send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
-#else
-	err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
-#endif
-
-
-/*
- * Print all of the monitored packets.
- */
-
-	while (1) {
-	  int n;
-
-#if __WIN32__
-	  n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
-#else
-	  n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
-#endif
-
-	  if (n != sizeof(mon_cmd)) {
-	    printf ("Read error, received %d command bytes.\n", n);
-	    exit (1);
-	  }
-
-	  assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data));
-
-	  if (mon_cmd.data_len > 0) {
-#if __WIN32__
-	    n = recv (server_sock, data, mon_cmd.data_len, 0);
-#else
-	    n = read (server_sock, data, mon_cmd.data_len);
-#endif
-
-	    if (n != mon_cmd.data_len) {
-	      printf ("Read error, client received %d data bytes when %d expected.\n", n, mon_cmd.data_len);
-	      exit (1);
-	    }
-	  }
-
-/* 
- * Print it.
- */
-
-	  if (mon_cmd.kind_lo == 'K') {
-	    packet_t pp;
-	    char *pinfo;
-	    int info_len;
-	    char result[400];
-	    char *p;
-	    alevel_t alevel;
-	    int chan;
-
-	    chan = mon_cmd.portx;
-	    memset (&alevel, 0, sizeof(alevel));
-	    pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
-	    ax25_format_addrs (pp, result);
-	    info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
-	    pinfo[info_len] = '\0';
-	    strcat (result, pinfo);
-	    for (p=result; *p!='\0'; p++) {
-	      if (! isprint(*p)) *p = ' ';
-	    }
-
-	    printf ("[%d] %s\n", chan, result);
-
-	    	
-/*
- * Look for Special touch tone packet with "t" in first position of the Information part.
- */
-
-	    if (*pinfo == 't') {
-	
-	      int n;
-	      char reply_text[200];
-	      packet_t reply_pp;
-	      struct {
-	        struct agwpe_s hdr;
-		char extra;
-	        unsigned char frame[AX25_MAX_PACKET_LEN];
-	      } xmit_raw;
-
-/*
- * Send touch tone sequence to calculator and get the answer.
- *
- * Put your own application here instead.  Here are some ideas:
- *
- *  http://www.tapr.org/pipermail/aprssig/2015-January/044069.html
- */
-	      n = calculator (pinfo+1);
-	      printf ("\nCalculator returns %d\n\n", n);
-
-/*
- * Convert to AX.25 frame.
- * Notice that the special destination will cause it to be spoken.
- */
-	      sprintf (reply_text, "N0CALL>SPEECH:%d", n);
-	      reply_pp = ax25_from_text(reply_text, 1);
-
-/*
- * Send it to the TNC.
- * In this example we are transmitting speech on the same channel
- * where the tones were heard.  We could also send AX.25 frames to 
- * other radio channels.
- */
-	      memset (&xmit_raw, 0, sizeof(xmit_raw));
-
-	      xmit_raw.hdr.portx = chan;
-	      xmit_raw.hdr.kind_lo = 'K';
-	      xmit_raw.hdr.data_len = 1 + ax25_pack (reply_pp, xmit_raw.frame);
-
-#if __WIN32__	      
-	      send (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len, 0);
-#else
-	      err = write (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len);
-#endif
-	      ax25_delete (reply_pp);
-	    }
-
-    
-	    ax25_delete (pp);
-
-	  }
-	}
-
-} /* main */
-
-
-/*------------------------------------------------------------------
- *
- * Name: 	calculator
- * 
- * Purpose:	Simple calculator to demonstrate Touch Tone to Speech 
- *		application tool kit.
- *
- * Inputs:	str	- Sequence of touch tone characters: 0-9 A-D * #
- *			  It should be terminated with #.
- *
- * Returns:	Numeric result of calculation.
- *
- * Description:	This is a simple calculator that recognizes 
- *			numbers,
- *			* for multiply 
- *			A for add
- *			# for equals result
- *
- *		Adding functions to B, C, and D is left as an
- *		exercise for the reader.
- *
- * Examples:	2 * 3 A 4 #			Ten
- *		5 * 1 0 0 A 3 #			Five Hundred Three
- *
- *---------------------------------------------------------------*/
-
-#define DO_LAST_OP \
-	switch (lastop) { \
-	  case NONE: result  = num; num = 0; break; \
-	  case ADD:  result += num; num = 0; break; \
-	  case SUB:  result -= num; num = 0; break; \
-	  case MUL:  result *= num; num = 0; break; \
-	  case DIV:  result /= num; num = 0; break; \
-	}
-
-static int calculator (char *str) 
-{
-	int result;
-	int num;
-	enum { NONE, ADD, SUB, MUL, DIV } lastop;
-	char *p;
-
-	result = 0;
-	num = 0;
-	lastop = NONE;
-
-	for (p = str; *p != '\0'; p++) {
-	  if (isdigit(*p)) {
-	    num = num * 10 + *p - '0';
-	  }
-	  else if (*p == '*') {
-	    DO_LAST_OP;
-	    lastop = MUL;
-	  }
-	  else if (*p == 'A' || *p == 'a') {
-	    DO_LAST_OP;
-	    lastop = ADD;
-	  }
-	  else if (*p == '#') {
-	    DO_LAST_OP;
-	    return (result);
-	  }
-	}
-	return (result);  // not expected.
-}
-
-
-/*------------------------------------------------------------------
- *
- * Name: 	connect_to_server
- * 
- * Purpose:	Connect to Dire Wolf TNC server.
- *
- * Inputs:	hostname
- *		port
- *
- * Returns:	File descriptor or -1 for error.
- *
- *---------------------------------------------------------------*/
-
-static int connect_to_server (char *hostname, char *port)
-{
-
-
-#if __WIN32__
-#else
-	int e;
-#endif
-
-#define MAX_HOSTS 30
-
-	struct addrinfo hints;
-	struct addrinfo *ai_head = NULL;
-	struct addrinfo *ai;
-	struct addrinfo *hosts[MAX_HOSTS];
-	int num_hosts, n;
-	int err;
-	char ipaddr_str[46];		/* text form of IP address */
-#if __WIN32__
-	WSADATA wsadata;
-#endif
-/* 
- * File descriptor for socket to server. 
- * Set to -1 if not connected. 
- * (Don't use SOCKET type because it is unsigned.) 
-*/
-	int server_sock = -1;	
-
-#if __WIN32__
-	err = WSAStartup (MAKEWORD(2,2), &wsadata);
-	if (err != 0) {
-	    printf("WSAStartup failed: %d\n", err);
-	    exit (1);
-	}
-
-	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
-          printf("Could not find a usable version of Winsock.dll\n");
-          WSACleanup();
-          exit (1);
-	}
-#endif
-
-	memset (&hints, 0, sizeof(hints));
-
-	hints.ai_family = AF_UNSPEC;	/* Allow either IPv4 or IPv6. */
-	// hints.ai_family = AF_INET;	/* IPv4 only. */
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_protocol = IPPROTO_TCP;
-
-/*
- * Connect to specified hostname & port.
- */
-
-	ai_head = NULL;
-	err = getaddrinfo(hostname, port, &hints, &ai_head);
-	if (err != 0) {
-#if __WIN32__
-	  printf ("Can't get address for server %s, err=%d\n", hostname, WSAGetLastError());
-#else 
-	  printf ("Can't get address for server %s, %s\n", hostname, gai_strerror(err));
-#endif
-	  freeaddrinfo(ai_head);
-      	  exit (1);
-	}
-
-
-	num_hosts = 0;
-	for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
-
-	  hosts[num_hosts] = ai;
-	  if (num_hosts < MAX_HOSTS) num_hosts++;
-	}
-
-	// Try each address until we find one that is successful.
-
-	for (n=0; n<num_hosts; n++) {
-	  int is;
-
-	  ai = hosts[n];
-
-	  ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
-	  is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-#if __WIN32__
-	  if (is == INVALID_SOCKET) {
-	    printf ("Socket creation failed, err=%d", WSAGetLastError());
-	    WSACleanup();
-	    is = -1;
-	    continue;
-	  }
-#else
-	  if (err != 0) {
-	    printf ("Socket creation failed, err=%s", gai_strerror(err));
-	    (void) close (is);
-	    is = -1;
-	    continue;
-	  }
-#endif
-
-	  err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
-#if __WIN32__
-	  if (err == SOCKET_ERROR) {
-	    closesocket (is);
-	    is = -1;
-	    continue;
-	  }
-#else
-	  if (err != 0) {
-	    (void) close (is);
-	    is = -1;
-	    continue;
-	  }
-	  int flag = 1;
-	  err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
-	  if (err < 0) {
-	    printf("setsockopt TCP_NODELAY failed.\n");
-	  }
-#endif
-
-/* 
- * Success. 
- */
-
- 	  printf("Client app now connected to %s (%s), port %s\n", hostname, ipaddr_str, port);
-	  server_sock = is;
-	  break;
-	}
-
-	freeaddrinfo(ai_head);
-
-	if (server_sock == -1) {
- 	  printf("Unnable to connect to %s (%s), port %s\n", hostname, ipaddr_str, port);
-	 
-	}
-
-	return (server_sock);
-
-} /* end connect_to_server */
-
-
-/*------------------------------------------------------------------
- *
- * Name: 	ia_to_text
- * 
- * Purpose:	Convert Internet address to text.
- *		Can't use InetNtop because it is supported only 
- *		on Windows Vista and later. 
- *
- *---------------------------------------------------------------*/
-
-
-static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
-{
-	struct sockaddr_in *sa4;
-	struct sockaddr_in6 *sa6;
-
-	switch (Family) {
-	  case AF_INET:
-	    sa4 = (struct sockaddr_in *)pAddr;
-#if __WIN32__
-	    sprintf (pStringBuf, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
-						sa4->sin_addr.S_un.S_un_b.s_b2,
-						sa4->sin_addr.S_un.S_un_b.s_b3,
-						sa4->sin_addr.S_un.S_un_b.s_b4);
-#else
-	    inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
-#endif
-	    break;
-	  case AF_INET6:
-	    sa6 = (struct sockaddr_in6 *)pAddr;
-#if __WIN32__
-	    sprintf (pStringBuf, "%x:%x:%x:%x:%x:%x:%x:%x",  
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
-					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
-#else
-	    inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
-#endif
-	    break;
-	  default:
-	    sprintf (pStringBuf, "Invalid address family!");
-	}
-	assert (strlen(pStringBuf) < StringBufSize);
-	return pStringBuf;
-
-} /* end ia_to_text */
-
-
-/* end ttcalc.c */
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2013, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      ttcalc.c
+ *
+ * Purpose:   	Simple Touch Tone to Speech calculator.
+ *		
+ * Description:	Demonstration of how Dire Wolf can be used
+ *		as a DTMF / Speech interface for ham radio applications.
+ *
+ * Usage:	Start up direwolf with configuration:
+ *			- DTMF decoder enabled.
+ *			- Text-to-speech enabled.
+ *			- Listening to standard port 8000 for a client application.
+ *
+ *		Run this in a different window.
+ *
+ *		User sends formulas such as:
+ *
+ *			2 * 3 * 4 #
+ *
+ *		with the touch tone pad.
+ *		The result is sent back with speech, e.g. "Twenty Four."
+ *		
+ *---------------------------------------------------------------*/
+
+
+#if __WIN32__
+
+#include <winsock2.h>
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501	/* Minimum OS version is XP. */
+#include <ws2tcpip.h>
+#else 
+#include <stdlib.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/errno.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+
+
+struct agwpe_s {	
+  short portx;			/* 0 for first, 1 for second, etc. */
+  short port_hi_reserved;	
+  short kind_lo;		/* message type */
+  short kind_hi;
+  char call_from[10];
+  char call_to[10];
+  int data_len;			/* Number of data bytes following. */
+  int user_reserved;
+};
+
+
+static int calculator (char *str);
+static int connect_to_server (char *hostname, char *port);
+static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: 	main
+ *
+ *---------------------------------------------------------------*/
+
+
+
+int main (int argc, char *argv[])
+{
+
+	int server_sock = -1;
+	struct agwpe_s mon_cmd;
+	char data[1024];
+	char hostname[30] = "localhost";
+	char port[10] = "8000";
+
+#if __WIN32__
+#else
+	int err;
+
+ 	setlinebuf (stdout);
+#endif
+
+	assert (calculator("12a34#") == 46);
+	assert (calculator("2*3A4#") == 10);
+	assert (calculator("5*100A3#") == 503);
+	assert (calculator("6a4*5#") == 50);
+
+/*
+ * Try to attach to Dire Wolf.
+ */
+
+	server_sock = connect_to_server (hostname, port);
+
+
+	if (server_sock == -1) {
+	  exit (1);
+	}
+
+/*
+ * Send command to toggle reception of frames in raw format.
+ *
+ * Note: Monitor format is only for UI frames.
+ */
+
+	memset (&mon_cmd, 0, sizeof(mon_cmd));
+
+	mon_cmd.kind_lo = 'k';
+
+#if __WIN32__	      
+	send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
+#else
+	err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
+#endif
+
+
+/*
+ * Print all of the monitored packets.
+ */
+
+	while (1) {
+	  int n;
+
+#if __WIN32__
+	  n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
+#else
+	  n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
+#endif
+
+	  if (n != sizeof(mon_cmd)) {
+	    printf ("Read error, received %d command bytes.\n", n);
+	    exit (1);
+	  }
+
+	  assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data));
+
+	  if (mon_cmd.data_len > 0) {
+#if __WIN32__
+	    n = recv (server_sock, data, mon_cmd.data_len, 0);
+#else
+	    n = read (server_sock, data, mon_cmd.data_len);
+#endif
+
+	    if (n != mon_cmd.data_len) {
+	      printf ("Read error, client received %d data bytes when %d expected.\n", n, mon_cmd.data_len);
+	      exit (1);
+	    }
+	  }
+
+/* 
+ * Print it.
+ */
+
+	  if (mon_cmd.kind_lo == 'K') {
+	    packet_t pp;
+	    char *pinfo;
+	    int info_len;
+	    char result[400];
+	    char *p;
+	    alevel_t alevel;
+	    int chan;
+
+	    chan = mon_cmd.portx;
+	    memset (&alevel, 0, sizeof(alevel));
+	    pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
+	    ax25_format_addrs (pp, result);
+	    info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
+	    pinfo[info_len] = '\0';
+	    strcat (result, pinfo);
+	    for (p=result; *p!='\0'; p++) {
+	      if (! isprint(*p)) *p = ' ';
+	    }
+
+	    printf ("[%d] %s\n", chan, result);
+
+	    	
+/*
+ * Look for Special touch tone packet with "t" in first position of the Information part.
+ */
+
+	    if (*pinfo == 't') {
+	
+	      int n;
+	      char reply_text[200];
+	      packet_t reply_pp;
+	      struct {
+	        struct agwpe_s hdr;
+		char extra;
+	        unsigned char frame[AX25_MAX_PACKET_LEN];
+	      } xmit_raw;
+
+/*
+ * Send touch tone sequence to calculator and get the answer.
+ *
+ * Put your own application here instead.  Here are some ideas:
+ *
+ *  http://www.tapr.org/pipermail/aprssig/2015-January/044069.html
+ */
+	      n = calculator (pinfo+1);
+	      printf ("\nCalculator returns %d\n\n", n);
+
+/*
+ * Convert to AX.25 frame.
+ * Notice that the special destination will cause it to be spoken.
+ */
+	      snprintf (reply_text, sizeof(reply_text), "N0CALL>SPEECH:%d", n);
+	      reply_pp = ax25_from_text(reply_text, 1);
+
+/*
+ * Send it to the TNC.
+ * In this example we are transmitting speech on the same channel
+ * where the tones were heard.  We could also send AX.25 frames to 
+ * other radio channels.
+ */
+	      memset (&xmit_raw, 0, sizeof(xmit_raw));
+
+	      xmit_raw.hdr.portx = chan;
+	      xmit_raw.hdr.kind_lo = 'K';
+	      xmit_raw.hdr.data_len = 1 + ax25_pack (reply_pp, xmit_raw.frame);
+
+#if __WIN32__	      
+	      send (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len, 0);
+#else
+	      err = write (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len);
+#endif
+	      ax25_delete (reply_pp);
+	    }
+
+    
+	    ax25_delete (pp);
+
+	  }
+	}
+
+} /* main */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: 	calculator
+ * 
+ * Purpose:	Simple calculator to demonstrate Touch Tone to Speech 
+ *		application tool kit.
+ *
+ * Inputs:	str	- Sequence of touch tone characters: 0-9 A-D * #
+ *			  It should be terminated with #.
+ *
+ * Returns:	Numeric result of calculation.
+ *
+ * Description:	This is a simple calculator that recognizes 
+ *			numbers,
+ *			* for multiply 
+ *			A for add
+ *			# for equals result
+ *
+ *		Adding functions to B, C, and D is left as an
+ *		exercise for the reader.
+ *
+ * Examples:	2 * 3 A 4 #			Ten
+ *		5 * 1 0 0 A 3 #			Five Hundred Three
+ *
+ *---------------------------------------------------------------*/
+
+#define DO_LAST_OP \
+	switch (lastop) { \
+	  case NONE: result  = num; num = 0; break; \
+	  case ADD:  result += num; num = 0; break; \
+	  case SUB:  result -= num; num = 0; break; \
+	  case MUL:  result *= num; num = 0; break; \
+	  case DIV:  result /= num; num = 0; break; \
+	}
+
+static int calculator (char *str) 
+{
+	int result;
+	int num;
+	enum { NONE, ADD, SUB, MUL, DIV } lastop;
+	char *p;
+
+	result = 0;
+	num = 0;
+	lastop = NONE;
+
+	for (p = str; *p != '\0'; p++) {
+	  if (isdigit(*p)) {
+	    num = num * 10 + *p - '0';
+	  }
+	  else if (*p == '*') {
+	    DO_LAST_OP;
+	    lastop = MUL;
+	  }
+	  else if (*p == 'A' || *p == 'a') {
+	    DO_LAST_OP;
+	    lastop = ADD;
+	  }
+	  else if (*p == '#') {
+	    DO_LAST_OP;
+	    return (result);
+	  }
+	}
+	return (result);  // not expected.
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: 	connect_to_server
+ * 
+ * Purpose:	Connect to Dire Wolf TNC server.
+ *
+ * Inputs:	hostname
+ *		port
+ *
+ * Returns:	File descriptor or -1 for error.
+ *
+ *---------------------------------------------------------------*/
+
+static int connect_to_server (char *hostname, char *port)
+{
+
+
+#if __WIN32__
+#else
+	int e;
+#endif
+
+#define MAX_HOSTS 30
+
+	struct addrinfo hints;
+	struct addrinfo *ai_head = NULL;
+	struct addrinfo *ai;
+	struct addrinfo *hosts[MAX_HOSTS];
+	int num_hosts, n;
+	int err;
+	char ipaddr_str[46];		/* text form of IP address */
+#if __WIN32__
+	WSADATA wsadata;
+#endif
+/* 
+ * File descriptor for socket to server. 
+ * Set to -1 if not connected. 
+ * (Don't use SOCKET type because it is unsigned.) 
+*/
+	int server_sock = -1;	
+
+#if __WIN32__
+	err = WSAStartup (MAKEWORD(2,2), &wsadata);
+	if (err != 0) {
+	    printf("WSAStartup failed: %d\n", err);
+	    exit (1);
+	}
+
+	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+          printf("Could not find a usable version of Winsock.dll\n");
+          WSACleanup();
+          exit (1);
+	}
+#endif
+
+	memset (&hints, 0, sizeof(hints));
+
+	hints.ai_family = AF_UNSPEC;	/* Allow either IPv4 or IPv6. */
+	// hints.ai_family = AF_INET;	/* IPv4 only. */
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+/*
+ * Connect to specified hostname & port.
+ */
+
+	ai_head = NULL;
+	err = getaddrinfo(hostname, port, &hints, &ai_head);
+	if (err != 0) {
+#if __WIN32__
+	  printf ("Can't get address for server %s, err=%d\n", hostname, WSAGetLastError());
+#else 
+	  printf ("Can't get address for server %s, %s\n", hostname, gai_strerror(err));
+#endif
+	  freeaddrinfo(ai_head);
+      	  exit (1);
+	}
+
+
+	num_hosts = 0;
+	for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
+
+	  hosts[num_hosts] = ai;
+	  if (num_hosts < MAX_HOSTS) num_hosts++;
+	}
+
+	// Try each address until we find one that is successful.
+
+	for (n=0; n<num_hosts; n++) {
+	  int is;
+
+	  ai = hosts[n];
+
+	  ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+	  is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+#if __WIN32__
+	  if (is == INVALID_SOCKET) {
+	    printf ("Socket creation failed, err=%d", WSAGetLastError());
+	    WSACleanup();
+	    is = -1;
+	    continue;
+	  }
+#else
+	  if (err != 0) {
+	    printf ("Socket creation failed, err=%s", gai_strerror(err));
+	    (void) close (is);
+	    is = -1;
+	    continue;
+	  }
+#endif
+
+	  err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
+#if __WIN32__
+	  if (err == SOCKET_ERROR) {
+	    closesocket (is);
+	    is = -1;
+	    continue;
+	  }
+#else
+	  if (err != 0) {
+	    (void) close (is);
+	    is = -1;
+	    continue;
+	  }
+	  int flag = 1;
+	  err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
+	  if (err < 0) {
+	    printf("setsockopt TCP_NODELAY failed.\n");
+	  }
+#endif
+
+/* 
+ * Success. 
+ */
+
+ 	  printf("Client app now connected to %s (%s), port %s\n", hostname, ipaddr_str, port);
+	  server_sock = is;
+	  break;
+	}
+
+	freeaddrinfo(ai_head);
+
+	if (server_sock == -1) {
+ 	  printf("Unnable to connect to %s (%s), port %s\n", hostname, ipaddr_str, port);
+	 
+	}
+
+	return (server_sock);
+
+} /* end connect_to_server */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: 	ia_to_text
+ * 
+ * Purpose:	Convert Internet address to text.
+ *		Can't use InetNtop because it is supported only 
+ *		on Windows Vista and later. 
+ *
+ *---------------------------------------------------------------*/
+
+
+static char * ia_to_text (int  Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
+{
+	struct sockaddr_in *sa4;
+	struct sockaddr_in6 *sa6;
+
+	switch (Family) {
+	  case AF_INET:
+	    sa4 = (struct sockaddr_in *)pAddr;
+#if __WIN32__
+	    snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
+						sa4->sin_addr.S_un.S_un_b.s_b2,
+						sa4->sin_addr.S_un.S_un_b.s_b3,
+						sa4->sin_addr.S_un.S_un_b.s_b4);
+#else
+	    inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
+#endif
+	    break;
+	  case AF_INET6:
+	    sa6 = (struct sockaddr_in6 *)pAddr;
+#if __WIN32__
+	    snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
+					ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
+#else
+	    inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
+#endif
+	    break;
+	  default:
+	    snprintf (pStringBuf, StringBufSize, "Invalid address family!");
+	}
+
+	return pStringBuf;
+
+} /* end ia_to_text */
+
+
+/* end ttcalc.c */
diff --git a/utm2ll.c b/utm2ll.c
index 818f831..e1d793c 100644
--- a/utm2ll.c
+++ b/utm2ll.c
@@ -1,145 +1,145 @@
-/* UTM to Latitude / Longitude conversion */
-
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <string.h>
-#include <ctype.h>
-
-
-#include "utm.h"
-#include "mgrs.h"
-#include "usng.h"
-#include "error_string.h"
-
-
-#define D2R(d) ((d) * M_PI / 180.)
-#define R2D(r) ((r) * 180. / M_PI)
-
-
-
-
-static void usage();
-
-
-int main (int argc, char *argv[]) 
-{
-	double easting;
-	double northing;
-	double lat, lon;
-	char szone[100];
-	long lzone;
-	char *zlet;
-	char hemi;
-	long err;
-	char message[300];
-
-
-	if (argc == 4) {
-
-// 3 command line arguments for UTM
-
-	  strcpy (szone, argv[1]);
-	  lzone = strtoul(szone, &zlet, 10);
-
-	  if (*zlet == '\0') {
-	    hemi = 'N';
-	  }
-	  else {
-	  
-	    if (islower(*zlet)) {
-	      *zlet = toupper(*zlet);
-	    }
-	    if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) {
-	      fprintf (stderr, "Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n");
-	      usage();
-	    }
-	    if (*zlet >= 'N') {
-	      hemi = 'N';
-	    }
-	    else {
-	      hemi = 'S';
-	    }
-	  }
-  
-	  easting = atof(argv[2]);
-
-	  northing = atof(argv[3]);
-
-	  err = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &lat, &lon);
-	  if (err == 0) {
-	    lat = R2D(lat);
-	    lon = R2D(lon);
-
-	    printf ("from UTM, latitude = %.6f, longitude = %.6f\n", lat, lon);
-	  }
-	  else {
-
-	    utm_error_string (err, message);
-	    fprintf (stderr, "Conversion from UTM failed:\n%s\n\n", message);
-
-	  }
-	}
-	else if (argc == 2) {
-
-// One command line argument, USNG or MGRS.
-
-// TODO: continue here.
-
-
-	  err = Convert_USNG_To_Geodetic (argv[1], &lat, &lon);
- 	  if (err == 0) {
-	    lat = R2D(lat);
-	    lon = R2D(lon);
-	    printf ("from USNG, latitude = %.6f, longitude = %.6f\n", lat, lon);
-	  }
-	  else {
-	    usng_error_string (err, message);
-	    fprintf (stderr, "Conversion from USNG failed:\n%s\n\n", message);
-	  }
-
-	  err = Convert_MGRS_To_Geodetic (argv[1], &lat, &lon);
- 	  if (err == 0) {
-	    lat = R2D(lat);
-	    lon = R2D(lon);
-	    printf ("from MGRS, latitude = %.6f, longitude = %.6f\n", lat, lon);
-	  }
-	  else {
-	    mgrs_error_string (err, message);
-	    fprintf (stderr, "Conversion from MGRS failed:\n%s\n\n", message);
-	  }
-
-	}
-	else {
-	  usage();
-	}
-
-	exit (0);
-}
-
-
-static void usage (void)
-{
-	fprintf (stderr, "UTM to Latitude / Longitude conversion\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "Usage:\n");
-	fprintf (stderr, "\tutm2ll  zone  easting  northing\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "where,\n");
-	fprintf (stderr, "\tzone is UTM zone 1 thru 60 with optional latitudinal band.\n");
-	fprintf (stderr, "\teasting is x coordinate in meters\n");
-	fprintf (stderr, "\tnorthing is y coordinate in meters\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "or:\n");
-	fprintf (stderr, "\tutm2ll  x\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "where,\n");
-	fprintf (stderr, "\tx is USNG or MGRS location.\n");
-	fprintf (stderr, "\n");
-	fprintf (stderr, "Examples:\n");
-	fprintf (stderr, "\tutm2ll 19T 306130 4726010\n");
-	fprintf (stderr, "\tutm2ll 19TCH06132600\n");
-
-	exit (1);
+/* UTM to Latitude / Longitude conversion */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "direwolf.h"
+#include "utm.h"
+#include "mgrs.h"
+#include "usng.h"
+#include "error_string.h"
+
+
+#define D2R(d) ((d) * M_PI / 180.)
+#define R2D(r) ((r) * 180. / M_PI)
+
+
+
+
+static void usage();
+
+
+int main (int argc, char *argv[]) 
+{
+	double easting;
+	double northing;
+	double lat, lon;
+	char szone[100];
+	long lzone;
+	char *zlet;
+	char hemi;
+	long err;
+	char message[300];
+
+
+	if (argc == 4) {
+
+// 3 command line arguments for UTM
+
+	  strlcpy (szone, argv[1], sizeof(szone));
+	  lzone = strtoul(szone, &zlet, 10);
+
+	  if (*zlet == '\0') {
+	    hemi = 'N';
+	  }
+	  else {
+	  
+	    if (islower(*zlet)) {
+	      *zlet = toupper(*zlet);
+	    }
+	    if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) {
+	      fprintf (stderr, "Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n");
+	      usage();
+	    }
+	    if (*zlet >= 'N') {
+	      hemi = 'N';
+	    }
+	    else {
+	      hemi = 'S';
+	    }
+	  }
+  
+	  easting = atof(argv[2]);
+
+	  northing = atof(argv[3]);
+
+	  err = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &lat, &lon);
+	  if (err == 0) {
+	    lat = R2D(lat);
+	    lon = R2D(lon);
+
+	    printf ("from UTM, latitude = %.6f, longitude = %.6f\n", lat, lon);
+	  }
+	  else {
+
+	    utm_error_string (err, message);
+	    fprintf (stderr, "Conversion from UTM failed:\n%s\n\n", message);
+
+	  }
+	}
+	else if (argc == 2) {
+
+// One command line argument, USNG or MGRS.
+
+// TODO: continue here.
+
+
+	  err = Convert_USNG_To_Geodetic (argv[1], &lat, &lon);
+ 	  if (err == 0) {
+	    lat = R2D(lat);
+	    lon = R2D(lon);
+	    printf ("from USNG, latitude = %.6f, longitude = %.6f\n", lat, lon);
+	  }
+	  else {
+	    usng_error_string (err, message);
+	    fprintf (stderr, "Conversion from USNG failed:\n%s\n\n", message);
+	  }
+
+	  err = Convert_MGRS_To_Geodetic (argv[1], &lat, &lon);
+ 	  if (err == 0) {
+	    lat = R2D(lat);
+	    lon = R2D(lon);
+	    printf ("from MGRS, latitude = %.6f, longitude = %.6f\n", lat, lon);
+	  }
+	  else {
+	    mgrs_error_string (err, message);
+	    fprintf (stderr, "Conversion from MGRS failed:\n%s\n\n", message);
+	  }
+
+	}
+	else {
+	  usage();
+	}
+
+	exit (0);
+}
+
+
+static void usage (void)
+{
+	fprintf (stderr, "UTM to Latitude / Longitude conversion\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "Usage:\n");
+	fprintf (stderr, "\tutm2ll  zone  easting  northing\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "where,\n");
+	fprintf (stderr, "\tzone is UTM zone 1 thru 60 with optional latitudinal band.\n");
+	fprintf (stderr, "\teasting is x coordinate in meters\n");
+	fprintf (stderr, "\tnorthing is y coordinate in meters\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "or:\n");
+	fprintf (stderr, "\tutm2ll  x\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "where,\n");
+	fprintf (stderr, "\tx is USNG or MGRS location.\n");
+	fprintf (stderr, "\n");
+	fprintf (stderr, "Examples:\n");
+	fprintf (stderr, "\tutm2ll 19T 306130 4726010\n");
+	fprintf (stderr, "\tutm2ll 19TCH06132600\n");
+
+	exit (1);
 }
\ No newline at end of file
diff --git a/version.h b/version.h
index 30efc3c..97b5594 100644
--- a/version.h
+++ b/version.h
@@ -1,8 +1,8 @@
-
-/* Dire Wolf version 1.2 */
-
-#define APP_TOCALL "APDW"
-
-#define MAJOR_VERSION 1
-#define MINOR_VERSION 2
-#define EXTRA_VERSION "Beta Test"
+
+/* Dire Wolf version 1.3 */
+
+#define APP_TOCALL "APDW"
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 3
+//#define EXTRA_VERSION "Beta Test"
diff --git a/walk96.c b/walk96.c
new file mode 100644
index 0000000..ef1c34c
--- /dev/null
+++ b/walk96.c
@@ -0,0 +1,205 @@
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2015  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:      walk96.c
+ *
+ * Purpose:   	Quick hack to read GPS location and send very frequent 
+ *		position reports frames to a KISS TNC.
+ *
+ *		
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+#include "direwolf.h"
+#include "config.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "latlong.h"
+#include "dwgps.h"
+#include "encode_aprs.h"
+#include "serial_port.h"
+#include "kiss_frame.h"
+
+
+#define MYCALL "WB2OSZ"			/************ Change this if you use it!!!  ***************/
+
+#define HOWLONG 20			/* Run for 20 seconds then quit. */
+
+
+
+static MYFDTYPE tnc;
+
+static void walk96 (int fix, double lat, double lon, float knots, float course, float alt);
+
+
+
+int main (int argc, char *argv[])
+{
+	struct misc_config_s config;
+	char cmd[100];
+	int debug_gps = 0;
+	int n;
+
+
+	// TD-D72A USB - Look for Silicon Labs CP210x.
+	// Just happens to be same on desktop & laptop.
+
+	tnc = serial_port_open ("COM5", 9600);	
+	if (tnc == MYFDERROR) {
+	  text_color_set (DW_COLOR_ERROR);
+          dw_printf ("Can't open serial port to KISS TNC.\n");
+	  exit (EXIT_FAILURE);	// defined in stdlib.h
+  	}
+
+	strlcpy (cmd, "\r\rhbaud 9600\rkiss on\rrestart\r", sizeof(cmd));
+	serial_port_write (tnc, cmd, strlen(cmd));
+
+	
+	// USB GPS happens to be COM22
+
+	memset (&config, 0, sizeof(config));
+	strlcpy (config.gpsnmea_port, "COM22", sizeof(config.nmea_port));
+
+	dwgps_init (&config, debug_gps);
+
+	SLEEP_SEC(1);				/* Wait for sample before reading. */
+
+	for (n=0; n<HOWLONG; n++) {
+
+	  dwgps_info_t info;
+	  dwfix_t fix;
+
+	  fix = dwgps_read (&info);
+
+	  if (fix > DWFIX_2D) {
+	    walk96 (fix, info.dlat, info.dlon, info.speed_knots, info.track, info.altitude);
+	  }
+	  else if (fix < 0) {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf ("Can't communicate with GPS receiver.\n");
+	    exit (EXIT_FAILURE);
+	  }
+	  else  {
+	    text_color_set (DW_COLOR_ERROR);
+            dw_printf ("GPS fix not available.\n");
+	  }
+	  SLEEP_SEC(1);
+	}
+
+
+	// Exit out of KISS mode.
+
+	serial_port_write (tnc, "\xc0\xff\xc0", 3);
+
+	SLEEP_MS(100);
+	exit (EXIT_SUCCESS);
+}
+
+
+/* Should be called once per second. */
+
+static void walk96 (int fix, double lat, double lon, float knots, float course, float alt)
+{
+	static int sequence = 0;
+	char comment[50];
+
+	sequence++;
+	snprintf (comment, sizeof(comment), "Sequence number %04d", sequence);
+
+
+/*
+ * Construct the packet in normal monitoring format.
+ */
+
+	int messaging = 0;
+	int compressed = 0;
+
+	char info[AX25_MAX_INFO_LEN];
+	int info_len;
+
+	char position_report[AX25_MAX_PACKET_LEN];
+
+
+// TODO (high, bug):    Why do we see !4237.13N/07120.84W=PHG0000...   when all values set to unknown.
+
+
+	info_len = encode_position (messaging, compressed,
+		lat, lon, (int)(DW_METERS_TO_FEET(alt)), 
+		'/', '=',
+		G_UNKNOWN, G_UNKNOWN, G_UNKNOWN, "",	// PHGd
+		(int)roundf(course), (int)roundf(knots),
+		445.925, 0, 0,
+		comment,
+		info, sizeof(info));
+
+	snprintf (position_report, sizeof(position_report), "%s>WALK96:%s", MYCALL, info);
+
+	text_color_set (DW_COLOR_XMIT);
+        dw_printf ("%s\n", position_report);
+
+/*
+ * Convert it into AX.25 frame.
+ */
+	packet_t pp;
+	unsigned char ax25_frame[AX25_MAX_PACKET_LEN];
+	int frame_len;
+
+	pp = ax25_from_text (position_report, 1);
+
+	if (pp == NULL) {
+	  text_color_set (DW_COLOR_ERROR);
+          dw_printf ("Unexpected error in ax25_from_text.  Quitting.\n");
+	  exit (EXIT_FAILURE);	// defined in stdlib.h
+	}
+
+	ax25_frame[0] = 0;	// Insert channel before KISS encapsulation. 
+
+	frame_len = ax25_pack (pp, ax25_frame+1);
+	ax25_delete (pp);
+
+/*
+ * Encapsulate as KISS and send to TNC.
+ */
+	
+	unsigned char kiss_frame[AX25_MAX_PACKET_LEN*2];
+	int kiss_len;
+
+	kiss_len = kiss_encapsulate (ax25_frame, frame_len+1, (unsigned char *)kiss_frame);
+
+	//text_color_set (DW_COLOR_DEBUG);
+        //dw_printf ("AX.25 frame length = %d, KISS frame length = %d\n", frame_len, kiss_len);
+
+	//kiss_debug_print (1, NULL, kiss_frame, kiss_len);
+
+	serial_port_write (tnc, kiss_frame, kiss_len);
+
+}
+
+/* end walk96.c */
diff --git a/xid.c b/xid.c
index 8811971..7580c94 100644
--- a/xid.c
+++ b/xid.c
@@ -1,509 +1,509 @@
-
-//
-//    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/>.
-
-
-
-/*------------------------------------------------------------------
- *
- * Module:      xid.c
- *
- * Purpose:   	....
- *		
- * Description:	
- *
- * References:	...
- *
- *		
- *
- *---------------------------------------------------------------*/
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include "textcolor.h"
-//#include "xid.h"
-
-
-struct ax25_param_s {
-
-	int full_duplex;
-	
-	// Order is important because negotiation keeps the lower.  
-	enum rej_e {implicit_reject, selective_reject, selective_reject_reject } rej;
-
-	enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo;
-
-	int i_field_length_rx;	/* In bytes.  XID has it in bits. */
-
-	int window_size;	
-
-	int ack_timer;		/* "T1" in mSec. */
-
-	int retries;		/* Inconsistently refered to as "N1" or "N2" */
-};
-
-
-
-#define FI_Format_Indicator	0x82	
-#define GI_Group_Identifier	0x80
-
-#define PI_Classes_of_Procedures	2	
-#define PI_HDLC_Optional_Functions	3	
-#define PI_I_Field_Length_Rx		6	
-#define PI_Window_Size_Rx		8	
-#define PI_Ack_Timer			9	
-#define PI_Retries			10	
-
-#define PV_Classes_Procedures_Balanced_ABM	0x0100
-#define PV_Classes_Procedures_Half_Duplex	0x2000
-#define PV_Classes_Procedures_Full_Duplex	0x4000
-
-#define PV_HDLC_Optional_Functions_REJ_cmd_resp		0x020000
-#define PV_HDLC_Optional_Functions_SREJ_cmd_resp	0x040000
-#define PV_HDLC_Optional_Functions_Extended_Address 	0x800000
-
-#define PV_HDLC_Optional_Functions_Modulo_8		0x000400
-#define PV_HDLC_Optional_Functions_Modulo_128		0x000800
-#define PV_HDLC_Optional_Functions_TEST_cmd_resp	0x002000
-#define PV_HDLC_Optional_Functions_16_bit_FCS		0x008000
-
-#define PV_HDLC_Optional_Functions_Synchronous_Tx	0x000002
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        ...
- *
- * Purpose:    	...
- *
- * Inputs:	...
- *
- * Outputs:	...
- *
- * Description:	
- *
- *--------------------------------------------------------------------*/
-
-
-//Returns:	1 for mostly successful (with possible error messages), 0 for failure.
-
-
-int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
-{
-	unsigned char *p;
-	int group_len;
-
-
-	result->full_duplex = 0;
-	result->rej = selective_reject;
-	result->modulo = modulo_8;
-	result->i_field_length_rx = 256;
-	result->window_size = result->modulo == modulo_128 ? 32 : 4;
-	result->ack_timer = 3000;
-	result->retries = 10;
-
-	p = info;
-
-	if (*p != FI_Format_Indicator) {
-	  return 0;
-	}
-	p++;
-
-	if (*p != GI_Group_Identifier) {
-	  return 0;
-	}
-	p++;
-
-	group_len = *p++;
-	group_len = (group_len << 8) + *p++;
-	
-	while (p < info + 4 + group_len) {
-
-	  int pind, plen, pval, j;
-
-	  pind = *p++;
-	  plen = *p++;		// should have sanity checking
-	  pval = 0;
-	  for (j=0; j<plen; j++) {
-	    pval = (pval << 8) + *p++;
-	  }
-
-	  switch (pind) {
-
-	    case PI_Classes_of_Procedures:
-	      
-	      if ( ! (pval & PV_Classes_Procedures_Balanced_ABM)) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected Balanced ABM to be set.\n");	
-	      }
-
-	      if (pval & PV_Classes_Procedures_Half_Duplex && ! (pval & PV_Classes_Procedures_Full_Duplex)) {
-	        result->full_duplex = 0;
-	      }
-	      else if (pval & PV_Classes_Procedures_Full_Duplex && ! (pval & PV_Classes_Procedures_Half_Duplex)) {
-	        result->full_duplex = 1;
-	      }
-	      else {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected one of Half or Full Duplex be set.\n");	
-	      }
-
-	      break;
-
-	    case PI_HDLC_Optional_Functions:	
-
-	      if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
-	        result->rej = selective_reject_reject;		/* Both bits set */
-	      }
-	      else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
-	        result->rej = implicit_reject;			/* Only REJ is set */
-	      }
-	      else if ( ! (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
-	        result->rej = selective_reject;			/* Only SREJ is set */
-	      }
-	      else {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected one or both of REJ, SREJ to be set.\n");	
-	      }
-
-	      if (pval & PV_HDLC_Optional_Functions_Modulo_8 && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
-	        result->modulo = modulo_8;
-	      }
-	      else if (pval & PV_HDLC_Optional_Functions_Modulo_128 && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) {
-	        result->modulo = modulo_128;
-	      }
-	      else {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected one of Modulo 8 or 128 be set.\n");	
-	      }
-
-	      if ( ! (pval & PV_HDLC_Optional_Functions_Extended_Address)) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected Extended Address to be set.\n");	
-	      }
-
-	      if ( ! (pval & PV_HDLC_Optional_Functions_TEST_cmd_resp)) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected TEST cmd/resp to be set.\n");	
-	      }
-
-	      if ( ! (pval & PV_HDLC_Optional_Functions_16_bit_FCS)) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected 16 bit FCS to be set.\n");	
-	      }
-
-	      if ( ! (pval & PV_HDLC_Optional_Functions_Synchronous_Tx)) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: Expected Synchronous Tx to be set.\n");	
-	      }
-
-	      break;
-
-	    case PI_I_Field_Length_Rx:	
-	      
-	      result->i_field_length_rx = pval / 8;
-
-	      if (pval & 0x7) {
-	        text_color_set (DW_COLOR_ERROR);
-	        dw_printf ("XID error: I Field Length Rx is not a whole number of bytes.\n");	
-	      }
-
-	      break;
-
-	    case PI_Window_Size_Rx:	
-
-	      result->window_size = pval;
-
-// TODO must be 1-7 for modulo 8 or 1-127 for modulo 128;
-
-//	      if (pval & 0x7) {
-//	        text_color_set (DW_COLOR_ERROR);
-//	        dw_printf ("XID error: Window Size Rx is not in range of 1 thru ???\n");	
-//	      }
-
-//continue here.
-
-	      break;
-
-	    case PI_Ack_Timer:	
-	      result->ack_timer = pval;
-	      break;
-
-	    case PI_Retries:
-	      result->retries = pval;
-	      break;
-
-	    default:		
-	      break;
-	  }
-	}
-
-	if (p != info + info_len) {
-	  text_color_set (DW_COLOR_ERROR);
-	  dw_printf ("XID error: Frame / Group Length mismatch.\n");	
-	}
-
-	return 1; 
-
-} /* end xid_parse */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        xid_encode
- *
- * Purpose:    	...
- *
- * Inputs:	param	- 
- *
- * Outputs:	info	- Information part of XID frame.
- *			  Does not include the control byte.
- *
- * Returns:	Number of bytes in the info part.
- *
- * Description:	
- *
- *--------------------------------------------------------------------*/
-
-
-int xid_encode (struct ax25_param_s *param, unsigned char *info)
-{
-	unsigned char *p;
-	int len;
-	int x;
-
-	p = info;
-
-	*p++ = FI_Format_Indicator;
-	*p++ = GI_Group_Identifier;
-	*p++ = 0;
-	*p++ = 0x17;
-
-	*p++ = PI_Classes_of_Procedures;
-	*p++ = 2;
-	
-	x = PV_Classes_Procedures_Balanced_ABM;
-
-	if (param->full_duplex)
-	  x |= PV_Classes_Procedures_Full_Duplex;
-	else
-	  x |= PV_Classes_Procedures_Half_Duplex;
-
-	*p++ = (x >> 8) & 0xff;
-	*p++ = x & 0xff;
-
-	*p++ = PI_HDLC_Optional_Functions;
-	*p++ = 3;
-
-	x = PV_HDLC_Optional_Functions_Extended_Address |
-		PV_HDLC_Optional_Functions_TEST_cmd_resp |
-		PV_HDLC_Optional_Functions_16_bit_FCS | 
-		PV_HDLC_Optional_Functions_Synchronous_Tx;
-
-	if (param->rej == implicit_reject || param->rej == selective_reject_reject)
-	  x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
-
-	if (param->rej == selective_reject || param->rej == selective_reject_reject)	
-	  x |= PV_HDLC_Optional_Functions_SREJ_cmd_resp;
-
-	if (param->modulo == modulo_128)
-	  x |= PV_HDLC_Optional_Functions_Modulo_128;
-	else
-	  x |= PV_HDLC_Optional_Functions_Modulo_8;
-
-	*p++ = (x >> 16) & 0xff;
-	*p++ = (x >> 8) & 0xff;
-	*p++ = x & 0xff;
-
-	*p++ = PI_I_Field_Length_Rx;
-	*p++ = 2;
-	x = param->i_field_length_rx * 8;
-	*p++ = (x >> 8) & 0xff;
-	*p++ = x & 0xff;
-
-	*p++ = PI_Window_Size_Rx;
-	*p++ = 1;
-	*p++ = param->window_size;
-
-	*p++ = PI_Ack_Timer;
-	*p++ = 2;
-	*p++ = param->ack_timer >> 8;
-	*p++ = param->ack_timer & 0xff;
-
-	*p++ = PI_Retries;
-	*p++ = 1;
-	*p++ = param->retries;
-
-	len = p - info;
-	assert (len == 27);
-	return (len);
-
-} /* end xid_encode */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        main
- *
- * Purpose:    	Unit test for other functions here.
- *
- * Description:	Run with:
- *
- *			gcc -DTEST -g xid.c textcolor.c ; ./a
- *
- *		Result should be:
- *
- *			XID test:  Success.
- *
- *		with no error messages.
- *
- *--------------------------------------------------------------------*/
-
-
-#if TEST
-
-/* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */
-/* This is the info part after a control byte of 0xAF. */
-
-static unsigned char example[27] = {
-
-	/* FI */	0x82,	/* Format indicator */
-	/* GI */	0x80,	/* Group Identifier - parameter negotiation */
-	/* GL */	0x00,	/* Group length - all of the PI/PL/PV fields */
-	/* GL */	0x17,	/* (2 bytes) */
-	/* PI */	0x02,	/* Parameter Indicator - classes of procedures */
-	/* PL */	0x02,	/* Parameter Length */
-#if 0 // Example in the protocol spec looks wrong.
-	/* PV */	0x00,	/* Parameter Variable - Half Duplex, Async, Balanced Mode */
-	/* PV */	0x20,	/*  */
-#else  // I think it should be like this instead.
-	/* PV */	0x21,	/* Parameter Variable - Half Duplex, Async, Balanced Mode */
-	/* PV */	0x00,	/* Reserved */
-#endif
-	/* PI */	0x03,	/* Parameter Indicator - optional functions */
-	/* PL */	0x03,	/* Parameter Length */
-	/* PV */	0x86,	/* Parameter Variable - SREJ/REJ, extended addr */ 
-	/* PV */	0xA8,	/* 16-bit FCS, TEST cmd/resp, Modulo 128 */
-	/* PV */	0x02,	/* synchronous transmit */
-	/* PI */	0x06,	/* Parameter Indicator - Rx I field length (bits) */
-	/* PL */	0x02,	/* Parameter Length */
-	/* PV */	0x04,	/* Parameter Variable - 1024 bits (128 octets) */
-	/* PV */	0x00,	/* */
-	/* PI */	0x08,	/* Parameter Indicator - Rx window size */
-	/* PL */	0x01,	/* Parameter length */
-	/* PV */	0x02,	/* Parameter Variable - 2 frames */
-	/* PI */	0x09,	/* Parameter Indicator - Timer T1 */
-	/* PL */	0x02,	/* Parameter Length */
-	/* PV */	0x10,	/* Parameter Variable - 4096 MSec */
-	/* PV */	0x00,	/* */
-	/* PI */	0x0A,	/* Parameter Indicator - Retries (N1) */
-	/* PL */	0x01,	/* Parameter Length */
-	/* PV */	0x03 	/* Parameter Variable - 3 ret */
-};
-
-int main (int argc, char *argv[]) {
-
-	struct ax25_param_s param;
-	struct ax25_param_s param2;
-	int n;
-	unsigned char info[40];
-
-/* parse example. */
-
-	n = xid_parse (example, sizeof(example), &param);
-
-	text_color_set (DW_COLOR_ERROR);
-
-	assert (n==1);
-	assert (param.full_duplex == 0);
-	assert (param.rej == selective_reject_reject);
-	assert (param.modulo == modulo_128);
-	assert (param.i_field_length_rx == 128);	
-	assert (param.window_size == 2);	
-	assert (param.ack_timer == 4096);	
-	assert (param.retries == 3);	
-
-/* encode and verify it comes out the same. */
-
-	n = xid_encode (&param, info);
-
-	assert (n == sizeof(example));
-	n = memcmp(info, example, 27);
-
-	//for (n=0; n<27; n++) {
-	//  dw_printf ("%2d  %02x  %02x\n", n, example[n], info[n]);
-	//}
-
-	assert (n == 0);
-	
-/* try a couple different values. */
-
-	param.full_duplex = 1;
-	param.rej = implicit_reject;
-	param.modulo = modulo_8;
-	param.i_field_length_rx = 2048;
-	param.window_size = 3;
-	param.ack_timer = 3000;
-	param.retries = 10;
-
-	n = xid_encode (&param, info);
-	n = xid_parse (info, n, &param2);
-
-	assert (param2.full_duplex == 1);
-	assert (param2.rej == implicit_reject);
-	assert (param2.modulo == modulo_8);
-	assert (param2.i_field_length_rx == 2048);
-	assert (param2.window_size == 3);
-	assert (param2.ack_timer == 3000);
-	assert (param2.retries == 10);
-
-/* Finally the third possbility for rej. */
-
-	param.full_duplex = 0;
-	param.rej = selective_reject;
-	param.modulo = modulo_8;
-	param.i_field_length_rx = 256;
-	param.window_size = 4;
-	param.ack_timer = 3000;
-	param.retries = 10;
-
-	n = xid_encode (&param, info);
-	n = xid_parse (info, n, &param2);
-
-	assert (param2.full_duplex == 0);
-	assert (param2.rej == selective_reject);
-	assert (param2.modulo == modulo_8);
-	assert (param2.i_field_length_rx == 256);
-	assert (param2.window_size == 4);
-	assert (param2.ack_timer == 3000);
-	assert (param2.retries == 10);
-
-	text_color_set (DW_COLOR_INFO);
-	dw_printf ("XID test:  Success.\n");	
-	
-	exit (0);
-	
-}
-
-#endif
-
-/* end xid.c */
+
+//
+//    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/>.
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      xid.c
+ *
+ * Purpose:   	....
+ *		
+ * Description:	
+ *
+ * References:	...
+ *
+ *		
+ *
+ *---------------------------------------------------------------*/
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "textcolor.h"
+//#include "xid.h"
+
+
+struct ax25_param_s {
+
+	int full_duplex;
+	
+	// Order is important because negotiation keeps the lower.  
+	enum rej_e {implicit_reject, selective_reject, selective_reject_reject } rej;
+
+	enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo;
+
+	int i_field_length_rx;	/* In bytes.  XID has it in bits. */
+
+	int window_size;	
+
+	int ack_timer;		/* "T1" in mSec. */
+
+	int retries;		/* Inconsistently refered to as "N1" or "N2" */
+};
+
+
+
+#define FI_Format_Indicator	0x82	
+#define GI_Group_Identifier	0x80
+
+#define PI_Classes_of_Procedures	2	
+#define PI_HDLC_Optional_Functions	3	
+#define PI_I_Field_Length_Rx		6	
+#define PI_Window_Size_Rx		8	
+#define PI_Ack_Timer			9	
+#define PI_Retries			10	
+
+#define PV_Classes_Procedures_Balanced_ABM	0x0100
+#define PV_Classes_Procedures_Half_Duplex	0x2000
+#define PV_Classes_Procedures_Full_Duplex	0x4000
+
+#define PV_HDLC_Optional_Functions_REJ_cmd_resp		0x020000
+#define PV_HDLC_Optional_Functions_SREJ_cmd_resp	0x040000
+#define PV_HDLC_Optional_Functions_Extended_Address 	0x800000
+
+#define PV_HDLC_Optional_Functions_Modulo_8		0x000400
+#define PV_HDLC_Optional_Functions_Modulo_128		0x000800
+#define PV_HDLC_Optional_Functions_TEST_cmd_resp	0x002000
+#define PV_HDLC_Optional_Functions_16_bit_FCS		0x008000
+
+#define PV_HDLC_Optional_Functions_Synchronous_Tx	0x000002
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        ...
+ *
+ * Purpose:    	...
+ *
+ * Inputs:	...
+ *
+ * Outputs:	...
+ *
+ * Description:	
+ *
+ *--------------------------------------------------------------------*/
+
+
+//Returns:	1 for mostly successful (with possible error messages), 0 for failure.
+
+
+int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
+{
+	unsigned char *p;
+	int group_len;
+
+
+	result->full_duplex = 0;
+	result->rej = selective_reject;
+	result->modulo = modulo_8;
+	result->i_field_length_rx = 256;
+	result->window_size = result->modulo == modulo_128 ? 32 : 4;
+	result->ack_timer = 3000;
+	result->retries = 10;
+
+	p = info;
+
+	if (*p != FI_Format_Indicator) {
+	  return 0;
+	}
+	p++;
+
+	if (*p != GI_Group_Identifier) {
+	  return 0;
+	}
+	p++;
+
+	group_len = *p++;
+	group_len = (group_len << 8) + *p++;
+	
+	while (p < info + 4 + group_len) {
+
+	  int pind, plen, pval, j;
+
+	  pind = *p++;
+	  plen = *p++;		// should have sanity checking
+	  pval = 0;
+	  for (j=0; j<plen; j++) {
+	    pval = (pval << 8) + *p++;
+	  }
+
+	  switch (pind) {
+
+	    case PI_Classes_of_Procedures:
+	      
+	      if ( ! (pval & PV_Classes_Procedures_Balanced_ABM)) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected Balanced ABM to be set.\n");	
+	      }
+
+	      if (pval & PV_Classes_Procedures_Half_Duplex && ! (pval & PV_Classes_Procedures_Full_Duplex)) {
+	        result->full_duplex = 0;
+	      }
+	      else if (pval & PV_Classes_Procedures_Full_Duplex && ! (pval & PV_Classes_Procedures_Half_Duplex)) {
+	        result->full_duplex = 1;
+	      }
+	      else {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected one of Half or Full Duplex be set.\n");	
+	      }
+
+	      break;
+
+	    case PI_HDLC_Optional_Functions:	
+
+	      if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
+	        result->rej = selective_reject_reject;		/* Both bits set */
+	      }
+	      else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
+	        result->rej = implicit_reject;			/* Only REJ is set */
+	      }
+	      else if ( ! (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
+	        result->rej = selective_reject;			/* Only SREJ is set */
+	      }
+	      else {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected one or both of REJ, SREJ to be set.\n");	
+	      }
+
+	      if (pval & PV_HDLC_Optional_Functions_Modulo_8 && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
+	        result->modulo = modulo_8;
+	      }
+	      else if (pval & PV_HDLC_Optional_Functions_Modulo_128 && ! (pval & PV_HDLC_Optional_Functions_Modulo_8)) {
+	        result->modulo = modulo_128;
+	      }
+	      else {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected one of Modulo 8 or 128 be set.\n");	
+	      }
+
+	      if ( ! (pval & PV_HDLC_Optional_Functions_Extended_Address)) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected Extended Address to be set.\n");	
+	      }
+
+	      if ( ! (pval & PV_HDLC_Optional_Functions_TEST_cmd_resp)) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected TEST cmd/resp to be set.\n");	
+	      }
+
+	      if ( ! (pval & PV_HDLC_Optional_Functions_16_bit_FCS)) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected 16 bit FCS to be set.\n");	
+	      }
+
+	      if ( ! (pval & PV_HDLC_Optional_Functions_Synchronous_Tx)) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: Expected Synchronous Tx to be set.\n");	
+	      }
+
+	      break;
+
+	    case PI_I_Field_Length_Rx:	
+	      
+	      result->i_field_length_rx = pval / 8;
+
+	      if (pval & 0x7) {
+	        text_color_set (DW_COLOR_ERROR);
+	        dw_printf ("XID error: I Field Length Rx is not a whole number of bytes.\n");	
+	      }
+
+	      break;
+
+	    case PI_Window_Size_Rx:	
+
+	      result->window_size = pval;
+
+// TODO must be 1-7 for modulo 8 or 1-127 for modulo 128;
+
+//	      if (pval & 0x7) {
+//	        text_color_set (DW_COLOR_ERROR);
+//	        dw_printf ("XID error: Window Size Rx is not in range of 1 thru ???\n");	
+//	      }
+
+//continue here.
+
+	      break;
+
+	    case PI_Ack_Timer:	
+	      result->ack_timer = pval;
+	      break;
+
+	    case PI_Retries:
+	      result->retries = pval;
+	      break;
+
+	    default:		
+	      break;
+	  }
+	}
+
+	if (p != info + info_len) {
+	  text_color_set (DW_COLOR_ERROR);
+	  dw_printf ("XID error: Frame / Group Length mismatch.\n");	
+	}
+
+	return 1; 
+
+} /* end xid_parse */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xid_encode
+ *
+ * Purpose:    	...
+ *
+ * Inputs:	param	- 
+ *
+ * Outputs:	info	- Information part of XID frame.
+ *			  Does not include the control byte.
+ *
+ * Returns:	Number of bytes in the info part.
+ *
+ * Description:	
+ *
+ *--------------------------------------------------------------------*/
+
+
+int xid_encode (struct ax25_param_s *param, unsigned char *info)
+{
+	unsigned char *p;
+	int len;
+	int x;
+
+	p = info;
+
+	*p++ = FI_Format_Indicator;
+	*p++ = GI_Group_Identifier;
+	*p++ = 0;
+	*p++ = 0x17;
+
+	*p++ = PI_Classes_of_Procedures;
+	*p++ = 2;
+	
+	x = PV_Classes_Procedures_Balanced_ABM;
+
+	if (param->full_duplex)
+	  x |= PV_Classes_Procedures_Full_Duplex;
+	else
+	  x |= PV_Classes_Procedures_Half_Duplex;
+
+	*p++ = (x >> 8) & 0xff;
+	*p++ = x & 0xff;
+
+	*p++ = PI_HDLC_Optional_Functions;
+	*p++ = 3;
+
+	x = PV_HDLC_Optional_Functions_Extended_Address |
+		PV_HDLC_Optional_Functions_TEST_cmd_resp |
+		PV_HDLC_Optional_Functions_16_bit_FCS | 
+		PV_HDLC_Optional_Functions_Synchronous_Tx;
+
+	if (param->rej == implicit_reject || param->rej == selective_reject_reject)
+	  x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
+
+	if (param->rej == selective_reject || param->rej == selective_reject_reject)	
+	  x |= PV_HDLC_Optional_Functions_SREJ_cmd_resp;
+
+	if (param->modulo == modulo_128)
+	  x |= PV_HDLC_Optional_Functions_Modulo_128;
+	else
+	  x |= PV_HDLC_Optional_Functions_Modulo_8;
+
+	*p++ = (x >> 16) & 0xff;
+	*p++ = (x >> 8) & 0xff;
+	*p++ = x & 0xff;
+
+	*p++ = PI_I_Field_Length_Rx;
+	*p++ = 2;
+	x = param->i_field_length_rx * 8;
+	*p++ = (x >> 8) & 0xff;
+	*p++ = x & 0xff;
+
+	*p++ = PI_Window_Size_Rx;
+	*p++ = 1;
+	*p++ = param->window_size;
+
+	*p++ = PI_Ack_Timer;
+	*p++ = 2;
+	*p++ = param->ack_timer >> 8;
+	*p++ = param->ack_timer & 0xff;
+
+	*p++ = PI_Retries;
+	*p++ = 1;
+	*p++ = param->retries;
+
+	len = p - info;
+	assert (len == 27);
+	return (len);
+
+} /* end xid_encode */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        main
+ *
+ * Purpose:    	Unit test for other functions here.
+ *
+ * Description:	Run with:
+ *
+ *			gcc -DTEST -g xid.c textcolor.c ; ./a
+ *
+ *		Result should be:
+ *
+ *			XID test:  Success.
+ *
+ *		with no error messages.
+ *
+ *--------------------------------------------------------------------*/
+
+
+#if TEST
+
+/* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */
+/* This is the info part after a control byte of 0xAF. */
+
+static unsigned char example[27] = {
+
+	/* FI */	0x82,	/* Format indicator */
+	/* GI */	0x80,	/* Group Identifier - parameter negotiation */
+	/* GL */	0x00,	/* Group length - all of the PI/PL/PV fields */
+	/* GL */	0x17,	/* (2 bytes) */
+	/* PI */	0x02,	/* Parameter Indicator - classes of procedures */
+	/* PL */	0x02,	/* Parameter Length */
+#if 0 // Example in the protocol spec looks wrong.
+	/* PV */	0x00,	/* Parameter Variable - Half Duplex, Async, Balanced Mode */
+	/* PV */	0x20,	/*  */
+#else  // I think it should be like this instead.
+	/* PV */	0x21,	/* Parameter Variable - Half Duplex, Async, Balanced Mode */
+	/* PV */	0x00,	/* Reserved */
+#endif
+	/* PI */	0x03,	/* Parameter Indicator - optional functions */
+	/* PL */	0x03,	/* Parameter Length */
+	/* PV */	0x86,	/* Parameter Variable - SREJ/REJ, extended addr */ 
+	/* PV */	0xA8,	/* 16-bit FCS, TEST cmd/resp, Modulo 128 */
+	/* PV */	0x02,	/* synchronous transmit */
+	/* PI */	0x06,	/* Parameter Indicator - Rx I field length (bits) */
+	/* PL */	0x02,	/* Parameter Length */
+	/* PV */	0x04,	/* Parameter Variable - 1024 bits (128 octets) */
+	/* PV */	0x00,	/* */
+	/* PI */	0x08,	/* Parameter Indicator - Rx window size */
+	/* PL */	0x01,	/* Parameter length */
+	/* PV */	0x02,	/* Parameter Variable - 2 frames */
+	/* PI */	0x09,	/* Parameter Indicator - Timer T1 */
+	/* PL */	0x02,	/* Parameter Length */
+	/* PV */	0x10,	/* Parameter Variable - 4096 MSec */
+	/* PV */	0x00,	/* */
+	/* PI */	0x0A,	/* Parameter Indicator - Retries (N1) */
+	/* PL */	0x01,	/* Parameter Length */
+	/* PV */	0x03 	/* Parameter Variable - 3 ret */
+};
+
+int main (int argc, char *argv[]) {
+
+	struct ax25_param_s param;
+	struct ax25_param_s param2;
+	int n;
+	unsigned char info[40];
+
+/* parse example. */
+
+	n = xid_parse (example, sizeof(example), &param);
+
+	text_color_set (DW_COLOR_ERROR);
+
+	assert (n==1);
+	assert (param.full_duplex == 0);
+	assert (param.rej == selective_reject_reject);
+	assert (param.modulo == modulo_128);
+	assert (param.i_field_length_rx == 128);	
+	assert (param.window_size == 2);	
+	assert (param.ack_timer == 4096);	
+	assert (param.retries == 3);	
+
+/* encode and verify it comes out the same. */
+
+	n = xid_encode (&param, info);
+
+	assert (n == sizeof(example));
+	n = memcmp(info, example, 27);
+
+	//for (n=0; n<27; n++) {
+	//  dw_printf ("%2d  %02x  %02x\n", n, example[n], info[n]);
+	//}
+
+	assert (n == 0);
+	
+/* try a couple different values. */
+
+	param.full_duplex = 1;
+	param.rej = implicit_reject;
+	param.modulo = modulo_8;
+	param.i_field_length_rx = 2048;
+	param.window_size = 3;
+	param.ack_timer = 3000;
+	param.retries = 10;
+
+	n = xid_encode (&param, info);
+	n = xid_parse (info, n, &param2);
+
+	assert (param2.full_duplex == 1);
+	assert (param2.rej == implicit_reject);
+	assert (param2.modulo == modulo_8);
+	assert (param2.i_field_length_rx == 2048);
+	assert (param2.window_size == 3);
+	assert (param2.ack_timer == 3000);
+	assert (param2.retries == 10);
+
+/* Finally the third possbility for rej. */
+
+	param.full_duplex = 0;
+	param.rej = selective_reject;
+	param.modulo = modulo_8;
+	param.i_field_length_rx = 256;
+	param.window_size = 4;
+	param.ack_timer = 3000;
+	param.retries = 10;
+
+	n = xid_encode (&param, info);
+	n = xid_parse (info, n, &param2);
+
+	assert (param2.full_duplex == 0);
+	assert (param2.rej == selective_reject);
+	assert (param2.modulo == modulo_8);
+	assert (param2.i_field_length_rx == 256);
+	assert (param2.window_size == 4);
+	assert (param2.ack_timer == 3000);
+	assert (param2.retries == 10);
+
+	text_color_set (DW_COLOR_INFO);
+	dw_printf ("XID test:  Success.\n");	
+	
+	exit (0);
+	
+}
+
+#endif
+
+/* end xid.c */
diff --git a/xmit.c b/xmit.c
index db37e61..21d4b67 100644
--- a/xmit.c
+++ b/xmit.c
@@ -1,994 +1,1060 @@
-
-//
-//    This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
-//
-
-
-/*------------------------------------------------------------------
- *
- * Module:      xmit.c
- *
- * Purpose:   	Transmit queued up packets when channel is clear.
- *		
- * Description:	Producers of packets to be transmitted call tq_append and then
- *		go merrily on their way, unconcerned about when the packet might
- *		actually get transmitted.
- *
- *		This thread waits until the channel is clear and then removes
- *		packets from the queue and transmits them.
- *
- *
- * Usage:	(1) The main application calls xmit_init.
- *
- *			This will initialize the transmit packet queue
- *			and create a thread to empty the queue when
- *			the channel is clear.
- *
- *		(2) The application queues up packets by calling tq_append.
- *
- *			Packets that are being digipeated should go in the 
- *			high priority queue so they will go out first.
- *
- *			Other packets should go into the lower priority queue.
- *
- *		(3) xmit_thread removes packets from the queue and transmits
- *			them when other signals are not being heard.
- *
- *---------------------------------------------------------------*/
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <math.h>
-#include <errno.h>
-
-//#include <sys/time.h>
-//#include <time.h>
-
-//#if __WIN32__
-//#include <windows.h>
-//#endif
-
-#include "direwolf.h"
-#include "ax25_pad.h"
-#include "textcolor.h"
-#include "audio.h"
-#include "tq.h"
-#include "xmit.h"
-#include "hdlc_send.h"
-#include "hdlc_rec.h"
-#include "ptt.h"
-#include "dtime_now.h"
-
-
-
-/*
- * Parameters for transmission.
- * Each channel can have different timing values.
- *
- * These are initialized once at application startup time
- * and some can be changed later by commands from connected applications.
- */
-
-
-static int xmit_slottime[MAX_CHANS];	/* Slot time in 10 mS units for persistance algorithm. */
-
-static int xmit_persist[MAX_CHANS];	/* Sets probability for transmitting after each */
-					/* slot time delay.  Transmit if a random number */
-					/* in range of 0 - 255 <= persist value.  */
-					/* Otherwise wait another slot time and try again. */
-
-static int xmit_txdelay[MAX_CHANS];	/* After turning on the transmitter, */
-					/* send "flags" for txdelay * 10 mS. */
-
-static int xmit_txtail[MAX_CHANS];		/* Amount of time to keep transmitting after we */
-					/* are done sending the data.  This is to avoid */
-					/* dropping PTT too soon and chopping off the end */
-					/* of the frame.  Again 10 mS units. */
-
-static int xmit_bits_per_sec[MAX_CHANS];	/* Data transmission rate. */
-					/* Often called baud rate which is equivalent in */
-					/* 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
-
-
-/*
- * When an audio device is in stereo mode, we can have two 
- * different channels that want to transmit at the same time.
- * We are not clever enough to multiplex them so use this
- * so only one is activte at the same time.
- */
-static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS];
-
-
-
-static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist);
-static void xmit_ax25_frames (int c, int p, packet_t pp);
-static void xmit_speech (int c, packet_t pp);
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        xmit_init
- *
- * Purpose:     Initialize the transmit process.
- *
- * Inputs:	modem		- Structure with modem and timing parameters.
- *
- *
- * Outputs:	Remember required information for future use.
- *
- * Description:	Initialize the queue to be empty and set up other
- *		mechanisms for sharing it between different threads.
- *
- *		Start up xmit_thread(s) to actually send the packets
- *		at the appropriate time.
- *
- * Version 1.2:	We now allow multiple audio devices with one or two channels each.
- *		Each audio channel has its own thread.
- *
- *--------------------------------------------------------------------*/
-
-static struct audio_s *save_audio_config_p;
-
-
-void xmit_init (struct audio_s *p_modem, int debug_xmit_packet)
-{
-	int j;
-	int ad;
-
-#if __WIN32__
-	HANDLE xmit_th[MAX_CHANS];
-#else
-	//pthread_attr_t attr;
-	//struct sched_param sp;
-	pthread_t xmit_tid[MAX_CHANS];
-#endif
-	//int e;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_init ( ... )\n");
-#endif
-
-	save_audio_config_p = p_modem;
-
-	g_debug_xmit_packet = debug_xmit_packet;
-
-/*
- * Push to Talk (PTT) control.
- */
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_init: about to call ptt_init \n");
-#endif
-	ptt_init (p_modem);
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_init: back from ptt_init \n");
-#endif
-
-/* 
- * Save parameters for later use.
- * TODO1.2:  Any reason to use global config rather than making a copy?
- */
-
-	for (j=0; j<MAX_CHANS; j++) {
-	  xmit_bits_per_sec[j] = p_modem->achan[j].baud;
-	  xmit_slottime[j] = p_modem->achan[j].slottime;
-	  xmit_persist[j] = p_modem->achan[j].persist;
-	  xmit_txdelay[j] = p_modem->achan[j].txdelay;
-	  xmit_txtail[j] = p_modem->achan[j].txtail;
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_init: about to call tq_init \n");
-#endif
-	tq_init (p_modem);
-
-
-	for (ad = 0; ad < MAX_ADEVS; ad++) {
-	  dw_mutex_init (&(audio_out_dev_mutex[ad]));
-	}
- 
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_init: about to create threads \n");
-#endif
-
-//TODO:  xmit thread should be higher priority to avoid
-// underrun on the audio output device.
-
-
-	for (j=0; j<MAX_CHANS; j++) {
-
-	  if (p_modem->achan[j].valid) {
-
-#if __WIN32__
-	    xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(long)j, 0, NULL);
-	    if (xmit_th[j] == NULL) {
-	       text_color_set(DW_COLOR_ERROR);
-	      dw_printf ("Could not create xmit thread %d\n", j);
-	      return;
-	    }
-#else
-	    int e;
-#if 0
-
-//TODO: not this simple.  probably need FIFO policy.
-	    pthread_attr_init (&attr);
-  	    e = pthread_attr_getschedparam (&attr, &sp);
-	    if (e != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      perror("pthread_attr_getschedparam");
-	    }
-
-	    text_color_set(DW_COLOR_ERROR);
-	    dw_printf ("Default scheduling priority = %d, min=%d, max=%d\n", 
-		sp.sched_priority, 
-		sched_get_priority_min(SCHED_OTHER),
-		sched_get_priority_max(SCHED_OTHER));
-	    sp.sched_priority--;
-
-  	    e = pthread_attr_setschedparam (&attr, &sp);
-	    if (e != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      perror("pthread_attr_setschedparam");
-	    }
-	
-	    e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(long)j);
-	    pthread_attr_destroy (&attr);
-#else
-	    e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(long)j);
-#endif
-	    if (e != 0) {
-	      text_color_set(DW_COLOR_ERROR);
-	      perror("Could not create xmit thread for audio device");
-	      return;
-	    }
-#endif
-	  }
-	}
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_init: finished \n");
-#endif
-
-
-} /* end tq_init */
-
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        xmit_set_txdelay
- *		xmit_set_persist
- *		xmit_set_slottime
- *		xmit_set_txtail
- *				
- *
- * Purpose:     The KISS protocol, and maybe others, can specify
- *		transmit timing parameters.  If the application
- *		specifies these, they will override what was read
- *		from the configuration file.
- *
- * Inputs:	channel	- should be 0 or 1.
- *
- *		value	- time values are in 10 mSec units.
- *
- *
- * Outputs:	Remember required information for future use.
- *
- * Question:	Should we have an option to enable or disable the
- *		application changing these values?
- *
- * Bugs:	No validity checking other than array subscript out of bounds.
- *
- *--------------------------------------------------------------------*/
-
-void xmit_set_txdelay (int channel, int value)
-{
-	if (channel >= 0 && channel < MAX_CHANS) {
-	  xmit_txdelay[channel] = value;
-	}
-}
-
-void xmit_set_persist (int channel, int value)
-{
-	if (channel >= 0 && channel < MAX_CHANS) {
-	  xmit_persist[channel] = value;
-	}
-}
-
-void xmit_set_slottime (int channel, int value)
-{
-	if (channel >= 0 && channel < MAX_CHANS) {
-	  xmit_slottime[channel] = value;
-	}
-}
-
-void xmit_set_txtail (int channel, int value)
-{
-	if (channel >= 0 && channel < MAX_CHANS) {
-	  xmit_txtail[channel] = value;
-	}
-}
-
-/*-------------------------------------------------------------------
- *
- * Name:        xmit_thread
- *
- * Purpose:     Process transmit queue for one channel.
- *
- * Inputs:	transmit packet queue.	
- *
- * Outputs:	
- *
- * Description:	We have different timing rules for different types of
- *		packets so they are put into different queues.
- *
- *		High Priority -
- *
- *			Packets which are being digipeated go out first.
- *			Latest recommendations are to retransmit these
- *			immdediately (after no one else is heard, of course)
- *			rather than waiting random times to avoid collisions.
- *			The KPC-3 configuration option for this is "UIDWAIT OFF".  (?)
- *
- *		Low Priority - 
- *
- *			Other packets are sent after a random wait time
- *			(determined by PERSIST & SLOTTIME) to help avoid
- *			collisions.	
- *
- *		If more than one audio channel is being used, a separate
- *		pair of transmit queues is used for each channel.
- *
- *
- * 
- * Version 1.2:	Allow more than one audio device.
- * 		each channel has its own thread.
- *		Add speech capability.
- *
- *--------------------------------------------------------------------*/
-
-#if __WIN32__
-static unsigned __stdcall xmit_thread (void *arg)
-#else
-static void * xmit_thread (void *arg)
-#endif
-{
-	int c = (int)(long)arg; // channel number.
-	packet_t pp;
-	int p;
-	int ok;
-
-/*
- * These are for timing of a transmission.
- * All are in usual unix time (seconds since 1/1/1970) but higher resolution
- */
-
-
-	while (1) {
-
-	  tq_wait_while_empty (c);
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("xmit_thread, channel %d: woke up\n", c);
-#endif
-	  
-	  for (p=0; p<TQ_NUM_PRIO; p++) {
-
-	      pp = tq_remove (c, p);
-#if DEBUG
-	      text_color_set(DW_COLOR_DEBUG);
-	      dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
-#endif
-	      if (pp != NULL) {
-
-/* 
- * Wait for the channel to be clear.
- * For the high priority queue, begin transmitting immediately.
- * For the low priority queue, wait a random amount of time, in hopes
- * of minimizing collisions.
- */
-	        ok = wait_for_clear_channel (c, (p==TQ_PRIO_0_HI), xmit_slottime[c], xmit_persist[c]);
-
-	        if (ok) {
-/*
- * Channel is clear and we have lock on output device. 
- */
-	          char dest[AX25_MAX_ADDR_LEN];
-
-	          if (ax25_is_aprs (pp)) { 
-		    ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
-	 	  }
-	 	  else {
-		    strcpy (dest, "");
-	          }
-
-		  if (strcmp(dest, "SPEECH") == 0) {
-	            xmit_speech (c, pp);
-	          }
-	          else {
-	            xmit_ax25_frames (c, p, pp);
-		  }
-
-	          dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(c)]));
-	        }
-	        else {
-/*
- * Timeout waiting for clear channel.
- * Discard the packet.
- * Display with ERROR color rather than XMIT color.
- */
-		  char stemp[1024];	/* max size needed? */
-		  int info_len;
-		  unsigned char *pinfo;
-
-
-	          text_color_set(DW_COLOR_ERROR);
-		  dw_printf ("Waited too long for clear channel.  Discarding packet below.\n");
-
-	          ax25_format_addrs (pp, stemp);
-
-	          info_len = ax25_get_info (pp, &pinfo);
-
-	          text_color_set(DW_COLOR_INFO);
-	          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, ! ax25_is_aprs(pp));
-	          dw_printf ("\n");
-		  ax25_delete (pp);
-
-		} /* wait for clear channel. */
-	    } /* for high priority then low priority */
-	  }
-	}
-
-	return 0;	/* unreachable but quiet the warning. */
-
-} /* end xmit_thread */
-
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        xmit_ax25_frames
- *
- * Purpose:     After we have a clear channel, and possibly waited a random time,
- *		we transmit one or more frames.
- *
- * Inputs:	c	- Channel number.
- *	
- *		p	- Priority of the queue.
- *
- *		pp	- Packet object pointer.
- *			  It will be deleted so caller should not try
- *			  to reference it after this.	
- *
- * Description:	Turn on transmitter.
- *		Send flags for TXDELAY time.
- *		Send the first packet, given by pp.
- *		Possibly send more packets from the same queue.
- *		Send flags for TXTAIL time.
- *		Turn off transmitter.
- *
- *
- * How many frames in one transmission?
- *
- *		Should we send multiple frames in one transmission if we 
- *		have more than one sitting in the queue?  At first I was thinking
- *		this would help reduce channel congestion.  I don't recall seeing
- *		anything in the specifications allowing or disallowing multiple
- *		frames in one transmission.  I can think of some scenarios 
- *		where it might help.  I can think of some where it would 
- *		definitely be counter productive.  
- *
- * What to others have to say about this topic?
- *
- *	"For what it is worth, the original APRSdos used a several second random
- *	generator each time any kind of packet was generated... This is to avoid
- *	bundling. Because bundling, though good for connected packet, is not good
- *	on APRS. Sometimes the digi begins digipeating the first packet in the
- *	bundle and steps all over the remainder of them. So best to make sure each
- *	packet is isolated in time from others..."
- *	
- *		Bob, WB4APR
- *	
- *
- * Version 0.9:	Earlier versions always sent one frame per transmission.
- *		This was fine for APRS but more and more people are now
- *		using this as a KISS TNC for connected protocols.
- *		Rather than having a MAXFRAME configuration file item,
- *		we try setting the maximum number automatically.
- *		1 for digipeated frames, 7 for others.
- *
- *--------------------------------------------------------------------*/
-
-
-static void xmit_ax25_frames (int c, int p, packet_t pp)
-{
-
-  	unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
-    	int flen;
-	char stemp[1024];	/* max size needed? */
-	int info_len;
-	unsigned char *pinfo;
-	int pre_flags, post_flags;
-	int num_bits;		/* Total number of bits in transmission */
-				/* including all flags and bit stuffing. */
-	int duration;		/* Transmission time in milliseconds. */
-	int already;
-	int wait_more;
-
-	int maxframe;		/* Maximum number of frames for one transmission. */
-	int numframe;		/* Number of frames sent during this transmission. */
-
-/*
- * These are for timing of a transmission.
- * All are in usual unix time (seconds since 1/1/1970) but higher resolution
- */
-	double time_ptt;	/* Time when PTT is turned on. */
-	double time_now;	/* Current time. */
-
-
-	int nb;
-
-	maxframe = (p == TQ_PRIO_0_HI) ? 1 : 7;
-
-
-/*
- * Print trasmitted packet.  Prefix by channel and priority.
- * Do this before we get into the time critical part.
- */
-	ax25_format_addrs (pp, stemp);
-	info_len = ax25_get_info (pp, &pinfo);
-	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, ! ax25_is_aprs(pp));
-	dw_printf ("\n");
-
-/* 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");
-	}
-
-/* 
- * Turn on transmitter.
- * Start sending leading flag bytes.
- */
-	time_ptt = dtime_now ();
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]);
-#endif
-	ptt_set (OCTYPE_PTT, c, 1);
-
-	pre_flags = MS_TO_BITS(xmit_txdelay[c] * 10, c) / 8;
-	num_bits =  hdlc_send_flags (c, pre_flags, 0);
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[c], pre_flags, num_bits);
-#endif
-
-
-/*
- * Transmit the frame.
- */	
-	flen = ax25_pack (pp, fbuf);
-	assert (flen >= 1 && flen <= sizeof(fbuf));
-	nb = hdlc_send_frame (c, fbuf, flen);
-	num_bits += nb;
-	numframe = 1;
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
-#endif
-	ax25_delete (pp);
-
-/*
- * Additional packets if available and not exceeding max.
- */
-
-	while (numframe < maxframe && tq_count (c,p) > 0) {
-
-	  pp = tq_remove (c, p);
-#if DEBUG
-	 text_color_set(DW_COLOR_DEBUG);
-	 dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
-#endif
-	 ax25_format_addrs (pp, stemp);
-	 info_len = ax25_get_info (pp, &pinfo);
-	 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, ! ax25_is_aprs(pp));
-	 dw_printf ("\n");
-
-	 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));
-	  nb = hdlc_send_frame (c, fbuf, flen);
-	  num_bits += nb;
-	  numframe++;
-#if DEBUG
-	  text_color_set(DW_COLOR_DEBUG);
-	  dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
-#endif
-	  ax25_delete (pp);
-	}
-
-/* 
- * Need TXTAIL because we don't know exactly when the sound is done.
- */
-
-	post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8;
-	nb = hdlc_send_flags (c, post_flags, 1);
-	num_bits += nb;
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[c], post_flags, nb, num_bits);
-#endif
-
-
-/* 
- * While demodulating is CPU intensive, generating the tones is not.
- * Example: on the RPi, with 50% of the CPU taken with two receive
- * channels, a transmission of more than a second is generated in
- * about 40 mS of elapsed real time.
- */
-
-	audio_wait(ACHAN2ADEV(c));		
-
-/* 
- * Ideally we should be here just about the time when the audio is ending.
- * However, the innards of "audio_wait" are not satisfactory in all cases.
- *
- * Calculate how long the frame(s) should take in milliseconds.
- */
-
-	duration = BITS_TO_MS(num_bits, c);
-
-/*
- * See how long it has been since PTT was turned on.
- * Wait additional time if necessary.
- */
-
-	time_now = dtime_now();
-	already = (int) ((time_now - time_ptt) * 1000.);
-	wait_more = duration - already;
-
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	dw_printf ("xmit_thread: xmit duration=%d, %d already elapsed since PTT, wait %d more\n", duration, already, wait_more );
-#endif
-
-	if (wait_more > 0) {
-	  SLEEP_MS(wait_more);
-	}
-	else if (wait_more < -100) {
-
-	  /* If we run over by 10 mSec or so, it's nothing to worry about. */
-	  /* However, if PTT is still on about 1/10 sec after audio */
-	  /* should be done, something is wrong. */
-
-	  /* Looks like a bug with the RPi audio system. Never an issue with Ubuntu.  */
-	  /* This runs over randomly sometimes. TODO:  investigate more fully sometime. */
-#ifndef __arm__
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Transmit timing error: PTT is on %d mSec too long.\n", -wait_more);
-#endif
-	}
-
-/*
- * Turn off transmitter.
- */
-#if DEBUG
-	text_color_set(DW_COLOR_DEBUG);
-	time_now = dtime_now();
-	dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration);
-#endif
-		
-	ptt_set (OCTYPE_PTT, c, 0);
-
-} /* end xmit_ax25_frames */
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        xmit_ax25_frames
- *
- * Purpose:     After we have a clear channel, and possibly waited a random time,
- *		we transmit one or more frames.
- *
- * Inputs:	c	- Channel number.
- *	
- *		pp	- Packet object pointer.
- *			  It will be deleted so caller should not try
- *			  to reference it after this.	
- *
- * Description:	Turn on transmitter.
- *		Invoke the text-to-speech script.
- *		Turn off transmitter.
- *
- *
- *--------------------------------------------------------------------*/
-
-
-static void xmit_speech (int c, packet_t pp)
-{
-
-
-	int info_len;
-	unsigned char *pinfo;
-
-/*
- * Print spoken packet.  Prefix by channel.
- */
-
-	info_len = ax25_get_info (pp, &pinfo);
-	text_color_set(DW_COLOR_XMIT);
-	dw_printf ("[%d.speech] \"%s\"\n", c, pinfo);
-
-
-	if (strlen(save_audio_config_p->tts_script) == 0) {
-          text_color_set(DW_COLOR_ERROR);
-          dw_printf ("Text-to-speech script has not been configured.\n");
-	  ax25_delete (pp);
-	  return;
-	}
-
-/* 
- * Turn on transmitter.
- */
-	ptt_set (OCTYPE_PTT, c, 1);
-
-/*
- * Invoke the speech-to-text script.
- */	
-
-	xmit_speak_it (save_audio_config_p->tts_script, c, (char*)pinfo);
-
-/*
- * Turn off transmitter.
- */
-		
-	ptt_set (OCTYPE_PTT, c, 0);
-	ax25_delete (pp);
-
-} /* end xmit_speech */
-
-
-/* Broken out into separate function so configuration can validate it. */
-/* Returns 0 for success. */
-
-int xmit_speak_it (char *script, int c, char *orig_msg)
-{
-	int err;
-	char cmd[2000];	
-	char *p;
-	char msg[2000];
-
-/* Remove any quotes because it will mess up command line argument parsing. */
-
-	strcpy (msg, orig_msg);
-
-	for (p=msg; *p!='\0'; p++) {
-	  if (*p == '"') *p = ' ';
-	}
-
-#if __WIN32__
-	sprintf (cmd, "%s %d \"%s\" >nul", script, c, msg);
-#else
-	sprintf (cmd, "%s %d \"%s\"", script, c, msg);
-#endif
-
-	//text_color_set(DW_COLOR_DEBUG);
-	//dw_printf ("cmd=%s\n", cmd);
-
-	err = system (cmd);
-
-	if (err != 0) {
-	  char cwd[1000];
-	  char path[3000];
-	  char *ignore;
-
-	  text_color_set(DW_COLOR_ERROR);
-	  dw_printf ("Failed to run text-to-speech script, %s\n", script);
-
-	  ignore = getcwd (cwd, sizeof(cwd));
-	  strcpy (path, getenv("PATH"));
-
-	  dw_printf ("CWD = %s\n", cwd);
-	  dw_printf ("PATH = %s\n", path);
-	
-	}
-	return (err);
-}
-
-
-/*-------------------------------------------------------------------
- *
- * Name:        wait_for_clear_channel
- *
- * Purpose:     Wait for the radio channel to be clear and any
- *		additional time for collision avoidance.
- *
- *
- *
- * Inputs:	channel	-	Radio channel number.
- *
- *		nowait	- 	Should be true for the high priority queue
- *				(packets being digipeated).  This will 
- *				allow transmission immediately when the 
- *				channel is clear rather than waiting a 
- *				random amount of time.
- *
- *		slottime - 	Amount of time to wait for each iteration
- *				of the waiting algorithm.  10 mSec units.
- *
- *		persist -	Probability of transmitting 
- *
- * Returns:	1 for OK.  0 for timeout.
- *
- * Description:	
- *
- *		New in version 1.2: also obtain a lock on audio out device.
- *
- * Transmit delay algorithm:
- *
- *		Wait for channel to be clear.
- *		Return if nowait is true.
- *
- *		Wait slottime * 10 milliseconds.
- *		Generate an 8 bit random number in range of 0 - 255.
- *		If random number <= persist value, return.
- *		Otherwise repeat.
- *
- * Example:
- *
- *		For typical values of slottime=10 and persist=63,
- *
- *		Delay		Probability
- *		-----		-----------
- *		100		.25					= 25%
- *		200		.75 * .25				= 19%
- *		300		.75 * .75 * .25				= 14%
- *		400		.75 * .75 * .75 * .25			= 11%
- *		500		.75 * .75 * .75 * .75 * .25		= 8%
- *		600		.75 * .75 * .75 * .75 * .75 * .25	= 6%
- *		700		.75 * .75 * .75 * .75 * .75 * .75 * .25	= 4%
- *		etc.		...
- *
- *--------------------------------------------------------------------*/
-
-/* Give up if we can't get a clear channel in a minute. */
-/* That's a long time to wait for APRS. */
-/* Might need to revisit some day for connected mode file transfers. */
-
-#define WAIT_TIMEOUT_MS (60 * 1000)	
-#define WAIT_CHECK_EVERY_MS 10
-
-static int wait_for_clear_channel (int channel, int nowait, int slottime, int persist)
-{
-	int r;
-	int n;
-
-
-	n = 0;
-
-start_over_again:
-
-	while (hdlc_rec_data_detect_any(channel)) {
-	  SLEEP_MS(WAIT_CHECK_EVERY_MS);
-	  n++;
-	  if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {
-	    return 0;
-	  }
-	}
-
-//TODO1.2:  rethink dwait.
-
-/*
- * Added in version 1.2 - for transceivers that can't
- * turn around fast enough when using squelch and VOX.
- */
-
-	if (save_audio_config_p->achan[channel].dwait > 0) {
-	  SLEEP_MS (save_audio_config_p->achan[channel].dwait * 10);
-	}
-
-	if (hdlc_rec_data_detect_any(channel)) {
-	  goto start_over_again;
-	}
-
-	if ( ! nowait) {
-
-	  while (1) {
-
-	    SLEEP_MS (slottime * 10);
-
-	    if (hdlc_rec_data_detect_any(channel)) {
-	      goto start_over_again;
-	    }
-
-	    r = rand() & 0xff;
-	    if (r <= persist) {
-	      break;
- 	    }	
-	  }
-	}
-
-// TODO1.2
-
-	while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(channel)]))) {
-	  SLEEP_MS(WAIT_CHECK_EVERY_MS);
-	  n++;
-	  if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {
-	    return 0;
-	  }
-	}
-
-	return 1;
-
-} /* end wait_for_clear_channel */
-
-
-/* end xmit.c */
-
-
-
+
+//
+//    This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+//    Copyright (C) 2011, 2013, 2014, 2015  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/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module:      xmit.c
+ *
+ * Purpose:   	Transmit queued up packets when channel is clear.
+ *		
+ * Description:	Producers of packets to be transmitted call tq_append and then
+ *		go merrily on their way, unconcerned about when the packet might
+ *		actually get transmitted.
+ *
+ *		This thread waits until the channel is clear and then removes
+ *		packets from the queue and transmits them.
+ *
+ *
+ * Usage:	(1) The main application calls xmit_init.
+ *
+ *			This will initialize the transmit packet queue
+ *			and create a thread to empty the queue when
+ *			the channel is clear.
+ *
+ *		(2) The application queues up packets by calling tq_append.
+ *
+ *			Packets that are being digipeated should go in the 
+ *			high priority queue so they will go out first.
+ *
+ *			Other packets should go into the lower priority queue.
+ *
+ *		(3) xmit_thread removes packets from the queue and transmits
+ *			them when other signals are not being heard.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+
+#include "direwolf.h"
+#include "ax25_pad.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "tq.h"
+#include "xmit.h"
+#include "hdlc_send.h"
+#include "hdlc_rec.h"
+#include "ptt.h"
+#include "dtime_now.h"
+#include "morse.h"
+
+
+
+/*
+ * Parameters for transmission.
+ * Each channel can have different timing values.
+ *
+ * These are initialized once at application startup time
+ * and some can be changed later by commands from connected applications.
+ */
+
+
+static int xmit_slottime[MAX_CHANS];	/* Slot time in 10 mS units for persistance algorithm. */
+
+static int xmit_persist[MAX_CHANS];	/* Sets probability for transmitting after each */
+					/* slot time delay.  Transmit if a random number */
+					/* in range of 0 - 255 <= persist value.  */
+					/* Otherwise wait another slot time and try again. */
+
+static int xmit_txdelay[MAX_CHANS];	/* After turning on the transmitter, */
+					/* send "flags" for txdelay * 10 mS. */
+
+static int xmit_txtail[MAX_CHANS];	/* Amount of time to keep transmitting after we */
+					/* are done sending the data.  This is to avoid */
+					/* dropping PTT too soon and chopping off the end */
+					/* of the frame.  Again 10 mS units. */
+
+static int xmit_bits_per_sec[MAX_CHANS];	/* Data transmission rate. */
+					/* Often called baud rate which is equivalent in */
+					/* 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
+
+
+/*
+ * When an audio device is in stereo mode, we can have two 
+ * different channels that want to transmit at the same time.
+ * We are not clever enough to multiplex them so use this
+ * so only one is activte at the same time.
+ */
+static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS];
+
+
+
+static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist);
+static void xmit_ax25_frames (int c, int p, packet_t pp);
+static void xmit_speech (int c, packet_t pp);
+static void xmit_morse (int c, packet_t pp, int wpm);
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_init
+ *
+ * Purpose:     Initialize the transmit process.
+ *
+ * Inputs:	modem		- Structure with modem and timing parameters.
+ *
+ *
+ * Outputs:	Remember required information for future use.
+ *
+ * Description:	Initialize the queue to be empty and set up other
+ *		mechanisms for sharing it between different threads.
+ *
+ *		Start up xmit_thread(s) to actually send the packets
+ *		at the appropriate time.
+ *
+ * Version 1.2:	We now allow multiple audio devices with one or two channels each.
+ *		Each audio channel has its own thread.
+ *
+ *--------------------------------------------------------------------*/
+
+static struct audio_s *save_audio_config_p;
+
+
+void xmit_init (struct audio_s *p_modem, int debug_xmit_packet)
+{
+	int j;
+	int ad;
+
+#if __WIN32__
+	HANDLE xmit_th[MAX_CHANS];
+#else
+	//pthread_attr_t attr;
+	//struct sched_param sp;
+	pthread_t xmit_tid[MAX_CHANS];
+#endif
+	//int e;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_init ( ... )\n");
+#endif
+
+	save_audio_config_p = p_modem;
+
+	g_debug_xmit_packet = debug_xmit_packet;
+
+/*
+ * Push to Talk (PTT) control.
+ */
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_init: about to call ptt_init \n");
+#endif
+	ptt_init (p_modem);
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_init: back from ptt_init \n");
+#endif
+
+/* 
+ * Save parameters for later use.
+ * TODO1.2:  Any reason to use global config rather than making a copy?
+ */
+
+	for (j=0; j<MAX_CHANS; j++) {
+	  xmit_bits_per_sec[j] = p_modem->achan[j].baud;
+	  xmit_slottime[j] = p_modem->achan[j].slottime;
+	  xmit_persist[j] = p_modem->achan[j].persist;
+	  xmit_txdelay[j] = p_modem->achan[j].txdelay;
+	  xmit_txtail[j] = p_modem->achan[j].txtail;
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_init: about to call tq_init \n");
+#endif
+	tq_init (p_modem);
+
+
+	for (ad = 0; ad < MAX_ADEVS; ad++) {
+	  dw_mutex_init (&(audio_out_dev_mutex[ad]));
+	}
+ 
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_init: about to create threads \n");
+#endif
+
+//TODO:  xmit thread should be higher priority to avoid
+// underrun on the audio output device.
+
+
+	for (j=0; j<MAX_CHANS; j++) {
+
+	  if (p_modem->achan[j].valid) {
+
+#if __WIN32__
+	    xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(long)j, 0, NULL);
+	    if (xmit_th[j] == NULL) {
+	       text_color_set(DW_COLOR_ERROR);
+	      dw_printf ("Could not create xmit thread %d\n", j);
+	      return;
+	    }
+#else
+	    int e;
+#if 0
+
+//TODO: not this simple.  probably need FIFO policy.
+	    pthread_attr_init (&attr);
+  	    e = pthread_attr_getschedparam (&attr, &sp);
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror("pthread_attr_getschedparam");
+	    }
+
+	    text_color_set(DW_COLOR_ERROR);
+	    dw_printf ("Default scheduling priority = %d, min=%d, max=%d\n", 
+		sp.sched_priority, 
+		sched_get_priority_min(SCHED_OTHER),
+		sched_get_priority_max(SCHED_OTHER));
+	    sp.sched_priority--;
+
+  	    e = pthread_attr_setschedparam (&attr, &sp);
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror("pthread_attr_setschedparam");
+	    }
+	
+	    e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(long)j);
+	    pthread_attr_destroy (&attr);
+#else
+	    e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(long)j);
+#endif
+	    if (e != 0) {
+	      text_color_set(DW_COLOR_ERROR);
+	      perror("Could not create xmit thread for audio device");
+	      return;
+	    }
+#endif
+	  }
+	}
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_init: finished \n");
+#endif
+
+
+} /* end tq_init */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_set_txdelay
+ *		xmit_set_persist
+ *		xmit_set_slottime
+ *		xmit_set_txtail
+ *				
+ *
+ * Purpose:     The KISS protocol, and maybe others, can specify
+ *		transmit timing parameters.  If the application
+ *		specifies these, they will override what was read
+ *		from the configuration file.
+ *
+ * Inputs:	channel	- should be 0 or 1.
+ *
+ *		value	- time values are in 10 mSec units.
+ *
+ *
+ * Outputs:	Remember required information for future use.
+ *
+ * Question:	Should we have an option to enable or disable the
+ *		application changing these values?
+ *
+ * Bugs:	No validity checking other than array subscript out of bounds.
+ *
+ *--------------------------------------------------------------------*/
+
+void xmit_set_txdelay (int channel, int value)
+{
+	if (channel >= 0 && channel < MAX_CHANS) {
+	  xmit_txdelay[channel] = value;
+	}
+}
+
+void xmit_set_persist (int channel, int value)
+{
+	if (channel >= 0 && channel < MAX_CHANS) {
+	  xmit_persist[channel] = value;
+	}
+}
+
+void xmit_set_slottime (int channel, int value)
+{
+	if (channel >= 0 && channel < MAX_CHANS) {
+	  xmit_slottime[channel] = value;
+	}
+}
+
+void xmit_set_txtail (int channel, int value)
+{
+	if (channel >= 0 && channel < MAX_CHANS) {
+	  xmit_txtail[channel] = value;
+	}
+}
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_thread
+ *
+ * Purpose:     Process transmit queue for one channel.
+ *
+ * Inputs:	transmit packet queue.	
+ *
+ * Outputs:	
+ *
+ * Description:	We have different timing rules for different types of
+ *		packets so they are put into different queues.
+ *
+ *		High Priority -
+ *
+ *			Packets which are being digipeated go out first.
+ *			Latest recommendations are to retransmit these
+ *			immdediately (after no one else is heard, of course)
+ *			rather than waiting random times to avoid collisions.
+ *			The KPC-3 configuration option for this is "UIDWAIT OFF".  (?)
+ *
+ *		Low Priority - 
+ *
+ *			Other packets are sent after a random wait time
+ *			(determined by PERSIST & SLOTTIME) to help avoid
+ *			collisions.	
+ *
+ *		If more than one audio channel is being used, a separate
+ *		pair of transmit queues is used for each channel.
+ *
+ *
+ * 
+ * Version 1.2:	Allow more than one audio device.
+ * 		each channel has its own thread.
+ *		Add speech capability.
+ *
+ *--------------------------------------------------------------------*/
+
+#if __WIN32__
+static unsigned __stdcall xmit_thread (void *arg)
+#else
+static void * xmit_thread (void *arg)
+#endif
+{
+	int c = (int)(long)arg; // channel number.
+	packet_t pp;
+	int p;
+	int ok;
+
+/*
+ * These are for timing of a transmission.
+ * All are in usual unix time (seconds since 1/1/1970) but higher resolution
+ */
+
+
+	while (1) {
+
+	  tq_wait_while_empty (c);
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("xmit_thread, channel %d: woke up\n", c);
+#endif
+	  
+	  for (p=0; p<TQ_NUM_PRIO; p++) {
+
+	      pp = tq_remove (c, p);
+#if DEBUG
+	      text_color_set(DW_COLOR_DEBUG);
+	      dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
+#endif
+	      if (pp != NULL) {
+
+/* 
+ * Wait for the channel to be clear.
+ * For the high priority queue, begin transmitting immediately.
+ * For the low priority queue, wait a random amount of time, in hopes
+ * of minimizing collisions.
+ */
+	        ok = wait_for_clear_channel (c, (p==TQ_PRIO_0_HI), xmit_slottime[c], xmit_persist[c]);
+
+	        if (ok) {
+/*
+ * Channel is clear and we have lock on output device. 
+ *
+ * If destination is "SPEECH" send info part to speech synthesizer.
+ * If destination is "MORSE" send as morse code.
+ */
+	          char dest[AX25_MAX_ADDR_LEN];
+		  int ssid = 0;
+
+
+	          if (ax25_is_aprs (pp)) { 
+
+		    ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest);
+		    ssid = ax25_get_ssid(pp, AX25_DESTINATION);
+	 	  }
+	 	  else {
+		    strlcpy (dest, "", sizeof(dest));
+	          }
+
+		  if (strcmp(dest, "SPEECH") == 0) {
+	            xmit_speech (c, pp);
+	          }
+		  else if (strcmp(dest, "MORSE") == 0) {
+
+		    int wpm = ssid * 2;
+		    if (wpm == 0) wpm = MORSE_DEFAULT_WPM;
+
+		    // This is a bit of a hack so we don't respond too quickly for APRStt.
+		    // It will be sent in high priority queue while a beacon wouldn't.  
+		    // Add a little delay so user has time release PTT after sending #.
+		    // This and default txdelay would give us a second.
+
+		    if (p == TQ_PRIO_0_HI) {
+	              //text_color_set(DW_COLOR_DEBUG);
+		      //dw_printf ("APRStt morse xmit delay hack...\n");
+		      SLEEP_MS (700);
+		    }
+
+	            xmit_morse (c, pp, wpm);
+	          }
+	          else {
+	            xmit_ax25_frames (c, p, pp);
+		  }
+
+	          dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(c)]));
+	        }
+	        else {
+/*
+ * Timeout waiting for clear channel.
+ * Discard the packet.
+ * Display with ERROR color rather than XMIT color.
+ */
+		  char stemp[1024];	/* max size needed? */
+		  int info_len;
+		  unsigned char *pinfo;
+
+
+	          text_color_set(DW_COLOR_ERROR);
+		  dw_printf ("Waited too long for clear channel.  Discarding packet below.\n");
+
+	          ax25_format_addrs (pp, stemp);
+
+	          info_len = ax25_get_info (pp, &pinfo);
+
+	          text_color_set(DW_COLOR_INFO);
+	          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, ! ax25_is_aprs(pp));
+	          dw_printf ("\n");
+		  ax25_delete (pp);
+
+		} /* wait for clear channel. */
+	    } /* for high priority then low priority */
+	  }
+	}
+
+	return 0;	/* unreachable but quiet the warning. */
+
+} /* end xmit_thread */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_ax25_frames
+ *
+ * Purpose:     After we have a clear channel, and possibly waited a random time,
+ *		we transmit one or more frames.
+ *
+ * Inputs:	c	- Channel number.
+ *	
+ *		p	- Priority of the queue.
+ *
+ *		pp	- Packet object pointer.
+ *			  It will be deleted so caller should not try
+ *			  to reference it after this.	
+ *
+ * Description:	Turn on transmitter.
+ *		Send flags for TXDELAY time.
+ *		Send the first packet, given by pp.
+ *		Possibly send more packets from the same queue.
+ *		Send flags for TXTAIL time.
+ *		Turn off transmitter.
+ *
+ *
+ * How many frames in one transmission?
+ *
+ *		Should we send multiple frames in one transmission if we 
+ *		have more than one sitting in the queue?  At first I was thinking
+ *		this would help reduce channel congestion.  I don't recall seeing
+ *		anything in the specifications allowing or disallowing multiple
+ *		frames in one transmission.  I can think of some scenarios 
+ *		where it might help.  I can think of some where it would 
+ *		definitely be counter productive.  
+ *
+ * What to others have to say about this topic?
+ *
+ *	"For what it is worth, the original APRSdos used a several second random
+ *	generator each time any kind of packet was generated... This is to avoid
+ *	bundling. Because bundling, though good for connected packet, is not good
+ *	on APRS. Sometimes the digi begins digipeating the first packet in the
+ *	bundle and steps all over the remainder of them. So best to make sure each
+ *	packet is isolated in time from others..."
+ *	
+ *		Bob, WB4APR
+ *	
+ *
+ * Version 0.9:	Earlier versions always sent one frame per transmission.
+ *		This was fine for APRS but more and more people are now
+ *		using this as a KISS TNC for connected protocols.
+ *		Rather than having a MAXFRAME configuration file item,
+ *		we try setting the maximum number automatically.
+ *		1 for digipeated frames, 7 for others.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static void xmit_ax25_frames (int c, int p, packet_t pp)
+{
+
+  	unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
+    	int flen;
+	char stemp[1024];	/* max size needed? */
+	int info_len;
+	unsigned char *pinfo;
+	int pre_flags, post_flags;
+	int num_bits;		/* Total number of bits in transmission */
+				/* including all flags and bit stuffing. */
+	int duration;		/* Transmission time in milliseconds. */
+	int already;
+	int wait_more;
+
+	int maxframe;		/* Maximum number of frames for one transmission. */
+	int numframe;		/* Number of frames sent during this transmission. */
+
+/*
+ * These are for timing of a transmission.
+ * All are in usual unix time (seconds since 1/1/1970) but higher resolution
+ */
+	double time_ptt;	/* Time when PTT is turned on. */
+	double time_now;	/* Current time. */
+
+
+	int nb;
+
+	maxframe = (p == TQ_PRIO_0_HI) ? 1 : 7;
+
+
+/*
+ * Print trasmitted packet.  Prefix by channel and priority.
+ * Do this before we get into the time critical part.
+ */
+	ax25_format_addrs (pp, stemp);
+	info_len = ax25_get_info (pp, &pinfo);
+	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, ! ax25_is_aprs(pp));
+	dw_printf ("\n");
+	(void)ax25_check_addresses (pp);
+
+/* 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");
+	}
+
+/* 
+ * Turn on transmitter.
+ * Start sending leading flag bytes.
+ */
+	time_ptt = dtime_now ();
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]);
+#endif
+	ptt_set (OCTYPE_PTT, c, 1);
+
+	pre_flags = MS_TO_BITS(xmit_txdelay[c] * 10, c) / 8;
+	num_bits =  hdlc_send_flags (c, pre_flags, 0);
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[c], pre_flags, num_bits);
+#endif
+
+
+/*
+ * Transmit the frame.
+ */	
+	flen = ax25_pack (pp, fbuf);
+	assert (flen >= 1 && flen <= sizeof(fbuf));
+	nb = hdlc_send_frame (c, fbuf, flen);
+	num_bits += nb;
+	numframe = 1;
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
+#endif
+	ax25_delete (pp);
+
+/*
+ * Additional packets if available and not exceeding max.
+ */
+
+	while (numframe < maxframe && tq_count (c,p) > 0) {
+
+	  pp = tq_remove (c, p);
+#if DEBUG
+	 text_color_set(DW_COLOR_DEBUG);
+	 dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp);
+#endif
+	 ax25_format_addrs (pp, stemp);
+	 info_len = ax25_get_info (pp, &pinfo);
+	 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, ! ax25_is_aprs(pp));
+	 dw_printf ("\n");
+	 (void)ax25_check_addresses (pp);
+
+	 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));
+	  nb = hdlc_send_frame (c, fbuf, flen);
+	  num_bits += nb;
+	  numframe++;
+#if DEBUG
+	  text_color_set(DW_COLOR_DEBUG);
+	  dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe);
+#endif
+	  ax25_delete (pp);
+	}
+
+/* 
+ * Need TXTAIL because we don't know exactly when the sound is done.
+ */
+
+	post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8;
+	nb = hdlc_send_flags (c, post_flags, 1);
+	num_bits += nb;
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[c], post_flags, nb, num_bits);
+#endif
+
+
+/* 
+ * While demodulating is CPU intensive, generating the tones is not.
+ * Example: on the RPi, with 50% of the CPU taken with two receive
+ * channels, a transmission of more than a second is generated in
+ * about 40 mS of elapsed real time.
+ */
+
+	audio_wait(ACHAN2ADEV(c));		
+
+/* 
+ * Ideally we should be here just about the time when the audio is ending.
+ * However, the innards of "audio_wait" are not satisfactory in all cases.
+ *
+ * Calculate how long the frame(s) should take in milliseconds.
+ */
+
+	duration = BITS_TO_MS(num_bits, c);
+
+/*
+ * See how long it has been since PTT was turned on.
+ * Wait additional time if necessary.
+ */
+
+	time_now = dtime_now();
+	already = (int) ((time_now - time_ptt) * 1000.);
+	wait_more = duration - already;
+
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	dw_printf ("xmit_thread: xmit duration=%d, %d already elapsed since PTT, wait %d more\n", duration, already, wait_more );
+#endif
+
+	if (wait_more > 0) {
+	  SLEEP_MS(wait_more);
+	}
+	else if (wait_more < -100) {
+
+	  /* If we run over by 10 mSec or so, it's nothing to worry about. */
+	  /* However, if PTT is still on about 1/10 sec after audio */
+	  /* should be done, something is wrong. */
+
+	  /* Looks like a bug with the RPi audio system. Never an issue with Ubuntu.  */
+	  /* This runs over randomly sometimes. TODO:  investigate more fully sometime. */
+#ifndef __arm__
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Transmit timing error: PTT is on %d mSec too long.\n", -wait_more);
+#endif
+	}
+
+/*
+ * Turn off transmitter.
+ */
+#if DEBUG
+	text_color_set(DW_COLOR_DEBUG);
+	time_now = dtime_now();
+	dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration);
+#endif
+		
+	ptt_set (OCTYPE_PTT, c, 0);
+
+} /* end xmit_ax25_frames */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_speech
+ *
+ * Purpose:     After we have a clear channel, and possibly waited a random time,
+ *		we transmit information part of frame as speech.
+ *
+ * Inputs:	c	- Channel number.
+ *	
+ *		pp	- Packet object pointer.
+ *			  It will be deleted so caller should not try
+ *			  to reference it after this.	
+ *
+ * Description:	Turn on transmitter.
+ *		Invoke the text-to-speech script.
+ *		Turn off transmitter.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static void xmit_speech (int c, packet_t pp)
+{
+
+
+	int info_len;
+	unsigned char *pinfo;
+
+/*
+ * Print spoken packet.  Prefix by channel.
+ */
+
+	info_len = ax25_get_info (pp, &pinfo);
+	text_color_set(DW_COLOR_XMIT);
+	dw_printf ("[%d.speech] \"%s\"\n", c, pinfo);
+
+
+	if (strlen(save_audio_config_p->tts_script) == 0) {
+          text_color_set(DW_COLOR_ERROR);
+          dw_printf ("Text-to-speech script has not been configured.\n");
+	  ax25_delete (pp);
+	  return;
+	}
+
+/* 
+ * Turn on transmitter.
+ */
+	ptt_set (OCTYPE_PTT, c, 1);
+
+/*
+ * Invoke the speech-to-text script.
+ */	
+
+	xmit_speak_it (save_audio_config_p->tts_script, c, (char*)pinfo);
+
+/*
+ * Turn off transmitter.
+ */
+		
+	ptt_set (OCTYPE_PTT, c, 0);
+	ax25_delete (pp);
+
+} /* end xmit_speech */
+
+
+/* Broken out into separate function so configuration can validate it. */
+/* Returns 0 for success. */
+
+int xmit_speak_it (char *script, int c, char *orig_msg)
+{
+	int err;
+	char cmd[2000];	
+	char *p;
+	char msg[2000];
+
+/* Remove any quotes because it will mess up command line argument parsing. */
+
+	strlcpy (msg, orig_msg, sizeof(msg));
+
+	for (p=msg; *p!='\0'; p++) {
+	  if (*p == '"') *p = ' ';
+	}
+
+#if __WIN32__
+	snprintf (cmd, sizeof(cmd), "%s %d \"%s\" >nul", script, c, msg);
+#else
+	snprintf (cmd, sizeof(cmd), "%s %d \"%s\"", script, c, msg);
+#endif
+
+	//text_color_set(DW_COLOR_DEBUG);
+	//dw_printf ("cmd=%s\n", cmd);
+
+	err = system (cmd);
+
+	if (err != 0) {
+	  char cwd[1000];
+	  char path[3000];
+	  char *ignore;
+
+	  text_color_set(DW_COLOR_ERROR);
+	  dw_printf ("Failed to run text-to-speech script, %s\n", script);
+
+	  ignore = getcwd (cwd, sizeof(cwd));
+	  strlcpy (path, getenv("PATH"), sizeof(path));
+
+	  dw_printf ("CWD = %s\n", cwd);
+	  dw_printf ("PATH = %s\n", path);
+	
+	}
+	return (err);
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        xmit_morse
+ *
+ * Purpose:     After we have a clear channel, and possibly waited a random time,
+ *		we transmit information part of frame as Morse code.
+ *
+ * Inputs:	c	- Channel number.
+ *	
+ *		pp	- Packet object pointer.
+ *			  It will be deleted so caller should not try
+ *			  to reference it after this.	
+ *
+ *		wpm	- Speed in words per minute.
+ *
+ * Description:	Turn on transmitter.
+ *		Send text as Morse code.
+ *		Turn off transmitter.
+ *
+ *--------------------------------------------------------------------*/
+
+
+static void xmit_morse (int c, packet_t pp, int wpm)
+{
+
+
+	int info_len;
+	unsigned char *pinfo;
+
+
+	info_len = ax25_get_info (pp, &pinfo);
+	text_color_set(DW_COLOR_XMIT);
+	dw_printf ("[%d.morse] \"%s\"\n", c, pinfo);
+
+	ptt_set (OCTYPE_PTT, c, 1);
+
+	morse_send (c, (char*)pinfo, wpm, xmit_txdelay[c] * 10, xmit_txtail[c] * 10);
+
+	ptt_set (OCTYPE_PTT, c, 0);
+	ax25_delete (pp);
+
+} /* end xmit_morse */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name:        wait_for_clear_channel
+ *
+ * Purpose:     Wait for the radio channel to be clear and any
+ *		additional time for collision avoidance.
+ *
+ *
+ *
+ * Inputs:	channel	-	Radio channel number.
+ *
+ *		nowait	- 	Should be true for the high priority queue
+ *				(packets being digipeated).  This will 
+ *				allow transmission immediately when the 
+ *				channel is clear rather than waiting a 
+ *				random amount of time.
+ *
+ *		slottime - 	Amount of time to wait for each iteration
+ *				of the waiting algorithm.  10 mSec units.
+ *
+ *		persist -	Probability of transmitting 
+ *
+ * Returns:	1 for OK.  0 for timeout.
+ *
+ * Description:	
+ *
+ *		New in version 1.2: also obtain a lock on audio out device.
+ *
+ * Transmit delay algorithm:
+ *
+ *		Wait for channel to be clear.
+ *		Return if nowait is true.
+ *
+ *		Wait slottime * 10 milliseconds.
+ *		Generate an 8 bit random number in range of 0 - 255.
+ *		If random number <= persist value, return.
+ *		Otherwise repeat.
+ *
+ * Example:
+ *
+ *		For typical values of slottime=10 and persist=63,
+ *
+ *		Delay		Probability
+ *		-----		-----------
+ *		100		.25					= 25%
+ *		200		.75 * .25				= 19%
+ *		300		.75 * .75 * .25				= 14%
+ *		400		.75 * .75 * .75 * .25			= 11%
+ *		500		.75 * .75 * .75 * .75 * .25		= 8%
+ *		600		.75 * .75 * .75 * .75 * .75 * .25	= 6%
+ *		700		.75 * .75 * .75 * .75 * .75 * .75 * .25	= 4%
+ *		etc.		...
+ *
+ *--------------------------------------------------------------------*/
+
+/* Give up if we can't get a clear channel in a minute. */
+/* That's a long time to wait for APRS. */
+/* Might need to revisit some day for connected mode file transfers. */
+
+#define WAIT_TIMEOUT_MS (60 * 1000)	
+#define WAIT_CHECK_EVERY_MS 10
+
+static int wait_for_clear_channel (int channel, int nowait, int slottime, int persist)
+{
+	int r;
+	int n;
+
+
+	n = 0;
+
+start_over_again:
+
+	while (hdlc_rec_data_detect_any(channel)) {
+	  SLEEP_MS(WAIT_CHECK_EVERY_MS);
+	  n++;
+	  if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {
+	    return 0;
+	  }
+	}
+
+//TODO1.2:  rethink dwait.
+
+/*
+ * Added in version 1.2 - for transceivers that can't
+ * turn around fast enough when using squelch and VOX.
+ */
+
+	if (save_audio_config_p->achan[channel].dwait > 0) {
+	  SLEEP_MS (save_audio_config_p->achan[channel].dwait * 10);
+	}
+
+	if (hdlc_rec_data_detect_any(channel)) {
+	  goto start_over_again;
+	}
+
+	if ( ! nowait) {
+
+	  while (1) {
+
+	    SLEEP_MS (slottime * 10);
+
+	    if (hdlc_rec_data_detect_any(channel)) {
+	      goto start_over_again;
+	    }
+
+	    r = rand() & 0xff;
+	    if (r <= persist) {
+	      break;
+ 	    }	
+	  }
+	}
+
+// TODO1.2
+
+	while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(channel)]))) {
+	  SLEEP_MS(WAIT_CHECK_EVERY_MS);
+	  n++;
+	  if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) {
+	    return 0;
+	  }
+	}
+
+	return 1;
+
+} /* end wait_for_clear_channel */
+
+
+/* end xmit.c */
+
+
+
diff --git a/xmit.h b/xmit.h
index 68259e7..b444f1e 100644
--- a/xmit.h
+++ b/xmit.h
@@ -1,25 +1,25 @@
-
-
-#ifndef XMIT_H
-#define XMIT_H 1
-
-#include "audio.h"	/* for struct audio_s */
-
-
-extern void xmit_init (struct audio_s *p_modem, int debug_xmit_packet);
-
-extern void xmit_set_txdelay (int channel, int value);
-
-extern void xmit_set_persist (int channel, int value);
-
-extern void xmit_set_slottime (int channel, int value);
-
-extern void xmit_set_txtail (int channel, int value);
-
-
-extern int xmit_speak_it (char *script, int c, char *msg);
-
-#endif
-
-/* end xmit.h */
-
+
+
+#ifndef XMIT_H
+#define XMIT_H 1
+
+#include "audio.h"	/* for struct audio_s */
+
+
+extern void xmit_init (struct audio_s *p_modem, int debug_xmit_packet);
+
+extern void xmit_set_txdelay (int channel, int value);
+
+extern void xmit_set_persist (int channel, int value);
+
+extern void xmit_set_slottime (int channel, int value);
+
+extern void xmit_set_txtail (int channel, int value);
+
+
+extern int xmit_speak_it (char *script, int c, char *msg);
+
+#endif
+
+/* end xmit.h */
+

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