diff --git a/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf b/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf new file mode 100644 index 0000000..20c43a7 Binary files /dev/null and b/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf differ diff --git a/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf b/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf new file mode 100644 index 0000000..6730138 Binary files /dev/null and b/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf differ diff --git a/APRStt-Implementation-Notes.pdf b/APRStt-Implementation-Notes.pdf index 31e5028..6477a52 100644 Binary files a/APRStt-Implementation-Notes.pdf and b/APRStt-Implementation-Notes.pdf differ diff --git a/CHANGES.txt b/CHANGES.txt index fa2fe31..86a1a42 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,74 @@ 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 ----------- @@ -193,7 +261,7 @@ 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. +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6800756 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +# Select proper Makefile for operating system. +# The Windows version is built with the help of Cygwin. + +win := $(shell uname | grep CYGWIN) +ifneq ($(win),) +include Makefile.win +else +include Makefile.linux +endif diff --git a/Makefile.linux b/Makefile.linux index 4152601..0a3537e 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -2,97 +2,197 @@ # Makefile for Linux version of Dire Wolf. # -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx +all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.desktop + @echo " " + @echo "Next step - install with:" + @echo " " + @echo " sudo make install" + @echo " " + +CC := gcc +CFLAGS := -O3 -pthread -Igeotranz -CC = gcc # -# The DSP filters can be sped up considerably with the SSE -# instructions. The SSE instructions were introduced in 1999 -# with the Pentium III series. +# 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 look at impact of various optimization levels. # -# Benchmark results with Ubuntu gcc version 4.6.3, 32 bit platform. +# 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. # -# seconds options, comments -# ------ ----------------- -# 123 -O2 -# 128 -O3 Slower than -O2 ? -# 123 -Ofast (should be same as -O3 -ffastmath) -# 126 -Ofast -march=pentium -# 88 -Ofast -msse -# 108 -Ofast -march=pentium -msse -# 88 -Ofast -march=pentium3 (this implies -msse) -# 89 -Ofast -march=pentium3 -msse +# 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. # -# Raspberry Pi, ARM11 (ARMv6 + VFP2) -# gcc (Debian 4.6.3-14+rpi1) 4.6.3 -# -# seconds options, comments -# ------ ----------------- -# 1015 -O2 -# 948 -O3 -# 928 -Ofast -# 937 -Ofast -fmpu=vfp (worse, no option for vfp2) -# -# Are we getting any vectorizing? +# 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." # - - -# -# Release 0.9 added a new feature than can consume a lot of CPU -# power: multiple AFSK demodulators running in parallel. -# These spend a lot of time spinning around in little loops -# calculating the sums of products for the DSP filters. -# -# 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. -# Here we find out if the gcc compiler is generating code -# for the i386. If so, we add the option to assume we will -# have at least a Pentium 3 to run on. -# -# When generating code for the x86_64 target, it is automatically -# assumed that the SSE instructions are available. -# -# If you are using gcc version 4.6 or later, you might get a -# small improvement by using the new "-Ofast" option that is -# not available in older compilers. -# "-O3" is used here for compatibility with older compilers. -# -# You might also get some improvement by using "-march=native" -# to fine tune the application for your particular type of -# hardware. -# -# If you are planning to distribute the binary version to -# other people (in some ham radio software collection), avoid -# fine tuning it for your particular computer. It could -# cause compatibility issues for those with older computers. -# +# 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. arch := $(shell echo | gcc -E -dM - | grep __i386__) - ifneq ($(arch),) -# You might see improvement with -march fine tuned to your hardware. -# Probably should keep pentium3 if you will be redistributing binaries -# to other people. -CFLAGS := -O3 -march=pentium3 -pthread -Iutm -else -CFLAGS := -O3 -pthread -Iutm +CFLAGS += -march=pentium3 endif +# +# ---------- x86_64 ---------- +# + +# +# 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 + + +# +# ---------- ARM - Raspberry Pi 1 models ---------- +# +# Raspberry Pi (before RPi model 2), ARM11 (ARMv6 + VFP2) +# gcc (Debian 4.6.3-14+rpi1) 4.6.3 +# +# +# seconds options comments +# ------ ------- --------- +# 892 -O3 +# 887 -O3 -ffast-math +# x -O3 -ffast-math -march=native (error: bad value for -march switch) +# 887 -O3 -ffast-math -mfpu=vfp +# 890 -O3 -ffast-math -march=armv6zk -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp +# +# +# The compiler, supplied with Raspbian, is configured with these options which are +# good for the pre version 2 models. +# --with-arch=armv6 --with-fpu=vfp --with-float=hard +# +# Run "gcc --help -v 2" and look near the end. +# +# + +# +# ---------- ARM - Raspberry Pi 2 ---------- +# +# Besides the higher clock speed, the Raspberry Pi 2 has the NEON instruction set +# which which should make things considerably faster. +# +# +# seconds options comments +# ------ ------- --------- +# 426 -O3 -ffast-math (already more than twice as fast) +# 429 -O3 -mfpu=neon +# 419 -O3 -mfpu=neon -funsafe-math-optimizations +# 412 -O3 -ffast-math -mfpu=neon +# 413 -O3 -ffast-math -mfpu=neon-vfpv4 +# 430 -O3 -ffast-math -mfpu=neon-vfpv4 -march=armv7-a +# 412 -O3 -ffast-math -mfpu=neon-vfpv4 -mtune=arm7 +# 410 -O3 -ffast-math -mfpu=neon-vfpv4 -funsafe-math-optimizations + +# +# gcc -march=armv7-a -mfpu=neon-vfpv4 +# +# I expected the -mfpu=neon option to have a much larger impact. +# Adding -march=armv7-a makes it slower! + +# +# If you compile with the RPi 2 specific options above and try to run it on the RPi +# model B (pre version 2), it will die with "illegal instruction." +# +# Dire Wolf is known to work on the BeagleBone, CubieBoard2, etc. +# The best compiler options will depend on the specific type of processor +# and the compiler target defaults. +# + +neon := $(shell cat /proc/cpuinfo | grep neon) +ifneq ($(neon),) +CFLAGS += -mfpu=neon +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 # If you want to use OSS (for FreeBSD, OpenBSD) instead of -# ALSA (for Linux), comment out the two lines below. +# ALSA (for Linux), comment out (or remove) the two lines below. CFLAGS += -DUSE_ALSA LDLIBS += -lasound @@ -104,23 +204,22 @@ LDLIBS += -lasound #LDLIBS += -lgps - # Name of current directory. # Used to generate zip file name for distribution. -z=$(notdir ${CURDIR}) +z := $(notdir ${CURDIR}) # Main application. -direwolf : direwolf.o config.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 \ +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 dedupe.o tq.o xmit.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 \ - utm.a + 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 @@ -137,53 +236,155 @@ fsk_fast_filter.h : demod_afsk.c -utm.a : LatLong-UTMconversion.o +# UTM, USNG, MGRS conversions. + +geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o ar -cr $@ $^ -LatLong-UTMconversion.o : utm/LatLong-UTMconversion.c +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 $@ $^ + + + +# 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. + +# However, if you are preparing a "binary" RPM or DEB package, the +# installation location should be /usr/bin. + +# This is a step in the right direction but not sufficient to use /usr instead. + +INSTALLDIR := /usr/local + + +# 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. + + +direwolf.desktop : + @echo "Generating customized direwolf.desktop ..." + @echo '[Desktop Entry]' > $@ + @echo 'Type=Application' >> $@ +ifneq ($(wildcard /usr/bin/lxterminal),) + @echo "Exec=lxterminal -t \"Dire Wolf\" -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@ +else ifneq ($(wildcard /usr/bin/lxterm),) + @echo "Exec=lxterm -hold -title \"Dire Wolf\" -bg white -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@ +else + @echo "Exec=xterm -hold -title \"Dire Wolf\" -bg white -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@ +endif + @echo 'Name=Dire Wolf' >> $@ + @echo 'Comment=APRS Soundcard TNC' >> $@ + @echo 'Icon=/usr/share/direwolf/dw-icon.png' >> $@ + @echo "Path=$(HOME)" >> $@ + @echo '#Terminal=true' >> $@ + @echo 'Categories=HamRadio' >> $@ + @echo 'Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25' >> $@ + # Optional installation into /usr/local/... # Needs to be run as root or with sudo. - # TODO: Review file locations. -install : direwolf decode_aprs tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop - install direwolf /usr/local/bin - install decode_aprs /usr/local/bin - install text2tt /usr/local/bin - install tt2text /usr/local/bin - install ll2utm /usr/local/bin - install utm2ll /usr/local/bin - install aclients /usr/local/bin - install log2gpx /usr/local/bin +install : 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 /usr/local/share/doc/direwolf/CHANGES.txt - install -D --mode=644 LICENSE-dire-wolf.txt /usr/local/share/doc/direwolf/LICENSE-dire-wolf.txt - install -D --mode=644 LICENSE-other.txt /usr/local/share/doc/direwolf/LICENSE-other.txt - install -D --mode=644 User-Guide.pdf /usr/local/share/doc/direwolf/User-Guide.pdf - install -D --mode=644 Raspberry-Pi-APRS.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS.pdf - install -D --mode=644 Raspberry-Pi-APRS-Tracker.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf - install -D --mode=644 APRStt-Implementation-Notes.pdf /usr/local/share/doc/direwolf/APRStt-Implementation-Notes.pdf - install -D --mode=644 Quick-Start-Guide-Windows.pdf /usr/local/share/doc/direwolf/Quick-Start-Guide-Windows.pdf + 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 + @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 " " + @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. # TODO: Handle Linux variations correctly. +.PHONY: install-conf +install-conf : direwolf.conf + cp direwolf.conf ~ +ifneq ($(wildcard $(HOME)/Desktop),) + @echo " " + @echo "This will add a desktop icon on some systems:" + @echo " " + @echo " make install-rpi" + @echo " " +endif + + +.PHONY: install-rpi install-rpi : dw-start.sh cp dw-start.sh ~ ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop -install-conf : direwolf.conf - cp direwolf.conf ~ # Separate application to decode raw data. @@ -204,10 +405,10 @@ tt2text : tt_text.c # Convert between Latitude/Longitude and UTM coordinates. -ll2utm : ll2utm.c utm.a +ll2utm : ll2utm.c geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lm -utm2ll : utm2ll.c utm.a +utm2ll : utm2ll.c geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lm @@ -219,7 +420,7 @@ log2gpx : log2gpx.c # Test application to generate sound. -gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c textcolor.c +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 demod.o : tune.h @@ -238,16 +439,21 @@ testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c textcolor.c $(CC) $(CFLAGS) -o $@ $^ -lm -lrt - time ./atest ../direwolf-0.2/02_Track_2.wav # Unit test for inner digipeater algorithm -dtest : digipeater.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c +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 APRStt. + +ttest : aprs_tt.c tt_text.c latlong.c misc.a geotranz.a + $(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ + + # Unit test for IGate @@ -277,61 +483,85 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c $(CC) $(CFLAGS) -g -o $@ $^ -SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c multi_modem.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \ - server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio.c \ - digipeater.c dedupe.c tq.c xmit.c beacon.c encode_aprs.c latlong.c encode_aprs.c latlong.c telemetry.c log.c +# Touch Tone to Speech sample application. + +ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o + $(CC) $(CFLAGS) -g -o $@ $^ -depend : $(SRCS) - makedepend $(INCLUDES) $^ +depend : $(wildcard *.c) + makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ - + +.PHONY: clean clean : - rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll fsk_fast_filter.h *.o *.a + 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 # Package it up for distribution. -dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \ - Raspberry-Pi-APRS-Tracker.pdf direwolf.desktop dw-start.sh +.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 - (cd .. ; zip $z-src.zip $z/CHANGES.txt $z/LICENSE* \ - $z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf $z/Raspberry-Pi-APRS.pdf Raspberry-Pi-APRS-Tracker.pdf \ - $z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \ - $z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ + egrep '^C|^L' direwolf.txt | cut -c2-999 > direwolf.conf + (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* \ + $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/direwolf.desktop $z/dw-start.sh ) + $z/dw-start.sh $z/direwolf.spec \ + $z/dwespeak.bat $z/dwespeak.sh ) + #User-Guide.pdf : User-Guide.docx # echo "***** User-Guide.pdf is out of date *****" -#Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx -# echo "***** Quick-Start-Guide-Windows.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 *****" -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 # @@ -339,3 +569,4 @@ tocalls-symbols : # # DO NOT DELETE + diff --git a/Makefile.win b/Makefile.win index a9b85f7..77ba9f1 100644 --- a/Makefile.win +++ b/Makefile.win @@ -16,19 +16,17 @@ # -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx +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. -#TODO: put -Ofast back in. - -CC = gcc -#CFLAGS = -g -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -mthreads -DUSE_REGEX_STATIC -CFLAGS = -g -Wall -march=pentium3 -msse -Iregex -Iutm -mthreads -DUSE_REGEX_STATIC -AR = ar +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. @@ -60,7 +58,7 @@ AR = ar # Name of zip file for distribution. -z=$(notdir ${CURDIR}) +z := $(notdir ${CURDIR}) @@ -71,15 +69,15 @@ demod_9600.o : fsk_demod_state.h demod_afsk.o : fsk_demod_state.h -direwolf : direwolf.o config.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 \ +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 dedupe.o tq.o xmit.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 \ - dw-icon.o regex.a misc.a utm.a - $(CC) $(CFLAGS) -g -o $@ $^ -lwinmm -lws2_32 + 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 $@ @@ -97,10 +95,30 @@ fsk_fast_filter.h : demod_afsk.c ./gen_fff > fsk_fast_filter.h -utm.a : LatLong-UTMconversion.o +# UTM, USNG, MGRS conversions. + +geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o ar -cr $@ $^ -LatLong-UTMconversion.o : utm/LatLong-UTMconversion.c +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 $@ $^ @@ -137,7 +155,7 @@ strcasestr.o : misc/strcasestr.c # Separate application to decode raw data. -decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.o telemetry.o regex.a misc.a utm.a +decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.o telemetry.o regex.a misc.a geotranz.a $(CC) $(CFLAGS) -o decode_aprs -DTEST $^ @@ -152,10 +170,10 @@ tt2text : tt_text.c # Convert between Latitude/Longitude and UTM coordinates. -ll2utm : ll2utm.c utm.a +ll2utm : ll2utm.c geotranz.a $(CC) $(CFLAGS) -o $@ $^ -utm2ll : utm2ll.c utm.a +utm2ll : utm2ll.c geotranz.a $(CC) $(CFLAGS) -o $@ $^ @@ -167,7 +185,7 @@ log2gpx : log2gpx.c misc/strsep.c misc/strtok_r.c # Test application to generate sound. -gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o textcolor.o misc.a regex.a +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. @@ -177,50 +195,57 @@ demod_9600.o : tune.h demod_afsk.o : tune.h -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ - rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c telemetry.c regex.a misc.a \ - fsk_demod_agc.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) -DNOFIX -o atest $^ - ./atest ../direwolf-0.2/02_Track_2.wav | grep "packets decoded in" >atest.out + $(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 symbols.c textcolor.c telemetry.c regex.a misc.a \ + 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 symbols.c textcolor.c telemetry.c regex.a misc.a \ + 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 - $(CC) $(CFLAGS) -o $@ $^ echo " " > tune.h - ./atest ..\\direwolf-0.2\\02_Track_2.wav + $(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 @@ -229,28 +254,21 @@ atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c # Unit test for inner digipeater algorithm -dtest : digipeater.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c misc.a regex.a +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 misc.a utm.a - $(CC) $(CFLAGS) -DTT_MAIN -o ttest aprs_tt.c tt_text.c misc.a utm.a +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 -g -o $@ $^ -lwinmm -lws2_32 - - -# 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 -lrt - ./udptest + $(CC) $(CFLAGS) -DITEST -o $@ $^ -lwinmm -lws2_32 # Unit test for telemetry decoding. @@ -264,22 +282,21 @@ etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a # 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) -g -o $@ $^ -lwinmm -lws2_32 + $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 -SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c \ - hdlc_rec2.c multi_modem.c redecode.c rdq.c rrbb.c \ - fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \ - server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio_win.c \ - digipeater.c dedupe.c tq.c xmit.c beacon.c \ - encode_aprs.c latlong.c telemetry.c \ - dtmf.c aprs_tt.c tt_text.c igate.c +# 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 -depend : $(SRCS) - makedepend $(INCLUDES) $^ - +.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 @@ -288,49 +305,100 @@ clean : # Package it up for distribution: Prebuilt Windows & source versions. -dist-win : direwolf.exe decode_aprs.exe CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \ - Raspberry-Pi-APRS.pdf Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf - rm -f ../$z-win.zip - zip ../$z-win.zip CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \ - Raspberry-Pi-APRS.pdf Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf LICENSE* *.conf \ - direwolf.exe decode_aprs.exe tocalls.txt symbols-new.txt symbolsX.txt \ - text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe aclients.exe log2gpx.exe +# Left out RPi Tracker due to Comcast upload size limit. -dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \ - Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf \ - direwolf.desktop dw-start.sh \ - tocalls.txt symbols-new.txt symbolsX.txt +.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/Quick-Start-Guide-Windows.pdf \ - $z/Raspberry-Pi-APRS.pdf $z/Raspberry-Pi-APRS-Tracker.pdf $z/APRStt-Implementation-Notes.pdf \ - $z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \ - $z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ + $z/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/direwolf.desktop $z/dw-start.sh ) - + $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 *****" -Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx - echo "***** Quick-Start-Guide-Windows.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"` @@ -340,15 +408,20 @@ backup : # 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 - - diff --git a/Quick-Start-Guide-Windows.pdf b/Quick-Start-Guide-Windows.pdf deleted file mode 100644 index 6d155f0..0000000 Binary files a/Quick-Start-Guide-Windows.pdf and /dev/null differ diff --git a/Raspberry-Pi-APRS-Tracker.pdf b/Raspberry-Pi-APRS-Tracker.pdf index f4f581b..eefd30f 100644 Binary files a/Raspberry-Pi-APRS-Tracker.pdf and b/Raspberry-Pi-APRS-Tracker.pdf differ diff --git a/Raspberry-Pi-APRS.pdf b/Raspberry-Pi-APRS.pdf index 50e85dc..49182a0 100644 Binary files a/Raspberry-Pi-APRS.pdf and b/Raspberry-Pi-APRS.pdf differ diff --git a/User-Guide.pdf b/User-Guide.pdf index e1a7501..ba4f7fd 100644 Binary files a/User-Guide.pdf and b/User-Guide.pdf differ diff --git a/aclients.c b/aclients.c index af6986d..657781e 100644 --- a/aclients.c +++ b/aclients.c @@ -575,11 +575,13 @@ static void * client_thread_net (void *arg) 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; - pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, -1); + 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'; diff --git a/aprs_tt.c b/aprs_tt.c index 20fd568..088f8c6 100644 --- a/aprs_tt.c +++ b/aprs_tt.c @@ -1,7 +1,8 @@ + // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014 John Langner, WB2OSZ +// 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 @@ -52,6 +53,7 @@ #include #include "direwolf.h" +#include "version.h" #include "ax25_pad.h" #include "hdlc_rec2.h" /* for process_rec_frame */ #include "textcolor.h" @@ -60,22 +62,30 @@ #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 -#include "utm/LatLong-UTMconversion.h" +// geotranz +#include "utm.h" +#include "mgrs.h" +#include "usng.h" +#include "error_string.h" -//TODO: #include "tt_user.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. + * Kept separate for each audio channel so the gateway CAN be listening + * on multiple channels at the same time. */ #define MAX_MSG_LEN 100 @@ -83,7 +93,6 @@ char *strtok_r(char *str, const char *delim, char **saveptr); static char msg_str[MAX_CHANS][MAX_MSG_LEN+1]; static int msg_len[MAX_CHANS]; -static void aprs_tt_message (int chan, char *msg); static int parse_fields (char *msg); static int parse_callsign (char *e); static int parse_object_name (char *e); @@ -135,7 +144,10 @@ static struct ttloc_s test_config[] = { { 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 @@ -154,6 +166,7 @@ void aprs_tt_init (struct tt_config_s *p) /* 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= 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 == '$') { @@ -216,21 +234,32 @@ void aprs_tt_button (int chan, char button) } if (button == '#') { -/* Process complete message. */ - - aprs_tt_message (chan, msg_str[chan]); +/* + * 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. */ - - poll_period++; - if (poll_period >= 39) { - poll_period = 0; - tt_user_background (); +/* + * 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 (); + } } } @@ -240,7 +269,7 @@ void aprs_tt_button (int chan, char button) /*------------------------------------------------------------------ * - * Name: aprs_tt_message + * Name: aprs_tt_sequence * * Purpose: Process complete received touch tone sequence * terminated by #. @@ -260,6 +289,11 @@ void aprs_tt_button (int chan, char button) * 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 */ @@ -272,6 +306,7 @@ static char m_callsign[20]; /* really object name */ * 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; @@ -287,7 +322,7 @@ static int m_ssid; -void aprs_tt_message (int chan, char *msg) +void aprs_tt_sequence (int chan, char *msg) { int err; @@ -303,18 +338,6 @@ void aprs_tt_message (int chan, char *msg) if (msg[0] == '#') return; -/* - * This takes the place of the usual line with audio level. - * Do it here, rather than in process_rec_frame, so any - * error messages are associated with the DTMF message - * rather than the most recent regular AX.25 frame. - */ - -#ifndef TT_MAIN - text_color_set(DW_COLOR_DEBUG); - dw_printf ("\nDTMF message\n"); -#endif - /* * The parse functions will fill these in. */ @@ -329,11 +352,6 @@ void aprs_tt_message (int chan, char *msg) strcpy (m_dao, "!T !"); /* start out unknown */ m_ssid = 12; -/* - * Send raw touch tone data to application. - */ - raw_tt_data_to_app (chan, msg); - /* * Parse the touch tone sequence. */ @@ -362,7 +380,7 @@ void aprs_tt_message (int chan, char *msg) // err == 0 OK, others, suitable error response. -} /* end aprs_tt_message */ +} /* end aprs_tt_sequence */ /*------------------------------------------------------------------ @@ -376,7 +394,7 @@ void aprs_tt_message (int chan, char *msg) * * Returns: None * - * Description: It should have one or more fields separatedy by *. + * Description: It should have one or more fields separated by *. * * callsign # * entry1 * callsign # @@ -412,6 +430,14 @@ static int parse_fields (char *msg) 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; @@ -475,6 +501,9 @@ static int parse_fields (char *msg) * 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) @@ -495,7 +524,12 @@ static int expand_macro (char *e) if (ipat >= 0) { - 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); + // 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); @@ -548,7 +582,10 @@ static int expand_macro (char *e) 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); @@ -589,6 +626,7 @@ static int expand_macro (char *e) * * Att...ttvk - Full callsign in two key method, numeric overlay, checksum. * Att...ttvvk - Full callsign in two key method, letter overlay, checksum. + * * *----------------------------------------------------------------*/ @@ -883,12 +921,13 @@ static int parse_symbol (char *e) * by total number of digits and sometimes the first digit. * * We handle most of them in a general way, processing - * them in 4 groups: + * them in 5 groups: * * * points * * vector * * grid * * utm + * * usng / mgrs * *----------------------------------------------------------------*/ @@ -897,10 +936,7 @@ static int parse_symbol (char *e) /* Average radius of earth in meters. */ #define R 6371000. -/* Convert between degrees and radians. */ -#define D2R(a) ((a) * 2. * M_PI / 360.) -#define R2D(a) ((a) * 360. / (2*M_PI)) static int parse_location (char *e) @@ -910,8 +946,10 @@ static int parse_location (char *e) char xstr[20], ystr[20], zstr[20], bstr[20], dstr[20]; double x, y, dist, bearing; double lat0, lon0; - double lat9, lon9; - double easting, northing; + double lat9, lon9; + long lerr; + double easting, northing; + char mh[20]; assert (*e == 'B'); @@ -969,6 +1007,7 @@ static int parse_location (char *e) /* 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))); @@ -1021,11 +1060,89 @@ static int parse_location (char *e) 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); + } - UTMtoLL (WSG84, northing, easting, tt_config.ttloc_ptr[ipat].utm.zone, - &m_latitude, &m_longitude); 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); } @@ -1072,7 +1189,7 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char * char mc; int k; -//TODO: remove dw_printf ("find_ttloc_match: e=%s\n", e); + // debug dw_printf ("find_ttloc_match: e=%s\n", e); for (ipat=0; ipat 0) { - for (c=m_callsign, s=src; *c != '\0' && strlen(src) < 6; c++) { - if (isupper(*c) || isdigit(*c)) { - *s++ = *c; - *s = '\0'; - } - } - } - else { - strcpy (src, "APRSTT"); - } + alevel_t alevel; - // TODO: test this. - err= symbols_into_dest (m_symtab_or_overlay, m_symbol_code, dest); - if (err) { - /* Error message was already printed. */ - /* Set reasonable default rather than keeping "GPS???" which */ - /* is invalid and causes trouble later. */ - - strcpy (dest, "GPSAA"); - } +// 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); @@ -1325,10 +1427,22 @@ static void raw_tt_data_to_app (int chan, char *msg) * 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) { - app_process_rec_packet (chan, -1, pp, -2, RETRY_NONE, "tt"); + + 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); @@ -1348,8 +1462,7 @@ static void raw_tt_data_to_app (int chan, char *msg) * * Description: Run unit test like this: * - * rm a.exe ; gcc tt_text.c -DTT_MAIN -DDEBUG aprs_tt.c strtok_r.o utm/LatLong-UTMconversion.c ; ./a.exe - * + * 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. @@ -1391,50 +1504,56 @@ int main (int argc, char *argv[]) /* Callsigns & abbreviations. */ - aprs_tt_message (0, "A9A2B42A7A7C71#"); /* WB4APR/7 */ - aprs_tt_message (0, "A27773#"); /* abbreviated form */ + 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_message (0, "A27776#"); /* Expect error message. */ + aprs_tt_sequence (0, "A27776#"); /* Expect error message. */ - aprs_tt_message (0, "A2A7A7C71#"); /* Spelled suffix, overlay, checksum */ - aprs_tt_message (0, "A27773#"); /* Suffix digits, overlay, checksum */ + aprs_tt_sequence (0, "A2A7A7C71#"); /* Spelled suffix, overlay, checksum */ + aprs_tt_sequence (0, "A27773#"); /* Suffix digits, overlay, checksum */ - aprs_tt_message (0, "A9A2B26C7D9D71#"); /* WB2OSZ/7 numeric overlay */ - aprs_tt_message (0, "A67979#"); /* abbreviated form */ + aprs_tt_sequence (0, "A9A2B26C7D9D71#"); /* WB2OSZ/7 numeric overlay */ + aprs_tt_sequence (0, "A67979#"); /* abbreviated form */ - aprs_tt_message (0, "A9A2B26C7D9D5A9#"); /* WB2OSZ/J letter overlay */ - aprs_tt_message (0, "A6795A7#"); /* abbreviated form */ + aprs_tt_sequence (0, "A9A2B26C7D9D5A9#"); /* WB2OSZ/J letter overlay */ + aprs_tt_sequence (0, "A6795A7#"); /* abbreviated form */ - aprs_tt_message (0, "A277#"); /* Tactical call "277" no overlay and no checksum */ + aprs_tt_sequence (0, "A277#"); /* Tactical call "277" no overlay and no checksum */ /* Locations */ - aprs_tt_message (0, "B01*A67979#"); - aprs_tt_message (0, "B988*A67979#"); + aprs_tt_sequence (0, "B01*A67979#"); + aprs_tt_sequence (0, "B988*A67979#"); /* expect about 52.79 +0.83 */ - aprs_tt_message (0, "B51000125*A67979#"); + 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_message (0, "B5206070*A67979#"); + aprs_tt_sequence (0, "B5206070*A67979#"); - aprs_tt_message (0, "B21234*A67979#"); - aprs_tt_message (0, "B533686*A67979#"); + aprs_tt_sequence (0, "B21234*A67979#"); + aprs_tt_sequence (0, "B533686*A67979#"); /* Comments */ - aprs_tt_message (0, "C1"); - aprs_tt_message (0, "C2"); - aprs_tt_message (0, "C146520"); - aprs_tt_message (0, "C7788444222550227776669660333666990122223333"); + aprs_tt_sequence (0, "C1"); + aprs_tt_sequence (0, "C2"); + aprs_tt_sequence (0, "C146520"); + aprs_tt_sequence (0, "C7788444222550227776669660333666990122223333"); /* Macros */ - aprs_tt_message (0, "88345"); + 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); diff --git a/aprs_tt.h b/aprs_tt.h index befe6c4..1631069 100644 --- a/aprs_tt.h +++ b/aprs_tt.h @@ -13,9 +13,9 @@ */ struct ttloc_s { - enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MACRO } type; + 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 */ + 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 */ @@ -40,12 +40,17 @@ struct ttloc_s { } grid; struct { - char zone[8]; 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; @@ -60,9 +65,14 @@ struct ttloc_s { 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_header[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN]; - /* e.g. "WB2OSZ-5>APDW07,WIDE1-1" */ + + 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. */ @@ -96,12 +106,15 @@ void aprs_tt_button (int chan, char button); #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 /* end aprs_tt.h */ \ No newline at end of file diff --git a/atest.c b/atest.c index c74c491..94b0b57 100644 --- a/atest.c +++ b/atest.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 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 @@ -23,7 +23,7 @@ * * Name: atest.c * - * Purpose: Unit test for the AFSK demodulator. + * Purpose: Test fixture for the AFSK demodulator. * * Inputs: Takes audio from a .WAV file insted of the audio device. * @@ -38,17 +38,12 @@ * (2) Burn a physical CD. * * (3) "Rip" the desired tracks with Windows Media Player. - * This results in .WMA files. + * Select .WAV file format. * - * (4) Upload the .WMA file(s) to http://media.io/ and - * convert to .WAV format. + * "Track 2" is used for most tests because that is more + * realistic for most people using the speaker output. * * - * Comparison to others: - * - * Here are some other scores from Track 2 of the TNC Test CD: - * http://sites.google.com/site/ki4mcw/Home/arduino-tnc - * * Without ONE_CHAN defined: * * Notice that the number of packets decoded, as reported by @@ -60,13 +55,6 @@ * * Only process one channel. * - * Version 0.4 decoded 870 packets. - * - * After a little tweaking, version 0.5 decodes 931 packets. - * - * After more tweaking, version 0.6 gets 965 packets. - * This is without the option to retry after getting a bad FCS. - * *--------------------------------------------------------------------*/ // #define X 1 @@ -74,7 +62,6 @@ #include #include -//#include #include #include #include @@ -86,13 +73,17 @@ #include "audio.h" #include "demod.h" -// #include "fsk_demod_agc.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 */ @@ -108,30 +99,87 @@ struct wav_header { /* .WAV file header. */ 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 wav_header header; +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 = 1; /* Reduce that sampling rate. */ +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 err; int c; - struct audio_s modem; int channel; time_t start_time; + + +#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) + int j; + + for (j=0; j 10000) { - fprintf (stderr, "Use a more reasonable bit rate in range of 100 - 10000.\n"); + 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 (modem.baud[0] < 600) { - modem.modem_type[0] = AFSK; - modem.mark_freq[0] = 1600; - modem.space_freq[0] = 1800; - } - else if (modem.baud[0] > 2400) { - modem.modem_type[0] = SCRAMBLE; - modem.mark_freq[0] = 0; - modem.space_freq[0] = 0; - printf ("Using scrambled baseband signal rather than AFSK.\n"); + 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 { - modem.modem_type[0] = AFSK; - modem.mark_freq[0] = 1200; - modem.space_freq[0] = 2200; + 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. */ - printf ("Demodulator profile set to \"%s\"\n", optarg); - strcpy (modem.profiles[0], optarg); + 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); - printf ("Decimate factor = %d\n", decimate); - modem.decimate[0] = decimate; + + 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 '?': + 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 (argv); + usage (); break; default: /* Should not be here. */ - printf("?? getopt returned character code 0%o ??\n", c); - //usage (argv); - } + text_color_set(DW_COLOR_ERROR); + dw_printf("?? getopt returned character code 0%o ??\n", c); + usage (); + } } if (optind >= argc) { - printf ("Specify .WAV file name on command line.\n"); - exit (1); + 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); - fprintf (stderr, "Couldn't open file for read: %s\n", argv[optind]); - //perror ("more info?"); - exit (1); + dw_printf ("Couldn't open file for read: %s\n", argv[optind]); + //perror ("more info?"); + exit (1); } start_time = time(NULL); @@ -328,29 +382,67 @@ int main (int argc, char *argv[]) /* * Read the file header. + * Doesn't handle all possible cases but good enough for our purposes. */ - fread (&header, sizeof(header), (size_t)1, fp); + err= fread (&header, (size_t)12, (size_t)1, fp); - assert (header.nchannels == 1 || header.nchannels == 2); - assert (header.wbitspersample == 8 || header.wbitspersample == 16); + 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); + } - modem.samples_per_sec = header.nsamplespersec; - modem.samples_per_sec = modem.samples_per_sec; - modem.bits_per_sample = header.wbitspersample; - modem.num_channels = header.nchannels; + 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); - printf ("%d samples per second\n", modem.samples_per_sec); - printf ("%d bits per sample\n", modem.bits_per_sample); - printf ("%d audio channels\n", modem.num_channels); - printf ("%d audio bytes in file\n", (int)(header.datasize)); + 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 (&modem); + multi_modem_init (&my_audio_config); e_o_f = 0; @@ -361,13 +453,13 @@ int main (int argc, char *argv[]) int audio_sample; int c; - for (c=0; c= 256 * 256) e_o_f = 1; @@ -386,8 +478,28 @@ int main (int argc, char *argv[]) } text_color_set(DW_COLOR_INFO); - printf ("\n\n"); - printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time)); + dw_printf ("\n\n"); + +#if EXPERIMENT_G + + for (j=0; j num_channels == 0) - pa -> num_channels = DEFAULT_NUM_CHANNELS; - if (pa -> samples_per_sec == 0) - pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + for (a=0; a bits_per_sample == 0) - pa -> bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + if (pa->adev[a].num_channels == 0) + pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS; - for (chan=0; chan mark_freq[chan] == 0) - pa -> mark_freq[chan] = DEFAULT_MARK_FREQ; + if (pa->adev[a].samples_per_sec == 0) + pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; - if (pa -> space_freq[chan] == 0) - pa -> space_freq[chan] = DEFAULT_SPACE_FREQ; + if (pa->adev[a].bits_per_sample == 0) + pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; - if (pa -> baud[chan] == 0) - pa -> baud[chan] = DEFAULT_BAUD; + for (chan=0; chanachan[chan].mark_freq == 0) + pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ; - if (pa->num_subchan[chan] == 0) - pa->num_subchan[chan] = 1; + 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. + * Open audio device(s). */ - udp_sock = -1; + for (a=0; aadev[a].defined) { - inbuf_size_in_bytes = 0; - inbuf_ptr = NULL; - inbuf_len = 0; - inbuf_next = 0; + adev[a].inbuf_size_in_bytes = 0; + adev[a].inbuf_ptr = NULL; + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; - outbuf_size_in_bytes = 0; - outbuf_ptr = NULL; - outbuf_len = 0; + adev[a].outbuf_size_in_bytes = 0; + adev[a].outbuf_ptr = NULL; + adev[a].outbuf_len = 0; /* * Determine the type of audio input. */ - audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; + adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; - if (strcasecmp(pa->adevice_in, "stdin") == 0 || strcmp(pa->adevice_in, "-") == 0) { - audio_in_type = AUDIO_IN_TYPE_STDIN; - /* Change - to stdin for readability. */ - strcpy (pa->adevice_in, "stdin"); - } - if (strncasecmp(pa->adevice_in, "udp:", 4) == 0) { - audio_in_type = AUDIO_IN_TYPE_SDR_UDP; - /* Supply default port if none specified. */ - if (strcasecmp(pa->adevice_in,"udp") == 0 || - strcasecmp(pa->adevice_in,"udp:") == 0) { - sprintf (pa->adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT); - } - } + 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". */ + /* If not specified, the device names should be "default". */ - strcpy (audio_in_name, pa->adevice_in); - strcpy (audio_out_name, pa->adevice_out); + strcpy (audio_in_name, pa->adev[a].adevice_in); + strcpy (audio_out_name, pa->adev[a].adevice_out); - text_color_set(DW_COLOR_INFO); + char ctemp[40]; - if (strcmp(audio_in_name,audio_out_name) == 0) { - dw_printf ("Audio device for both receive and transmit: %s\n", audio_in_name); - } - else { - dw_printf ("Audio input device for receive: %s\n", audio_in_name); - dw_printf ("Audio out device for transmit: %s\n", audio_out_name); - } + 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. @@ -285,127 +334,132 @@ int audio_open (struct audio_s *pa) /* * Input device. */ - switch (audio_in_type) { + + switch (adev[a].g_audio_in_type) { /* * Soundcard - ALSA. */ - case AUDIO_IN_TYPE_SOUNDCARD: + case AUDIO_IN_TYPE_SOUNDCARD: #if USE_ALSA - err = snd_pcm_open (&audio_in_handle, audio_in_name, SND_PCM_STREAM_CAPTURE, 0); - if (err < 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device %s for input\n%s\n", + 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); - } + return (-1); + } - inbuf_size_in_bytes = set_alsa_params (audio_in_handle, pa, audio_in_name, "input"); - break; + 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->adevice_in, O_RDWR); + 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->adevice_in); -// sprintf (message, "Could not open audio device %s", pa->adevice_in); -// perror (message); - return (-1); - } + 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); + } - outbuf_size_in_bytes = inbuf_size_in_bytes = set_oss_params (oss_audio_device_fd, pa); + adev[a].outbuf_size_in_bytes = adev[a].inbuf_size_in_bytes = set_oss_params (oss_audio_device_fd, pa); - if (inbuf_size_in_bytes <= 0 || outbuf_size_in_bytes <= 0) { - return (-1); - } + 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: + case AUDIO_IN_TYPE_SDR_UDP: - //Create socket and bind socket + //Create socket and bind socket - { - struct sockaddr_in si_me; - int slen=sizeof(si_me); - int data_size = 0; + { + struct sockaddr_in si_me; + int slen=sizeof(si_me); + int data_size = 0; - //Create UDP Socket - if ((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; - } + //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); + 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(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; - } - } - inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; + //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; + break; /* * stdin. */ - case AUDIO_IN_TYPE_STDIN: + case AUDIO_IN_TYPE_STDIN: - /* Do we need to adjust any properties of stdin? */ + /* Do we need to adjust any properties of stdin? */ - inbuf_size_in_bytes = 1024; - - break; + adev[a].inbuf_size_in_bytes = 1024; + + break; - default: + default: - text_color_set(DW_COLOR_ERROR); - dw_printf ("Internal error, invalid audio_in_type\n"); - return (-1); - } + 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 (&audio_out_handle, audio_out_name, SND_PCM_STREAM_PLAYBACK, 0); + 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", + 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); - } + return (-1); + } - outbuf_size_in_bytes = set_alsa_params (audio_out_handle, pa, audio_out_name, "output"); + adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output"); - if (inbuf_size_in_bytes <= 0 || outbuf_size_in_bytes <= 0) { - return (-1); - } + if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { + return (-1); + } #endif /* * Finally allocate buffer for each direction. */ - inbuf_ptr = malloc(inbuf_size_in_bytes); - assert (inbuf_ptr != NULL); - inbuf_len = 0; - inbuf_next = 0; + 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; - outbuf_ptr = malloc(outbuf_size_in_bytes); - assert (outbuf_ptr != NULL); - outbuf_len = 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); @@ -423,16 +477,16 @@ int audio_open (struct audio_s *pa) */ /* * Terminology: - * Sample - for one channel. e.g. 2 bytes for 16 bit. + * 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 (snd_pcm_t *handle, struct audio_s *pa, char *devname, char *inout) +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. */ + snd_pcm_uframes_t fpp; /* Frames per period. */ unsigned int val; @@ -476,7 +530,7 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname err = snd_pcm_hw_params_set_format (handle, hw_params, - pa->bits_per_sample == 8 ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16_LE); + 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", @@ -488,7 +542,7 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname /* Number of audio channels. */ - err = snd_pcm_hw_params_set_channels (handle, hw_params, pa->num_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", @@ -500,7 +554,7 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname /* Audio sample rate. */ - val = pa->samples_per_sec; + val = pa->adev[a].samples_per_sec; dir = 0; @@ -514,31 +568,54 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname return (-1); } - if (val != pa->samples_per_sec) { + 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->samples_per_sec, val); + pa->adev[a].samples_per_sec, val); dw_printf ("for %s %s.\n", devname, inout); - pa->samples_per_sec = val; + pa->adev[a].samples_per_sec = val; } - - /* Guessing around 20 reads/sec might be good. */ + + /* Original: */ + /* Guessed around 20 reads/sec might be good. */ /* Period too long = too much latency. */ - /* Period too short = too much overhead of many small transfers. */ + /* Period too short = more overhead of many small transfers. */ - fpp = pa->samples_per_sec / 20; + /* 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); @@ -550,8 +627,6 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname return (-1); } - - err = snd_pcm_hw_params (handle, hw_params); if (err < 0) { text_color_set(DW_COLOR_ERROR); @@ -560,7 +635,6 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname return (-1); } - /* Driver might not like our suggested period size */ /* and might have another idea. */ @@ -578,16 +652,15 @@ static int set_alsa_params (snd_pcm_t *handle, struct audio_s *pa, char *devname /* The read and write use units of frames, not bytes. */ - bytes_per_frame = snd_pcm_frames_to_bytes (handle, 1); - assert (bytes_per_frame == pa->num_channels * pa->bits_per_sample / 8); + 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 * bytes_per_frame; - + 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", bytes_per_frame, (int)fpp, buf_size_in_bytes); + 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); @@ -614,33 +687,33 @@ static int set_oss_params (int fd, struct audio_s *pa) int ossbuf_size_in_bytes; - err = ioctl (fd, SNDCTL_DSP_CHANNELS, &(pa->num_channels)); + 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->samples_per_sec; + asked_for = pa->adev[a].samples_per_sec; - err = ioctl (fd, SNDCTL_DSP_SPEED, &(pa->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->samples_per_sec != asked_for) { + 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->samples_per_sec); + 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->bits_per_sample)); + 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"); @@ -687,6 +760,9 @@ static int set_oss_params (int fd, struct audio_s *pa) * 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); @@ -706,7 +782,7 @@ static int set_oss_params (int fd, struct audio_s *pa) * respond quickly. */ - ossbuf_size_in_bytes = calcbufsize(pa->samples_per_sec, pa->num_channels, pa->bits_per_sample); + 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); @@ -731,6 +807,8 @@ static int set_oss_params (int fd, struct audio_s *pa) * * 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. * @@ -744,7 +822,7 @@ static int set_oss_params (int fd, struct audio_s *pa) // Use hot attribute for all functions called for every audio sample. __attribute__((hot)) -int audio_get (void) +int audio_get (int a) { int n; int retries = 0; @@ -752,11 +830,11 @@ int audio_get (void) #if STATISTICS /* Gather numbers for read from audio device. */ - static int duration = 100; /* report every 100 seconds. */ - static time_t last_time = 0; - time_t this_time; - static int sample_count; - static int error_count; +#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 @@ -766,54 +844,102 @@ int audio_get (void) #endif - assert (inbuf_size_in_bytes >= 100 && inbuf_size_in_bytes <= 32768); + assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768); -#if USE_ALSA - switch (audio_in_type) { + switch (adev[a].g_audio_in_type) { /* * Soundcard - ALSA */ case AUDIO_IN_TYPE_SOUNDCARD: - while (inbuf_next >= inbuf_len) { - assert (audio_in_handle != NULL); +#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", inbuf_size_in_bytes / bytes_per_frame); + 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 (audio_in_handle, inbuf_ptr, inbuf_size_in_bytes / bytes_per_frame); + 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", - inbuf_size_in_bytes / bytes_per_frame, n); + adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n); #endif #if STATISTICS - if (last_time == 0) { - last_time = time(NULL); - sample_count = 0; - error_count = 0; + +// 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 += n; + sample_count[a] += n; } else { - error_count++; + error_count[a]++; } - this_time = time(NULL); - if (this_time >= last_time + duration) { + 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); - dw_printf ("\nPast %d seconds, %d audio samples processed, %d errors.\n\n", - duration, sample_count, error_count); - last_time = this_time; - sample_count = 0; - error_count = 0; + + 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 @@ -822,8 +948,8 @@ int audio_get (void) /* Success */ - inbuf_len = n * bytes_per_frame; /* convert to number of bytes */ - inbuf_next = 0; + adev[a].inbuf_len = n * adev[a].bytes_per_frame; /* convert to number of bytes */ + adev[a].inbuf_next = 0; } else if (n == 0) { @@ -834,8 +960,8 @@ int audio_get (void) dw_printf ("Audio input got zero bytes: %s\n", snd_strerror(n)); SLEEP_MS(10); - inbuf_len = 0; - inbuf_next = 0; + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; } else { /* Error */ @@ -845,12 +971,12 @@ int audio_get (void) // Audio input device error: Unknown error text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio input device error: %s\n", snd_strerror(n)); + 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) { - inbuf_len = 0; - inbuf_next = 0; + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; return (-1); } @@ -858,7 +984,7 @@ int audio_get (void) /* EPIPE means overrun */ - snd_pcm_recover (audio_in_handle, n, 1); + snd_pcm_recover (adev[a].audio_in_handle, n, 1); } else { @@ -868,10 +994,36 @@ int audio_get (void) /* when the Update Manager decides to run. */ SLEEP_MS (250); - snd_pcm_recover (audio_in_handle, n, 1); + 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; /* @@ -880,21 +1032,21 @@ int audio_get (void) case AUDIO_IN_TYPE_SDR_UDP: - while (inbuf_next >= inbuf_len) { + while (adev[a].inbuf_next >= adev[a].inbuf_len) { int ch, res,i; - assert (udp_sock > 0); - res = recv(udp_sock, inbuf_ptr, inbuf_size_in_bytes, 0); + 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); - inbuf_len = 0; - inbuf_next = 0; + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; return (-1); } - inbuf_len = res; - inbuf_next = 0; + adev[a].inbuf_len = res; + adev[a].inbuf_next = 0; } break; @@ -903,49 +1055,26 @@ int audio_get (void) */ case AUDIO_IN_TYPE_STDIN: - while (inbuf_next >= inbuf_len) { + while (adev[a].inbuf_next >= adev[a].inbuf_len) { int ch, res,i; - res = read(STDIN_FILENO, inbuf_ptr, (size_t)inbuf_size_in_bytes); + 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); } - inbuf_len = res; - inbuf_next = 0; + adev[a].inbuf_len = res; + adev[a].inbuf_next = 0; } break; } - -#else /* end ALSA, begin OSS */ - - while (audio_in_type == AUDIO_IN_TYPE_SOUNDCARD && inbuf_next >= inbuf_len) { - assert (oss_audio_device_fd > 0); - n = read (oss_audio_device_fd, inbuf_ptr, inbuf_size_in_bytes); - //text_color_set(DW_COLOR_DEBUG); - // dw_printf ("audio_get(): read %d returns %d\n", inbuf_size_in_bytes, n); - if (n < 0) { - text_color_set(DW_COLOR_ERROR); - perror("Can't read from audio device"); - inbuf_len = 0; - inbuf_next = 0; - return (-1); - } - inbuf_len = n; - inbuf_next = 0; - } - -#endif /* USE_ALSA */ - - - - if (inbuf_next < inbuf_len) - n = inbuf_ptr[inbuf_next++]; + 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; @@ -969,7 +1098,9 @@ int audio_get (void) * * Purpose: Send one byte to the audio device. * - * Inputs: c - One byte in range of 0 - 255. + * Inputs: a + * + * c - One byte in range of 0 - 255. * * Returns: Normally non-negative. * -1 for any type of error. @@ -982,15 +1113,15 @@ int audio_get (void) * *----------------------------------------------------------------*/ -int audio_put (int c) +int audio_put (int a, int c) { /* Should never be full at this point. */ - assert (outbuf_len < outbuf_size_in_bytes); + assert (adev[a].outbuf_len < adev[a].outbuf_size_in_bytes); - outbuf_ptr[outbuf_len++] = c; + adev[a].outbuf_ptr[adev[a].outbuf_len++] = c; - if (outbuf_len == outbuf_size_in_bytes) { - return (audio_flush()); + if (adev[a].outbuf_len == adev[a].outbuf_size_in_bytes) { + return (audio_flush(a)); } return (0); @@ -1012,7 +1143,7 @@ int audio_put (int c) * *----------------------------------------------------------------*/ -int audio_flush (void) +int audio_flush (int a) { #if USE_ALSA int k; @@ -1020,7 +1151,7 @@ int audio_flush (void) int retries = 10; snd_pcm_status_t *status; - assert (audio_out_handle != NULL); + assert (adev[a].audio_out_handle != NULL); /* @@ -1035,7 +1166,7 @@ int audio_flush (void) snd_pcm_status_alloca(&status); - k = snd_pcm_status (audio_out_handle, 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)); @@ -1046,7 +1177,7 @@ int audio_flush (void) //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Audio output state = %d. Try to start.\n", k); - k = snd_pcm_prepare (audio_out_handle); + k = snd_pcm_prepare (adev[a].audio_out_handle); if (k != 0) { text_color_set(DW_COLOR_ERROR); @@ -1055,15 +1186,15 @@ int audio_flush (void) } - psound = outbuf_ptr; + psound = adev[a].outbuf_ptr; while (retries-- > 0) { - k = snd_pcm_writei (audio_out_handle, psound, outbuf_len / bytes_per_frame); -#if DEBUG + 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", - outbuf_len / bytes_per_frame, k); + adev[a].outbuf_len / adev[a].bytes_per_frame, k); fflush (stdout); #endif if (k == -EPIPE) { @@ -1072,7 +1203,7 @@ int audio_flush (void) /* No problemo. Recover and go around again. */ - snd_pcm_recover (audio_out_handle, k, 1); + snd_pcm_recover (adev[a].audio_out_handle, k, 1); } else if (k < 0) { text_color_set(DW_COLOR_ERROR); @@ -1081,21 +1212,21 @@ int audio_flush (void) /* Some other error condition. */ /* Try again. What do we have to lose? */ - snd_pcm_recover (audio_out_handle, k, 1); + snd_pcm_recover (adev[a].audio_out_handle, k, 1); } - else if (k != outbuf_len / bytes_per_frame) { + 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, outbuf_len / bytes_per_frame); + k, adev[a].outbuf_len / adev[a].bytes_per_frame); /* Go around again with the rest of it. */ - psound += k * bytes_per_frame; - outbuf_len -= k * bytes_per_frame; + psound += k * adev[a].bytes_per_frame; + adev[a].outbuf_len -= k * adev[a].bytes_per_frame; } else { /* Success! */ - outbuf_len = 0; + adev[a].outbuf_len = 0; return (0); } } @@ -1103,7 +1234,7 @@ int audio_flush (void) text_color_set(DW_COLOR_ERROR); dw_printf ("Audio write error retry count exceeded.\n"); - outbuf_len = 0; + adev[a].outbuf_len = 0; return (-1); #else /* OSS */ @@ -1112,13 +1243,13 @@ int audio_flush (void) unsigned char *ptr; int len; - ptr = outbuf_ptr; - len = outbuf_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 DEBUG +#if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("audio_flush(): write %d returns %d\n", len, k); fflush (stdout); @@ -1126,7 +1257,7 @@ int audio_flush (void) if (k < 0) { text_color_set(DW_COLOR_ERROR); perror("Can't write to audio device"); - outbuf_len = 0; + adev[a].outbuf_len = 0; return (-1); } if (k < len) { @@ -1137,7 +1268,7 @@ int audio_flush (void) len -= k; } - outbuf_len = 0; + adev[a].outbuf_len = 0; return (0); #endif @@ -1148,96 +1279,74 @@ int audio_flush (void) * * Name: audio_wait * - * Purpose: Wait until all the queued up audio out has been played. + * Purpose: Finish up audio output before turning PTT off. * - * Inputs: duration - hint at number of milliseconds to wait. + * Inputs: a - Index for audio device (not channel!) * - * Returns: Normally non-negative. - * -1 for any type of error. + * Returns: None. * - * Description: In our particular application, we would want to make sure - * that the entire packet has been sent out before turning - * off the transmitter PTT control. + * 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. - * There is an OSS system call for this but it doesn't work - * on Cygwin. The application crashes at a later time. * - * Haven't yet verified correct operation with ALSA. - * * 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) Add (1) and (2) resulting in when PTT should be turned off. - * (4) Take difference between current time and PPT off time - * and provide this as the additional delay required. + * (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. * *----------------------------------------------------------------*/ -int audio_wait (int duration) +void audio_wait (int a) { - int err = 0; - audio_flush (); -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_wait(): before sync, fd=%d\n", oss_audio_device_fd); -#endif + audio_flush (a); #if USE_ALSA - //double t_enter, t_leave; - //int drain_ms; - - //t_enter = dtime_now(); - /* For playback, this should wait for all pending frames */ /* to be played and then stop. */ - snd_pcm_drain (audio_out_handle); - - //t_leave = dtime_now(); - //drain_ms = (int)((t_leave - t_enter) * 1000.); - - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("audio_wait(): suggested delay = %d ms, actual = %d\n", - // duration, drain_ms); + snd_pcm_drain (adev[a].audio_out_handle); /* - * 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. + * When this was first implemented, I observed: * - * Keep the sleep delay so PTT is not turned off too soon. + * "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. */ - if (duration > 0) { - SLEEP_MS (duration); - } - #else assert (oss_audio_device_fd > 0); - - // This causes a crash later on Cygwin. - // Haven't tried it on Linux yet. + // 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); - if (duration > 0) { - SLEEP_MS (duration); - } - #endif #if DEBUG @@ -1245,8 +1354,6 @@ int audio_wait (int duration) dw_printf ("audio_wait(): after sync, status=%d\n", err); #endif - return (err); - } /* end audio_wait */ @@ -1254,7 +1361,7 @@ int audio_wait (int duration) * * Name: audio_close * - * Purpose: Close the audio device. + * Purpose: Close the audio device(s). * * Returns: Normally non-negative. * -1 for any type of error. @@ -1265,36 +1372,42 @@ int audio_wait (int duration) int audio_close (void) { int err = 0; + int a; + + for (a = 0; a < MAX_ADEVS; a++) { #if USE_ALSA - assert (audio_in_handle != NULL); - assert (audio_out_handle != NULL); + if (adev[a].audio_in_handle != NULL && adev[a].audio_out_handle != NULL) { - audio_wait (0); - - snd_pcm_close (audio_in_handle); - snd_pcm_close (audio_out_handle); + audio_wait (a); + snd_pcm_close (adev[a].audio_in_handle); + snd_pcm_close (adev[a].audio_out_handle); + #else - assert (oss_audio_device_fd > 0); - audio_wait (0); + if (oss_audio_device_fd > 0) { + + audio_wait (a); - close (oss_audio_device_fd); + close (oss_audio_device_fd); - oss_audio_device_fd = -1; + oss_audio_device_fd = -1; #endif - free (inbuf_ptr); - free (outbuf_ptr); - inbuf_size_in_bytes = 0; - inbuf_ptr = NULL; - inbuf_len = 0; - inbuf_next = 0; + free (adev[a].inbuf_ptr); + free (adev[a].outbuf_ptr); - outbuf_size_in_bytes = 0; - outbuf_ptr = NULL; - outbuf_len = 0; + 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); diff --git a/audio.h b/audio.h index 8c40afa..3f6017a 100644 --- a/audio.h +++ b/audio.h @@ -3,7 +3,8 @@ * * Module: audio.h * - * Purpose: Interface to audio device commonly called a "sound card." + * Purpose: Interface to audio device commonly called a "sound card" + * for historical reasons. * *---------------------------------------------------------------*/ @@ -12,7 +13,8 @@ #define AUDIO_H 1 #include "direwolf.h" /* for MAX_CHANS used throughout the application. */ -#include "hdlc_rec2.h" /* for enum retry_e */ +#include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ + /* @@ -27,7 +29,7 @@ enum ptt_method_e { typedef enum ptt_method_e ptt_method_t; -enum ptt_line_e { PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 }; +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 { @@ -35,91 +37,196 @@ enum audio_in_type_e { 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 { - /* Properites of the sound device. */ + /* 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. */ - char adevice_in[80]; /* Name of the audio input device (or file?). */ + 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?). */ + 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). */ + 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]; - enum audio_in_type_e audio_in_type; - /* Where is input (receive) audio coming from? */ /* Common to all channels. */ - enum retry_e fix_bits; /* Level of effort to recover from */ - /* a bad FCS on the frame. */ + 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. */ - enum modem_t {AFSK, NONE, SCRAMBLE} modem_type[MAX_CHANS]; - /* Usual AFSK. */ - /* Baseband signal. */ - /* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */ - int decimate[MAX_CHANS]; /* Reduce AFSK sample rate by this factor to */ + 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[MAX_CHANS]; /* Two tones for AFSK modulation, in Hz. */ - int space_freq[MAX_CHANS]; /* Standard tones are 1200 and 2200 for 1200 baud. */ + int mark_freq; /* Two tones for AFSK modulation, in Hz. */ + int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ - int baud[MAX_CHANS]; /* Data bits (more accurately, symbols) per second. */ + int baud; /* Data bits (more accurately, symbols) per second. */ /* Standard rates are 1200 for VHF and 300 for HF. */ - char profiles[MAX_CHANS][16]; /* 1 or more of ABC etc. */ + /* Next 3 come from config file or command line. */ - int num_freq[MAX_CHANS]; /* Number of different frequency pairs for decoders. */ + char profiles[16]; /* zero or more of ABC etc, optional + */ - int offset[MAX_CHANS]; /* Spacing between filter frequencies. */ + int num_freq; /* Number of different frequency pairs for decoders. */ - int num_subchan[MAX_CHANS]; /* Total number of modems / hdlc decoders for each channel. */ + 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. */ - - ptt_method_t ptt_method[MAX_CHANS]; /* serial port or GPIO. */ - - char ptt_device[MAX_CHANS][20]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */ - - ptt_line_t ptt_line[MAX_CHANS]; /* Control line wehn using serial port. */ - /* PTT_RTS, PTT_DTR. */ - - int ptt_gpio[MAX_CHANS]; /* GPIO number. */ - int ptt_lpt_bit[MAX_CHANS]; /* Bit number for parallel printer port. */ + /* 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[MAX_CHANS]; /* Invert the output. */ + int ptt_invert; /* Invert the output. */ + int ptt_invert2; /* Invert the secondary output. */ - int slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistance algorithm. */ + } 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[MAX_CHANS]; /* Sets probability for transmitting after each */ + 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[MAX_CHANS]; /* After turning on the transmitter, */ + int txdelay; /* After turning on the transmitter, */ /* send "flags" for txdelay * 10 mS. */ /* Default value is 30 meaning 300 milliseconds. */ - int txtail[MAX_CHANS]; /* Amount of time to keep transmitting after we */ + 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 @@ -180,10 +287,11 @@ struct audio_s { * 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 /* not sure yet. */ +#define DEFAULT_TXTAIL 10 /* @@ -194,13 +302,13 @@ struct audio_s { int audio_open (struct audio_s *pa); -int audio_get (void); +int audio_get (int a); /* a = audio device, 0 for first */ -int audio_put (int c); +int audio_put (int a, int c); -int audio_flush (void); +int audio_flush (int a); -int audio_wait (int duration); +void audio_wait (int a); int audio_close (void); diff --git a/audio_win.c b/audio_win.c index cd33955..26cfd1b 100644 --- a/audio_win.c +++ b/audio_win.c @@ -1,11 +1,13 @@ -#define DEBUGUDP 1 +//#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 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -24,18 +26,21 @@ /*------------------------------------------------------------------ * - * Module: audio.c + * 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. + * *---------------------------------------------------------------*/ @@ -65,9 +70,15 @@ #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 @@ -75,27 +86,57 @@ * 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 40 +#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)) -static enum audio_in_type_e audio_in_type; + +#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. */ -static SOCKET udp_sock; -static char stream_data[SDR_UDP_BUF_MAXLEN]; -static int stream_len; -static int stream_next; - - -#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) -#define calcbufsize(rate,chans,bits) roundup1k( ( (rate)*(chans)*(bits) / 8 * ONE_BUF_TIME)/1000 ) + + SOCKET udp_sock; + char stream_data[SDR_UDP_BUF_MAXLEN]; + int stream_len; + int stream_next; /* For sound output. */ @@ -105,86 +146,22 @@ static int stream_next; #define DWU_PLAYING 2 /* Was given to sound system for playing. */ #define DWU_DONE 3 /* Sound system is done with it. */ -static HWAVEOUT audio_out_handle = 0; + HWAVEOUT audio_out_handle; -static volatile WAVEHDR out_wavehdr[NUM_OUT_BUF]; -static int out_current; /* index to above. */ -static int outbuf_size; + 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. */ -static HWAVEIN audio_in_handle = 0; -static WAVEHDR in_wavehdr[NUM_IN_BUF]; -static volatile WAVEHDR *in_headp; /* head of queue to process. */ -static CRITICAL_SECTION in_cs; - - - - - - - -/*------------------------------------------------------------------ - * - * Name: print_capabilities - * - * Purpose: Display capabilities of the available audio devices. - * - * Example: - * - * - * Available audio input devices for receive (*=selected): - * 0: Microphone (Realtek High Defini mono: 11 22 44 96 stereo: 11 22 44 96 - * 1: Microphone (Bluetooth SCO Audio mono: 11 22 44 96 stereo: 11 22 44 96 - * 2: Microphone (Bluetooth AV Audio) mono: 11 22 44 96 stereo: 11 22 44 96 - * 3: Microphone (USB PnP Sound Devic mono: 11 22 44 96 stereo: 11 22 44 96 - * Available audio output devices for transmit (*=selected): - * 0: Speakers (Realtek High Definiti mono: 11 22 44 96 stereo: 11 22 44 96 - * 1: Speakers (Bluetooth SCO Audio) mono: 11 22 44 96 stereo: 11 22 44 96 - * 2: Realtek Digital Output (Realtek mono: 11 22 44 96 stereo: 11 22 44 96 - * 3: Realtek Digital Output(Optical) mono: 11 22 44 96 stereo: 11 22 44 96 - * 4: Speakers (Bluetooth AV Audio) mono: 11 22 44 96 stereo: 11 22 44 96 - * 5: Speakers (USB PnP Sound Device) mono: 11 22 44 96 stereo: 11 22 44 96 - * - * - * History: Removed in version 0.9. - * - * Post Mortem discussion: - * - * It turns out to be quite bogus and perhaps deceiving. - * - * The chip (http://www.szlnst.com/Uploadfiles/HS100.pdf) in the cheap - * USB Audio device is physically capable of only 44.1 and 48 KHz - * sampling rates. Input is mono only. Output is stereo only. - * There is discussion of this in the Raspberry Pi document. - * - * Here, it looks like it has much more general capabilities. - * It seems the audio system puts some virtual layer on top of - * it to provide resampling for different rates and silent - * right channel for stereo input. - * - * - *----------------------------------------------------------------*/ - -#if 0 -static void print_capabilities (DWORD formats) -{ - dw_printf (" mono:"); - dw_printf ("%s", (formats & WAVE_FORMAT_1M16) ? " 11" : " "); - dw_printf ("%s", (formats & WAVE_FORMAT_2M16) ? " 22" : " "); - dw_printf ("%s", (formats & WAVE_FORMAT_4M16) ? " 44" : " "); - dw_printf ("%s", (formats & WAVE_FORMAT_96M16) ? " 96" : " "); - - dw_printf (" stereo:"); - dw_printf ("%s", (formats & WAVE_FORMAT_1S16) ? " 11" : " "); - dw_printf ("%s", (formats & WAVE_FORMAT_2S16) ? " 22" : " "); - dw_printf ("%s", (formats & WAVE_FORMAT_4S16) ? " 44" : " "); - dw_printf ("%s", (formats & WAVE_FORMAT_96S16) ? " 96" : " "); -} -#endif + 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]; /*------------------------------------------------------------------ @@ -242,160 +219,223 @@ static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DW int audio_open (struct audio_s *pa) { + int a; + int err; int chan; int n; - int in_dev_no; - int out_dev_no; + int in_dev_no[MAX_ADEVS]; + int out_dev_no[MAX_ADEVS]; - WAVEFORMATEX wf; int num_devices; WAVEINCAPS wic; WAVEOUTCAPS woc; - assert (audio_in_handle == 0); - assert (audio_out_handle == 0); + save_audio_config_p = pa; + + + for (a=0; aadev[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 -> num_channels == 0) - pa -> num_channels = DEFAULT_NUM_CHANNELS; + if (pa -> adev[a].num_channels == 0) + pa -> adev[a].num_channels = DEFAULT_NUM_CHANNELS; - if (pa -> samples_per_sec == 0) - pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + if (pa -> adev[a].samples_per_sec == 0) + pa -> adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; - if (pa -> bits_per_sample == 0) - pa -> bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + if (pa -> adev[a].bits_per_sample == 0) + pa -> adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; - for (chan=0; chan mark_freq[chan] == 0) - pa -> mark_freq[chan] = DEFAULT_MARK_FREQ; + A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; - if (pa -> space_freq[chan] == 0) - pa -> space_freq[chan] = DEFAULT_SPACE_FREQ; + for (chan=0; chan achan[chan].mark_freq == 0) + pa -> achan[chan].mark_freq = DEFAULT_MARK_FREQ; - if (pa -> baud[chan] == 0) - pa -> baud[chan] = DEFAULT_BAUD; + if (pa -> achan[chan].space_freq == 0) + pa -> achan[chan].space_freq = DEFAULT_SPACE_FREQ; - if (pa->num_subchan[chan] == 0) - pa->num_subchan[chan] = 1; - } + if (pa -> achan[chan].baud == 0) + pa -> achan[chan].baud = DEFAULT_BAUD; - wf.wFormatTag = WAVE_FORMAT_PCM; - wf.nChannels = pa -> num_channels; - wf.nSamplesPerSec = pa -> samples_per_sec; - wf.wBitsPerSample = pa -> bits_per_sample; - wf.nBlockAlign = (wf.wBitsPerSample / 8) * wf.nChannels; - wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; - wf.cbSize = 0; - - outbuf_size = calcbufsize(wf.nSamplesPerSec,wf.nChannels,wf.wBitsPerSample); + if (pa->achan[chan].num_subchan == 0) + pa->achan[chan].num_subchan = 1; + } - udp_sock = INVALID_SOCKET; + A->udp_sock = INVALID_SOCKET; - in_dev_no = WAVE_MAPPER; /* = -1 */ - out_dev_no = WAVE_MAPPER; + 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->adevice_in, "stdin") == 0 || strcmp(pa->adevice_in, "-") == 0) { - audio_in_type = AUDIO_IN_TYPE_STDIN; - /* Change - to stdin for readability. */ - strcpy (pa->adevice_in, "stdin"); - } - else if (strncasecmp(pa->adevice_in, "udp:", 4) == 0) { - audio_in_type = AUDIO_IN_TYPE_SDR_UDP; - /* Supply default port if none specified. */ - if (strcasecmp(pa->adevice_in,"udp") == 0 || - strcasecmp(pa->adevice_in,"udp:") == 0) { - sprintf (pa->adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT); - } - } - else { - audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; + 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. */ + /* Does config file have a number? */ + /* If so, it is an index into list of devices. */ - if (strlen(pa->adevice_in) == 1 && isdigit(pa->adevice_in[0])) { - in_dev_no = atoi(pa->adevice_in); - } + 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? */ + /* Otherwise, does it have search string? */ - if (in_dev_no == WAVE_MAPPER && strlen(pa->adevice_in) >= 1) { - num_devices = waveInGetNumDevs(); - for (n=0 ; nadevice_in) != NULL) { - in_dev_no = n; + if (in_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) { + num_devices = waveInGetNumDevs(); + for (n=0 ; nadev[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); } } - } - if (in_dev_no == WAVE_MAPPER) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adevice_in); - } - } - } + } /* * Select output device. + * Only soundcard at this point. + * Purhaps we'd like to add UDP for an SDR transmitter. */ - if (strlen(pa->adevice_out) == 1 && isdigit(pa->adevice_out[0])) { - out_dev_no = atoi(pa->adevice_out); - } + 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 == WAVE_MAPPER && strlen(pa->adevice_out) >= 1) { - num_devices = waveOutGetNumDevs(); - for (n=0 ; nadevice_out) != NULL) { - out_dev_no = n; + if (out_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { + num_devices = waveOutGetNumDevs(); + for (n=0 ; nadev[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 (out_dev_no == WAVE_MAPPER) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adevice_out); - } - } - + } /* if defined */ + } /* for each device */ + /* - * Display what is available and anything selected. + * 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(); - if (in_dev_no < -1 || in_dev_no >= num_devices) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Invalid input (receive) audio device number %d.\n", in_dev_no); - in_dev_no = WAVE_MAPPER; - } + + for (a=0; aadev[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; nadev[a].defined) { + dw_printf (" %c", n==in_dev_no[a] ? '*' : ' '); - /* Display stdin or udp:port if appropriate. */ + } + } + dw_printf (" %d: %s", n, wic.szPname); - if (audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) { - dw_printf ("* %s\n", pa->adevice_in); - } + for (a=0; aadev[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; aadev[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; aaaadev[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"); @@ -405,164 +445,218 @@ int audio_open (struct audio_s *pa) /* Which is the default? The first one? */ num_devices = waveOutGetNumDevs(); - if (out_dev_no < -1 || out_dev_no >= num_devices) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Invalid output (transmit) audio device number %d.\n", out_dev_no); - out_dev_no = WAVE_MAPPER; - } - text_color_set(DW_COLOR_INFO); - for (n=0; nadev[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; + } } } - err = waveOutOpen (&audio_out_handle, out_dev_no, &wf, (DWORD_PTR)out_callback, 0, CALLBACK_FUNCTION); - if (err != MMSYSERR_NOERROR) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device for output.\n"); - return (-1); - } + text_color_set(DW_COLOR_INFO); + for (n=0; nadev[a].defined) { + dw_printf (" %c", n==out_dev_no[a] ? '*' : ' '); + + } + } + dw_printf (" %d: %s", n, woc.szPname); + + for (a=0; aadev[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; aadev[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*)out_wavehdr, 0, sizeof(out_wavehdr)); + memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr)); - for (n = 0; n < NUM_OUT_BUF; n++) { - out_wavehdr[n].lpData = malloc(outbuf_size); - out_wavehdr[n].dwUser = DWU_FILLING; - out_wavehdr[n].dwBufferLength = 0; - } - out_current = 0; + 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 (audio_in_type) { + switch (A->g_audio_in_type) { /* * Soundcard. */ - case AUDIO_IN_TYPE_SOUNDCARD: + case AUDIO_IN_TYPE_SOUNDCARD: - InitializeCriticalSection (&in_cs); + InitializeCriticalSection (&(A->in_cs)); - err = waveInOpen (&audio_in_handle, in_dev_no, &wf, (DWORD_PTR)in_callback, 0, CALLBACK_FUNCTION); - if (err != MMSYSERR_NOERROR) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device for input.\n"); - return (-1); - } + 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. - */ + /* + * Set up the input buffers. + */ - memset ((void*)in_wavehdr, 0, sizeof(in_wavehdr)); + memset ((void*)(A->in_wavehdr), 0, sizeof(A->in_wavehdr)); - for (n = 0; n < NUM_OUT_BUF; n++) { - in_wavehdr[n].dwBufferLength = outbuf_size; /* all the same size */ - in_wavehdr[n].lpData = malloc(outbuf_size); - } - in_headp = NULL; + 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. - */ + /* + * Give them to the sound input system. + */ - for (n = 0; n < NUM_OUT_BUF; n++) { - waveInPrepareHeader(audio_in_handle, &(in_wavehdr[n]), sizeof(WAVEHDR)); - waveInAddBuffer(audio_in_handle, &(in_wavehdr[n]), sizeof(WAVEHDR)); - } + 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. - */ + /* + * Start it up. + * The callback function is called when one is filled. + */ - waveInStart (audio_in_handle); - break; + waveInStart (A->audio_in_handle); + break; /* * UDP. */ - case AUDIO_IN_TYPE_SDR_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; + { + 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); - } + 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); - } + 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 + // Create UDP Socket - udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (udp_sock == INVALID_SOCKET) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError()); - return -1; - } + 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->adevice_in + 4)); - si_me.sin_addr.s_addr = htonl(INADDR_ANY); + 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 + // Bind to the socket - if (bind(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; - } - stream_next= 0; - stream_len = 0; - } + 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; + break; /* * stdin. */ - case AUDIO_IN_TYPE_STDIN: + case AUDIO_IN_TYPE_STDIN: - setmode (STDIN_FILENO, _O_BINARY); - stream_next= 0; - stream_len = 0; + setmode (STDIN_FILENO, _O_BINARY); + A->stream_next= 0; + A->stream_len = 0; - break; + break; - default: + default: - text_color_set(DW_COLOR_ERROR); - dw_printf ("Internal error, invalid audio_in_type\n"); - return (-1); - } + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, invalid audio_in_type\n"); + return (-1); + } + + } + } return (0); @@ -576,6 +670,15 @@ int audio_open (struct audio_s *pa) 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; @@ -583,13 +686,13 @@ static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWOR p->dwUser = -1; /* needs to be unprepared. */ p->lpNext = NULL; - EnterCriticalSection (&in_cs); + EnterCriticalSection (&(A->in_cs)); - if (in_headp == NULL) { - in_headp = p; /* first one in list */ + if (A->in_headp == NULL) { + A->in_headp = p; /* first one in list */ } else { - WAVEHDR *last = (WAVEHDR*)in_headp; + WAVEHDR *last = (WAVEHDR*)(A->in_headp); while (last->lpNext != NULL) { last = last->lpNext; @@ -597,7 +700,7 @@ static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWOR last->lpNext = p; /* append to last one */ } - LeaveCriticalSection (&in_cs); + LeaveCriticalSection (&(A->in_cs)); } } @@ -625,6 +728,9 @@ static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DW * * 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. * @@ -638,23 +744,29 @@ static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DW // Use hot attribute for all functions called for every audio sample. __attribute__((hot)) -int audio_get (void) +int audio_get (int a) { + struct adev_s *A; + WAVEHDR *p; int n; int sample; -#if DEBUGUDP - /* Gather numbers for read from UDP stream. */ + A = &(adev[a]); - static int duration = 100; /* report every 100 seconds. */ - static time_t last_time = 0; - time_t this_time; - static int sample_count; - static int error_count; +#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 (audio_in_type) { + switch (A->g_audio_in_type) { /* * Soundcard. @@ -669,20 +781,24 @@ int audio_get (void) */ int timeout = 25; - while (in_headp == NULL) { - SLEEP_MS (ONE_BUF_TIME / 5); + 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); - dw_printf ("Audio input failure.\n"); + +// 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*)in_headp; /* no need to be volatile at this point */ + p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */ if (p->dwUser == -1) { - waveInUnprepareHeader(audio_in_handle, p, sizeof(WAVEHDR)); + waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); p->dwUser = 0; /* Index for next byte. */ } @@ -700,13 +816,13 @@ int audio_get (void) * Buffer is all used up. Give it back to sound input system. */ - EnterCriticalSection (&in_cs); - in_headp = p->lpNext; - LeaveCriticalSection (&in_cs); + EnterCriticalSection (&(A->in_cs)); + A->in_headp = p->lpNext; + LeaveCriticalSection (&(A->in_cs)); p->dwFlags = 0; - waveInPrepareHeader(audio_in_handle, p, sizeof(WAVEHDR)); - waveInAddBuffer(audio_in_handle, p, sizeof(WAVEHDR)); + waveInPrepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); + waveInAddBuffer(A->audio_in_handle, p, sizeof(WAVEHDR)); } break; /* @@ -714,49 +830,52 @@ int audio_get (void) */ case AUDIO_IN_TYPE_SDR_UDP: - while (stream_next >= stream_len) { + while (A->stream_next >= A->stream_len) { int res; - assert (udp_sock > 0); + assert (A->udp_sock > 0); - res = recv (udp_sock, stream_data, SDR_UDP_BUF_MAXLEN, 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()); - stream_len = 0; - stream_next = 0; + A->stream_len = 0; + A->stream_next = 0; return (-1); } #if DEBUGUDP - if (last_time == 0) { - last_time = time(NULL); - sample_count = 0; - error_count = 0; + +// 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 += res/2; + sample_count[a] += res/2; } else { - error_count++; + error_count[a]++; } - this_time = time(NULL); - if (this_time >= last_time + duration) { + this_time[a] = time(NULL); + if (this_time[a] >= last_time + duration) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("\nPast %d seconds, %d audio samples processed, %d errors.\n\n", - duration, sample_count, error_count); - last_time = this_time; - sample_count = 0; - error_count = 0; + 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 - stream_len = res; - stream_next = 0; + A->stream_len = res; + A->stream_next = 0; } - sample = stream_data[stream_next] & 0xff; - stream_next++; + sample = A->stream_data[A->stream_next] & 0xff; + A->stream_next++; return (sample); break; /* @@ -764,20 +883,20 @@ int audio_get (void) */ case AUDIO_IN_TYPE_STDIN: - while (stream_next >= stream_len) { + while (A->stream_next >= A->stream_len) { int res; - res = read(STDIN_FILENO, stream_data, 1024); + 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); } - stream_len = res; - stream_next = 0; + A->stream_len = res; + A->stream_next = 0; } - return (stream_data[stream_next++] & 0xff); + return (A->stream_data[A->stream_next++] & 0xff); break; } @@ -792,7 +911,9 @@ int audio_get (void) * * Purpose: Send one byte to the audio device. * - * Inputs: c - One byte in range of 0 - 255. + * 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. @@ -808,9 +929,12 @@ int audio_get (void) * *----------------------------------------------------------------*/ -int audio_put (int c) +int audio_put (int a, int c) { WAVEHDR *p; + + struct adev_s *A; + A = &(adev[a]); /* * Wait if no buffers are available. @@ -818,7 +942,7 @@ int audio_put (int c) */ int timeout = 10; - while ( out_wavehdr[out_current].dwUser == DWU_PLAYING) { + while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) { SLEEP_MS (ONE_BUF_TIME); timeout--; if (timeout <= 0) { @@ -829,10 +953,10 @@ int audio_put (int c) } } - p = (LPWAVEHDR)(&(out_wavehdr[out_current])); + p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); if (p->dwUser == DWU_DONE) { - waveOutUnprepareHeader (audio_out_handle, p, sizeof(WAVEHDR)); + waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR)); p->dwBufferLength = 0; p->dwUser = DWU_FILLING; } @@ -840,12 +964,12 @@ int audio_put (int c) /* Should never be full at this point. */ assert (p->dwBufferLength >= 0); - assert (p->dwBufferLength < outbuf_size); + assert (p->dwBufferLength < A->outbuf_size); p->lpData[p->dwBufferLength++] = c; - if (p->dwBufferLength == outbuf_size) { - return (audio_flush()); + if (p->dwBufferLength == A->outbuf_size) { + return (audio_flush(a)); } return (0); @@ -859,6 +983,8 @@ int audio_put (int c) * * 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. * @@ -867,21 +993,23 @@ int audio_put (int c) * *----------------------------------------------------------------*/ -int audio_flush (void) +int audio_flush (int a) { WAVEHDR *p; MMRESULT e; + struct adev_s *A; + + A = &(adev[a]); - - p = (LPWAVEHDR)(&(out_wavehdr[out_current])); + p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { p->dwUser = DWU_PLAYING; - waveOutPrepareHeader(audio_out_handle, p, sizeof(WAVEHDR)); + waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR)); - e = waveOutWrite(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); @@ -892,7 +1020,7 @@ int audio_flush (void) p->dwUser = DWU_DONE; return (-1); } - out_current = (out_current + 1) % NUM_OUT_BUF; + A->out_current = (A->out_current + 1) % NUM_OUT_BUF; } return (0); @@ -903,67 +1031,42 @@ int audio_flush (void) * * Name: audio_wait * - * Purpose: Wait until all the queued up audio out has been played. + * Purpose: Finish up audio output before turning PTT off. * - * Inputs: duration - hint at number of milliseconds to wait. + * Inputs: a - Index for audio device (not channel!) * - * Returns: Normally non-negative. - * -1 for any type of error. + * Returns: None. * - * Description: In our particular application, we want to make sure - * that the entire packet has been sent out before turning - * off the transmitter PTT control. + * 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. * - * The original implementation (on Cygwin) tried using: - * - * ioctl (audio_device_fd, SNDCTL_DSP_SYNC, NULL); - * - * but this caused the application to crash at a later time. - * - * This might be revisited someday for the Windows version, - * but for now, we continue to use the work-around because it - * works fine. - * * 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) Add (1) and (2) resulting in when PTT should be turned off. - * (4) Take difference between current time and PPT off time - * and provide this as the additional delay required. + * (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. * *----------------------------------------------------------------*/ -int audio_wait (int duration) +void audio_wait (int a) { - int err = 0; - audio_flush (); -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_wait(): before sync, fd=%d\n", audio_device_fd); -#endif - - - if (duration > 0) { - SLEEP_MS (duration); - } - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_wait(): after sync, status=%d\n", err); -#endif - - return (err); + audio_flush (a); } /* end audio_wait */ @@ -972,7 +1075,8 @@ int audio_wait (int duration) * * Name: audio_close * - * Purpose: Close the audio device. + * + * Purpose: Close all of the audio devices. * * Returns: Normally non-negative. * -1 for any type of error. @@ -986,55 +1090,66 @@ int audio_close (void) int n; + int a; - assert (audio_in_handle != 0); - assert (audio_out_handle != 0); + for (a=0; aadev[a].defined) { - audio_wait (0); + 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(audio_in_handle); - waveInStop(audio_in_handle); - waveInClose(audio_in_handle); - audio_in_handle = 0; + 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++) { + for (n = 0; n < NUM_IN_BUF; n++) { - waveInUnprepareHeader (audio_in_handle, (LPWAVEHDR)(&(in_wavehdr[n])), sizeof(WAVEHDR)); - in_wavehdr[n].dwFlags = 0; - free (in_wavehdr[n].lpData); - in_wavehdr[n].lpData = NULL; - } + 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 (&in_cs); + DeleteCriticalSection (&(A->in_cs)); /* Make sure all output buffers have been played then free them. */ - for (n = 0; n < NUM_OUT_BUF; n++) { - if (out_wavehdr[n].dwUser == DWU_PLAYING) { + for (n = 0; n < NUM_OUT_BUF; n++) { + if (A->out_wavehdr[n].dwUser == DWU_PLAYING) { - int timeout = 2 * NUM_OUT_BUF; - while (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"); + 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; } - waveOutUnprepareHeader (audio_out_handle, (LPWAVEHDR)(&(out_wavehdr[n])), sizeof(WAVEHDR)); + waveOutClose (A->audio_out_handle); + A->audio_out_handle = 0; - out_wavehdr[n].dwUser = DWU_DONE; - } - free (out_wavehdr[n].lpData); - out_wavehdr[n].lpData = NULL; - } + } /* if device configured */ + } /* for each device. */ - waveOutClose (audio_out_handle); - audio_out_handle = 0; + /* Not right. always returns 0 but at this point, doesn't matter. */ return (err); diff --git a/ax25_pad.c b/ax25_pad.c index 40609eb..ac7efbf 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011 , 2013, 2014 John Langner, WB2OSZ +// 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 @@ -157,12 +157,38 @@ char *strtok_r(char *str, const char *delim, char **saveptr); /* * Accumulate statistics. - * If new_count gets more than a few larger than delete_count plus the size of + * If new_count gets much larger than delete_count plus the size of * the transmit queue we have a memory leak. */ -static int new_count = 0; -static int delete_count = 0; +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 @@ -185,11 +211,12 @@ static packet_t ax25_new (void) struct packet_s *this_p; -#if DEBUG +#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++; /* @@ -197,11 +224,16 @@ static packet_t ax25_new (void) */ if (new_count > delete_count + 100) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count); + 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); @@ -215,16 +247,33 @@ static packet_t ax25_new (void) * *------------------------------------------------------------------------------*/ +#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); - memset (this_p, 0, sizeof (struct packet_s)); - delete_count++; + + this_p->magic1 = 0; + this_p->magic1 = 0; + + //memset (this_p, 0, sizeof (struct packet_s)); free (this_p); } @@ -255,7 +304,11 @@ void ax25_delete (packet_t this_p) * *------------------------------------------------------------------------------*/ +#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 { /* @@ -278,9 +331,16 @@ packet_t ax25_from_text (char *monitor, int strict) 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. */ @@ -488,11 +548,13 @@ packet_t ax25_from_text (char *monitor, int strict) * *------------------------------------------------------------------------------*/ - -packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel) +#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; - //int found_last; packet_t this_p; int a; @@ -515,6 +577,7 @@ packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel) * */ + if (flen < AX25_MIN_PACKET_LEN || flen > AX25_MAX_PACKET_LEN) { text_color_set(DW_COLOR_ERROR); @@ -524,6 +587,13 @@ packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel) 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); @@ -553,15 +623,28 @@ packet_t ax25_from_frame (unsigned char *fbuf, int flen, int alevel) *------------------------------------------------------------------------------*/ +#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); @@ -1480,6 +1563,121 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_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 @@ -1519,6 +1717,8 @@ static void hex_dump (unsigned char *p, int len) /* 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); } @@ -1580,7 +1780,7 @@ void ax25_hex_dump (packet_t this_p) c = fptr[this_p->num_addr*7]; p = fptr[this_p->num_addr*7+1]; - ctrl_to_text (c, cp_text); + 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 */ @@ -1677,6 +1877,7 @@ int ax25_is_aprs (packet_t this_p) /*------------------------------------------------------------------ * * Function: ax25_get_control + ax25_get_c2 * * Purpose: Get Control field from packet. * @@ -1699,6 +1900,18 @@ int ax25_get_control (packet_t 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 @@ -1722,6 +1935,9 @@ 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)]); } @@ -1897,10 +2113,69 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) 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 */ diff --git a/ax25_pad.h b/ax25_pad.h index 12db481..0e5b133 100644 --- a/ax25_pad.h +++ b/ax25_pad.h @@ -73,6 +73,7 @@ struct packet_s { int magic1; /* for error checking. */ + int seq; /* unique sequence number for debugging. */ #define MAGIC 0x41583235 @@ -150,7 +151,7 @@ typedef struct packet_s *packet_t; * "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 purposes. + * We can assume 1 for our current purposes. */ static inline int ax25_get_control_offset (packet_t this_p) @@ -161,10 +162,11 @@ static inline int ax25_get_control_offset (packet_t this_p) static inline int ax25_get_num_control (packet_t this_p) { - return (1); + 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. @@ -196,14 +198,15 @@ static int ax25_get_num_pid (packet_t this_p) /* - * APRS always has an Information field with at least one octet for the - * Data Type Indicator. AX.25 has this for only 5 frame types depending - * on the control field. + * AX.25 has info field for 5 frame types depending on the control field. + * * xxxx xxx0 I - * 000x 0011 UI + * 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) @@ -228,20 +231,81 @@ static int ax25_get_num_info (packet_t this_p) #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 -//static packet_t ax25_new (void); +#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); -extern void ax25_clear (packet_t pp); +#endif -extern packet_t ax25_from_text (char *, int strict); - -extern packet_t ax25_from_frame (unsigned char *data, int len, int alevel); - -extern packet_t ax25_dup (packet_t copy_from); extern int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard); @@ -279,11 +343,14 @@ 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); @@ -293,6 +360,8 @@ 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 */ diff --git a/beacon.c b/beacon.c index 92e75a8..aebcf1b 100644 --- a/beacon.c +++ b/beacon.c @@ -5,7 +5,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2013,2014 John Langner, WB2OSZ +// 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 @@ -58,6 +58,7 @@ #include "latlong.h" #include "dwgps.h" #include "log.h" +#include "dlq.h" @@ -73,8 +74,9 @@ static int g_using_gps = 0; * Save pointers to configuration settings. */ -static struct misc_config_s *g_misc_config_p; -static struct digi_config_s *g_digi_config_p; +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; @@ -102,9 +104,11 @@ void beacon_tracker_set_debug (int level) * * Purpose: Initialize the beacon process. * - * Inputs: pconfig - misc. configuration from config file. + * 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. - * Use to obtain "mycall" for each channel. + * TODO: Is this needed? * * * Outputs: Remember required information for future use. @@ -119,7 +123,7 @@ void beacon_tracker_set_debug (int level) -void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi) +void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct digi_config_s *pdigi) { time_t now; int j; @@ -142,6 +146,7 @@ void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi) /* * Save parameters for later use. */ + g_modem_config_p = pmodem; g_misc_config_p = pconfig; g_digi_config_p = pdigi; @@ -156,9 +161,9 @@ void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi) if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ - if (chan < pdigi->num_chans) { + if (g_modem_config_p->achan[chan].valid) { - if (strlen(pdigi->mycall[chan]) > 0 && strcasecmp(pdigi->mycall[chan], "NOCALL") != 0) { + 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) { @@ -239,7 +244,7 @@ void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi) text_color_set(DW_COLOR_DEBUG); dw_printf ("beacon[%d] chan=%d, delay=%d, every=%d\n", j, - g_misc_config_p->beacon[j].chan, + g_misc_config_p->beacon[j].sendto_chan, g_misc_config_p->beacon[j].delay, g_misc_config_p->beacon[j].every); #endif @@ -587,7 +592,7 @@ static void * beacon_thread (void *arg) assert (g_misc_config_p->beacon[j].sendto_chan >= 0); - strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].sendto_chan]); + 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); @@ -694,6 +699,7 @@ static void * beacon_thread (void *arg) if (g_tracker_debug_level >= 3) { decode_aprs_t A; + alevel_t alevel; memset (&A, 0, sizeof(A)); A.g_freq = G_UNKNOWN; @@ -711,7 +717,8 @@ static void * beacon_thread (void *arg) A.g_altitude = my_alt_ft; /* Fake channel of 999 to distinguish from real data. */ - log_write (999, &A, NULL, 0, 0); + memset (&alevel, 0, sizeof(alevel)); + log_write (999, &A, NULL, alevel, 0); } } else { @@ -748,6 +755,9 @@ static void * beacon_thread (void *arg) /* Send to desired destination. */ + alevel_t alevel; + + switch (g_misc_config_p->beacon[j].sendto_type) { case SENDTO_IGATE: @@ -769,9 +779,10 @@ static void * beacon_thread (void *arg) case SENDTO_RECV: - // TODO: Put into receive queue rather than calling directly. + /* Simulated reception. */ - app_process_rec_packet (g_misc_config_p->beacon[j].sendto_chan, 0, pp, -1, 0, ""); + memset (&alevel, 0xff, sizeof(alevel)); + dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, pp, alevel, 0, ""); break; } } diff --git a/beacon.h b/beacon.h index a94e6aa..3e02873 100644 --- a/beacon.h +++ b/beacon.h @@ -1,6 +1,6 @@ /* beacon.h */ -void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi); +void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct digi_config_s *pdigi); void beacon_tracker_set_debug (int level); diff --git a/config.c b/config.c index 11f42b0..d0ebee6 100644 --- a/config.c +++ b/config.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ +// 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 @@ -38,6 +38,7 @@ #include #include #include +#include #if __WIN32__ #include "pthreads/pthread.h" @@ -54,7 +55,18 @@ #include "igate.h" #include "latlong.h" #include "symbols.h" -#include "LatLong-UTMconversion.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" @@ -122,8 +134,33 @@ static const struct units_s { #define NUM_UNITS (sizeof(units) / sizeof(struct units_s)) -static int beacon_options(char *cmd, struct beacon_s *b, int line); +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); +} /*------------------------------------------------------------------ * @@ -247,6 +284,83 @@ static double parse_ll (char *str, enum parse_ll_which_e which, int line) 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 () { @@ -338,7 +452,7 @@ static int parse_interval (char *str, int line) * * Inputs: fname - Name of configuration file. * - * Outputs: p_modem - Radio channel parameters stored here. + * Outputs: p_audio_config - Radio channel parameters stored here. * * p_digi_config - Digipeater configuration stored here. * @@ -361,7 +475,7 @@ static int parse_interval (char *str, int line) *--------------------------------------------------------------------*/ -void config_init (char *fname, struct audio_s *p_modem, +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, @@ -374,6 +488,7 @@ void config_init (char *fname, struct audio_s *p_modem, //int err; int line; int channel; + int adevice; #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -384,52 +499,75 @@ void config_init (char *fname, struct audio_s *p_modem, * First apply defaults. */ - memset (p_modem, 0, sizeof(struct audio_s)); + memset (p_audio_config, 0, sizeof(struct audio_s)); - strcpy (p_modem->adevice_in, DEFAULT_ADEVICE); - strcpy (p_modem->adevice_out, DEFAULT_ADEVICE); + /* First audio device is always available with defaults. */ + /* Others must be explicitly defined before use. */ - p_modem->num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ - p_modem->samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ - p_modem->bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */ - p_modem->fix_bits = DEFAULT_FIX_BITS; + for (adevice=0; adeviceadev[adevice].adevice_in, DEFAULT_ADEVICE); + strcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE); - p_modem->modem_type[channel] = AFSK; - p_modem->mark_freq[channel] = DEFAULT_MARK_FREQ; /* -m option */ - p_modem->space_freq[channel] = DEFAULT_SPACE_FREQ; /* -s option */ - p_modem->baud[channel] = DEFAULT_BAUD; /* -b option */ - - /* None. Will set default later based on other factors. */ - strcpy (p_modem->profiles[channel], ""); - - p_modem->num_freq[channel] = 1; - p_modem->num_subchan[channel] = 1; - p_modem->offset[channel] = 0; - - // temp test. - // p_modem->num_subchan[channel] = 9; - // p_modem->offset[channel] = 60; - - p_modem->ptt_method[channel] = PTT_METHOD_NONE; - strcpy (p_modem->ptt_device[channel], ""); - p_modem->ptt_line[channel] = PTT_LINE_RTS; - p_modem->ptt_gpio[channel] = 0; - p_modem->ptt_lpt_bit[channel] = 0; - p_modem->ptt_invert[channel] = 0; - - p_modem->slottime[channel] = DEFAULT_SLOTTIME; - p_modem->persist[channel] = DEFAULT_PERSIST; - p_modem->txdelay[channel] = DEFAULT_TXDELAY; - p_modem->txtail[channel] = DEFAULT_TXTAIL; + 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; channelachan[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_modem->num_channels; + //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); @@ -450,7 +588,6 @@ void config_init (char *fname, struct audio_s *p_modem, p_tt_config->xmit_delay[6] = 8 * 60; memset (p_misc_config, 0, sizeof(struct misc_config_s)); - p_misc_config->num_channels = p_modem->num_channels; 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 */ @@ -493,6 +630,7 @@ void config_init (char *fname, struct audio_s *p_modem, channel = 0; + adevice = 0; fp = fopen (fname, "r"); #ifndef __WIN32__ @@ -540,24 +678,42 @@ void config_init (char *fname, struct audio_s *p_modem, */ /* - * ADEVICE - Name of input sound device, and optionally output, if different. + * 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 (strcasecmp(t, "ADEVICE") == 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; } - strncpy (p_modem->adevice_in, t, sizeof(p_modem->adevice_in)-1); - strncpy (p_modem->adevice_out, t, sizeof(p_modem->adevice_out)-1); + + 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_modem->adevice_out, t, sizeof(p_modem->adevice_out)-1); + strncpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)-1); } } @@ -575,7 +731,7 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= MIN_SAMPLES_PER_SEC && n <= MAX_SAMPLES_PER_SEC) { - p_modem->samples_per_sec = n; + p_audio_config->adev[adevice].samples_per_sec = n; } else { text_color_set(DW_COLOR_ERROR); @@ -585,7 +741,7 @@ void config_init (char *fname, struct audio_s *p_modem, } /* - * ACHANNELS - Number of audio channels: 1 or 2 + * ACHANNELS - Number of audio channels for current device: 1 or 2 */ else if (strcasecmp(t, "ACHANNELS") == 0) { @@ -597,10 +753,15 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } n = atoi(t); - if (n >= 1 && n <= MAX_CHANS) { - p_modem->num_channels = n; - p_digi_config->num_chans = p_modem->num_channels; - p_misc_config->num_channels = p_modem->num_channels; + 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); @@ -626,12 +787,26 @@ void config_init (char *fname, struct audio_s *p_modem, } 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: Audio channel number must be 0 or 1.\n", line); - channel = 0; + dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1); } } @@ -654,13 +829,16 @@ void config_init (char *fname, struct audio_s *p_modem, for (c = 0; c < MAX_CHANS; c++) { - if (c == channel || strlen(p_digi_config->mycall[c]) == 0 || strcasecmp(p_digi_config->mycall[c], "NOCALL") == 0) { + 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_digi_config->mycall[c], t, sizeof(p_digi_config->mycall[c])-1); + strncpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall)-1); - for (p = p_digi_config->mycall[c]; *p != '\0'; p++) { + for (p = p_audio_config->achan[c].mycall; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } @@ -673,9 +851,19 @@ void config_init (char *fname, struct audio_s *p_modem, /* - * MODEM - Replaces former HBAUD, MARK, SPACE, and adds new multi modem capability. + * MODEM - Set modem properties for current channel. * - * MODEM baud [ mark space [A][B][C] [ num-decoders spacing ] ] + * + * 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@offset - Multiple decoders on different frequencies. + * */ else if (strcasecmp(t, "MODEM") == 0) { @@ -683,146 +871,253 @@ void config_init (char *fname, struct audio_s *p_modem, t = strtok (NULL, " ,\t\n\r"); if (t == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Missing date transmission rate for MODEM command.\n", line); + dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line); continue; } n = atoi(t); if (n >= 100 && n <= 10000) { - p_modem->baud[channel] = n; + 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_modem->baud[channel] = DEFAULT_BAUD; + 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_modem->baud[channel]); + 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) { - text_color_set(DW_COLOR_INFO); - dw_printf ("Note: Using scrambled baseband rather than AFSK modem.\n"); - p_modem->modem_type[channel] = SCRAMBLE; - p_modem->mark_freq[channel] = 0; - p_modem->space_freq[channel] = 0; + /* all done. */ continue; } - 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_modem->mark_freq[channel] = n; - } - else { - p_modem->mark_freq[channel] = DEFAULT_MARK_FREQ; - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", - line, p_modem->mark_freq[channel]); - } - - /* Get space frequency */ + if (alldigits(t)) { - 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_modem->space_freq[channel] = n; - } - else { - p_modem->space_freq[channel] = DEFAULT_SPACE_FREQ; - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", - line, p_modem->space_freq[channel]); - } +/* old style */ - /* New feature in 0.9 - Optional filter profile(s). */ - - /* First, set a default based on platform and baud. */ - - if (p_modem->baud[channel] < 600) { - - /* "D" is a little better at 300 baud. */ - - strcpy (p_modem->profiles[channel], "D"); - } - else { -#if __arm__ - /* We probably don't have a lot of CPU power available. */ - - if (p_modem->baud[channel] == DEFAULT_BAUD && - p_modem->mark_freq[channel] == DEFAULT_MARK_FREQ && - p_modem->space_freq[channel] == DEFAULT_SPACE_FREQ && - p_modem->samples_per_sec == DEFAULT_SAMPLES_PER_SEC) { - - strcpy (p_modem->profiles[channel], "F"); + 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 { - strcpy (p_modem->profiles[channel], "A"); - } -#else - strcpy (p_modem->profiles[channel], "C"); -#endif - } - - t = strtok (NULL, " ,\t\n\r"); - if (t != NULL) { - if (isalpha(t[0])) { - // TODO: should check all letters. - strncpy (p_modem->profiles[channel], t, sizeof(p_modem->profiles[channel])); - p_modem->num_subchan[channel] = strlen(p_modem->profiles[channel]); - t = strtok (NULL, " ,\t\n\r"); - if (strlen(p_modem->profiles[channel]) > 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) { + p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Number of modems is out of range. Using 3.\n", line); - n = 3; + 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; } - p_modem->num_freq[channel] = n; - p_modem->num_subchan[channel] = n; + 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) { - n = atoi(t); - if (n < 5 || n > abs(p_modem->mark_freq[channel] - p_modem->space_freq[channel])/2) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable value for offset between modems. Using 50 Hz.\n", line); - n = 50; + + /* 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; + } } - p_modem->offset[channel] = n; - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Missing frequency offset between modems. Using 50 Hz.\n", line); - p_modem->offset[channel] = 50; } -// TODO: power saver + /* 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@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); + } } @@ -831,6 +1126,7 @@ void config_init (char *fname, struct audio_s *p_modem, * and 9600 for baseband with scrambling. */ +#if 0 else if (strcasecmp(t, "HBAUD") == 0) { int n; t = strtok (NULL, " ,\t\n\r"); @@ -841,30 +1137,32 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 100 && n <= 10000) { - p_modem->baud[channel] = n; + 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_modem->modem_type[channel] = SCRAMBLE; + //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_modem->baud[channel] = DEFAULT_BAUD; + 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_modem->baud[channel]); + 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"); @@ -875,20 +1173,22 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 300 && n <= 3000) { - p_modem->mark_freq[channel] = n; + p_audio_config->achan[channel].mark_freq = n; } else { - p_modem->mark_freq[channel] = DEFAULT_MARK_FREQ; + 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_modem->mark_freq[channel]); + 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"); @@ -899,31 +1199,118 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 300 && n <= 3000) { - p_modem->space_freq[channel] = n; + p_audio_config->achan[channel].space_freq = n; } else { - p_modem->space_freq[channel] = DEFAULT_SPACE_FREQ; + 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_modem->space_freq[channel]); + line, p_audio_config->achan[channel].space_freq); } } +#endif /* - * PTT - Push To Talk signal line. + * DTMF - Enable DTMF decoder. * - * PTT serial-port [-]rts-or-dtr - * PTT GPIO [-]gpio-num - * PTT LPT [-]bit-num + * 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, "PTT") == 0) { - //int n; + + 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 ("Config file line %d: Missing serial port name for PTT command.\n", - line); + 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; } @@ -933,24 +1320,24 @@ void config_init (char *fname, struct audio_s *p_modem, #if __WIN32__ text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file line %d: PTT with GPIO is only available on Linux.\n", line); + 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.\n", line); + dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, otname); continue; } if (*t == '-') { - p_modem->ptt_gpio[channel] = atoi(t+1); - p_modem->ptt_invert[channel] = 1; + p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t+1); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { - p_modem->ptt_gpio[channel] = atoi(t); - p_modem->ptt_invert[channel] = 0; + p_audio_config->achan[channel].octrl[ot].ptt_gpio = atoi(t); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } - p_modem->ptt_method[channel] = PTT_METHOD_GPIO; + p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; #endif } else if (strcasecmp(t, "LPT") == 0) { @@ -962,67 +1349,132 @@ void config_init (char *fname, struct audio_s *p_modem, t = strtok (NULL, " ,\t\n\r"); if (t == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file line %d: Missing LPT bit number.\n", line); + dw_printf ("Config file line %d: Missing LPT bit number for %s.\n", line, otname); continue; } if (*t == '-') { - p_modem->ptt_lpt_bit[channel] = atoi(t+1); - p_modem->ptt_invert[channel] = 1; + 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_modem->ptt_lpt_bit[channel] = atoi(t); - p_modem->ptt_invert[channel] = 0; + p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } - p_modem->ptt_method[channel] = PTT_METHOD_LPT; + 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: PTT with LPT is only available on x86 Linux.\n", line); + 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_modem->ptt_device[channel], t, sizeof(p_modem->ptt_device[channel])); + 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 PTT device name.\n", - line); + dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n", + line, otname); continue; } if (strcasecmp(t, "rts") == 0) { - p_modem->ptt_line[channel] = PTT_LINE_RTS; - p_modem->ptt_invert[channel] = 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_modem->ptt_line[channel] = PTT_LINE_DTR; - p_modem->ptt_invert[channel] = 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_modem->ptt_line[channel] = PTT_LINE_RTS; - p_modem->ptt_invert[channel] = 1; + 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_modem->ptt_line[channel] = PTT_LINE_DTR; - p_modem->ptt_invert[channel] = 1; + 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 PTT device name.\n", - line); + dw_printf ("Config file line %d: Expected RTS or DTR after %s device name.\n", + line, otname); continue; } - p_modem->ptt_method[channel] = PTT_METHOD_SERIAL; + + 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. */ @@ -1037,13 +1489,13 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 0 && n <= 255) { - p_modem->slottime[channel] = n; + p_audio_config->achan[channel].slottime = n; } else { - p_modem->slottime[channel] = DEFAULT_SLOTTIME; + 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_modem->slottime[channel]); + line, p_audio_config->achan[channel].slottime); } } @@ -1061,13 +1513,13 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 0 && n <= 255) { - p_modem->persist[channel] = n; + p_audio_config->achan[channel].persist = n; } else { - p_modem->persist[channel] = DEFAULT_PERSIST; + 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_modem->persist[channel]); + line, p_audio_config->achan[channel].persist); } } @@ -1085,13 +1537,13 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 0 && n <= 255) { - p_modem->txdelay[channel] = n; + p_audio_config->achan[channel].txdelay = n; } else { - p_modem->txdelay[channel] = DEFAULT_TXDELAY; + 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_modem->txdelay[channel]); + line, p_audio_config->achan[channel].txdelay); } } @@ -1109,18 +1561,49 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); if (n >= 0 && n <= 255) { - p_modem->txtail[channel] = n; + p_audio_config->achan[channel].txtail = n; } else { - p_modem->txtail[channel] = DEFAULT_TXTAIL; + 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_modem->txtail[channel]); + 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) { @@ -1136,10 +1619,16 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } from_chan = atoi(t); - if (from_chan < 0 || from_chan > p_digi_config->num_chans-1) { + 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", - p_digi_config->num_chans-1, line); + 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; } @@ -1150,10 +1639,16 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } to_chan = atoi(t); - if (to_chan < 0 || to_chan > p_digi_config->num_chans-1) { + 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", - p_digi_config->num_chans-1, line); + 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; } @@ -1194,21 +1689,25 @@ void config_init (char *fname, struct audio_s *p_modem, 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"); } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: Expected OFF, DROP, MARK, or TRACE on line %d.\n", line); - } + } + 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); } } @@ -1242,8 +1741,8 @@ void config_init (char *fname, struct audio_s *p_modem, else if (strcasecmp(t, "regen") == 0) { int from_chan, to_chan; - //int e; - //char message[100]; + int e; + char message[100]; t = strtok (NULL, " ,\t\n\r"); @@ -1253,10 +1752,16 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } from_chan = atoi(t); - if (from_chan < 0 || from_chan > p_digi_config->num_chans-1) { + 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", - p_digi_config->num_chans-1, line); + 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; } @@ -1267,10 +1772,16 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } to_chan = atoi(t); - if (to_chan < 0 || to_chan > p_digi_config->num_chans-1) { + 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", - p_digi_config->num_chans-1, line); + 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; } @@ -1279,6 +1790,87 @@ void config_init (char *fname, struct audio_s *p_modem, } + +/* + * ==================== 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 ==================== */ @@ -1626,6 +2218,9 @@ void config_init (char *fname, struct audio_s *p_modem, 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); @@ -1641,7 +2236,7 @@ void config_init (char *fname, struct audio_s *p_modem, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_UTM; strcpy(tl->pattern, ""); - strcpy(tl->utm.zone, ""); + tl->utm.lzone = 0; tl->utm.scale = 1; tl->utm.x_offset = 0; tl->utm.y_offset = 0; @@ -1652,6 +2247,7 @@ void config_init (char *fname, struct audio_s *p_modem, 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); @@ -1659,11 +2255,14 @@ void config_init (char *fname, struct audio_s *p_modem, 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; jttloc_len--; continue; } - memset (tl->utm.zone, 0, sizeof (tl->utm.zone)); - strncpy (tl->utm.zone, t, sizeof (tl->utm.zone) - 1); - znum = strtoul(tl->utm.zone, &zlet, 10); + tl->utm.lzone = parse_utm_zone (t, &(tl->utm.hemi)); - if (znum < 1 || znum > 60) { + /* 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: Zone number is out of range.\n\n", line); - continue; + 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 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; } - if (*zlet != '\0' && strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) { + /* Should be the end. */ + + t = strtok (NULL, " ,\t\n\r"); + if (t != NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n", line); - continue; - } + dw_printf ("Line %d: Unexpected stuff at end ignored: %s\n", line, t); + } + } - /* Optional scale. */ + +/* + * 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; } - tl->utm.scale = atof(t); + strcpy (tl->pattern, t); - /* Optional x offset. */ - - t = strtok (NULL, " ,\t\n\r"); - if (t == NULL) { + 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; } - tl->utm.x_offset = atof(t); - /* Optional y offset. */ + /* Optionally one of 0-9ABCD */ - t = strtok (NULL, " ,\t\n\r"); - if (t == NULL) { + 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; } - tl->utm.y_offset = atof(t); + + /* temp debugging */ + + //for (j=0; jttloc_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) { @@ -1746,6 +2545,7 @@ void config_init (char *fname, struct audio_s *p_modem, /* 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) { @@ -1758,16 +2558,20 @@ void config_init (char *fname, struct audio_s *p_modem, p_count[0] = p_count[1] = p_count[2] = 0; for (j=0; 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. */ @@ -1812,15 +2616,33 @@ void config_init (char *fname, struct audio_s *p_modem, /* * TTOBJ - TT Object Report options. * - * TTOBJ xmit-chan header + * TTOBJ recv-chan xmit-chan [ via-path ] */ -// TODO: header can be generated automatically. Should not be in config file. - - else if (strcasecmp(t, "TTOBJ") == 0) { - int n; + 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) { @@ -1829,25 +2651,35 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } - n = atoi(t); - if (n < 0 || n > p_digi_config->num_chans-1) { + 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", - p_digi_config->num_chans-1, line); + MAX_CHANS-1, line); continue; } - p_tt_config->obj_xmit_chan = n; + 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) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Missing object header for TTOBJ command.\n", line); - continue; - } - // TODO: Should do some validity checking. + if (t != NULL) { - strncpy (p_tt_config->obj_xmit_header, t, sizeof(p_tt_config->obj_xmit_header)); - + // TODO: Should do some validity checking. + strncpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via)); + } } /* @@ -1950,10 +2782,10 @@ void config_init (char *fname, struct audio_s *p_modem, } n = atoi(t); - if (n < 0 || n > p_digi_config->num_chans-1) { + 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", - p_digi_config->num_chans-1, line); + MAX_CHANS-1, line); continue; } p_igate_config->tx_chan = n; @@ -2040,6 +2872,8 @@ void config_init (char *fname, struct audio_s *p_modem, /* * AGWPORT - Port number for "AGW TCPIP Socket Interface" + * + * In version 1.2 we allow 0 to disable listening. */ else if (strcasecmp(t, "AGWPORT") == 0) { @@ -2051,7 +2885,7 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } n = atoi(t); - if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { + if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->agwpe_port = n; } else { @@ -2075,7 +2909,7 @@ void config_init (char *fname, struct audio_s *p_modem, continue; } n = atoi(t); - if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { + if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->kiss_port = n; } else { @@ -2131,30 +2965,6 @@ void config_init (char *fname, struct audio_s *p_modem, } } -/* - * FIX_BITS - 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_modem->fix_bits = (retry_t)n; - } - else { - p_modem->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_modem->fix_bits); - } - } - /* * BEACON channel delay every message * @@ -2217,7 +3027,7 @@ void config_init (char *fname, struct audio_s *p_modem, /* 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)) { + 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++; } } @@ -2238,7 +3048,7 @@ void config_init (char *fname, struct audio_s *p_modem, int n; -#define SB_NUM(name,sbvar,minn,maxx,unit) \ +#define SB_NUM(name,sbvar,minn,maxx,unit) \ t = strtok (NULL, " ,\t\n\r"); \ if (t == NULL) { \ text_color_set(DW_COLOR_ERROR); \ @@ -2312,18 +3122,24 @@ void config_init (char *fname, struct audio_s *p_modem, */ int i, j, k, b; - for (i=0; inum_chans; i++) { - for (j=0; jnum_chans; j++) { + for (i=0; ienabled[i][j]) { - if (strcmp(p_digi_config->mycall[i], "NOCALL") == 0) { + 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_digi_config->mycall[j], "NOCALL") == 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; @@ -2331,27 +3147,30 @@ void config_init (char *fname, struct audio_s *p_modem, b = 0; for (k=0; knum_beacons; k++) { - if (p_misc_config->beacon[p_misc_config->num_beacons].sendto_chan == j) b++; + 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", i); + 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; + //p_digi_config->enabled[i][j] = 0; } } } - if (strlen(p_igate_config->t2_login) > 0) { + if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) { - if (strcmp(p_digi_config->mycall[i], "NOCALL") == 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_digi_config->mycall[p_igate_config->tx_chan], "NOCALL") == 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; @@ -2368,7 +3187,7 @@ void config_init (char *fname, struct audio_s *p_modem, * Returns 1 for success, 0 for serious error. */ -static int beacon_options(char *cmd, struct beacon_s *b, int line) +static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config) { char options[1000]; char *o; @@ -2495,16 +3314,35 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line) 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 = atoi(value+1); + b->sendto_chan = n; } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { - b->sendto_type = SENDTO_XMIT; - b->sendto_chan = atoi(value+1); + 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 = atoi(value); + b->sendto_chan = n; } } else if (strcasecmp(keyword, "DEST") == 0) { @@ -2607,44 +3445,25 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line) if (strlen(zone) > 0 && easting != G_UNKNOWN && northing != G_UNKNOWN) { - int znum; - char *zlet; + long lzone; + char hemi; + long lerr; + double dlat, dlon; - znum = strtoul(zone, &zlet, 10); + lzone = parse_utm_zone (zone, &hemi); - if (znum >= 1 && znum <= 60) { + lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon); - //printf ("zlet = %c 0x%02x\n", *zlet, *zlet); - - if (*zlet == '\0' || strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) { - - if (easting >= 0 && easting <= 999999) { - - if (northing >= 0 && northing <= 9999999) { - - UTMtoLL (WSG84, northing, easting, zone, &b->lat, &b->lon); - - // printf ("config UTM debug: latitude = %.6f, longitude = %.6f\n", b->lat, b->lon); - - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file, line %d: Northing value is out of range.\n", line); - } - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file, line %d: Easting value is out of range.\n", line); - } - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file, line %d: Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n", line); - } + 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 ("Config file, line %d: UTM zone is out of range.\n", line); + dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message); } } else { @@ -2683,6 +3502,26 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line) } } +/* 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); } diff --git a/config.h b/config.h index 534555b..267c9fb 100644 --- a/config.h +++ b/config.h @@ -32,8 +32,6 @@ enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV }; struct misc_config_s { - int num_channels; /* Number of radio channels. */ - 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. */ diff --git a/decode_aprs.c b/decode_aprs.c index 7325314..618c808 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013,2014 John Langner, WB2OSZ +// 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 @@ -105,12 +105,12 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *, int); static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *, int); static void aprs_mic_e (decode_aprs_t *A, packet_t, unsigned char *, int); //static void aprs_compressed_pos (decode_aprs_t *A, unsigned char *, int); -static void aprs_message (decode_aprs_t *A, unsigned char *, int); +static void aprs_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); +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); @@ -119,8 +119,8 @@ static void aprs_ultimeter (decode_aprs_t *A, char *, int); static void third_party_header (decode_aprs_t *A, char *, int); static void decode_position (decode_aprs_t *A, position_t *ppos); static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *ppos); -static double get_latitude_8 (char *p); -static double get_longitude_9 (char *p); +static double get_latitude_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); @@ -138,6 +138,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen); * * 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, @@ -149,7 +151,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen); * *------------------------------------------------------------------*/ -void decode_aprs (decode_aprs_t *A, packet_t pp) +void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) { char dest[AX25_MAX_ADDR_LEN]; @@ -161,6 +163,8 @@ void decode_aprs (decode_aprs_t *A, packet_t pp) 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. */ @@ -252,7 +256,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp) case ':': /* Message */ - aprs_message (A, pinfo, info_len); + aprs_message (A, pinfo, info_len, quiet); break; case ';': /* Object */ @@ -274,7 +278,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp) //break; case 'T': /* Telemetry */ - aprs_telemetry (A, (char*)pinfo, info_len); + aprs_telemetry (A, (char*)pinfo, info_len, quiet); break; case '_': /* Positionless Weather Report */ @@ -435,33 +439,16 @@ void decode_aprs_print (decode_aprs_t *A) { * * Any example was checked for each hemihemisphere using * http://www.amsat.org/cgi-bin/gridconv - * - * Bug: This does not check for invalid values. */ if (strlen(A->g_maidenhead) > 0) { - dw_printf("Grid square = %s, ", A->g_maidenhead); if (A->g_lat == G_UNKNOWN && A->g_lon == G_UNKNOWN) { - - A->g_lon = (toupper(A->g_maidenhead[0]) - 'A') * 20 - 180; - A->g_lat = (toupper(A->g_maidenhead[1]) - 'A') * 10 - 90; - A->g_lon += (A->g_maidenhead[2] - '0') * 2; - A->g_lat += (A->g_maidenhead[3] - '0'); - - if (strlen(A->g_maidenhead) >=6) { - A->g_lon += (toupper(A->g_maidenhead[4]) - 'A') * 5.0 / 60.0; - A->g_lat += (toupper(A->g_maidenhead[5]) - 'A') * 2.5 / 60.0; - - A->g_lon += 2.5 / 60.0; /* Move from corner to center of square */ - A->g_lat += 1.25 / 60.0; - } - else { - A->g_lon += 1.0; /* Move from corner to center of square */ - A->g_lat += 0.5; - } + ll_from_grid_square (A->g_maidenhead, &(A->g_lat), &(A->g_lon)); } + + dw_printf("Grid square = %s, ", A->g_maidenhead); } strcpy (stemp, ""); @@ -639,19 +626,23 @@ void decode_aprs_print (decode_aprs_t *A) { * 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. */ - for (j=0; jg_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; jg_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"); - } + + if ( ! A->g_quiet) { + + for (j=0; jg_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; jg_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"); + } + } } } } @@ -890,7 +881,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) * *------------------------------------------------------------------*/ -static void nmea_checksum (char *sent) +static void nmea_checksum (decode_aprs_t *A, char *sent) { char *p; char *next; @@ -906,13 +897,17 @@ static void nmea_checksum (char *sent) p = strchr (sent, '*'); if (p == NULL) { - text_color_set (DW_COLOR_INFO); - dw_printf("Missing GPS checksum.\n"); + if ( ! A->g_quiet) { + 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); + 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. @@ -929,7 +924,7 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) strncpy (stemp, (char *)info, ilen); stemp[ilen] = '\0'; - nmea_checksum (stemp); + nmea_checksum (A, stemp); next = stemp; ptype = strsep(&next, ","); @@ -1202,7 +1197,7 @@ MIC-E, JEEP, In Service */ -static int mic_e_digit (char c, int mask, int *std_msg, int *cust_msg) +static int mic_e_digit (decode_aprs_t *A, char c, int mask, int *std_msg, int *cust_msg) { if (c >= '0' && c <= '9') { @@ -1237,8 +1232,10 @@ static int mic_e_digit (char c, int mask, int *std_msg, int *cust_msg) return (0); } - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character \"%c\" in MIC-E destination/latitude.\n", c); + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid character \"%c\" in MIC-E destination/latitude.\n", c); + } return (0); } @@ -1273,12 +1270,12 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest); - A->g_lat = mic_e_digit(dest[0], 4, &std_msg, &cust_msg) * 10 + - mic_e_digit(dest[1], 2, &std_msg, &cust_msg) + - (mic_e_digit(dest[2], 1, &std_msg, &cust_msg) * 1000 + - mic_e_digit(dest[3], 0, &std_msg, &cust_msg) * 100 + - mic_e_digit(dest[4], 0, &std_msg, &cust_msg) * 10 + - mic_e_digit(dest[5], 0, &std_msg, &cust_msg)) / 6000.0; + 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. */ @@ -1293,8 +1290,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid MIC-E N/S encoding in 4th character of destination.\n"); + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid MIC-E N/S encoding in 4th character of destination.\n"); + } } @@ -1312,8 +1311,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int else { offset = 0; - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid MIC-E Longitude Offset in 5th character of destination.\n"); + 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. */ @@ -1342,9 +1343,11 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } else { - A->g_lon = G_UNKNOWN; - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character 0x%02x for MIC-E Longitude Degrees.\n", ch); + 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. */ @@ -1378,8 +1381,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } else { A->g_lon = G_UNKNOWN; - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character 0x%02x for MIC-E Longitude Minutes.\n", ch); + 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. */ @@ -1396,8 +1401,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } else { A->g_lon = G_UNKNOWN; - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes.\n", ch); + 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); + } } } } @@ -1425,8 +1432,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid MIC-E E/W encoding in 6th character of destination.\n"); + 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. */ @@ -1437,8 +1446,10 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' && ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table)) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n"); + 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 = '/'; } @@ -1513,6 +1524,8 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int 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; } @@ -1556,9 +1569,11 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2])) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in MIC-E altitude. Must be in range of '!' to '{'.\n"); - dw_printf("Bogus altitude of %.0f changed to unknown.\n", A->g_altitude); + 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; } @@ -1578,6 +1593,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * * Inputs: info - Pointer to Information field. * ilen - Information field length. + * quiet - supress error messages. * * Outputs: ??? TBD * @@ -1593,7 +1609,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * *------------------------------------------------------------------*/ -static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen) +static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int quiet) { struct aprs_message_s { @@ -1619,6 +1635,8 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen) addressee[i--] = '\0'; } + strcpy (A->g_addressee, addressee); + /* * Special message formats contain telemetry metadata. * It applies to the addressee, not the sender. @@ -1640,11 +1658,11 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen) } 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); + 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); + telemetry_bit_sense_message (addressee, p->message+5, quiet); } else { sprintf (A->g_msg_type, "APRS Message for \"%s\"", addressee); @@ -1829,8 +1847,10 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) else if (p->name[i] == '_') strcpy (A->g_msg_type, "Killed Item"); else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Item name too long or not followed by ! or _.\n"); + 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"); } @@ -1995,14 +2015,18 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' && ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table)) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", 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') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space); + 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); @@ -2022,14 +2046,18 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) if (A->g_symbol_table != '/' && A->g_symbol_table != '\\' && ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table)) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", 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') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space); + 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); @@ -2085,6 +2113,7 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) * * Inputs: info - Pointer to Information field. * ilen - Information field length. + * quiet - suppress error messages. * * Outputs: ??? * @@ -2097,12 +2126,12 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) * *------------------------------------------------------------------*/ -static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen) +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, A->g_telemetry, A->g_comment); + telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, A->g_comment); } /* end aprs_telemetry */ @@ -2326,12 +2355,16 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) else if ( A->g_speed == G_UNKNOWN) { if ( ! getwdata (&wp, 'c', 3, &A->g_course)) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Didn't find wind direction in form c999.\n"); + if ( ! 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 */ - text_color_set(DW_COLOR_ERROR); - dw_printf("Didn't find wind speed in form s999.\n"); + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Didn't find wind speed in form s999.\n"); + } } } @@ -2366,8 +2399,10 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) } } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Didn't find wind gust in form g999.\n"); + 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)) { @@ -2378,8 +2413,10 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) } } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Didn't find temperature in form t999.\n"); + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Didn't find temperature in form t999.\n"); + } } /* @@ -2722,8 +2759,8 @@ static void third_party_header (decode_aprs_t *A, char *info, int ilen) static void decode_position (decode_aprs_t *A, position_t *ppos) { - A->g_lat = get_latitude_8 (ppos->lat); - A->g_lon = get_longitude_9 (ppos->lon); + 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; @@ -2775,8 +2812,10 @@ static void decode_compressed_position (decode_aprs_t *A, compressed_position_t } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in compressed latitude. Must be in range of '!' to '{'.\n"); + 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; } @@ -2786,8 +2825,10 @@ static void decode_compressed_position (decode_aprs_t *A, compressed_position_t } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in compressed longitude. Must be in range of '!' to '{'.\n"); + 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; } @@ -2801,8 +2842,10 @@ static void decode_compressed_position (decode_aprs_t *A, compressed_position_t A->g_symbol_table = pcpos->sym_table_id - 'a' + '0'; } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid symbol table id for compressed position.\n"); + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid symbol table id for compressed position.\n"); + } A->g_symbol_table = '/'; } @@ -2860,7 +2903,7 @@ static void decode_compressed_position (decode_aprs_t *A, compressed_position_t * *------------------------------------------------------------------*/ -double get_latitude_8 (char *p) +double get_latitude_8 (char *p, int quiet) { struct lat_s { unsigned char deg[2]; @@ -2877,16 +2920,20 @@ double get_latitude_8 (char *p) if (isdigit(plat->deg[0])) result += ((plat->deg[0]) - '0') * 10; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in latitude. Expected 0-9 for tens of degrees.\n"); + 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 { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in latitude. Expected 0-9 for degrees.\n"); + 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); } @@ -2895,8 +2942,10 @@ double get_latitude_8 (char *p) else if (plat->minn[0] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in latitude. Expected 0-5 for tens of minutes.\n"); + 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); } @@ -2905,14 +2954,18 @@ double get_latitude_8 (char *p) else if (plat->minn[1] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in latitude. Expected 0-9 for minutes.\n"); + 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 != '.') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Unexpected character \"%c\" found where period expected in latitude.\n", 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); } @@ -2921,8 +2974,10 @@ double get_latitude_8 (char *p) else if (plat->hmin[0] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in latitude. Expected 0-9 for tenths of minutes.\n"); + 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); } @@ -2931,8 +2986,10 @@ double get_latitude_8 (char *p) else if (plat->hmin[1] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in latitude. Expected 0-9 for hundredths of minutes.\n"); + 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); } @@ -2942,21 +2999,27 @@ double get_latitude_8 (char *p) return (result); } else if (plat->ns == 'n') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Lower case n found for latitude hemisphere. Specification requires upper case N or S.\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') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Lower case s found for latitude hemisphere. Specification requires upper case N or S.\n"); + 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 { - text_color_set(DW_COLOR_ERROR); - dw_printf("Error: '%c' found for latitude hemisphere. Specification requires upper case N or s.\n", plat->ns); + 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); } } @@ -2993,7 +3056,7 @@ double get_latitude_8 (char *p) *------------------------------------------------------------------*/ -double get_longitude_9 (char *p) +double get_longitude_9 (char *p, int quiet) { struct lat_s { unsigned char deg[3]; @@ -3010,24 +3073,30 @@ double get_longitude_9 (char *p) if (plon->deg[0] == '0' || plon->deg[0] == '1') result += ((plon->deg[0]) - '0') * 100; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0 or 1 for hundreds of degrees.\n"); + 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 { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0-9 for tens of degrees.\n"); + 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 { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0-9 for degrees.\n"); + 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); } @@ -3036,8 +3105,10 @@ double get_longitude_9 (char *p) else if (plon->minn[0] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0-5 for tens of minutes.\n"); + 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); } @@ -3046,14 +3117,18 @@ double get_longitude_9 (char *p) else if (plon->minn[1] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0-9 for minutes.\n"); + 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 != '.') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Unexpected character \"%c\" found where period expected in longitude.\n", 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); } @@ -3062,8 +3137,10 @@ double get_longitude_9 (char *p) else if (plon->hmin[0] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0-9 for tenths of minutes.\n"); + 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); } @@ -3072,8 +3149,10 @@ double get_longitude_9 (char *p) else if (plon->hmin[1] == ' ') ; else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in longitude. Expected 0-9 for hundredths of minutes.\n"); + 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); } @@ -3083,21 +3162,27 @@ double get_longitude_9 (char *p) return (result); } else if (plon->ew == 'e') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Lower case e found for longitude hemisphere. Specification requires upper case E or W.\n"); + 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') { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Lower case w found for longitude hemisphere. Specification requires upper case E or W.\n"); + 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 { - text_color_set(DW_COLOR_ERROR); - dw_printf("Error: '%c' found for longitude hemisphere. Specification requires upper case E or W.\n", plon->ew); + 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); } } @@ -3279,8 +3364,10 @@ int get_maidenhead (decode_aprs_t *A, char *p) /* We have 4 characters matching the rule. */ if (islower(p[0]) || islower(p[1])) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n"); + 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' && @@ -3289,8 +3376,10 @@ int get_maidenhead (decode_aprs_t *A, char *p) /* We have 6 characters matching the rule. */ if (islower(p[4]) || islower(p[5])) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n"); + 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; @@ -3584,9 +3673,11 @@ static void decode_tocall (decode_aprs_t *A, char *dest) #endif } else { - 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"); + 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"); + } } @@ -3876,9 +3967,11 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } if (strncmp(smtemp, "MHz", 3) != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: \"%s\" has non-standard capitalization and might not be recognized by some systems.\n", smtemp); - dw_printf("For best compatibility, it should be exactly like this: \"MHz\" (upper,upper,lower case)\n"); + 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); @@ -3928,9 +4021,11 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } } if (A->g_tone == G_UNKNOWN) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Bad CTCSS/PL specification: \"%s\"\n", sttemp); - dw_printf("Integer does not correspond to standard tone.\n"); + 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); @@ -4141,11 +4236,12 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) (x >= 420 && x <= 450) || (x >= 902 && x <= 928)) { - sprintf (good, "%07.3fMHz", x); - text_color_set(DW_COLOR_ERROR); - dw_printf("\"%s\" in comment looks like a frequency in non-standard format.\n", bad); - dw_printf("For most systems to recognize it, use exactly this form \"%s\" at beginning of comment.\n", good); - + if ( ! A->g_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; } @@ -4182,11 +4278,12 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) for (i = 0; i < NUM_CTCSS; i++) { if (strcmp (s_ctcss[i], bad2) == 0) { - sprintf (good, "T%03d", i_ctcss[i]); - text_color_set(DW_COLOR_ERROR); - dw_printf("\"%s\" in comment looks like it might be a CTCSS tone in non-standard format.\n", bad1); - dw_printf("For most systems to recognize it, use exactly this form \"%s\" at near beginning of comment, after any frequency.\n", good); - + if ( ! A->g_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); } @@ -4196,9 +4293,11 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } if ((A->g_offset == 6000 || A->g_offset == -6000) && A->g_freq >= 144 && A->g_freq <= 148) { - text_color_set(DW_COLOR_ERROR); - dw_printf("A transmit offset of 6 MHz on the 2 meter band doesn't seem right.\n"); - dw_printf("Each unit is 10 kHz so you should probably be using \"-060\" or \"+060\"\n"); + 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"); + } } /* @@ -4255,6 +4354,9 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * 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. * *------------------------------------------------------------------*/ @@ -4334,7 +4436,7 @@ int main (int argc, char *argv[]) decode_aprs_t A; // log directory option someday? - decode_aprs (&A, pp); + decode_aprs (&A, pp, 0); //Print it all out in human readable format. diff --git a/decode_aprs.h b/decode_aprs.h index 78752c2..ec8975a 100644 --- a/decode_aprs.h +++ b/decode_aprs.h @@ -22,6 +22,8 @@ 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. */ @@ -54,9 +56,11 @@ typedef struct decode_aprs_s { double g_lat, g_lon; /* Location, degrees. Negative for South or West. */ /* Set to G_UNKNOWN if missing or error. */ - char g_maidenhead[9]; /* 4 or 6 (or 8?) character maidenhead locator. */ + char g_maidenhead[12]; /* 4 or 6 (or 8?) character maidenhead locator. */ - char g_name[20]; /* Object or item name. */ + 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. */ @@ -68,7 +72,7 @@ typedef struct decode_aprs_s { int g_gain; /* Antenna gain in dB. */ - char g_directivity[10]; /* Direction of max signal strength */ + char g_directivity[12]; /* Direction of max signal strength */ float g_range; /* Precomputed radio range in miles. */ @@ -76,7 +80,7 @@ typedef struct decode_aprs_s { char g_mfr[80]; /* Manufacturer or application. */ - char g_mic_e_status[30]; /* MIC-E message. */ + char g_mic_e_status[32]; /* MIC-E message. */ double g_freq; /* Frequency, MHz */ @@ -98,7 +102,7 @@ typedef struct decode_aprs_s { -extern void decode_aprs (decode_aprs_t *A, packet_t pp); +extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet); extern void decode_aprs_print (decode_aprs_t *A); diff --git a/demod.c b/demod.c index abcb6f8..a24bcb6 100644 --- a/demod.c +++ b/demod.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 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 @@ -65,7 +65,8 @@ // Properties of the radio channels. -static struct audio_s modem; +static struct audio_s *save_audio_config_p; + // Current state of all the decoders. @@ -84,7 +85,7 @@ static int sample_count[MAX_CHANS][MAX_SUBCHANS]; * * Purpose: Initialize the demodulator(s) used for reception. * - * Inputs: pa - Pointer to modem_s structure with + * Inputs: pa - Pointer to audio_s structure with * various parameters for the modem(s). * * Returns: 0 for success, -1 for failure. @@ -99,173 +100,448 @@ int demod_init (struct audio_s *pa) { int j; int chan; /* Loop index over number of radio channels. */ - int subchan; /* for each modem for channel. */ char profile; - //float fc; - - struct demodulator_state_s *D; + /* - * Save parameters for later use. + * Save audio configuration for later use. */ - memcpy (&modem, pa, sizeof(modem)); - for (chan = 0; chan < modem.num_channels; chan++) { + save_audio_config_p = pa; - assert (chan >= 0 && chan < MAX_CHANS); + for (chan = 0; chan < MAX_CHANS; chan++) { - switch (modem.modem_type[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)); - case AFSK: /* * Pick a good default demodulator if none specified. */ - if (strlen(modem.profiles[chan]) == 0) { + if (num_letters == 0) { - if (modem.baud[chan] < 600) { + if (save_audio_config_p->achan[chan].baud < 600) { /* This has been optimized for 300 baud. */ - strcpy (modem.profiles[chan], "D"); - if (modem.samples_per_sec > 40000) { - modem.decimate[chan] = 3; - } + 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) { - if (modem.baud[chan] == FFF_BAUD && - modem.mark_freq[chan] == FFF_MARK_FREQ && - modem.space_freq[chan] == FFF_SPACE_FREQ && - modem.samples_per_sec == FFF_SAMPLES_PER_SEC) { - - modem.profiles[chan][0] = FFF_PROFILE; - modem.profiles[chan][1] = '\0'; + just_letters[0] = FFF_PROFILE; + just_letters[1] = '\0'; } else { - strcpy (modem.profiles[chan], "A"); + strcpy (just_letters, "A"); } #else - strcpy (modem.profiles[chan], "C"); + /* 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; } - if (modem.decimate[chan] == 0) modem.decimate[chan] = 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, modem.baud[chan], - modem.mark_freq[chan], modem.space_freq[chan], - modem.profiles[chan], - modem.samples_per_sec); - if (modem.decimate[chan] != 1) - dw_printf (" / %d", modem.decimate[chan]); + 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"); - if (strlen(modem.profiles[chan]) > 1) { -/* - * Multiple profiles, usually for 1200 baud. +/* + * Initialize the demodulator(s). + * + * We have 3 cases to consider. */ - assert (modem.num_subchan[chan] == strlen(modem.profiles[chan])); + + + 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 (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + for (d = 0; d < save_audio_config_p->achan[chan].num_demod; d++) { int mark, space; - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - D = &demodulator_state[chan][subchan]; + assert (d >= 0 && d < MAX_SUBCHANS); - profile = modem.profiles[chan][subchan]; - mark = modem.mark_freq[chan]; - space = modem.space_freq[chan]; + struct demodulator_state_s *D; + D = &demodulator_state[chan][d]; - if (modem.num_subchan[chan] != 1) { + 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, subchan, profile, mark, space); + dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space); } - demod_afsk_init (modem.samples_per_sec / modem.decimate[chan], modem.baud[chan], - 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 { + else if (have_plus) { + /* - * Possibly multiple frequency pairs. + * PLUS - which implies we have only one letter and one frequency pair. + * + * One demodulator feeds multiple slicers, each a subchannel. */ - - assert (modem.num_freq[chan] == modem.num_subchan[chan]); - assert (strlen(modem.profiles[chan]) == 1); - for (subchan = 0; subchan < modem.num_freq[chan]; subchan++) { + 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 (subchan >= 0 && subchan < MAX_SUBCHANS); - D = &demodulator_state[chan][subchan]; + assert (d >= 0 && d < MAX_SUBCHANS); - profile = modem.profiles[chan][0]; + struct demodulator_state_s *D; + D = &demodulator_state[chan][d]; - k = subchan * modem.offset[chan] - ((modem.num_subchan[chan] - 1) * modem.offset[chan]) / 2; - mark = modem.mark_freq[chan] + k; - space = modem.space_freq[chan] + k; + profile = save_audio_config_p->achan[chan].profiles[0]; - if (modem.num_subchan[chan] != 1) { + 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, subchan, profile, mark, space); + dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space); } - demod_afsk_init (modem.samples_per_sec / modem.decimate[chan], modem.baud[chan], + 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 subchan */ + /* 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; - default: +//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, %d sample rate x %d.\n", - chan, modem.baud[chan], - modem.samples_per_sec, UPSAMPLE); + 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 - subchan = 0; - D = &demodulator_state[chan][subchan]; + save_audio_config_p->achan[chan].num_subchan = 1; + save_audio_config_p->achan[chan].num_demod = 1; - demod_9600_init (UPSAMPLE * modem.samples_per_sec, modem.baud[chan], D); + 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. */ - } /* for chan ... */ + } /* if channel number is valid */ + } /* for chan ... */ - for (chan=0; chan= 0 && subchan < MAX_SUBCHANS); - - sample_sum[chan][subchan] = 0; - sample_count[chan][subchan] = subchan; /* stagger */ - - D = &demodulator_state[chan][subchan]; - -/* For collecting input signal level. */ - - D->lev_period = modem.samples_per_sec * 0.100; // Samples in 0.100 seconds. - - } - } - return (0); } /* end demod_init */ @@ -276,12 +552,14 @@ int demod_init (struct audio_s *pa) * * Name: demod_get_sample * - * Purpose: Get one audio sample fromt the sound input source. + * 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: modem.bits_per_sample - So we know whether to + * 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. @@ -296,18 +574,18 @@ int demod_init (struct audio_s *pa) __attribute__((hot)) -int demod_get_sample (void) +int demod_get_sample (int a) { int x1, x2; signed short sam; /* short to force sign extention. */ - assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16); + assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16); - if (modem.bits_per_sample == 8) { + if (save_audio_config_p->adev[a].bits_per_sample == 8) { - x1 = audio_get(); + x1 = audio_get(a); if (x1 < 0) return(FSK_READ_ERR); assert (x1 >= 0 && x1 <= 255); @@ -318,10 +596,10 @@ int demod_get_sample (void) } else { - x1 = audio_get(); /* lower byte first */ + x1 = audio_get(a); /* lower byte first */ if (x1 < 0) return(FSK_READ_ERR); - x2 = audio_get(); + x2 = audio_get(a); if (x2 < 0) return(FSK_READ_ERR); assert (x1 >= 0 && x1 <= 255); @@ -394,51 +672,57 @@ void demod_process_sample (int chan, int subchan, int sam) D = &demodulator_state[chan][subchan]; -#if 1 /* TODO: common level detection. */ + /* Scale to nice number, actually -2.0 to +2.0 for extra headroom */ - /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */ - - fsam = sam / 16384.0; + fsam = sam / 16384.0f; /* * Accumulate measure of the input signal level. */ - abs_fsam = fsam >= 0 ? fsam : -fsam; - - if (abs_fsam > D->lev_peak_acc) { - D->lev_peak_acc = abs_fsam; + + +/* + * 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); } - D->lev_sum_acc += abs_fsam; - - D->lev_count++; - if (D->lev_count >= D->lev_period) { - D->lev_prev_peak = D->lev_last_peak; - D->lev_last_peak = D->lev_peak_acc; - D->lev_peak_acc = 0; - - D->lev_prev_ave = D->lev_last_ave; - D->lev_last_ave = D->lev_sum_acc / D->lev_count; - D->lev_sum_acc = 0; - - D->lev_count = 0; + 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); } -#endif /* * Select decoder based on modulation type. */ - switch (modem.modem_type[chan]) { + switch (save_audio_config_p->achan[chan].modem_type) { - case AFSK: + case MODEM_OFF: - if (modem.decimate[chan] > 1) { + // 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] >= modem.decimate[chan]) { - demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / modem.decimate[chan], D); + 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; } @@ -448,6 +732,8 @@ void demod_process_sample (int chan, int subchan, int sam) } break; + case MODEM_BASEBAND: + case MODEM_SCRAMBLE: default: #define ZEROSTUFF 1 @@ -501,69 +787,67 @@ void demod_process_sample (int chan, int subchan, int sam) -/*------------------------------------------------------------------- - * - * Name: fsk_demod_print_agc - * - * Purpose: Print information about input signal amplitude. - * This will be useful for adjusting transmitter audio levels. - * We also want to avoid having an input level so high - * that the A/D converter "clips" the signal. - * - * - * Inputs: chan - Audio channel. 0 for left, 1 for right. - * - * Returns: None - * - * Descripion: Not sure what to use for final form. - * For now display the AGC peaks for both tones. - * This will be called at the end of a frame. - * - * Future: Come up with a sensible scale and add command line option. - * Probably makes more sense to return a single number - * and let the caller print it. - * Just an experiment for now. - * - *--------------------------------------------------------------------*/ - -#if 0 -void demod_print_agc (int chan, int subchan) -{ - - struct demodulator_state_s *D; - assert (chan >= 0 && chan < MAX_CHANS); - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - - D = &demodulator_state[chan][subchan]; - - dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50)); - - - - //dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n", - // D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave, - // D->m_peak, D->m_valley, D->s_peak, D->s_valley); - -} -#endif - +/* 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. */ -int demod_get_audio_level (int chan, int subchan) +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]; - return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) ); + // 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); } diff --git a/demod.h b/demod.h index d47fced..3233b9b 100644 --- a/demod.h +++ b/demod.h @@ -3,14 +3,15 @@ /* 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 (void); +int demod_get_sample (int a); void demod_process_sample (int chan, int subchan, int sam); void demod_print_agc (int chan, int subchan); -int demod_get_audio_level (int chan, int subchan); \ No newline at end of file +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 eef4e15..264cf20 100644 --- a/demod_9600.c +++ b/demod_9600.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 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 @@ -51,17 +51,15 @@ #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) { - int j; - - // TODO: memmove any faster? - for (j = size - 1; j >= 1; j--) { - buff[j] = buff[j-1]; - } + memmove(buff+1,buff,(size-1)*sizeof(float)); buff[0] = val; } @@ -69,15 +67,29 @@ static inline void push_sample (float val, float *buff, int size) /* FIR filter kernel. */ __attribute__((hot)) -static inline float convolve (const float *data, const float *filter, int filter_size) +static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) { - float sum = 0; - int j; + float sum = 0.0f; + int j; - for (j=0; jpll_step_per_sample = (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); - D->filter_len_bits = 72 * 9600.0 / (44100.0 * 2.0); - D->lp_filter_size = (int) (( D->filter_len_bits * (float)samples_per_sec / baud) + 0.5); + 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 - D->lpf_baud = 0.59; #ifdef TUNE_LPF_BAUD D->lpf_baud = TUNE_LPF_BAUD; #endif - D->agc_fast_attack = 0.080; #ifdef TUNE_AGC_FAST D->agc_fast_attack = TUNE_AGC_FAST; #endif - D->agc_slow_decay = 0.00012; #ifdef TUNE_AGC_SLOW D->agc_slow_decay = TUNE_AGC_SLOW; #endif - D->pll_locked_inertia = 0.88; - D->pll_searching_inertia = 0.67; - #if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING) D->pll_locked_inertia = TUNE_PLL_LOCKED; D->pll_searching_inertia = TUNE_PLL_SEARCHING; @@ -169,7 +190,14 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s //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, BP_WINDOW_HAMMING); + 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 */ @@ -227,20 +255,9 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s * ftp://ftp.tapr.org/general/9600baud/96man2x0.txt * * - * TODO: This works in a simulated environment but it has not yet - * been successfully tested for interoperability with - * other systems over the air. - * That's why it is not mentioned in documentation. - * - * The signal from the radio speaker does NOT have - * enough bandwidth and the waveform is hopelessly distorted. - * It will be necessary to obtain a signal right after - * the discriminator of the receiver. - * It will probably also be necessary to tap directly into - * the modulator, bypassing the microphone amplifier. - * *--------------------------------------------------------------------*/ +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) @@ -258,8 +275,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D int j; int subchan = 0; - int demod_data; /* Still scrambled. */ - static int descram; /* Data bit de-scrambled. */ + int demod_data; /* Still scrambled. */ assert (chan >= 0 && chan < MAX_CHANS); @@ -279,9 +295,13 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * indexing in the later loops that multipy and add. */ - /* Scale to nice number, range -1.0 to +1.0. */ + /* 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 / 32768.0; + fsam = sam / 16384.0; push_sample (fsam, D->raw_cb, D->lp_filter_size); @@ -291,6 +311,30 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D 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. @@ -300,23 +344,60 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * and scaling the results to be roughly in the -1.0 to +1.0 range. */ - demod_out = 2.0 * agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); + 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.01) { - demod_data = 1; + +// 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 if (demod_out < -0.01) { - demod_data = 0; - } else { - demod_data = D->prev_demod_data; + int s; + + assert (subchan == 0); + + /* Multiple slicers each feeding its own HDLC decoder. */ + + for (s=0; snum_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. @@ -343,10 +424,10 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * This was optimized for 1200 baud AFSK. There might be some opportunity * for improvement here. */ - D->prev_d_c_pll = D->data_clock_pll; - D->data_clock_pll += D->pll_step_per_sample; + 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->data_clock_pll < 0 && D->prev_d_c_pll > 0) { + if (D->slicer[subchan].data_clock_pll < 0 && D->slicer[subchan].prev_d_c_pll > 0) { /* Overflow. */ @@ -354,56 +435,37 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * 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] == SCRAMBLE); + //assert (modem.modem_type[chan] == MODEM_SCRAMBLE); - //if (modem.modem_type[chan] == SCRAMBLE) { + //if (modem.modem_type[chan] == MODEM_SCRAMBLE) { -// TODO: This needs to be rearranged to allow attempted "fixing" -// of corrupted bits later. We need to store the original -// received bits and do the descrambling after attempted -// repairs. However, we also need to descramble now to -// detect the flag sequences. + descram = descramble (demod_data, &(D->slicer[subchan].lfsr)); - - descram = descramble (demod_data, &(D->lfsr)); -#if SLICENDICE - // TODO: Needs more thought. - // Does it even make sense to remember demod_out in this case? - // We would need to do the re-thresholding before descrambling. - //hdlc_rec_bit_sam (chan, subchan, descram, descram ? 1.0 : -1.0); -#else - -// TODO: raw received bit and true later. - - hdlc_rec_bit (chan, subchan, descram, 0, D->lfsr); - -#endif + 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. */ -#if SLICENDICE - //hdlc_rec_bit_sam (chan, subchan, demod_data, demod_data ? 1.0 : -1.0); -#else //hdlc_rec_bit (chan, subchan, demod_data); -#endif //} } - if (demod_data != D->prev_demod_data) { + if (demod_data != D->slicer[subchan].prev_demod_data) { // Note: Test for this demodulator, not overall for channel. - if (hdlc_rec_data_detect_1 (chan, subchan)) { - D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia); + 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->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia); + D->slicer[subchan].data_clock_pll = (int)(D->slicer[subchan].data_clock_pll * D->pll_searching_inertia); } } @@ -411,7 +473,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D #if DEBUG5 //if (chan == 0) { - if (hdlc_rec_data_detect_1 (chan,subchan)) { + if (hdlc_rec_gathering (chan,subchan)) { char fname[30]; @@ -454,9 +516,11 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * Remember demodulator output (pre-descrambling) so we can compare next time * for the DPLL sync. */ - D->prev_demod_data = demod_data; + D->slicer[subchan].prev_demod_data = demod_data; + +} /* end nudge_pll */ + -} /* end demod_9600_process_sample */ diff --git a/demod_9600.h b/demod_9600.h index 39bbcb8..a764711 100644 --- a/demod_9600.h +++ b/demod_9600.h @@ -2,6 +2,10 @@ /* 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); diff --git a/demod_afsk.c b/demod_afsk.c index f80306a..5214f8f 100644 --- a/demod_afsk.c +++ b/demod_afsk.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013,2014 John Langner, WB2OSZ +// 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 @@ -52,8 +52,7 @@ #include "direwolf.h" #include "audio.h" -//#include "fsk_demod.h" -//#include "gen_tone.h" + #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" @@ -80,10 +79,10 @@ static inline float z (float x, float y) y = fabsf(y); if (x > y) { - return (x * .941246 + y * .41); + return (x * .941246f + y * .41f); } else { - return (y * .941246 + x * .41); + return (y * .941246f + x * .41f); } } @@ -100,15 +99,29 @@ static inline void push_sample (float val, float *buff, int size) /* FIR filter kernel. */ __attribute__((hot)) -static inline float convolve (const float *data, const float *filter, int filter_size) +static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size) { - float sum = 0; - int j; + float sum = 0.0f; + int j; - for (j=0; j= *ppeak) { - *ppeak = in * fast_attack + *ppeak * (1. - fast_attack); + *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack); } else { - *ppeak = in * slow_decay + *ppeak * (1. - slow_decay); + *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay); } if (in <= *pvalley) { - *pvalley = in * fast_attack + *pvalley * (1. - fast_attack); + *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack); } else { - *pvalley = in * slow_decay + *pvalley * (1. - slow_decay); + *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay); } if (*ppeak > *pvalley) { - return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); + return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); } - return (0.0); + return (0.0f); } +/* + * for multi-slicer experiment. + */ + +#define MIN_G 0.5f +#define MAX_G 4.0f + +/* TODO: static */ float space_gain[MAX_SUBCHANS]; + + /*------------------------------------------------------------------ * @@ -183,7 +206,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, profile = TUNE_PROFILE; #endif - if (toupper(profile) == 'F') { + + if (profile == 'F') { if (baud != DEFAULT_BAUD || mark_freq != DEFAULT_MARK_FREQ || @@ -198,76 +222,164 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, } } - if (profile == 'a' || profile == 'A' || profile == 'f' || profile == 'F') { + 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->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ - D->bp_window = BP_WINDOW_TRUNCATED; + 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->lpf_baud = 0; + 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; - } - else if (profile == 'b' || profile == 'B') { + break; + + case 'B': /* Original bandpass. Use FIR lowpass instead. */ - D->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ - D->bp_window = BP_WINDOW_TRUNCATED; + 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_iir = 0; 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; - } - else if (profile == 'c' || profile == 'C') { + break; + + case 'C': /* Cosine window, 76 taps for bandpass, FIR lowpass. */ - D->filter_len_bits = 2.068; /* 76 @ 44100, 1200 */ - D->bp_window = BP_WINDOW_COSINE; + 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_iir = 0; 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; - } - else if (profile == 'd' || profile == 'D') { + 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; /* Cosine window. */ - D->filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */ - D->bp_window = BP_WINDOW_COSINE; + 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_iir = 0; 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); } - else { - 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) @@ -288,6 +400,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, 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. @@ -295,24 +408,39 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec)); - /* - * My initial guess at length of filter was about one bit time. - * By trial and error, the optimal value was found to somewhat longer. - * This was optimized for 44,100 sample rate, 1200 baud, 1200/2200 Hz. - * More experimentation is needed for other situations. + * Convert number of bit times to number of taps. */ - D->ms_filter_size = (int) round( D->filter_len_bits * (float)samples_per_sec / (float)baud ); + 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. */ -#if defined(TUNE_MS_FILTER_SIZE) +#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 - D->lp_filter_size = D->ms_filter_size; +#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) { @@ -324,11 +452,24 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, 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); + } /* - * For narrow AFSK (e.g. 200 Hz shift), it might be beneficial to - * have a bandpass filter before the mark/space detector. - * For now, make it the same number of taps for simplicity. + * 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) { @@ -343,14 +484,17 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, f1 = f1 / (float)samples_per_sec; f2 = f2 / (float)samples_per_sec; - //gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_HAMMING); - //gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_BLACKMAN); - gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_COSINE); + //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__); @@ -363,6 +507,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, dw_printf (" j shape M sin M cos \n"); #endif + float Gs = 0, Gc = 0; + for (j=0; jms_filter_size; j++) { float am; float center; @@ -377,23 +523,40 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, 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->bp_window, D->ms_filter_size, j); + 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; jms_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; jms_filter_size; j++) { float as; float center; @@ -402,19 +565,29 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, 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->bp_window, D->ms_filter_size, j); + 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 } -/* Do we want to normalize for unity gain? */ +/* Normalize for unity gain */ +#if DEBUG1 + dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; +#endif + for (j=0; jms_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. @@ -425,7 +598,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, 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, BP_WINDOW_TRUNCATED); + gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); } /* @@ -476,6 +649,27 @@ failed experiment #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= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); - - - - /* * Filters use last 'filter_size' samples. * @@ -631,32 +825,10 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */ - fsam = sam / 16384.0; + fsam = sam / 16384.0f; -/* - * Accumulate measure of the input signal level. - */ - abs_fsam = fsam >= 0 ? fsam : -fsam; - -// TODO: move to common code - - if (abs_fsam > D->lev_peak_acc) { - D->lev_peak_acc = abs_fsam; - } - D->lev_sum_acc += abs_fsam; + abs_fsam = fsam >= 0.0f ? fsam : -fsam; - D->lev_count++; - if (D->lev_count >= D->lev_period) { - D->lev_prev_peak = D->lev_last_peak; - D->lev_last_peak = D->lev_peak_acc; - D->lev_peak_acc = 0; - - D->lev_prev_ave = D->lev_last_ave; - D->lev_last_ave = D->lev_sum_acc / D->lev_count; - D->lev_sum_acc = 0; - - D->lev_count = 0; - } /* * Optional bandpass filter before the mark/space discriminator. @@ -665,8 +837,8 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat if (D->use_prefilter) { float cleaner; - push_sample (fsam, D->raw_cb, D->ms_filter_size); - cleaner = convolve (D->raw_cb, D->pre_filter, D->ms_filter_size); + 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 { @@ -687,10 +859,10 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat -// TODO: How do we test for profile F here? +// TODO1.2: is this right or do we need to store profile in the modulator info? - if (0) { - //if (toupper(modem.profiles[chan][subchan]) == toupper(FFF_PROFILE)) { + + if (D->profile == toupper(FFF_PROFILE)) { /* ========== Faster for default values on slower processors. ========== */ @@ -748,115 +920,130 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat /* Original, but faster, IIR. */ - m_amp = D->lpf_iir * m_amp + (1.0 - D->lpf_iir) * D->m_amp_prev; + 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.0 - D->lpf_iir) * D->s_amp_prev; + 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 have different amounts of DC bias. + * The two filters also have different amounts of DC bias. * - * Try to compensate for this by normalizing them separately with automatic gain - * control (AGC). This works by looking at the minimum and maximum outputs + * 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 agc.c and fsk_demod_agc.h for more information. */ + /* 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)); - /* Demodulator output is difference between response from two filters. */ - /* AGC should generally keep this around -1 to +1 range. */ + if (D->num_slicers <= 1) { - demod_out = m_norm - s_norm; + /* 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. */ -/* Try adding some Hysteresis. */ -/* (Not to be confused with Hysteria.) */ + demod_out = m_norm - s_norm; - if (demod_out > D->hysteresis) { - demod_data = 1; - } - else if (demod_out < (- (D->hysteresis))) { - demod_data = 0; - } - else { - demod_data = D->prev_demod_data; - } + /* Try adding some Hysteresis. */ + /* (Not to be confused with Hysteria.) */ - -/* - * 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->prev_d_c_pll = D->data_clock_pll; - D->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->data_clock_pll < 0 && D->prev_d_c_pll > 0) { - - /* Overflow. */ -#if SLICENDICE - hdlc_rec_bit_sam (chan, subchan, demod_data, demod_out); -#else - hdlc_rec_bit (chan, subchan, demod_data, 0, -1); -#endif - } - - if (demod_data != D->prev_demod_data) { - - // Note: Test for this demodulator, not overall for channel. - - if (hdlc_rec_data_detect_1 (chan, subchan)) { - D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia); + if (demod_out > D->hysteresis) { + demod_data = 1; } + else if (demod_out < (- (D->hysteresis))) { + demod_data = 0; + } else { - D->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia); + 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; snum_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_data_detect_1 (chan, subchan)) { + if (hdlc_rec_gathering (chan, subchan)) { char fname[30]; @@ -886,87 +1073,71 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat #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->prev_demod_data = demod_data; + D->slicer[subchan].prev_demod_data = demod_data; +} /* end nudge_pll */ -} /* end demod_afsk_process_sample */ #endif /* GEN_FFF */ -#if 0 - -/*------------------------------------------------------------------- - * - * Name: fsk_demod_print_agc - * - * Purpose: Print information about input signal amplitude. - * This will be useful for adjusting transmitter audio levels. - * We also want to avoid having an input level so high - * that the A/D converter "clips" the signal. - * - * - * Inputs: chan - Audio channel. 0 for left, 1 for right. - * - * Returns: None - * - * Descripion: Not sure what to use for final form. - * For now display the AGC peaks for both tones. - * This will be called at the end of a frame. - * - * Future: Come up with a sensible scale and add command line option. - * Probably makes more sense to return a single number - * and let the caller print it. - * Just an experiment for now. - * - *--------------------------------------------------------------------*/ - -#if 0 -void fsk_demod_print_agc (int chan, int subchan) -{ - - struct demodulator_state_s *D; - - - assert (chan >= 0 && chan < MAX_CHANS); - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - - D = &demodulator_state[chan][subchan]; - - dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50)); - - - - //dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n", - // D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave, - // D->m_peak, D->m_valley, D->s_peak, D->s_valley); - -} -#endif - -/* 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. */ - -int fsk_demod_get_audio_level (int chan, int subchan) -{ - struct demodulator_state_s *D; - - - assert (chan >= 0 && chan < MAX_CHANS); - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - - D = &demodulator_state[chan][subchan]; - - return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) ); -} - - - - -#endif /* 0 */ - /* end demod_afsk.c */ diff --git a/digipeater.c b/digipeater.c index 7bb570b..f8230a9 100644 --- a/digipeater.c +++ b/digipeater.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 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 @@ -58,7 +58,7 @@ #include #include #include -//#include /* for isdigit */ +#include /* for isdigit, isupper */ #include "regex.h" #include @@ -68,17 +68,23 @@ #include "textcolor.h" #include "dedupe.h" #include "tq.h" +#include "pfilter.h" -static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit, - regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt); +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 digi_config_s my_config; +static struct audio_s *save_audio_config_p; +static struct digi_config_s *save_digi_config_p; /*------------------------------------------------------------------------------ @@ -87,19 +93,21 @@ static struct digi_config_s my_config; * * Purpose: Initialize with stuff from configuration file. * - * Input: p_digi_config - Address of structure with all the - * necessary configuration details. + * Inputs: p_audio_config - Configuration for audio channels. + * + * p_digi_config - Digipeater configuration details. * - * Outputs: Make local copy for later use. + * Outputs: Save pointers to configuration for later use. * * Description: Called once at application startup time. * *------------------------------------------------------------------------------*/ -void digipeater_init (struct digi_config_s *p_digi_config) +void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config) { - memcpy (&my_config, p_digi_config, sizeof(my_config)); - + save_audio_config_p = p_audio_config; + save_digi_config_p = p_digi_config; + dedupe_init (p_digi_config->dedupe_time); } @@ -131,7 +139,12 @@ void digipeater (int from_chan, packet_t pp) // dw_printf ("digipeater()\n"); - assert (from_chan >= 0 && from_chan < my_config.num_chans); + 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); + } /* @@ -140,12 +153,14 @@ void digipeater (int from_chan, packet_t pp) * We want these to get out quickly. */ - for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan == from_chan) { - result = digipeat_match (pp, my_config.mycall[from_chan], my_config.mycall[to_chan], - &my_config.alias[from_chan][to_chan], &my_config.wide[from_chan][to_chan], - to_chan, my_config.preempt[from_chan][to_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); @@ -161,12 +176,14 @@ void digipeater (int from_chan, packet_t pp) * These are lower priority */ - for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan != from_chan) { - result = digipeat_match (pp, my_config.mycall[from_chan], my_config.mycall[to_chan], - &my_config.alias[from_chan][to_chan], &my_config.wide[from_chan][to_chan], - to_chan, my_config.preempt[from_chan][to_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); @@ -185,27 +202,29 @@ void digipeater (int from_chan, packet_t pp) * * Purpose: A simple digipeater for APRS. * - * Input: pp - Pointer to a packet object. + * 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. + * associated with the radio channel where the + * packet was received. * - * mycall_rec - Call of my station, with optional SSID, - * associated with the radio channel where the - * packet was received. Could be the same as - * mycall_rec or different. + * 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). + * alias - Compiled pattern for my station aliases or + * "trapping" (repeating only once). * - * wide - Compiled pattern for normal WIDEn-n digipeating. + * 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. + * preempt - Option for "preemptive" digipeating. + * + * filter_str - Filter expression string or NULL. * * Returns: Packet object for transmission or NULL. * @@ -237,8 +256,8 @@ static char *dest_ssid_path[16] = { "WIDE2-2" }; /* West */ -static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit, - regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt) +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; @@ -247,6 +266,23 @@ static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit 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: @@ -328,10 +364,10 @@ static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit if (dedupe_check(pp, to_chan)) { //#if DEBUG /* Might be useful if people are wondering why */ - /* some are not repeated. Might cause confusion. */ + /* some are not repeated. Might also cause confusion. */ text_color_set(DW_COLOR_INFO); - dw_printf ("Digipeater: Drop redundant packet.\n"); + dw_printf ("Digipeater: Drop redundant packet to channel %d.\n", to_chan); //#endif assert (result == NULL); return NULL; @@ -359,7 +395,6 @@ static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit * and aliases against all remaining unused digipeaters. */ - if (preempt != PREEMPT_OFF) { int r2; @@ -482,10 +517,10 @@ void digi_regen (int from_chan, packet_t pp) // dw_printf ("digi_regen()\n"); - assert (from_chan >= 0 && from_chan < my_config.num_chans); + assert (from_chan >= 0 && from_chan < MAX_CHANS); - for (to_chan=0; to_chanregen[from_chan][to_chan]) { result = ax25_dup (pp); if (result != NULL) { // TODO: if AX.25 and has been digipeated, put in HI queue? @@ -497,6 +532,7 @@ void digi_regen (int from_chan, packet_t pp) } /* end dig_regen */ + /*------------------------------------------------------------------------- * * Name: main @@ -520,6 +556,8 @@ static int failed; static enum preempt_e preempt = PREEMPT_OFF; +static char typefilter[20] = ""; + static void test (char *in, char *out) { @@ -575,7 +613,9 @@ static void test (char *in, char *out) text_color_set(DW_COLOR_REC); dw_printf ("Rec\t%s\n", rec); - result = digipeat_match (pp, mycall, mycall, &alias_re, &wide_re, 0, preempt); +//TODO: Add filtering to test. +// V + result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL); if (result != NULL) { @@ -789,6 +829,69 @@ int main (int argc, char *argv[]) ""); +#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? */ diff --git a/digipeater.h b/digipeater.h index 21602ed..d1b26b8 100644 --- a/digipeater.h +++ b/digipeater.h @@ -7,6 +7,8 @@ #include "direwolf.h" /* for MAX_CHANS */ #include "ax25_pad.h" /* for packet_t */ +#include "audio.h" /* for radio channel properties */ + /* * Information required for digipeating. @@ -18,11 +20,6 @@ struct digi_config_s { - int num_chans; - - char mycall[MAX_CHANS][AX25_MAX_ADDR_LEN]; /* Call associated */ - /* with each of the radio channels. */ - /* Could be the same or different. */ int dedupe_time; /* Don't digipeat duplicate packets */ /* within this number of seconds. */ @@ -41,6 +38,13 @@ struct digi_config_s { 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. }; @@ -49,7 +53,7 @@ struct digi_config_s { * Call once at application start up time. */ -extern void digipeater_init (struct digi_config_s *p_digi_config); +extern void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_digi_config); /* * Call this for each packet received. diff --git a/direwolf.c b/direwolf.c index a106ef5..be7e497 100644 --- a/direwolf.c +++ b/direwolf.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ +// 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 @@ -44,6 +44,12 @@ #include #include +#if __ARM__ +//#include +//#include // 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 @@ -77,6 +83,7 @@ #include "server.h" #include "kiss.h" #include "kissnet.h" +#include "kiss_frame.h" #include "nmea.h" #include "gen_tone.h" #include "digipeater.h" @@ -84,7 +91,6 @@ #include "xmit.h" #include "ptt.h" #include "beacon.h" -#include "ax25_pad.h" #include "redecode.h" #include "dtmf.h" #include "aprs_tt.h" @@ -92,8 +98,8 @@ #include "igate.h" #include "symbols.h" #include "dwgps.h" -#include "nmea.h" #include "log.h" +#include "recv.h" //static int idx_decoded = 0; @@ -138,11 +144,18 @@ static void __cpuid(int cpuinfo[4], int infotype){ * *--------------------------------------------------------------------*/ -static struct audio_s modem; +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; @@ -155,9 +168,9 @@ int main (int argc, char *argv[]) int xmit_calibrate_option = 0; int enable_pseudo_terminal = 0; struct digi_config_s digi_config; - struct tt_config_s tt_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]; @@ -165,11 +178,12 @@ int main (int argc, char *argv[]) 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__ @@ -212,11 +226,13 @@ int main (int argc, char *argv[]) } } + // 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 1\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - //dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "M", __DATE__); - dw_printf ("Dire Wolf version %d.%d, December 2014\n", MAJOR_VERSION, MINOR_VERSION); + //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__ @@ -298,7 +314,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "B:D:c:pxr:b:n:d:t:Ul:", + c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:", long_options, &option_index); if (c == -1) break; @@ -341,12 +357,18 @@ int main (int argc, char *argv[]) } 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 of -D. \n"); + dw_printf ("Crazy value for -D. \n"); exit (EXIT_FAILURE); } break; @@ -367,10 +389,10 @@ int main (int argc, char *argv[]) } break; - case 'n': /* -n number of audio channels. 1 or 2. */ + case 'n': /* -n number of audio channels for first audio device. 1 or 2. */ n_opt = atoi(optarg); - if (n_opt < 1 || n_opt > MAX_CHANS) + 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"); @@ -415,6 +437,24 @@ int main (int argc, char *argv[]) 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; } } @@ -469,41 +509,50 @@ int main (int argc, char *argv[]) symbols_init (); - config_init (config_file, &modem, &digi_config, &tt_config, &igate_config, &misc_config); + config_init (config_file, &audio_config, &digi_config, &tt_config, &igate_config, &misc_config); if (r_opt != 0) { - modem.samples_per_sec = r_opt; + audio_config.adev[0].samples_per_sec = r_opt; } if (n_opt != 0) { - modem.num_channels = n_opt; + audio_config.adev[0].num_channels = n_opt; + if (n_opt == 2) { + audio_config.achan[1].valid = 1; + } } if (b_opt != 0) { - modem.bits_per_sample = b_opt; + audio_config.adev[0].bits_per_sample = b_opt; } if (B_opt != 0) { - modem.baud[0] = B_opt; + audio_config.achan[0].baud = B_opt; - if (modem.baud[0] < 600) { - modem.modem_type[0] = AFSK; - modem.mark_freq[0] = 1600; - modem.space_freq[0] = 1800; - modem.decimate[0] = 3; + 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 (modem.baud[0] > 2400) { - modem.modem_type[0] = SCRAMBLE; - modem.mark_freq[0] = 0; - modem.space_freq[0] = 0; + 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 { - modem.modem_type[0] = AFSK; - modem.mark_freq[0] = 1200; - modem.space_freq[0] = 2200; + 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) { - // Don't document. This will change. - modem.decimate[0] = D_opt; + // Reduce audio sampling rate to reduce CPU requirements. + audio_config.achan[0].decimate = D_opt; } if (strlen(l_opt) > 0) { @@ -513,7 +562,7 @@ int main (int argc, char *argv[]) misc_config.enable_kiss_pt = enable_pseudo_terminal; if (strlen(input_file) > 0) { - strcpy (modem.adevice_in, input_file); + strcpy (audio_config.adev[0].adevice_in, input_file); } /* @@ -525,7 +574,7 @@ int main (int argc, char *argv[]) * Can always "cat" the file and pipe it into stdin. */ - err = audio_open (&modem); + err = audio_open (&audio_config); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Pointless to continue without audio device.\n"); @@ -536,31 +585,31 @@ int main (int argc, char *argv[]) /* * Initialize the AFSK demodulator and HDLC decoder. */ - multi_modem_init (&modem); + multi_modem_init (&audio_config); /* * Initialize the touch tone decoder & APRStt gateway. */ - dtmf_init (modem.samples_per_sec); + dtmf_init (&audio_config); aprs_tt_init (&tt_config); - tt_user_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 (&modem, 100); + gen_tone_init (&audio_config, 100); - assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16); - assert (modem.num_channels == 1 || modem.num_channels == 2); - assert (modem.samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.samples_per_sec <= MAX_SAMPLES_PER_SEC); + 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 (&modem, d_p_opt); + xmit_init (&audio_config, d_p_opt); /* * If -x option specified, transmit alternating tones for transmitter @@ -571,38 +620,39 @@ int main (int argc, char *argv[]) if (xmit_calibrate_option) { int max_duration = 60; /* seconds */ - int n = modem.baud[0] * max_duration; + 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 (chan, 1); + ptt_set (OCTYPE_PTT, chan, 1); while (n-- > 0) { tone_gen_put_bit (chan, n & 1); } - ptt_set (chan, 0); + ptt_set (OCTYPE_PTT, chan, 0); exit (0); } /* * Initialize the digipeater and IGate functions. */ - digipeater_init (&digi_config); - igate_init (&igate_config, &digi_config); + 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 (&misc_config); + 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. @@ -612,12 +662,12 @@ int main (int argc, char *argv[]) /* * Create thread for trying to salvage frames with bad FCS. */ - redecode_init (); + redecode_init (&audio_config); /* * Enable beaconing. */ - beacon_init (&misc_config, &digi_config); + beacon_init (&audio_config, &misc_config, &digi_config); log_init(misc_config.logdir); @@ -625,47 +675,11 @@ int main (int argc, char *argv[]) /* * Get sound samples and decode them. * Use hot attribute for all functions called for every audio sample. - * TODO: separate function with __attribute__((hot)) */ - eof = 0; - while ( ! eof) - { - - int audio_sample; - int c; - char tt; - - for (c=0; c= 256 * 256) - eof = 1; - - multi_modem_process_sample(c,audio_sample); - /* Previously, 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. */ - - if (tt_config.obj_xmit_header[0] != '\0') { - tt = dtmf_sample (c, audio_sample/16384.); - if (tt != ' ') { - aprs_tt_button (c, tt); - } - } - } - - /* When a complete frame is accumulated, */ - /* process_rec_frame, below, is called. */ - - } + recv_init (&audio_config); + recv_process (); exit (EXIT_SUCCESS); } @@ -680,7 +694,7 @@ int main (int argc, char *argv[]) * * Inputs: chan - Audio channel number, 0 or 1. * subchan - Which modem caught it. - * Special case -1 for APRStt gateway. + * Special case -1 for DTMF decoder. * pp - Packet handle. * alevel - Audio level, range of 0 - 100. * (Special case, use negative to skip @@ -695,8 +709,10 @@ int main (int argc, char *argv[]) * *--------------------------------------------------------------------*/ +// 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, int alevel, retry_t retries, char *spectrum) + +void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) { char stemp[500]; @@ -705,12 +721,17 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret 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); @@ -730,45 +751,54 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret ax25_get_addr_with_ssid(pp, h, heard); } - if (alevel >= 0) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); - text_color_set(DW_COLOR_DEBUG); - dw_printf ("\n"); - //idx_decoded++; - //dw_printf ("DECODED[%d] " , idx_decoded); + 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') { + 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 = %d [%s] %s\n", heard, probably_really, alevel, retry_text[(int)retries], spectrum); + + 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 = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum); - } - /* Cranking up the input currently produces */ - /* no more than 97. Issue a warning before we */ - /* reach this saturation point. */ - - if (alevel > 90) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio input level is too high. Reduce so most stations are around 50.\n"); + 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. @@ -788,7 +818,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret else { text_color_set(DW_COLOR_DEBUG); } - if (modem.num_subchan[chan] > 1) { + if (audio_config.achan[chan].num_subchan > 1) { dw_printf ("[%d.%d] ", chan, subchan); } else { @@ -837,12 +867,13 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret /* Decode the contents of APRS frames and display in human-readable form. */ +/* Suppress decoding if "-q d" option used. */ - if (ax25_is_aprs(pp)) { + if ( ( ! q_d_opt ) && ax25_is_aprs(pp)) { decode_aprs_t A; - decode_aprs (&A, pp); + decode_aprs (&A, pp, 0); //Print it all out in human readable format. @@ -852,7 +883,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret log_write (chan, &A, pp, alevel, retries); - // Convert to NMEA waypoint sentence. + // 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, @@ -874,28 +905,53 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, ret 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) { - igate_send_rec_packet (chan, pp); - } + 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); + digi_regen (chan, pp); -/* Note that packet can be modified in place so this is the last thing we should do with it. */ -/* Again, use only those with correct CRC. */ -/* We don't want to spread corrupted data! */ -/* Single bit change appears to be safe from observations so far but be cautious. */ +/* + *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); + if (ax25_is_aprs(pp) && retries == RETRY_NONE) { + + digipeater (chan, pp); + } } - ax25_delete (pp); } /* end app_process_rec_packet */ @@ -948,15 +1004,14 @@ static void usage (char **argv) 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. Standard values are 300, 1200, 9600.\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"); @@ -964,9 +1019,11 @@ static void usage (char **argv) 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"); diff --git a/direwolf.conf b/direwolf.conf index bb6ab41..b564b3c 100644 --- a/direwolf.conf +++ b/direwolf.conf @@ -2,6 +2,8 @@ # # # Configuration file for Dire Wolf # # # +# Linux version # +# # ############################################################# # # Consult the User Guide for more details on configuration options. @@ -14,13 +16,11 @@ # 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. @@ -28,7 +28,6 @@ # 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 @@ -50,20 +49,12 @@ # # Command parameters are generally case sensitive. i.e. upper and lower case are different. # -# Example: The next two are equivalent -# -# PTT /dev/ttyS0 RTS -# ptt /dev/ttyS0 RTS -# -# But this not equivalent because device names are case sensitive. -# -# PTT /dev/TTYs0 RTS -# ############################################################# # # -# AUDIO DEVICE PROPERTIES # +# FIRST AUDIO DEVICE PROPERTIES # +# (Channel 0 + 1 if in stereo) # # # ############################################################# @@ -71,40 +62,10 @@ # Many people will simply use the default sound device. # Some might want to use an alternative device by chosing it here. # -# When the Windows version starts up, it displays something like -# this with the available sound devices and capabilities: -# -# Available audio input devices for receive (*=selected): -# 0: Microphone (Realtek High Defini -# 1: Microphone (Bluetooth SCO Audio -# 2: Microphone (Bluetooth AV Audio) -# 3: Microphone (USB PnP Sound Devic -# Available audio output devices for transmit (*=selected): -# 0: Speakers (Realtek High Definiti -# 1: Speakers (Bluetooth SCO Audio) -# 2: Realtek Digital Output (Realtek -# 3: Realtek Digital Output(Optical) -# 4: Speakers (Bluetooth AV Audio) -# 5: Speakers (USB PnP Sound Device) - -# Example: To use the USB Audio, use a command like this with -# the input and output device numbers. (Remove the # comment character.) - -#ADEVICE 3 5 - -# The position in the list can change when devices (e.g. USB) are added and removed. -# You can also specify devices by using part of the name. -# Here is an example of specifying the USB Audio device. -# This is case-sensitive. Upper and lower case are not treated the same. - -#ADEVICE USB - - # 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. Examples: +# and a card number reported by "arecord -l" command. Example: -# ADEVICE plughw:CARD=Device,DEV=0 # ADEVICE plughw:1,0 # Starting with version 1.0, you can also use "-" or "stdin" to @@ -116,30 +77,35 @@ # ADEVICE UDP:7355 default -# -# This is the sound card audio sample rate. -# The default is 44100. Other standard values are 22050 or 11025. -# -# Change this only if your computer can't keep up. -# A lower rate means lower CPU demands but performance will be degraded. -# - -ARATE 44100 - # -# Number of audio channels. 1 or 2. -# If you specify 2, it is possible to attach two different transceivers -# and receive from both simultaneously. +# Number of audio channels for this souncard: 1 or 2. # ACHANNELS 1 - -# Use this instead if you want to use two transceivers. - #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 # @@ -149,11 +115,10 @@ ACHANNELS 1 CHANNEL 0 # -# The following will apply to the first or only channel. -# When two channels are used, this is the left audio channel. +# 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. @@ -164,35 +129,44 @@ CHANNEL 0 # Example (don't use this unless you are me): MYCALL WB2OSZ-5 # -MYCALL NOCALL - - +MYCALL N0CALL # -# VHF FM operation normally uses 1200 baud data with AFSK tones of 1200 and 2200 Hz. +# 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 1200 2200 - -# -# 200 Hz shift is normally used for 300 baud HF SSB operation. -# -# Note that if you change the tones here, you will need to adjust -# your tuning dial accordingly to get the same transmitted frequencies. -# -# In the second example, we have 7 demodulators spaced 30 Hz apart -# to capture signals that are off frequency. -# If you run out of CPU power, drop the audio sample rate down to 22050. - -#MODEM 300 1600 1800 -#MODEM 300 1600 1800 7 30 - -# -# 9600 baud doesn't use AFSK so no tones are listed. -# - +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@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) @@ -201,10 +175,13 @@ MODEM 1200 1200 2200 # # 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 -DTR +#PTT COM1 RTS -DTR #PTT /dev/ttyUSB0 RTS # @@ -217,23 +194,11 @@ MODEM 1200 1200 2200 #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. -# -# After turning on transmitter, send "flag" characters for -# TXDELAY * 10 milliseconds for transmitter to stabilize before -# sending data. 300 milliseconds is a good default. -# - -TXDELAY 30 - -# -# Keep transmitting for TXTAIL * 10 milliseconds after sending -# the data. This is needed to avoid dropping PTT too soon and -# chopping of the end of the data because we don't have -# precise control of when the sound will actually get out. -# - -TXTAIL 10 +#DCD COM1 -DTR +#DCD GPIO 24 ############################################################# @@ -242,33 +207,20 @@ TXTAIL 10 # # ############################################################# -CHANNEL 1 +#CHANNEL 1 # -# The following will apply to the second (right) channel if ACHANNELS is 2. -# +# Specify MYCALL, MODEM, PTT, etc. configuration items for +# CHANNEL 1. Repeat for any other channels. -# -# The two radio channels can have the same or different station identifiers. -# -# -# Example (don't use this unless you are me): MYCALL WB2OSZ-5 -# -MYCALL NOCALL - -MODEM 1200 1200 2200 - -# -# For this example, we use the same serial port for both -# transmitters. RTS for channel 0 and DTR for channel 1. -# - -#PTT COM1 DTR - -TXDELAY 30 -TXTAIL 10 +############################################################# +# # +# TEXT TO SPEECH COMMAND FILE # +# # +############################################################# +#SPEECH dwespeak.sh ############################################################# @@ -279,52 +231,19 @@ TXTAIL 10 # # Dire Wolf acts as a virtual TNC and can communicate with -# two different protocols: -# - the "AGW TCPIP Socket Interface" - default port 8000 -# - KISS TNC via serial port -# - KISS protocol over TCP socket - default port 8001 +# client applications by different protocols: # -# See descriptions of AGWPORT, KISSPORT, and NULLMODEM in the -# User Guide for more details. +# - 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 # -# Some applications are designed to operate with only a physical -# TNC attached to a serial port. For these, we provide a virtual serial -# port ("pseudo terminal" in Linux) that appears to be connected to a TNC. -# -# Linux: -# Linux applications can often specify "/tmp/kisstnc" -# for the serial port name. Behind the scenes, Dire Wolf -# creates a pseudo terminal. Unfortunately we can't specify the name -# and we wouldn't want to reconfigure the application each time. -# To get around this, /tmp/kisstnc is a symbolic link to the -# non-constant pseudo terminal name. -# -# Use the -p command line option to enable this feature. -# -# Windows: -# -# Microsoft Windows applications need a serial port -# name like COM1, COM2, COM3, or COM4. -# -# Take a look at the User Guide for instructions to set up -# two virtual serial ports named COM3 and COM4 connected by -# a null modem. -# -# Using the default configuration, Dire Wolf will connect to -# COM3 and the client application will use COM4. -# -# Uncomment following line to use this feature. - -#NULLMODEM COM3 - - -# -# It is sometimes possible to recover frames with a bad FCS. +# 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) @@ -332,7 +251,7 @@ KISSPORT 8001 # ... see User Guide for more values and in-depth discussion. # -FIX_BITS 1 +#FIX_BITS 0 # ############################################################# @@ -358,19 +277,27 @@ FIX_BITS 1 # The others are kept local. # -#PBEACON delay=00:10 every=0:30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 +#PBEACON delay=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" -#PBEACON delay=00:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 -#PBEACON delay=10:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" -#PBEACON delay=20:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" # With UTM coordinates instead of latitude and longitude. -#PBEACON delay=00:15 every=10 overlay=S symbol="digi" zone=19T easting=306130 northing=4726010 +#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 + # -# Modify this for your particular situation before removing -# the # comment character from the beginning of the lines above. +# 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. # @@ -382,14 +309,21 @@ FIX_BITS 1 # # For most common situations, use something like this by removing -# the "#" from the beginning of the line below. +# the "#" from the beginning of the line below. # -#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE +#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 + ############################################################# # # @@ -431,11 +365,19 @@ FIX_BITS 1 #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.aprs2.net/wiki/pmwiki.php/Main/FilterGuide -# Example: +# 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 @@ -505,9 +447,9 @@ TTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy TTMACRO z Cz -# Transmit object reports on channel 0 with this header. +# Receive on channel 0, Transmit object reports on channel 1 with optional via path. -#TTOBJ 0 WB2OSZ-5>APDW10 +#TTOBJ 0 1 WIDE1-1 # Advertise gateway position with beacon. diff --git a/direwolf.desktop b/direwolf.desktop deleted file mode 100644 index bfc0eb4..0000000 --- a/direwolf.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Type=Application -Exec=lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf" -Name=Dire Wolf -Comment=APRS Soundcard TNC -Icon=/usr/share/direwolf/dw-icon.png -Path=/home/pi -#Terminal=true -Categories=HamRadio -Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25 \ No newline at end of file diff --git a/direwolf.h b/direwolf.h index b7ce241..aeac219 100644 --- a/direwolf.h +++ b/direwolf.h @@ -3,11 +3,41 @@ #define DIREWOLF_H 1 + /* - * Maximum number of radio channels. + * 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_CHANS 2 +#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>1) +#define ADEVFIRSTCHAN(n) ((n) * 2) /* * Maximum number of modems per channel. @@ -54,4 +84,78 @@ #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 diff --git a/direwolf.spec b/direwolf.spec new file mode 100644 index 0000000..3c64e62 --- /dev/null +++ b/direwolf.spec @@ -0,0 +1,62 @@ +Name: direwolf +Version: 1.1b1 +Release: 1%{?dist} +Summary: Soundcard based AX.25 TNC + +Group: Applications/Communications +License: GPLv2 +URL: http://home.comcast.net/~wb2osz +Source0: http://home.comcast.net/~wb2osz/Version%201.1/direwolf-%{version}.tgz +Packager: David Ranch (KI6ZHD) +Distribution: RedHat Linux + +Patch0: direwolf-makefile7.patch + +BuildRequires: automake +BuildRequires: alsa-lib-devel + + +%description +Dire Wolf is a software "soundcard" modem/TNC and APRS encoder/decoder. It can +be used stand-alone to receive APRS messages, as a digipeater, APRStt gateway, +or Internet Gateway (IGate). It can also be used as a virtual TNC for other +applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, UISS, +Linux AX25, SARTrack, RMS Express, and many others. + +%prep + +%setup -q -n %{name}-%{version} +%patch0 -p0 + +%build +make -f Makefile.linux tocalls-symbols +make %{?_smp_mflags} -f Makefile.linux + + +%install +make -f Makefile.linux install DESTDIR=$RPM_BUILD_ROOT +make -f Makefile.linux install-conf DESTDIR=$RPM_BUILD_ROOT + +# Install icon +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/ +cp dw-icon.png ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/ +mv symbols-new.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ +mv symbolsX.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ +mv tocalls.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ +desktop-file-install \ + --dir=${RPM_BUILD_ROOT}%{_datadir}/applications direwolf.desktop + + +%files +%{_sysconfdir}/ax25/direwolf.conf +%{_bindir}/* +%{_datadir}/pixmaps/dw-icon.png +%{_datadir}/applications/%{name}.desktop +%{_datadir}/direwolf/* +%{_docdir}/%{name}/* + + + +%changelog +* Sat Dec 20 2014 David Ranch - 1.1b1-1 +- new spec file diff --git a/direwolf.txt b/direwolf.txt new file mode 100644 index 0000000..c857739 --- /dev/null +++ b/direwolf.txt @@ -0,0 +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@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 new file mode 100644 index 0000000..06c876c --- /dev/null +++ b/dlq.c @@ -0,0 +1,630 @@ + +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include + +#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 */ diff --git a/dlq.h b/dlq.h new file mode 100644 index 0000000..a80dc32 --- /dev/null +++ b/dlq.h @@ -0,0 +1,30 @@ + +/*------------------------------------------------------------------ + * + * 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 */ diff --git a/dsp.c b/dsp.c index 07f1688..f2a4343 100644 --- a/dsp.c +++ b/dsp.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 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 @@ -18,8 +18,6 @@ // - - /*------------------------------------------------------------------ * * Name: dsp.c @@ -163,7 +161,7 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype } /* - * Normalize lowpass for unity gain. + * Normalize lowpass for unity gain at DC. */ G = 0; for (j=0; j= 3 && filter_size <= MAX_FILTER_SIZE); for (j=0; j + +#if __WIN32__ +#include +#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); +} diff --git a/dtime_now.h b/dtime_now.h new file mode 100644 index 0000000..5d7c39a --- /dev/null +++ b/dtime_now.h @@ -0,0 +1,3 @@ + + +extern double dtime_now (void); \ No newline at end of file diff --git a/dtmf.c b/dtmf.c index e0f7309..0102db4 100644 --- a/dtmf.c +++ b/dtmf.c @@ -1,7 +1,11 @@ + +//#define DEBUG 1 + + // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013 John Langner, WB2OSZ +// Copyright (C) 2013, 2014 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -40,9 +44,6 @@ #include "dtmf.h" -// Define for unit test. -//#define DTMF_TEST 1 - #if DTMF_TEST #define TIMEOUT_SEC 1 /* short for unit test below. */ @@ -56,25 +57,25 @@ static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 }; /* - * Current state of the decoding. + * Current state of the DTMF decoding. */ -static struct { +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]; - struct { /* Separate for each audio channel. */ + 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]; - 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; - } C[MAX_CHANS]; -} D; @@ -86,30 +87,51 @@ static struct { * Purpose: Initialize the DTMF decoder. * This should be called once at application start up time. * - * Inputs: sample_rate - Audio sample frequency, typically - * 44100, 22050, 8000, etc. + * 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 (int sample_rate) + +void dtmf_init (struct audio_s *p_audio_config) { int j; /* Loop over all tones frequencies. */ int c; /* Loop over all audio channels. */ + /* - * Processing block size. + * Pick a suitable processing block size. * Larger = narrower bandwidth, slower response. */ - D.sample_rate = sample_rate; - D.block_size = (205 * sample_rate) / 8000; + + for (c=0; cachan[c].dtmf_decode != DTMF_DECODE_OFF) { #if DEBUG - dw_printf (" freq k coef \n"); + dw_printf ("channel %d:\n", c); #endif - for (j=0; jsample_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; jblock_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate); - D.coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D.block_size)); + D->coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D->block_size)); - assert (D.coef[j] > 0 && D.coef[j] < 2.0); + 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]); + dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]); #endif + } + } } for (c=0; cn = 0; for (j=0; jQ1[j] = 0; + D->Q2[j] = 0; } - D.C[c].prev_dec = ' '; - D.C[c].debounced = ' '; - D.C[c].prev_debounced = ' '; - D.C[c].timeout = 0; + D->prev_dec = ' '; + D->debounced = ' '; + D->prev_debounced = ' '; + D->timeout = 0; } } @@ -167,29 +192,33 @@ char dtmf_sample (int c, float input) 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; iQ1[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.C[c].n++; - if (D.C[c].n == D.block_size) { + D->n++; + if (D->n == D->block_size) { int row, col; for (i=0; iQ1[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.C[c].n = 0; + D->n = 0; /* @@ -233,42 +262,42 @@ char dtmf_sample (int c, float input) decoded = rc2char[row*4+col]; } else { - decoded = '.'; + decoded = ' '; } // Consider valid only if we get same twice in a row. - if (decoded == D.C[c].prev_dec) { - D.C[c].debounced = decoded; + if (decoded == D->prev_dec) { + D->debounced = decoded; /* Reset timeout timer. */ if (decoded != ' ') { - D.C[c].timeout = ((TIMEOUT_SEC) * D.sample_rate) / D.block_size; + D->timeout = ((TIMEOUT_SEC) * D->sample_rate) / D->block_size; } } - D.C[c].prev_dec = decoded; + D->prev_dec = decoded; // Return only new button pushes. // Also report timeout after period of inactivity. ret = '.'; - if (D.C[c].debounced != D.C[c].prev_debounced) { - if (D.C[c].debounced != ' ') { - ret = D.C[c].debounced; + if (D->debounced != D->prev_debounced) { + if (D->debounced != ' ') { + ret = D->debounced; } } if (ret == '.') { - if (D.C[c].timeout > 0) { - D.C[c].timeout--; - if (D.C[c].timeout == 0) { + if (D->timeout > 0) { + D->timeout--; + if (D->timeout == 0) { ret = '$'; } } } - D.C[c].prev_debounced = D.C[c].debounced; + D->prev_debounced = D->debounced; #if DEBUG - dw_printf (" dec=%c, deb=%c, ret=%c \n", - decoded, D.C[c].debounced, ret); + dw_printf (" dec=%c, deb=%c, ret=%c, to=%d \n", + decoded, D->debounced, ret, D->timeout); #endif return (ret); } @@ -283,6 +312,8 @@ char dtmf_sample (int c, float input) * * Purpose: Unit test for functions above. * + * Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe + * *----------------------------------------------------------------*/ @@ -298,6 +329,7 @@ push_button (char button, int ms) char x; static char result[100]; static int result_len = 0; + int c = 0; // fake channel number. switch (button) { @@ -319,9 +351,15 @@ push_button (char button, int ms) 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); @@ -331,11 +369,11 @@ push_button (char button, int ms) default: fa = 0; fb = 0; } - for (i = 0; i < (ms*D.sample_rate)/1000; i++) { + for (i = 0; i < (ms*dd[c].sample_rate)/1000; i++) { input = sin(phasea) + sin(phaseb); - phasea += 2 * M_PI * fa / D.sample_rate; - phaseb += 2 * M_PI * fb / D.sample_rate; + 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. */ @@ -351,10 +389,19 @@ push_button (char button, int ms) } } +static struct audio_s my_audio_config; + + main () { - dtmf_init(44100); + 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. */ @@ -389,7 +436,7 @@ main () dw_printf ("\nTest timeout after inactivity.\n\n"); /* For this test we use 1 second. */ - /* In practice, it will probably more like 10 or 20. */ + /* In practice, it will probably more like 5. */ push_button ('1', 250); push_button (' ', 500); push_button ('2', 250); push_button (' ', 500); diff --git a/dtmf.h b/dtmf.h index 5c3c584..dea09df 100644 --- a/dtmf.h +++ b/dtmf.h @@ -1,7 +1,9 @@ /* dtmf.h */ -void dtmf_init (int sample_rate); +#include "audio.h" + +void dtmf_init (struct audio_s *p_audio_config); char dtmf_sample (int c, float input); diff --git a/dwespeak.bat b/dwespeak.bat new file mode 100644 index 0000000..8ea2150 --- /dev/null +++ b/dwespeak.bat @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..341d2cf --- /dev/null +++ b/dwespeak.sh @@ -0,0 +1,5 @@ +#!/bin/bash +chan=$1 +msg=$2 +sleep 1 +espeak -v en-sc "$msg" diff --git a/fsk_demod_state.h b/fsk_demod_state.h index 500bf79..9185da6 100644 --- a/fsk_demod_state.h +++ b/fsk_demod_state.h @@ -10,6 +10,7 @@ * 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, @@ -17,12 +18,16 @@ typedef enum bp_window_e { BP_WINDOW_TRUNCATED, 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. @@ -37,15 +42,19 @@ struct demodulator_state_s #define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */ /* - * FIR filter length relative to one bit time. - * Use same for both bandpass and lowpass. + * Filter length for Mark & Space in bit times. + * e.g. 1 means 1/1200 second for 1200 baud. */ - float filter_len_bits; + float ms_filter_len_bits; /* - * Window type for the mark/space filters. + * Window type for the various filters. */ - bp_window_t bp_window; + + bp_window_t pre_window; + bp_window_t ms_window; + bp_window_t lp_window; + /* * Alternate Low pass filters. @@ -53,18 +62,38 @@ struct demodulator_state_s * Second is frequency as ratio to baud rate for FIR. */ int lpf_use_fir; /* 0 for IIR, 1 for FIR. */ - float lpf_iir; - float lpf_baud; + + 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. @@ -87,6 +116,10 @@ struct demodulator_state_s /* 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))); /* @@ -102,18 +135,23 @@ struct demodulator_state_s /* * The rest are continuously updated. */ - 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. /* * 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. @@ -126,8 +164,6 @@ struct demodulator_state_s * Kernel for the lowpass filters. */ - int lp_filter_size; - float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); @@ -138,43 +174,51 @@ struct demodulator_state_s 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 -/* These are used only for "9600" baud data. */ - - int lfsr; // Descrambler shift register. - - -/* - * Finally, try to come up with some sort of measure of the audio input level. - * Let's try gathering both the peak and average of the - * absolute value of the input signal over some period such as 100 mS. - * - */ - int lev_period; // How many samples go into one measure. - - int lev_count; // Number accumulated so far. - - float lev_peak_acc; // Highest peak so far. - - float lev_sum_acc; // Accumulated sum so far. - -/* - * These will be updated every 'lev_period' samples: - */ - float lev_last_peak; - float lev_last_ave; - float lev_prev_peak; - float lev_prev_ave; /* * Special for Rino decoder only. * One for each possible signal polarity. */ -#if 1 +#if 0 struct gr_state_s { diff --git a/gen_packets.c b/gen_packets.c index f37665c..a98a83c 100644 --- a/gen_packets.c +++ b/gen_packets.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 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 @@ -22,14 +22,41 @@ * * Name: gen_packets.c * - * Purpose: Test program for generating AFSK AX.25 frames. + * 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. * - * Bugs: Most options not implemented for second 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 + * + * *------------------------------------------------------------------*/ @@ -55,283 +82,271 @@ 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 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; + 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 */ + case 'B': /* -B for data Bit rate */ /* 300 implies 1600/1800 AFSK. */ /* 1200 implies 1200/2200 AFSK. */ /* 9600 implies scrambled. */ - modem.baud[0] = atoi(optarg); - text_color_set(DW_COLOR_INFO); - dw_printf ("Data rate set to %d bits / second.\n", modem.baud[0]); - if (modem.baud[0] < 100 || modem.baud[0] > 10000) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); - exit (EXIT_FAILURE); - } + 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.baud[0]) { - case 300: - modem.mark_freq[0] = 1600; - modem.space_freq[0] = 1800; - break; - case 1200: - modem.mark_freq[0] = 1200; - modem.space_freq[0] = 2200; - break; - case 9600: - modem.modem_type[0] = SCRAMBLE; - text_color_set(DW_COLOR_INFO); - dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); - break; - } - break; + 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 */ + case 'g': /* -g for g3ruh scrambling */ - modem.modem_type[0] = SCRAMBLE; - text_color_set(DW_COLOR_INFO); - dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); - break; + 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 */ + case 'm': /* -m for Mark freq */ - modem.mark_freq[0] = atoi(optarg); - text_color_set(DW_COLOR_INFO); - dw_printf ("Mark frequency set to %d Hz.\n", modem.mark_freq[0]); - if (modem.mark_freq[0] < 300 || modem.mark_freq[0] > 3000) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable value in range of 300 - 3000.\n"); - exit (EXIT_FAILURE); - } - break; + 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 */ + case 's': /* -s for Space freq */ - modem.space_freq[0] = atoi(optarg); - text_color_set(DW_COLOR_INFO); - dw_printf ("Space frequency set to %d Hz.\n", modem.space_freq[0]); - if (modem.space_freq[0] < 300 || modem.space_freq[0] > 3000) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable value in range of 300 - 3000.\n"); - exit (EXIT_FAILURE); - } - break; + 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. */ + case 'n': /* -n number of packets with increasing noise. */ - packet_count = atoi(optarg); + packet_count = atoi(optarg); - g_add_noise = 1; + g_add_noise = 1; - break; + break; - case 'a': /* -a for amplitude */ + 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 > 100) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Amplitude must be in range of 0 to 100.\n"); - exit (EXIT_FAILURE); - } - break; + 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 */ + case 'r': /* -r for audio sample Rate */ - modem.samples_per_sec = atoi(optarg); - text_color_set(DW_COLOR_INFO); - dw_printf ("Audio sample rate set to %d samples / second.\n", modem.samples_per_sec); - if (modem.samples_per_sec < MIN_SAMPLES_PER_SEC || modem.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", + 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; + exit (EXIT_FAILURE); + } + break; - case 'z': /* -z leading zeros before frame flag */ + 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); + 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 translast to time yet because the data bit rate */ - /* could be changed later. */ + /* 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) { + 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 ("Use a more reasonable value.\n"); - exit (EXIT_FAILURE); - } - break; - - case '8': /* -8 for 8 bit samples */ - - modem.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.num_channels = 2; - 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); - } - } - - 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); + dw_printf("?? getopt returned character code 0%o ??\n", c); + usage (argv); } - 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); } - if (input_fp != stdin) { - fclose (input_fp); - } - } - else { - text_color_set(DW_COLOR_INFO); - dw_printf ("built in message...\n"); - } +/* + * Open the output file. + */ if (strlen(output_file) == 0) { text_color_set(DW_COLOR_ERROR); @@ -350,12 +365,67 @@ int main(int argc, char **argv) } - gen_tone_init (&modem, amplitude); + gen_tone_init (&modem, amplitude/2); - assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16); - assert (modem.num_channels == 1 || modem.num_channels == 2); - assert (modem.samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.samples_per_sec <= MAX_SAMPLES_PER_SEC); + 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) { @@ -368,24 +438,18 @@ int main(int argc, char **argv) char stemp[80]; - if (modem.modem_type[0] == SCRAMBLE) { - g_noise_level = 0.33 * (amplitude / 100.0) * ((float)i / packet_count); + if (modem.achan[0].modem_type == MODEM_SCRAMBLE) { + g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count); } else { - g_noise_level = (amplitude / 100.0) * ((float)i / packet_count); + /* About 2/3 should be decoded properly. */ + g_noise_level = amplitude *.0023 * ((float)i / packet_count); } - sprintf (stemp, "WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world! %04d", i); + sprintf (stemp, "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! %04d of %04d", i, packet_count); + + send_packet (stemp); - pp = ax25_from_text (stemp, 1); - flen = ax25_pack (pp, fbuf); - for (c=0; cAPRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world!", 1); - flen = ax25_pack (pp, fbuf); - for (c=0; cAPRS,W1AB-9*,W1ABC-10,WB1ABC-15:,Hello, world!", 1); - flen = ax25_pack (pp, fbuf); - for (c=0; cAPRS,W1AB-9,W1ABC-10*,WB1ABC-15:,Hello, world!", 1); - flen = ax25_pack (pp, fbuf); - for (c=0; cAPRS,W1AB-9,W1ABC-10,WB1ABC-15*:,Hello, world!", 1); - flen = ax25_pack (pp, fbuf); - for (c=0; cTEST:,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(); @@ -444,9 +475,9 @@ static void usage (char **argv) text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); - dw_printf ("Usage: xxx [options] [file]\n"); + dw_printf ("Usage: gen_packets [options] [file]\n"); dw_printf ("Options:\n"); - dw_printf (" -a Signal amplitude in range of 0 - 100%%. Default 50.\n"); + dw_printf (" -a Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD); dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 9600.\n"); dw_printf (" -g Scrambled baseband rather than AFSK.\n"); @@ -455,34 +486,34 @@ static void usage (char **argv) dw_printf (" -r Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC); dw_printf (" -n Generate specified number of frames with increasing noise.\n"); dw_printf (" -o 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 of leading zero bits before frame.\n"); - dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\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 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-n message. The format should correspond to ...\n"); - dw_printf ("blah blah blah. For example,\n"); - dw_printf (" WB2OSZ-1>APDW10,WIDE2-2:!4237.14NS07120.83W#\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: %s\n", argv[0]); + 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: %s -g -b 9600\n", argv[0]); - dw_printf ("Shortcut: %s -B 9600\n", argv[0]); + 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: %s -m 1600 -s 1800 -b 300\n", argv[0]); - dw_printf ("Shortcut: %s -B 300\n", argv[0]); + 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!\" | %s -a 25 -o x.wav -\n", argv[0]); + 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"); @@ -548,14 +579,14 @@ static int audio_file_open (char *fname, struct audio_s *pa) /* * Fill in defaults for any missing values. */ - if (pa -> num_channels == 0) - pa -> num_channels = DEFAULT_NUM_CHANNELS; + if (pa -> adev[0].num_channels == 0) + pa -> adev[0].num_channels = DEFAULT_NUM_CHANNELS; - if (pa -> samples_per_sec == 0) - pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + if (pa -> adev[0].samples_per_sec == 0) + pa -> adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; - if (pa -> bits_per_sample == 0) - pa -> bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + if (pa -> adev[0].bits_per_sample == 0) + pa -> adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* @@ -578,9 +609,9 @@ static int audio_file_open (char *fname, struct audio_s *pa) header.fmtsize = 16; // Always 16. header.wformattag = 1; // 1 for PCM. - header.nchannels = pa -> num_channels; - header.nsamplespersec = pa -> samples_per_sec; - header.wbitspersample = pa -> bits_per_sample; + 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; @@ -628,7 +659,7 @@ static int audio_file_open (char *fname, struct audio_s *pa) *----------------------------------------------------------------*/ -int audio_put (int c) +int audio_put (int a, int c) { static short sample16; int s; @@ -669,7 +700,7 @@ int audio_put (int c) } /* end audio_put */ -int audio_flush () +int audio_flush (int a) { return 0; } @@ -693,8 +724,8 @@ static int audio_file_close (void) { int n; - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_close()\n"); + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("audio_close()\n"); /* * Go back and fix up lengths in header. diff --git a/gen_tone.c b/gen_tone.c index 5500204..cbf481f 100644 --- a/gen_tone.c +++ b/gen_tone.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011 John Langner, WB2OSZ +// 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 @@ -40,11 +40,14 @@ #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 modem; +static struct audio_s *save_audio_config_p; /* * 8 bit samples are unsigned bytes in range of 0 .. 255. @@ -57,7 +60,9 @@ static struct audio_s modem; #define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) -static int ticks_per_sample; /* same for all channels. */ +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]; @@ -76,6 +81,61 @@ 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; jachan[chan].valid) { - f1_change_per_sample[chan] = (int) (((double)modem.mark_freq[chan] * TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5); + int a = ACHAN2ADEV(chan); - f2_change_per_sample[chan] = (int) (((double)modem.space_freq[chan] * TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5); + ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); - tone_phase[chan] = 0; + 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; + bit_len_acc[chan] = 0; - lfsr[chan] = 0; + lfsr[chan] = 0; + } } for (j=0; j<256; j++) { @@ -149,6 +217,55 @@ int gen_tone_init (struct audio_s *pp, int amp) 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 */ @@ -171,11 +288,14 @@ int gen_tone_init (struct audio_s *pp, int amp) * *--------------------------------------------------------------------*/ +static void put_sample (int chan, int a, int sam); + void tone_gen_put_bit (int chan, int dat) { - int cps = dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; - unsigned short sam = 0; - int x; + int a = ACHAN2ADEV(chan); /* device for channel. */ + + + assert (save_audio_config_p->achan[chan].valid); if (dat < 0) { @@ -184,7 +304,9 @@ void tone_gen_put_bit (int chan, int dat) dat = 0; } - if (modem.modem_type[chan] == SCRAMBLE) { + 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; @@ -192,51 +314,34 @@ void tone_gen_put_bit (int chan, int dat) do { - if (modem.modem_type[chan] == AFSK) { - tone_phase[chan] += cps; + 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 { - // TODO: Might want to low pass filter this. - sam = dat ? amp16bit : (-amp16bit); - } + + float fsam = dat ? amp16bit : (-amp16bit); - /* Ship out an audio sample. */ + /* version 1.2 - added a low pass filter instead of square wave out. */ - assert (modem.num_channels == 1 || modem.num_channels == 2); + push_sample (fsam, raw[chan], lp_filter_size[chan]); + + resample[chan]++; + if (resample[chan] >= UPSAMPLE) { + int sam; - /* Generalize to allow 8 bits someday? */ - - assert (modem.bits_per_sample == 16); - - - if (modem.num_channels == 1) - { - audio_put (sam & 0xff); - audio_put ((sam >> 8) & 0xff); - } - else if (modem.num_channels == 2) - { - if (chan == 1) - { - audio_put (0); // silent left - audio_put (0); - } - - audio_put (sam & 0xff); - audio_put ((sam >> 8) & 0xff); - //byte_count += 2; - - if (chan == 0) - { - audio_put (0); // silent right - audio_put (0); + 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; + bit_len_acc[chan] += ticks_per_sample[chan]; } while (bit_len_acc[chan] < ticks_per_bit[chan]); @@ -244,6 +349,53 @@ void tone_gen_put_bit (int chan, int dat) } +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 @@ -268,26 +420,26 @@ int main () int chan1 = 0; int chan2 = 1; int r; - struct audio_s audio_param; + struct audio_s my_audio_config; /* to sound card */ /* one channel. 2 times: one second of each tone. */ - memset (&audio_param, 0, sizeof(audio_param)); - strcpy (audio_param.adevice_in, DEFAULT_ADEVICE); - strcpy (audio_param.adevice_out, DEFAULT_ADEVICE); + 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 (&audio_param); - gen_tone_init (&audio_param, 100); + audio_open (&my_audio_config); + gen_tone_init (&my_audio_config, 100); for (r=0; r<2; r++) { - for (n=0; n +#include + +#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 new file mode 100644 index 0000000..43e591d --- /dev/null +++ b/geotranz/error_string.h @@ -0,0 +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); diff --git a/geotranz/mgrs.c b/geotranz/mgrs.c new file mode 100644 index 0000000..9fb8289 --- /dev/null +++ b/geotranz/mgrs.c @@ -0,0 +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 +#include +#include +#include +#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, <r2_low_value, <r2_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, <r2_low_value, <r2_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 new file mode 100644 index 0000000..79a1c28 --- /dev/null +++ b/geotranz/mgrs.h @@ -0,0 +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 */ diff --git a/geotranz/polarst.c b/geotranz/polarst.c new file mode 100644 index 0000000..0cafaa4 --- /dev/null +++ b/geotranz/polarst.c @@ -0,0 +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 +#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 */ + + + diff --git a/geotranz/polarst.h b/geotranz/polarst.h new file mode 100644 index 0000000..60b8aa0 --- /dev/null +++ b/geotranz/polarst.h @@ -0,0 +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 */ + diff --git a/geotranz/readme.txt b/geotranz/readme.txt new file mode 100644 index 0000000..fd2488a --- /dev/null +++ b/geotranz/readme.txt @@ -0,0 +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 Intelligence has endorsed any product derived from GEOTRANS. diff --git a/geotranz/releasenotes.txt b/geotranz/releasenotes.txt new file mode 100644 index 0000000..ab863a9 --- /dev/null +++ b/geotranz/releasenotes.txt @@ -0,0 +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. + + + + + + diff --git a/geotranz/tranmerc.c b/geotranz/tranmerc.c new file mode 100644 index 0000000..893db7e --- /dev/null +++ b/geotranz/tranmerc.c @@ -0,0 +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 +#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 new file mode 100644 index 0000000..5755ddd --- /dev/null +++ b/geotranz/tranmerc.h @@ -0,0 +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 */ diff --git a/geotranz/ups.c b/geotranz/ups.c new file mode 100644 index 0000000..f9975f2 --- /dev/null +++ b/geotranz/ups.c @@ -0,0 +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 +#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 new file mode 100644 index 0000000..9f6c817 --- /dev/null +++ b/geotranz/ups.h @@ -0,0 +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 */ diff --git a/geotranz/usng.c b/geotranz/usng.c new file mode 100644 index 0000000..dbce146 --- /dev/null +++ b/geotranz/usng.c @@ -0,0 +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 +#include +#include +#include +#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, <r2_low_value, <r2_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, <r2_low_value, <r2_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 new file mode 100644 index 0000000..456cb37 --- /dev/null +++ b/geotranz/usng.h @@ -0,0 +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 */ diff --git a/geotranz/utm.c b/geotranz/utm.c new file mode 100644 index 0000000..81c0f46 --- /dev/null +++ b/geotranz/utm.c @@ -0,0 +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 */ diff --git a/geotranz/utm.h b/geotranz/utm.h new file mode 100644 index 0000000..0b32051 --- /dev/null +++ b/geotranz/utm.h @@ -0,0 +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 */ diff --git a/hdlc_rec.c b/hdlc_rec.c index e2d6bd1..e645ee1 100644 --- a/hdlc_rec.c +++ b/hdlc_rec.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 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 @@ -18,6 +18,7 @@ // + /******************************************************************************** * * File: hdlc_rec.c @@ -38,6 +39,8 @@ #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. */ @@ -65,10 +68,15 @@ 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. */ @@ -93,9 +101,6 @@ struct hdlc_state_s { /* This will not be triggered by voice or other */ /* noise or even tones. */ - enum retry_e fix_bits; /* Level of effort to recover from */ - /* a bad FCS on the frame. */ - rrbb_t rrbb; /* Handle for bit array for raw received bits. */ }; @@ -103,7 +108,11 @@ struct hdlc_state_s { static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS]; -static int num_subchan[MAX_CHANS]; +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); /*********************************************************************************** @@ -128,27 +137,35 @@ void hdlc_rec_init (struct audio_s *pa) assert (pa != NULL); - for (j=0; jnum_channels; j++) + for (j=0; jnum_subchan[j]; + composite_dcd[j] = 0; - assert (num_subchan[j] >= 1 && num_subchan[j] < MAX_SUBCHANS); + if (pa->achan[j].valid) { - for (k=0; kachan[j].num_subchan; - H->prev_raw = 0; - H->pat_det = 0; - H->flag4_det = 0; - H->olen = -1; - H->frame_len = 0; - H->data_detect = 0; - H->fix_bits = pa->fix_bits; - H->rrbb = rrbb_new(j, k, pa->modem_type[j] == SCRAMBLE, -1); + assert (num_subchan[j] >= 1 && num_subchan[j] <= MAX_SUBCHANS); + + for (k=0; kprev_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; } @@ -171,8 +188,6 @@ void hdlc_rec_init (struct audio_s *pa) * * descram_state - Current descrambler state. * - * sam - Possible future: Sample from demodulator output. - * Should nominally be in roughly -1 to +1 range. * * Description: This is called once for each received bit. * For each valid frame, process_rec_frame() @@ -180,11 +195,11 @@ void hdlc_rec_init (struct audio_s *pa) * ***********************************************************************************/ -#if SLICENDICE -void hdlc_rec_bit_sam (int chan, int subchan, int raw, float demod_out) -#else -void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state) -#endif +// 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. */ @@ -207,8 +222,19 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram * A '1' bit is represented by no change. */ - dbit = (raw == H->prev_raw); - H->prev_raw = raw; + 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. @@ -226,37 +252,49 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram /* - * "Data Carrier detect" function based on data rather than - * tones from a modem. + * "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. + * has also been observed rarely. + * Recognize zero(s) followed by a flag even though it vilolates the spec. */ - if (H->flag4_det == 0x7e7e7e7e) { - +/* + * 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; -#if DEBUG3 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("DCD%d = 1 flags\n", chan); -#endif + dcd_change (chan, subchan, 1); } } - - if (H->flag4_det == 0x7e000000) { + //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; -#if DEBUG3 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("DCD%d = 1 zero fill\n", chan); -#endif + dcd_change (chan, subchan, 1); } } @@ -271,10 +309,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram if ( H->data_detect ) { H->data_detect = 0; -#if DEBUG3 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("DCD%d = 0\n", chan); -#endif + dcd_change (chan, subchan, 0); } } @@ -285,11 +320,9 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram * The rest is concerned with framing. */ -#if SLICENDICE - rrbb2_append_bit (H->rrbb, demod_out); -#else + rrbb_append_bit (H->rrbb, raw); -#endif + if (H->pat_det == 0x7e) { rrbb_chop8 (H->rrbb); @@ -338,7 +371,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram expected_fcs = fcs_calc (H->frame_buf, H->frame_len - 2); if (actual_fcs == expected_fcs) { - int alevel = demod_get_audio_level (chan, subchan); + 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. */ } @@ -360,33 +393,58 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram #if TEST text_color_set(DW_COLOR_DEBUG); - dw_printf ("\nfound flag, %d bits in frame\n", rrbb_get_len(H->rrbb) - 1); + 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) { - int alevel = demod_get_audio_level (chan, subchan); + alevel_t alevel = demod_get_audio_level (chan, subchan); rrbb_set_audio_level (H->rrbb, alevel); - rrbb_set_fix_bits (H->rrbb, H->fix_bits); - hdlc_rec2_block (H->rrbb, H->fix_bits); + hdlc_rec2_block (H->rrbb); /* Now owned by someone else who will free it. */ - H->rrbb = rrbb_new (chan, subchan, is_scrambled, descram_state); /* Allocate a new one. */ + H->rrbb = rrbb_new (chan, subchan, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */ } else { - rrbb_clear (H->rrbb, is_scrambled, descram_state); + rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); } H->olen = 0; /* Allow accumulation of octets. */ H->frame_len = 0; -#if SLICENDICE - rrbb2_append_bit (H->rrbb, H->prev_raw ? 1.0 : -1.0); /* Last bit of flag. Needed to get first data bit. */ -#else + rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */ -#endif + /* 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) { /* @@ -397,15 +455,13 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram * 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, descram_state); -#if SLICENDICE - rrbb2_append_bit (H->rrbb, H->prev_raw ? 1.0 : -1.0); /* Last bit of flag. Needed to get first data bit. */ -#else - rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */ -#endif + rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); + } else if ( (H->pat_det & 0xfc) == 0x7c ) { @@ -449,8 +505,105 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram /*------------------------------------------------------------------- * - * Name: hdlc_rec_data_detect_1 - * hdlc_rec_data_detect_any + * 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. @@ -458,7 +611,7 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram * This is used by the transmit logic to transmit only * when the channel is clear. * - * Inputs: chan - Audio channel. 0 for left, 1 for right. + * Inputs: chan - Audio channel. * * Returns: True if channel is busy (data detected) or * false if OK to transmit. @@ -466,10 +619,6 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram * * Description: We have two different versions here. * - * hdlc_rec_data_detect_1 tests a single decoder (subchan) - * and is used by the DPLL to determine how much inertia - * to use when trying to follow the incoming signal. - * * 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 @@ -480,31 +629,13 @@ void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram * *--------------------------------------------------------------------*/ -int hdlc_rec_data_detect_1 (int chan, int subchan) -{ - assert (chan >= 0 && chan < MAX_CHANS); - - return ( hdlc_state[chan][subchan].data_detect ); - -} /* end hdlc_rec_data_detect_1 */ - - int hdlc_rec_data_detect_any (int chan) { int subchan; assert (chan >= 0 && chan < MAX_CHANS); - for (subchan = 0; subchan < num_subchan[chan]; subchan++) { - - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - - if (hdlc_state[chan][subchan].data_detect) { - return (1); - } - } - return (0); - + return (composite_dcd[chan] != 0); } /* end hdlc_rec_data_detect_any */ diff --git a/hdlc_rec.h b/hdlc_rec.h index 1bc4101..647dd77 100644 --- a/hdlc_rec.h +++ b/hdlc_rec.h @@ -2,23 +2,29 @@ #include "audio.h" -#include "rrbb.h" /* Possibly defines SLICENDICE. */ +//#include "rrbb.h" void hdlc_rec_init (struct audio_s *pa); -#if SLICENDICE -void hdlc_rec_bit_sam (int chan, int subchan, int raw, float demod_out); -#else + void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state); -#endif + /* 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_1 (int chan, int subchan); + int hdlc_rec_data_detect_any (int chan); diff --git a/hdlc_rec2.c b/hdlc_rec2.c index 78c4532..e6553e9 100644 --- a/hdlc_rec2.c +++ b/hdlc_rec2.c @@ -1,6 +1,6 @@ // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ +// 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 @@ -43,6 +43,39 @@ * 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 @@ -59,10 +92,20 @@ #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. @@ -87,6 +130,11 @@ struct hdlc_state_s { /* 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. */ @@ -104,15 +152,49 @@ struct hdlc_state_s { }; -#define MAX_RETRY_SWAP_BITS 24 /* Maximum number of contiguous bits to swap */ +#define MAX_RETRY_SWAP_BITS 24 /* Maximum number of contiguous bits to swap */ #define MAX_RETRY_REMOVE_SEPARATED_BITS 24 /* Maximum number of contiguous bits to remove */ -static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_conf_t retry_conf); -static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits); -static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped); -#if DEBUG_LATER -static double dtime_now (void); -#endif +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; +} + + /*********************************************************************************** * @@ -121,7 +203,6 @@ static double dtime_now (void); * Purpose: Extract HDLC frame from a stream of bits. * * Inputs: block - Handle for bit array. - * fix_bits - Level of effort to recover frames with bad FCS. * * Description: The other (original) hdlc decoder took one bit at a time * right out of the demodulator. @@ -132,21 +213,18 @@ static double dtime_now (void); * This allows us to try decoding the same received data more * than once. * - * Bugs: This does not work for 9600 baud, more accurately when - * the transmitted bits are scrambled. - * - * Currently we unscramble the bits as they come from the - * receiver. Instead we need to save the original received - * bits and apply the descrambling after flipping the bits. + * Version 1.2: Now works properly for G3RUH type scrambling. * ***********************************************************************************/ -void hdlc_rec2_block (rrbb_t block, retry_t fix_bits) +void hdlc_rec2_block (rrbb_t block) { int chan = rrbb_get_chan(block); int subchan = rrbb_get_subchan(block); - int alevel = rrbb_get_audio_level(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; @@ -155,38 +233,14 @@ void hdlc_rec2_block (rrbb_t block, retry_t fix_bits) dw_printf ("\n--- try to decode ---\n"); #endif -#if SLICENDICE -/* - * Unfinished experiment. Get back to this again someday. - * The demodulator output is (should be) roughly in the range of -1 to 1. - * Formerly we sliced it at 0 and saved only a single bit for the sample. - * Now we save the sample so we can try adjusting the slicing point. - * - * First time thru, set the slicing point to 0. - */ - - for (n = 0; n < 1 ; n++) { - - rrbb_set_slice_val (block, n); - - ok = try_decode (block, chan, subchan, alevel, RETRY_NONE, -1, -1, -1); - - if (ok) { -//#if DEBUG - text_color_set(DW_COLOR_INFO); - dw_printf ("Got it with no errors. Slice val = %d \n", n); -//#endif - rrbb_delete (block); - return; - } - } - rrbb_set_slice_val (block, 0); - -#else /* not SLICENDICE */ /* Create an empty retry configuration */ retry_conf_t retry_cfg; - /* By default we don't try to alter any bits */ +/* + * 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; @@ -195,7 +249,7 @@ void hdlc_rec2_block (rrbb_t block, retry_t fix_bits) /* 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); + ok = try_decode (block, chan, subchan, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE)); if (ok) { #if DEBUG @@ -205,31 +259,80 @@ void hdlc_rec2_block (rrbb_t block, retry_t fix_bits) rrbb_delete (block); return; } -#endif - - if (try_to_fix_quick_now (block, chan, subchan, alevel, fix_bits)) { + +/* + * 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; } /* - * Put in queue for retrying later at lower priority. + * 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) { + 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); - return; } - rdq_append (block); - -} +} /* end hdlc_rec2_block */ -static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits) +/*********************************************************************************** + * + * 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); @@ -241,7 +344,10 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel * Try fixing one bit. */ if (fix_bits < RETRY_SWAP_SINGLE) { - return 0; + + /* Stop before single bit fix up. */ + + return 0; /* failure. */ } /* Try to swap one bit */ retry_cfg.type = RETRY_TYPE_SWAP; @@ -251,7 +357,7 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel for (i=0; iachan[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); - fix_bits = rrbb_get_fix_bits (block); + + if (fix_bits < RETRY_SWAP_TWO_SEP) { - return ; + goto failure; } @@ -485,7 +624,7 @@ void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel ok = 0; for (j=i+2; jcomputed_data[ind]; } -static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_conf_t retry_conf) +/*********************************************************************************** + * + * 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. */ @@ -661,6 +887,9 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_co 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. */ @@ -669,8 +898,9 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_co /* 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)) { + + 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; } @@ -747,7 +977,26 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_co * 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 */ - if (raw == H.prev_raw) { + + 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) { @@ -760,7 +1009,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_co H.oacc >>= 1; H.oacc |= 0x80; } else { - H.prev_raw = raw; + /* The special pattern 01111110 indicates beginning and ending of a frame: exit. */ if (H.pat_det == 0x7e) { #if DEBUGx @@ -838,19 +1087,33 @@ static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_co 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)) { + 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); - assert (rrbb_get_audio_level(block) == alevel); 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; @@ -895,11 +1158,37 @@ end: } /* end try_decode */ -/* - * Try to weed out bogus packets from initially failed FCS matches. - */ -static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped) +/*********************************************************************************** + * + * 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; @@ -913,10 +1202,14 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped) return 1; } -#if DEBUGx - text_color_set(DW_COLOR_XMIT); - dw_printf ("sanity_check: address part length = %d\n", alen); -#endif + +/* + * 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. @@ -932,7 +1225,7 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped) if (alen % 7 != 0) { #if DEBUGx text_color_set(DW_COLOR_ERROR); - dw_printf ("sanity_check: FAILED. Address part not multiple of 7.\n"); + dw_printf ("sanity_check: FAILED. Address part length %d not multiple of 7.\n", alen); #endif return 0; } @@ -980,14 +1273,22 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped) } } + +/* + * 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. - * Checking that would mean precluding use for other types of packet operation. - * - * The next section is also assumes APRS and might discard good data - * for other protocols. */ + if (buf[alen] != 0x03 || buf[alen+1] != 0xf0) { + return (0); + } /* * Finally, look for bogus characters in the information part. @@ -1024,6 +1325,7 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped) || ch == 0x0d || ch == 0x80 || ch == 0x9f + || ch == 0xc2 || ch == 0xb0 || ch == 0xf8) ) { #if DEBUGx @@ -1041,37 +1343,3 @@ static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped) /* end hdlc_rec2.c */ - - -// TODO: Also in xmit.c. Move to some common location. - - -/* 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 time. */ - - - -static double dtime_now (void) -{ -#if __WIN32__ - /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ - - FILETIME ft; - - GetSystemTimeAsFileTime (&ft); - - return ((( (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); - - return (ts.tv_sec + ts.tv_nsec / 1000000000.); -#endif -} - diff --git a/hdlc_rec2.h b/hdlc_rec2.h index 1008e7c..ccc95ee 100644 --- a/hdlc_rec2.h +++ b/hdlc_rec2.h @@ -3,24 +3,12 @@ #define HDLC_REC2_H 1 -#include "ax25_pad.h" /* for packet_t */ +#include "ax25_pad.h" /* for packet_t, alevel_t */ #include "rrbb.h" +#include "audio.h" /* for struct audio_s */ + + -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 retry_mode_e { RETRY_MODE_CONTIGUOUS=0, @@ -72,16 +60,19 @@ static const char * retry_text[] = { "TWO_SEP", "MANY", "REMOVE_MANY", - "REMOVE_SEP"}; + "REMOVE_SEP", + "PASSALL" }; #endif -void hdlc_rec2_block (rrbb_t block, retry_t fix_bits); +void hdlc_rec2_init (struct audio_s *audio_config_p); -void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel); +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, int level, retry_t retries, char *spectrum); +void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t level, retry_t retries, char *spectrum); #endif diff --git a/hdlc_send.c b/hdlc_send.c index 77d9c2f..3511488 100644 --- a/hdlc_send.c +++ b/hdlc_send.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -31,7 +31,9 @@ static void send_control (int, int); static void send_data (int, int); static void send_bit (int, int); -static int number_of_bits_sent; + + +static int number_of_bits_sent[MAX_CHANS]; @@ -74,7 +76,7 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen) int j, fcs; - number_of_bits_sent = 0; + number_of_bits_sent[chan] = 0; #if DEBUG @@ -97,7 +99,7 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen) send_control (chan, 0x7e); /* End frame */ - return (number_of_bits_sent); + return (number_of_bits_sent[chan]); } @@ -133,7 +135,7 @@ int hdlc_send_flags (int chan, int nflags, int finish) int j; - number_of_bits_sent = 0; + number_of_bits_sent[chan] = 0; #if DEBUG @@ -152,10 +154,10 @@ int hdlc_send_flags (int chan, int nflags, int finish) /* Push out the final partial buffer! */ if (finish) { - audio_flush(); + audio_flush(ACHAN2ADEV(chan)); } - return (number_of_bits_sent); + return (number_of_bits_sent[chan]); } @@ -209,7 +211,7 @@ static void send_bit (int chan, int b) tone_gen_put_bit (chan, output); - number_of_bits_sent++; + number_of_bits_sent[chan]++; } /* end hdlc_send.c */ \ No newline at end of file diff --git a/igate.c b/igate.c index 3219045..9bdec10 100644 --- a/igate.c +++ b/igate.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 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 @@ -97,7 +97,7 @@ #include "tq.h" #include "igate.h" #include "latlong.h" - +#include "pfilter.h" #if __WIN32__ @@ -191,10 +191,16 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S 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"); @@ -209,9 +215,6 @@ int main (int argc, char *argv[]) igate_config.tx_limit_5 = 5; memset (&digi_config, 0, sizeof(digi_config)); - digi_config.num_chans = 2; - strcpy (digi_config.mycall[0], "WB2OSZ-1"); - strcpy (digi_config.mycall[1], "WB2OSZ-2"); igate_init(&igate_config, &digi_config); @@ -271,15 +274,10 @@ int main (int argc, char *argv[]) * we need to reestablish the connection later. */ -static struct igate_config_s g_config; - -static int g_num_chans; /* Number of radio channels. */ - -static char g_mycall[MAX_CHANS][AX25_MAX_ADDR_LEN]; - /* Call-ssid associated */ - /* with each of the radio channels. */ - /* Could be the same or different. */ +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. @@ -324,12 +322,15 @@ static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ * * Purpose: One time initialization when main application starts up. * - * Inputs: p_igate_config - IGate configuration. - * - * p_digi_config - Digipeater configuration. All we care about is: + * 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. @@ -338,7 +339,7 @@ static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ *--------------------------------------------------------------------*/ -void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config) +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; @@ -360,6 +361,12 @@ void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_ 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; @@ -373,16 +380,6 @@ void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_ rx_to_ig_init (); ig_to_tx_init (); -/* - * Save the arguments for later use. - */ - memcpy (&g_config, p_igate_config, sizeof (g_config)); - - g_num_chans = p_digi_config->num_chans; - assert (g_num_chans >= 1 && g_num_chans <= MAX_CHANS); - for (j=0; jmycall[j]); - } /* @@ -396,7 +393,7 @@ void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_ /* * This connects to the server and sets igate_sock. - * It also sends periodic messages to say I'm still here. + * It also sends periodic messages to say I'm still alive. */ #if __WIN32__ @@ -507,10 +504,10 @@ static void * connnect_thread (void *arg) WSADATA wsadata; #endif - sprintf (server_port_str, "%d", g_config.t2_server_port); + 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", g_config.t2_server_port, server_port_str); + dw_printf ("DEBUG: igate connect_thread start, port = %d = '%s'\n", save_igate_config_p->t2_server_port, server_port_str); #endif #if __WIN32__ @@ -562,15 +559,15 @@ static void * connnect_thread (void *arg) SLEEP_SEC (5); ai_head = NULL; - err = getaddrinfo(g_config.t2_server_name, server_port_str, &hints, &ai_head); + 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", - g_config.t2_server_name, WSAGetLastError()); + save_igate_config_p->t2_server_name, WSAGetLastError()); #else dw_printf ("Can't get address for IGate server %s, %s\n", - g_config.t2_server_name, gai_strerror(err)); + save_igate_config_p->t2_server_name, gai_strerror(err)); #endif freeaddrinfo(ai_head); @@ -631,7 +628,7 @@ static void * connnect_thread (void *arg) if (err != 0) { text_color_set(DW_COLOR_INFO); dw_printf("Connect to IGate server %s (%s) failed.\n\n", - g_config.t2_server_name, ipaddr_str); + save_igate_config_p->t2_server_name, ipaddr_str); (void) close (is); is = -1; stats_failed_connect++; @@ -645,7 +642,7 @@ static void * connnect_thread (void *arg) if (err == SOCKET_ERROR) { text_color_set(DW_COLOR_INFO); dw_printf("Connect to IGate server %s (%s) failed.\n\n", - g_config.t2_server_name, ipaddr_str); + save_igate_config_p->t2_server_name, ipaddr_str); closesocket (is); is = -1; stats_failed_connect++; @@ -656,7 +653,7 @@ static void * connnect_thread (void *arg) if (err != 0) { text_color_set(DW_COLOR_INFO); dw_printf("Connect to IGate server %s (%s) failed.\n\n", - g_config.t2_server_name, ipaddr_str); + save_igate_config_p->t2_server_name, ipaddr_str); (void) close (is); is = -1; stats_failed_connect++; @@ -677,7 +674,7 @@ static void * connnect_thread (void *arg) /* Success. */ text_color_set(DW_COLOR_INFO); - dw_printf("\nNow connected to IGate server %s (%s)\n", g_config.t2_server_name, ipaddr_str ); + 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); } @@ -708,11 +705,11 @@ static void * connnect_thread (void *arg) SLEEP_SEC(3); sprintf (stemp, "user %s pass %s vers Dire-Wolf %d.%d", - g_config.t2_login, g_config.t2_passcode, + save_igate_config_p->t2_login, save_igate_config_p->t2_passcode, MAJOR_VERSION, MINOR_VERSION); - if (g_config.t2_filter != NULL) { + if (save_igate_config_p->t2_filter != NULL) { strcat (stemp, " filter "); - strcat (stemp, g_config.t2_filter); + strcat (stemp, save_igate_config_p->t2_filter); } strcat (stemp, "\r\n"); send_msg_to_server (stemp); @@ -794,6 +791,24 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) 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++; @@ -932,7 +947,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) ax25_format_addrs (pp, msg); msg[strlen(msg)-1] = '\0'; /* Remove trailing ":" */ strcat (msg, ",qAR,"); - strcat (msg, g_mycall[chan]); + strcat (msg, save_audio_config_p->achan[chan].mycall); strcat (msg, ":"); strcat (msg, (char*)pinfo); strcat (msg, "\r\n"); @@ -1155,6 +1170,7 @@ static void * igate_recv_thread (void *arg) /* * Convert to third party packet and transmit. */ + text_color_set(DW_COLOR_REC); dw_printf ("\n[ig] "); ax25_safe_print ((char *)message, len, 0); @@ -1192,17 +1208,18 @@ static void xmit_packet (char *message) 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 (g_config.tx_chan == -1) { + if (to_chan == -1) { return; } stats_tx_igate_packets++; - assert (g_config.tx_chan >= 0 && g_config.tx_chan < MAX_CHANS); + assert (save_igate_config_p->tx_chan >= 0 && save_igate_config_p->tx_chan < MAX_CHANS); /* * Try to parse it into a packet object. @@ -1217,6 +1234,30 @@ static void xmit_packet (char *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? */ @@ -1234,7 +1275,7 @@ static void xmit_packet (char *message) */ ax25_set_addr (pp3, AX25_REPEATER_1, "TCPIP"); ax25_set_h (pp3, AX25_REPEATER_1); - ax25_set_addr (pp3, AX25_REPEATER_2, g_mycall[g_config.tx_chan]); + 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); /* @@ -1256,9 +1297,9 @@ static void xmit_packet (char *message) packet_t pradio; sprintf (radio, "%s>%s%d%d%s:}%s", - g_mycall[g_config.tx_chan], + save_audio_config_p->achan[save_igate_config_p->tx_chan].mycall, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, - g_config.tx_via, + save_igate_config_p->tx_via, payload); pradio = ax25_from_text (radio, 1); @@ -1268,7 +1309,7 @@ static void xmit_packet (char *message) ax25_delete (pradio); #else /* This consumes packet so don't reference it again! */ - tq_append (g_config.tx_chan, TQ_PRIO_1_LO, pradio); + tq_append (save_igate_config_p->tx_chan, TQ_PRIO_1_LO, pradio); #endif stats_rf_xmit_packets++; ig_to_tx_remember (pp3); @@ -1473,14 +1514,14 @@ static int ig_to_tx_allow (packet_t pp) if (ig2tx_time_stamp[j] >= now - 300) count_5++; } - if (count_1 >= g_config.tx_limit_1) { + 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", g_config.tx_limit_1); + 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 >= g_config.tx_limit_5) { + 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", g_config.tx_limit_5); + dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5); return 0; } diff --git a/igate.h b/igate.h index bbf5a6d..58f2649 100644 --- a/igate.h +++ b/igate.h @@ -14,6 +14,8 @@ #include "ax25_pad.h" #include "digipeater.h" +#include "audio.h" + #define DEFAULT_IGATE_PORT 14580 @@ -55,7 +57,7 @@ struct igate_config_s { /* Call this once at startup */ -void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config); +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. */ diff --git a/kiss_frame.c b/kiss_frame.c index 9c56e94..66efe85 100644 --- a/kiss_frame.c +++ b/kiss_frame.c @@ -104,6 +104,24 @@ void text_color_set (dw_color_t c) +/*------------------------------------------------------------------- + * + * 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 @@ -267,9 +285,7 @@ static int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out) * * Outputs: kf - Current state is updated. * - * Returns: TRUE when a complete frame is ready for processing. - // TODO: void later - * + * Returns: none. * *-----------------------------------------------------------------*/ @@ -433,6 +449,7 @@ 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; @@ -452,22 +469,24 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug) 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"); + 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. */ - // Should really check if single or dual channel mode. - // Do more thoroughly in 1.2. - - if (port != 0 && port != 1) { + if (port < 0 || port >= MAX_CHANS || ! save_audio_config_p->achan[port].valid) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Invalid channel %d from KISS client app.\n", port); + 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; } - - pp = ax25_from_frame (kiss_msg+1, kiss_len-1, -1); + + 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"); @@ -493,7 +512,7 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug) case 1: /* TXDELAY */ text_color_set(DW_COLOR_INFO); - dw_printf ("KISS protocol set TXDELAY = %d, port %d\n", kiss_msg[1], port); + 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; @@ -507,14 +526,14 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug) case 3: /* SlotTime */ text_color_set(DW_COLOR_INFO); - dw_printf ("KISS protocol set SlotTime = %d, port %d\n", kiss_msg[1], port); + 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, port %d\n", kiss_msg[1], port); + 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; diff --git a/kiss_frame.h b/kiss_frame.h index 118f48e..ac84b82 100644 --- a/kiss_frame.h +++ b/kiss_frame.h @@ -1,6 +1,8 @@ /* kiss_frame.h */ +#include "audio.h" /* for struct audio_s */ + /* * Special characters used by SLIP protocol. @@ -38,6 +40,7 @@ typedef struct kiss_frame_s { } kiss_frame_t; +void kiss_frame_init (struct audio_s *pa); int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out); diff --git a/kissnet.c b/kissnet.c index 66c728f..f3a7633 100644 --- a/kissnet.c +++ b/kissnet.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011-2014 John Langner, WB2OSZ +// 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 @@ -105,6 +105,11 @@ #include #include #include +#ifdef __OpenBSD__ +#include +#else +#include +#endif #endif #include @@ -133,8 +138,6 @@ static int client_sock; /* File descriptor for socket for */ /* Set to -1 if not connected. */ /* (Don't use SOCKET type because it is unsigned.) */ -static int num_channels; /* Number of radio ports. */ - static void * connect_listen_thread (void *arg); static void * kissnet_listen_thread (void *arg); @@ -161,6 +164,8 @@ void kiss_net_set_debug (int n) * 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: @@ -192,8 +197,13 @@ void kissnet_init (struct misc_config_s *mc) memset (&kf, 0, sizeof(kf)); client_sock = -1; - num_channels = mc->num_channels; + 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. */ @@ -315,7 +325,9 @@ static void * connect_listen_thread (void *arg) 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()); + 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(); @@ -362,7 +374,9 @@ static void * connect_listen_thread (void *arg) } -#else + +#else /* End of Windows case, now Linux. */ + struct sockaddr_in sockaddr; /* Internet socket address stuct */ socklen_t sockaddr_size = sizeof(struct sockaddr_in); @@ -387,7 +401,10 @@ static void * connect_listen_thread (void *arg) if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) { text_color_set(DW_COLOR_ERROR); - perror ("connect_listen_thread: Bind failed"); + 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); } diff --git a/latlong.c b/latlong.c index 9d83cad..696ce5f 100644 --- a/latlong.c +++ b/latlong.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013,2014 John Langner, WB2OSZ +// 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 @@ -519,3 +519,166 @@ double longitude_from_nmea (char *pstr, char *phemi) 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 */ + +/* end latlong.c */ \ No newline at end of file diff --git a/latlong.h b/latlong.h index 5c926ec..ae98fe6 100644 --- a/latlong.h +++ b/latlong.h @@ -18,3 +18,7 @@ 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 409dae3..3ff663d 100644 --- a/ll2utm.c +++ b/ll2utm.c @@ -2,38 +2,97 @@ #include #include +#include -#include "LatLong-UTMconversion.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(); -void main (int argc, char *argv[]) +int main (int argc, char *argv[]) { double easting; double northing; double lat, lon; - char zone[8]; + char mgrs[32]; + char usng[32]; + char hemisphere; + long lzone; + long err; + char message[300]; + if (argc != 3) usage(); + lat = atof(argv[1]); - lat = atof(argv[1]); - if (lat < -90 || lat > 90) { - fprintf (stderr, "Latitude value is out of range.\n\n"); - usage(); + 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. } - lon = atof(argv[2]); - if (lon < -180 || lon > 180) { - fprintf (stderr, "Longitude value is out of range.\n\n"); - usage(); + +// 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); } - LLtoUTM (WSG84, lat, lon, &northing, &easting, zone); +// Same for USNG. - printf ("zone = %s, easting = %.0f, northing = %.0f\n", zone, easting, northing); + 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); } diff --git a/log.c b/log.c index 64e838b..238f875 100644 --- a/log.c +++ b/log.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014 John Langner, WB2OSZ +// 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 @@ -173,7 +173,7 @@ void log_init (char *path) * *------------------------------------------------------------------*/ -void log_write (int chan, decode_aprs_t *A, packet_t pp, int alevel, retry_t retries) +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]; @@ -256,6 +256,8 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, int alevel, retry_t ret char sstatus[40]; char stelemetry[200]; char scomment[256]; + char alevel_text[32]; + // Microsoft doesn't recognize %T as equivalent to %H:%M:%S @@ -287,6 +289,9 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, int alevel, retry_t ret } } + ax25_alevel_to_text (alevel, alevel_text); + + // Might need to quote anything that could contain comma or quote. strcpy(sdti, ""); @@ -319,13 +324,14 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, int alevel, retry_t ret strcpy (stone, ""); if (A->g_tone != G_UNKNOWN) sprintf (stone, "%.1f", A->g_tone); if (A->g_dcs != G_UNKNOWN) sprintf (stone, "D%03o", A->g_dcs); - fprintf (g_log_fp, "%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", + 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, (int)retries, sdti, + 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); } diff --git a/log.h b/log.h index e7f7a5f..5cc0264 100644 --- a/log.h +++ b/log.h @@ -6,11 +6,12 @@ #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, int alevel, retry_t retries); +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/man1/aclients.1 b/man1/aclients.1 new file mode 100644 index 0000000..38762a1 --- /dev/null +++ b/man1/aclients.1 @@ -0,0 +1,49 @@ +.TH ACLIENTS 1 + +.SH NAME +aclients \- Test program for side-by-side TNC performance comparison. + + +.SH SYNOPSIS +.B aclients +.I tnc ... +.RS +.P +Each \fItnc\fR reference is either a serial port or TCP network port followed by = and a comment. +.P +.RE + +.SH DESCRIPTION +\fBaclients\fR is used to compare how well different TNCs decode AS.25 frames. +.P + + + +.SH OPTIONS +None. + + + +.SH EXAMPLES + +.B aclients /dev/ttyS0=KPC3+ /dev/ttyUSB0=D710A 8000=DireWolf +.P +Serial port /dev/ttyS0 is connected to a KPC3+ with monitor mode turned on. +.P +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 +Packets from each are displayed in columns so it is easy to see how well each decodes +the received signals. +.P + +The "Receive Performance" section of the \fBUser Guide\fR contains some complete examples +of how to set up tests and the results. + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/atest.1 b/man1/atest.1 new file mode 100644 index 0000000..83fba0f --- /dev/null +++ b/man1/atest.1 @@ -0,0 +1,91 @@ +.TH ATEST 1 + +.SH NAME +atest \- Decode AX.25 frames from an audio file. + + +.SH SYNOPSIS +.B atest +[ \fIoptions\fR ] +.I wav-file-in +.RS +.P +\fIwav-file-in\fR is a WAV format audio file. +.P +.RE + +.SH DESCRIPTION +\fBatest\fR is a test application which decodes AX.25 frames from an audio recording. This provides an easy way to test Dire Wolf decoding performance much quicker than normal real-time. + + + +.SH OPTIONS + +.TP +.BI "-B " "n" +Bits / second for data. Proper modem selected for 300, 1200, 9600. +300 baud uses 1600/1800 Hz AFSK. +1200 (default) baud uses 1200/2200 Hz AFSK. +9600 baud uses K9NG/G2RUH standard. + +.TP +.BI "-D " "n" +Divide audio sample rate by n. + +.TP +.BI "-F " "n" +Amount of effort to try fixing frames with an invalid CRC. +0 (default) = consider only correct frames. +1 = Try to fix only a single bit. +more = Try modifying more bits to get a good CRC. + +.TP +.BI "-P " "m" +Select the demodulator type such as A, B, C, D (default for 300 baud), E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+. + + + +.SH EXAMPLES +.P +.PD 0 +.B gen_packets -o test1.wav +.P +.B atest test1.wav +.PD +.P +.PD 0 +.B gen_packets -B 300 -o test3.wav +.P +.B atest -B 300 test3.wav +.PD +.P +.PD 0 +.B gen_packets -B 9600 -o test9.wav +.P +.B atest -B 9600 test9.wav +.PD +.P +.RS +This generates and decodes 3 test files with 1200, 300, and 9600 bits per second. +.RE +.P +.PD 0 +.B atest 02_Track_2.wav +.P +.B atest -P C+ 02_Track_2.wav +.P +.B atest -F 1 02_Track_2.wav +.P +.B atest -P C+ -F 1 02_Track_2.wav +.PD +.P +.RS +Try different combinations of options to find the best decoding performance. +.RE +.P + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/decode_aprs.1 b/man1/decode_aprs.1 new file mode 100644 index 0000000..b7b24d9 --- /dev/null +++ b/man1/decode_aprs.1 @@ -0,0 +1,86 @@ +.TH DECODE_APRS 1 + +.SH NAME +decode_aprs \- Convert APRS raw data to human readable form. + + +.SH SYNOPSIS +.B decode_aprs +[ \fItext-file\fR ] +.RS +.P +\fItext-file\fR should contain AX.25 packets in the standard monitoring format. +If no file specified, data will be read from stdin. +.P +.RE + +.SH DESCRIPTION +\fBdecode_aprs\fR is useful for understanding sometimes obscure APRS packets and finding errors. + + +.SH OPTIONS +None. + + + +.SH EXAMPLES + +You see something like this show up on your screen: +.P +.RS +M0XER-3>APRS63,WIDE2-1:!/4\\;u/)K$O J]YD/A=041216|h`RY(1>q!(| +.RE +.P +What does it mean? If you haven't spent a lot of time studying the APRS protocol +specification, most of it probably looks like random noise. +Pipe it into decode_aprs to find out. +.P +.RS +.B echo 'M0XER-3>APRS63,WIDE2-1:!/4\\\\;u/)K$O J]YD/A=041216|h`RY(1>q!(|' | decode_aprs +.RE +.P + +http://www.findu.com/cgi-bin/errors.cgi has a never-ending collection of packets +with errors. Sometimes it's not obvious what is wrong with them. +Dire Wolf will usually tell you what is wrong. First, +cut-n-paste the bad packets into a text file. Here a couple examples: +.P +.RS +n2cma>APRS,TCPIP*,qAC,SEVENTH:@212127z43.2333n/77.1w_338/002g001t025P000h65b10208.wview_5_19_0 +.P +K0YTH-10>APNU3B,NULL,qAR,K0DMF-10:!4601.5NS09255.52W#PHG6360/W2,MNn 444.575 +.RE +.P +If you simply fed this into decode_aprs, it would complain about the +lower case in qA-something, added by the IGate, in the via path. +We can take it out with something like this: +.P +.RS +.B cat findu-errors.txt | sed -e 's/,qA.*:/:/' | decode_aprs +.RE +.P +In the first case, we get, +.P +.RS +Address has lower case letters. "n2cma" must be all upper case. +.RE +.P +After changing the source address to upper case, there are other issues. Identifying them is left as an exercise for the reader. +.P +And in the second example, +.P +.RS +.PD 0 +Invalid character in latitude. Found 'N' when expecting 0-9 for hundredths of minutes. +.P +Invalid character in longitude. Found '9' when expecting 0 or 1 for hundreds of degrees. +.PD +.RE + + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/direwolf.1 b/man1/direwolf.1 new file mode 100644 index 0000000..69859af --- /dev/null +++ b/man1/direwolf.1 @@ -0,0 +1,136 @@ +.TH DIREWOLF 1 + +.SH NAME +direwolf \- Soundcard TNC for packet radio. + + +.SH SYNOPSIS +.B direwolf +[ \fIoptions\fR ] +[ \- | \fBudp:\fR9999 ] +.P +The first audio channel can be streamed thru stdin or a UDP port. This is typically used with an SDR receiver. + + +.SH DESCRIPTION +\fBdirewolf\fR is a software "soundcard" modem/TNC and APRS encoder/decoder. +It can be used stand-alone to receive APRS messages, as a digipeater, +APRStt gateway, or Internet Gateway (IGate). +It can also be used as a virtual TNC for other applications such as +APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, UISS, Linux AX25, SARTrack, +RMS Express, and many others. + + +.SH OPTIONS +.TP +.BI "-c " "file" +Read configuration file from specified location rather than the default locations. + +.TP +.BI "-l " "dir" +Generate log files in specified directory. Use "." for current directory. + +.TP +.BI "-r " "n" +Audio sample rate per second for first channel. Default 44100. + +.TP +.BI "-n " "n" +Number of audio channels for first device. 1 or 2. Default 1. + +.TP +.BI "-b " "n" +Audio sample size for first channel. 8 or 16. Default 16. + +.TP +.BI "-B " "n" +Data rate in bits/sec for first channel. Standard values are 300, 1200, 9600. +.PD 0 +.RS +.RS +If < 600, tones are set to 1600 & 1800. +.P +If > 2400, K9NG/G3RUH scrambling is used. +.P +Otherwise, AFSK tones are set to 1200 & 2200. +.RE +.RE +.PD + +.TP +.BI "-D " "n" +Divide audio sample by n for first channel. + +.TP +.BI "-d " "x" +Debug options. Specify one or more of the following in place of x. +.PD 0 +.RS +.RS +a = AGWPE network protocol client. +.P +k = KISS serial port client. +.P +n = Network KISS client. +.P +u = Display non-ASCII text in hexadecimal. +.P +p = Packet dump in hexadecimal. +.P +t = GPS Tracker. +.P +o = Output controls such as PTT and DCD. +.RE +.RE +.PD + +.TP +.BI "-q " "x" +Quiet (suppress output). Specify one or more of the following in place of x. +.PD 0 +.RS +.RS +h = Heard line with the audio level. +.P +d = Decoding of APRS packets. +.RE +.RE +.PD + +.TP +.BI "-t " "n" +Text colors. 1=normal, 0=disabled. + +.TP +.B "-p " +Enable pseudo terminal for KISS protocol. + +.TP +.B "-x " +Send Xmit level calibration tones. + +.TP +.B "-U " +Print UTF-8 test string and exit. + + +.SH EXAMPLES +gqrx (2.3 and later) has the ability to send streaming audio through a UDP socket to another application for further processing. +direwolf can listen over a UDP port with options like this: +.RS +.P +direwolf -n 1 -r 48000 -b 16 udp:7355 +.RE +.P +Other SDR applications might produce audio on stdout so it is convenient to pipe into the next application. In this example, the final "-" means read from stdin. +.RS +.P +rtl_fm -f 144.39M -o 4 - | direwolf -n 1 -r 24000 -b 16 - +.RE + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/gen_packets.1 b/man1/gen_packets.1 new file mode 100644 index 0000000..b07e7f6 --- /dev/null +++ b/man1/gen_packets.1 @@ -0,0 +1,112 @@ +.TH GEN_PACKETS 1 + +.SH NAME +gen_packets \- Generate audio file for AX.25 frames. + + +.SH SYNOPSIS +.B gen_packets -o +.I wav-file-out +[ \fIoptions\fR ] [ \fItext-file\fR | - ] +.RS +.P +\fIwav-file-out\fR is the result. The -o option is required. +.P +\fItext-file\fR may contain AX.25 packets in the standard monitoring format. Use "-" to read from stdin. If not specified, a default builtin message will be used. +.RE + +.SH DESCRIPTION +\fBgen_packets\fR is a test application which converts text to AX.25 audio for testing packet decoders. + +It is very flexible allowing a wide range of audio sample rates, data speeds, and AFSK tones. It will even generate the scrambled signals commonly used for 9600 baud operation. + + +.SH OPTIONS + +.TP +.BI "-a " "n" +Signal amplitude in range of 0 - 200%. Default 50. Note that 100% is corresponds to signal peaks of +/- 16383 so we have plenty of headroom to avoid saturation. + +.TP +.BI "-b " "n" +Bits / second for data. Default is 1200. + +.TP +.BI "-B " "n" +Bits / second for data. Proper modem selected for 300, 1200, 9600. + +.TP +.BI "-g" +Scrambled baseband rather than AFSK. + +.TP +.BI "-m " "n" +Mark frequency. Default is 1200. + +.TP +.BI "-s " "n" +Space frequency. Default is 2200. + +.TP +.BI "-r " "n" +Audio sample Rate. Default is 44100. + +.TP +.BI "-n " "n" +Generate specified number of frames with increasing noise. (For built-in message only.) + +.TP +.BI "-o " "file" +Send output to .wav file. + +.TP +.B "-8" +8 bit audio rather than 16. + +.TP +.B "-2" +2 channels of audio rather than 1. + + +.SH EXAMPLES +.P +.B gen_packets -o x.wav +.P +.RS +With all defaults, a built-in test message is generated +with standard Bell 202 tones used for packet radio on ordinary +VHF FM transceivers. +.RE +.P +.B gen_packets -o x.wav -g -b 9600 +.PD 0 +.P +.PD +.B gen_packets -o x.wav -B 9600 +.P +.RS +Both of these are equivalent. "-B 9600" automatically selects scrambled baseband rather than AFSK. +.RE +.P +.B gen_packets -o x.wav -m 1600 -s 1800 -b 300 +.PD 0 +.P +.PD +.B gen_packets -o x.wav -B 300 +.P +.RS +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 - +.P +.RS +Read message from stdin and put quarter volume sound into the file x.wav. +.RE +.P + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/ll2utm.1 b/man1/ll2utm.1 new file mode 100644 index 0000000..e29e0b6 --- /dev/null +++ b/man1/ll2utm.1 @@ -0,0 +1,34 @@ +.TH LL2UTM 1 + +.SH NAME +ll2utm \- Convert Latitude and Longitude to UTM coordinates. + + +.SH SYNOPSIS +.B ll2utm +.I latitude longitude +.P +Latitude and longitude are in decimal degrees. Use negative for south or west. + +.SH DESCRIPTION +\fBll2utm\fR converts Latitude and Longitude to UTM coordinates. + + +.SH OPTIONS +.TP +None. + + +.SH EXAMPLES +.P +.B ll2utm 42.619 -71.34717 +.P +zone = 19T, easting = 307504, northing = 4721177 +.P + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/log2gpx.1 b/man1/log2gpx.1 new file mode 100644 index 0000000..f94b56b --- /dev/null +++ b/man1/log2gpx.1 @@ -0,0 +1,40 @@ +.TH LOG2GPX 1 + +.SH NAME +log2gpx \- Convert Dire Wolf log files to GPX format. + + +.SH SYNOPSIS +.B log2gpx +[ \fIfile\fR ... ] +.P +The command line can contain one or more log file names. If no files are specified, stdin is used. +.P +The result is always written to stdout. Redirect stdout to save results to a file. + + +.SH DESCRIPTION +\fBlog2gpx\fR converts Dire Wolf log files to the GPX format used by many mapping applications. +.P +Stationary entities are converted to waypoints. Moving entities are converted to tracks. + +.SH OPTIONS +.TP +None. + + +.SH EXAMPLES +.P +.B direwolf -l logdir +.P +.B log2gpx logdir/* > everybody.gpx +.P +.B egrep -e '^[^,]+,[^,]+,[^,]+,WB2OSZ,' logdir/* | log2gpx > justme.gpx +.P + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/text2tt.1 b/man1/text2tt.1 new file mode 100644 index 0000000..43a9a58 --- /dev/null +++ b/man1/text2tt.1 @@ -0,0 +1,64 @@ +.TH TEXT2TT 1 + +.SH NAME +text2tt \- Convert text to Touch Tone representation + + +.SH SYNOPSIS +.B text2tt +.I text ... +.P + + +.SH DESCRIPTION +\fBtext2tt\fR converts text to the Touch Tone sequence used by APRStt. There are two types +of encoding: +.RS +.HP +.BR "Multi-Press" " - Used for comments." +.RS +.P +Letters are represented by one or more presses of the same key depending on their order listed on the button. e.g. Press 5 key once for J, twice for K, thrice for L. +.P +To specify a digit use the number of letters listed on the button plus one. e.g. 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. +.RE +.P +.BR "Two-Key" " - Used for callsigns." +.RS +.P +Digits are represented by a single key press. +.P +Letters (or space) are represented by the corresponding key followed by A, B, C, or D depending on the order of the letter in the order listed. +.RE +.RE +.P +This application will convert using both methods. + + +.SH OPTIONS +.TP +None. + + +.SH EXAMPLES +.P +.B text2tt abcdefg 0123 +.P +.PD 0 +.P +Push buttons for multi-press method: +.P +"2A22A2223A33A33340A00122223333" +.P +Push buttons for two-key method: +.P +"2A2B2C3A3B3C4A0A0123" +.PD +.P + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/tt2text.1 b/man1/tt2text.1 new file mode 100644 index 0000000..84159c5 --- /dev/null +++ b/man1/tt2text.1 @@ -0,0 +1,66 @@ +.TH TT2TEXT 1 + +.SH NAME +tt2text \- Convert Touch Tone sequence to text + + +.SH SYNOPSIS +.B tt2text +.I touch-tone-squence +.P + + +.SH DESCRIPTION +\fBtt2text\fR converts a Touch Tone squence to text. There are two types +of encoding: +.RS +.HP +.BR "Multi-Press" " - Used for comments." +.RS +.P +Letters are represented by one or more presses of the same key depending on their order listed on the button. e.g. Press 5 key once for J, twice for K, thrice for L. +.P +To specify a digit use the number of letters listed on the button plus one. e.g. 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. +.RE +.P +.BR "Two-Key" " - Used for callsigns." +.RS +.P +Digits are represented by a single key press. +.P +Letters (or space) are represented by the corresponding key followed by A, B, C, or D depending on the order of the letter in the order listed. +.RE +.RE +.P +This application will try to decode the sequence using both methods. + + +.SH OPTIONS +.TP +None. + + +.SH EXAMPLES +.P +.B tt2text 2A22A2223A33A33340A00122223333 +.P +.PD 0 +.P +Could be either type of encoding. +.P +Decoded text from multi-press method: +.P +"ABCDEFG 0123" +.P +Decoded text from two-key method: +.P +"A2A222D3D3334 00122223333" +.PD +.P + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/man1/utm2ll.1 b/man1/utm2ll.1 new file mode 100644 index 0000000..6d420bf --- /dev/null +++ b/man1/utm2ll.1 @@ -0,0 +1,40 @@ +.TH UTM2LL 1 + +.SH NAME +utm2ll \- Convert UTM coordinates to Latitude and Longitude. + + +.SH SYNOPSIS +.B utm2ll +.I zone easting northing +.RS +.P +\fIzone\fR is UTM zone 1 thru 60 with optional latitudinal band. +.P +\fIeasting\fR is x coordinate in meters. +.P +\fInorthing\fR is y coordinate in meters. +.RE + +.SH DESCRIPTION +\fBll2utm\fR converts UTM coordinates to latitude and longitude. + + +.SH OPTIONS +.TP +None. + + +.SH EXAMPLES +.P +.B utm2ll 19T 306130 4726010 +.P +latitude = 42.662139, longitude = -71.365553 +.P + + +.SH SEE ALSO +More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. + +Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll + diff --git a/multi_modem.c b/multi_modem.c index 7d23dab..7236de5 100644 --- a/multi_modem.c +++ b/multi_modem.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 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 @@ -18,7 +18,6 @@ // - /*------------------------------------------------------------------ * * Name: multi_modem.c @@ -88,11 +87,12 @@ #include "demod.h" #include "hdlc_rec.h" #include "hdlc_rec2.h" +#include "dlq.h" // Properties of the radio channels. -static struct audio_s modem; +static struct audio_s *save_audio_config_p; // Candidates for further processing. @@ -100,7 +100,7 @@ static struct audio_s modem; static struct { packet_t packet_p; - int alevel; + alevel_t alevel; retry_t retries; int age; unsigned int crc; @@ -139,23 +139,32 @@ static void pick_best_candidate (int chan); * *------------------------------------------------------------------------------*/ -void multi_modem_init (struct audio_s *pmodem) +void multi_modem_init (struct audio_s *pa) { int chan; + /* - * Save parameters for later use. + * Save audio configuration for later use. */ - memcpy (&modem, pmodem, sizeof(modem)); + + save_audio_config_p = pa; memset (candidate, 0, sizeof(candidate)); - demod_init (pmodem); - hdlc_rec_init (pmodem); + demod_init (save_audio_config_p); + hdlc_rec_init (save_audio_config_p); - for (chan=0; chanachan[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; + } } } @@ -163,7 +172,7 @@ void multi_modem_init (struct audio_s *pmodem) //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 plast1; crc_t pnext; crc_t new_crc; @@ -204,8 +213,8 @@ int crc_queue_append (unsigned int crc, unsigned int chan) { unsigned int crc_queue_remove (unsigned int chan) { unsigned int res; - crc_t plast; - crc_t pnext; +// crc_t plast; +// crc_t pnext; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf("In crc_queue_remove\n"); @@ -258,6 +267,20 @@ unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { * 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. * *------------------------------------------------------------------------------*/ @@ -265,10 +288,19 @@ unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { __attribute__((hot)) void multi_modem_process_sample (int chan, int audio_sample) { + int d; int subchan; - for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { - demod_process_sample(chan, subchan, audio_sample); + /* 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++; @@ -276,8 +308,7 @@ void multi_modem_process_sample (int chan, int audio_sample) pick_best_candidate (chan); } } - } -} + }} @@ -383,7 +414,7 @@ void multi_modem_process_sample (int chan, int audio_sample) than one. */ -void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, int alevel, retry_t retries) +void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries) { packet_t pp; @@ -397,11 +428,12 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, return; /* oops! why would it fail? */ } + /* - * If single modem, push it thru and forget about all this foolishness. + * If single modem/deocder, push it thru and forget about all this foolishness. */ - if (modem.num_subchan[chan] == 1) { - app_process_rec_packet (chan, subchan, pp, alevel, retries, ""); + if (save_audio_config_p->achan[chan].num_subchan == 1) { + dlq_append (DLQ_REC_FRAME, chan, subchan, pp, alevel, retries, ""); return; } @@ -419,7 +451,7 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int dropped = 0; memset (spectrum, 0, sizeof(spectrum)); - memset (spectrum, '_', (size_t)modem.num_subchan[chan]); + memset (spectrum, '_', (size_t)save_audio_config_p->achan[chan].num_subchan); spectrum[subchan] = '.'; mycrc = ax25_m_m_crc(pp); @@ -446,7 +478,7 @@ void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, #if DEBUG dw_printf ("Send the best one along.\n"); #endif - app_process_rec_packet (chan, subchan, pp, alevel, retries, spectrum); + 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; @@ -497,7 +529,7 @@ static void pick_best_candidate (int chan) memset (spectrum, 0, sizeof(spectrum)); - for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { /* Build the spectrum display. */ @@ -520,7 +552,7 @@ static void pick_best_candidate (int chan) /* Bump it up slightly if others nearby have the same CRC. */ - for (k = 0; k < modem.num_subchan[chan]; k++) { + 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); @@ -532,7 +564,7 @@ static void pick_best_candidate (int chan) best_subchan = 0; best_score = 0; - for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + 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; @@ -545,7 +577,7 @@ static void pick_best_candidate (int chan) text_color_set(DW_COLOR_DEBUG); dw_printf ("\n%s\n", spectrum); - for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + 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, @@ -566,7 +598,47 @@ static void pick_best_candidate (int chan) /* * send the best one along. */ - app_process_rec_packet (chan, best_subchan, + +#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), @@ -578,16 +650,23 @@ static void pick_best_candidate (int chan) /* Clear out in preparation for next time. */ - for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) { + 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 = 0; + + 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 + } diff --git a/multi_modem.h b/multi_modem.h index f1dcdd9..e84a549 100644 --- a/multi_modem.h +++ b/multi_modem.h @@ -14,7 +14,7 @@ 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, int level, retry_t retries); +void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries); #endif diff --git a/pfilter.c b/pfilter.c new file mode 100644 index 0000000..db5fc65 --- /dev/null +++ b/pfilter.c @@ -0,0 +1,1069 @@ +// +// 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 . +// + + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include +#include + +#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 */ + + diff --git a/pfilter.h b/pfilter.h new file mode 100644 index 0000000..1171280 --- /dev/null +++ b/pfilter.h @@ -0,0 +1,4 @@ + +/* 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 b3b9533..f412971 100644 --- a/ptt.c +++ b/ptt.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2013,2014 John Langner, WB2OSZ +// 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 @@ -17,12 +17,11 @@ // along with this program. If not, see . - /*------------------------------------------------------------------ * * Module: ptt.c * - * Purpose: Activate the push to talk (PTT) signal to turn on transmitter. + * 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. * @@ -30,12 +29,15 @@ * can be used for the second channel. * * If __WIN32__ is defined, we use the Windows interface. - * Otherwise we use the unix version suitable for either Cygwin or Linux. + * 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 @@ -86,16 +88,23 @@ typedef int HANDLE; #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); } -#endif - #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; +} + /*------------------------------------------------------------------- * @@ -103,8 +112,30 @@ typedef int HANDLE; * * Purpose: Open serial port(s) used for PTT signals and set to proper state. * - * Inputs: modem - Structure with communication parameters. + * 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. * @@ -112,36 +143,19 @@ typedef int HANDLE; * *--------------------------------------------------------------------*/ -static int ptt_num_channels; -static ptt_method_t ptt_method[MAX_CHANS]; /* Method for PTT signal. */ - /* PTT_METHOD_NONE - not configured. Could be using VOX. */ - /* PTT_METHOD_SERIAL - serial (com) port. */ - /* PTT_METHOD_GPIO - general purpose I/O. */ - /* PTT_METHOD_LPT - Parallel printer port. */ -static char ptt_device[MAX_CHANS][20]; /* Name of serial port device. */ - /* e.g. COM1 or /dev/ttyS0. */ +static struct audio_s *save_audio_config_p; /* Save config information for later use. */ -static ptt_line_t ptt_line[MAX_CHANS]; /* RTS or DTR when using serial port. */ - -static int ptt_gpio[MAX_CHANS]; /* GPIO number. Only used for Linux. */ - /* Valid only when ptt_method is PTT_METHOD_GPIO. */ - -int ptt_lpt_bit[MAX_CHANS]; /* Bit number for parallel printer port. */ - /* Bit 0 = pin 2, ..., bit 7 = pin 9. */ - /* Valid only when ptt_method is PTT_METHOD_LPT. */ - -static int ptt_invert[MAX_CHANS]; /* Invert the signal. */ - /* Normally higher voltage means transmit. */ - -static HANDLE ptt_fd[MAX_CHANS]; /* Serial port handle or fd. */ +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 *p_modem) +void ptt_init (struct audio_s *audio_config_p) { int ch; HANDLE fd; @@ -155,126 +169,141 @@ void ptt_init (struct audio_s *p_modem) dw_printf ("ptt_init ( ... )\n"); #endif -/* - * First copy everything from p_modem to local variables - * so it is available for later use. - * - * Maybe all the PTT stuff should have its own structure. - */ + save_audio_config_p = audio_config_p; - ptt_num_channels = p_modem->num_channels; + strcpy (otnames[OCTYPE_PTT], "PTT"); + strcpy (otnames[OCTYPE_DCD], "DCD"); + strcpy (otnames[OCTYPE_FUTURE], "FUTURE"); - assert (ptt_num_channels >= 1 && ptt_num_channels <= MAX_CHANS); - for (ch=0; chptt_method[ch]; - strcpy (ptt_device[ch], p_modem->ptt_device[ch]); - ptt_line[ch] = p_modem->ptt_line[ch]; - ptt_gpio[ch] = p_modem->ptt_gpio[ch]; - ptt_lpt_bit[ch] = p_modem->ptt_lpt_bit[ch]; - ptt_invert[ch] = p_modem->ptt_invert[ch]; - ptt_fd[ch] = INVALID_HANDLE_VALUE; -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("ch=%d, method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n", + 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, - ptt_method[ch], - ptt_device[ch], - ptt_line[ch], - ptt_gpio[ch], - ptt_lpt_bit[ch], - ptt_invert[ch]); -#endif + 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; chachan[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. */ + /* Translate Windows device name into Linux name. */ + /* COM1 -> /dev/ttyS0, etc. */ - if (strncasecmp(ptt_device[ch], "COM", 3) == 0) { - int n = atoi (ptt_device[ch] + 3); - text_color_set(DW_COLOR_INFO); - dw_printf ("Converted PTT device '%s'", ptt_device[ch]); - if (n < 1) n = 1; - sprintf (ptt_device[ch], "/dev/ttyS%d", n-1); - dw_printf (" to Linux equivalent '%s'\n", ptt_device[ch]); - } -#endif - /* Can't open the same device more than once so we */ - /* need more logic to look for the case of both radio */ - /* channels using different pins of the same COM port. */ - - /* TODO: Needs to be rewritten in a more general manner */ - /* if we ever have more than 2 channels. */ - - if (ch == 1 && strcmp(ptt_device[0],ptt_device[1]) == 0) { - fd = ptt_fd[0]; - } - else { -#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, ptt_device[ch]); - if (strncasecmp(bettername, "COM", 3) == 0) { - int n; - n = atoi(bettername+3); - if (n >= 10) { - strcpy (bettername, "\\\\.\\"); - strcat (bettername, ptt_device[ch]); + 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); } - } - fd = CreateFile(bettername, +#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. */ - /* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */ + /* 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 (ptt_device[ch], O_RDONLY | O_NONBLOCK); + fd = open (audio_config_p->achan[ch].octrl[ot].ptt_device, O_RDONLY | O_NONBLOCK); #endif - } + } - if (fd != INVALID_HANDLE_VALUE) { - ptt_fd[ch] = fd; - } - else { + if (fd != INVALID_HANDLE_VALUE) { + ptt_fd[ch][ot] = fd; + } + else { #if __WIN32__ #else - int e = errno; + int e = errno; #endif - text_color_set(DW_COLOR_ERROR); - dw_printf ("ERROR can't open device %s for channel %d PTT control.\n", - ptt_device[ch], ch); + 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)); + dw_printf ("%s\n", strerror(errno)); #endif - /* Don't try using it later if device open failed. */ + /* Don't try using it later if device open failed. */ - ptt_method[ch] = PTT_METHOD_NONE; - } + audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; + } /* - * Set initial state of PTT off. + * Set initial state off. * ptt_set will invert output signal if appropriate. */ - ptt_set (ch, 0); + ptt_set (ot, ch, 0); - } /* if serial method. */ - - } /* For each channel. */ + } /* if serial method. */ + } /* for each output type. */ + } /* if channel valid. */ + } /* For each channel. */ /* @@ -285,13 +314,18 @@ void ptt_init (struct audio_s *p_modem) #else /* - * Does any channel use GPIO? + * Does any of them use GPIO? */ using_gpio = 0; - for (ch=0; chachan[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; + } + } } } @@ -353,93 +387,98 @@ void ptt_init (struct audio_s *p_modem) * the pins we want to use. */ - for (ch=0; chachan[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", ptt_gpio[ch]); - 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); + 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", ptt_gpio[ch]); - err = system (stemp); - sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]); - err = system (stemp); + 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", ptt_gpio[ch]); + 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 (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", ptt_gpio[ch]); - dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]); - 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", ptt_gpio[ch]); - 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); - } + 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 (ptt_invert[ch]) { - strcpy (hilo, "high"); + 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); + } } - 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 @@ -448,59 +487,74 @@ void ptt_init (struct audio_s *p_modem) /* * Set up parallel printer port. - * Hardcoded for single port. - * For x86 Linux only. + * + * Restrictions: + * Only the primary printer port. + * For x86 Linux only. */ #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) - for (ch=0; chachan[ch].valid) { + int ot; + for (ot = 0; ot < NUM_OCTYPES; ot++) { + if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_LPT) { - if (ptt_method[ch] == PTT_METHOD_LPT) { + /* Can't open the same device more than once so we */ + /* need more logic to look for the case of mutiple radio */ + /* channels using different pins of the LPT port. */ + /* Did some earlier channel use the same ptt device name? */ - /* Can't open the same device more than once so we */ - /* need more logic to look for the case of both radio */ - /* channels using different pins of the same LPT port. */ + 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; + } + } + } + } - /* TODO: Needs to be rewritten in a more general manner */ - /* if we ever have more than 2 channels. */ + if ( ! same_device_used) { + fd = open ("/dev/port", O_RDWR | O_NDELAY); + } - if (ch == 1 && strcmp(ptt_device[0],ptt_device[1]) == 0) { - fd = ptt_fd[0]; - } - else { - fd = open ("/dev/port", O_RDWR | O_NDELAY); - } + if (fd != INVALID_HANDLE_VALUE) { + ptt_fd[ch][ot] = fd; + } + else { - if (fd != INVALID_HANDLE_VALUE) { - ptt_fd[ch] = fd; - } - else { + int e = errno; - 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"); - 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. */ - /* Don't try using it later if device open failed. */ - - ptt_method[ch] = PTT_METHOD_NONE; - } + audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; + } + /* - * Set initial state of PTT off. + * Set initial state off. * ptt_set will invert output signal if appropriate. */ - ptt_set (ch, 0); - - } /* if parallel printer port method. */ + ptt_set (ot, ch, 0); + } /* if parallel printer port method. */ + } /* for each output type */ + } /* if valid channel. */ } /* For each channel. */ @@ -510,12 +564,15 @@ void ptt_init (struct audio_s *p_modem) /* Why doesn't it transmit? Probably forgot to specify PTT option. */ - for (ch=0; chachan[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 */ @@ -523,63 +580,109 @@ void ptt_init (struct audio_s *p_modem) * * Name: ptt_set * - * Purpose: Turn transmitter on or off. + * 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: chan channel, 0 .. (number of channels)-1 + * Inputs: ot - Output control type: + * OCTYPE_PTT, OCTYPE_DCD, OCTYPE_FUTURE * - * ptt 1 for transmit, 0 for receive. + * 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 means transmit unless invert is set. + * More positive output corresponds to 1 unless invert is set. * *--------------------------------------------------------------------*/ -void ptt_set (int chan, int ptt) +void ptt_set (int ot, int chan, int ptt_signal) { -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("ptt_set ( %d, %d )\n", chan, ptt); -#endif + 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 (ptt_invert[chan]) { + if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert) { ptt = ! ptt; } - - assert (chan >= 0 && chan < MAX_CHANS); + if (save_audio_config_p->achan[chan].octrl[ot].ptt_invert2) { + ptt2 = ! ptt2; + } /* * Using serial port? */ - if (ptt_method[chan] == PTT_METHOD_SERIAL && - ptt_fd[chan] != INVALID_HANDLE_VALUE) { + if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_SERIAL && + ptt_fd[chan][ot] != INVALID_HANDLE_VALUE) { - if (ptt_line[chan] == PTT_LINE_RTS) { + if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_RTS) { if (ptt) { - RTS_ON(ptt_fd[chan]); + RTS_ON(ptt_fd[chan][ot]); } else { - RTS_OFF(ptt_fd[chan]); + RTS_OFF(ptt_fd[chan][ot]); } } - else if (ptt_line[chan] == PTT_LINE_DTR) { + else if (save_audio_config_p->achan[chan].octrl[ot].ptt_line == PTT_LINE_DTR) { if (ptt) { - DTR_ON(ptt_fd[chan]); + DTR_ON(ptt_fd[chan][ot]); } else { - DTR_OFF(ptt_fd[chan]); + 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 */ + } /* @@ -589,17 +692,17 @@ void ptt_set (int chan, int ptt) #if __WIN32__ #else - if (ptt_method[chan] == PTT_METHOD_GPIO) { + 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", ptt_gpio[chan]); + 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 PTT signal.\n", stemp); + dw_printf ("Error opening %s to set %s signal.\n", stemp, otnames[ot]); dw_printf ("%s\n", strerror(e)); return; } @@ -609,7 +712,7 @@ void ptt_set (int chan, int ptt) if (write (fd, stemp, 1) != 1) { int e = errno; text_color_set(DW_COLOR_ERROR); - dw_printf ("Error setting GPIO %d for PTT\n", ptt_gpio[chan]); + 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); @@ -623,32 +726,32 @@ void ptt_set (int chan, int ptt) #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) - if (ptt_method[chan] == PTT_METHOD_LPT && - ptt_fd[chan] != INVALID_HANDLE_VALUE) { + 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], (off_t)LPT_IO_ADDR, SEEK_SET); - if (read (ptt_fd[chan], &lpt_data, (size_t)1) != 1) { + 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 PTT\n", chan); + 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 << ptt_lpt_bit[chan] ); + lpt_data |= ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit ); } else { - lpt_data &= ~ ( 1 << ptt_lpt_bit[chan] ); + lpt_data &= ~ ( 1 << save_audio_config_p->achan[chan].octrl[ot].ptt_lpt_bit ); } - lseek (ptt_fd[chan], (off_t)LPT_IO_ADDR, SEEK_SET); - if (write (ptt_fd[chan], &lpt_data, (size_t)1) != 1) { + 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 PTT\n", chan); + dw_printf ("Error writing to LPT for channel %d %s\n", chan, otnames[ot]); dw_printf ("%s\n", strerror(e)); } } @@ -664,7 +767,7 @@ void ptt_set (int chan, int ptt) * * Name: ptt_term * - * Purpose: Make sure PTT is turned off when we exit. + * Purpose: Make sure PTT and others are turned off when we exit. * * Inputs: none * @@ -676,18 +779,28 @@ void ptt_term (void) { int n; - for (n = 0; n < ptt_num_channels; n++) { - ptt_set (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++) { + ptt_set (ot, n, 0); + } + } } - for (n = 0; n < ptt_num_channels; n++) { - if (ptt_fd[n] != INVALID_HANDLE_VALUE) { + 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]); + CloseHandle (ptt_fd[n][ot]); #else - close(ptt_fd[n]); + close(ptt_fd[n][ot]); #endif - ptt_fd[n] = INVALID_HANDLE_VALUE; + ptt_fd[n][ot] = INVALID_HANDLE_VALUE; + } + } } } } @@ -711,28 +824,30 @@ void text_color_set (dw_color_t c) { } main () { - struct audio_s modem; + struct audio_s my_audio_config; int n; int chan; - memset (&modem, 0, sizeof(modem)); + memset (&my_audio_config, 0, sizeof(my_audio_config)); - modem.num_channels = 2; + my_audio_config.adev[0].num_channels = 2; - modem.ptt_method[0] = PTT_METHOD_SERIAL; - //strcpy (modem.ptt_device[0], "COM1"); - strcpy (modem.ptt_device[0], "/dev/ttyUSB0"); - modem.ptt_line[0] = PTT_LINE_RTS; + 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; - modem.ptt_method[1] = PTT_METHOD_SERIAL; - //strcpy (modem.ptt_device[1], "COM1"); - strcpy (modem.ptt_device[1], "/dev/ttyUSB0"); - modem.ptt_line[1] = PTT_LINE_DTR; + 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 (&modem); + ptt_init (&my_audio_config); SLEEP_SEC(2); @@ -742,9 +857,9 @@ main () chan = 0; for (n=0; n<3; n++) { - ptt_set (chan, 1); + ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); - ptt_set (chan, 0); + ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } @@ -752,9 +867,9 @@ main () chan = 1; for (n=0; n<3; n++) { - ptt_set (chan, 1); + ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); - ptt_set (chan, 0); + ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } @@ -762,9 +877,9 @@ main () /* Same thing again but invert RTS. */ - modem.ptt_invert[0] = 1; + my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_invert = 1; - ptt_init (&modem); + ptt_init (&my_audio_config); SLEEP_SEC(2); @@ -772,9 +887,9 @@ main () chan = 0; for (n=0; n<3; n++) { - ptt_set (chan, 1); + ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); - ptt_set (chan, 0); + ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } @@ -782,9 +897,9 @@ main () chan = 1; for (n=0; n<3; n++) { - ptt_set (chan, 1); + ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); - ptt_set (chan, 0); + ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } @@ -795,21 +910,22 @@ main () #if __arm__ - memset (&modem, 0, sizeof(modem)); - modem.num_channels = 1; - modem.ptt_method[0] = PTT_METHOD_GPIO; - modem.ptt_gpio[0] = 25; + 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", modem.ptt_gpio[0]); + dw_printf ("Try GPIO %d a few times...\n", my_audio_config.ptt_gpio[0]); - ptt_init (&modem); + ptt_init (&my_audio_config); SLEEP_SEC(2); chan = 0; for (n=0; n<3; n++) { - ptt_set (chan, 1); + ptt_set (OCTYPE_PTT, chan, 1); SLEEP_SEC(1); - ptt_set (chan, 0); + ptt_set (OCTYPE_PTT, chan, 0); SLEEP_SEC(1); } @@ -817,20 +933,22 @@ main () #endif - memset (&modem, 0, sizeof(modem)); - modem.num_channels = 2; - modem.ptt_method[0] = PTT_METHOD_LPT; - modem.ptt_lpt_bit[0] = 0; - modem.ptt_method[1] = PTT_METHOD_LPT; - modem.ptt_lpt_bit[1] = 1; + 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 (&modem); + ptt_init (&my_audio_config); for (n=0; n<8; n++) { - ptt_set (0, n & 1); - ptt_set (1, (n>>1) & 1); + ptt_set (OCTYPE_PTT, 0, n & 1); + ptt_set (OCTYPE_PTT, 1, (n>>1) & 1); SLEEP_SEC(1); } @@ -840,14 +958,14 @@ main () #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) - + // TODO #endif } -#endif +#endif /* TEST */ /* end ptt.c */ diff --git a/ptt.h b/ptt.h index 014b8c0..7e9f639 100644 --- a/ptt.h +++ b/ptt.h @@ -4,12 +4,14 @@ #define PTT_H 1 -#include "audio.h" /* for struct audio_s */ +#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 chan, int ptt); +void ptt_set (int octype, int chan, int ptt); void ptt_term (void); diff --git a/pttest.c b/pttest.c deleted file mode 100644 index 82c7a81..0000000 --- a/pttest.c +++ /dev/null @@ -1,287 +0,0 @@ - - -/*------------------------------------------------------------------ - * - * Module: pttest.c - * - * Purpose: Test for pseudo terminal. - * - * Input: - * - * Outputs: - * - * Description: The protocol name is an acronym for Keep it Simple Stupid. - * You would expect it to be simple but this caused a lot - * of effort on Linux. The problem is that writes to a pseudo - * terminal eventually block if nothing at the other end - * is removing the data. This causes the application to - * hang and stop receiving after a while. - * - * This is an attempt to demonstrate the problem in a small - * test case and, hopefully, find a solution. - * - * - * Instructions: - * First compile like this: - * - * - * Run it, noting the name of pseudo terminal, - * typically /dev/pts/1. - * - * In another window, type: - * - * cat /dev/pts/1 - * - * This should run "forever" as long as something is - * reading from the slave side of the pseudo terminal. - * - * If nothing is removing the data, this runs for a while - * and then blocks on the write. - * For this particular application we just want to discard - * excess data if no one is listening. - * - * - * Failed experiments: - * - * Notice that ??? always returns 0 for amount of data - * in the queue. - * Define TEST1 to make the device non-blocking. - * Write fails entirely. - * - * Define TEST2 to use a different method. - * Also fails in the same way. - * - * - *---------------------------------------------------------------*/ - -#include -#include - - -#define __USE_XOPEN2KXSI 1 -#define __USE_XOPEN 1 -//#define __USE_POSIX 1 -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -//#include "direwolf.h" -//#include "tq.h" -//#include "ax25_pad.h" -//#include "textcolor.h" -//#include "kiss.h" -//#include "xmit.h" - - - -static MYFDTYPE pt_master_fd = -1; /* File descriptor for my end. */ - -static MYFDTYPE pt_slave_fd = -1; /* File descriptor for pseudo terminal */ - /* for use by application. */ -static int msg_number; -static int total_bytes; - - -/*------------------------------------------------------------------- - * - * 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 int kiss_open_pt (void); - - -main (int argc, char *argv) -{ - - pt_master_fd = kiss_open_pt (); - printf ("msg total qcount\n"); - - msg_number = 0; - total_bytes = 0; - -#endif -} - - -/* - * Returns fd for master side of pseudo terminal or MYFDERROR for error. - */ - - -static int kiss_open_pt (void) -{ - int fd; - char *slave_device; - struct termios ts; - int e; - int flags; - - fd = posix_openpt(O_RDWR|O_NOCTTY); - - if (fd == -1 - || grantpt (fd) == -1 - || unlockpt (fd) == -1 - || (slave_device = ptsname (fd)) == NULL) { - text_color_set(DW_COLOR_ERROR); - printf ("ERROR - Could not create pseudo terminal.\n"); - return (-1); - } - - - e = tcgetattr (fd, &ts); - if (e != 0) { - 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); - 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 TEST1 - // this is worse. all writes fail. errno = ? bad file descriptor - flags = fcntl(fd, F_GETFL, 0); - e = fcntl (fd, F_SETFL, flags | O_NONBLOCK); - if (e != 0) { - printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno); - perror ("pt fcntl"); - } -#endif -#if TEST2 - // same - flags = 1; - e = ioctl (fd, FIONBIO, &flags); - if (e != 0) { - printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno); - perror ("pt ioctl"); - } -#endif - - printf("Virtual KISS TNC is available on %s\n", slave_device); - - - // Sample code shows this. Why would we open it here? - - // pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY); - - - return (fd); -} - - - -/*------------------------------------------------------------------- - * - * Name: kiss_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. - * Not including the FCS. - * - * - * 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 (void) -{ - - - char kiss_buff[100]; - - int kiss_len; - int q_count = 123; - - - int j; - - strcpy (kiss_buff, "The quick brown fox jumps over the lazy dog.\n"); - kiss_len = strlen(kiss_buff); - - - if (pt_master_fd != MYFDERROR) { - int err; - - msg_number++; - total_bytes += kiss_len; - -//#if DEBUG - printf ("%3d %5d %5d\n", msg_number, total_bytes, q_count); -//#endif - err = write (pt_master_fd, kiss_buff, kiss_len); - - if (err == -1 && errno == EWOULDBLOCK) { -//#if DEBUG - printf ("Discarding message because write would block.\n"); -//#endif - } - else if (err != kiss_len) - { - printf ("\nError sending message on pseudo terminal. len=%d, write returned %d, errno = %d\n\n", - kiss_len, err, errno); - perror ("pt write"); - } - - } -//#endif - - - -} - - -/* end pttest.c */ diff --git a/rdq.c b/rdq.c index 3126291..cbfc2cf 100644 --- a/rdq.c +++ b/rdq.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012 John Langner, WB2OSZ +// 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 @@ -44,22 +44,24 @@ -static rrbb_t queue_head = NULL; /* Head of linked list for queue. */ +static rrbb_t queue_head = NULL; /* Head of linked list for queue. */ static int rdq_len = 0; #define RDQ_UNDERRUN_THRESHOLD 30 /* A warning will be emitted if there are still this number of packets to decode in the queue and we try to add another one */ -#if __WIN32__ -static CRITICAL_SECTION rdq_cs; /* Critical section for updating queues. */ + + +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_mutex_t rdq_mutex; /* Critical section for updating queues. */ - static pthread_cond_t wake_up_cond; /* Notify try decode again thread when queue not empty. */ -static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ +static dw_mutex_t wake_up_mutex; /* Required by cond_wait. */ #endif @@ -94,17 +96,11 @@ void rdq_init (void) dw_printf ("rdq_init: pthread_mutex_init...\n"); #endif + dw_mutex_init (&rdq_mutex); + #if __WIN32__ - InitializeCriticalSection (&rdq_cs); #else - err = pthread_mutex_init (&wake_up_mutex, NULL); - err = pthread_mutex_init (&rdq_mutex, NULL); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_init: pthread_mutex_init err=%d", err); - perror (""); - exit (1); - } + dw_mutex_init (&wake_up_mutex); #endif @@ -179,17 +175,9 @@ void rdq_append (rrbb_t rrbb) dw_printf ("rdq_append (rrbb=%p)\n", rrbb); dw_printf ("rdq_append: enter critical section\n"); #endif -#if __WIN32__ - EnterCriticalSection (&rdq_cs); -#else - err = pthread_mutex_lock (&rdq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_append: pthread_mutex_lock err=%d", err); - perror (""); - exit (1); - } -#endif + + + dw_mutex_lock (&rdq_mutex); //was_empty = 1; //if (queue_head != NULL) { @@ -211,17 +199,8 @@ void rdq_append (rrbb_t rrbb) dw_printf ("Too many packets to decode (%d) in the queue, decrease the FIX_BITS value\n", rdq_len); } -#if __WIN32__ - LeaveCriticalSection (&rdq_cs); -#else - err = pthread_mutex_unlock (&rdq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_append: pthread_mutex_unlock err=%d", err); - perror (""); - exit (1); - } -#endif + dw_mutex_unlock (&rdq_mutex); + #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_append: left critical section\n"); @@ -231,13 +210,7 @@ void rdq_append (rrbb_t rrbb) #if __WIN32__ SetEvent (wake_up_event); #else - err = pthread_mutex_lock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_append: pthread_mutex_lock wu err=%d", err); - perror (""); - exit (1); - } + dw_mutex_lock (&wake_up_mutex); err = pthread_cond_signal (&wake_up_cond); if (err != 0) { @@ -247,13 +220,7 @@ void rdq_append (rrbb_t rrbb) exit (1); } - err = pthread_mutex_unlock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_append: pthread_mutex_unlock wu err=%d", err); - perror (""); - exit (1); - } + dw_mutex_unlock (&wake_up_mutex); #endif } @@ -284,17 +251,7 @@ void rdq_wait_while_empty (void) dw_printf ("rdq_wait_while_empty () : enter critical section\n"); #endif -#if __WIN32__ - EnterCriticalSection (&rdq_cs); -#else - err = pthread_mutex_lock (&rdq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_wait_while_empty: pthread_mutex_lock err=%d", err); - perror (""); - exit (1); - } -#endif + dw_mutex_lock (&rdq_mutex); #if DEBUG //text_color_set(DW_COLOR_DEBUG); @@ -304,18 +261,8 @@ void rdq_wait_while_empty (void) if (queue_head != NULL) is_empty = 0; + dw_mutex_unlock (&rdq_mutex); -#if __WIN32__ - LeaveCriticalSection (&rdq_cs); -#else - err = pthread_mutex_unlock (&rdq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_wait_while_empty: pthread_mutex_unlock err=%d", err); - perror (""); - exit (1); - } -#endif #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("rdq_wait_while_empty () : left critical section\n"); @@ -342,17 +289,10 @@ void rdq_wait_while_empty (void) #endif #else - err = pthread_mutex_lock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_wait_while_empty: pthread_mutex_lock wu err=%d", err); - perror (""); - exit (1); - } + 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); @@ -365,13 +305,7 @@ void rdq_wait_while_empty (void) exit (1); } - err = pthread_mutex_unlock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); - perror (""); - exit (1); - } + dw_mutex_unlock (&wake_up_mutex); #endif } @@ -412,17 +346,8 @@ rrbb_t rdq_remove (void) dw_printf ("rdq_remove() enter critical section\n"); #endif -#if __WIN32__ - EnterCriticalSection (&rdq_cs); -#else - err = pthread_mutex_lock (&rdq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_remove: pthread_mutex_lock err=%d", err); - perror (""); - exit (1); - } -#endif + dw_mutex_lock (&rdq_mutex); + rdq_len--; #if DEBUG dw_printf ("-rdq_len: %d\n", rdq_len); @@ -436,19 +361,9 @@ rrbb_t rdq_remove (void) queue_head = rrbb_get_nextp(result_p); rrbb_set_nextp (result_p, NULL); } - -#if __WIN32__ - LeaveCriticalSection (&rdq_cs); -#else - err = pthread_mutex_unlock (&rdq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_remove: pthread_mutex_unlock err=%d", err); - perror (""); - exit (1); - } -#endif + 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); @@ -456,6 +371,4 @@ rrbb_t rdq_remove (void) return (result_p); } - - /* end rdq.c */ diff --git a/recv.c b/recv.c new file mode 100644 index 0000000..1892562 --- /dev/null +++ b/recv.c @@ -0,0 +1,328 @@ + +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include +//#include +//#include +//#include +#include + +#ifdef __FreeBSD__ +#include +#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; aadev[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= 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 */ + diff --git a/recv.h b/recv.h new file mode 100644 index 0000000..3201991 --- /dev/null +++ b/recv.h @@ -0,0 +1,6 @@ + +/* 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 1db45a8..d975adf 100644 --- a/redecode.c +++ b/redecode.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 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 @@ -18,7 +18,6 @@ // - /*------------------------------------------------------------------ * * Module: redecode.c @@ -65,7 +64,9 @@ #include "ptt.h" +/* Audio configuration for the fix_bits / passall optiions. */ +static struct audio_s *save_audio_config_p; @@ -95,10 +96,9 @@ static void * redecode_thread (void *arg); *--------------------------------------------------------------------*/ - -void redecode_init (void) +void redecode_init (struct audio_s *p_audio_config) { - //int j; + #if __WIN32__ HANDLE redecode_th; #else @@ -112,6 +112,7 @@ void redecode_init (void) dw_printf ("redecode_init ( ... )\n"); #endif + save_audio_config_p = p_audio_config; #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -180,12 +181,6 @@ static unsigned redecode_thread (void *arg) static void * redecode_thread (void *arg) #endif { - rrbb_t block; - int blen; - int chan, subchan; - int alevel; - - #if __WIN32__ HANDLE tid = GetCurrentThread(); //int tp; @@ -199,6 +194,7 @@ static void * redecode_thread (void *arg) #endif while (1) { + rrbb_t block; rdq_wait_while_empty (); #if DEBUG @@ -208,7 +204,6 @@ static void * redecode_thread (void *arg) block = rdq_remove (); - #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("redecode_thread: rdq_remove() returned %p\n", block); @@ -218,16 +213,21 @@ static void * redecode_thread (void *arg) if (block != NULL) { - chan = rrbb_get_chan(block); - subchan = rrbb_get_subchan(block); - alevel = rrbb_get_audio_level(block); - blen = rrbb_get_len(block); + 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); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen); #endif - hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel); + ok = hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel); #if DEBUG text_color_set(DW_COLOR_DEBUG); diff --git a/redecode.h b/redecode.h index 5d0df50..ffd9a95 100644 --- a/redecode.h +++ b/redecode.h @@ -6,7 +6,7 @@ #include "rrbb.h" -extern void redecode_init (void); +extern void redecode_init (struct audio_s *p_audio_config); #endif diff --git a/rrbb.c b/rrbb.c index 61e3466..2348898 100644 --- a/rrbb.c +++ b/rrbb.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 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 @@ -31,6 +31,9 @@ * 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 @@ -51,7 +54,6 @@ #define MAGIC1 0x12344321 #define MAGIC2 0x56788765 -#ifndef SLICENDICE static const unsigned int masks[SOI] = { 0x00000001, 0x00000002, @@ -85,7 +87,6 @@ static const unsigned int masks[SOI] = { 0x20000000, 0x40000000, 0x80000000 }; -#endif static int new_count = 0; static int delete_count = 0; @@ -105,13 +106,15 @@ static int delete_count = 0; * * 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) +rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state, int prev_descram) { rrbb_t result; @@ -130,7 +133,12 @@ rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state) new_count++; - rrbb_clear (result, is_scrambled, descram_state); + 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); } @@ -141,25 +149,36 @@ rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state) * * Purpose: Clear by setting length to zero, etc. * - * Inputs: Handle for sample array. + * 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) +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->audio_level = 9999; + + 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; } /*********************************************************************************** @@ -173,21 +192,11 @@ void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state) * ***********************************************************************************/ -#if SLICENDICE -void rrbb2_append_bit (rrbb_t b, float val) -{ - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - assert (b->len >= 0); - if (b->len >= MAX_NUM_BITS) { - return; /* Silently discard if full. */ - } +// TODO: Forget about bit packing and just use bytes. +// We have hundreds of MB. Why waste time to save a couple KB? + - b->data[b->len++] = (int)(val * 1000.); -} -#else void rrbb_append_bit (rrbb_t b, int val) { unsigned int di, mi; @@ -212,7 +221,7 @@ void rrbb_append_bit (rrbb_t b, int val) b->len++; } -#endif + /*********************************************************************************** * @@ -258,92 +267,6 @@ int rrbb_get_len (rrbb_t b) } -/*********************************************************************************** - * - * Name: rrbb_set_slice_val - * - * Purpose: Set slicing value to determine whether a sample is bit 0 or 1. - * - * Inputs: Handle for sample array. - * Slicing point value. - * - ***********************************************************************************/ - -#if SLICENDICE - -static int cmp_slice (slice_t *a, slice_t *b) -{ - return ( *a - *b ); -} - -void rrbb_set_slice_val (rrbb_t b, slice_t slice_val) -{ - slice_t sorted[MAX_NUM_BITS]; - int n, i; - int sum, ave, median, izero; - - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - - b->slice_val = slice_val; - - memcpy (sorted, b->data, b->len * sizeof(slice_t)); - - /* Typically takes 14 milliseconds on a reasonable PC. */ - qsort (sorted, (size_t)(b->len), sizeof(slice_t), cmp_slice); - - text_color_set (DW_COLOR_DEBUG); - - n = 0; - dw_printf ("[%d..%d] ", n, n+9); - for (i=n; i<=n+9; i++) dw_printf (" %d", sorted[i]); - dw_printf ("\n"); - - n = ( b->len / 2 ) - 10; - dw_printf ("m[%d..%d] ", n, n+19); - for (i=n; i<=n+19; i++) dw_printf (" %d", sorted[i]); - dw_printf ("\n"); - - n = b->len - 1 - 9; - dw_printf ("[%d..%d] ", n, n+9); - for (i=n; i<=n+9; i++) dw_printf (" %d", sorted[i]); - dw_printf ("\n"); - - sum = 0; - for (i=0; ilen; i++) { - sum += sorted[i]; - } - ave = sum / b->len; - - //b->slice_val = ave; - //b->slice_val = sorted[b->len/2]; - - /* Find first one >= 0. */ - izero = -1; - for (i=0; ilen; i++) { - if (sorted[i] >= 0) { - izero = i; - break; - } - } - - if (izero >= 0) { - n = izero - 10; - dw_printf ("z[%d..%d] ", n, n+19); - for (i=n; i<=n+19; i++) dw_printf (" %d", sorted[i]); - dw_printf ("\n"); - - b->slice_val = sorted[izero-1]; - - } - - - -} - -#endif - /*********************************************************************************** * @@ -356,22 +279,6 @@ void rrbb_set_slice_val (rrbb_t b, slice_t slice_val) * ***********************************************************************************/ -#if SLICENDICE -int rrbb_get_bit (rrbb_t b, unsigned int ind) -{ - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - assert (ind >= 0 && ind < b->len); - - if (b->data[ind] > b->slice_val) { - return 1; - } - else { - return 0; - } -} -#else int rrbb_get_bit (rrbb_t b, unsigned int ind) { assert (b != NULL); @@ -387,7 +294,7 @@ int rrbb_get_bit (rrbb_t b, unsigned int ind) return 0; } } -#endif + unsigned int rrbb_get_computed_bit (rrbb_t b, unsigned int ind) { return b->computed_data[ind]; @@ -557,17 +464,17 @@ int rrbb_get_subchan (rrbb_t b) * Purpose: Set audio level at time the frame was received. * * Inputs: b Handle for bit array. - * a Audio level. + * alevel Audio level. * ***********************************************************************************/ -void rrbb_set_audio_level (rrbb_t b, int a) +void rrbb_set_audio_level (rrbb_t b, alevel_t alevel) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); - b->audio_level = a; + b->alevel = alevel; } @@ -581,56 +488,16 @@ void rrbb_set_audio_level (rrbb_t b, int a) * ***********************************************************************************/ -int rrbb_get_audio_level (rrbb_t b) +alevel_t rrbb_get_audio_level (rrbb_t b) { assert (b != NULL); assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); - return (b->audio_level); + return (b->alevel); } -/*********************************************************************************** - * - * Name: rrbb_set_fix_bits - * - * Purpose: Set fix bits at time the frame was received. - * - * Inputs: b Handle for bit array. - * a fix_bits. - * - ***********************************************************************************/ - -void rrbb_set_fix_bits (rrbb_t b, int a) -{ - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - - b->fix_bits = a; -} - - -/*********************************************************************************** - * - * Name: rrbb_get_fix_bits - * - * Purpose: Get fix bits at time the frame was received. - * - * Inputs: b Handle for bit array. - * - ***********************************************************************************/ - -int rrbb_get_fix_bits (rrbb_t b) -{ - assert (b != NULL); - assert (b->magic1 == MAGIC1); - assert (b->magic2 == MAGIC2); - - return (b->fix_bits); -} - /*********************************************************************************** * @@ -675,6 +542,26 @@ int rrbb_get_descram_state (rrbb_t b) } +/*********************************************************************************** + * + * 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 efb49a8..c24be07 100644 --- a/rrbb.h +++ b/rrbb.h @@ -4,11 +4,6 @@ #define RRBB_H -/* Try something new in version 1.0 */ -/* Get back to this later. Disable for now. */ - -//#define SLICENDICE 1 - typedef short slice_t; @@ -35,22 +30,16 @@ typedef struct rrbb_s { 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 audio_level; /* Received audio level at time of frame capture. */ + alevel_t alevel; /* Received audio level at time of frame capture. */ unsigned int len; /* Current number of samples in array. */ - int fix_bits; /* Level of effort to recover from */ - /* a bad FCS on the frame. */ - int is_scrambled; /* Is data scrambled G3RUH / K9NG style? */ int descram_state; /* Descrambler state before first data bit of frame. */ + int prev_descram; /* Previous descrambled bit. */ -#if SLICENDICE - slice_t slice_val; - slice_t data[MAX_NUM_BITS]; -#else unsigned int data[(MAX_NUM_BITS+SOI-1)/SOI]; unsigned int computed_data[MAX_NUM_BITS]; -#endif + int magic2; } *rrbb_t; @@ -64,24 +53,18 @@ typedef void *rrbb_t; -rrbb_t rrbb_new (int chan, int subchan, int is_scrambled, int descram_state); +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_clear (rrbb_t b, int is_scrambled, int descram_state); -#if SLICENDICE -void rrbb2_append_bit (rrbb_t b, float val); -#else void rrbb_append_bit (rrbb_t b, int val); -#endif + void rrbb_chop8 (rrbb_t b); int rrbb_get_len (rrbb_t b); -#if SLICENDICE -void rrbb_set_slice_val (rrbb_t b, slice_t slice_val); -#endif - int rrbb_get_bit (rrbb_t b, unsigned int ind); unsigned int rrbb_get_computed_bit (rrbb_t b, unsigned int ind); int rrbb_compute_bits (rrbb_t b); @@ -91,21 +74,17 @@ int rrbb_compute_bits (rrbb_t b); 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, int a); - -int rrbb_get_audio_level (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); -int rrbb_get_fix_bits(rrbb_t b); -void rrbb_set_fix_bits(rrbb_t b, int fix_bits); #endif diff --git a/server.c b/server.c index 9bad9a2..6fb3699 100644 --- a/server.c +++ b/server.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ +// 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 @@ -57,6 +57,8 @@ * * '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? @@ -77,6 +79,8 @@ * '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 @@ -111,6 +115,11 @@ #include #include #include +#ifdef __OpenBSD__ +#include +#else +#include +#endif #endif #include @@ -128,6 +137,7 @@ #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. @@ -152,10 +162,11 @@ static int enable_send_monitor_to_client[MAX_NET_CLIENTS]; /* the client app must send a command to enable this. */ -static int num_channels; /* Number of radio ports. */ // 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 @@ -329,6 +340,8 @@ static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int * 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: @@ -338,8 +351,10 @@ static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int * *--------------------------------------------------------------------*/ +static struct audio_s *save_audio_config_p; -void server_init (struct misc_config_s *mc) + +void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc) { int client; @@ -359,12 +374,21 @@ void server_init (struct misc_config_s *mc) dw_printf ("server_init ( %d )\n", server_port); debug_a = 1; #endif + + save_audio_config_p = audio_config_p; + for (client=0; clientnum_channels; + + 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]. @@ -459,7 +483,7 @@ static THREAD_F connect_listen_thread (void *arg) dw_printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); //sleep (1); - return (NULL); + return (NULL); // TODO: what should this be for Windows? } memset (&hints, 0, sizeof(hints)); @@ -474,14 +498,14 @@ static THREAD_F connect_listen_thread (void *arg) dw_printf("getaddrinfo failed: %d\n", err); //sleep (1); WSACleanup(); - return (NULL); + 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); + return (NULL); // TODO: what should this be for Windows? } #if DEBUG @@ -492,12 +516,13 @@ static THREAD_F connect_listen_thread (void *arg) 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()); + 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); + return (NULL); // TODO: what should this be for Windows? } freeaddrinfo(ai); @@ -528,7 +553,7 @@ static THREAD_F connect_listen_thread (void *arg) { text_color_set(DW_COLOR_ERROR); dw_printf("Listen failed with error: %d\n", WSAGetLastError()); - return (NULL); + return (NULL); // TODO: what should this be for Windows? } text_color_set(DW_COLOR_INFO); @@ -541,7 +566,7 @@ static THREAD_F connect_listen_thread (void *arg) dw_printf("Accept failed with error: %d\n", WSAGetLastError()); closesocket(listen_sock); WSACleanup(); - return (NULL); + return (NULL); // TODO: what should this be for Windows? } text_color_set(DW_COLOR_INFO); @@ -585,7 +610,10 @@ static THREAD_F connect_listen_thread (void *arg) if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) { text_color_set(DW_COLOR_ERROR); - perror ("connect_listen_thread: Bind failed"); + 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); } @@ -873,6 +901,40 @@ static int read_from_socket (int fd, char *ptr, int len) * *--------------------------------------------------------------------*/ + +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; @@ -884,7 +946,7 @@ static THREAD_F cmd_listen_thread (void *arg) /* Maximum for 'V': 1 + 8*10 + 256 */ } cmd; - int client = (int) arg; + int client = (int)(long)arg; assert (client >= 0 && client < MAX_NET_CLIENTS); @@ -945,7 +1007,7 @@ static THREAD_F cmd_listen_thread (void *arg) close (client_sock[client]); #endif client_sock[client] = -1; - return NULL; + return NULL; // TODO: what should this be for Windows? } cmd.data[0] = '\0'; @@ -992,7 +1054,7 @@ static THREAD_F cmd_listen_thread (void *arg) 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); + assert (reply.hdr.data_len == 8); // Xastir only prints this and doesn't care otherwise. // APRSIS32 doesn't seem to care. @@ -1003,17 +1065,8 @@ static THREAD_F cmd_listen_thread (void *arg) assert (sizeof(reply) == 44); - if (debug_client) { - debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply)); - } + send_to_client (client, &reply); -// TODO: Should have unified function instead of multiple versions everywhere. - -#if __WIN32__ - send (client_sock[client], (char*)(&reply), sizeof(reply), 0); -#else - n = write (client_sock[client], &reply, sizeof(reply)); -#endif } break; @@ -1022,35 +1075,66 @@ static THREAD_F cmd_listen_thread (void *arg) { struct { struct agwpe_s hdr; - char info[100]; + char info[200]; } reply; + int j, count; + + memset (&reply, 0, sizeof(reply)); reply.hdr.kind_lo = 'G'; - reply.hdr.data_len = sizeof (reply.info); + // 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; jachan[j].valid) { + count++; + } + } + sprintf (reply.info, "%d;", count); + + for (j=0; jachan[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;"); } - - assert (reply.hdr.data_len == 100); - - if (debug_client) { - debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply)); - } - -#if __WIN32__ - send (client_sock[client], (char*)(&reply), sizeof(reply), 0); -#else - n = write (client_sock[client], &reply, sizeof(reply)); #endif + reply.hdr.data_len = strlen(reply.info) + 1; + + send_to_client (client, &reply); + } break; @@ -1093,15 +1177,8 @@ static THREAD_F cmd_listen_thread (void *arg) assert (sizeof(reply) == 48); - if (debug_client) { - debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply)); - } + send_to_client (client, &reply); -#if __WIN32__ - send (client_sock[client], (char*)(&reply), sizeof(reply), 0); -#else - n = write (client_sock[client], &reply, sizeof(reply)); -#endif } break; @@ -1129,16 +1206,7 @@ static THREAD_F cmd_listen_thread (void *arg) reply.hdr.data_len = strlen(reply.info); - if (debug_client) { - debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply.hdr) + reply.hdr.data_len); - } - -#if __WIN32__ - send (client_sock[client], &reply, sizeof(reply.hdr) + reply.hdr.data_len, 0); -#else - write (client_sock[client], &reply, sizeof(reply.hdr) + reply.hdr.data_len); -#endif - + send_to_client (client, &reply); #endif } break; @@ -1232,6 +1300,7 @@ static THREAD_F cmd_listen_thread (void *arg) // packet_t pp; + alevel_t alevel; // Bug fix in version 1.1: // @@ -1246,7 +1315,8 @@ static THREAD_F cmd_listen_thread (void *arg) // first byte of the frame. Unfortunately, it did not subtract one from // cmd.hdr.data_len so we ended up sending an extra byte. - pp = ax25_from_frame ((unsigned char *)cmd.data+1, cmd.hdr.data_len - 1, -1); + 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); @@ -1293,15 +1363,8 @@ static THREAD_F cmd_listen_thread (void *arg) // Previously used sizeof(reply) but compiler rounded it up to next byte boundary. // That's why more cumbersome size expression is used. - if (debug_client) { - debug_print (TO_CLIENT, client, &reply.hdr, sizeof(reply.hdr) + sizeof(reply.data)); - } + send_to_client (client, &reply); -#if __WIN32__ - send (client_sock[client], (char*)(&reply), sizeof(reply.hdr) + sizeof(reply.data), 0); -#else - n = write (client_sock[client], &reply, sizeof(reply.hdr) + sizeof(reply.data)); -#endif } break; @@ -1318,7 +1381,7 @@ static THREAD_F cmd_listen_thread (void *arg) text_color_set(DW_COLOR_ERROR); dw_printf ("\n"); - dw_printf ("Can't process command from AGW client app %d.\n", client); + 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; @@ -1372,6 +1435,31 @@ static THREAD_F cmd_listen_thread (void *arg) 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); diff --git a/server.h b/server.h index 03514ac..19e7e13 100644 --- a/server.h +++ b/server.h @@ -11,7 +11,7 @@ void server_set_debug (int n); -void server_init (struct misc_config_s *misc_config); +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); diff --git a/telemetry.c b/telemetry.c index 310c4df..95fc141 100644 --- a/telemetry.c +++ b/telemetry.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014 John Langner, WB2OSZ +// 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 @@ -23,6 +23,15 @@ //#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 + /*------------------------------------------------------------------ * @@ -220,6 +229,7 @@ static int t_ndp (char *str) * * 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. @@ -243,7 +253,7 @@ static int t_ndp (char *str) * *--------------------------------------------------------------------*/ -void telemetry_data_original (char *station, char *info, char *output, char *comment) +void telemetry_data_original (char *station, char *info, int quiet, char *output, char *comment) { int n; int seq; @@ -275,8 +285,10 @@ void telemetry_data_original (char *station, char *info, char *output, char *com } if (strncmp(info, "T#", 2) != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Error: Information part of telemetry packet must begin with \"#\"\n"); + if ( ! quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Error: Information part of telemetry packet must begin with \"#\"\n"); + } return; } @@ -298,9 +310,11 @@ void telemetry_data_original (char *station, char *info, char *output, char *com ndp[n] = t_ndp(p); } if (strlen(p) != 3 || araw[n] < 0 || araw[n] > 255 || araw[n] != (int)(araw[n])) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Telemetry analog values should be 3 digit integer values in range of 000 to 255.\n"); - dw_printf("Some applications might not interpret \"%s\" properly.\n", p); + 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++; } @@ -312,8 +326,10 @@ void telemetry_data_original (char *station, char *info, char *output, char *com int k; if (strlen(next) < 8) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Expected to find 8 binary digits after \"%s\" for the digital values.\n", p); + if ( ! 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); @@ -327,16 +343,20 @@ void telemetry_data_original (char *station, char *info, char *output, char *com draw[k] = 1; } else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Found \"%c\" when expecting 0 or 1 for digital value %d.\n", next[k], k+1); + 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) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Found fewer than expected number of telemetry data values.\n"); + if ( ! quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Found fewer than expected number of telemetry data values.\n"); + } } /* @@ -632,6 +652,7 @@ void telemetry_unit_label_message (char *station, char *msg) * 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. * @@ -643,7 +664,7 @@ void telemetry_unit_label_message (char *station, char *msg) * *--------------------------------------------------------------------*/ -void telemetry_coefficents_message (char *station, char *msg) +void telemetry_coefficents_message (char *station, char *msg, int quiet) { int n; char stemp[256]; @@ -678,18 +699,22 @@ void telemetry_coefficents_message (char *station, char *msg) pm->coeff_ndp[n/3][n%3] = t_ndp (p); } else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Equation coefficent position A%d%c is empty.\n", n/3+1, n%3+'a'); - dw_printf ("Some applications might not handle this correctly.\n"); + 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) { - 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 ( ! 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 @@ -718,6 +743,7 @@ void telemetry_coefficents_message (char *station, char *msg) * 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. * @@ -727,7 +753,7 @@ void telemetry_coefficents_message (char *station, char *msg) * *--------------------------------------------------------------------*/ -void telemetry_bit_sense_message (char *station, char *msg) +void telemetry_bit_sense_message (char *station, char *msg, int quiet) { int n; struct t_metadata_s *pm; @@ -741,8 +767,10 @@ void telemetry_bit_sense_message (char *station, char *msg) pm = t_get_metadata(station); if (strlen(msg) < 8) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("The telemetry bit sense message should have at least 8 characters.\n"); + 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++) { @@ -754,8 +782,10 @@ void telemetry_bit_sense_message (char *station, char *msg) pm->sense[n] = 0; } else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Bit position %d sense value was \"%c\" when 0 or 1 was expected.\n", n+1, msg[n]); + 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]); + } } } @@ -927,8 +957,19 @@ static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_A } /* end t_data_process */ +/*------------------------------------------------------------------- + * + * Unit test. Run with: + * + * make -f Makefile.? etest + * + *--------------------------------------------------------------------*/ + + #if TEST + + int main ( ) { char result[256]; @@ -938,33 +979,42 @@ int main ( ) 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", result, comment); + 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", result, 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", result, comment); - telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,0110", result, comment); - telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123", result, comment); - telemetry_data_original ("WB2OSZ", "T#005,199,000,255,,123,01101001", result, comment); - telemetry_data_original ("WB2OSZ", "T#005,199,000,255,073,123,01101009", result, comment); + 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", result, comment); + 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); @@ -985,26 +1035,32 @@ int main ( ) #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"); + 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"); - telemetry_coefficents_message ("N0QBF-11", "0,5.2,0,0,.53,-32,3,4.39,49,-32,3,18,1,,3"); + 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"); + telemetry_bit_sense_message ("N0QBF-11", "10110000,N0QBF's Big Balloon", 0); // Too few and invalid digits. - telemetry_bit_sense_message ("N0QBF-11", "1011000"); - telemetry_bit_sense_message ("N0QBF-11", "10110008"); + telemetry_bit_sense_message ("N0QBF-11", "1011000", 0); + telemetry_bit_sense_message ("N0QBF-11", "10110008", 0); #endif - telemetry_coefficents_message ("M0XER-3", "0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0"); - telemetry_bit_sense_message ("M0XER-3", "11111111,10mW research balloon"); + 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"); @@ -1014,6 +1070,8 @@ int main ( ) telemetry_data_base91 ("M0XER-3", "x&G=!(8s!,", result); + // TODO: Should return success/fail so visual inspection is not needed. + exit (0); } diff --git a/telemetry.h b/telemetry.h index a6b8c71..d2b56e7 100644 --- a/telemetry.h +++ b/telemetry.h @@ -2,7 +2,7 @@ /* telemetry.h */ -void telemetry_data_original (char *station, char *info, char *output, char *comment); +void telemetry_data_original (char *station, char *info, int quiet, char *output, char *comment); void telemetry_data_base91 (char *station, char *cdata, char *output); @@ -10,6 +10,6 @@ void telemetry_name_message (char *station, char *msg); void telemetry_unit_label_message (char *station, char *msg); -void telemetry_coefficents_message (char *station, char *msg); +void telemetry_coefficents_message (char *station, char *msg, int quiet); -void telemetry_bit_sense_message (char *station, char *msg); +void telemetry_bit_sense_message (char *station, char *msg, int quiet); diff --git a/textcolor.c b/textcolor.c index 94054a6..e0fd3a1 100644 --- a/textcolor.c +++ b/textcolor.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -58,7 +58,7 @@ * * 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 have been + * 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 @@ -74,19 +74,6 @@ #if __WIN32__ -// /* Missing from MinGW header file. */ -// #define vsprintf_s vsnprintf - -//_CRTIMP int __cdecl __MINGW_NOTHROW vsprintf_s (char*, size_t, const char*, __VALIST); - -//int vsprintf_s( -// char *buffer, -// size_t numberOfElements, -// const char *format, -// va_list argptr -//); - - #include #define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY) @@ -120,7 +107,6 @@ 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. */ @@ -131,12 +117,12 @@ static const char background_white[] = "\e[5;47m"; /* 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 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. */ @@ -146,6 +132,28 @@ 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 */ @@ -160,6 +168,10 @@ 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"; @@ -214,7 +226,7 @@ void text_color_init (int enable_color) if (g_enable_color) { //printf ("%s", clear_eos); printf ("%s", background_white); - //printf ("%s", clear_eos); + printf ("%s", clear_eos); printf ("%s", black); } #endif diff --git a/textcolor.h b/textcolor.h index 33f568e..4e38c83 100644 --- a/textcolor.h +++ b/textcolor.h @@ -7,6 +7,10 @@ * *--------------------------------------------------------------------*/ + +#ifndef TEXTCOLOR_H +#define TEXTCOLOR_H 1 + enum dw_color_e { DW_COLOR_INFO, /* black */ DW_COLOR_ERROR, /* red */ DW_COLOR_REC, /* green */ @@ -51,3 +55,4 @@ int dw_printf (const char *fmt, ...) __attribute__((format(printf,1,2))); /* gnu C lib. */ #endif +#endif diff --git a/tocalls.txt b/tocalls.txt index cca3b8a..9b4dfba 100644 --- a/tocalls.txt +++ b/tocalls.txt @@ -1,13 +1,16 @@ -APRS TO-CALL VERSION NUMBERS 02 Sep 2014 +APRS TO-CALL VERSION NUMBERS 27 Apr 2015 ------------------------------------------------------------------- WB4APR - -02 Sep 14 added APSTMx for W7QO's Balloon trackers -21 Aug 14 added APSMSx for Paul Defrusne's SMS gateway -11 Aug 14 added APCWP8 for John GM7HHB, WinphoneAPRS -18 Dec 13 added APZWKR for GM1WKR NetSked application -22 Oct 13 added APFIxx for APRS.FI OH7LZB, Hessu -23 Aug 13 added APOxxx for OSCAR satellites for AMSAT-LU by LU9DO +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 @@ -39,6 +42,7 @@ 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 @@ -50,8 +54,10 @@ a TOCALL number series: 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 @@ -77,6 +83,7 @@ a TOCALL number series: 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) @@ -126,6 +133,7 @@ a TOCALL number series: 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 @@ -173,6 +181,7 @@ a TOCALL number 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) diff --git a/tq.c b/tq.c index 9f4a58d..d7d59dc 100644 --- a/tq.c +++ b/tq.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2012 John Langner, WB2OSZ +// 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 @@ -31,6 +31,8 @@ * 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 @@ -48,30 +50,28 @@ -static int tq_num_channels; /* Set once during intialization and */ - /* should not change after that. */ 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 CRITICAL_SECTION tq_cs; /* Critical section for updating queues. */ - -static HANDLE wake_up_event; /* Notify transmit thread when queue not empty. */ +static HANDLE wake_up_event[MAX_CHANS]; /* Notify transmit thread when queue not empty. */ #else -static pthread_mutex_t tq_mutex; /* Critical section for updating queues. */ +static pthread_cond_t wake_up_cond[MAX_CHANS]; /* Notify transmit thread when queue not empty. */ -static pthread_cond_t wake_up_cond; /* Notify transmit thread when queue not empty. */ +static pthread_mutex_t wake_up_mutex[MAX_CHANS]; /* Required by cond_wait. */ -static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ - -static int xmit_thread_is_waiting = 0; +static int xmit_thread_is_waiting[MAX_CHANS]; #endif -static int tq_is_empty (void); +static int tq_is_empty (int chan); /*------------------------------------------------------------------- @@ -80,7 +80,7 @@ static int tq_is_empty (void); * * Purpose: Initialize the transmit queue. * - * Inputs: nchan - Number of communication channels. + * Inputs: audio_config_p - Audio device configuration. * * Outputs: * @@ -104,23 +104,25 @@ static int tq_is_empty (void); * (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. + * Each audio channel has its own queue. * *--------------------------------------------------------------------*/ -void tq_init (int nchan) +static struct audio_s *save_audio_config_p; + + + +void tq_init (struct audio_s *audio_config_p) { int c, p; - int err; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_init ( %d )\n", nchan); #endif - tq_num_channels = nchan; - assert (tq_num_channels >= 1 && tq_num_channels <= MAX_CHANS); + + save_audio_config_p = audio_config_p; for (c=0; cachan[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 - err = pthread_cond_init (&wake_up_cond, NULL); + int err; + for (c = 0; c < MAX_CHANS; c++) { -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("tq_init: pthread_cond_init returns %d\n", err); -#endif + 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); + } - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_init: pthread_cond_init err=%d", err); - perror (""); - exit (1); + dw_mutex_init(&(wake_up_mutex[c])); + } } - xmit_thread_is_waiting = 0; #endif - } /* end tq_init */ @@ -207,6 +202,9 @@ void tq_init (int nchan) * 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. * @@ -214,31 +212,63 @@ void tq_init (int nchan) void tq_append (int chan, int prio, packet_t pp) { -// int was_empty; -// int c, p; packet_t plast; packet_t pnext; - int err; + //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 (tq_num_channels >= 1 && tq_num_channels <= MAX_CHANS); + + + + assert (chan >= 0 && chan < MAX_CHANS); assert (prio >= 0 && prio < TQ_NUM_PRIO); - if (chan < 0 || chan >= tq_num_channels) { +#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 radio channel %d.\n", chan); + dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); ax25_delete(pp); return; } -/* Is transmit queue out of control? */ +/* + * 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 (tq_count(chan,prio) > 20) { + if (ax25_is_aprs(pp) && tq_count(chan,prio) > 100) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Transmit packet queue is too long. Discarding transmit request.\n"); + 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; @@ -248,17 +278,8 @@ void tq_append (int chan, int prio, packet_t pp) text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_append: enter critical section\n"); #endif -#if __WIN32__ - EnterCriticalSection (&tq_cs); -#else - err = pthread_mutex_lock (&tq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_append: pthread_mutex_lock err=%d", err); - perror (""); - exit (1); - } -#endif + + dw_mutex_lock (&tq_mutex); // was_empty = 1; // for (c=0; c= 1 && tq_num_channels <= MAX_CHANS); + assert (chan >= 0 && chan < MAX_CHANS); - -#if __WIN32__ - EnterCriticalSection (&tq_cs); -#else - err = pthread_mutex_lock (&tq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_wait_while_empty: pthread_mutex_lock err=%d", err); - perror (""); - exit (1); - } -#endif + dw_mutex_lock (&tq_mutex); #if DEBUG //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("tq_wait_while_empty (): after pthread_mutex_lock\n"); + //dw_printf ("tq_wait_while_empty (%d): after pthread_mutex_lock\n", chan); #endif - is_empty = tq_is_empty(); + is_empty = tq_is_empty(chan); + + dw_mutex_unlock (&tq_mutex); -#if __WIN32__ - LeaveCriticalSection (&tq_cs); -#else - err = pthread_mutex_unlock (&tq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_wait_while_empty: pthread_mutex_unlock err=%d", err); - perror (""); - exit (1); - } -#endif #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("tq_wait_while_empty () : left critical section\n"); + 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 (): is_empty = %d\n", is_empty); + 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 (): SLEEP - about to call cond wait\n"); + dw_printf ("tq_wait_while_empty (%d): SLEEP - about to call cond wait\n", chan); #endif #if __WIN32__ - WaitForSingleObject (wake_up_event, INFINITE); + WaitForSingleObject (wake_up_event[chan], INFINITE); #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -411,45 +394,33 @@ void tq_wait_while_empty (void) #endif #else - err = pthread_mutex_lock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_wait_while_empty: pthread_mutex_lock wu err=%d", err); - perror (""); - exit (1); - } + dw_mutex_lock (&(wake_up_mutex[chan])); - xmit_thread_is_waiting = 1; - err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); - xmit_thread_is_waiting = 0; + 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 (): WOKE UP - returned from cond wait, err = %d\n", err); + 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: 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 ("tq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); + 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 () returns\n"); + dw_printf ("tq_wait_while_empty (%d) returns\n", chan); #endif } @@ -474,24 +445,13 @@ packet_t tq_remove (int chan, int prio) { packet_t result_p; - int err; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_remove(%d,%d) enter critical section\n", chan, prio); #endif -#if __WIN32__ - EnterCriticalSection (&tq_cs); -#else - err = pthread_mutex_lock (&tq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_remove: pthread_mutex_lock err=%d", err); - perror (""); - exit (1); - } -#endif + dw_mutex_lock (&tq_mutex); if (queue_head[chan][prio] == NULL) { @@ -504,22 +464,20 @@ packet_t tq_remove (int chan, int prio) ax25_set_nextp (result_p, NULL); } -#if __WIN32__ - LeaveCriticalSection (&tq_cs); -#else - err = pthread_mutex_unlock (&tq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("tq_remove: pthread_mutex_unlock err=%d", err); - perror (""); - exit (1); - } -#endif + 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); } @@ -528,29 +486,29 @@ packet_t tq_remove (int chan, int prio) * * Name: tq_is_empty * - * Purpose: Test queue is empty. - * - * Inputs: None - this applies to all channels and priorities. + * Purpose: Test if queues for specified channel are empty. + * + * Inputs: chan Channel * * Returns: True if nothing in the queue. * *--------------------------------------------------------------------*/ -static int tq_is_empty (void) +static int tq_is_empty (int chan) { - int c, p; + int p; + + assert (chan >= 0 && chan < MAX_CHANS); - for (c=0; c= 0 && c < MAX_CHANS); - assert (p >= 0 && p < TQ_NUM_PRIO); + assert (p >= 0 && p < TQ_NUM_PRIO); - if (queue_head[c][p] != NULL) - return (0); - } + if (queue_head[chan][p] != NULL) + return (0); } + return (1); } /* end tq_is_empty */ diff --git a/tq.h b/tq.h index ff1d73f..92ee3ba 100644 --- a/tq.h +++ b/tq.h @@ -20,11 +20,11 @@ -void tq_init (int nchan); +void tq_init (struct audio_s *audio_config_p); void tq_append (int chan, int prio, packet_t pp); -void tq_wait_while_empty (void); +void tq_wait_while_empty (int chan); packet_t tq_remove (int chan, int prio); diff --git a/tt_text.c b/tt_text.c index 3df216c..b40f1d2 100644 --- a/tt_text.c +++ b/tt_text.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013 John Langner, WB2OSZ +// Copyright (C) 2013, 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 @@ -71,11 +71,28 @@ * * 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] = { @@ -92,6 +109,52 @@ static const char translate[10][4] = { /* 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 #include #include @@ -100,6 +163,7 @@ static const char translate[10][4] = { #include #include "textcolor.h" +#include "tt_text.h" #if defined(ENC_MAIN) || defined(DEC_MAIN) @@ -304,6 +368,217 @@ int tt_text_to_two_key (char *text, int quiet, char *buttons) } /* 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 @@ -427,7 +702,6 @@ int tt_two_key_to_text (char *buttons, int quiet, char *text) char c; int row, col; int errors = 0; - int n; *t = '\0'; @@ -491,6 +765,163 @@ int tt_two_key_to_text (char *buttons, int quiet, char *text) } /* 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 @@ -598,15 +1029,25 @@ int main (int argc, char *argv[]) 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 */ @@ -665,6 +1106,18 @@ int main (int argc, char *argv[]) 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 */ diff --git a/tt_text.h b/tt_text.h index 23892da..a1690e5 100644 --- a/tt_text.h +++ b/tt_text.h @@ -2,16 +2,26 @@ /* tt_text.h */ -int tt_text_to_multipress (char *text, int quiet, char *buttons); +/* 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); + /* end tt_text.h */ \ No newline at end of file diff --git a/tt_user.c b/tt_user.c index fd048cf..2afe23d 100644 --- a/tt_user.c +++ b/tt_user.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013 John Langner, WB2OSZ +// Copyright (C) 2013, 2014 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -43,6 +43,7 @@ #include #include "direwolf.h" +#include "version.h" #include "ax25_pad.h" #include "textcolor.h" #include "aprs_tt.h" @@ -61,7 +62,7 @@ */ #if TT_MAIN -#define MAX_TT_USERS 3 +#define MAX_TT_USERS 3 #else #define MAX_TT_USERS 100 #endif @@ -153,35 +154,18 @@ static void xmit_object_report (int i, double c_lat, double c_long, int ambiguit * *----------------------------------------------------------------*/ -static struct tt_config_s tt_config; +static struct audio_s *save_audio_config_p; + +static struct tt_config_s *save_tt_config_p; -void tt_user_init (struct tt_config_s *p) +void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_config) { int i; -#if TT_MAIN - /* For unit testing. */ + save_audio_config_p = p_audio_config; - memset (&tt_config, 0, sizeof(struct tt_config_s)); - - /* Don't care about the location translation here. */ - - tt_config.retain_time = 20; /* Normally 80 minutes. */ - tt_config.num_xmits = 3; - assert (tt_config.num_xmits <= TT_MAX_XMITS); - tt_config.xmit_delay[0] = 3; /* Before initial transmission. */ - tt_config.xmit_delay[1] = 5; - tt_config.xmit_delay[2] = 5; - - tt_config.corral_lat = 42.61900; - tt_config.corral_lon = -71.34717; - tt_config.corral_offset = 0.02 / 60; - tt_config.corral_ambiguity = 0; - -#else - memcpy (&tt_config, p, sizeof(struct tt_config_s)); -#endif + save_tt_config_p = p_tt_config; for (i=0; ixmit_delay[0]; return (0); /* Success! */ @@ -515,16 +499,16 @@ void tt_user_background (void) for (i=0; inum_xmits && tt_user[i].next_xmit <= now) { - xmit_object_report (i, tt_config.corral_lat, tt_config.corral_lon, - tt_config.corral_ambiguity, tt_config.corral_offset); + 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 < tt_config.num_xmits) { + if (tt_user[i].xmits < save_tt_config_p->num_xmits) { /* Schedule next one. */ - tt_user[i].next_xmit += tt_config.xmit_delay[tt_user[i].xmits]; + tt_user[i].next_xmit += save_tt_config_p->xmit_delay[tt_user[i].xmits]; } } } @@ -535,9 +519,9 @@ void tt_user_background (void) */ for (i=0; iretain_time < now) { -//TODO: remove printf ("debug: purging expired user %d\n", i); + // debug - dw_printf ("debug: purging expired user %d\n", i); clear_user (i); } @@ -599,6 +583,7 @@ static void xmit_object_report (int i, double c_lat, double c_long, int ambiguit packet_t pp; unsigned char fbuf[AX25_MAX_PACKET_LEN]; int flen; + char c4[4]; assert (i >= 0 && i < MAX_TT_USERS); @@ -649,60 +634,69 @@ static void xmit_object_report (int i, double c_lat, double c_long, int ambiguit info_comment[MAX_COMMENT_LEN] = '\0'; /* - * Combine with header from configuration file. - * - * (If APRStt gateway has been configured.) + * Packet header is built from mycall (of transmit channel) and software version. */ - if (tt_config.obj_xmit_header[0] != '\0') { -// TODO: Should take the call from radio channel configuration. -// Application version is compiled in. -// Config should have only optional via path. + 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); - strcpy (stemp, tt_config.obj_xmit_header); - strcat (stemp, ":"); +/* + * Append via path if specified. + */ - encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, + 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); + strcat (stemp, object_info); - //text_color_set(DW_COLOR_ERROR); - //printf ("\nDEBUG: %s\n\n", stemp); + //text_color_set(DW_COLOR_ERROR); + //printf ("\nDEBUG: %s\n\n", stemp); #if TT_MAIN - printf ("---> %s\n\n", stemp); + printf ("---> %s\n\n", stemp); #else /* * Convert to packet and append to transmit queue. */ - pp = ax25_from_text (stemp, 1); + pp = ax25_from_text (stemp, 1); - flen = ax25_pack (pp, fbuf); + 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. + // 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); + int chan = 0; + igate_send_rec_packet (chan, pp); - /* Remember it so we don't digipeat our own. */ + /* Remember it so we don't digipeat our own. */ - dedupe_remember (pp, tt_config.obj_xmit_chan); + dedupe_remember (pp, save_tt_config_p->obj_xmit_chan); - tq_append (tt_config.obj_xmit_chan, TQ_PRIO_1_LO, pp); + tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp); #endif - } + } @@ -755,23 +749,49 @@ void tt_user_dump (void) * * 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 ; ./a.exe + * $ 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 -void text_color_set ( enum dw_color_e c ) -{ - return; -} + +static struct audio_s my_audio_config; + +static struct tt_config_s my_tt_config; + int main (int argc, char *argv[]) { int n; - tt_user_init(NULL); +/* 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); diff --git a/tt_user.h b/tt_user.h index 7d5b3f3..913e915 100644 --- a/tt_user.h +++ b/tt_user.h @@ -2,7 +2,9 @@ /* tt_user.h */ -void tt_user_init (struct tt_config_s *p); +#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); diff --git a/ttcalc.c b/ttcalc.c new file mode 100644 index 0000000..e3f32cf --- /dev/null +++ b/ttcalc.c @@ -0,0 +1,544 @@ + +// +// 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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#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; nai_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 */ diff --git a/udp_test.c b/udp_test.c deleted file mode 100644 index 4596fed..0000000 --- a/udp_test.c +++ /dev/null @@ -1,407 +0,0 @@ - -// -// This file is part of Dire Wolf, an amateur radio packet TNC. -// -// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ -// Contributed by Fabrice Faure for UDP part -// -// 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 . -// - - -/*------------------------------------------------------------------- - * - * Name: udp_test.c - * - * Purpose: Unit test for the udp reception with AFSK demodulator. - * - * Inputs: Get data by listening on a given UDP port (first parameter is the udp port) - * - * Description: This can be used to test the AFSK demodulator with udp data - * - *--------------------------------------------------------------------*/ - -// #define X 1 - - -#include -#include -//#include -#include -#include -#include -#include - -#define UDPTEST_C 1 - -#include "audio.h" -#include "demod.h" -// #include "fsk_demod_agc.h" -#include "textcolor.h" -#include "ax25_pad.h" -#include "hdlc_rec2.h" -#include -#include -#include -#include -static FILE *fp; -static int e_o_f; -static int packets_decoded = 0; -//Bytes read in the current UDP socket buffer -static int bytes_read = 0; -//Total bytes read from UDP -static int total_bytes_read = 0; -//UDP socket used for receiving data -static int sock; - -//UDP receiving port -#define DEFAULT_UDP_PORT 6667 -//Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes) -#define UDP_BUF_MAXLEN 20000 -//UDP receiving buffer , may use double or FIFO buffers in the future for better performance -unsigned char udp_buf[UDP_BUF_MAXLEN]; - -//TODO Provide cmdline parameters or config to change these values -#define DEFAULT_UDP_NUM_CHANNELS 1 -#define DEFAULT_UDP_SAMPLES_PER_SEC 48000 -#define DEFAULT_UDP_BITS_PER_SAMPLE 16 - - -int main (int argc, char *argv[]) -{ - - struct audio_s modem; - int channel; - time_t start_time; - int udp_port; - - text_color_init(1); - text_color_set(DW_COLOR_INFO); - if (argc < 2) { - udp_port = DEFAULT_UDP_PORT; - printf ("Using default UDP port : %d\n", udp_port); - } else { - udp_port = atoi(argv[1]); - } - - struct sockaddr_in si_me; - int i, slen=sizeof(si_me); - int data_size = 0; - - if ((sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { - fprintf (stderr, "Couldn't create socket %d\n", errno); - exit(errno); - } - - memset((char *) &si_me, 0, sizeof(si_me)); - si_me.sin_family = AF_INET; - si_me.sin_port = htons(6667); - si_me.sin_addr.s_addr = htonl(INADDR_ANY); - if (bind(sock, &si_me, sizeof(si_me))==-1) { - fprintf (stderr, "Couldn't bind socket %d\n", errno); - exit(errno); - } - -#ifdef DEBUG_RECEIVED_DATA - fp = fopen("udp.raw", "w"); - if (fp == NULL) { - fprintf (stderr, "Couldn't open file for read: %s\n", argv[1]); - //perror ("more info?"); - exit (1); - } -#endif - - start_time = time(NULL); - -/* - * First apply defaults. - * TODO: split config into two parts: _init (use here) and _read (only in direwolf). - */ - modem.num_channels = DEFAULT_UDP_NUM_CHANNELS; - modem.samples_per_sec = DEFAULT_UDP_SAMPLES_PER_SEC; - modem.bits_per_sample = DEFAULT_UDP_BITS_PER_SAMPLE; - - /* TODO: should have a command line option for this. */ - //modem.fix_bits = RETRY_NONE; - //modem.fix_bits = RETRY_SINGLE; - //modem.fix_bits = RETRY_DOUBLE; - //modem.fix_bits = RETRY_TRIPLE; - modem.fix_bits = RETRY_TWO_SEP; - //Only one channel for UDP - channel = 0; - - modem.modem_type[channel] = AFSK; - modem.mark_freq[channel] = DEFAULT_MARK_FREQ; - modem.space_freq[channel] = DEFAULT_SPACE_FREQ; - modem.baud[channel] = DEFAULT_BAUD; - - strcpy (modem.profiles[channel], "C"); - // temp - // strcpy (modem.profiles[channel], "F"); - modem.num_subchan[channel] = strlen(modem.profiles); - - //TODO: add -h command line option. -//#define HF 1 - -#if HF - modem.mark_freq[channel] = 1600; - modem.space_freq[channel] = 1800; - modem.baud[channel] = 300; - strcpy (modem.profiles[channel], "B"); - modem.num_subchan[channel] = strlen(modem.profiles); -#endif - - - modem.num_freq[channel] = 1; - modem.offset[channel] = 0; - - - text_color_set(DW_COLOR_INFO); - printf ("%d samples per second\n", modem.samples_per_sec); - printf ("%d bits per sample\n", modem.bits_per_sample); - printf ("%d audio channels\n", modem.num_channels); -/* - * Initialize the AFSK demodulator and HDLC decoder. - */ - multi_modem_init (&modem); - - - e_o_f = 0; - bytes_read = 0; - data_size = 0; - - while ( ! e_o_f ) - { - - int audio_sample; - int c; - - //If all the data in the udp buffer has been processed, get new data from udp socket - if (bytes_read == data_size) { - data_size = buffer_get(UDP_BUF_MAXLEN); - //Got EOF packet - if (data_size >= 0 && data_size <= 1) { - printf("Got NULL packet : terminate decoding (packet received with size %d)", data_size); - e_o_f = 1; - break; - } - - bytes_read = 0; - } - - - /* This reads either 1 or 2 bytes depending on */ - /* bits per sample. */ - - audio_sample = demod_get_sample (); - - if (audio_sample >= 256 * 256) - e_o_f = 1; - 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); - printf ("\n\n"); - printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time)); -#ifdef DEBUG_RECEIVED_DATA - fclose(fp); -#endif - exit (0); -} - -int buffer_get (unsigned int size) { - struct sockaddr_in si_other; - int slen=sizeof(si_other); - int ch, res,i; - if (size > UDP_BUF_MAXLEN) { - printf("size too big %d", size); - return -1; - } - - res = recvfrom(sock, udp_buf, size, 0, &si_other, &slen); -#ifdef DEBUG_RECEIVED_DATA - fwrite(udp_buf,res,1,fp); -#endif - - return res; -} -/* - * Simulate sample from the audio device. - */ -int audio_get (void) -{ - int ch; - ch = udp_buf[bytes_read]; - bytes_read++; - total_bytes_read++; - - return (ch); -} - - - -/* - * Rather than queuing up frames with bad FCS, - * try to fix them immediately. - */ - -void rdq_append (rrbb_t rrbb) -{ - int chan; - int 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); -} - - -/* - * This is called when we have a good frame. - */ - -void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, retry_t retries, char *spectrum) -{ - - //int err; - //char *p; - char stemp[500]; - unsigned char *pinfo; - int info_len; - int h; - char heard[20]; - //packet_t pp; - - - packets_decoded++; - - - ax25_format_addrs (pp, stemp); - - info_len = ax25_get_info (pp, &pinfo); - - /* Print so we can see what is going on. */ - -#if 1 - /* Display audio input level. */ - /* Who are we hearing? Original station or digipeater. */ - - h = ax25_get_heard(pp); - ax25_get_addr_with_ssid(pp, h, heard); - - text_color_set(DW_COLOR_DEBUG); - printf ("\n"); - - if (h != AX25_SOURCE) { - printf ("Digipeater "); - } - printf ("%s audio level = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum); - - -#endif - -// Display non-APRS packets in a different color. - - if (ax25_is_aprs(pp)) { - text_color_set(DW_COLOR_REC); - printf ("[%d] ", chan); - } - else { - text_color_set(DW_COLOR_DEBUG); - printf ("[%d] ", chan); - } - - printf ("%s", stemp); /* stations followed by : */ - ax25_safe_print ((char *)pinfo, info_len, 0); - printf ("\n"); - - ax25_delete (pp); - -} /* end app_process_rec_packet */ - - - - - - -/* 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 time. */ - - - -double dtime_now (void) -{ -#if __WIN32__ - /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ - - FILETIME ft; - - GetSystemTimeAsFileTime (&ft); - - return ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + - (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); -#else - /* tv_sec is seconds from Jan 1, 1970. */ - - struct timespec ts; - int sec, ns; - double x1, x2; - double result; - - clock_gettime (CLOCK_REALTIME, &ts); - - //result = (double)(ts.tv_sec) + (double)(ts.tv_nsec) / 1000000000.; - //result = (double)(ts.tv_sec) + ((double)(ts.tv_nsec) * .001 * .001 *.001); - sec = (int)(ts.tv_sec); - ns = (int)(ts.tv_nsec); - x1 = (double)(sec); - //x1 = (double)(sec-1300000000); /* try to work around strange result. */ - //x2 = (double)(ns) * .001 * .001 *.001; - x2 = (double)(ns/1000000) *.001; - result = x1 + x2; - - /* Sometimes this returns NAN. How could that possibly happen? */ - /* This is REALLY BIZARRE! */ - /* Multiplying a number by a billionth often produces NAN. */ - /* Adding a fraction to a number over a billion often produces NAN. */ - - /* Hardware problem??? Need to test on different computer. */ - - if (isnan(result)) { - text_color_set(DW_COLOR_ERROR); - printf ("\ndtime_now(): %d, %d -> %.3f + %.3f -> NAN!!!\n\n", sec, ns, x1, x2); - } - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - printf ("dtime_now() returns %.3f\n", result); -#endif - - return (result); -#endif -} - - - -/* end atest.c */ diff --git a/utm/LatLong-UTMconversion.c b/utm/LatLong-UTMconversion.c deleted file mode 100644 index a0c4cd9..0000000 --- a/utm/LatLong-UTMconversion.c +++ /dev/null @@ -1,190 +0,0 @@ -//LatLong- UTM conversion.c -//Lat Long - UTM, UTM - Lat Long conversions - -#include -#include -#include -#include "constants.h" -#include "LatLong-UTMconversion.h" - - -/*Reference ellipsoids derived from Peter H. Dana's website- -http://www.utexas.edu/depts/grg/gcraft/notes/datum/elist.html -Department of Geography, University of Texas at Austin -Internet: pdana@mail.utexas.edu -3/22/95 - -Source -Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System -1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency -*/ - - - -void LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long, - double *UTMNorthing, double *UTMEasting, char* UTMZone) -{ -//converts lat/long to UTM coords. Equations from USGS Bulletin 1532 -//East Longitudes are positive, West longitudes are negative. -//North latitudes are positive, South latitudes are negative -//Lat and Long are in decimal degrees - //Written by Chuck Gantz- chuck.gantz@globalstar.com - - double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius; - double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared; - double k0 = 0.9996; - - double LongOrigin; - double eccPrimeSquared; - double N, T, C, A, M; - -//Make sure the longitude is between -180.00 .. 179.9 - double LongTemp = (Long+180)-(int)((Long+180)/360)*360-180; // -180.00 .. 179.9; - - double LatRad = Lat*deg2rad; - double LongRad = LongTemp*deg2rad; - double LongOriginRad; - int ZoneNumber; - - ZoneNumber = (int)((LongTemp + 180)/6) + 1; - - if( Lat >= 56.0 && Lat < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0 ) - ZoneNumber = 32; - - // Special zones for Svalbard - if( Lat >= 72.0 && Lat < 84.0 ) - { - if( LongTemp >= 0.0 && LongTemp < 9.0 ) ZoneNumber = 31; - else if( LongTemp >= 9.0 && LongTemp < 21.0 ) ZoneNumber = 33; - else if( LongTemp >= 21.0 && LongTemp < 33.0 ) ZoneNumber = 35; - else if( LongTemp >= 33.0 && LongTemp < 42.0 ) ZoneNumber = 37; - } - LongOrigin = (ZoneNumber - 1)*6 - 180 + 3; //+3 puts origin in middle of zone - LongOriginRad = LongOrigin * deg2rad; - - //compute the UTM Zone from the latitude and longitude - sprintf(UTMZone, "%d%c", ZoneNumber, UTMLetterDesignator(Lat)); - - eccPrimeSquared = (eccSquared)/(1-eccSquared); - - N = a/sqrt(1-eccSquared*sin(LatRad)*sin(LatRad)); - T = tan(LatRad)*tan(LatRad); - C = eccPrimeSquared*cos(LatRad)*cos(LatRad); - A = cos(LatRad)*(LongRad-LongOriginRad); - - M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256)*LatRad - - (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*LatRad) - + (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*LatRad) - - (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*LatRad)); - - *UTMEasting = (double)(k0*N*(A+(1-T+C)*A*A*A/6 - + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120) - + 500000.0); - - *UTMNorthing = (double)(k0*(M+N*tan(LatRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24 - + (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720))); - if(Lat < 0) - *UTMNorthing += 10000000.0; //10000000 meter offset for southern hemisphere -} - -char UTMLetterDesignator(double Lat) -{ -//This routine determines the correct UTM letter designator for the given latitude -//returns 'Z' if latitude is outside the UTM limits of 84N to 80S - //Written by Chuck Gantz- chuck.gantz@globalstar.com - char LetterDesignator; - - if((84 >= Lat) && (Lat >= 72)) LetterDesignator = 'X'; - else if((72 > Lat) && (Lat >= 64)) LetterDesignator = 'W'; - else if((64 > Lat) && (Lat >= 56)) LetterDesignator = 'V'; - else if((56 > Lat) && (Lat >= 48)) LetterDesignator = 'U'; - else if((48 > Lat) && (Lat >= 40)) LetterDesignator = 'T'; - else if((40 > Lat) && (Lat >= 32)) LetterDesignator = 'S'; - else if((32 > Lat) && (Lat >= 24)) LetterDesignator = 'R'; - else if((24 > Lat) && (Lat >= 16)) LetterDesignator = 'Q'; - else if((16 > Lat) && (Lat >= 8)) LetterDesignator = 'P'; - else if(( 8 > Lat) && (Lat >= 0)) LetterDesignator = 'N'; - else if(( 0 > Lat) && (Lat >= -8)) LetterDesignator = 'M'; - else if((-8> Lat) && (Lat >= -16)) LetterDesignator = 'L'; - else if((-16 > Lat) && (Lat >= -24)) LetterDesignator = 'K'; - else if((-24 > Lat) && (Lat >= -32)) LetterDesignator = 'J'; - else if((-32 > Lat) && (Lat >= -40)) LetterDesignator = 'H'; - else if((-40 > Lat) && (Lat >= -48)) LetterDesignator = 'G'; - else if((-48 > Lat) && (Lat >= -56)) LetterDesignator = 'F'; - else if((-56 > Lat) && (Lat >= -64)) LetterDesignator = 'E'; - else if((-64 > Lat) && (Lat >= -72)) LetterDesignator = 'D'; - else if((-72 > Lat) && (Lat >= -80)) LetterDesignator = 'C'; - else LetterDesignator = 'Z'; //This is here as an error flag to show that the Latitude is outside the UTM limits - - return LetterDesignator; -} - - -void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone, - double *Lat, double *Long ) -{ -//converts UTM coords to lat/long. Equations from USGS Bulletin 1532 -//East Longitudes are positive, West longitudes are negative. -//North latitudes are positive, South latitudes are negative -//Lat and Long are in decimal degrees. - //Written by Chuck Gantz- chuck.gantz@globalstar.com - - double k0 = 0.9996; - double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius; - double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared; - double eccPrimeSquared; - double e1 = (1-sqrt(1-eccSquared))/(1+sqrt(1-eccSquared)); - double N1, T1, C1, R1, D, M; - double LongOrigin; - double mu, phi1, phi1Rad; - double x, y; - int ZoneNumber; - char* ZoneLetter; - int NorthernHemisphere; //1 for northern hemispher, 0 for southern - - x = UTMEasting - 500000.0; //remove 500,000 meter offset for longitude - y = UTMNorthing; - - ZoneNumber = strtoul(UTMZone, &ZoneLetter, 10); - if (*ZoneLetter == '\0') - { - NorthernHemisphere = 1; //no letter - assume northern hemisphere - } - else if((*ZoneLetter >= 'N' && *ZoneLetter <= 'X') || - (*ZoneLetter >= 'n' && *ZoneLetter <= 'x')) - { - NorthernHemisphere = 1; //point is in northern hemisphere - } - else - { - NorthernHemisphere = 0; //point is in southern hemisphere - y -= 10000000.0; //remove 10,000,000 meter offset used for southern hemisphere - } - - LongOrigin = (ZoneNumber - 1)*6 - 180 + 3; //+3 puts origin in middle of zone - - eccPrimeSquared = (eccSquared)/(1-eccSquared); - - M = y / k0; - mu = M/(a*(1-eccSquared/4-3*eccSquared*eccSquared/64-5*eccSquared*eccSquared*eccSquared/256)); - - phi1Rad = mu + (3*e1/2-27*e1*e1*e1/32)*sin(2*mu) - + (21*e1*e1/16-55*e1*e1*e1*e1/32)*sin(4*mu) - +(151*e1*e1*e1/96)*sin(6*mu); - phi1 = phi1Rad*rad2deg; - - N1 = a/sqrt(1-eccSquared*sin(phi1Rad)*sin(phi1Rad)); - T1 = tan(phi1Rad)*tan(phi1Rad); - C1 = eccPrimeSquared*cos(phi1Rad)*cos(phi1Rad); - R1 = a*(1-eccSquared)/pow(1-eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5); - D = x/(N1*k0); - - *Lat = phi1Rad - (N1*tan(phi1Rad)/R1)*(D*D/2-(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24 - +(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared-3*C1*C1)*D*D*D*D*D*D/720); - *Lat = *Lat * rad2deg; - - *Long = (D-(1+2*T1+C1)*D*D*D/6+(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1) - *D*D*D*D*D/120)/cos(phi1Rad); - *Long = LongOrigin + *Long * rad2deg; - -} diff --git a/utm/LatLong-UTMconversion.h b/utm/LatLong-UTMconversion.h deleted file mode 100644 index e05bfa0..0000000 --- a/utm/LatLong-UTMconversion.h +++ /dev/null @@ -1,30 +0,0 @@ -//LatLong- UTM conversion..h -//definitions for lat/long to UTM and UTM to lat/lng conversions -#include - -#ifndef LATLONGCONV -#define LATLONGCONV - -void LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long, - double *UTMNorthing, double *UTMEasting, char* UTMZone); -void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone, - double *Lat, double *Long ); -char UTMLetterDesignator(double Lat); -void LLtoSwissGrid(const double Lat, const double Long, - double *SwissNorthing, double *SwissEasting); -void SwissGridtoLL(const double SwissNorthing, const double SwissEasting, - double *Lat, double *Long); - -struct Ellipsoid_s { - int id; - char* ellipsoidName; - double EquatorialRadius; - double eccentricitySquared; -}; - -typedef struct Ellipsoid_s Ellipsoid; - -#define WSG84 23 - - -#endif diff --git a/utm/README.txt b/utm/README.txt deleted file mode 100644 index 5be72f8..0000000 --- a/utm/README.txt +++ /dev/null @@ -1,14 +0,0 @@ - -The other files in this directory were copied from - -http://www.gpsy.com/gpsinfo/geotoutm/ - -See attachments to message from Chuck Gantz. -There is no obvious copyright on the source code. -The links in the message are no longer valid. - - -A few minor modifications were made: - -1. Convert from C++ to C. -2. Make the zone check more robust. \ No newline at end of file diff --git a/utm/SwissGrid.cpp b/utm/SwissGrid.cpp deleted file mode 100644 index b3dd3c5..0000000 --- a/utm/SwissGrid.cpp +++ /dev/null @@ -1,140 +0,0 @@ - -#include -#include "constants.h" -#include "LatLong- UTM conversion.h" - -//forward declarations -double CorrRatio(double LatRad, const double C); -double NewtonRaphson(const double initEstimate); - - -void LLtoSwissGrid(const double Lat, const double Long, - double &SwissNorthing, double &SwissEasting) -{ -//converts lat/long to Swiss Grid coords. Equations from "Supplementary PROJ.4 Notes- -//Swiss Oblique Mercator Projection", August 5, 1995, Release 4.3.3, by Gerald I. Evenden -//Lat and Long are in decimal degrees -//This transformation is, of course, only valid in Switzerland - //Written by Chuck Gantz- chuck.gantz@globalstar.com - double a = ellipsoid[3].EquatorialRadius; //Bessel ellipsoid - double eccSquared = ellipsoid[3].eccentricitySquared; - double ecc = sqrt(eccSquared); - - double LongOrigin = 7.43958333; //E7d26'22.500" - double LatOrigin = 46.95240556; //N46d57'8.660" - - double LatRad = Lat*deg2rad; - double LongRad = Long*deg2rad; - double LatOriginRad = LatOrigin*deg2rad; - double LongOriginRad = LongOrigin*deg2rad; - - double c = sqrt(1+((eccSquared * pow(cos(LatOriginRad), 4)) / (1-eccSquared))); - - double equivLatOrgRadPrime = asin(sin(LatOriginRad) / c); - - //eqn. 1 - double K = log(tan(FOURTHPI + equivLatOrgRadPrime/2)) - -c*(log(tan(FOURTHPI + LatOriginRad/2)) - - ecc/2 * log((1+ecc*sin(LatOriginRad)) / (1-ecc*sin(LatOriginRad)))); - - - double LongRadPrime = c*(LongRad - LongOriginRad); //eqn 2 - double w = c*(log(tan(FOURTHPI + LatRad/2)) - - ecc/2 * log((1+ecc*sin(LatRad)) / (1-ecc*sin(LatRad)))) + K; //eqn 1 - double LatRadPrime = 2 * (atan(exp(w)) - FOURTHPI); //eqn 1 - - //eqn 3 - double sinLatDoublePrime = cos(equivLatOrgRadPrime) * sin(LatRadPrime) - - sin(equivLatOrgRadPrime) * cos(LatRadPrime) * cos(LongRadPrime); - double LatRadDoublePrime = asin(sinLatDoublePrime); - - //eqn 4 - double sinLongDoublePrime = cos(LatRadPrime)*sin(LongRadPrime) / cos(LatRadDoublePrime); - double LongRadDoublePrime = asin(sinLongDoublePrime); - - double R = a*sqrt(1-eccSquared) / (1-eccSquared*sin(LatOriginRad) * sin(LatOriginRad)); - - SwissNorthing = R*log(tan(FOURTHPI + LatRadDoublePrime/2)) + 200000.0; //eqn 5 - SwissEasting = R*LongRadDoublePrime + 600000.0; //eqn 6 - -} - - -void SwissGridtoLL(const double SwissNorthing, const double SwissEasting, - double& Lat, double& Long) -{ - double a = ellipsoid[3].EquatorialRadius; //Bessel ellipsoid - double eccSquared = ellipsoid[3].eccentricitySquared; - double ecc = sqrt(eccSquared); - - double LongOrigin = 7.43958333; //E7d26'22.500" - double LatOrigin = 46.95240556; //N46d57'8.660" - - double LatOriginRad = LatOrigin*deg2rad; - double LongOriginRad = LongOrigin*deg2rad; - - double R = a*sqrt(1-eccSquared) / (1-eccSquared*sin(LatOriginRad) * sin(LatOriginRad)); - - double LatRadDoublePrime = 2*(atan(exp((SwissNorthing - 200000.0)/R)) - FOURTHPI); //eqn. 7 - double LongRadDoublePrime = (SwissEasting - 600000.0)/R; //eqn. 8 with equation corrected - - - double c = sqrt(1+((eccSquared * pow(cos(LatOriginRad), 4)) / (1-eccSquared))); - double equivLatOrgRadPrime = asin(sin(LatOriginRad) / c); - - double sinLatRadPrime = cos(equivLatOrgRadPrime)*sin(LatRadDoublePrime) - + sin(equivLatOrgRadPrime)*cos(LatRadDoublePrime)*cos(LongRadDoublePrime); - double LatRadPrime = asin(sinLatRadPrime); - - double sinLongRadPrime = cos(LatRadDoublePrime)*sin(LongRadDoublePrime)/cos(LatRadPrime); - double LongRadPrime = asin(sinLongRadPrime); - - Long = (LongRadPrime/c + LongOriginRad) * rad2deg; - - Lat = NewtonRaphson(LatRadPrime) * rad2deg; - -} - -double NewtonRaphson(const double initEstimate) -{ - double Estimate = initEstimate; - double tol = 0.00001; - double corr; - - double eccSquared = ellipsoid[3].eccentricitySquared; - double ecc = sqrt(eccSquared); - - double LatOrigin = 46.95240556; //N46d57'8.660" - double LatOriginRad = LatOrigin*deg2rad; - - double c = sqrt(1+((eccSquared * pow(cos(LatOriginRad), 4)) / (1-eccSquared))); - - double equivLatOrgRadPrime = asin(sin(LatOriginRad) / c); - - //eqn. 1 - double K = log(tan(FOURTHPI + equivLatOrgRadPrime/2)) - -c*(log(tan(FOURTHPI + LatOriginRad/2)) - - ecc/2 * log((1+ecc*sin(LatOriginRad)) / (1-ecc*sin(LatOriginRad)))); - double C = (K - log(tan(FOURTHPI + initEstimate/2)))/c; - - do - { - corr = CorrRatio(Estimate, C); - Estimate = Estimate - corr; - } - while (fabs(corr) > tol); - - return Estimate; -} - - - -double CorrRatio(double LatRad, const double C) -{ - double eccSquared = ellipsoid[3].eccentricitySquared; - double ecc = sqrt(eccSquared); - double corr = (C + log(tan(FOURTHPI + LatRad/2)) - - ecc/2 * log((1+ecc*sin(LatRad)) / (1-ecc*sin(LatRad)))) * (((1-eccSquared*sin(LatRad)*sin(LatRad)) * cos(LatRad)) / (1-eccSquared)); - - return corr; -} diff --git a/utm/UTMConversions.cpp b/utm/UTMConversions.cpp deleted file mode 100644 index 34e5ae9..0000000 --- a/utm/UTMConversions.cpp +++ /dev/null @@ -1,39 +0,0 @@ -//UTM Conversion.cpp- test program for lat/long to UTM and UTM to lat/long conversions -#include -#include -#include "LatLong-UTMconversion.h" - - -void main() -{ - double Lat = 47.37816667; - double Long = 8.23250000; - double UTMNorthing; - double UTMEasting; - double SwissNorthing; - double SwissEasting; - char UTMZone[4]; - int RefEllipsoid = 23;//WGS-84. See list with file "LatLong- UTM conversion.cpp" for id numbers - - cout << "Starting position(Lat, Long): " << Lat << " " << Long < #include +#include +#include +#include + + +#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 "LatLong-UTMconversion.h" static void usage(); -void main (int argc, char *argv[]) +int main (int argc, char *argv[]) { double easting; double northing; double lat, lon; - char zone[100]; - int znum; + char szone[100]; + long lzone; char *zlet; + char hemi; + long err; + char message[300]; - if (argc != 4) usage(); - strcpy (zone, argv[1]); - znum = strtoul(zone, &zlet, 10); + if (argc == 4) { - if (znum < 1 || znum > 60) { - fprintf (stderr, "Zone number is out of range.\n\n"); +// 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(); } - //printf ("zlet = %c 0x%02x\n", *zlet, *zlet); - if (*zlet != '\0' && strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) == NULL) { - fprintf (stderr, "Latitudinal band must be one of CDEFGHJKLMNPQRSTUVWX.\n\n"); - usage(); - } - - easting = atof(argv[2]); - if (easting < 0 || easting > 999999) { - fprintf (stderr, "Easting value is out of range.\n\n"); - usage(); - } - - northing = atof(argv[3]); - if (northing < 0 || northing > 9999999) { - fprintf (stderr, "Northing value is out of range.\n\n"); - usage(); - } - - UTMtoLL (WSG84, northing, easting, zone, &lat, &lon); - - printf ("latitude = %f, longitude = %f\n", lat, lon); + exit (0); } @@ -65,8 +131,15 @@ static void usage (void) fprintf (stderr, "\teasting is x coordinate in meters\n"); fprintf (stderr, "\tnorthing is y coordinate in meters\n"); fprintf (stderr, "\n"); - fprintf (stderr, "Example:\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 fd7e8c8..51cf3f5 100644 --- a/version.h +++ b/version.h @@ -1,7 +1,8 @@ -/* Dire Wolf version 1.1 */ +/* Dire Wolf version 1.2 */ #define APP_TOCALL "APDW" #define MAJOR_VERSION 1 -#define MINOR_VERSION 1 +#define MINOR_VERSION 2 +#define EXTRA_VERSION "Beta Test" diff --git a/xid.c b/xid.c new file mode 100644 index 0000000..7580c94 --- /dev/null +++ b/xid.c @@ -0,0 +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 . + + + +/*------------------------------------------------------------------ + * + * Module: xid.c + * + * Purpose: .... + * + * Description: + * + * References: ... + * + * + * + *---------------------------------------------------------------*/ + + +#include +#include +#include + +#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; jfull_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), ¶m); + + 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 (¶m, 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 (¶m, info); + n = xid_parse (info, n, ¶m2); + + 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 (¶m, info); + n = xid_parse (info, n, ¶m2); + + 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 6822b62..e94c5da 100644 --- a/xmit.c +++ b/xmit.c @@ -1,7 +1,8 @@ + // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011,2013,2014 John Langner, WB2OSZ +// 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 @@ -56,13 +57,14 @@ #include #include #include +#include //#include -#include +//#include -#if __WIN32__ -#include -#endif +//#if __WIN32__ +//#include +//#endif #include "direwolf.h" #include "ax25_pad.h" @@ -73,10 +75,9 @@ #include "hdlc_send.h" #include "hdlc_rec.h" #include "ptt.h" +#include "dtime_now.h" -static int xmit_num_channels; /* Number of radio channels. */ - /* * Parameters for transmission. @@ -87,9 +88,6 @@ static int xmit_num_channels; /* Number of radio channels. */ */ - - - 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 */ @@ -125,7 +123,20 @@ static unsigned __stdcall xmit_thread (void *arg); 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); /*------------------------------------------------------------------- @@ -142,30 +153,38 @@ static int wait_for_clear_channel (int channel, int nowait, int slotttime, int p * 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 + * 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; + HANDLE xmit_th[MAX_CHANS]; #else //pthread_attr_t attr; //struct sched_param sp; - pthread_t xmit_tid; + pthread_t xmit_tid[MAX_CHANS]; #endif - int e; + //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; /* @@ -184,75 +203,86 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) /* * Save parameters for later use. + * TODO1.2: Any reason to use global config rather than making a copy? */ - xmit_num_channels = p_modem->num_channels; - assert (xmit_num_channels >= 1 && xmit_num_channels <= MAX_CHANS); for (j=0; jbaud[j]; - xmit_slottime[j] = p_modem->slottime[j]; - xmit_persist[j] = p_modem->persist[j]; - xmit_txdelay[j] = p_modem->txdelay[j]; - xmit_txtail[j] = p_modem->txtail[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 (xmit_num_channels); + 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 thread \n"); + 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. -#if __WIN32__ - xmit_th = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, NULL, 0, NULL); - if (xmit_th == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not create xmit thread\n"); - return; - } -#else + for (j=0; jachan[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"); - } + 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", + 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--; + sp.sched_priority--; - e = pthread_attr_setschedparam (&attr, &sp); - if (e != 0) { - text_color_set(DW_COLOR_ERROR); - perror("pthread_attr_setschedparam"); - } + e = pthread_attr_setschedparam (&attr, &sp); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("pthread_attr_setschedparam"); + } - e = pthread_create (&xmit_tid, &attr, xmit_thread, (void *)0); - pthread_attr_destroy (&attr); + e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(long)j); + pthread_attr_destroy (&attr); #else - e = pthread_create (&xmit_tid, NULL, xmit_thread, (void *)0); + 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"); - return; + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Could not create xmit thread for audio device"); + return; + } +#endif + } } -#endif #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -324,16 +354,13 @@ void xmit_set_txtail (int channel, int value) * * Name: xmit_thread * - * Purpose: Initialize the transmit process. + * Purpose: Process transmit queue for one channel. * - * Inputs: None. + * Inputs: transmit packet queue. * * 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 + * Description: We have different timing rules for different types of * packets so they are put into different queues. * * High Priority - @@ -354,7 +381,138 @@ void xmit_set_txtail (int channel, int value) * pair of transmit queues is used for each channel. * * - * Thought for future research: + * + * 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= 1 && flen <= sizeof(fbuf)); - num_bits += hdlc_send_frame (c, fbuf, flen); - numframe = 1; - ax25_delete (pp); + 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) { + while (numframe < maxframe && tq_count (c,p) > 0) { - pp = tq_remove (c, 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); + 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"); + 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"); - } + if (g_debug_xmit_packet) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("------\n"); + ax25_hex_dump (pp); + dw_printf ("------\n"); + } /* * Transmit the frame. */ - flen = ax25_pack (pp, fbuf); - assert (flen >= 1 && flen <= sizeof(fbuf)); - num_bits += hdlc_send_frame (c, fbuf, flen); - numframe++; - ax25_delete (pp); - } - -/* - * Generous TXTAIL because we don't know exactly when the sound is done. - */ - - post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8; - num_bits += hdlc_send_flags (c, post_flags, 1); - - -/* - * We don't know when the sound has actually been produced. - * hldc_send finishes before anything starts coming out of the speaker. - * It's all queued up somewhere. - * - * Calculate duration of entire frame in milliseconds. - * - * Subtract out elapsed time already since PTT was turned to determine - * how much longer to wait til we turn PTT off. - */ - duration = BITS_TO_MS(num_bits, c); - time_now = dtime_now(); - already = (int) ((time_now - time_ptt) * 1000.); - wait_more = duration - already; - + 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: maxframe = %d, numframe = %d\n", maxframe, numframe); + 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 -/* - * Wait for all audio to be out before continuing. - * Provide a hint at delay required in case we don't have a - * way to ask the hardware when all the sound has been pushed out. - */ -// TODO: We have an issue if this is negative. That means -// we couldn't generate the data fast enough for the sound -// system output and there probably gaps in the signal. - audio_wait(wait_more); +/* + * 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 (c, 0); - } - else { -/* - * Timeout waiting for clear channel. - * Discard the packet. - * Display with ERROR color rather than XMIT color. - */ + ptt_set (OCTYPE_PTT, c, 0); + ax25_delete (pp); - text_color_set(DW_COLOR_ERROR); - dw_printf ("Waited too long for clear channel. Discarding packet below.\n"); +} /* end xmit_speech */ - ax25_format_addrs (pp, stemp); - info_len = ax25_get_info (pp, &pinfo); +/* Broken out into separate function so configuration can validate it. */ +/* Returns 0 for success. */ - text_color_set(DW_COLOR_INFO); - dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); +int xmit_speak_it (char *script, int c, char *orig_msg) +{ + int err; + char cmd[2000]; + char *p; + char msg[2000]; - dw_printf ("%s", stemp); /* stations followed by : */ - ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); - dw_printf ("\n"); - ax25_delete (pp); +/* Remove any quotes because it will mess up command line argument parsing. */ - } - } /* for each channel */ - } /* for high priority then low priority */ - } + strcpy (msg, orig_msg); + + for (p=msg; *p!='\0'; p++) { + if (*p == '"') *p = ' '; } -} /* end xmit_thread */ +#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); +} /*------------------------------------------------------------------- @@ -611,6 +868,8 @@ static void * xmit_thread (void *arg) * 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 @@ -628,6 +887,8 @@ static void * xmit_thread (void *arg) * * Description: * + * New in version 1.2: also obtain a lock on audio out device. + * * Transmit delay algorithm: * * Wait for channel to be clear. @@ -656,6 +917,8 @@ static void * xmit_thread (void *arg) *--------------------------------------------------------------------*/ /* 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 @@ -665,7 +928,11 @@ static int wait_for_clear_channel (int channel, int nowait, int slottime, int pe int r; int n; + n = 0; + +start_over_again: + while (hdlc_rec_data_detect_any(channel)) { SLEEP_MS(WAIT_CHECK_EVERY_MS); n++; @@ -674,85 +941,53 @@ static int wait_for_clear_channel (int channel, int nowait, int slottime, int pe } } - if (nowait) { - return 1; +//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); } - while (1) { + if (hdlc_rec_data_detect_any(channel)) { + goto start_over_again; + } - SLEEP_MS (slottime * 10); + if ( ! nowait) { - if (hdlc_rec_data_detect_any(channel)) { - continue; + while (1) { + + SLEEP_MS (slottime * 10); + + if (hdlc_rec_data_detect_any(channel)) { + goto start_over_again; + } + + r = rand() & 0xff; + if (r <= persist) { + break; + } } - - r = rand() & 0xff; - if (r <= persist) { - return 1; - } } +// 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 */ - - -/* 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 time. */ - - - -double dtime_now (void) -{ -#if __WIN32__ - /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ - - FILETIME ft; - - GetSystemTimeAsFileTime (&ft); - - return ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + - (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); -#else - /* tv_sec is seconds from Jan 1, 1970. */ - - struct timespec ts; - int sec, ns; - double x1, x2; - double result; - - clock_gettime (CLOCK_REALTIME, &ts); - - sec = (int)(ts.tv_sec); - ns = (int)(ts.tv_nsec); - x1 = (double)(sec); - x2 = (double)(ns/1000000) *.001; - result = x1 + x2; - - /* Sometimes this returns NAN. How could that possibly happen? */ - /* This is REALLY BIZARRE! */ - /* Multiplying a number by a billionth often produces NAN. */ - /* Adding a fraction to a number over a billion often produces NAN. */ - - /* Turned out to be a hardware problem with one specific computer. */ - - if (isnan(result)) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\ndtime_now(): %d, %d -> %.3f + %.3f -> NAN!!!\n\n", sec, ns, x1, x2); - } - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("dtime_now() returns %.3f\n", result); -#endif - - return (result); -#endif -} - - /* end xmit.c */ diff --git a/xmit.h b/xmit.h index a237b32..b444f1e 100644 --- a/xmit.h +++ b/xmit.h @@ -16,7 +16,8 @@ extern void xmit_set_slottime (int channel, int value); extern void xmit_set_txtail (int channel, int value); -extern double dtime_now (void); + +extern int xmit_speak_it (char *script, int c, char *msg); #endif