diff --git a/.gitattributes b/.gitattributes index d643da3..5d99a3d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,7 +22,8 @@ *.cpp text *.h text *.pl text -Makefile* text +*.py text +*.sh text *.txt text *.desktop text *.conf text @@ -30,3 +31,5 @@ Makefile* text *.ico binary *.png binary + +Makefile* text \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 1546a40..8b714e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,41 @@ ---------- +## Version 1.3 -- Development snapshot F -- September 2015 ## + +### New Feature: ### + +- Command line option "-a n" to print audio device statistics each n seconds. Previously this was always each 100 seconds on Linux and not available on Windows. + +### Bug Fixed: ### + +- Crashed when receiving packet with comment of a few hundred characters. + +---------- + +## Version 1.3 -- Development snapshot E -- August 2015 ## + +### Bug Fixed: ### + +- Crashed when receiving packet with unexpected form of GPS NMEA sentence. + +---------- + +## Version 1.3 -- Development snapshot D -- July 2015 ## + +### New Features: ### + +- Enhancements to APRStt gateway including Morse code and speech responses to to APRStt tone sequences. + +- Preliminary support for Mac OS X. NEEDS MORE TESTING! + +- A list of all symbols available can be obtained with the -S +command line option. + +- APRS Telemetry Toolkit (incomplete). + +---------- + ## Version 1.2 -- June 2015 ## ### New Features ### diff --git a/Makefile b/Makefile index 6800756..0ae5394 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,16 @@ # Select proper Makefile for operating system. # The Windows version is built with the help of Cygwin. +# In my case, I see CYGWIN_NT-6.1-WOW so we don't check for +# equal to some value. Your mileage my vary. + win := $(shell uname | grep CYGWIN) +dar := $(shell uname | grep Darwin) + ifneq ($(win),) -include Makefile.win + include Makefile.win +else ifeq ($(dar),Darwin) + include Makefile.macosx else -include Makefile.linux + include Makefile.linux endif diff --git a/Makefile.linux b/Makefile.linux index 0a3537e..ff5cfe9 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -2,7 +2,7 @@ # Makefile for Linux version of Dire Wolf. # -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.desktop +all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.desktop direwolf.conf @echo " " @echo "Next step - install with:" @echo " " @@ -216,10 +216,10 @@ direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hd hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \ fcs_calc.o ax25_pad.o \ decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ - gen_tone.o audio.o digipeater.o pfilter.o dedupe.o tq.o xmit.o \ + gen_tone.o audio.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \ ptt.o beacon.o dwgps.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \ - dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o log.o telemetry.o dtime_now.o \ - geotranz.a + dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o serial_port.o log.o telemetry.o dtime_now.o \ + misc.a geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lpthread -lrt $(LDLIBS) -lm @@ -263,6 +263,25 @@ utm.o : geotranz/utm.c $(CC) $(CFLAGS) -c -o $@ $^ +# Provide our own copy of strlcpy and strlcat because they are not included with Linux. +# We don't need the others in that same directory. + +misc.a : strlcpy.o strlcat.o + ar -cr $@ $^ + +strlcpy.o : misc/strlcpy.c + $(CC) $(CFLAGS) -I. -c -o $@ $^ + +strlcat.o : misc/strlcat.c + $(CC) $(CFLAGS) -I. -c -o $@ $^ + + + +# Generate apprpriate sample configuration file for this platform. + +direwolf.conf : generic.conf + egrep '^C|^L' generic.conf | cut -c2-999 > direwolf.conf + # Where should we install it? @@ -282,7 +301,7 @@ INSTALLDIR := /usr/local # It was hardcoded with lxterminal, /home/pi, and so on. # In version 1.2, try to customize this to match other situations better. -# TODO1.2: Test this better. +# TODO: Test this better. direwolf.desktop : @@ -310,7 +329,7 @@ endif # TODO: Review file locations. install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \ - tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop + 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 @@ -323,20 +342,21 @@ install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx ge install atest $(INSTALLDIR)/bin install ttcalc $(INSTALLDIR)/bin install dwespeak.sh $(INSTALLDIR)/bin + install telemetry-toolkit/*.p[ly] $(INSTALLDIR)/bin install -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt install -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt install -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt install -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png install -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop - install -D --mode=644 CHANGES.txt $(INSTALLDIR)/share/doc/direwolf/CHANGES.txt + install -D --mode=644 README.md $(INSTALLDIR)/share/doc/direwolf/README.md + install -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md install -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt install -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt - install -D --mode=644 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 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf + install -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf + install -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf + install -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf + install -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.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 @@ -348,15 +368,15 @@ install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx ge install -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1 install -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1 @echo " " - @echo "If this is your first install, not an upgrade, type this" - @echo "to put a copy of the sample configuration file in your home directory:" + @echo "If this is your first install, not an upgrade, type this to put a copy" + @echo "of the sample configuration file (direwolf.conf) in your home directory:" @echo " " @echo " make install-conf" @echo " " # TODO: Should we put the sample direwolf.conf file somewhere like -# /usr/share/doc/direwolf/examples or /etc/ax25 and add that to the +# /usr/share/doc/direwolf/examples and add that to the # end of the search path list? # That would make it easy to see user customizations compared to the # latest sample. @@ -371,6 +391,8 @@ install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx ge .PHONY: install-conf install-conf : direwolf.conf cp direwolf.conf ~ + cp telemetry-toolkit/telem-m0xer-3.txt ~ + cp telemetry-toolkit/telem-*.conf ~ ifneq ($(wildcard $(HOME)/Desktop),) @echo " " @echo "This will add a desktop icon on some systems:" @@ -389,7 +411,7 @@ install-rpi : dw-start.sh # Separate application to decode raw data. -decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.c telemetry.o +decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.c telemetry.o tt_text.o misc.a $(CC) $(CFLAGS) -o decode_aprs -DTEST $^ -lm @@ -405,22 +427,22 @@ tt2text : tt_text.c # Convert between Latitude/Longitude and UTM coordinates. -ll2utm : ll2utm.c geotranz.a +ll2utm : ll2utm.c geotranz.a textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lm -utm2ll : utm2ll.c geotranz.a +utm2ll : utm2ll.c geotranz.a textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lm # Convert from log file to GPX. -log2gpx : log2gpx.c +log2gpx : log2gpx.c textcolor.o misc.a $(CC) $(CFLAGS) -o $@ $^ -lm # Test application to generate sound. -gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c textcolor.c dsp.c +gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c misc.a $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm demod.o : tune.h @@ -428,7 +450,7 @@ demod_afsk.o : tune.h demod_9600.o : tune.h testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tune.h textcolor.c + fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tune.h textcolor.c misc.a $(CC) $(CFLAGS) -o atest $^ -lm ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out @@ -437,34 +459,36 @@ 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 + fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tt_text.c textcolor.c \ + misc.a $(CC) $(CFLAGS) -o $@ $^ -lm -lrt # Unit test for inner digipeater algorithm -dtest : digipeater.c pfilter.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 misc.a $(CC) $(CFLAGS) -DTEST -o $@ $^ ./dtest # Unit test for APRStt. -ttest : aprs_tt.c tt_text.c latlong.c misc.a geotranz.a +ttest : aprs_tt.c tt_text.c latlong.c textcolor.o misc.a geotranz.a misc.a $(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ # Unit test for IGate -itest : igate.c textcolor.c ax25_pad.c fcs_calc.c +itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a $(CC) $(CFLAGS) -DITEST -o $@ $^ ./itest # Unit test for UDP reception with AFSK demodulator -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 +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 misc.a $(CC) $(CFLAGS) -o $@ $^ -lm -lrt ./udptest @@ -472,20 +496,20 @@ udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec # Unit test for telemetry decoding. -etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a +etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.o misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ -lm -lrt ./etest # Multiple AGWPE network or serial port clients to test TNCs side by side. -aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c +aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a $(CC) $(CFLAGS) -g -o $@ $^ # Touch Tone to Speech sample application. -ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o +ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a $(CC) $(CFLAGS) -g -o $@ $^ @@ -503,49 +527,35 @@ clean : # Package it up for distribution. .PHONY: dist-src -dist-src : CHANGES.txt User-Guide.pdf Raspberry-Pi-APRS.pdf \ - Raspberry-Pi-APRS-Tracker.pdf APRStt-Implementation-Notes.pdf \ - A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \ +dist-src : README.md CHANGES.md + doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \ + doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \ dw-start.sh dwespeak.bat dwespeak.sh \ tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec rm -f fsk_fast_filter.h echo " " > tune.h rm -f ../$z-src.zip - egrep '^C|^L' direwolf.txt | cut -c2-999 > direwolf.conf (cd .. ; zip $z-src.zip \ - $z/CHANGES.txt \ + $z/README.md \ + $z/CHANGES.md \ $z/LICENSE* \ - $z/User-Guide.pdf \ - $z/Raspberry-Pi-APRS.pdf \ - $z/Raspberry-Pi-APRS-Tracker.pdf \ - $z/APRStt-Implementation-Notes.pdf \ - $z/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \ - $z/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \ + $z/doc/User-Guide.pdf \ + $z/doc/Raspberry-Pi-APRS.pdf \ + $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ + $z/doc/APRStt-Implementation-Notes.pdf \ + $z/doc/APRS-Telemetry-Toolkit.pdf \ $z/Makefile* \ $z/*.c $z/*.h \ $z/regex/* $z/misc/* $z/geotranz/* \ $z/man1/* \ - $z/direwolf.conf $z/direwolf.txt \ + $z/generic.conf \ $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ $z/dw-start.sh $z/direwolf.spec \ - $z/dwespeak.bat $z/dwespeak.sh ) + $z/dwespeak.bat $z/dwespeak.sh \ + $z/telemetry-toolkit/* ) - -#User-Guide.pdf : User-Guide.docx -# echo "***** User-Guide.pdf is out of date *****" - -#Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx -# echo "***** Raspberry-Pi-APRS.pdf is out of date *****" - -#Raspberry-Pi-APRS-Tracker.pdf : Raspberry-Pi-APRS-Tracker.docx -# echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****" - -#A-Better-APRS-Packet-Demodulator.pdf : A-Better-APRS-Packet-Demodulator.docx -# echo "***** A-Better-APRS-Packet-Demodulator.pdf is out of date *****" - - # # The locations below appear to be the most recent. # The copy at http://www.aprs.org/tocalls.txt is out of date. diff --git a/Makefile.macosx b/Makefile.macosx new file mode 100644 index 0000000..f0770f1 --- /dev/null +++ b/Makefile.macosx @@ -0,0 +1,516 @@ +# +# Makefile for Macintosh 10.8+ version of Dire Wolf. +# + +all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.conf + @echo " " + @echo "Next step install with: " + @echo " " + @echo " sudo make install" + @echo " " + @echo "System SDK's (10.8 - 10.10) must be located here to make use of them. " + @echo " /Developer/SDKs " + @echo " " + +SYS_LIBS := +SYS_MIN := +SDK := $(shell find /Developer -maxdepth 1 -type d -name "SDKs") +#$(info $$SDK = ${SDK}) +ifeq (${SDK},/Developer/SDKs) + SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.8.sdk") + ifeq (${SDK},/Developer/SDKs/MacOSX10.8.sdk) + SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.8.sdk + SYS_MIN := -mmacosx-version-min=10.8 + else + SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.9.sdk") + ifeq (${SDK},/Developer/SDKs/MacOSX10.9.sdk) + SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.9.sdk + SYS_MIN := -mmacosx-version-min=10.9 + else + SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.10.sdk") + ifeq (${SDK},/Developer/SDKs/MacOSX10.10.sdk) + SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.10.sdk + SYS_MIN := -mmacosx-version-min=10.10 + endif + endif + endif +endif + +EXTRA_CFLAGS := +DARWIN_CC := $(shell which clang) +ifeq (${DARWIN_CC},) +DARWIN_CC := $(shell which gcc) +EXTRA_CFLAGS := +else +EXTRA_CFLAGS := -fvectorize -fslp-vectorize -fslp-vectorize-aggressive -pthread +endif + +# Change as required in support of the available libraries + +#CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN) +CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN) +CFLAGS := -Os -pthread -Igeotranz $(EXTRA_CFLAGS) +# $(info $$CC is [${CC}]) + +# +# The DSP filters spend a lot of time spinning around in little +# loops multiplying and adding arrays of numbers. The Intel "SSE" +# instructions, introduced in 1999 with the Pentium III series, +# can speed this up considerably. +# +# SSE2 instructions, added in 2000, don't seem to offer any advantage. +# +# +# Let's take a look at the effect of the compile options. +# +# +# Times are elapsed time to process Track 2 of the TNC test CD. +# +# i.e. "./atest 02_Track_2.wav" +# Default demodulator type is new "E" added for version 1.2. +# + +# +# ---------- x86 (32 bit) ---------- +# + +# +# gcc 4.6.3 running on Ubuntu 12.04.05. +# Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions. +# Probably from around 2004 or 2005. +# +# When gcc is generating code for a 32 bit x86 target, it assumes the ancient +# i386 processor. This is good for portability but bad for performance. +# +# The code can run considerably faster by taking advantage of the SSE instructions +# available in the Pentium 3 or later. +# +# seconds options comments +# ------ ------- -------- +# 524 +# 183 -O2 +# 182 -O3 +# 183 -O3 -ffast-math (should be same as -Ofast) +# 184 -Ofast +# 189 -O3 -ffast-math -march=pentium +# 122 -O3 -ffast-math -msse +# 122 -O3 -ffast-math -march=pentium -msse +# 121 -O3 -ffast-math -march=pentium3 (this implies -msse) +# 120 -O3 -ffast-math -march=native +# +# Note that "-march=native" is essentially the same as "-march=pentium3." +# + +# If the compiler is generating code for the i386 target, we can +# get much better results by telling it we have at least a Pentium 3. + +CFLAGS += -march=core2 -msse4.1 -std=gnu99 +#CFLAGS += -march=pentium3 -sse + +# +# gcc 4.8.2 running on Ubuntu 14.04.1. +# Intel Core 2 Duo from around 2007 or 2008. +# +# 64 bit target implies that we have SSE and probably even better vector instructions. +# +# seconds options comments +# ------ ------- -------- +# 245 +# 75 -01 +# 72 -02 +# 71 -03 +# 73 -O3 -march=native +# 42 -O3 -ffast-math +# 42 -Ofast (note below) +# 40 -O3 -ffast-math -march=native +# +# +# Note that "-Ofast" is a newer option roughly equivalent to "-O3 -ffast-math". +# I use the longer form because it is compatible with older compilers. +# +# Why don't I don't have "-march=native?" +# Older compilers don't recognize "native" as one of the valid options. +# One article said it was added with gcc 4.2 but I haven't verified that. +# + +# Add -ffastmath in only if compiler version recognizes it. + +useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) +ifneq ($(useffast),) +CFLAGS += -ffast-math +endif + +# +# You would expect "-march=native" to produce the fastest code. +# Why don't I use it here? +# +# 1. In my benchmarks, above, it has a negligible impact if any at all. +# 2. Some older versions of gcc don't recognize "native" as a valid choice. +# 3. Results are less portable. Not a consideration if you are +# building only for your own use but very important for anyone +# redistributing a "binary" version. +# +# If you are planning to distribute the binary version to other +# people (in some ham radio software collection, RPM, or DEB package), +# avoid # fine tuning it for your particular computer. It could +# cause compatibility issues for those with older computers. +# + +#CFLAGS += -D_FORTIFY_SOURCE + +# Use PortAudio Library + +# Force static linking of portaudio if the static library is available. +PA_LIB_STATIC := $(shell find /opt/local/lib -maxdepth 1 -type f -name "libportaudio.a") +#$(info $$PA_LIB_STATIC is [${PA_LIB_STATIC}]) +ifeq (${PA_LIB_STATIC},) +LDLIBS += -L/opt/local/lib -lportaudio +else +LDLIBS += /opt/local/lib/libportaudio.a +endif + +# Include libraries portaudio requires. +LDLIBS += -framework CoreAudio -framework AudioUnit -framework AudioToolbox +LDLIBS += -framework Foundation -framework CoreServices + +CFLAGS += -DUSE_PORTAUDIO -I/opt/local/include + +# Uncomment following lines to enable GPS interface & tracker function. +# Not available for MacOSX. +# Although MacPorts has gpsd, wonder if it's the same thing. + +#CFLAGS += -DENABLE_GPS +#LDLIBS += -lgps + +# Name of current directory. +# Used to generate zip file name for distribution. + +z := $(notdir ${CURDIR}) + + +# Main application. + +direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \ + config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o \ + demod.o digipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \ + encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \ + geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \ + kiss.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \ + nmea.o serial_port.o pfilter.o ptt.o rdq.o recv.o redecode.o rrbb.o server.o \ + symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xmit.o + $(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm + + +# Optimization for slow processors. + +demod.o : fsk_fast_filter.h + +demod_afsk.o : fsk_fast_filter.h + + +fsk_fast_filter.h : demod_afsk.c + $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm + ./gen_fff > fsk_fast_filter.h + + + +# UTM, USNG, MGRS conversions. + +geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o + ar -cr $@ $^ + +error_string.o : geotranz/error_string.c + $(CC) $(CFLAGS) -c -o $@ $^ + +mgrs.o : geotranz/mgrs.c + $(CC) $(CFLAGS) -c -o $@ $^ + +polarst.o : geotranz/polarst.c + $(CC) $(CFLAGS) -c -o $@ $^ + +tranmerc.o : geotranz/tranmerc.c + $(CC) $(CFLAGS) -c -o $@ $^ + +ups.o : geotranz/ups.c + $(CC) $(CFLAGS) -c -o $@ $^ + +usng.o : geotranz/usng.c + $(CC) $(CFLAGS) -c -o $@ $^ + +utm.o : geotranz/utm.c + $(CC) $(CFLAGS) -c -o $@ $^ + + + +# Generate apprpriate sample configuration file for this platform. + +direwolf.conf : generic.conf + egrep '^C|^M' generic.conf | cut -c2-999 > direwolf.conf + + +# Where should we install it? + +# My understanding, of the convention, is that something you compile +# from source, that is not a standard part of the operating system, +# should go in /usr/local/bin. + +# This is a step in the right direction but not sufficient to use /usr instead. + +INSTALLDIR := /usr/local + +# TODO: Test this better. + +# Optional installation into /usr/local/... +# Needs to be run as root or with sudo. +# TODO: Review file locations. + +install : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \ + tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png + ginstall direwolf $(INSTALLDIR)/bin + ginstall decode_aprs $(INSTALLDIR)/bin + ginstall text2tt $(INSTALLDIR)/bin + ginstall tt2text $(INSTALLDIR)/bin + ginstall ll2utm $(INSTALLDIR)/bin + ginstall utm2ll $(INSTALLDIR)/bin + ginstall aclients $(INSTALLDIR)/bin + ginstall log2gpx $(INSTALLDIR)/bin + ginstall gen_packets $(INSTALLDIR)/bin + ginstall atest $(INSTALLDIR)/bin + ginstall ttcalc $(INSTALLDIR)/bin + ginstall dwespeak.sh $(INSTALLDIR)/bin + ginstall telemetry-toolkit/*.p[ly] $(INSTALLDIR)/bin + ginstall -D --mode=644 tocalls.txt $(INSTALLDIR)/share/direwolf/tocalls.txt + ginstall -D --mode=644 symbols-new.txt $(INSTALLDIR)/share/direwolf/symbols-new.txt + ginstall -D --mode=644 symbolsX.txt $(INSTALLDIR)/share/direwolf/symbolsX.txt + ginstall -D --mode=644 dw-icon.png $(INSTALLDIR)/share/direwolf/dw-icon.png + ginstall -D --mode=644 README.md $(INSTALLDIR)/share/doc/direwolf/README.md + ginstall -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md + ginstall -D --mode=644 direwolf.conf $(INSTALLDIR)/share/direwolf/config/direwolf.conf + ginstall -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt + ginstall -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt + ginstall -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf + ginstall -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf + ginstall -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf + ginstall -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf + ginstall -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf + ginstall -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1 + ginstall -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1 + ginstall -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1 + ginstall -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1 + ginstall -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1 + ginstall -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1 + ginstall -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1 + ginstall -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1 + ginstall -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1 + ginstall -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1 + @echo " " + @echo "If this is your first install, not an upgrade, type this to put a copy" + @echo "of the sample configuration file (direwolf.conf) in your home directory:" + @echo " " + @echo " make install-conf" + @echo " " + + +# TODO: Should we put the sample direwolf.conf file somewhere like +# /usr/share/doc/direwolf/examples and add that to the +# end of the search path list? +# That would make it easy to see user customizations compared to the +# latest sample. + +# These would be done as ordinary user. + +# The Raspberry Pi has ~/Desktop but Ubuntu does not. + +# TODO: Handle Linux variations correctly. + + +.PHONY: install-conf +install-conf : direwolf.conf + cp direwolf.conf ~ + cp telemetry-toolkit/telem-m0xer-3.txt ~ + cp telemetry-toolkit/telem-*.conf ~ + + +# Separate application to decode raw data. + +decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.c telemetry.o tt_text.o + $(CC) $(CFLAGS) -o decode_aprs -DTEST $^ -lm + +# Convert between text and touch tone representation. + +text2tt : tt_text.c + $(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c + +tt2text : tt_text.c + $(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c + + +# Convert between Latitude/Longitude and UTM coordinates. + +ll2utm : ll2utm.c geotranz.a + $(CC) $(CFLAGS) -o $@ $^ -lm + +utm2ll : utm2ll.c geotranz.a + $(CC) $(CFLAGS) -o $@ $^ -lm + + +# Convert from log file to GPX. + +log2gpx : log2gpx.c + $(CC) $(CFLAGS) -o $@ $^ -lm + + +# Test application to generate sound. + +gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c + $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm + +demod.o : tune.h +demod_afsk.o : tune.h +demod_9600.o : tune.h + +testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ + fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tune.h textcolor.c + $(CC) $(CFLAGS) -o atest $^ -lm + ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out + + +# Unit test for AFSK demodulator + + +atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ + fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c textcolor.c tt_text.c + $(CC) $(CFLAGS) -o $@ $^ -lm + +# Unit test for inner digipeater algorithm + + +dtest : digipeater.c pfilter.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 + + +itest : igate.c textcolor.c ax25_pad.c fcs_calc.c + $(CC) $(CFLAGS) -DITEST -o $@ $^ + ./itest + + +# Unit test for UDP reception with AFSK demodulator + +udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c + $(CC) $(CFLAGS) -o $@ $^ -lm + ./udptest + + +# Unit test for telemetry decoding. + + +etest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a + $(CC) $(CFLAGS) -o $@ $^ -lm + ./etest + + +# Multiple AGWPE network or serial port clients to test TNCs side by side. + +aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c + $(CC) $(CFLAGS) -g -o $@ $^ + + +# Touch Tone to Speech sample application. + +ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o + $(CC) $(CFLAGS) -g -o $@ $^ + + +depend : $(wildcard *.c) + makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ + + +.PHONY: clean +clean : + rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc \ + fsk_fast_filter.h *.o *.a + echo " " > tune.h + + +.PHONY: dist-mac +dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \ + tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png + rm -f ../direwolf_dist_bin.zip + (cd .. ; zip direwolf_dist_bin.zip \ + $(INSTALLDIR)/bin/direwolf \ + $(INSTALLDIR)/bin/decode_aprs \ + $(INSTALLDIR)/bin/text2tt \ + $(INSTALLDIR)/bin/tt2text \ + $(INSTALLDIR)/bin/ll2utm \ + $(INSTALLDIR)/bin/utm2ll \ + $(INSTALLDIR)/bin/aclients \ + $(INSTALLDIR)/bin/log2gpx \ + $(INSTALLDIR)/bin/gen_packets \ + $(INSTALLDIR)/bin/atest \ + $(INSTALLDIR)/bin/ttcalc \ + $(INSTALLDIR)/bin/dwespeak.sh \ + $(INSTALLDIR)/share/direwolf/tocalls.txt \ + $(INSTALLDIR)/share/direwolf/config/direwolf.conf \ + $(INSTALLDIR)/share/direwolf/symbols-new.txt \ + $(INSTALLDIR)/share/direwolf/symbolsX.txt \ + $(INSTALLDIR)/share/direwolf/dw-icon.png \ + $(INSTALLDIR)/share/doc/direwolf/README.md \ + $(INSTALLDIR)/share/doc/direwolf/CHANGES.md \ + $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt \ + $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt \ + $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf \ + $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf \ + $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf \ + $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf \ + $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf \ + $(INSTALLDIR)/man/man1/aclients.1 \ + $(INSTALLDIR)/man/man1/atest.1 \ + $(INSTALLDIR)/man/man1/decode_aprs.1 \ + $(INSTALLDIR)/man/man1/direwolf.1 \ + $(INSTALLDIR)/man/man1/gen_packets.1 \ + $(INSTALLDIR)/man/man1/ll2utm.1 \ + $(INSTALLDIR)/man/man1/log2gpx.1 \ + $(INSTALLDIR)/man/man1/text2tt.1 \ + $(INSTALLDIR)/man/man1/tt2text.1 \ + $(INSTALLDIR)/man/man1/utm2ll.1 \ + ) + +# Package it up for distribution. + +.PHONY: dist-src +dist-src : README.md CHANGES.md \ + doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \ + doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \ + dw-start.sh dwespeak.bat dwespeak.sh \ + tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec + rm -f fsk_fast_filter.h + echo " " > tune.h + rm -f ../$z-src.zip + (cd .. ; zip $z-src.zip \ + $z/README.md \ + $z/CHANGES.md \ + $z/LICENSE* \ + $z/doc/User-Guide.pdf \ + $z/doc/Raspberry-Pi-APRS.pdf \ + $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ + $z/doc/APRStt-Implementation-Notes.pdf \ + $z/Makefile* \ + $z/*.c $z/*.h \ + $z/regex/* $z/misc/* $z/geotranz/* \ + $z/man1/* \ + $z/generic.conf \ + $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ + $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ + $z/dw-start.sh $z/direwolf.spec \ + $z/dwespeak.bat $z/dwespeak.sh \ + $z/telemetry-toolkit/* ) diff --git a/Makefile.win b/Makefile.win index 77ba9f1..b59498c 100644 --- a/Makefile.win +++ b/Makefile.win @@ -26,7 +26,7 @@ CC := gcc CFLAGS := -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC AR := ar -#CFLAGS += -g +CFLAGS += -g # # Let's see impact of various optimization levels. @@ -73,9 +73,9 @@ direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hd hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \ fcs_calc.o ax25_pad.o \ decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ - gen_tone.o audio_win.o digipeater.o pfilter.o dedupe.o tq.o xmit.o \ + gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o \ ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \ - dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o log.o telemetry.o dtime_now.o \ + dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o serial_port.o log.o telemetry.o dtime_now.o \ dw-icon.o regex.a misc.a geotranz.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 @@ -139,7 +139,7 @@ regex.o : regex/regex.c # There are also a couple other functions in the misc # subdirectory that are missing on Windows. -misc.a : strsep.o strtok_r.o strcasestr.o +misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o ar -cr $@ $^ strsep.o : misc/strsep.c @@ -151,11 +151,16 @@ strtok_r.o : misc/strtok_r.c strcasestr.o : misc/strcasestr.c $(CC) $(CFLAGS) -c -o $@ $^ +strlcpy.o : misc/strlcpy.c + $(CC) $(CFLAGS) -I. -c -o $@ $^ + +strlcat.o : misc/strlcat.c + $(CC) $(CFLAGS) -I. -c -o $@ $^ # Separate application to decode raw data. -decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.o telemetry.o regex.a misc.a geotranz.a +decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c latlong.c log.o telemetry.o tt_text.o regex.a misc.a geotranz.a $(CC) $(CFLAGS) -o decode_aprs -DTEST $^ @@ -170,22 +175,23 @@ tt2text : tt_text.c # Convert between Latitude/Longitude and UTM coordinates. -ll2utm : ll2utm.c geotranz.a +ll2utm : ll2utm.c textcolor.c geotranz.a misc.a $(CC) $(CFLAGS) -o $@ $^ -utm2ll : utm2ll.c geotranz.a +utm2ll : utm2ll.c textcolor.c geotranz.a misc.a $(CC) $(CFLAGS) -o $@ $^ # Convert from log file to GPX. -log2gpx : log2gpx.c misc/strsep.c misc/strtok_r.c +#log2gpx : log2gpx.c misc/strsep.c misc/strtok_r.c +log2gpx : log2gpx.c misc.a $(CC) $(CFLAGS) -o $@ $^ # Test application to generate sound. -gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o textcolor.o dsp.o misc.a regex.a +gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o textcolor.o dsp.o misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ # For tweaking the demodulator. @@ -234,7 +240,7 @@ testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2. # 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 \ + rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c tt_text.c textcolor.c telemetry.c misc.a regex.a \ fsk_fast_filter.h echo " " > tune.h $(CC) $(CFLAGS) -o $@ $^ @@ -291,6 +297,16 @@ ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 +# Send GPS location to KISS TNC each second. + +walk96 : walk96.c nmea.c kiss_frame.c \ + latlong.o encode_aprs.o serial_port.o textcolor.o \ + ax25_pad.o fcs_calc.o regex.a \ + misc.a + $(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32 + + + .PHONY: depend depend : $(wildcard *.c) @@ -310,21 +326,21 @@ clean : .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 + generic.conf dwespeak.bat \ + README.md CHANGES.md \ + doc/User-Guide.pdf \ + doc/Raspberry-Pi-APRS.pdf \ + doc/APRStt-Implementation-Notes.pdf rm -f ../$z-win.zip - egrep '^C|^W' direwolf.txt | cut -c2-999 > direwolf.conf + egrep '^C|^W' generic.conf | 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 \ + zip --junk-paths ../$z-win.zip \ + README.md \ + CHANGES.md \ + doc/User-Guide.pdf \ + doc/Raspberry-Pi-APRS.pdf \ + doc/APRStt-Implementation-Notes.pdf \ + doc/APRS-Telemetry-Toolkit.pdf \ LICENSE* \ direwolf.conf \ direwolf.exe \ @@ -337,67 +353,96 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l gen_packets.exe \ atest.exe \ ttcalc.exe \ - dwespeak.bat + dwespeak.bat \ + telemetry-toolkit/* .PHONY: dist-src -dist-src : CHANGES.txt \ - User-Guide.pdf \ - Raspberry-Pi-APRS.pdf \ - Raspberry-Pi-APRS-Tracker.pdf \ - APRStt-Implementation-Notes.pdf \ - A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \ - A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \ +dist-src : README.md CHANGES.md \ + doc/User-Guide.pdf \ + doc/Raspberry-Pi-APRS.pdf \ + doc/Raspberry-Pi-APRS-Tracker.pdf \ + doc/APRStt-Implementation-Notes.pdf \ + doc/APRS-Telemetry-Toolkit.pdf \ dw-start.sh dwespeak.bat dwespeak.sh \ tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec rm -f fsk_fast_filter.h echo " " > tune.h rm -f ../$z-src.zip - egrep '^C|^L' direwolf.txt | cut -c2-999 > direwolf.conf - dos2unix direwolf.conf + dos2unix generic.conf dos2unix Makefile dos2unix Makefile.linux + dos2unix Makefile.macosx + dos2unix telemetry-toolkit/telem-balloon.conf + dos2unix telemetry-toolkit/telem-balloon.pl + dos2unix telemetry-toolkit/telem-bits.pl + dos2unix telemetry-toolkit/telem-data.pl + dos2unix telemetry-toolkit/telem-data91.pl + dos2unix telemetry-toolkit/telem-eqns.pl + dos2unix telemetry-toolkit/telem-m0xer-3.txt + dos2unix telemetry-toolkit/telem-parm.pl + dos2unix telemetry-toolkit/telem-unit.pl + dos2unix telemetry-toolkit/telem-volts.py + dos2unix telemetry-toolkit/telem-volts.conf (cd .. ; zip $z-src.zip \ - $z/CHANGES.txt \ + $z/README.md \ + $z/CHANGES.md \ $z/LICENSE* \ - $z/User-Guide.pdf \ - $z/Raspberry-Pi-APRS.pdf \ - $z/Raspberry-Pi-APRS-Tracker.pdf \ - $z/APRStt-Implementation-Notes.pdf \ - $z/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \ - $z/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \ - $z/Makefile.win $z/Makefile.linux $z/Makefile \ + $z/doc/User-Guide.pdf \ + $z/doc/Raspberry-Pi-APRS.pdf \ + $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ + $z/doc/APRStt-Implementation-Notes.pdf \ + $z/doc/APRS-Telemetry-Toolkit.pdf \ + $z/Makefile.win $z/Makefile.linux $z/Makefile.macosx $z/Makefile \ $z/*.c $z/*.h \ $z/regex/* $z/misc/* $z/geotranz/* \ $z/man1/* \ - $z/direwolf.conf $z/direwolf.txt \ + $z/generic.conf \ $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ $z/dw-start.sh $z/direwolf.spec \ - $z/dwespeak.bat $z/dwespeak.sh ) + $z/dwespeak.bat $z/dwespeak.sh \ + $z/telemetry-toolkit/* ) unix2dos Makefile unix2dos Makefile.linux - rm direwolf.conf + unix2dos Makefile.macosx + unix2dos telemetry-toolkit/telem-balloon.conf + unix2dos telemetry-toolkit/telem-balloon.pl + dos2unix telemetry-toolkit/telem-bits.pl + unix2dos telemetry-toolkit/telem-data.pl + unix2dos telemetry-toolkit/telem-data91.pl + unix2dos telemetry-toolkit/telem-eqns.pl + unix2dos telemetry-toolkit/telem-m0xer-3.txt + unix2dos telemetry-toolkit/telem-parm.pl + unix2dos telemetry-toolkit/telem-unit.pl + unix2dos telemetry-toolkit/telem-volts.py + unix2dos telemetry-toolkit/telem-volts.conf + -User-Guide.pdf : User-Guide.docx +doc/User-Guide.pdf : doc/User-Guide.docx echo "***** User-Guide.pdf is out of date *****" -Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx +doc/Raspberry-Pi-APRS.pdf : doc/Raspberry-Pi-APRS.docx echo "***** Raspberry-Pi-APRS.pdf is out of date *****" -Raspberry-Pi-APRS-Tracker.pdf : Raspberry-Pi-APRS-Tracker.docx +doc/Raspberry-Pi-APRS-Tracker.pdf : doc/Raspberry-Pi-APRS-Tracker.docx echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****" -APRStt-Implementation-Notes.pdf : APRStt-Implementation-Notes.docx +doc/APRStt-Implementation-Notes.pdf : doc/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 +doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf : doc/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 +doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf : doc/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 *****" +doc/APRS-Telemetry-Toolkit.pdf : doc/APRS-Telemetry-Toolkit.docx + echo "***** APRS-Telemetry-Toolkit.pdf is out of date *****" + + + .PHONY: backup backup : mkdir /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"` diff --git a/aclients.c b/aclients.c index 657781e..ecc9cea 100644 --- a/aclients.c +++ b/aclients.c @@ -582,6 +582,7 @@ static void * client_thread_net (void *arg) use_chan == mon_cmd.portx; memset (&alevel, 0xff, sizeof(alevel)); pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel); + assert (pp != NULL); ax25_format_addrs (pp, result); info_len = ax25_get_info (pp, (unsigned char **)(&pinfo)); pinfo[info_len] = '\0'; diff --git a/aprs_tt.c b/aprs_tt.c index 088f8c6..cf5fb4a 100644 --- a/aprs_tt.c +++ b/aprs_tt.c @@ -42,12 +42,12 @@ // What do we call the parts separated by * key? Entry? Field? -#include #include +#include #include #include #include - +#include #include #include @@ -64,6 +64,8 @@ #include "latlong.h" #include "dlq.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ +#include "tq.h" + #if __WIN32__ char *strtok_r(char *str, const char *delim, char **saveptr); @@ -144,6 +146,8 @@ 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_MHEAD, "BAxxxxxx", .mhead.prefix = "326129" }, + { TTLOC_SATSQ, "BAxxxx" }, { TTLOC_MACRO, "xxyyy", .macro.definition = "B9xx*AB166*AA2B4C5B3B0Ayyy" }, @@ -310,6 +314,7 @@ static char m_callsign[20]; /* really object name */ static char m_symtab_or_overlay; static char m_symbol_code; +static char m_loc_text[24]; static double m_longitude; static double m_latitude; static char m_comment[200]; @@ -325,6 +330,10 @@ static int m_ssid; void aprs_tt_sequence (int chan, char *msg) { int err; + char audible_response[1000]; + packet_t pp; + char script_response[1000]; + #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -341,15 +350,16 @@ void aprs_tt_sequence (int chan, char *msg) /* * The parse functions will fill these in. */ - strcpy (m_callsign, ""); + strlcpy (m_callsign, "", sizeof(m_callsign)); m_symtab_or_overlay = '\\'; m_symbol_code = 'A'; + strlcpy (m_loc_text, "", sizeof(m_loc_text)); m_longitude = G_UNKNOWN; m_latitude = G_UNKNOWN; - strcpy (m_comment, ""); - strcpy (m_freq, ""); + strlcpy (m_comment, "", sizeof(m_comment)); + strlcpy (m_freq, "", sizeof(m_freq)); m_mic_e = ' '; - strcpy (m_dao, "!T !"); /* start out unknown */ + strlcpy (m_dao, "!T !", sizeof(m_dao)); /* start out unknown */ m_ssid = 12; /* @@ -371,13 +381,46 @@ void aprs_tt_sequence (int chan, char *msg) */ #ifndef TT_MAIN - err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_latitude, m_longitude, + err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, + m_loc_text, m_latitude, m_longitude, m_freq, m_comment, m_mic_e, m_dao); #endif } - // TODO send response to user. - // err == 0 OK, others, suitable error response. + +/* + * If a command / script was supplied, run it now. + * This can do additional processing and provide a custom audible response. + */ + + strlcpy (script_response, "", sizeof(script_response)); + + if (strlen(tt_config.ttcmd) > 0) { + + dw_run_cmd (tt_config.ttcmd, 1, script_response, (int)sizeof(script_response)); + + } + +/* + * Send response to user by constructing packet with SPEECH or MORSE as destination. + * Source shouldn't matter because it doesn't get transmitted as AX.25 frame. + * Use high priority queue for consistent timing. + */ + + snprintf (audible_response, sizeof(audible_response), + "APRSTT>%s:%s", + tt_config.response[err].method, + (strlen(script_response) > 0) ? script_response : tt_config.response[err].mtext); + + pp = ax25_from_text (audible_response, 0); + + if (pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error. Couldn't make frame from \"%s\"\n", audible_response); + return; + } + + tq_append (chan, TQ_PRIO_0_HI, pp); } /* end aprs_tt_sequence */ @@ -414,42 +457,55 @@ static int parse_fields (char *msg) char stemp[MAX_MSG_LEN+1]; char *e; char *save; + int err; - strcpy (stemp, msg); + + //text_color_set(DW_COLOR_DEBUG); + //printf ("parse_fields (%s).\n", msg); + + strlcpy (stemp, msg, sizeof(stemp)); e = strtok_r (stemp, "*#", &save); while (e != NULL) { + //text_color_set(DW_COLOR_DEBUG); + //printf ("parse_fields () field = %s\n", e); + switch (*e) { case 'A': switch (e[1]) { case 'A': - parse_object_name (e); + err = parse_object_name (e); + if (err != 0) return (err); break; case 'B': - parse_symbol (e); + err = parse_symbol (e); + if (err != 0) return (err); 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); + strlcpy(m_callsign, stemp, sizeof(m_callsign)); } + // TODO1.3: else return (?) break; default: - parse_callsign (e); + err = parse_callsign (e); break; } break; case 'B': - parse_location (e); + err = parse_location (e); + if (err != 0) return (err); break; case 'C': - parse_comment (e); + err = parse_comment (e); + if (err != 0) return (err); break; case '0': @@ -462,7 +518,8 @@ static int parse_fields (char *msg) case '7': case '8': case '9': - expand_macro (e); + err = expand_macro (e); + if (err != 0) return (err); break; case '\0': @@ -473,7 +530,7 @@ static int parse_fields (char *msg) default: text_color_set(DW_COLOR_ERROR); - dw_printf ("Entry does not start with A, B, C, or digit: \"%s\"\n", msg); + dw_printf ("Field does not start with A, B, C, or digit: \"%s\"\n", msg); return (TT_ERROR_D_MSG); } @@ -481,6 +538,9 @@ static int parse_fields (char *msg) e = strtok_r (NULL, "*#", &save); } + //text_color_set(DW_COLOR_DEBUG); + //printf ("parse_fields () normal return\n"); + return (0); } /* end parse_fields */ @@ -546,7 +606,7 @@ static int expand_macro (char *e) * Substitute values in to the definition. */ - strcpy (stemp, ""); + strlcpy (stemp, "", sizeof(stemp)); for (d = tt_config.ttloc_ptr[ipat].macro.definition; *d != '\0'; d++) { @@ -557,20 +617,20 @@ static int expand_macro (char *e) switch (*d) { case 'x': - strcat (stemp, xstr); + strlcat (stemp, xstr, sizeof(stemp)); break; case 'y': - strcat (stemp, ystr); + strlcat (stemp, ystr, sizeof(stemp)); break; case 'z': - strcat (stemp, zstr); + strlcat (stemp, zstr, sizeof(stemp)); break; default: { char c1[2]; c1[0] = *d; c1[1] = '\0'; - strcat (stemp, c1); + strlcat (stemp, c1, sizeof(stemp)); } break; } @@ -675,7 +735,7 @@ static int parse_callsign (char *e) */ if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) { - strcpy (m_callsign, e+1); + strlcpy (m_callsign, e+1, sizeof(m_callsign)); return (0); } @@ -844,7 +904,7 @@ static int parse_object_name (char *e) static int parse_symbol (char *e) { int len; - char nstr[4]; + char nstr[3]; int nn; char stemp[10]; @@ -950,6 +1010,8 @@ static int parse_location (char *e) long lerr; double easting, northing; char mh[20]; + char stemp[32]; + assert (*e == 'B'); @@ -1020,12 +1082,12 @@ static int parse_location (char *e) if (strlen(xstr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing X coordinate.\n"); - strcpy (xstr, "0"); + strlcpy (xstr, "0", sizeof(xstr)); } if (strlen(ystr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing Y coordinate.\n"); - strcpy (ystr, "0"); + strlcpy (ystr, "0", sizeof(ystr)); } lat0 = tt_config.ttloc_ptr[ipat].grid.lat0; @@ -1046,13 +1108,13 @@ static int parse_location (char *e) text_color_set(DW_COLOR_ERROR); dw_printf ("Missing X coordinate.\n"); /* Avoid divide by zero later. Put in middle of range. */ - strcpy (xstr, "5"); + strlcpy (xstr, "5", sizeof(xstr)); } if (strlen(ystr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Missing Y coordinate.\n"); /* Avoid divide by zero later. Put in middle of range. */ - strcpy (ystr, "5"); + strlcpy (ystr, "5", sizeof(ystr)); } x = atof(xstr); @@ -1060,7 +1122,17 @@ 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; - + + if (isalpha(tt_config.ttloc_ptr[ipat].utm.latband)) { + snprintf (m_loc_text, sizeof(m_loc_text), "%d%c %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), tt_config.ttloc_ptr[ipat].utm.latband, easting, northing); + } + else if (tt_config.ttloc_ptr[ipat].utm.latband == '-') { + snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(- tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing); + } + else { + snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing); + } + lerr = Convert_UTM_To_Geodetic(tt_config.ttloc_ptr[ipat].utm.lzone, tt_config.ttloc_ptr[ipat].utm.hemi, easting, northing, &lat0, &lon0); @@ -1088,24 +1160,26 @@ static int parse_location (char *e) 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"); + strlcpy (xstr, "5", sizeof(xstr)); } if (strlen(ystr) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("MGRS/USNG: Missing Y (northing) coordinate.\n"); /* Should not be possible to get here. Fake it and carry on. */ - strcpy (ystr, "5"); + strlcpy (ystr, "5", sizeof(ystr)); } char loc[40]; - strcpy (loc, tt_config.ttloc_ptr[ipat].mgrs.zone); - strcat (loc, xstr); - strcat (loc, ystr); + strlcpy (loc, tt_config.ttloc_ptr[ipat].mgrs.zone, sizeof(loc)); + strlcat (loc, xstr, sizeof(loc)); + strlcat (loc, ystr, sizeof(loc)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("MGRS/USNG location debug: %s\n", loc); + strlcpy (m_loc_text, loc, sizeof(m_loc_text)); + if (tt_config.ttloc_ptr[ipat].type == TTLOC_MGRS) lerr = Convert_MGRS_To_Geodetic(loc, &lat0, &lon0); else @@ -1127,17 +1201,48 @@ static int parse_location (char *e) } break; + case TTLOC_MHEAD: + + + /* Combine prefix from configuration and digits from user. */ + + strlcpy (stemp, tt_config.ttloc_ptr[ipat].mhead.prefix, sizeof(stemp)); + strlcat (stemp, xstr, sizeof(stemp)); + + if (strlen(stemp) != 4 && strlen(stemp) != 6 && strlen(stemp) != 10 && strlen(stemp) != 12) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Expected total of 4, 6, 10, or 12 digits for the Maidenhead Locator \"%s\" + \"%s\"\n", + tt_config.ttloc_ptr[ipat].mhead.prefix, xstr); + return (TT_ERROR_INVALID_MHEAD); + } + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Case MHEAD: Convert to text \"%s\".\n", stemp); + + if (tt_mhead_to_text (stemp, 0, mh) == 0) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Case MHEAD: Resulting text \"%s\".\n", mh); + + strlcpy (m_loc_text, mh, sizeof(m_loc_text)); + + ll_from_grid_square (mh, &m_latitude, &m_longitude); + } + 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); + return (TT_ERROR_INVALID_SATSQ); } /* Convert 4 digits to usual AA99 form, then to location. */ - if (tt_gridsquare_to_text (xstr, 0, mh) == 0) { + if (tt_satsq_to_text (xstr, 0, mh) == 0) { + + strlcpy (m_loc_text, mh, sizeof(m_loc_text)); + ll_from_grid_square (mh, &m_latitude, &m_longitude); } break; @@ -1149,9 +1254,13 @@ static int parse_location (char *e) return (0); } - /* Send reject sound. */ /* Does not match any location specification. */ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Received location \"%s\" does not match any definitions.\n", e); + + /* Send reject sound. */ + return (TT_ERROR_INVALID_LOC); } /* end parse_location */ @@ -1198,11 +1307,11 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char * if (strlen(e) == len) { match = 1; - strcpy (xstr, ""); - strcpy (ystr, ""); - strcpy (zstr, ""); - strcpy (bstr, ""); - strcpy (dstr, ""); + strlcpy (xstr, "", sizeof(xstr)); + strlcpy (ystr, "", sizeof(ystr)); + strlcpy (zstr, "", sizeof(zstr)); + strlcpy (bstr, "", sizeof(bstr)); + strlcpy (dstr, "", sizeof(dstr)); for (k=0; k%s:t%s", src, dest, msg); + snprintf (raw_tt_msg, sizeof(raw_tt_msg), "%s>%s:t%s", src, dest, msg); pp = ax25_from_text (raw_tt_msg, 1); @@ -1454,6 +1565,103 @@ static void raw_tt_data_to_app (int chan, char *msg) +/*------------------------------------------------------------------ + * + * Name: dw_run_cmd + * + * Purpose: Run a command and capture the output. + * + * Inputs: cmd - The command. + * + * oneline - 0 = Keep original line separators. Caller + * must deal with operating system differences. + * 1 = Change CR, LF, TAB to space so result + * is one line of text. + * 2 = Also remove any trailing whitespace. + * + * maxresult - Amount of space available for result. + * + * Outputs: result - Output captured from running command. + * + * Returns: -1 for any sort of error. + * >0 for number of characters returned (= strlen(result)) + * + * Description: This is currently used for running a user-specified + * script to generate a custom speech response. + * + * Future: There are potential other uses so it should probably + * be relocated to a file of other misc. utilities. + * + *----------------------------------------------------------------*/ + +int dw_run_cmd (char *cmd, int oneline, char *result, int maxresult) +{ + FILE *fp; + + strlcpy (result, "", sizeof(result)); + + fp = popen (cmd, "r"); + if (fp != NULL) { + int remaining = maxresult; + char *pr = result; + int err; + + while (remaining > 2 && fgets(pr, remaining, fp) != NULL) { + pr = result + strlen(result); + remaining = maxresult - strlen(result); + } + + if ((err = pclose(fp)) != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR: Unable to run \"%s\"\n", cmd); + // On Windows, non-existent file produces "Operation not permitted" + // Maybe we should put in a test for whether file exists. + dw_printf ("%s\n", strerror(err)); + + return (-1); + } + + // take out any newline characters. + + if (oneline) { + for (pr = result; *pr != '\0'; pr++) { + if (*pr == '\r' || *pr == '\n' || *pr == '\t') { + *pr = ' '; + } + } + + if (oneline > 1) { + pr = result + strlen(result) - 1; + while (pr >= result && *pr == ' ') { + *pr = '\0'; + pr--; + } + } + } + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("%s returns \"%s\"\n", cmd, result); + + return (strlen(result)); + } + + else { + // explain_popen() would be nice but doesn't seem to be commonly available. + + // We get here only if fork or pipe fails. + // The command not existing must be caught above. + + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR: Unable to run \"%s\"\n", cmd); + dw_printf ("%s\n", strerror(errno)); + + return (-1); + } + + +} /* end dw_run_cmd */ + + /*------------------------------------------------------------------ * * Name: main diff --git a/aprs_tt.h b/aprs_tt.h index 1631069..f504569 100644 --- a/aprs_tt.h +++ b/aprs_tt.h @@ -5,6 +5,7 @@ #define APRS_TT_H 1 + /* * For holding location format specifications from config file. * Same thing is also useful for macro definitions. @@ -13,7 +14,7 @@ */ struct ttloc_s { - enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_SATSQ } type; + enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_MHEAD, TTLOC_SATSQ } type; char pattern[20]; /* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx, BAxxxx */ /* For macros, it should be all fixed digits, */ @@ -44,6 +45,7 @@ struct ttloc_s { double x_offset; double y_offset; long lzone; /* UTM zone, should be 1-60 */ + char latband; /* Latitude band if specified, otherwise space or - */ char hemi; /* UTM Hemisphere, should be 'N' or 'S'. */ } utm; @@ -51,52 +53,21 @@ struct ttloc_s { char zone[8]; /* Zone and square for USNG/MGRS */ } mgrs; + struct { + char prefix[24]; /* should be 10, 6, or 4 digits to be */ + /* prepended to the received sequence. */ + } mhead; + struct { char *definition; } macro; }; }; -/* - * Configuration options for APRStt. - */ - -#define TT_MAX_XMITS 10 - -struct tt_config_s { - - int gateway_enabled; /* Send DTMF sequences to APRStt gateway. */ - - int obj_recv_chan; /* Channel to listen for tones. */ - - int obj_xmit_chan; /* Channel to transmit object report. */ - - char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)]; - /* e.g. empty or "WIDE2-1,WIDE1-1" */ - - int retain_time; /* Seconds to keep information about a user. */ - int num_xmits; /* Number of times to transmit object report. */ - int xmit_delay[TT_MAX_XMITS]; /* Delay between them. */ - - struct ttloc_s *ttloc_ptr; /* Pointer to variable length array of above. */ - int ttloc_size; /* Number of elements allocated. */ - int ttloc_len; /* Number of elements actually used. */ - - double corral_lat; /* The "corral" for unknown locations. */ - double corral_lon; - double corral_offset; - int corral_ambiguity; -}; - - - - -void aprs_tt_init (struct tt_config_s *p_config); - -void aprs_tt_button (int chan, char button); /* Error codes for sending responses to user. */ +#define TT_ERROR_OK 0 /* Success. */ #define TT_ERROR_D_MSG 1 /* D was first char of field. Not implemented yet. */ #define TT_ERROR_INTERNAL 2 /* Internal error. Shouldn't be here. */ #define TT_ERROR_MACRO_NOMATCH 3 /* No definition for digit sequence. */ @@ -106,7 +77,97 @@ 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 TT_ERROR_INVALID_MHEAD 10 /* Invalid Maidenhead Locator. */ +#define TT_ERROR_INVALID_SATSQ 11 /* Satellite square must be 4 digits. */ + +#define TT_ERROR_MAXP1 12 /* Number of items above. i.e. Last number plus 1. */ + + +#if CONFIG_C /* Is this being included from config.c? */ + +/* Must keep in sync with above !!! */ + +static const char *tt_msg_id[TT_ERROR_MAXP1] = { + "OK", + "D_MSG", + "INTERNAL", + "MACRO_NOMATCH", + "BAD_CHECKSUM", + "INVALID_CALL", + "INVALID_OBJNAME", + "INVALID_SYMBOL", + "INVALID_LOC", + "NO_CALL", + "INVALID_MHEAD", + "INVALID_SATSQ" +}; + +#endif + +/* + * Configuration options for APRStt. + */ + +#define TT_MAX_XMITS 10 + +#define TT_MTEXT_LEN 64 + + +struct tt_config_s { + + int gateway_enabled; /* Send DTMF sequences to APRStt gateway. */ + + int obj_recv_chan; /* Channel to listen for tones. */ + + int obj_xmit_chan; /* Channel to transmit object report. */ + /* -1 for none. This could happpen if we */ + /* are only sending to application */ + /* and/or IGate. */ + + int obj_send_to_app; /* send to attached application(s). */ + + int obj_send_to_ig; /* send to IGate. */ + + char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)]; + /* e.g. empty or "WIDE2-1,WIDE1-1" */ + + int retain_time; /* Seconds to keep information about a user. */ + + int num_xmits; /* Number of times to transmit object report. */ + + int xmit_delay[TT_MAX_XMITS]; /* Delay between them. */ + /* e.g. 3 seconds before first transmission then */ + /* delays of 16, 32, seconds etc. in between repeats. */ + + struct ttloc_s *ttloc_ptr; /* Pointer to variable length array of above. */ + int ttloc_size; /* Number of elements allocated. */ + int ttloc_len; /* Number of elements actually used. */ + + double corral_lat; /* The "corral" for unknown locations. */ + double corral_lon; + double corral_offset; + int corral_ambiguity; + + char status[10][TT_MTEXT_LEN]; /* Up to 9 status messages. e.g. "/enroute" */ + /* Position 0 means none and can't be changed. */ + + struct { + char method[AX25_MAX_ADDR_LEN]; /* SPEECH or MORSE[-n] */ + char mtext[TT_MTEXT_LEN]; /* Message text. */ + } response[TT_ERROR_MAXP1]; + + char ttcmd[80]; /* Command to generate custom audible response. */ +}; + + + + +void aprs_tt_init (struct tt_config_s *p_config); + +void aprs_tt_button (int chan, char button); + + + #define APRSTT_LOC_DESC_LEN 32 /* Need at least 26 */ @@ -115,6 +176,9 @@ void aprs_tt_dao_to_desc (char *dao, char *str); void aprs_tt_sequence (int chan, char *msg); +int dw_run_cmd (char *cmd, int oneline, char *result, int maxresult); + + #endif /* end aprs_tt.h */ \ No newline at end of file diff --git a/atest.c b/atest.c index 94b0b57..28a5457 100644 --- a/atest.c +++ b/atest.c @@ -565,7 +565,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t a int info_len; int h; char heard[20]; - char alevel_text[32]; + char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; packets_decoded++; diff --git a/audio.c b/audio.c index 1b39d1a..4e90c5a 100644 --- a/audio.c +++ b/audio.c @@ -1,7 +1,4 @@ -// Remove next line to eliminate annoying/useful (depending on who you ask) debug messages every 100 seconds. -#define STATISTICS 1 - // // This file is part of Dire Wolf, an amateur radio packet TNC. @@ -95,6 +92,7 @@ #include "direwolf.h" #include "audio.h" +#include "audio_stats.h" #include "textcolor.h" #include "dtime_now.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ @@ -290,14 +288,14 @@ int audio_open (struct audio_s *pa) 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"); + strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in)); } if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) { adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP; /* Supply default port if none specified. */ if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 || strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) { - sprintf (pa->adev[a].adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT); + snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT); } } @@ -305,16 +303,16 @@ int audio_open (struct audio_s *pa) /* If not specified, the device names should be "default". */ - strcpy (audio_in_name, pa->adev[a].adevice_in); - strcpy (audio_out_name, pa->adev[a].adevice_out); + strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name)); + strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name)); char ctemp[40]; if (pa->adev[a].num_channels == 2) { - sprintf (ctemp, " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); + snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); } else { - sprintf (ctemp, " (channel %d)", ADEVFIRSTCHAN(a)); + snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a)); } text_color_set(DW_COLOR_INFO); @@ -358,7 +356,7 @@ int audio_open (struct audio_s *pa) 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); +// snprintf (message, sizeof(message), "Could not open audio device %s", pa->adev[a].adevice_in); // perror (message); return (-1); } @@ -874,75 +872,6 @@ int audio_get (int a) adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n); #endif -#if STATISTICS - -// TODO1.2: add audio level information to windows version. Common function? -// TODO1.2: add quiet option to suppress this. - -/* - * Print information about the sample rate as a debugging aid. - * I've never seen an issue with Windows or x86 Linux but the Raspberry Pi - * has a very troublesome audio input system where many samples got lost. - * Occasional lines like this would immediately identify the issue. - * - * Past 100 seconds, 4409856 audio samples processed, 0 errors. - * - * That's a little hard to read. Maybe we'd be better off with an average - * and fewer digits like this: 44.1 k - * - * While we are at it we can also print the current audio level(s) providing - * more clues if nothing is being decoded. - */ - - if (last_time[a] == 0) { - last_time[a] = time(NULL); - sample_count[a] = 0; - error_count[a] = 0; - } - else { - if (n > 0) { - sample_count[a] += n; - } - else { - error_count[a]++; - } - this_time[a] = time(NULL); - if (this_time[a] >= last_time[a] + duration) { - -#if 1 /* Try this for version 1.2 and see how people react. */ - - float ave_rate = (sample_count[a] / 1000.0) / duration; - - text_color_set(DW_COLOR_DEBUG); - - if (save_audio_config_p->adev[a].num_channels > 1) { - int ch0 = ADEVFIRSTCHAN(a); - alevel_t alevel0 = demod_get_audio_level(a,ch0); - int ch1 = ADEVFIRSTCHAN(a) + 1; - alevel_t alevel1 = demod_get_audio_level(a,ch1); - - dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio levels CH%d %d, CH%d %d\n\n", - a, ave_rate, error_count[a], ch0, alevel0.rec, ch1, alevel1.rec); - } - else { - int ch0 = ADEVFIRSTCHAN(a); - alevel_t alevel0 = demod_get_audio_level(a,ch0); - - dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio level CH%d %d\n\n", - a, ave_rate, error_count[a], ch0, alevel0.rec); - } - -#else - text_color_set(DW_COLOR_DEBUG); - dw_printf ("\nADEVICE%d: Past %d seconds, %d audio samples processed, %d errors.\n\n", - a, duration, sample_count[a], error_count[a]); -#endif - last_time[a] = this_time[a]; - sample_count[a] = 0; - error_count[a] = 0; - } - } -#endif if (n > 0) { @@ -950,6 +879,12 @@ int audio_get (int a) adev[a].inbuf_len = n * adev[a].bytes_per_frame; /* convert to number of bytes */ adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + n, + save_audio_config_p->statistics_interval); + } else if (n == 0) { @@ -973,6 +908,11 @@ int audio_get (int a) text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input device %d error: %s\n", a, snd_strerror(n)); + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + /* Try to recover a few times and eventually give up. */ if (++retries > 10) { adev[a].inbuf_len = 0; @@ -1015,10 +955,21 @@ int audio_get (int a) perror("Can't read from audio device"); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + return (-1); } adev[a].inbuf_len = n; adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + n / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); } #endif /* USE_ALSA */ @@ -1042,11 +993,23 @@ int audio_get (int a) dw_printf ("Can't read from udp socket, res=%d", res); adev[a].inbuf_len = 0; adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + return (-1); } adev[a].inbuf_len = res; adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); + } break; @@ -1065,6 +1028,11 @@ int audio_get (int a) exit (0); } + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); + adev[a].inbuf_len = res; adev[a].inbuf_next = 0; } diff --git a/audio.h b/audio.h index 3f6017a..c493eef 100644 --- a/audio.h +++ b/audio.h @@ -87,6 +87,9 @@ struct audio_s { char tts_script[80]; /* Script for text to speech. */ + int statistics_interval; /* Number of seconds between the audio */ + /* statistics reports. This is set by */ + /* the "-a" option. 0 to disable feature. */ /* Properties for each audio channel, common to receive and transmit. */ /* Can be different for each radio channel. */ @@ -227,7 +230,7 @@ struct audio_s { }; -#if __WIN32__ +#if __WIN32__ || __APPLE__ #define DEFAULT_ADEVICE "" /* Windows: Empty string = default audio device. */ #else #if USE_ALSA diff --git a/audio_portaudio.c b/audio_portaudio.c new file mode 100644 index 0000000..dc8fd28 --- /dev/null +++ b/audio_portaudio.c @@ -0,0 +1,1305 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2015 Robert Stiles, KK5VD +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +/*------------------------------------------------------------------ + * + * Module: audio_portaudio.c + * + * Purpose: Interface to audio device commonly called a "sound card" for + * historical reasons. + * + * This version is for Various OS' using Port Audio + * + * Major Revisions: + * + * 1.2 - Add ability to use more than one audio device. + * 1.3 - New file added for Port Audio for Mac and possibly others. + * + *---------------------------------------------------------------*/ + +#if defined(USE_PORTAUDIO) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "audio.h" +#include "audio_stats.h" +#include "textcolor.h" +#include "dtime_now.h" +#include "demod.h" /* for alevel_t & demod_get_audio_level() */ + +#include "portaudio.h" + +/* Audio configuration. */ + +static struct audio_s *save_audio_config_p; + +/* Current state for each of the audio devices. */ + +static struct adev_s { + + pthread_mutex_t input_mutex; + pthread_cond_t input_cond; + + PaStream *inStream; + PaStreamParameters inputParameters; + int pa_input_device_number; + int no_of_input_channels; + int input_finished; + int input_pause; + int input_flush; + + void *audio_in_handle; + int inbuf_size_in_bytes; /* number of bytes allocated */ + unsigned char *inbuf_ptr; + int inbuf_len; /* number byte of actual data available. */ + int inbuf_next; /* index of next to remove. */ + int inbuf_bytes_per_frame; /* number of bytes for a sample from all channels. */ + int inbuf_frames_per_buffer; /* number of frames in a buffer. */ + + pthread_mutex_t output_mutex; + pthread_cond_t output_cond; + + PaStream *outStream; + PaStreamParameters outputParameters; + int pa_output_device_number; + int no_of_output_channels; + int output_pause; + int output_finished; + int output_flush; + int output_wait_flag; + + void *audio_out_handle; + int outbuf_size_in_bytes; + unsigned char *outbuf_ptr; + int outbuf_len; + int outbuf_next; /* index of next to remove. */ + int outbuf_bytes_per_frame; /* number of bytes for a sample from all channels. */ + int outbuf_frames_per_buffer; /* number of frames in a buffer. */ + + enum audio_in_type_e g_audio_in_type; + + int udp_sock; /* UDP socket for receiving data */ + +} adev[MAX_ADEVS]; + +// Originally 40. Version 1.2, try 10 for lower latency. + +#define ONE_BUF_TIME 10 +#define SAMPLE_SILENCE 0 + +#define PA_INPUT 1 +#define PA_OUTPUT 2 + +#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff) + +#undef FOR_FUTURE_USE + +static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *devname, char *inout); +static void print_pa_devices(void); +static int check_pa_configure(struct adev_s *dev, int sample_rate); +static void list_supported_sample_rates(struct adev_s *dev); +static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo); +static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag); +static int calcbufsize(int rate, int chans, int bits); + + +static int calcbufsize(int rate, int chans, int bits) +{ + int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000; + int size2 = roundup1k(size1); +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n", + rate, chans, bits, size1, size2); +#endif + return (size2); +} + +/*------------------------------------------------------------------ + * Search the portaudio device tree looking for the request device. + * One of the issues with portaudio has to do with devices returning + * the same device name for more then one connected device + * (ie two SignaLinks). Appending a Portaudio device index to the + * the device name ensure we can find the correct one. And if it's not + * available return the first occurence that matches the device name. + *----------------------------------------------------------------*/ +static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag) +{ + int numDevices = Pa_GetDeviceCount(); + const PaDeviceInfo * di = (PaDeviceInfo *)0; + int i = 0; + + // First check to see if the requested index matches the device name. + if(reqDeviceNo < numDevices) { + di = Pa_GetDeviceInfo((PaDeviceIndex) reqDeviceNo); + if(strncmp(di->name, _devName, 80) == 0) { + if((io_flag == PA_INPUT) && di->maxInputChannels) + return reqDeviceNo; + if((io_flag == PA_OUTPUT) && di->maxOutputChannels) + return reqDeviceNo; + } + } + + // Requested device index doesn't match device name. Search for one. + for(i = 0; i < numDevices; i++) { + di = Pa_GetDeviceInfo((PaDeviceIndex) i); + if(strncmp(di->name, _devName, 80) == 0) { + if((io_flag == PA_INPUT) && di->maxInputChannels) + return i; + if((io_flag == PA_OUTPUT) && di->maxOutputChannels) + return i; + } + } + + // No Matches found + return -1; +} + +/*------------------------------------------------------------------ + * Extract device name and number. + *----------------------------------------------------------------*/ +static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo) +{ + char *cPtr = (char *)0; + char cVal = 0; + int count = 0; + char numStr[8]; + + if(!deviceStr || !_devName || !_devNo) { + dw_printf( "Internal Error: Func %s passed null pointer.\n", __func__); + return -1; + } + + cPtr = deviceStr; + + memset(_devName, 0, length); + memset(numStr, 0, sizeof(numStr)); + + while(*cPtr) { + cVal = *cPtr++; + if(cVal == ':') break; + if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) { + _devName[count++] = cVal; + } + + } + + count = 0; + + while(*cPtr) { + cVal = *cPtr++; + if(isdigit(cVal) && (count < (sizeof(numStr) - 1))) { + numStr[count++] = cVal; + } + } + + if(numStr[0] == 0) { + *_devNo = 0; + } else { + sscanf(numStr, "%d", _devNo); + } + + return 0; +} + +/*------------------------------------------------------------------ + * List the supported sample rates. + *----------------------------------------------------------------*/ +static void list_supported_sample_rates(struct adev_s *dev) +{ + static double standardSampleRates[] = { + 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, + 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */ + }; + int i, printCount; + PaError err; + + printCount = 0; + for(i = 0; standardSampleRates[i] > 0; i++ ) { + err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, standardSampleRates[i] ); + if( err == paFormatIsSupported ) { + if( printCount == 0 ) { + dw_printf( "\t%8.2f", standardSampleRates[i] ); + printCount = 1; + } + else if( printCount == 4 ) { + dw_printf( ",\n\t%8.2f", standardSampleRates[i] ); + printCount = 1; + } + else { + dw_printf( ", %8.2f", standardSampleRates[i] ); + ++printCount; + } + } + } + + if( !printCount ) + dw_printf( "None\n" ); + else + dw_printf( "\n" ); +} + +/*------------------------------------------------------------------ + * Check PA Configure parameters. + *----------------------------------------------------------------*/ +static int check_pa_configure(struct adev_s *dev, int sample_rate) +{ + if(!dev) { + dw_printf( "Internal Error: Func %s struct adev_s *dev null pointer.\n", __func__); + return -1; + } + + PaError err = 0; + err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, sample_rate); + if(err == paFormatIsSupported) return 0; + dw_printf( "PortAudio Config Error: %s\n", Pa_GetErrorText(err)); + return err; +} + +/*------------------------------------------------------------------ + * Print a list of device names and parameters + *----------------------------------------------------------------*/ +static void print_pa_devices(void) +{ + int i, numDevices, defaultDisplayed; + const PaDeviceInfo *deviceInfo; + + numDevices = Pa_GetDeviceCount(); + + if( numDevices < 0 ) { + dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices ); + return; + } + + dw_printf( "Number of devices = %d\n", numDevices ); + + for(i = 0; i < numDevices; i++ ) { + deviceInfo = Pa_GetDeviceInfo( i ); + dw_printf( "--------------------------------------- device #%d\n", i ); + + /* Mark global and API specific default devices */ + defaultDisplayed = 0; + if( i == Pa_GetDefaultInputDevice() ) { + dw_printf( "[ Default Input" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + dw_printf( "[ Default %s Input", hostInfo->name ); + defaultDisplayed = 1; + } + + if( i == Pa_GetDefaultOutputDevice() ) { + dw_printf( (defaultDisplayed ? "," : "[") ); + dw_printf( " Default Output" ); + defaultDisplayed = 1; + } + else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) { + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + dw_printf( (defaultDisplayed ? "," : "[") ); + dw_printf( " Default %s Output", hostInfo->name ); + defaultDisplayed = 1; + } + + if( defaultDisplayed ) + dw_printf( " ]\n" ); + + /* print device info fields */ + dw_printf( "Name = \"%s\"\n", deviceInfo->name ); + dw_printf( "Host API = %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name ); + dw_printf( "Max inputs = %d\n", deviceInfo->maxInputChannels ); + dw_printf( "Max outputs = %d\n", deviceInfo->maxOutputChannels ); + } +} + +/*------------------------------------------------------------------ + * Port Audio Input Callback + *----------------------------------------------------------------*/ +static int paInput16CB( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + struct adev_s *data = (struct adev_s *) userData; + const int16_t *rptr = (const int16_t *) inputBuffer; + size_t framesToCalc = 0; + size_t i = 0; + int finished = 0; + int word = 0; + size_t bytes_left = data->inbuf_size_in_bytes - data->inbuf_len; + size_t framesLeft = bytes_left / data->inbuf_bytes_per_frame; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + if( framesLeft < framesPerBuffer ) { + framesToCalc = framesLeft; + finished = paComplete; + } else { + framesToCalc = framesPerBuffer; + finished = paContinue; + } + + if( inputBuffer == NULL || data->input_flush) { + for(i = 0; i < framesToCalc; i++) { + data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; + data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; + if(data->no_of_input_channels == 2) { + data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; + data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE; + } + } + } else { + for(i = 0; i < framesToCalc; i++) { + word = *rptr++; /* left */ + data->inbuf_ptr[data->inbuf_len++] = word & 0xff; + data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff; + + if(data->no_of_input_channels == 2) { + word = *rptr++; /* right */ + data->inbuf_ptr[data->inbuf_len++] = word & 0xff; + data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff; + } + } + } + + if((finished == paComplete) || + (data->inbuf_len >= data->inbuf_size_in_bytes)) { + pthread_cond_signal(&data->input_cond); + finished = data->input_finished; + } + + return finished; +} + +#if FOR_FUTURE_USE +/*------------------------------------------------------------------ + * Port Audio Output Callback + *----------------------------------------------------------------*/ +static int paOutput16CB( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) +{ + struct adev_s *data = (struct adev_s *) userData; + int16_t *wptr = (int16_t *) outputBuffer; + size_t i = 0; + int finished = 0; + size_t bytes_left = data->outbuf_size_in_bytes - data->outbuf_len; + size_t framesLeft = bytes_left / data->outbuf_bytes_per_frame; + int word = 0; + + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + if(framesLeft && (framesLeft < framesPerBuffer)) { + /* final buffer... */ + for(i = 0; i < framesLeft; i++ ) { + word = data->outbuf_ptr[data->outbuf_len++] & 0xff; + word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; + *wptr++ = word; /* left */ + if(data->no_of_output_channels == 2 ) { + word = data->outbuf_ptr[data->outbuf_len++] & 0xff; + word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; + *wptr++ = word; /* right */ + } + } + for( ; i < framesPerBuffer; i++ ) { + *wptr++ = 0; /* left */ + if(data->no_of_output_channels == 2 ) + *wptr++ = 0; /* right */ + } + finished = paContinue; + } else { + for(i = 0; i < framesPerBuffer; i++ ) { + word = data->outbuf_ptr[data->outbuf_len++] & 0xff; + word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; + *wptr++ = word; /* left */ + if(data->no_of_output_channels == 2) { + word = data->outbuf_ptr[data->outbuf_len++] & 0xff; + word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff; + *wptr++ = word; /* right */ + } + } + finished = paComplete; + } + + if(data->output_flush) { + data->output_flush = 0; + finished = paComplete; + } + + pthread_cond_signal(&data->output_cond); + finished = data->output_finished; + + return finished; +} +#endif + +/*------------------------------------------------------------------ + * + * Name: audio_open + * + * Purpose: Open the digital audio device. + * + * New in version 1.0, we recognize "udp:" optionally + * followed by a port number. + * + * Inputs: pa - Address of structure of type audio_s. + * + * Using a structure, rather than separate arguments + * seemed to make sense because we often pass around + * the same set of parameters various places. + * + * The fields that we care about are: + * num_channels + * samples_per_sec + * bits_per_sample + * If zero, reasonable defaults will be provided. + * + * The device names are in adevice_in and adevice_out. + * where c is the "card" (for historical purposes) + * and d is the "device" within the "card." + * + * + * Outputs: pa - The ACTUAL values are returned here. + * + * These might not be exactly the same as what was requested. + * + * Example: ask for stereo, 16 bits, 22050 per second. + * An ordinary desktop/laptop PC should be able to handle this. + * However, some other sort of smaller device might be + * more restrictive in its capabilities. + * It might say, the best I can do is mono, 8 bit, 8000/sec. + * + * The sofware modem must use this ACTUAL information + * that the device is supplying, that could be different + * than what the user specified. + * + * Returns: 0 for success, -1 for failure. + * + * + *----------------------------------------------------------------*/ + +int audio_open (struct audio_s *pa) +{ + int err = 0; + int chan = 0; + int a = 0; + int clear_value = 0; + char audio_in_name[80]; + char audio_out_name[80]; + static int initalize_flag = 0; + PaError paerr = paNoError; + + if(!initalize_flag) { + paerr = Pa_Initialize(); + initalize_flag = -1; + } + + if(paerr != paNoError ) return -1; + + save_audio_config_p = pa; + + memset (adev, 0, sizeof(adev)); + memset (audio_in_name, 0, sizeof(audio_in_name)); + memset (audio_out_name, 0, sizeof(audio_out_name)); + + for (a = 0; a < MAX_ADEVS; a++) { + adev[a].udp_sock = -1; + } + + /* + * Fill in defaults for any missing values. + */ + + for (a = 0; a < MAX_ADEVS; a++) { + if (pa->adev[a].num_channels == 0) + pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS; + + if (pa->adev[a].samples_per_sec == 0) + pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; + + if (pa->adev[a].bits_per_sample == 0) + pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; + + for (chan = 0; chan < MAX_CHANS; chan++) { + if (pa->achan[chan].mark_freq == 0) + pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ; + + if (pa->achan[chan].space_freq == 0) + pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ; + + if (pa->achan[chan].baud == 0) + pa->achan[chan].baud = DEFAULT_BAUD; + + if (pa->achan[chan].num_subchan == 0) + pa->achan[chan].num_subchan = 1; + } + } + + /* + * Open audio device(s). + */ + + for (a = 0; a < MAX_ADEVS; a++) { + if (pa->adev[a].defined) { + + adev[a].inbuf_size_in_bytes = 0; + adev[a].outbuf_size_in_bytes = 0; + + /* + * Determine the type of audio input. + */ + + adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; + + if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) { + adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN; + /* Change "-" to stdin for readability. */ + strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in)); + } + + if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) { + adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP; + /* Supply default port if none specified. */ + if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 || + strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) { + snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT); + } + } + + /* Let user know what is going on. */ + /* If not specified, the device names should be "default". */ + + strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name)); + strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name)); + + char ctemp[40]; + + if (pa->adev[a].num_channels == 2) { + snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); + } else { + snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a)); + } + + text_color_set(DW_COLOR_INFO); + + if (strcmp(audio_in_name,audio_out_name) == 0) { + dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp); + } else { + dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp); + dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp); + } + + /* + * Now attempt actual opens. + */ + + /* + * Input device. + */ + + switch (adev[a].g_audio_in_type) { + + case AUDIO_IN_TYPE_SOUNDCARD: + print_pa_devices(); + err = set_portaudio_params (a, &adev[a], pa, audio_in_name, audio_out_name); + if(err < 0) return -1; + + pthread_mutex_init(&adev[a].input_mutex, NULL); + pthread_cond_init(&adev[a].input_cond, NULL); + + pthread_mutex_init(&adev[a].output_mutex, NULL); + pthread_cond_init(&adev[a].output_cond, NULL); + + if(pa->adev[a].bits_per_sample == 8) + clear_value = 128; + else + clear_value = 0; + + break; + + /* + * UDP. + */ + case AUDIO_IN_TYPE_SDR_UDP: + + // Create socket and bind socket + + { + struct sockaddr_in si_me; + //Create UDP Socket + if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket, errno %d\n", errno); + return -1; + } + + memset((char *) &si_me, 0, sizeof(si_me)); + si_me.sin_family = AF_INET; + si_me.sin_port = htons((short)atoi(audio_in_name+4)); + si_me.sin_addr.s_addr = htonl(INADDR_ANY); + + //Bind to the socket + if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't bind socket, errno %d\n", errno); + return -1; + } + } + //adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; + + break; + + /* + * stdin. + */ + case AUDIO_IN_TYPE_STDIN: + + /* Do we need to adjust any properties of stdin? */ + + //adev[a].inbuf_size_in_bytes = 1024; + + break; + + default: + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, invalid audio_in_type\n"); + return (-1); + } + + /* + * Finally allocate buffer for each direction. + */ + + adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes); + assert (adev[a].inbuf_ptr != NULL); + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; + memset(adev[a].inbuf_ptr, clear_value, adev[a].inbuf_size_in_bytes); + + adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes); + assert (adev[a].outbuf_ptr != NULL); + adev[a].outbuf_len = 0; + adev[a].outbuf_next = 0; + memset(adev[a].outbuf_ptr, clear_value, adev[a].outbuf_size_in_bytes); + + if(adev[a].inStream) { + err = Pa_StartStream(adev[a].inStream); + if(err != paNoError) { + dw_printf ("Input stream start Error %s\n", Pa_GetErrorText(err)); + } + } + + if(adev[a].outStream) { + err = Pa_StartStream(adev[a].outStream); + if(err != paNoError) { + dw_printf ("Output stream start Error %s\n", Pa_GetErrorText(err)); + } + } + } /* end of audio device defined */ + } /* end of for each audio device */ + + return (0); + +} /* end audio_open */ + + +/* + * Set parameters for sound card. + * + * See ?? for details. + */ +static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *_audio_in_name, char *_audio_out_name) +{ + int numDevices = 0; + int err = 0; + int buffer_size = 0; + int sampleFormat = 0; + int no_of_bytes_per_sample = 0; + int reqInDeviceNo = 0; + int reqOutDeviceNo = 0; + char input_devName[80]; + char output_devName[80]; + + text_color_set(DW_COLOR_ERROR); + + if(!dev || !pa || !_audio_in_name || !_audio_out_name) { + dw_printf ("Internal error, invalid function parameter pointer(s) (null)\n"); + return -1; + } + + if(_audio_in_name[0] == 0) { + dw_printf ("Input device name null\n"); + return -1; + } + + if(_audio_out_name[0] == 0) { + dw_printf ("Output device name null\n"); + return -1; + } + + numDevices = Pa_GetDeviceCount(); + if( numDevices < 0 ) { + dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices ); + return -1; + } + + err = pa_devNN(_audio_in_name, input_devName, sizeof(input_devName), &reqInDeviceNo); + if(err < 0) return -1; + + reqInDeviceNo = searchPADevice(dev, input_devName, reqInDeviceNo, PA_INPUT); + if(reqInDeviceNo < 0) { + dw_printf ("Requested Input Audio Device not found %s.\n", input_devName); + return -1; + } + + err = pa_devNN(_audio_out_name, output_devName, sizeof(output_devName), &reqOutDeviceNo); + if(err < 0) return -1; + + reqOutDeviceNo = searchPADevice(dev, output_devName, reqOutDeviceNo, PA_OUTPUT); + if(reqOutDeviceNo < 0) { + dw_printf ("Requested Output Audio Device not found %s.\n", output_devName); + return -1; + } + + dev->pa_input_device_number = reqInDeviceNo; + dev->pa_output_device_number = reqOutDeviceNo; + + switch(pa->adev[a].bits_per_sample) { + case 8: + sampleFormat = paInt8; + no_of_bytes_per_sample = sizeof(int8_t); + assert("int8_t size not equal to 1" && sizeof(int8_t) == 1); + break; + + case 16: + sampleFormat = paInt16; + no_of_bytes_per_sample = sizeof(int16_t); + assert("int16_t size not equal to 2" && sizeof(int16_t) == 2); + break; + + default: + dw_printf ("Unsupported Sample Size %s.\n", output_devName); + return -1; + } + + + buffer_size = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample); + + dev->inbuf_size_in_bytes = buffer_size; + dev->inbuf_bytes_per_frame = no_of_bytes_per_sample * pa->adev[a].num_channels; + dev->inbuf_frames_per_buffer = dev->inbuf_size_in_bytes / dev->inbuf_bytes_per_frame; + + dev->inputParameters.device = dev->pa_input_device_number; + dev->inputParameters.channelCount = pa->adev[a].num_channels; + dev->inputParameters.sampleFormat = sampleFormat; + dev->inputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->inputParameters.device)->defaultLowInputLatency; + dev->inputParameters.hostApiSpecificStreamInfo = NULL; + + dev->outbuf_size_in_bytes = buffer_size; + dev->outbuf_bytes_per_frame = no_of_bytes_per_sample * pa->adev[a].num_channels; + dev->outbuf_frames_per_buffer = dev->outbuf_size_in_bytes / dev->outbuf_bytes_per_frame; + + dev->outputParameters.device = dev->pa_output_device_number; + dev->outputParameters.channelCount = pa->adev[a].num_channels; + dev->outputParameters.sampleFormat = sampleFormat; + dev->outputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->outputParameters.device)->defaultHighOutputLatency; + dev->outputParameters.hostApiSpecificStreamInfo = NULL; + + err = check_pa_configure(dev, pa->adev[a].samples_per_sec); + if(err) { + if(err == paInvalidSampleRate) + list_supported_sample_rates(dev); + return -1; + } + + err = Pa_OpenStream(&dev->inStream, &dev->inputParameters, NULL, + pa->adev[a].samples_per_sec, dev->inbuf_frames_per_buffer, 0, paInput16CB, dev ); + + if( err != paNoError ) { + dw_printf( "PortAudio OpenStream (input) Error: %s\n", Pa_GetErrorText(err)); + return -1; + } + + err = Pa_OpenStream(&dev->outStream, NULL, &dev->outputParameters, + // pa->adev[a].samples_per_sec, framesPerBuffer, 0, paOutput16CB, dev ); + pa->adev[a].samples_per_sec, dev->outbuf_frames_per_buffer, 0, NULL, dev ); + + if( err != paNoError ) { + dw_printf( "PortAudio OpenStream (output) Error: %s\n", Pa_GetErrorText(err)); + return -1; + } + + dev->input_finished = paContinue; + dev->output_finished = paContinue; + + return buffer_size; +} + + +/*------------------------------------------------------------------ + * + * Name: audio_get + * + * Purpose: Get one byte from the audio device. + * + * Inputs: a - Our number for audio device. + * + * Returns: 0 - 255 for a valid sample. + * -1 for any type of error. + * + * Description: The caller must deal with the details of mono/stereo + * and number of bytes per sample. + * + * This will wait if no data is currently available. + * + *----------------------------------------------------------------*/ + +// Use hot attribute for all functions called for every audio sample. + +__attribute__((hot)) +int audio_get (int a) +{ + int n; + int retries = 0; + + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + + dw_printf ("audio_get():\n"); + +#endif + + assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768); + + switch (adev[a].g_audio_in_type) { + + /* + * Soundcard - PortAudio + */ + case AUDIO_IN_TYPE_SOUNDCARD: + + while (adev[a].inbuf_next >= adev[a].inbuf_len) { + + assert (adev[a].inStream != NULL); +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame); +#endif + if(adev[a].inbuf_len >= adev[a].inbuf_size_in_bytes) { + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; + } + + pthread_mutex_lock(&adev[a].input_mutex); + pthread_cond_wait(&adev[a].input_cond, &adev[a].input_mutex); + pthread_mutex_unlock(&adev[a].input_mutex); + + n = adev[a].inbuf_len / adev[a].inbuf_bytes_per_frame; +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_get(): readi asked for %d and got %d frames\n", + adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n); +#endif + + + if (n > 0) { + + /* Success */ + + adev[a].inbuf_len = n * adev[a].inbuf_bytes_per_frame; /* convert to number of bytes */ + adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + n, + save_audio_config_p->statistics_interval); + + } + else if (n == 0) { + + /* Didn't expect this, but it's not a problem. */ + /* Wait a little while and try again. */ + + text_color_set(DW_COLOR_ERROR); + dw_printf ("[%s], Audio input got zero bytes\n", __func__); + SLEEP_MS(10); + + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; + } + else { + /* Error */ + // TODO: Needs more study and testing. + + // TODO: print n. should snd_strerror use n or errno? + // Audio input device error: Unknown error + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio input device %d error\n", a); + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + + /* Try to recover a few times and eventually give up. */ + if (++retries > 10) { + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; + return (-1); + } + + if (n == -EPIPE) { + + /* EPIPE means overrun */ + + //snd_pcm_recover (adev[a].audio_in_handle, n, 1); + + } + else { + /* Could be some temporary condition. */ + /* Wait a little then try again. */ + /* Sometimes I get "Resource temporarily available" */ + /* when the Update Manager decides to run. */ + + SLEEP_MS (250); + //snd_pcm_recover (adev[a].audio_in_handle, n, 1); + } + } + } + + break; + + /* + * UDP. + */ + + case AUDIO_IN_TYPE_SDR_UDP: + + while (adev[a].inbuf_next >= adev[a].inbuf_len) { + int res; + + assert (adev[a].udp_sock > 0); + res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); + if (res < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't read from udp socket, res=%d", res); + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + + return (-1); + } + + adev[a].inbuf_len = res; + adev[a].inbuf_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); + } + break; + + /* + * stdin. + */ + case AUDIO_IN_TYPE_STDIN: + + while (adev[a].inbuf_next >= adev[a].inbuf_len) { + int res; + + res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes); + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nEnd of file on stdin. Exiting.\n"); + exit (0); + } + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); + + adev[a].inbuf_len = res; + adev[a].inbuf_next = 0; + } + + break; + } + + + if (adev[a].inbuf_next < adev[a].inbuf_len) + n = adev[a].inbuf_ptr[adev[a].inbuf_next++]; + else + n = 0; + +#if DEBUGx + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_get(): returns %d\n", n); + +#endif + + + return (n); + +} /* end audio_get */ + + +/*------------------------------------------------------------------ + * + * Name: audio_put + * + * Purpose: Send one byte to the audio device. + * + * Inputs: a + * + * c - One byte in range of 0 - 255. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * Description: The caller must deal with the details of mono/stereo + * and number of bytes per sample. + * + * See Also: audio_flush + * audio_wait + * + *----------------------------------------------------------------*/ +int audio_put (int a, int c) +{ + int err = 0; + size_t frames = 0; + + //#define __TIMED__ +#ifdef __TIMED__ + static int count = 0; + static double start = 0, end = 0, diff = 0; + + if(adev[a].outbuf_len == 0) + start = dtime_now(); +#endif + + if(c >= 0) { + adev[a].outbuf_ptr[adev[a].outbuf_len++] = c; + } + + if ((adev[a].outbuf_len >= adev[a].outbuf_size_in_bytes) || (c < 0)) { + + frames = adev[a].outbuf_len / adev[a].outbuf_bytes_per_frame; + + if(frames > 0) { + err = Pa_WriteStream(adev[a].outStream, adev[a].outbuf_ptr, frames); + } + + // Getting underflow error for some reason on the first pass. Upon examination of the + // audio data revealed no discontinuity in the signal. Time measurements indicate this routine + // on this machine (2.8Ghz/Xeon E5462/2008 vintage) can handle ~6 times the current + // sample rate (44100/2 bytes per frame). For now, mask the error. + // Transfer Time:0.184750080 No of Frames:56264 Per frame:0.000003284 speed:6.905695 + + if ((err != paNoError) && (err != paOutputUnderflowed)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("[%s] Audio Output Error: %s\n", __func__, Pa_GetErrorText(err)); + } + +#ifdef __TIMED__ + count += frames; + if(c < 0) { // When the Ax25 frames are flushed. + end = dtime_now(); + diff = end - start; + if(count) + dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n", + diff, count, diff/(count * 1.0), (1.0/44100.0)/(diff/(count * 1.0))); + count = 0; + } +#endif + adev[a].outbuf_len = 0; + adev[a].outbuf_next = 0; + } + + return (0); +} + +/*------------------------------------------------------------------ + * + * Name: audio_flush + * + * Purpose: Push out any partially filled output buffer. + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * See Also: audio_flush + * audio_wait + * + *----------------------------------------------------------------*/ + +int audio_flush (int a) +{ + audio_put(a, -1); + return 0; +} /* end audio_flush */ + + +/*------------------------------------------------------------------ + * + * Name: audio_wait + * + * Purpose: Finish up audio output before turning PTT off. + * + * Inputs: a - Index for audio device (not channel!) + * + * Returns: None. + * + * Description: Flush out any partially filled audio output buffer. + * Wait until all the queued up audio out has been played. + * Take any other necessary actions to stop audio output. + * + * In an ideal world: + * + * We would like to ask the hardware when all the queued + * up sound has actually come out the speaker. + * + * In reality: + * + * This has been found to be less than reliable in practice. + * + * Caller does the following: + * + * (1) Make note of when PTT is turned on. + * (2) Calculate how long it will take to transmit the + * frame including TXDELAY, frame (including + * "flags", data, FCS and bit stuffing), and TXTAIL. + * (3) Call this function, which might or might not wait long enough. + * (4) Add (1) and (2) resulting in when PTT should be turned off. + * (5) Take difference between current time and desired PPT off time + * and wait for additoinal time if required. + * + *----------------------------------------------------------------*/ + +void audio_wait (int a) +{ + audio_flush(a); + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_wait(): after sync, status=%d\n", err); +#endif +} /* end audio_wait */ + + +/*------------------------------------------------------------------ + * + * Name: audio_close + * + * Purpose: Close the audio device(s). + * + * Returns: Normally non-negative. + * -1 for any type of error. + * + * + *----------------------------------------------------------------*/ + +int audio_close (void) +{ + int err = 0; + int a; + + for (a = 0; a < MAX_ADEVS; a++) { + if(adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD) { + + audio_wait (a); + + if (adev[a].inStream != NULL) { + pthread_mutex_destroy(&adev[a].input_mutex); + pthread_cond_destroy(&adev[a].input_cond); + err |= (int) Pa_CloseStream(adev[a].inStream); + } + + if(adev[a].outStream != NULL) { + pthread_mutex_destroy(&adev[a].output_mutex); + pthread_cond_destroy(&adev[a].output_cond); + err |= (int) Pa_CloseStream(adev[a].outStream); + } + + err |= (int) Pa_Terminate(); + } + + if(adev[a].inbuf_ptr) + free (adev[a].inbuf_ptr); + + if(adev[a].outbuf_ptr) + free (adev[a].outbuf_ptr); + + adev[a].inbuf_size_in_bytes = 0; + adev[a].inbuf_ptr = NULL; + adev[a].inbuf_len = 0; + adev[a].inbuf_next = 0; + + adev[a].outbuf_size_in_bytes = 0; + adev[a].outbuf_ptr = NULL; + adev[a].outbuf_len = 0; + adev[a].outbuf_next = 0; + } + + if(err < 0) + err = -1; + + return (err); + +} /* end audio_close */ + +/* end audio_portaudio.c */ + +#endif // USE_PORTAUDIO diff --git a/audio_stats.c b/audio_stats.c new file mode 100644 index 0000000..9c67769 --- /dev/null +++ b/audio_stats.c @@ -0,0 +1,178 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2015 John Langner, WB2OSZ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + + +/*------------------------------------------------------------------ + * + * Module: audio_stats.c + * + * Purpose: Print statistics for audio input stream. + * + * A common complaint is that there is no indication of + * audio input level until a packet is received correctly. + * That's true for the Windows version but the Linux version + * prints something like this each 100 seconds: + * + * ADEVICE0: Sample rate approx. 44.1 k, 0 errors, receive audio level CH0 73 + * + * Some complain about the clutter but it has been a useful + * troubleshooting tool. In the earlier RPi days, the sample + * rate was quite low due to a device driver issue. + * Using a USB hub on the RPi also caused audio problems. + * One adapter, that I tried, produces samples at the + * right rate but all the samples are 0. + * + * Here we pull the code out of the Linux version of audio.c + * so we have a common function for all the platforms. + * + * We also add a command line option to adjust the time + * between reports or turn them off entirely. + * + * Revisions: This is new in version 1.3. + * + *---------------------------------------------------------------*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include "direwolf.h" +#include "audio_stats.h" +#include "textcolor.h" +#include "dtime_now.h" +#include "demod.h" /* for alevel_t & demod_get_audio_level() */ + + + +/*------------------------------------------------------------------ + * + * Name: audio_stats + * + * Purpose: Add sample count from one buffer to the statistics. + * Print if specified amount of time has passed. + * + * Inputs: adev - Audio device number: 0, 1, ..., MAX_ADEVS-1 + * + nchan - Number of channels for this device, 1 or 2. + * + * nsamp - How many audio samples were read. + * + * interval - How many seconds between reports. + * 0 to turn off. + * + * Returns: none + * + * Description: ... + * + *----------------------------------------------------------------*/ + + +void audio_stats (int adev, int nchan, int nsamp, int interval) +{ + + /* Gather numbers for read from audio device. */ + + + static time_t last_time[MAX_ADEVS] = { 0, 0, 0 }; + time_t this_time[MAX_ADEVS]; + static int sample_count[MAX_ADEVS]; + static int error_count[MAX_ADEVS]; + static int suppress_first[MAX_ADEVS]; + + + if (interval <= 0) { + return; + } + + assert (adev >= 0 && adev < MAX_ADEVS); + +/* + * Print information about the sample rate as a troubleshooting aid. + * I've never seen an issue with Windows or x86 Linux but the Raspberry Pi + * has a very troublesome audio input system where many samples got lost. + * + * While we are at it we can also print the current audio level(s) providing + * more clues if nothing is being decoded. + */ + + if (last_time[adev] == 0) { + last_time[adev] = time(NULL); + sample_count[adev] = 0; + error_count[adev] = 0; + suppress_first[adev] = 1; + /* suppressing the first one could mean a rather */ + /* long wait for the first message. We make the */ + /* first collection interval 3 seconds. */ + last_time[adev] -= (interval - 3); + } + else { + if (nsamp > 0) { + sample_count[adev] += nsamp; + } + else { + error_count[adev]++; + } + this_time[adev] = time(NULL); + if (this_time[adev] >= last_time[adev] + interval) { + + if (suppress_first[adev]) { + + /* The issue we had is that the first time the rate */ + /* would be off considerably because we didn't start */ + /* on a second boundary. So we will suppress printing */ + /* of the first one. */ + + suppress_first[adev] = 0; + } + else { + float ave_rate = (sample_count[adev] / 1000.0) / interval; + + text_color_set(DW_COLOR_DEBUG); + + if (nchan > 1) { + int ch0 = ADEVFIRSTCHAN(adev); + alevel_t alevel0 = demod_get_audio_level(ch0,0); + int ch1 = ADEVFIRSTCHAN(adev) + 1; + alevel_t alevel1 = demod_get_audio_level(ch1,0); + + dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio levels CH%d %d, CH%d %d\n\n", + adev, ave_rate, error_count[adev], ch0, alevel0.rec, ch1, alevel1.rec); + } + else { + int ch0 = ADEVFIRSTCHAN(adev); + alevel_t alevel0 = demod_get_audio_level(ch0,0); + + dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio level CH%d %d\n\n", + adev, ave_rate, error_count[adev], ch0, alevel0.rec); + } + } + last_time[adev] = this_time[adev]; + sample_count[adev] = 0; + error_count[adev] = 0; + } + } + +} /* end audio_stats.c */ + diff --git a/audio_stats.h b/audio_stats.h new file mode 100644 index 0000000..4cf8ad0 --- /dev/null +++ b/audio_stats.h @@ -0,0 +1,7 @@ + + +/* audio_stats.h */ + + +extern void audio_stats (int adev, int nchan, int nsamp, int interval); + diff --git a/audio_win.c b/audio_win.c index 26cfd1b..cd4b999 100644 --- a/audio_win.c +++ b/audio_win.c @@ -1,13 +1,8 @@ -//#define DEBUGUDP 1 -//#define DEBUG 1 - -#define STATISTICS 1 - // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ +// 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 @@ -68,6 +63,7 @@ #include "direwolf.h" #include "audio.h" +#include "audio_stats.h" #include "textcolor.h" #include "ptt.h" #include "demod.h" /* for alevel_t & demod_get_audio_level() */ @@ -290,14 +286,14 @@ int audio_open (struct audio_s *pa) 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"); + strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in)); } else if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) { A->g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP; /* Supply default port if none specified. */ if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 || strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) { - sprintf (pa->adev[a].adevice_in, "udp:%d", DEFAULT_UDP_AUDIO_PORT); + snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT); } } else { @@ -754,18 +750,6 @@ int audio_get (int a) A = &(adev[a]); -#if defined(DEBUGUDP) || defined(STATISTICS) - - /* Gather numbers for read from UDP stream. */ - /* Gather numbers for read from audio device. */ - -#define duration 100 /* report every 100 seconds. */ - static time_t last_time[MAX_ADEVS]; - time_t this_time[MAX_ADEVS]; - static int sample_count[MAX_ADEVS]; - static int error_count[MAX_ADEVS]; -#endif - switch (A->g_audio_in_type) { /* @@ -791,6 +775,12 @@ int audio_get (int a) // TODO1.2: Need more details. Can we keep going? dw_printf ("Timeout waiting for input from audio device %d.\n", a); + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + return (-1); } } @@ -800,6 +790,11 @@ int audio_get (int a) if (p->dwUser == -1) { waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); p->dwUser = 0; /* Index for next byte. */ + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + p->dwBytesRecorded / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); } if (p->dwUser < p->dwBytesRecorded) { @@ -841,36 +836,20 @@ int audio_get (int a) dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError()); A->stream_len = 0; A->stream_next = 0; + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + 0, + save_audio_config_p->statistics_interval); + return (-1); } -#if DEBUGUDP + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); -// TODO: We should collect audio statistics like this for all types of input. Move to common function. - - if (last_time[a] == 0) { - last_time[a] = time(NULL); - sample_count[a] = 0; - error_count[a] = 0; - } - else { - if (res > 0) { - sample_count[a] += res/2; - } - else { - error_count[a]++; - } - this_time[a] = time(NULL); - if (this_time[a] >= last_time + duration) { - text_color_set(DW_COLOR_DEBUG); - dw_printf ("\ADEVICE%d: Past %d seconds, %d UDP audio samples processed, %d errors.\n\n", - a, duration, sample_count[a], error_count[a]); - last_time[a] = this_time[a]; - sample_count[a] = 0; - error_count[a] = 0; - } - } -#endif A->stream_len = res; A->stream_next = 0; } @@ -892,6 +871,11 @@ int audio_get (int a) dw_printf ("\nEnd of file on stdin. Exiting.\n"); exit (0); } + + audio_stats (a, + save_audio_config_p->adev[a].num_channels, + res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8), + save_audio_config_p->statistics_interval); A->stream_len = res; A->stream_next = 0; diff --git a/ax25_pad.c b/ax25_pad.c index ac7efbf..fe6ebec 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -1,4 +1,3 @@ - // // This file is part of Dire Wolf, an amateur radio packet TNC. // @@ -151,6 +150,7 @@ char *strtok_r(char *str, const char *delim, char **saveptr); #endif +#include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "fcs_calc.h" @@ -232,10 +232,19 @@ static packet_t ax25_new (void) } this_p = calloc(sizeof (struct packet_s), (size_t)1); + + if (this_p == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - can't allocate memory in ax25_new.\n"); + } + + assert (this_p != NULL); + this_p->magic1 = MAGIC; this_p->seq = last_seq_num; this_p->magic2 = MAGIC; this_p->num_addr = (-1); + return (this_p); } @@ -258,6 +267,13 @@ void ax25_delete (packet_t this_p) dw_printf ("ax25_delete(): before free, new=%d, delete=%d\n", new_count, delete_count); #endif + if (this_p == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - NULL pointer passed to ax25_delete.\n"); + return; + } + + delete_count++; #if AX25MEMDEBUG @@ -345,7 +361,7 @@ packet_t ax25_from_text (char *monitor, int strict) /* information field of an AX.25 frame? */ /* Yes, but it would be difficult in the from-text case. */ - strcpy (stuff, monitor); + strlcpy (stuff, monitor, sizeof(stuff)); /* * Translate hexadecimal values like <0xff> to non-printing characters. @@ -379,8 +395,8 @@ packet_t ax25_from_text (char *monitor, int strict) stuff[match[0].rm_so + 5] = '\0'; n = strtol (stuff + match[0].rm_so + 3, &p, 16); stuff[match[0].rm_so] = n; - strcpy (temp, stuff + match[0].rm_eo); - strcpy (stuff + match[0].rm_so + 1, temp); + strlcpy (temp, stuff + match[0].rm_eo, sizeof(temp)); + strlcpy (stuff + match[0].rm_so + 1, temp, sizeof(stuff)-match[0].rm_so-1); } else { keep_going = 0; @@ -520,10 +536,9 @@ packet_t ax25_from_text (char *monitor, int strict) /* * Append the info part. */ - strcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo); + strlcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo, sizeof(this_p->frame_data)-this_p->frame_len); this_p->frame_len += strlen(pinfo); - return (this_p); } @@ -634,6 +649,8 @@ packet_t ax25_dup (packet_t copy_from) this_p = ax25_new (); + assert (this_p != NULL); + save_seq = this_p->seq; memcpy (this_p, copy_from, sizeof (struct packet_s)); @@ -684,11 +701,11 @@ packet_t ax25_dup (packet_t copy_from) int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard) { char *p; - char sstr[4]; + char sstr[8]; /* Should be 1 or 2 digits for SSID. */ int i, j, k; int maxlen; - strcpy (out_addr, ""); + *out_addr = '\0'; *out_ssid = 0; *out_heard = 0; @@ -712,8 +729,8 @@ int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, i } } - strcpy (sstr, ""); j = 0; + sstr[j] = '\0'; if (*p == '-') { for (p++; isalnum(*p); p++) { if (j >= 2) { @@ -1081,6 +1098,8 @@ int ax25_get_num_repeaters (packet_t this_p) * * Outputs: station - String representation of the station, including the SSID. * e.g. "WB2OSZ-15" + * Usually variables will be AX25_MAX_ADDR_LEN bytes + * but 10 would be adequate. * * Bugs: No bounds checking is performed. Be careful. * @@ -1094,7 +1113,7 @@ int ax25_get_num_repeaters (packet_t this_p) void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) { int ssid; - char sstr[4]; + char sstr[8]; /* Should be 1 or 2 digits for SSID. */ int i; assert (this_p->magic1 == MAGIC); @@ -1105,7 +1124,7 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__); dw_printf ("Address index, %d, is less than zero.\n", n); - strcpy (station, "??????"); + strlcpy (station, "??????", 10); return; } @@ -1113,7 +1132,7 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__); dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr); - strcpy (station, "??????"); + strlcpy (station, "??????", 10); return; } @@ -1128,12 +1147,72 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) ssid = ax25_get_ssid (this_p, n); if (ssid != 0) { - sprintf (sstr, "-%d", ssid); - strcat (station, sstr); + snprintf (sstr, sizeof(sstr), "-%d", ssid); + strlcat (station, sstr, 10); } } +/*------------------------------------------------------------------------------ + * + * Name: ax25_get_addr_no_ssid + * + * Purpose: Return specified address WITHOUT any SSID. + * + * Inputs: n - Index of address. Use the symbols + * AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc. + * + * Outputs: station - String representation of the station, WITHOUT the SSID. + * e.g. "WB2OSZ" + * Usually variables will be AX25_MAX_ADDR_LEN bytes + * but 7 would be adequate. + * + * Bugs: No bounds checking is performed. Be careful. + * + * Assumption: ax25_from_text or ax25_from_frame was called first. + * + * Returns: Character string in usual human readable format, + * + * + *------------------------------------------------------------------------------*/ + +void ax25_get_addr_no_ssid (packet_t this_p, int n, char *station) +{ + int ssid; + int i; + + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + + if (n < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error detected in ax25_get_addr_no_ssid, %s, line %d.\n", __FILE__, __LINE__); + dw_printf ("Address index, %d, is less than zero.\n", n); + strlcpy (station, "??????", 7); + return; + } + + if (n >= this_p->num_addr) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error detected in ax25_get_no_with_ssid, %s, line %d.\n", __FILE__, __LINE__); + dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr); + strlcpy (station, "??????", 7); + return; + } + + memset (station, 0, 7); + for (i=0; i<6; i++) { + unsigned char ch; + + ch = (this_p->frame_data[n*7+i] >> 1) & 0x7f; + if (ch <= ' ') break; + station[i] = ch; + } + +} /* end ax25_get_addr_no_ssid */ + + /*------------------------------------------------------------------------------ * * Name: ax25_get_ssid @@ -1490,6 +1569,8 @@ packet_t ax25_get_nextp (packet_t this_p) * *------------------------------------------------------------------*/ +// TODO: max len for result. buffer overflow? + void ax25_format_addrs (packet_t this_p, char *result) { int i; @@ -1591,7 +1672,8 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) * *------------------------------------------------------------------*/ - +// TODO: need someway to ensure caller allocated enough space. +#define DESC_SIZ 16 ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns) { @@ -1600,14 +1682,14 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * assert (this_p->magic1 == MAGIC); assert (this_p->magic2 == MAGIC); - strcpy (desc, "????"); + strlcpy (desc, "????", DESC_SIZ); *pf = -1; *nr = -1; *ns = -1; c = ax25_get_control(this_p); if (c < 0) { - strcpy (desc, "Not AX.25"); + strlcpy (desc, "Not AX.25", DESC_SIZ); return (frame_not_AX25); } @@ -1626,7 +1708,7 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * *pf = (c >> 4) & 1; *nr = (c >> 5) & 7; } - strcpy (desc, "I frame"); + strlcpy (desc, "I frame", DESC_SIZ); return (frame_type_I); } else if ((c & 2) == 0) { @@ -1644,10 +1726,10 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * } 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; + case 0: strlcpy (desc, "S frame RR", DESC_SIZ); return (frame_type_RR); break; + case 1: strlcpy (desc, "S frame RNR", DESC_SIZ); return (frame_type_RNR); break; + case 2: strlcpy (desc, "S frame REJ", DESC_SIZ); return (frame_type_REJ); break; + case 3: strlcpy (desc, "S frame SREJ", DESC_SIZ); return (frame_type_SREJ); break; } } else { @@ -1658,16 +1740,16 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * 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; + case 0x6f: strlcpy (desc, "U frame SABME", DESC_SIZ); return (frame_type_SABME); break; + case 0x2f: strlcpy (desc, "U frame SABM", DESC_SIZ); return (frame_type_SABM); break; + case 0x43: strlcpy (desc, "U frame DISC", DESC_SIZ); return (frame_type_DISC); break; + case 0x0f: strlcpy (desc, "U frame DM", DESC_SIZ); return (frame_type_DM); break; + case 0x63: strlcpy (desc, "U frame UA", DESC_SIZ); return (frame_type_UA); break; + case 0x87: strlcpy (desc, "U frame FRMR", DESC_SIZ); return (frame_type_FRMR); break; + case 0x03: strlcpy (desc, "U frame UI", DESC_SIZ); return (frame_type_UI); break; + case 0xaf: strlcpy (desc, "U frame XID", DESC_SIZ); return (frame_type_XID); break; + case 0xe3: strlcpy (desc, "U frame TEST", DESC_SIZ); return (frame_type_TEST); break; + default: strlcpy (desc, "U frame ???", DESC_SIZ); return (frame_type_U); break; } } @@ -1740,26 +1822,28 @@ static void ctrl_to_text (int c, char *out) /* Text description of protocol id octet. */ -static void pid_to_text (int p, char *out) +#define PID_TEXT_SIZE 80 + +static void pid_to_text (int p, char out[PID_TEXT_SIZE]) { - if ((p & 0x30) == 0x10) { sprintf (out, "AX.25 layer 3 implemented."); } - else if ((p & 0x30) == 0x20) { sprintf (out, "AX.25 layer 3 implemented."); } - else if (p == 0x01) { sprintf (out, "ISO 8208/CCITT X.25 PLP"); } - else if (p == 0x06) { sprintf (out, "Compressed TCP/IP packet. Van Jacobson (RFC 1144)"); } - else if (p == 0x07) { sprintf (out, "Uncompressed TCP/IP packet. Van Jacobson (RFC 1144)"); } - else if (p == 0x08) { sprintf (out, "Segmentation fragment"); } - else if (p == 0xC3) { sprintf (out, "TEXNET datagram protocol"); } - else if (p == 0xC4) { sprintf (out, "Link Quality Protocol"); } - else if (p == 0xCA) { sprintf (out, "Appletalk"); } - else if (p == 0xCB) { sprintf (out, "Appletalk ARP"); } - else if (p == 0xCC) { sprintf (out, "ARPA Internet Protocol"); } - else if (p == 0xCD) { sprintf (out, "ARPA Address resolution"); } - else if (p == 0xCE) { sprintf (out, "FlexNet"); } - else if (p == 0xCF) { sprintf (out, "NET/ROM"); } - else if (p == 0xF0) { sprintf (out, "No layer 3 protocol implemented."); } - else if (p == 0xFF) { sprintf (out, "Escape character. Next octet contains more Level 3 protocol information."); } - else { sprintf (out, "Unknown protocol id = 0x%02x", p); } + if ((p & 0x30) == 0x10) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); } + else if ((p & 0x30) == 0x20) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); } + else if (p == 0x01) { snprintf (out, PID_TEXT_SIZE, "ISO 8208/CCITT X.25 PLP"); } + else if (p == 0x06) { snprintf (out, PID_TEXT_SIZE, "Compressed TCP/IP packet. Van Jacobson (RFC 1144)"); } + else if (p == 0x07) { snprintf (out, PID_TEXT_SIZE, "Uncompressed TCP/IP packet. Van Jacobson (RFC 1144)"); } + else if (p == 0x08) { snprintf (out, PID_TEXT_SIZE, "Segmentation fragment"); } + else if (p == 0xC3) { snprintf (out, PID_TEXT_SIZE, "TEXNET datagram protocol"); } + else if (p == 0xC4) { snprintf (out, PID_TEXT_SIZE, "Link Quality Protocol"); } + else if (p == 0xCA) { snprintf (out, PID_TEXT_SIZE, "Appletalk"); } + else if (p == 0xCB) { snprintf (out, PID_TEXT_SIZE, "Appletalk ARP"); } + else if (p == 0xCC) { snprintf (out, PID_TEXT_SIZE, "ARPA Internet Protocol"); } + else if (p == 0xCD) { snprintf (out, PID_TEXT_SIZE, "ARPA Address resolution"); } + else if (p == 0xCE) { snprintf (out, PID_TEXT_SIZE, "FlexNet"); } + else if (p == 0xCF) { snprintf (out, PID_TEXT_SIZE, "NET/ROM"); } + else if (p == 0xF0) { snprintf (out, PID_TEXT_SIZE, "No layer 3 protocol implemented."); } + else if (p == 0xFF) { snprintf (out, PID_TEXT_SIZE, "Escape character. Next octet contains more Level 3 protocol information."); } + else { snprintf (out, PID_TEXT_SIZE, "Unknown protocol id = 0x%02x", p); } } @@ -1785,17 +1869,17 @@ void ax25_hex_dump (packet_t this_p) if ( (c & 0x01) == 0 || /* I xxxx xxx0 */ c == 0x03 || c == 0x13) { /* UI 000x 0011 */ - char p_text[100]; + char pid_text[PID_TEXT_SIZE]; - pid_to_text (p, p_text); + pid_to_text (p, pid_text); - strcat (cp_text, ", "); - strcat (cp_text, p_text); + strlcat (cp_text, ", ", sizeof(cp_text)); + strlcat (cp_text, pid_text, sizeof(cp_text)); } - sprintf (l_text, ", length = %d", flen); - strcat (cp_text, l_text); + snprintf (l_text, sizeof(l_text), ", length = %d", flen); + strlcat (cp_text, l_text, sizeof(cp_text)); dw_printf ("%s\n", cp_text); } @@ -2097,7 +2181,7 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) /* UTF-8 does not use fe and ff except in a possible */ /* "Byte Order Mark" (BOM) at the beginning. */ - sprintf (safe_str + safe_len, "<0x%02x>", ch); + snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch); safe_len += 6; } else { @@ -2138,6 +2222,8 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) * Comma is to be avoided because one place this * ends up is in a CSV format file. * + * size should be AX25_ALEVEL_TO_TEXT_SIZE. + * * Returns: True if something to print. (currently if alevel.original >= 0) * False if not. * @@ -2153,25 +2239,30 @@ void ax25_safe_print (char *pstr, int len, int ascii_only) *------------------------------------------------------------------*/ -int ax25_alevel_to_text (alevel_t alevel, char *text) +int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]) { if (alevel.rec < 0) { - strcpy (text, ""); + strlcpy (text, "", AX25_ALEVEL_TO_TEXT_SIZE); return (0); } // TODO1.2: haven't thought much about non-AFSK cases yet. // What should we do for 9600 baud? -// Possibility: low/high tone for DTMF??? + +// For DTMF omit the two extra numbers. if (alevel.mark >= 0 && alevel.space < 0) { /* baseband */ - sprintf (text, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); + snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); + } + else if (alevel.mark == -2 && alevel.space == -2) { /* DTMF */ + + snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); } else { /* AFSK */ - //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); + //snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d:%d(%d/%d=%05.3f=)", alevel.original, alevel.rec, alevel.mark, alevel.space, alevel.ms_ratio); + snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%d/%d)", alevel.rec, alevel.mark, alevel.space); } return (1); diff --git a/ax25_pad.h b/ax25_pad.h index 0e5b133..d133808 100644 --- a/ax25_pad.h +++ b/ax25_pad.h @@ -318,7 +318,8 @@ extern void ax25_remove_addr (packet_t this_p, int n); extern int ax25_get_num_addr (packet_t pp); extern int ax25_get_num_repeaters (packet_t this_p); -extern void ax25_get_addr_with_ssid (packet_t pp, int n, char *); +extern void ax25_get_addr_with_ssid (packet_t pp, int n, char *station); +extern void ax25_get_addr_no_ssid (packet_t pp, int n, char *station); extern int ax25_get_ssid (packet_t pp, int n); extern void ax25_set_ssid (packet_t this_p, int n, int ssid); @@ -360,7 +361,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); +#define AX25_ALEVEL_TO_TEXT_SIZE 32 // overkill but safe. +extern int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]); #endif /* AX25_PAD_H */ diff --git a/beacon.c b/beacon.c index aebcf1b..1583723 100644 --- a/beacon.c +++ b/beacon.c @@ -59,6 +59,7 @@ #include "dwgps.h" #include "log.h" #include "dlq.h" +#include "aprs_tt.h" // for dw_run_cmd - should relocate someday. @@ -205,11 +206,11 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct case BEACON_CUSTOM: - /* INFO is required. */ + /* INFO or INFOCMD is required. */ - if (g_misc_config_p->beacon[j].custom_info == NULL) { + if (g_misc_config_p->beacon[j].custom_info == NULL && g_misc_config_p->beacon[j].custom_infocmd == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file, line %d: INFO is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno); + dw_printf ("Config file, line %d: INFO or INFOCMD is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; continue; } @@ -578,6 +579,8 @@ static void * beacon_thread (void *arg) char mycall[AX25_MAX_ADDR_LEN]; int alt_ft; + char super_comment[AX25_MAX_INFO_LEN]; // Fixed part + any dynamic part. + /* * Obtain source call for the beacon. * This could potentially be different on different channels. @@ -588,11 +591,11 @@ static void * beacon_thread (void *arg) * Version 1.1 - channel should now be 0 for IGate. * Type of destination is encoded separately. */ - strcpy (mycall, "NOCALL"); + strlcpy (mycall, "NOCALL", sizeof(mycall)); assert (g_misc_config_p->beacon[j].sendto_chan >= 0); - strcpy (mycall, g_modem_config_p->achan[g_misc_config_p->beacon[j].sendto_chan].mycall); + strlcpy (mycall, g_modem_config_p->achan[g_misc_config_p->beacon[j].sendto_chan].mycall, sizeof(mycall)); if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { text_color_set(DW_COLOR_ERROR); @@ -606,22 +609,52 @@ static void * beacon_thread (void *arg) * src > dest [ , via ] */ - strcpy (beacon_text, mycall); - strcat (beacon_text, ">"); + strlcpy (beacon_text, mycall, sizeof(beacon_text)); + strlcat (beacon_text, ">", sizeof(beacon_text)); if (g_misc_config_p->beacon[j].dest != NULL) { - strcat (beacon_text, g_misc_config_p->beacon[j].dest); + strlcat (beacon_text, g_misc_config_p->beacon[j].dest, sizeof(beacon_text)); } else { - sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION); - strcat (beacon_text, stemp); + snprintf (stemp, sizeof(stemp), "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION); + strlcat (beacon_text, stemp, sizeof(beacon_text)); } if (g_misc_config_p->beacon[j].via != NULL) { - strcat (beacon_text, ","); - strcat (beacon_text, g_misc_config_p->beacon[j].via); + strlcat (beacon_text, ",", sizeof(beacon_text)); + strlcat (beacon_text, g_misc_config_p->beacon[j].via, sizeof(beacon_text)); } - strcat (beacon_text, ":"); + strlcat (beacon_text, ":", sizeof(beacon_text)); + + +/* + * If the COMMENTCMD option was specified, run specified command to get variable part. + * Result is any fixed part followed by any variable part. + */ + +// TODO: test & document. + + strlcpy (super_comment, "", sizeof(super_comment)); + if (g_misc_config_p->beacon[j].comment != NULL) { + strlcpy (super_comment, g_misc_config_p->beacon[j].comment, sizeof(super_comment)); + } + + if (g_misc_config_p->beacon[j].commentcmd != NULL) { + char var_comment[AX25_MAX_INFO_LEN]; + int k; + + /* Run given command to get variable part of comment. */ + + k = dw_run_cmd (g_misc_config_p->beacon[j].commentcmd, 2, var_comment, (int)sizeof(var_comment)); + if (k > 0) { + strlcat (super_comment, var_comment, sizeof(super_comment)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", g_misc_config_p->beacon[j].lineno); + } + } + /* * Add the info part depending on beacon type. @@ -638,9 +671,9 @@ static void * beacon_thread (void *arg) g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir, 0, 0, /* course, speed */ g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, - g_misc_config_p->beacon[j].comment, - info); - strcat (beacon_text, info); + super_comment, + info, sizeof(info)); + strlcat (beacon_text, info, sizeof(beacon_text)); g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; break; @@ -650,9 +683,9 @@ static void * beacon_thread (void *arg) g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol, g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir, 0, 0, /* course, speed */ - g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, g_misc_config_p->beacon[j].comment, - info); - strcat (beacon_text, info); + g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, super_comment, + info, sizeof(info)); + strlcat (beacon_text, info, sizeof(beacon_text)); g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; break; @@ -673,9 +706,9 @@ static void * beacon_thread (void *arg) g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir, coarse, (int)roundf(my_speed_knots), g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, - g_misc_config_p->beacon[j].comment, - info); - strcat (beacon_text, info); + super_comment, + info, sizeof(info)); + strlcat (beacon_text, info, sizeof(beacon_text)); /* Remember most recent tracker beacon. */ @@ -707,7 +740,7 @@ static void * beacon_thread (void *arg) A.g_tone = G_UNKNOWN; A.g_dcs = G_UNKNOWN; - strcpy (A.g_src, mycall); + strlcpy (A.g_src, mycall, sizeof(A.g_src)); A.g_symbol_table = g_misc_config_p->beacon[j].symtab; A.g_symbol_code = g_misc_config_p->beacon[j].symbol; A.g_lat = my_lat; @@ -730,12 +763,32 @@ static void * beacon_thread (void *arg) case BEACON_CUSTOM: if (g_misc_config_p->beacon[j].custom_info != NULL) { - strcat (beacon_text, g_misc_config_p->beacon[j].custom_info); + + /* Fixed handcrafted text. */ + + strlcat (beacon_text, g_misc_config_p->beacon[j].custom_info, sizeof(beacon_text)); + } + else if (g_misc_config_p->beacon[j].custom_infocmd != NULL) { + char info_part[AX25_MAX_INFO_LEN]; + char *p; + int k; + + /* Run given command to obtain the info part for packet. */ + + k = dw_run_cmd (g_misc_config_p->beacon[j].custom_infocmd, 2, info_part, (int)sizeof(info_part)); + if (k > 0) { + strlcat (beacon_text, info_part, sizeof(beacon_text)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", g_misc_config_p->beacon[j].lineno); + strlcpy (beacon_text, "", sizeof(beacon_text)); // abort! + } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__); - continue; + strlcpy (beacon_text, "", sizeof(beacon_text)); // abort! } g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every; break; @@ -749,6 +802,10 @@ static void * beacon_thread (void *arg) /* * Parse monitor format into form for transmission. */ + if (strlen(beacon_text) == 0) { + continue; + } + pp = ax25_from_text (beacon_text, strict); if (pp != NULL) { diff --git a/config.c b/config.c index d0ebee6..03a56ef 100644 --- a/config.c +++ b/config.c @@ -17,6 +17,8 @@ // along with this program. If not, see . // +#define CONFIG_C 1 + // #define DEBUG 1 @@ -41,7 +43,7 @@ #include #if __WIN32__ -#include "pthreads/pthread.h" +//#include "pthreads/pthread.h" #else #include #endif @@ -56,6 +58,7 @@ #include "latlong.h" #include "symbols.h" #include "xmit.h" +#include "tt_text.h" // geotranz @@ -204,7 +207,7 @@ static double parse_ll (char *str, enum parse_ll_which_e which, int line) /* * Remove any negative sign. */ - strcpy (stemp, str); + strlcpy (stemp, str, sizeof(stemp)); sign = +1; if (stemp[0] == '-') { sign = -1; @@ -294,7 +297,9 @@ static double parse_ll (char *str, enum parse_ll_which_e which, int line) * * Inputs: szone - String like [-]number[letter] * - * Output: hemi - Hemisphere, 'N' or 'S'. + * Outputs: latband - Latitude band if specified, otherwise space or -. + * + * hemi - Hemisphere, always one of 'N' or 'S'. * * Returns: Zone as number. * Type is long because Convert_UTM_To_Geodetic expects that. @@ -314,12 +319,13 @@ static double parse_ll (char *str, enum parse_ll_which_e which, int line) * *----------------------------------------------------------------*/ -long parse_utm_zone (char *szone, char *hemi) +long parse_utm_zone (char *szone, char *latband, char *hemi) { long lzone; char *zlet; + *latband = ' '; *hemi = 'N'; /* default */ lzone = strtol(szone, &zlet, 10); @@ -329,6 +335,7 @@ long parse_utm_zone (char *szone, char *hemi) /* Allow negative number to mean south. */ if (lzone < 0) { + *latband = '-'; *hemi = 'S'; lzone = (- lzone); } @@ -337,6 +344,7 @@ long parse_utm_zone (char *szone, char *hemi) if (islower (*zlet)) { *zlet = toupper(*zlet); } + *latband = *zlet; if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) { if (*zlet < 'N') { *hemi = 'S'; @@ -356,7 +364,8 @@ long parse_utm_zone (char *szone, char *hemi) } return (lzone); -} + +} /* end parse_utm_zone */ @@ -382,7 +391,7 @@ main () parse_ll ("12.5S", LON); // error parse_ll ("12^30", LAT); - parse_ll ("12°30", LAT); + parse_ll ("12\xb030", LAT); // ISO Latin-1 degree symbol parse_ll ("91", LAT); // out of range parse_ll ("91", LON); @@ -444,6 +453,120 @@ static int parse_interval (char *str, int line) +/*------------------------------------------------------------------- + * + * Name: split + * + * Purpose: Separate a line into command and parameters. + * + * Inputs: string - Complete command line to start process. + * NULL for subsequent calls. + * + * rest_of_line - Caller wants remainder of line, not just + * the next parameter. + * + * Returns: Pointer to next part with any quoting removed. + * + * Description: the configuration file started out very simple and strtok + * was used to split up the lines. As more complicated options + * were added, there were several different situations where + * parameter values might contain spaces. These were handled + * inconsistently in different places. In version 1.3, we now + * treat them consistently in one place. + * + * + *--------------------------------------------------------------------*/ + +#define MAXCMDLEN 256 + + +static char *split (char *string, int rest_of_line) +{ + static char cmd[MAXCMDLEN]; + static char token[MAXCMDLEN]; + static char *nextp = NULL; + static char *c; // current position in cmd. + char *s, *t; + int in_quotes; + +/* + * If string is provided, make a copy. + * Drop any CRLF at the end. + * Change any tabs to spaces so we don't have to check for it later. + */ + if (string != NULL) { + + // dw_printf("split in: '%s'\n", string); + + c = cmd; + for (s = string; *s != '\0'; s++) { + if (*s == '\t') { + *c++ = ' '; + } + else if (*s == '\r' || *s == '\n') { + ; + } + else { + *c++ = *s; + } + } + *c = '\0'; + c = cmd; + } + +/* + * Get next part, separated by whitespace, keeping spaces within quotes. + * Quotation marks inside need to be doubled. + */ + + while (*c == ' ') { + c++; + }; + + t = token; + in_quotes = 0; + for ( ; *c != '\0'; c++) { + + if (*c == '"') { + if (in_quotes) { + if (c[1] == '"') { + *t++ = *c++; + } + else { + in_quotes = 0; + } + } + else { + in_quotes = 1; + } + } + else if (*c == ' ') { + if (in_quotes || rest_of_line) { + *t++ = *c; + } + else { + break; + } + } + else { + *t++ = *c; + } + } + *t = '\0'; + + // dw_printf("split out: '%s'\n", token); + + t = token; + if (*t == '\0') { + return (NULL); + } + + return (t); + +} /* end split */ + + + /*------------------------------------------------------------------- * * Name: config_init @@ -482,13 +605,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, struct misc_config_s *p_misc_config) { FILE *fp; - char stuff[256]; - //char *p; - //int c, p; - //int err; + char filepath[128]; + char stuff[MAXCMDLEN]; int line; int channel; int adevice; + int m; #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -506,8 +628,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, for (adevice=0; adeviceadev[adevice].adevice_in, DEFAULT_ADEVICE); - strcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE); + strlcpy (p_audio_config->adev[adevice].adevice_in, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_in)); + strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out)); p_audio_config->adev[adevice].defined = 0; p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ @@ -529,7 +651,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, 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, ""); + strlcpy (p_audio_config->achan[channel].profiles, "", sizeof(p_audio_config->achan[channel].profiles)); p_audio_config->achan[channel].num_freq = 1; p_audio_config->achan[channel].offset = 0; @@ -540,7 +662,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, 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, ""); + strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "", sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].ptt_gpio = 0; @@ -563,7 +685,6 @@ void config_init (char *fname, struct audio_s *p_audio_config, memset (p_digi_config, 0, sizeof(struct digi_config_s)); - //p_digi_config->num_chans = p_audio_config->adev[0].num_channels; // TODO: rethink for > 2 case. p_digi_config->dedupe_time = DEFAULT_DEDUPE; memset (p_tt_config, 0, sizeof(struct tt_config_s)); @@ -575,9 +696,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Retention time and decay algorithm from 13 Feb 13 version of */ /* http://www.aprs.org/aprstt/aprstt-coding24.txt */ + /* Reduced by transmit count by one. An 8 minute delay in between transmissions seems awful long. */ p_tt_config->retain_time = 80 * 60; - p_tt_config->num_xmits = 7; + p_tt_config->num_xmits = 6; assert (p_tt_config->num_xmits <= TT_MAX_XMITS); p_tt_config->xmit_delay[0] = 3; /* Before initial transmission. */ p_tt_config->xmit_delay[1] = 16; @@ -585,7 +707,25 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_tt_config->xmit_delay[3] = 64; p_tt_config->xmit_delay[4] = 2 * 60; p_tt_config->xmit_delay[5] = 4 * 60; - p_tt_config->xmit_delay[6] = 8 * 60; + p_tt_config->xmit_delay[6] = 8 * 60; // not currently used. + + strlcpy (p_tt_config->status[0], "", sizeof(p_tt_config->status[0])); + strlcpy (p_tt_config->status[1], "/off duty", sizeof(p_tt_config->status[1])); + strlcpy (p_tt_config->status[2], "/enroute", sizeof(p_tt_config->status[2])); + strlcpy (p_tt_config->status[3], "/in service", sizeof(p_tt_config->status[3])); + strlcpy (p_tt_config->status[4], "/returning", sizeof(p_tt_config->status[4])); + strlcpy (p_tt_config->status[5], "/committed", sizeof(p_tt_config->status[5])); + strlcpy (p_tt_config->status[6], "/special", sizeof(p_tt_config->status[6])); + strlcpy (p_tt_config->status[7], "/priority", sizeof(p_tt_config->status[7])); + strlcpy (p_tt_config->status[8], "/emergency", sizeof(p_tt_config->status[8])); + strlcpy (p_tt_config->status[9], "/custom 1", sizeof(p_tt_config->status[9])); + + for (m = 0; m < TT_ERROR_MAXP1; m++) { + strlcpy (p_tt_config->response[m].method, "MORSE", sizeof(p_tt_config->response[m].method)); + strlcpy (p_tt_config->response[m].mtext, "?", sizeof(p_tt_config->response[m].mtext)); + } + strlcpy (p_tt_config->response[TT_ERROR_OK].mtext, "R", sizeof(p_tt_config->response[TT_ERROR_OK].mtext)); + memset (p_misc_config, 0, sizeof(struct misc_config_s)); p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT; @@ -614,10 +754,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Ideally we'd like to figure out if com0com is installed */ /* and automatically enable this. */ - //strcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM); - strcpy (p_misc_config->nullmodem, ""); - strcpy (p_misc_config->nmea_port, ""); - strcpy (p_misc_config->logdir, ""); + //strlcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM, sizeof(p_misc_config->nullmodem)); + strlcpy (p_misc_config->nullmodem, "", sizeof(p_misc_config->nullmodem)); + strlcpy (p_misc_config->nmea_port, "", sizeof(p_misc_config->nmea_port)); + strlcpy (p_misc_config->logdir, "", sizeof(p_misc_config->logdir)); /* @@ -626,34 +766,51 @@ void config_init (char *fname, struct audio_s *p_audio_config, * Windows: File must be in current working directory. * * Linux: Search current directory then home directory. + * + * Future possibility - Could also search home directory + * for Windows by combinting two variables: + * HOMEDRIVE=C: + * HOMEPATH=\Users\John + * + * It's not clear if this always points to same location: + * USERPROFILE=C:\Users\John */ channel = 0; adevice = 0; - fp = fopen (fname, "r"); +// TODO: Would be better to have a search list and loop thru it. + + strlcpy(filepath, fname, sizeof(filepath)); + + fp = fopen (filepath, "r"); + #ifndef __WIN32__ if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) { /* Failed to open the default location. Try home dir. */ char *p; + + strlcpy (filepath, "", sizeof(filepath)); + p = getenv("HOME"); if (p != NULL) { - strcpy (stuff, p); - strcat (stuff, "/direwolf.conf"); - fp = fopen (stuff, "r"); + strlcpy (filepath, p, sizeof(filepath)); + strlcat (filepath, "/direwolf.conf", sizeof(filepath)); + fp = fopen (filepath, "r"); } } #endif if (fp == NULL) { // TODO: not exactly right for all situations. text_color_set(DW_COLOR_ERROR); - dw_printf ("ERROR - Could not open config file %s\n", fname); + dw_printf ("ERROR - Could not open config file %s\n", filepath); dw_printf ("Try using -c command line option for alternate location.\n"); return; } + dw_printf ("\nReading config file %s\n", filepath); line = 0; while (fgets(stuff, sizeof(stuff), fp) != NULL) { @@ -661,8 +818,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, line++; + t = split(stuff,0); - t = strtok (stuff, " ,\t\n\r"); if (t == NULL) { continue; } @@ -678,7 +835,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ /* - * ADEVICE[n] - Name of input sound device, and optionally output, if different. + * ADEVICE[n] - Name of input sound device, and optionally output, if different. + * + * ADEVICE plughw:1,0 -- same for in and out. + * ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair. + * */ /* Note that ALSA name can contain comma such as hw:1,0 */ @@ -696,7 +857,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - t = strtok (NULL, " \t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line); @@ -711,19 +872,96 @@ void config_init (char *fname, struct audio_s *p_audio_config, 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"); + t = split(NULL,0); if (t != NULL) { strncpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)-1); } } + +/* + * PAIDEVICE[n] input-device + * PAODEVICE[n] output-device + * + * This was submitted by KK5VD for the Mac OS X version. (__APPLE__) + * + * It looks like device names can contain spaces making it a little + * more difficult to put two names on the same line unless we come up with + * some other delimiter between them or a quoting scheme to handle + * embedded spaces in a name. + * + * It concerns me that we could have one defined without the other + * if we don't put in more error checking later. + * + * version 1.3 dev snapshot C: + * + * We now have a general quoting scheme so the original ADEVICE can handle this. + * These options will probably be removed before general 1.3 release. + */ + + else if (strcasecmp(t, "PAIDEVICE") == 0) { + adevice = 0; + if (isdigit(t[9])) { + adevice = t[9] - '0'; + } + + if (adevice < 0 || adevice >= MAX_ADEVS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line); + adevice = 0; + continue; + } + + t = split(NULL,1); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line); + continue; + } + + p_audio_config->adev[adevice].defined = 1; + + /* First channel of device is valid. */ + p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1; + + strncpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)-1); + } + else if (strcasecmp(t, "PAODEVICE") == 0) { + adevice = 0; + if (isdigit(t[9])) { + adevice = t[9] - '0'; + } + + if (adevice < 0 || adevice >= MAX_ADEVS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line); + adevice = 0; + continue; + } + + t = split(NULL,1); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line); + continue; + } + + p_audio_config->adev[adevice].defined = 1; + + /* First channel of device is valid. */ + p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1; + + strncpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)-1); + } + + /* * ARATE - Audio samples per second, 11025, 22050, 44100, etc. */ else if (strcasecmp(t, "ARATE") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing audio sample rate for ARATE command.\n", line); @@ -746,7 +984,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "ACHANNELS") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing number of audio channels for ACHANNELS command.\n", line); @@ -779,7 +1017,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "CHANNEL") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing channel number for CHANNEL command.\n", line); @@ -814,7 +1052,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, * MYCALL station */ else if (strcasecmp(t, "mycall") == 0) { - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing value for MYCALL command on line %d.\n", line); @@ -843,7 +1081,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, *p = toupper(*p); /* silently force upper case. */ } } - // TODO: additional checks if valid + // TODO: additional checks if valid. } } } @@ -868,7 +1106,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "MODEM") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line); @@ -911,7 +1149,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Get mark frequency. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { /* all done. */ continue; @@ -939,7 +1177,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Get space frequency */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing tone frequency for space.\n", line); @@ -975,7 +1213,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* New feature in 0.9 - Optional filter profile(s). */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { /* Look for some combination of letter(s) and + */ @@ -994,7 +1232,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } strncpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles)); - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (strlen(p_audio_config->achan[channel].profiles) > 1 && t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Can't combine multiple demodulator types and multiple frequencies.\n", line); @@ -1014,7 +1252,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } p_audio_config->achan[channel].num_freq = n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { n = atoi(t); if (n < 5 || n > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) { @@ -1037,7 +1275,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } else { -/* New style. */ +/* New style in version 1.2. */ while (t != NULL) { char *s; @@ -1110,7 +1348,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t); } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); } /* A later place catches disallowed combination of + and @. */ @@ -1121,94 +1359,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } -/* - * (deprecated) HBAUD - Set data bits per second. Standard values are 300 & 1200 for AFSK - * and 9600 for baseband with scrambling. - */ -#if 0 - else if (strcasecmp(t, "HBAUD") == 0) { - int n; - t = strtok (NULL, " ,\t\n\r"); - if (t == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Missing date transmission rate for HBAUD command.\n", line); - continue; - } - n = atoi(t); - if (n >= 100 && n <= 10000) { - p_audio_config->achan[channel].baud = n; - if (n != 300 && n != 1200 && n != 9600) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Warning: Non-standard baud rate. Are you sure?\n", line); - } - if (n == 9600) { - /* TODO: should be separate option to keep it more general. */ - //text_color_set(DW_COLOR_ERROR); - //dw_printf ("Line %d: Note: Using scrambled baseband for 9600 baud.\n", line); - p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; - } - } - else { - p_audio_config->achan[channel].baud = DEFAULT_BAUD; - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", - line, p_audio_config->achan[channel].baud); - } - } -#endif - -/* - * (deprecated) MARK - Mark tone frequency. - */ - -#if 0 - else if (strcasecmp(t, "MARK") == 0) { - int n; - t = strtok (NULL, " ,\t\n\r"); - if (t == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Missing tone frequency for MARK command.\n", line); - continue; - } - n = atoi(t); - if (n >= 300 && n <= 3000) { - p_audio_config->achan[channel].mark_freq = n; - } - else { - p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", - line, p_audio_config->achan[channel].mark_freq); - } - } -#endif - -/* - * (deprecated) SPACE - Space tone frequency. - */ - -#if 0 - else if (strcasecmp(t, "SPACE") == 0) { - int n; - t = strtok (NULL, " ,\t\n\r"); - if (t == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Missing tone frequency for SPACE command.\n", line); - continue; - } - n = atoi(t); - if (n >= 300 && n <= 3000) { - p_audio_config->achan[channel].space_freq = n; - } - else { - p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", - line, p_audio_config->achan[channel].space_freq); - } - } -#endif /* * DTMF - Enable DTMF decoder. @@ -1234,7 +1385,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "FIX_BITS") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for FIX_BITS command.\n", line); @@ -1251,7 +1402,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, line, p_audio_config->achan[channel].fix_bits); } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); while (t != NULL) { // If more than one sanity test, we silently take the last one. @@ -1272,7 +1423,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, 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"); + t = split(NULL,0); } } @@ -1295,18 +1446,18 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (strcasecmp(t, "PTT") == 0) { ot = OCTYPE_PTT; - strcpy (otname, "PTT"); + strlcpy (otname, "PTT", sizeof(otname)); } else if (strcasecmp(t, "DCD") == 0) { ot = OCTYPE_DCD; - strcpy (otname, "DCD"); + strlcpy (otname, "DCD", sizeof(otname)); } else { ot = OCTYPE_FUTURE; - strcpy (otname, "FUTURE"); + strlcpy (otname, "FUTURE", sizeof(otname)); } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing serial port name for %s command.\n", @@ -1322,7 +1473,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, otname); #else - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, otname); @@ -1346,7 +1497,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing LPT bit number for %s.\n", line, otname); @@ -1371,9 +1522,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* serial port case. */ - strncpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); + strlcpy (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"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n", @@ -1412,7 +1563,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Some interfaces want the two control lines driven with opposite polarity. */ /* e.g. PTT COM1 RTS -DTR */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { if (strcasecmp(t, "rts") == 0) { @@ -1457,7 +1608,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "DWAIT") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing delay time for DWAIT command.\n", line); @@ -1481,7 +1632,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "SLOTTIME") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing delay time for SLOTTIME command.\n", line); @@ -1505,7 +1656,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "PERSIST") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing probability for PERSIST command.\n", line); @@ -1529,7 +1680,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "TXDELAY") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for TXDELAY command.\n", line); @@ -1553,7 +1704,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "TXTAIL") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for TXTAIL command.\n", line); @@ -1579,7 +1730,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "SPEECH") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing script for Text-to-Speech function.\n", line); @@ -1589,7 +1740,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* See if we can run it. */ if (xmit_speak_it(t, -1, " ") == 0) { - strcpy (p_audio_config->tts_script, t); + if (strlcpy (p_audio_config->tts_script, t, sizeof(p_audio_config->tts_script)) >= sizeof(p_audio_config->tts_script)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Script for text-to-speech function is too long.\n", line); + } } else { text_color_set(DW_COLOR_ERROR); @@ -1612,7 +1766,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, char message[100]; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); @@ -1632,7 +1786,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); @@ -1652,7 +1806,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing alias pattern on line %d.\n", line); @@ -1667,7 +1821,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing wide pattern on line %d.\n", line); @@ -1685,23 +1839,23 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_digi_config->enabled[from_chan][to_chan] = 1; p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { if (strcasecmp(t, "OFF") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); } else if (strcasecmp(t, "DROP") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); } else if (strcasecmp(t, "MARK") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); } else if (strcasecmp(t, "TRACE") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); } } @@ -1717,7 +1871,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "DEDUPE") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for DEDUPE command.\n", line); @@ -1745,7 +1899,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, char message[100]; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); @@ -1765,7 +1919,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); @@ -1807,7 +1961,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, char message[100]; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); @@ -1833,7 +1987,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); @@ -1858,7 +2012,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } - t = strtok (NULL, "\n\r"); /* Take rest of line including spaces. */ + t = split(NULL,1); /* Take rest of line including spaces. */ if (t == NULL) { t = " "; /* Empty means permit nothing. */ @@ -1883,7 +2037,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "TTCORRAL") == 0) { //int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTCORRAL command.\n", line); @@ -1891,7 +2045,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } p_tt_config->corral_lat = parse_ll(t,LAT,line); - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line); @@ -1899,7 +2053,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } p_tt_config->corral_lon = parse_ll(t,LON,line); - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line); @@ -1930,6 +2084,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); + // Should make this a function/macro instead of repeating code. /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; @@ -1940,19 +2095,19 @@ void config_init (char *fname, struct audio_s *p_audio_config, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_POINT; - strcpy(tl->pattern, ""); + strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->point.lat = 0; tl->point.lon = 0; /* Pattern: B and digits */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line); continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); @@ -1967,7 +2122,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Latitude */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line); @@ -1977,7 +2132,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Longitude */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTPOINT command.\n", line); @@ -2018,20 +2173,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_VECTOR; - strcpy(tl->pattern, ""); + strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->vector.lat = 0; tl->vector.lon = 0; tl->vector.scale = 1; /* Pattern: B5bbbd... */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line); continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); @@ -2050,7 +2205,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Latitude */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line); @@ -2060,7 +2215,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Longitude */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTVECTOR command.\n", line); @@ -2070,7 +2225,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Longitude */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing scale for TTVECTOR command.\n", line); @@ -2080,7 +2235,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Unit. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing unit for TTVECTOR command.\n", line); @@ -2132,7 +2287,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_GRID; - strcpy(tl->pattern, ""); + strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->grid.lat0 = 0; tl->grid.lon0 = 0; tl->grid.lat9 = 0; @@ -2140,13 +2295,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Pattern: B [digit] x... y... */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line); continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); @@ -2161,7 +2316,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Minimum Latitude - all zeros in received data */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line); @@ -2171,7 +2326,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Minimum Longitude - all zeros in received data */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line); @@ -2181,7 +2336,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Maximum Latitude - all nines in received data */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line); @@ -2191,7 +2346,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Maximum Longitude - all nines in received data */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line); @@ -2235,7 +2390,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_UTM; - strcpy(tl->pattern, ""); + strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->utm.lzone = 0; tl->utm.scale = 1; tl->utm.x_offset = 0; @@ -2243,14 +2398,14 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Pattern: B [digit] x... y... */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line); p_tt_config->ttloc_len--; continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); @@ -2268,7 +2423,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Zone 1 - 60 and optional latitudinal letter. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing zone for TTUTM command.\n", line); @@ -2276,25 +2431,25 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - tl->utm.lzone = parse_utm_zone (t, &(tl->utm.hemi)); + tl->utm.lzone = parse_utm_zone (t, &(tl->utm.latband), &(tl->utm.hemi)); /* Optional scale. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { tl->utm.scale = atof(t); /* Optional x offset. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { tl->utm.x_offset = atof(t); /* Optional y offset. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { tl->utm.y_offset = atof(t); @@ -2357,19 +2512,19 @@ void config_init (char *fname, struct audio_s *p_audio_config, else { tl->type = TTLOC_USNG; } - strcpy(tl->pattern, ""); - strcpy(tl->mgrs.zone, ""); + strlcpy(tl->pattern, "", sizeof(tl->pattern)); + strlcpy(tl->mgrs.zone, "", sizeof(tl->mgrs.zone)); /* Pattern: B [digit] x... y... */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTUSNG/TTMGRS command.\n", line); p_tt_config->ttloc_len--; continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); @@ -2397,15 +2552,14 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Zone 1 - 60 and optional latitudinal letter. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing zone & square for TTUSNG/TTMGRS command.\n", line); p_tt_config->ttloc_len--; continue; } - memset (tl->mgrs.zone, 0, sizeof (tl->mgrs.zone)); - strncpy (tl->mgrs.zone, t, sizeof (tl->mgrs.zone) - 1); + strlcpy (tl->mgrs.zone, t, sizeof(tl->mgrs.zone)); /* Try converting it rather do our own error checking. */ @@ -2426,13 +2580,124 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Should be the end. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected stuff at end ignored: %s\n", line, t); } } +/* + * TTMHEAD - Define pattern to be used for Maidenhead Locator. + * + * TTMHEAD pattern [ prefix ] + * + * Pattern would be B[0-9A-D]xxxx... + * Optional prefix is 10, 6, or 4 digits. + * + * The total number of digts in both must be 4, 6, 10, or 12. + */ + else if (strcasecmp(t, "TTMHEAD") == 0) { + +// TODO1.3: TTMHEAD needs testing. + + struct ttloc_s *tl; + int j; + int k; + int count_x; + int count_other; + + + assert (p_tt_config->ttloc_size >= 2); + assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); + + /* Allocate new space, but first, if already full, make larger. */ + if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { + p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; + p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); + } + p_tt_config->ttloc_len++; + assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); + + tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); + tl->type = TTLOC_MHEAD; + strlcpy(tl->pattern, "", sizeof(tl->pattern)); + strlcpy(tl->mhead.prefix, "", sizeof(tl->mhead.prefix)); + + /* Pattern: B, optional additional button, some number of xxxx... for matching */ + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing pattern for TTMHEAD command.\n", line); + p_tt_config->ttloc_len--; + continue; + } + strlcpy (tl->pattern, t, sizeof(tl->pattern)); + + if (t[0] != 'B') { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTMHEAD pattern must begin with upper case 'B'.\n", line); + p_tt_config->ttloc_len--; + continue; + } + + /* Optionally one of 0-9ABCD */ + + if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { + j = 2; + } + else { + j = 1; + } + + count_x = 0; + count_other = 0; + for (k = j ; k < strlen(t); k++) { + if (t[k] == 'x') count_x++; + else count_other++; + } + + if (count_other != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTMHEAD must have only lower case x to match received data.\n", line); + p_tt_config->ttloc_len--; + continue; + } + + // optional prefix + + t = split(NULL,0); + if (t != NULL) { + char mh[30]; + + strlcpy(tl->mhead.prefix, t, sizeof(tl->mhead.prefix)); + + if (!alldigits(t) || (strlen(t) != 4 && strlen(t) != 6 && strlen(t) != 10)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTMHEAD prefix must be 4, 6, or 10 digits.\n", line); + p_tt_config->ttloc_len--; + continue; + } + if (tt_mhead_to_text(t, 0, mh) != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTMHEAD prefix not a valid DTMF sequence.\n", line); + p_tt_config->ttloc_len--; + continue; + } + } + + k = strlen(tl->mhead.prefix) + count_x; + + if (k != 4 && k != 6 && k != 10 && k != 12 ) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTMHEAD prefix and user data must have a total of 4, 6, 10, or 12 digits.\n", line); + p_tt_config->ttloc_len--; + continue; + } + + } + /* * TTSATSQ - Define pattern to be used for Satellite square. @@ -2440,7 +2705,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, * TTSATSQ pattern * * Pattern would be B[0-9A-D]xxxx + * + * Must have exactly 4 x. */ + else if (strcasecmp(t, "TTSATSQ") == 0) { // TODO1.2: TTSATSQ To be continued... @@ -2461,20 +2729,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_SATSQ; - strcpy(tl->pattern, ""); + strlcpy(tl->pattern, "", sizeof(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"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTSATSQ command.\n", line); p_tt_config->ttloc_len--; continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); @@ -2520,6 +2788,15 @@ void config_init (char *fname, struct audio_s *p_audio_config, * definition can contain: * 0-9, A, B, C, D, *, #, x, y, z. * Not sure why # was included in there. + * + * new for version 1.3 - in progress + * + * AA{objname} + * AB{symbol} + * AC{call} + * + * These provide automatic conversion from plain text to the TT encoding. + * */ else if (strcasecmp(t, "TTMACRO") == 0) { @@ -2527,6 +2804,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, int j; //char ch; int p_count[3], d_count[3]; + int tt_error = 0; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); @@ -2541,19 +2819,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_MACRO; - strcpy(tl->pattern, ""); + strlcpy(tl->pattern, "", sizeof(tl->pattern)); /* Pattern: Any combination of digits, x, y, and z. */ /* Also make note of which letters are used in pattern and defintition. */ /* Version 1.2: also allow A,B,C,D in the pattern. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line); + p_tt_config->ttloc_len--; continue; } - strcpy (tl->pattern, t); + strlcpy (tl->pattern, t, sizeof(tl->pattern)); p_count[0] = p_count[1] = p_count[2] = 0; @@ -2561,6 +2840,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, if ( strchr ("0123456789ABCDxyz", t[j]) == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMACRO pattern can contain only digits, A, B, C, D, and lower case x, y, or z.\n", line); + p_tt_config->ttloc_len--; continue; } /* Count how many x, y, z in the pattern. */ @@ -2572,30 +2852,161 @@ void config_init (char *fname, struct audio_s *p_audio_config, //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. */ + /* Next we should find the definition. */ /* It can contain touch tone characters and lower case x, y, z for substitutions. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,1);; if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing definition for TTMACRO command.\n", line); tl->macro.definition = ""; /* Don't die on null pointer later. */ + p_tt_config->ttloc_len--; continue; } - tl->macro.definition = strdup(t); + + /* Make a pass over the definition, looking for the xx{...} substitutions. */ + /* These are done just once when reading the configuration file. */ + + char *pi; + char *ps; + char stemp[100]; // text inside of xx{...} + char ttemp[300]; // Converted to tone sequences. + char otemp[1000]; // Result after any substitutions. + char t2[2]; + + strlcpy (otemp, "", sizeof(otemp)); + t2[1] = '\0'; + pi = t; + while (*pi == ' ' || *pi == '\t') { + pi++; + } + for ( ; *pi != '\0'; pi++) { + + if (strncmp(pi, "AC{", 3) == 0) { + + // Convert to fixed length 10 digit callsign. + + pi += 3; + ps = stemp; + while (*pi != '}' && *pi != '*' && *pi != '\0') { + *ps++ = *pi++; + } + if (*pi == '}') { + *ps = '\0'; + if (tt_text_to_call10 (stemp, 0, ttemp) == 0) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("DEBUG Line %d: AC{%s} -> AC%s\n", line, stemp, ttemp); + strlcat (otemp, "AC", sizeof(otemp)); + strlcat (otemp, ttemp, sizeof(otemp)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: AC{%s} could not be converted to tones for callsign.\n", line, stemp); + tt_error++; + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: AC{... is missing matching } in TTMACRO definition.\n", line); + tt_error++; + } + } + + else if (strncmp(pi, "AA{", 3) == 0) { + + // Convert to object name. + + pi += 3; + ps = stemp; + while (*pi != '}' && *pi != '*' && *pi != '\0') { + *ps++ = *pi++; + } + if (*pi == '}') { + *ps = '\0'; + if (strlen(stemp) > 9) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Object name %s has been truncated to 9 characters.\n", line, stemp); + stemp[9] = '\0'; + } + if (tt_text_to_two_key (stemp, 0, ttemp) == 0) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("DEBUG Line %d: AA{%s} -> AA%s\n", line, stemp, ttemp); + strlcat (otemp, "AA", sizeof(otemp)); + strlcat (otemp, ttemp, sizeof(otemp)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: AA{%s} could not be converted to tones for object name.\n", line, stemp); + tt_error++; + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: AA{... is missing matching } in TTMACRO definition.\n", line); + tt_error++; + } + } + + else if (strncmp(pi, "AB{", 3) == 0) { + + // Attempt conversion from description to symbol code. + + pi += 3; + ps = stemp; + while (*pi != '}' && *pi != '*' && *pi != '\0') { + *ps++ = *pi++; + } + if (*pi == '}') { + char symtab; + char symbol; + char overlay; + char symdest[8]; + + *ps = '\0'; + + // First try to find something matching the description. + + if (symbols_code_from_description (' ', stemp, &symtab, &symbol) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Couldn't convert \"%s\" to APRS symbol code. Using default.\n", line, stemp); + symtab = '\\'; // Alternate + symbol = 'A'; // Box + } + + // Convert symtab(overlay) & symbol to tone sequence. + + symbols_to_tones (symtab, symbol, ttemp); + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("DEBUG config file Line %d: AB{%s} -> %s\n", line, stemp, ttemp); + + strlcat (otemp, ttemp, sizeof(otemp)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: AB{... is missing matching } in TTMACRO definition.\n", line); + tt_error++; + } + } + + else if (strchr("0123456789ABCD*#xyz", *pi) != NULL) { + t2[0] = *pi; + strlcat (otemp, t2, sizeof(otemp)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line); + tt_error++; + } + } + + /* Make sure that number of x, y, z, in pattern and definition match. */ d_count[0] = d_count[1] = d_count[2] = 0; - for (j=0; j= 'x' && t[j] <= 'z') { - d_count[t[j]-'x']++; + for (j=0; j= 'x' && otemp[j] <= 'z') { + d_count[otemp[j]-'x']++; } } @@ -2611,19 +3022,34 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Line %d: '%c' is referenced in TTMACRO definition but does not appear in the pattern.\n", line, 'x'+j); } } + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("DEBUG Config Line %d: %s -> %s\n", line, t, otemp); + + if (tt_error == 0) { + tl->macro.definition = strdup(otemp); + } + else { + p_tt_config->ttloc_len--; + } } /* * TTOBJ - TT Object Report options. * - * TTOBJ recv-chan xmit-chan [ via-path ] + * TTOBJ recv-chan where-to [ via-path ] + * + * whereto is any combination of transmit channel, APP, IG. */ else if (strcasecmp(t, "TTOBJ") == 0) { - int r, x; + int r, x = -1; + int app = 0; + int ig = 0; + char *p; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing DTMF receive channel for TTOBJ command.\n", line); @@ -2644,44 +3070,211 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line); continue; } - x = atoi(t); - if (x < 0 || x > MAX_CHANS-1) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); - continue; - } - if ( ! p_audio_config->achan[x].valid) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", - line, x); - continue; + // Can have any combination of number, APP, IG. + // Would it be easier with strtok? + + for (p = t; *p != '\0'; p++) { + + if (isdigit(*p)) { + x = *p - '0'; + if (x < 0 || x > MAX_CHANS-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); + x = -1; + } + else if ( ! p_audio_config->achan[x].valid) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x); + x = -1; + } + } + else if (*p == 'a' || *p == 'A') { + app = 1; + } + else if (*p == 'i' || *p == 'I') { + ig = 1; + } + else if (strchr("pPgG,", *p) != NULL) { + ; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Expected comma separated list with some combination of transmit channel, APP, and IG.\n", line); + } } // This enables the DTMF decoder on the specified channel. // Additional channels can be enabled with the DTMF command. -// Note that they do not enable the APRStt gateway. +// Note that DTMF command does not enable the APRStt gateway. + + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Debug TTOBJ r=%d, x=%d, app=%d, ig=%d\n", r, x, app, ig); p_audio_config->achan[r].dtmf_decode = DTMF_DECODE_ON; p_tt_config->gateway_enabled = 1; p_tt_config->obj_recv_chan = r; p_tt_config->obj_xmit_chan = x; + p_tt_config->obj_send_to_app = app; + p_tt_config->obj_send_to_ig = ig; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { - // TODO: Should do some validity checking. + // TODO: Should do some validity checking on the path. strncpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via)); } } +/* + * TTERR - TT responses for success or errors. + * + * TTERR msg_id method text... + */ + + else if (strcasecmp(t, "TTERR") == 0) { + int n, msg_num; + char *p; + char method[AX25_MAX_ADDR_LEN]; + int ssid; + int heard; + + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing message identifier for TTERR command.\n", line); + continue; + } + + msg_num = -1; + for (n=0; n= 0 && msg_num < TT_ERROR_MAXP1); + + strlcpy (p_tt_config->response[msg_num].method, method, sizeof(p_tt_config->response[msg_num].method)); + +// TODO1.3: Need SSID too! + + strlcpy (p_tt_config->response[msg_num].mtext, t, sizeof(p_tt_config->response[msg_num].mtext)); + p_tt_config->response[msg_num].mtext[TT_MTEXT_LEN-1] = '\0'; + + } + +/* + * TTSTATUS - TT custom status messages. + * + * TTSTATUS status_id text... + */ + + else if (strcasecmp(t, "TTSTATUS") == 0) { + int status_num; + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing status number for TTSTATUS command.\n", line); + continue; + } + + status_num = atoi(t); + + if (status_num < 1 || status_num > 9) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Status number for TTSTATUS command must be in range of 1 to 9.\n", line); + continue; + } + + t = split(NULL,1);; + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing status text for TTSTATUS command.\n", line); + continue; + } + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Line %d: TTSTATUS debug %d \"%s\"\n", line, status_num, t); + + while (*t == ' ' || *t == '\t') t++; // remove leading white space. + + strncpy (p_tt_config->status[status_num], t, TT_MTEXT_LEN); + p_tt_config->status[status_num][TT_MTEXT_LEN-1] = '\0'; + + } + + +/* + * TTCMD - Command to run when valid sequence is received. + * Any text generated will be sent back to user. + * + * TTCMD ... + */ + + else if (strcasecmp(t, "TTCMD") == 0) { + int status_num; + + t = split(NULL,1); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing command for TTCMD command.\n", line); + continue; + } + + strncpy (p_tt_config->ttcmd, t, sizeof(p_tt_config->ttcmd)); + p_tt_config->ttcmd[sizeof(p_tt_config->ttcmd)-1] = '\0'; + + } + + /* * ==================== Internet gateway ==================== */ @@ -2695,7 +3288,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "IGSERVER") == 0) { - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line); @@ -2723,7 +3316,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Alternatively, the port number could be separated by white space. */ - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { @@ -2747,7 +3340,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "IGLOGIN") == 0) { - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing login callsign for IGLOGIN command.\n", line); @@ -2756,7 +3349,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, // TODO: Wouldn't hurt to do validity checking of format. strncpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login)-1); - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line); @@ -2774,7 +3367,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "IGTXVIA") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing transmit channel for IGTXVIA command.\n", line); @@ -2790,7 +3383,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } p_igate_config->tx_chan = n; - t = strtok (NULL, " \t\n\r"); + t = split(NULL,0); if (t != NULL) { char *p; p_igate_config->tx_via[0] = ','; @@ -2812,7 +3405,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "IGFILTER") == 0) { //int n; - t = strtok (NULL, "\n\r"); /* Take rest of line as one string. */ + t = split(NULL,1); /* Take rest of line as one string. */ if (t != NULL && strlen(t) > 0) { p_igate_config->t2_filter = strdup (t); @@ -2829,7 +3422,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "IGTXLIMIT") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line); @@ -2848,7 +3441,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, line, p_igate_config->tx_limit_1); } - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing five minute limit for IGTXLIMIT command.\n", line); @@ -2878,7 +3471,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "AGWPORT") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing port number for AGWPORT command.\n", line); @@ -2902,7 +3495,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "KISSPORT") == 0) { int n; - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line); @@ -2924,7 +3517,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, * NULLMODEM - Device name for our end of the virtual "null modem" */ else if (strcasecmp(t, "nullmodem") == 0) { - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing device name for my end of the 'null modem' on line %d.\n", line); @@ -2939,7 +3532,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, * NMEA - Device name for communication with NMEA device. */ else if (strcasecmp(t, "nmea") == 0) { - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing device name for NMEA port on line %d.\n", line); @@ -2954,7 +3547,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, * LOGDIR - Directory name for storing log files. Use "." for current working directory. */ else if (strcasecmp(t, "logdir") == 0) { - t = strtok (NULL, " ,\t\n\r"); + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing directory name for LOGDIR on line %d.\n", line); @@ -2963,6 +3556,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, else { strncpy (p_misc_config->logdir, t, sizeof(p_misc_config->logdir)-1); } + t = split(NULL,0); + if (t != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: LOGDIR on line %d should have directory path and nothing more.\n", line); + } } /* @@ -3049,7 +3647,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, int n; #define SB_NUM(name,sbvar,minn,maxx,unit) \ - t = strtok (NULL, " ,\t\n\r"); \ + t = split(NULL,0); \ if (t == NULL) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \ @@ -3066,7 +3664,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } #define SB_TIME(name,sbvar,minn,maxx,unit) \ - t = strtok (NULL, " ,\t\n\r"); \ + t = split(NULL,0); \ if (t == NULL) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \ @@ -3164,7 +3762,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, 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, ""); + strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login)); } if (p_igate_config->tx_chan >= 0 && ( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 || @@ -3200,8 +3798,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ double easting = G_UNKNOWN; double northing = G_UNKNOWN; - strcpy (temp_symbol, ""); - strcpy (zone, ""); + strlcpy (temp_symbol, "", sizeof(temp_symbol)); + strlcpy (zone, "", sizeof(zone)); b->sendto_type = SENDTO_XMIT; b->sendto_chan = 0; @@ -3215,74 +3813,13 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ b->symtab = '/'; b->symbol = '-'; /* house */ -/* - * cmd should be rest of command line after ?BEACON was removed. - * - * Quoting is required for any values containing spaces. - * This could happen for an object name, comment, symbol description, ... - * To prevent strtok from stopping at those spaces, change them to - * non-breaking space character temporarily. After spliting everything - * up at white space, change them back to normal spaces. - */ - -#define NBSP (' ' + 0x80) - p = cmd; /* Process from here. */ - o = options; /* to here. */ - q = 0; /* Keep track of whether in quoted part. */ - - for ( ; *p != '\0' ; p++) { - - switch (*p) { - - case '"': - if (!q) { /* opening quote */ - if (*(p-1) != '=') { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: line %d: Suspicious use of \" not after =.\n", line); - dw_printf ("Suggestion: Double it and quote entire value.\n"); - *o++ = '"'; /* Treat as regular character. */ - } - else { - q = 1; - } - } - else { /* embedded or closing quote */ - if (*(p+1) == '"') { - *o++ = '"'; /* reduce double to single */ - p++; - } - else if (isspace(*(p+1)) || *(p+1) == '\0') { - q = 0; - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: line %d: Suspicious use of \" not at end of value.\n", line); - dw_printf ("Suggestion: Double it and quote entire value.\n"); - *o++ = '"'; /* Treat as regular character. */ - } - } - break; - - case ' ': - - *o++ = q ? NBSP : ' '; - break; - - default: - *o++ = *p; - break; - } - } - *o = '\0'; - - for (t = strtok (options, " \t\n\r"); t != NULL; t = strtok (NULL, " \t\n\r")) { + while ((t = split(NULL,0)) != NULL) { char keyword[20]; char value[200]; char *e; char *p; - //int q; e = strchr(t, '='); @@ -3292,15 +3829,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ return (0); } *e = '\0'; - strcpy (keyword, t); - strcpy (value, e+1); - -/* Put back normal spaces. */ - - for (p = value; *p != '\0'; p++) { - // char is signed for MinGW! - if (((int)(*p) & 0xff) == NBSP) *p = ' '; - } + strlcpy (keyword, t, sizeof(keyword)); + strlcpy (value, e+1, sizeof(value)); if (strcasecmp(keyword, "DELAY") == 0) { b->delay = parse_interval(value,line); @@ -3367,6 +3897,9 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ else if (strcasecmp(keyword, "INFO") == 0) { b->custom_info = strdup(value); } + else if (strcasecmp(keyword, "INFOCMD") == 0) { + b->custom_infocmd = strdup(value); + } else if (strcasecmp(keyword, "OBJNAME") == 0) { strncpy(b->objname, value, 9); } @@ -3390,7 +3923,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (strcasecmp(keyword, "SYMBOL") == 0) { /* Defer processing in case overlay appears later. */ - strcpy (temp_symbol, value); + strlcpy (temp_symbol, value, sizeof(temp_symbol)); } else if (strcasecmp(keyword, "OVERLAY") == 0) { if (strlen(value) == 1 && (isupper(value[0]) || isdigit(value[0]))) { @@ -3425,6 +3958,9 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ else if (strcasecmp(keyword, "COMMENT") == 0) { b->comment = strdup(value); } + else if (strcasecmp(keyword, "COMMENTCMD") == 0) { + b->commentcmd = strdup(value); + } else if (strcasecmp(keyword, "COMPRESS") == 0 || strcasecmp(keyword, "COMPRESSED") == 0) { b->compress = atoi(value); } @@ -3438,6 +3974,11 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } } + if (b->custom_info != NULL && b->custom_infocmd != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time..\n", line); + } + /* * Convert UTM coordintes to lat / long. */ @@ -3446,11 +3987,11 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ if (strlen(zone) > 0 && easting != G_UNKNOWN && northing != G_UNKNOWN) { long lzone; - char hemi; + char latband, hemi; long lerr; double dlat, dlon; - lzone = parse_utm_zone (zone, &hemi); + lzone = parse_utm_zone (zone, &latband, &hemi); lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon); diff --git a/config.h b/config.h index 267c9fb..be65cd7 100644 --- a/config.h +++ b/config.h @@ -32,8 +32,8 @@ enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV }; struct misc_config_s { - int agwpe_port; /* Port number for the “AGW TCPIP Socket Interface” */ - int kiss_port; /* Port number for the “KISS” protocol. */ + int agwpe_port; /* Port number for the "AGW TCPIP Socket Interface" */ + int kiss_port; /* Port number for the "KISS" protocol. */ int enable_kiss_pt; /* Enable pseudo terminal for KISS. */ /* Want this to be off by default because it hangs */ /* after a while if nothing is reading from other end. */ @@ -97,6 +97,9 @@ struct misc_config_s { char *custom_info; /* Info part for handcrafted custom beacon. */ /* Ignore the rest below if this is set. */ + char *custom_infocmd; /* Command to generate info part. */ + /* Again, other options below are then ignored. */ + int messaging; /* Set messaging attribute for position report. */ /* i.e. Data Type Indicator of '=' rather than '!' */ @@ -119,6 +122,7 @@ struct misc_config_s { float offset; /* MHz. */ char *comment; /* Comment or NULL. */ + char *commentcmd; /* Command to append more to Comment or NULL. */ } beacon[MAX_BEACONS]; diff --git a/decode_aprs.c b/decode_aprs.c index 618c808..282c016 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -38,9 +38,7 @@ #include #include /* for atof */ #include /* for strtok */ -#if __WIN32__ -char *strsep(char **stringp, const char *delim); -#endif + #include /* for pow */ #include /* for isdigit */ #include @@ -110,6 +108,8 @@ static void aprs_object (decode_aprs_t *A, unsigned char *, int); static void aprs_item (decode_aprs_t *A, unsigned char *, int); static void aprs_station_capabilities (decode_aprs_t *A, char *, int); static void aprs_status_report (decode_aprs_t *A, char *, int); +static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet); +static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet); static void aprs_telemetry (decode_aprs_t *A, char *, int, int quiet); static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int); static void aprs_morse_code (decode_aprs_t *A, char *, int); @@ -130,6 +130,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen); + /*------------------------------------------------------------------ * * Function: decode_aprs @@ -165,34 +166,32 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) A->g_quiet = quiet; - sprintf (A->g_msg_type, "Unknown message type %c", *pinfo); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown message type %c", *pinfo); - A->g_symbol_table = '/'; /* Default to primary table. */ + A->g_symbol_table = '/'; /* Default to primary table. */ A->g_symbol_code = ' '; /* What should we have for default symbol? */ A->g_lat = G_UNKNOWN; A->g_lon = G_UNKNOWN; - //strcpy (A->g_maidenhead, ""); - //strcpy (A->g_name, ""); A->g_speed = G_UNKNOWN; A->g_course = G_UNKNOWN; A->g_power = G_UNKNOWN; A->g_height = G_UNKNOWN; A->g_gain = G_UNKNOWN; - //strcpy (A->g_directivity, ""); A->g_range = G_UNKNOWN; A->g_altitude = G_UNKNOWN; - //strcpy(A->g_mfr, ""); - //strcpy(A->g_mic_e_status, ""); A->g_freq = G_UNKNOWN; A->g_tone = G_UNKNOWN; A->g_dcs = G_UNKNOWN; A->g_offset = G_UNKNOWN; - //strcpy (A->g_weather, ""); - //strcpy (A->g_comment, ""); + + A->g_footprint_lat = G_UNKNOWN; + A->g_footprint_lon = G_UNKNOWN; + A->g_footprint_radius = G_UNKNOWN; + /* * Extract source and destination including the SSID. @@ -255,6 +254,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) case ':': /* Message */ + /* Directed Station Query */ aprs_message (A, pinfo, info_len, quiet); break; @@ -274,10 +274,14 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) aprs_status_report (A, (char*)pinfo, info_len); break; - //case '?': /* Query */ - //break; + + case '?': /* General Query */ + + aprs_general_query (A, (char*)pinfo, info_len, quiet); + break; case 'T': /* Telemetry */ + aprs_telemetry (A, (char*)pinfo, info_len, quiet); break; @@ -384,41 +388,43 @@ void decode_aprs_print (decode_aprs_t *A) { * - mic-e status * - power/height/gain, range */ - strcpy (stemp, A->g_msg_type); + strlcpy (stemp, A->g_msg_type, sizeof(stemp)); if (strlen(A->g_name) > 0) { - strcat (stemp, ", \""); - strcat (stemp, A->g_name); - strcat (stemp, "\""); + strlcat (stemp, ", \"", sizeof(stemp)); + strlcat (stemp, A->g_name, sizeof(stemp)); + strlcat (stemp, "\"", sizeof(stemp)); } - symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description); - strcat (stemp, ", "); - strcat (stemp, symbol_description); + if (A->g_symbol_code != ' ') { + symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description, sizeof(symbol_description)); + strlcat (stemp, ", ", sizeof(stemp)); + strlcat (stemp, symbol_description, sizeof(stemp)); + } if (strlen(A->g_mfr) > 0) { - strcat (stemp, ", "); - strcat (stemp, A->g_mfr); + strlcat (stemp, ", ", sizeof(stemp)); + strlcat (stemp, A->g_mfr, sizeof(stemp)); } if (strlen(A->g_mic_e_status) > 0) { - strcat (stemp, ", "); - strcat (stemp, A->g_mic_e_status); + strlcat (stemp, ", ", sizeof(stemp)); + strlcat (stemp, A->g_mic_e_status, sizeof(stemp)); } if (A->g_power > 0) { char phg[100]; - sprintf (phg, ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity); - strcat (stemp, phg); + snprintf (phg, sizeof(phg), ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity); + strlcat (stemp, phg, sizeof(stemp)); } if (A->g_range > 0) { char rng[100]; - sprintf (rng, ", range=%.1f", A->g_range); - strcat (stemp, rng); + snprintf (rng, sizeof(rng), ", range=%.1f", A->g_range); + strlcat (stemp, rng, sizeof(stemp)); } text_color_set(DW_COLOR_DECODED); dw_printf("%s\n", stemp); @@ -451,7 +457,7 @@ void decode_aprs_print (decode_aprs_t *A) { dw_printf("Grid square = %s, ", A->g_maidenhead); } - strcpy (stemp, ""); + strlcpy (stemp, "", sizeof(stemp)); if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) { @@ -469,10 +475,10 @@ void decode_aprs_print (decode_aprs_t *A) { } deg = (int) absll; min = (absll - deg) * 60.0; - sprintf (s_lat, "%c %02d%s%07.4f", news, deg, CH_DEGREE, min); + snprintf (s_lat, sizeof(s_lat), "%c %02d%s%07.4f", news, deg, CH_DEGREE, min); } else { - strcpy (s_lat, "Invalid Latitude"); + strlcpy (s_lat, "Invalid Latitude", sizeof(s_lat)); } if (A->g_lon != G_UNKNOWN) { @@ -487,72 +493,72 @@ void decode_aprs_print (decode_aprs_t *A) { } deg = (int) absll; min = (absll - deg) * 60.0; - sprintf (s_lon, "%c %03d%s%07.4f", news, deg, CH_DEGREE, min); + snprintf (s_lon, sizeof(s_lon), "%c %03d%s%07.4f", news, deg, CH_DEGREE, min); } else { - strcpy (s_lon, "Invalid Longitude"); + strlcpy (s_lon, "Invalid Longitude", sizeof(s_lon)); } - sprintf (stemp, "%s, %s", s_lat, s_lon); + snprintf (stemp, sizeof(stemp), "%s, %s", s_lat, s_lon); } if (strlen(A->g_aprstt_loc) > 0) { - if (strlen(stemp) > 0) strcat (stemp, ", "); - strcat (stemp, A->g_aprstt_loc); + if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); + strlcat (stemp, A->g_aprstt_loc, sizeof(stemp)); }; if (A->g_speed != G_UNKNOWN) { char spd[20]; - if (strlen(stemp) > 0) strcat (stemp, ", "); - sprintf (spd, "%.0f MPH", A->g_speed); - strcat (stemp, spd); + if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); + snprintf (spd, sizeof(spd), "%.0f MPH", A->g_speed); + strlcat (stemp, spd, sizeof(stemp)); }; if (A->g_course != G_UNKNOWN) { char cse[20]; - if (strlen(stemp) > 0) strcat (stemp, ", "); - sprintf (cse, "course %.0f", A->g_course); - strcat (stemp, cse); + if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); + snprintf (cse, sizeof(cse), "course %.0f", A->g_course); + strlcat (stemp, cse, sizeof(stemp)); }; if (A->g_altitude != G_UNKNOWN) { char alt[20]; - if (strlen(stemp) > 0) strcat (stemp, ", "); - sprintf (alt, "alt %.0f ft", A->g_altitude); - strcat (stemp, alt); + if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp)); + snprintf (alt, sizeof(alt), "alt %.0f ft", A->g_altitude); + strlcat (stemp, alt, sizeof(stemp)); }; if (A->g_freq != G_UNKNOWN) { char ftemp[30]; - sprintf (ftemp, ", %.3f MHz", A->g_freq); - strcat (stemp, ftemp); + snprintf (ftemp, sizeof(ftemp), ", %.3f MHz", A->g_freq); + strlcat (stemp, ftemp, sizeof(stemp)); } if (A->g_offset != G_UNKNOWN) { char ftemp[30]; if (A->g_offset % 1000 == 0) { - sprintf (ftemp, ", %+dM", A->g_offset/1000); + snprintf (ftemp, sizeof(ftemp), ", %+dM", A->g_offset/1000); } else { - sprintf (ftemp, ", %+dk", A->g_offset); + snprintf (ftemp, sizeof(ftemp), ", %+dk", A->g_offset); } - strcat (stemp, ftemp); + strlcat (stemp, ftemp, sizeof(stemp)); } if (A->g_tone != G_UNKNOWN) { if (A->g_tone == 0) { - strcat (stemp, ", no PL"); + strlcat (stemp, ", no PL", sizeof(stemp)); } else { char ftemp[30]; - sprintf (ftemp, ", PL %.1f", A->g_tone); - strcat (stemp, ftemp); + snprintf (ftemp, sizeof(ftemp), ", PL %.1f", A->g_tone); + strlcat (stemp, ftemp, sizeof(stemp)); } } @@ -560,8 +566,8 @@ void decode_aprs_print (decode_aprs_t *A) { char ftemp[30]; - sprintf (ftemp, ", DCS %03o", A->g_dcs); - strcat (stemp, ftemp); + snprintf (ftemp, sizeof(ftemp), ", DCS %03o", A->g_dcs); + strlcat (stemp, ftemp, sizeof(stemp)); } if (strlen (stemp) > 0) { @@ -588,17 +594,13 @@ void decode_aprs_print (decode_aprs_t *A) { A->g_weather[n-1] = '\0'; n--; } - if (n > 0) { - //int j; - + if (n > 0) { ax25_safe_print (A->g_weather, -1, 0); dw_printf("\n"); } if (strlen(A->g_telemetry) > 0) { - //int j; - ax25_safe_print (A->g_telemetry, -1, 0); dw_printf("\n"); } @@ -695,7 +697,7 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) } *q; - strcpy (A->g_msg_type, "Position"); + strlcpy (A->g_msg_type, "Position", sizeof(A->g_msg_type)); p = (struct aprs_ll_pos_s *)info; q = (struct aprs_compressed_pos_s *)info; @@ -709,7 +711,7 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) /* In this case, we expect 7 byte "data extension" */ /* for the wind direction and speed. */ - strcpy (A->g_msg_type, "Weather Report"); + strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, p->comment, TRUE); } else { @@ -728,7 +730,7 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) /* compressed data so we don't expect a 7 byte "data */ /* extension" for them. */ - strcpy (A->g_msg_type, "Weather Report"); + strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, q->comment, FALSE); } else { @@ -798,7 +800,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) } *q; - strcpy (A->g_msg_type, "Position with time"); + strlcpy (A->g_msg_type, "Position with time", sizeof(A->g_msg_type)); time_t ts = 0; @@ -817,7 +819,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) /* In this case, we expect 7 byte "data extension" */ /* for the wind direction and speed. */ - strcpy (A->g_msg_type, "Weather Report"); + strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, p->comment, TRUE); } else { @@ -838,7 +840,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) /* compressed data so we don't expect a 7 byte "data */ /* extension" for them. */ - strcpy (A->g_msg_type, "Weather Report"); + strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type)); weather_data (A, q->comment, FALSE); } else { @@ -920,10 +922,9 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) char *next; - strcpy (A->g_msg_type, "Raw NMEA"); + strlcpy (A->g_msg_type, "Raw NMEA", sizeof(A->g_msg_type)); - strncpy (stemp, (char *)info, ilen); - stemp[ilen] = '\0'; + strlcpy (stemp, (char *)info, sizeof(stemp)); nmea_checksum (A, stemp); next = stemp; @@ -957,15 +958,30 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) /* Process time??? */ - if (plat != NULL && strlen(plat) > 0) { + if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) { A->g_lat = latitude_from_nmea(plat, pns); } - if (plon != NULL && strlen(plon) > 0) { + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete latitude in sentence.\n"); + } + + if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) { A->g_lon = longitude_from_nmea(plon, pew); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete longitude in sentence.\n"); + } + if (paltitude != NULL && strlen(paltitude) > 0) { A->g_altitude = DW_METERS_TO_FEET(atof(paltitude)); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete altitude in sentence.\n"); + } + } else if (strcmp(ptype, "$GPGLL") == 0) { @@ -981,12 +997,21 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) plon = strsep(&next, ","); pew = strsep(&next, ","); - if (plat != NULL && strlen(plat) > 0) { + if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) { A->g_lat = latitude_from_nmea(plat, pns); } - if (plon != NULL && strlen(plon) > 0) { + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete latitude in sentence.\n"); + } + + if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) { A->g_lon = longitude_from_nmea(plon, pew); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete longitude in sentence.\n"); + } } else if (strcmp(ptype, "$GPRMC") == 0) @@ -1018,18 +1043,38 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) /* process time ??? date ??? */ - if (plat != NULL && strlen(plat) > 0) { + if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) { A->g_lat = latitude_from_nmea(plat, pns); } - if (plon != NULL && strlen(plon) > 0) { + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete latitude in sentence.\n"); + } + + if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) { A->g_lon = longitude_from_nmea(plon, pew); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete longitude in sentence.\n"); + } + if (pknots != NULL && strlen(pknots) > 0) { A->g_speed = DW_KNOTS_TO_MPH(atof(pknots)); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete speed in sentence.\n"); + } + if (pcourse != NULL && strlen(pcourse) > 0) { A->g_course = atof(pcourse); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete course in sentence.\n"); + } + } else if (strcmp(ptype, "$GPVTG") == 0) { @@ -1060,14 +1105,22 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) if (pknots != NULL && strlen(pknots) > 0) { A->g_speed = DW_KNOTS_TO_MPH(atof(pknots)); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete speed in sentence.\n"); + } + if (ptcourse != NULL && strlen(ptcourse) > 0) { A->g_course = atof(ptcourse); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete course in sentence.\n"); + } } else if (strcmp(ptype, "$GPWPL") == 0) { - //char *plat, *pns, *plon, *pew, *pident; char *plat; /* Latitude */ char *pns; /* North/South */ @@ -1082,12 +1135,21 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) pew = strsep(&next, ","); pident = strsep(&next, ","); - if (plat != NULL && strlen(plat) > 0) { + if (plat != NULL && strlen(plat) > 0 && pns != NULL && strlen(pns) > 0) { A->g_lat = latitude_from_nmea(plat, pns); } - if (plon != NULL && strlen(plon) > 0) { + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete latitude in sentence.\n"); + } + + if (plon != NULL && strlen(plon) > 0 && pew != NULL && strlen(pew) > 0) { A->g_lon = longitude_from_nmea(plon, pew); } + else { + text_color_set (DW_COLOR_ERROR); + dw_printf("Incomplete longitude in sentence.\n"); + } /* do something with identifier? */ @@ -1109,7 +1171,7 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) * * Description: * - * Destination Address Field — + * Destination Address Field - * * The 7-byte Destination Address field contains * the following encoded information: @@ -1261,7 +1323,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" }; unsigned char *pfirst, *plast; - strcpy (A->g_msg_type, "MIC-E"); + strlcpy (A->g_msg_type, "MIC-E", sizeof(A->g_msg_type)); p = (struct aprs_mic_e_s *)info; @@ -1456,16 +1518,16 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int /* Message type from two 3-bit codes. */ if (std_msg == 0 && cust_msg == 0) { - strcpy (A->g_mic_e_status, "Emergency"); + strlcpy (A->g_mic_e_status, "Emergency", sizeof(A->g_mic_e_status)); } else if (std_msg == 0 && cust_msg != 0) { - strcpy (A->g_mic_e_status, cust_text[cust_msg]); + strlcpy (A->g_mic_e_status, cust_text[cust_msg], sizeof(A->g_mic_e_status)); } else if (std_msg != 0 && cust_msg == 0) { - strcpy (A->g_mic_e_status, std_text[std_msg]); + strlcpy (A->g_mic_e_status, std_text[std_msg], sizeof(A->g_mic_e_status)); } else { - strcpy (A->g_mic_e_status, "Unknown MIC-E Message Type"); + strlcpy (A->g_mic_e_status, "Unknown MIC-E Message Type", sizeof(A->g_mic_e_status)); } /* Speed and course from next 3 bytes. */ @@ -1492,7 +1554,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int /* Now try to pick out manufacturer and other optional items. */ /* The telemetry field, in the original spec, is no longer used. */ - strcpy (A->g_mfr, "Unknown manufacturer"); + strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr)); pfirst = info + sizeof(struct aprs_mic_e_s); plast = info + ilen - 1; @@ -1511,35 +1573,35 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int if (isT(*pfirst)) { - if (*pfirst == ' ') { strcpy (A->g_mfr, "Original MIC-E"); pfirst++; } + if (*pfirst == ' ') { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == '>' && *plast == '=') { strcpy (A->g_mfr, "Kenwood TH-D72"); pfirst++; plast--; } - else if (*pfirst == '>') { strcpy (A->g_mfr, "Kenwood TH-D7A"); pfirst++; } + else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } + else if (*pfirst == '>') { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == ']' && *plast == '=') { strcpy (A->g_mfr, "Kenwood TM-D710"); pfirst++; plast--; } - else if (*pfirst == ']') { strcpy (A->g_mfr, "Kenwood TM-D700"); pfirst++; } + else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } + else if (*pfirst == ']') { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strcpy (A->g_mfr, "Yaesu VX-8"); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strcpy (A->g_mfr, "Yaesu FTM-350"); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strcpy (A->g_mfr, "Yaesu VX-8G"); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strcpy (A->g_mfr, "Yaesu FT1D"); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strcpy (A->g_mfr, "Yaesu FTM-400DR"); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strcpy (A->g_mfr, "Yaesu FTM-100D"); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strcpy (A->g_mfr, "Yaesu FT2D"); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strcpy (A->g_mfr, "Byonics TinyTrack3"); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strcpy (A->g_mfr, "Byonics TinyTrack4"); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*(plast-1) == '\\') { strcpy (A->g_mfr, "Hamhud ?"); pfirst++; plast-=2; } - else if (*(plast-1) == '/') { strcpy (A->g_mfr, "Argent ?"); pfirst++; plast-=2; } - else if (*(plast-1) == '^') { strcpy (A->g_mfr, "HinzTec anyfrog"); pfirst++; plast-=2; } - else if (*(plast-1) == '*') { strcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"); pfirst++; plast-=2; } - else if (*(plast-1) == '~') { strcpy (A->g_mfr, "OTHER"); pfirst++; plast-=2; } + else if (*(plast-1) == '\\') { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*(plast-1) == '/') { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*(plast-1) == '^') { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*(plast-1) == '*') { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*(plast-1) == '~') { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } // Should Original Mic-E and Kenwood be moved down to here? - else if (*pfirst == '`') { strcpy (A->g_mfr, "Mic-Emsg"); pfirst++; plast-=2; } - else if (*pfirst == '\'') { strcpy (A->g_mfr, "McTrackr"); pfirst++; plast-=2; } + else if (*pfirst == '`') { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'') { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; plast-=2; } } /* @@ -1602,9 +1664,8 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * It's a lot more complicated with different types of addressees * and replies with acknowledgement or rejection. * - * Displaying and logging these messages could be useful. * - * Examples: + * Examples: ... * * *------------------------------------------------------------------*/ @@ -1618,16 +1679,35 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q char colon; /* : */ char message[73]; /* 0-67 characters for message */ /* { followed by 1-5 characters for message number */ + + /* If the first chracter is '?' it is a Directed Station Query. */ } *p; char addressee[AX25_MAX_ADDR_LEN]; int i; - p = (struct aprs_message_s *)info; + strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type)); + + if (ilen < 11) { + if (! quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Message must have a minimum of 11 characters for : addressee :\n"); + } + return; + } + + if (p->colon != ':') { + if (! quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Message must begin with : addressee :\n"); + } + return; + } + memset (addressee, 0, sizeof(addressee)); - strncpy (addressee, p->addressee, sizeof(p->addressee)); + memcpy (addressee, p->addressee, sizeof(p->addressee)); // copy exactly 9 bytes. /* Trim trailing spaces. */ i = strlen(addressee) - 1; @@ -1635,7 +1715,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q addressee[i--] = '\0'; } - strcpy (A->g_addressee, addressee); + strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee)); /* * Special message formats contain telemetry metadata. @@ -1649,27 +1729,38 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q */ if (strncmp(p->message,"PARM.",5) == 0) { - sprintf (A->g_msg_type, "Telemetry Parameter Name Message for \"%s\"", addressee); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Parameter Name Message for \"%s\"", addressee); telemetry_name_message (addressee, p->message+5); } else if (strncmp(p->message,"UNIT.",5) == 0) { - sprintf (A->g_msg_type, "Telemetry Unit/Label Message for \"%s\"", addressee); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee); telemetry_unit_label_message (addressee, p->message+5); } else if (strncmp(p->message,"EQNS.",5) == 0) { - sprintf (A->g_msg_type, "Telemetry Equation Coefficents Message for \"%s\"", addressee); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee); telemetry_coefficents_message (addressee, p->message+5, quiet); } else if (strncmp(p->message,"BITS.",5) == 0) { - sprintf (A->g_msg_type, "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); telemetry_bit_sense_message (addressee, p->message+5, quiet); } + +/* + * If first character of message is "?" it is a query directed toward a specific station. + */ + + else if (p->message[0] == '?') { + + strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type)); + + aprs_directed_station_query (A, addressee, p->message+1, quiet); + } else { - sprintf (A->g_msg_type, "APRS Message for \"%s\"", addressee); + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message for \"%s\"", addressee); /* No location so don't use process_comment () */ - strcpy (A->g_comment, p->message); + strlcpy (A->g_comment, p->message, sizeof(A->g_comment)); } } @@ -1729,19 +1820,23 @@ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) p = (struct aprs_object_s *)info; q = (struct aprs_compressed_object_s *)info; - strncpy (A->g_name, p->name, 9); - A->g_name[9] = '\0'; + //assert (sizeof(A->g_name) > sizeof(p->name)); + + memset (A->g_name, 0, sizeof(A->g_name)); + memcpy (A->g_name, p->name, sizeof(p->name)); // copy exactly 9 bytes. + + /* Trim trailing spaces. */ i = strlen(A->g_name) - 1; while (i >= 0 && A->g_name[i] == ' ') { A->g_name[i--] = '\0'; } - if (p->live_killed == '*') - strcpy (A->g_msg_type, "Object"); + if (p->live_killed == '*') + strlcpy (A->g_msg_type, "Object", sizeof(A->g_msg_type)); else if (p->live_killed == '_') - strcpy (A->g_msg_type, "Killed Object"); + strlcpy (A->g_msg_type, "Killed Object", sizeof(A->g_msg_type)); else - strcpy (A->g_msg_type, "Object - invalid live/killed"); + strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type)); ts = get_timestamp (A, p->time_stamp); @@ -1754,7 +1849,7 @@ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) /* In this case, we expect 7 byte "data extension" */ /* for the wind direction and speed. */ - strcpy (A->g_msg_type, "Weather Report with Object"); + strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type)); weather_data (A, p->comment, TRUE); } else { @@ -1773,7 +1868,7 @@ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen) /* of weather report and object with compressed */ /* position. */ - strcpy (A->g_msg_type, "Weather Report with Object"); + strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type)); weather_data (A, q->comment, FALSE); } else { @@ -1813,6 +1908,9 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) struct aprs_item_s { char dti; /* ) */ char name[9]; /* Actually variable length 3 - 9 bytes. */ + /* DON'T refer to the rest of this structure; */ + /* the offsets will be wrong! */ + char live_killed; /* ! for live or _ for killed */ position_t pos; char comment[43]; /* First 7 bytes could be data extension. */ @@ -1821,6 +1919,9 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) struct aprs_compressed_item_s { char dti; /* ) */ char name[9]; /* Actually variable length 3 - 9 bytes. */ + /* DON'T refer to the rest of this structure; */ + /* the offsets will be wrong! */ + char live_killed; /* ! for live or _ for killed */ compressed_position_t cpos; char comment[40]; /* No data extension in this case. */ @@ -1835,6 +1936,7 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) p = (struct aprs_item_s *)info; q = (struct aprs_compressed_item_s *)info; + memset (A->g_name, 0, sizeof(A->g_name)); i = 0; while (i < 9 && p->name[i] != '!' && p->name[i] != '_') { A->g_name[i] = p->name[i]; @@ -1843,15 +1945,15 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) } if (p->name[i] == '!') - strcpy (A->g_msg_type, "Item"); + strlcpy (A->g_msg_type, "Item", sizeof(A->g_msg_type)); else if (p->name[i] == '_') - strcpy (A->g_msg_type, "Killed Item"); + strlcpy (A->g_msg_type, "Killed Item", sizeof(A->g_msg_type)); else { if ( ! A->g_quiet) { text_color_set(DW_COLOR_ERROR); dw_printf("Item name too long or not followed by ! or _.\n"); } - strcpy (A->g_msg_type, "Object - invalid live/killed"); + strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type)); } ppos = p->name + i + 1; @@ -1895,12 +1997,14 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen) static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen) { - strcpy (A->g_msg_type, "Station Capabilities"); + strlcpy (A->g_msg_type, "Station Capabilities", sizeof(A->g_msg_type)); - // Is process_comment() applicable? + // process_comment() not applicable here because it + // extracts information found in certain formats. - strcpy (A->g_comment, info+1); -} + strlcpy (A->g_comment, info+1, sizeof(A->g_comment)); + +} /* end aprs_station_capabilities */ @@ -1980,7 +2084,7 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) } *ps; - strcpy (A->g_msg_type, "Status Report"); + strlcpy (A->g_msg_type, "Status Report", sizeof(A->g_msg_type)); pt = (struct aprs_status_time_s *)info; pm4 = (struct aprs_status_m4_s *)info; @@ -1998,7 +2102,10 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) isdigit(pt->ztime[5]) && pt->ztime[6] == 'z') { - strcpy (A->g_comment, pt->comment); + // process_comment() not applicable here because it + // extracts information found in certain formats. + + strlcpy (A->g_comment, pt->comment, sizeof(A->g_comment)); } /* @@ -2006,8 +2113,8 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) */ else if (get_maidenhead (A, pm6->mhead6) == 6) { - strncpy (A->g_maidenhead, pm6->mhead6, 6); - A->g_maidenhead[6] = '\0'; + memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead)); + memcpy (A->g_maidenhead, pm6->mhead6, sizeof(pm6->mhead6)); A->g_symbol_table = pm6->sym_table_id; A->g_symbol_code = pm6->symbol_code; @@ -2029,7 +2136,10 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) } } - strcpy (A->g_comment, pm6->comment); + // process_comment() not applicable here because it + // extracts information found in certain formats. + + strlcpy (A->g_comment, pm6->comment, sizeof(A->g_comment)); } /* @@ -2037,8 +2147,8 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) */ else if (get_maidenhead (A, pm4->mhead4) == 4) { - strncpy (A->g_maidenhead, pm4->mhead4, 4); - A->g_maidenhead[4] = '\0'; + memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead)); + memcpy (A->g_maidenhead, pm4->mhead4, sizeof(pm4->mhead4)); A->g_symbol_table = pm4->sym_table_id; A->g_symbol_code = pm4->symbol_code; @@ -2060,14 +2170,17 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) } } - strcpy (A->g_comment, pm4->comment); + // process_comment() not applicable here because it + // extracts information found in certain formats. + + strlcpy (A->g_comment, pm4->comment, sizeof(A->g_comment)); } /* * Whole thing is status text. */ else { - strcpy (A->g_comment, ps->comment); + strlcpy (A->g_comment, ps->comment, sizeof(A->g_comment)); } @@ -2102,7 +2215,207 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) *hp = '\0'; } } -} + +} /* end aprs_status_report */ + + +/*------------------------------------------------------------------ + * + * Function: aprs_general_query + * + * Purpose: Decode "General Query" for all stations. + * + * Inputs: info - Pointer to Information field. First character should be "?". + * ilen - Information field length. + * quiet - suppress error messages. + * + * Outputs: A - Decoded packet structure + * A->g_query_type + * A->g_query_lat (optional) + * A->g_query_lon (optional) + * A->g_query_radius (optional) + * + * Description: Formats are: + * + * ?query? + * ?query?lat,long,radius + * + * 'query' is one of APRS, IGATE, WX, ... + * optional footprint, in degrees and miles radius, means only + * those in the specified circle should respond. + * + * Examples from specification, Chapter 15: + * + * ?APRS? + * ?APRS? 34.02,-117.15,0200 + * ?IGATE? + * + *------------------------------------------------------------------*/ + +static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet) +{ + char *q2; + char *p; + char *tok; + char stemp[256]; + double lat, lon; + float radius; + + strlcpy (A->g_msg_type, "General Query", sizeof(A->g_msg_type)); + +/* + * First make a copy because we will modify it while parsing it. + */ + + strlcpy (stemp, info, sizeof(stemp)); + +/* + * There should be another "?" after the query type. + */ + q2 = strchr(stemp+1, '?'); + if (q2 == NULL) { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("General Query must have ? after the query type.\n"); + } + return; + } + + *q2 = '\0'; + strlcpy (A->g_query_type, stemp+1, sizeof(A->g_query_type)); + +// TODO: remove debug + + text_color_set(DW_COLOR_DEBUG); + dw_printf("DEBUG: General Query type = \"%s\"\n", A->g_query_type); + + p = q2 + 1; + if (strlen(p) == 0) { + return; + } + +/* + * Try to extract footprint. + * Spec says positive coordinate would be preceded by space + * and radius must be exactly 4 digits. We are more forgiving. + */ + tok = strsep(&p, ","); + if (tok != NULL) { + lat = atof(tok); + tok = strsep(&p, ","); + if (tok != NULL) { + lon = atof(tok); + tok = strsep(&p, ","); + if (tok != NULL) { + radius = atof(tok); + + if (lat < -90 || lat > 90) { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid latitude for General Query footprint.\n"); + } + return; + } + + if (lon < -180 || lon > 180) { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid longitude for General Query footprint.\n"); + } + return; + } + + if (radius <= 0 || radius > 9999) { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Invalid radius for General Query footprint.\n"); + } + return; + } + + A->g_footprint_lat = lat; + A->g_footprint_lon = lon; + A->g_footprint_radius = radius; + } + else { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Can't get radius for General Query footprint.\n"); + } + return; + } + } + else { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Can't get longitude for General Query footprint.\n"); + } + return; + } + } + else { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Can't get latitude for General Query footprint.\n"); + } + return; + } + +// TODO: remove debug + + text_color_set(DW_COLOR_DEBUG); + dw_printf("DEBUG: General Query footprint = %.6f %.6f %.2f\n", lat, lon, radius); + + +} /* end aprs_general_query */ + + + +/*------------------------------------------------------------------ + * + * Function: aprs_directed_station_query + * + * Purpose: Decode "Directed Station Query" aimed at specific station. + * This is actually a special format of the more general "message." + * + * Inputs: addressee - To whom it is directed. + * Redundant because it is already in A->addressee. + * + * query - What's left over after ":addressee:?" in info part. + * + * quiet - suppress error messages. + * + * Outputs: A - Decoded packet structure + * A->g_query_type + * A->g_query_callsign (optional) + * + * Description: The caller has already removed the :addressee:? part so we are left + * with a query type of exactly 5 characters and optional "callsign + * of heard station." + * + * Examples from specification, Chapter 15. Our "query" argument. + * + * :KH2Z :?APRSD APRSD + * :KH2Z :?APRSHVN0QBF APRSHVN0QBF + * :KH2Z :?APRST APRST + * :KH2Z :?PING? PING? + * + * "PING?" contains "?" only to pad it out to exactly 5 characters. + * + *------------------------------------------------------------------*/ + +static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet) +{ + char query_type[20]; /* Does the query type always need to be exactly 5 characters? */ + /* If not, how would we know where the extra optional information starts? */ + + char callsign[AX25_MAX_ADDR_LEN]; + + //if (strlen(query) < 5) ... + + +} /* end aprs_directed_station_query */ + /*------------------------------------------------------------------ @@ -2129,7 +2442,7 @@ 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, int quiet) { - strcpy (A->g_msg_type, "Telemetry"); + strlcpy (A->g_msg_type, "Telemetry", sizeof(A->g_msg_type)); telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, A->g_comment); @@ -2156,14 +2469,14 @@ static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet) static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen) { - strcpy (A->g_msg_type, "Raw Touch Tone Data"); + strlcpy (A->g_msg_type, "Raw Touch Tone Data", sizeof(A->g_msg_type)); /* Just copy the info field without the message type. */ if (*info == '{') - strcpy (A->g_comment, info+3); + strlcpy (A->g_comment, info+3, sizeof(A->g_comment)); else - strcpy (A->g_comment, info+1); + strlcpy (A->g_comment, info+1, sizeof(A->g_comment)); } /* end aprs_raw_touch_tone */ @@ -2187,14 +2500,14 @@ static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen) static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen) { - strcpy (A->g_msg_type, "Morse Code Data"); + strlcpy (A->g_msg_type, "Morse Code Data", sizeof(A->g_msg_type)); /* Just copy the info field without the message type. */ if (*info == '{') - strcpy (A->g_comment, info+3); + strlcpy (A->g_comment, info+3, sizeof(A->g_comment)); else - strcpy (A->g_comment, info+1); + strlcpy (A->g_comment, info+1, sizeof(A->g_comment)); } /* end aprs_morse_code */ @@ -2227,7 +2540,7 @@ static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *i } *p; - strcpy (A->g_msg_type, "Positionless Weather Report"); + strlcpy (A->g_msg_type, "Positionless Weather Report", sizeof(A->g_msg_type)); time_t ts = 0; @@ -2289,7 +2602,7 @@ static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *i static int getwdata (char **wpp, char ch, int dlen, float *val) { - char stemp[8]; + char stemp[8]; // larger than maximum dlen. int i; @@ -2318,8 +2631,8 @@ static int getwdata (char **wpp, char ch, int dlen, float *val) } } - strncpy (stemp, (*wpp)+1, dlen); - stemp[dlen] = '\0'; + memset (stemp, 0, sizeof(stemp)); + memcpy (stemp, (*wpp)+1, dlen); *val = atof(stemp); //dw_printf("debug: getwdata returning %f\n", *val); @@ -2372,12 +2685,12 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) // from one of three methods. if (A->g_speed != G_UNKNOWN) { - char ctemp[30]; - sprintf (A->g_weather, "wind %.1f mph", A->g_speed); + snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph", A->g_speed); if (A->g_course != G_UNKNOWN) { - sprintf (ctemp, ", direction %.0f", A->g_course); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", direction %.0f", A->g_course); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } @@ -2393,9 +2706,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) */ if (getwdata (&wp, 'g', 3, &fval)) { if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", gust %.0f", fval); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", gust %.0f", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else { @@ -2407,9 +2720,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) if (getwdata (&wp, 't', 3, &fval)) { if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", temperature %.0f", fval); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", temperature %.0f", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else { @@ -2430,9 +2743,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* r = rainfall, 1/100 inch, last hour */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", rain %.2f in last hour", fval / 100.); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last hour", fval / 100.); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'p', 3, &fval)) { @@ -2440,9 +2753,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* p = rainfall, 1/100 inch, last 24 hours */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", rain %.2f in last 24 hours", fval / 100.); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last 24 hours", fval / 100.); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'P', 3, &fval)) { @@ -2450,9 +2763,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* P = rainfall, 1/100 inch, since midnight */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", rain %.2f since midnight", fval / 100.); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", rain %.2f since midnight", fval / 100.); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'h', 2, &fval)) { @@ -2462,8 +2775,8 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) if (fval != G_UNKNOWN) { char ctemp[30]; if (fval == 0) fval = 100; - sprintf (ctemp, ", humidity %.0f", fval); - strcat (A->g_weather, ctemp); + snprintf (ctemp, sizeof(ctemp), ", humidity %.0f", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'b', 5, &fval)) { @@ -2472,10 +2785,10 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* Here, display as inches of mercury. */ if (fval != G_UNKNOWN) { - char ctemp[30]; + char ctemp[40]; fval = DW_MBAR_TO_INHG(fval * 0.1); - sprintf (ctemp, ", barometer %.2f", fval); - strcat (A->g_weather, ctemp); + snprintf (ctemp, sizeof(ctemp), ", barometer %.2f", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'L', 3, &fval)) { @@ -2483,9 +2796,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* L = Luminosity, watts/ sq meter, 000-999 */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", %.0f watts/m^2", fval); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'l', 3, &fval)) { @@ -2493,9 +2806,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* l = Luminosity, watts/ sq meter, 1000-1999 */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", %.0f watts/m^2", fval + 1000); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval + 1000); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 's', 3, &fval)) { @@ -2506,9 +2819,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* position in the message so there is no confusion. */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", %.1f snow in 24 hours", fval); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", %.1f snow in 24 hours", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 's', 3, &fval)) { @@ -2516,9 +2829,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) /* # = Raw rain counter */ if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", raw rain counter %.f", fval); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", raw rain counter %.f", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } else if (getwdata (&wp, 'X', 3, &fval)) { @@ -2530,9 +2843,9 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) // TODO: decode this properly if (fval != G_UNKNOWN) { - char ctemp[30]; - sprintf (ctemp, ", nuclear Radiation %.f", fval); - strcat (A->g_weather, ctemp); + char ctemp[40]; + snprintf (ctemp, sizeof(ctemp), ", nuclear Radiation %.f", fval); + strlcat (A->g_weather, ctemp, sizeof(A->g_weather)); } } @@ -2554,8 +2867,8 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) * / {UIV32N} */ - strcat (A->g_weather, ", \""); - strcat (A->g_weather, wp); + strlcat (A->g_weather, ", \"", sizeof(A->g_weather)); + strlcat (A->g_weather, wp, sizeof(A->g_weather)); /* * Drop any CR / LF character at the end. */ @@ -2569,10 +2882,11 @@ static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix) A->g_weather[n-1] = '\0'; } - strcat (A->g_weather, "\""); + strlcat (A->g_weather, "\"", sizeof(A->g_weather)); return; -} + +} /* end weather_data */ /*------------------------------------------------------------------ @@ -2631,7 +2945,7 @@ static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) int n; - strcpy (A->g_msg_type, "Ultimeter"); + strlcpy (A->g_msg_type, "Ultimeter", sizeof(A->g_msg_type)); if (*info == '$') { @@ -2660,7 +2974,7 @@ static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) baro = DW_MBAR_TO_INHG(h_baro * 0.1); ohumid = h_ohumid * 0.1; - sprintf (A->g_weather, "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f", + snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f", windpeak, wdir, otemp, baro, ohumid); } } @@ -2701,7 +3015,7 @@ static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen) wdir = (h_wdir & 0xff) * 360. / 256.; otemp = h_otemp * 0.1; - sprintf (A->g_weather, "wind %.1f mph, direction %.0f, temperature %.1f\n", + snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f\n", windpeak, wdir, otemp); } @@ -2729,7 +3043,7 @@ static void third_party_header (decode_aprs_t *A, char *info, int ilen) { int n; - strcpy (A->g_msg_type, "Third Party Header"); + strlcpy (A->g_msg_type, "Third Party Header", sizeof(A->g_msg_type)); /* more later? */ @@ -3202,7 +3516,7 @@ double get_longitude_9 (char *p, int quiet) * * Day/Hours/Minutes (DHM) format is a fixed 7-character field, consisting of * a 6-digit day/time group followed by a single time indicator character (z or - * /). The day/time group consists of a two-digit day-of-the-month (01–31) and + * /). The day/time group consists of a two-digit day-of-the-month (01-31) and * a four-digit time in hours and minutes. * Times can be expressed in zulu (UTC/GMT) or local time. For example: * @@ -3223,12 +3537,12 @@ double get_longitude_9 (char *p, int quiet) * Note: This format may not be used in Status Reports. * * Month/Day/Hours/Minutes (MDHM) format is a fixed 8-character field, - * consisting of the month (01–12) and day-of-the-month (01–31), followed by + * consisting of the month (01-12) and day-of-the-month (01-31), followed by * the time in hours and minutes zulu. For example: * * 10092345 is 23 hours 45 minutes zulu on October 9th. * - * This format is only used in reports from stand-alone “positionless” weather + * This format is only used in reports from stand-alone "positionless" weather * stations (i.e. reports that do not contain station position information). * * @@ -3430,7 +3744,7 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext) int n; if (strlen(pdext) < 7) { - strcpy (A->g_comment, pdext); + strlcpy (A->g_comment, pdext, sizeof(A->g_comment)); return 0; } @@ -3480,7 +3794,7 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext) A->g_height = (1 << (pdext[4] - '0')) * 10; A->g_gain = pdext[5] - '0'; if (pdext[6] >= '0' && pdext[6] <= '8') { - strcpy (A->g_directivity, dir[pdext[6]-'0']); + strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity)); } process_comment (A, pdext+7, -1); @@ -3507,7 +3821,7 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext) A->g_height = (1 << (pdext[4] - '0')) * 10; A->g_gain = pdext[5] - '0'; if (pdext[6] >= '0' && pdext[6] <= '8') { - strcpy (A->g_directivity, dir[pdext[6]-'0']); + strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity)); } process_comment (A, pdext+7, -1); @@ -3554,6 +3868,16 @@ static struct tocalls_s { static int num_tocalls = 0; +// Make sure the array is null terminated. +static const char *search_locations[] = { + (const char *) "tocalls.txt", +#ifndef __WIN32__ + (const char *) "/usr/share/direwolf/tocalls.txt", + (const char *) "/usr/local/share/direwolf/tocalls.txt", +#endif + (const char *) NULL +}; + static int tocall_cmp (const struct tocalls_s *x, const struct tocalls_s *y) { if (x->len != y->len) return (y->len - x->len); @@ -3562,12 +3886,12 @@ static int tocall_cmp (const struct tocalls_s *x, const struct tocalls_s *y) static void decode_tocall (decode_aprs_t *A, char *dest) { - FILE *fp; - int n; + FILE *fp = 0; + int n = 0; static int first_time = 1; char stuff[100]; - char *p; - char *r; + char *p = NULL; + char *r = NULL; //dw_printf("debug: decode_tocall(\"%s\")\n", dest); @@ -3593,12 +3917,13 @@ static void decode_tocall (decode_aprs_t *A, char *dest) if (first_time) { - fp = fopen("tocalls.txt", "r"); -#ifndef __WIN32__ - if (fp == NULL) { - fp = fopen("/usr/share/direwolf/tocalls.txt", "r"); - } -#endif + n = 0; + fp = NULL; + do { + if(search_locations[n] == NULL) break; + fp = fopen(search_locations[n++], "r"); + } while (fp == NULL); + if (fp != NULL) { while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) { @@ -3666,7 +3991,7 @@ static void decode_tocall (decode_aprs_t *A, char *dest) * models before getting to the more generic APY. */ -#if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) +#if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp); #else qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), (__compar_fn_t)tocall_cmp); @@ -3687,8 +4012,7 @@ static void decode_tocall (decode_aprs_t *A, char *dest) for (n=0; ng_mfr, tocalls[n].description, sizeof(A->g_mfr)-1); - A->g_mfr[sizeof(A->g_mfr)-1] = '\0'; + strlcpy (A->g_mfr, tocalls[n].description, sizeof(A->g_mfr)); return; } } @@ -3713,12 +4037,14 @@ static void decode_tocall (decode_aprs_t *A, char *dest) * *------------------------------------------------------------------*/ +// TODO: potential for buffer overflow here. + static void substr_se (char *dest, const char *src, int start, int endp1) { int len = endp1 - start; if (start < 0 || endp1 < 0 || len <= 0) { - strcpy (dest, ""); + dest[0] = '\0'; return; } memcpy (dest, src + start, len); @@ -3823,7 +4149,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) char emsg[100]; #define MAXMATCH 4 regmatch_t match[MAXMATCH]; - char temp[256]; + char temp[sizeof(A->g_comment)]; int keep_going; @@ -3919,15 +4245,37 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) first_time = 0; } - if (clen >= 0) { - assert (clen < sizeof(A->g_comment)); +/* + * If clen is >= 0, take only specified number of characters. + * Otherwise, take it all. + */ + if (clen < 0) { + clen = strlen(pstart); + } + +/* + * Watch out for buffer overflow. + * KG6AZZ reports that there is a local digipeater that seems to + * malfunction ocassionally. It corrupts the packet, as it is + * digipeated, causing the comment to be hundreds of characters long. + */ + + if (clen > sizeof(A->g_comment) - 1) { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Comment is extremely long, %d characters.\n", clen); + dw_printf("Please report this, along with surrounding lines, so we can find the cause.\n"); + } + clen = sizeof(A->g_comment) - 1; + } + + if (clen > 0) { memcpy (A->g_comment, pstart, (size_t)clen); A->g_comment[clen] = '\0'; } else { - strcpy (A->g_comment, pstart); + A->g_comment[0] = '\0'; } - //dw_printf("\nInitial comment='%s'\n", A->g_comment); /* @@ -3974,8 +4322,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } } - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)); } else if (strlen(A->g_name) > 0) { @@ -4028,16 +4376,16 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } } - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)); } else if (regexec (&std_toff_re, A->g_comment, MAXMATCH, match, 0) == 0) { printf ("NO tone\n"); A->g_tone = 0; - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)); } else if (regexec (&std_dcs_re, A->g_comment, MAXMATCH, match, 0) == 0) { @@ -4049,8 +4397,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) A->g_dcs = strtoul (sttemp, NULL, 8); - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } else if (regexec (&std_offset_re, A->g_comment, MAXMATCH, match, 0) == 0) { @@ -4061,8 +4409,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) A->g_offset = 10 * atoi(sttemp); - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } else if (regexec (&std_range_re, A->g_comment, MAXMATCH, match, 0) == 0) { @@ -4080,8 +4428,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) A->g_range = DW_KM_TO_MILES(atoi(sttemp)); } - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } else { keep_going = 0; @@ -4107,8 +4455,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) telemetry_data_base91 (A->g_src, tdata, A->g_telemetry); - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } @@ -4140,16 +4488,16 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) if (d == 'T') { if (a == ' ' && o == ' ') { - sprintf (A->g_aprstt_loc, "APRStt corral location"); + snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt corral location"); } else if (isdigit(a) && o == ' ') { - sprintf (A->g_aprstt_loc, "APRStt location %c of 10", a); + snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c of 10", a); } else if (isdigit(a) && isdigit(o)) { - sprintf (A->g_aprstt_loc, "APRStt location %c%c of 100", a, o); + snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c of 100", a, o); } else if (a == 'B' && isdigit(o)) { - sprintf (A->g_aprstt_loc, "APRStt location %c%c...", a, o); + snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c...", a, o); } } @@ -4194,8 +4542,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } } - strcpy (temp, A->g_comment + match[0].rm_eo); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } /* @@ -4207,12 +4555,12 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); - strcpy (temp, A->g_comment + match[0].rm_eo); + strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp)); A->g_comment[match[0].rm_eo] = '\0'; A->g_altitude = atoi(A->g_comment + match[0].rm_so + 3); - strcpy (A->g_comment + match[0].rm_so, temp); + strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so); } //dw_printf("Final comment='%s'\n", A->g_comment); @@ -4221,8 +4569,9 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * Finally look for something that looks like frequency or CTCSS tone * in the remaining comment. Point this out and suggest the * standardized format. + * Don't complain if we have already found a valid value. */ - if (regexec (&bad_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) + if (A->g_freq == G_UNKNOWN && regexec (&bad_freq_re, A->g_comment, MAXMATCH, match, 0) == 0) { char bad[30]; char good[30]; @@ -4248,7 +4597,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } } - if (regexec (&bad_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) + if (A->g_tone == G_UNKNOWN && regexec (&bad_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) { char bad1[30]; /* original 99.9 or 999.9 format or one of 67 77 100 123 */ char bad2[30]; /* 99.9 or 999.9 format. ".0" appended for special cases. */ @@ -4256,9 +4605,9 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) int i; substr_se (bad1, A->g_comment, match[2].rm_so, match[2].rm_eo); - strcpy (bad2, bad1); + strlcpy (bad2, bad1, sizeof(bad2)); if (strcmp(bad2, "67") == 0 || strcmp(bad2, "77") == 0 || strcmp(bad2, "100") == 0 || strcmp(bad2, "123") == 0) { - strcat (bad2, ".0"); + strlcat (bad2, ".0", sizeof(bad2)); } // TODO: Why wasn't freq/PL recognized here? @@ -4279,7 +4628,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) if (strcmp (s_ctcss[i], bad2) == 0) { if ( ! A->g_quiet) { - sprintf (good, "T%03d", i_ctcss[i]); + snprintf (good, sizeof(good), "T%03d", i_ctcss[i]); text_color_set(DW_COLOR_ERROR); dw_printf("\"%s\" in comment looks like it might be a CTCSS tone in non-standard format.\n", bad1); dw_printf("For most systems to recognize it, use exactly this form \"%s\" at near beginning of comment, after any frequency.\n", good); diff --git a/decode_aprs.h b/decode_aprs.h index ec8975a..6e0ce1f 100644 --- a/decode_aprs.h +++ b/decode_aprs.h @@ -61,6 +61,8 @@ typedef struct decode_aprs_s { char g_name[12]; /* Object or item name. Max. 9 characters. */ char g_addressee[12]; /* Addressee for a "message." Max. 9 characters. */ + /* Also for Directed Station Query which is a */ + /* special case of message. */ float g_speed; /* Speed in MPH. */ @@ -88,7 +90,23 @@ typedef struct decode_aprs_s { int g_dcs; /* Digital coded squelch, print as 3 octal digits. */ - int g_offset; /* Transmit offset, KHz */ + int g_offset; /* Transmit offset, kHz */ + + + char g_query_type[12]; /* General Query: APRS, IGATE, WX, ... */ + /* Addressee is NOT set. */ + + /* Directed Station Query: exactly 5 characters. */ + /* APRSD, APRST, PING?, ... */ + /* Addressee is set. */ + + double g_footprint_lat; /* A general query may contain a foot print. */ + double g_footprint_lon; /* Set all to G_UNKNOWN if not used. */ + float g_footprint_radius; /* Radius in miles. */ + + char g_query_callsign[12]; /* Directed query may contain callsign. */ + /* e.g. tell me all objects from that callsign. */ + char g_weather[500]; /* Weather. Can get quite long. Rethink max size. */ diff --git a/demod.c b/demod.c index a24bcb6..b11a600 100644 --- a/demod.c +++ b/demod.c @@ -191,28 +191,16 @@ int demod_init (struct audio_s *pa) #if __arm__ /* We probably don't have a lot of CPU power available. */ /* Previously we would use F if possible otherwise fall back to A. */ -#if 0 - if (save_audio_config_p->achan[chan].baud == FFF_BAUD && - save_audio_config_p->achan[chan].mark_freq == FFF_MARK_FREQ && - save_audio_config_p->achan[chan].space_freq == FFF_SPACE_FREQ && - save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec == FFF_SAMPLES_PER_SEC) { - just_letters[0] = FFF_PROFILE; - just_letters[1] = '\0'; - } - else { - strcpy (just_letters, "A"); - } -#else /* In version 1.2, new default is E+ /3. */ strcpy (just_letters, "E"); // version 1.2 now E. if (have_plus != -1) have_plus = 1; // Add as default for version 1.2 // If not explicitly turned off. if (save_audio_config_p->achan[chan].decimate == 0) { - save_audio_config_p->achan[chan].decimate = 3; + if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) { + 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 diff --git a/digipeater.c b/digipeater.c index f8230a9..19d1c46 100644 --- a/digipeater.c +++ b/digipeater.c @@ -134,7 +134,6 @@ void digipeater_init (struct audio_s *p_audio_config, struct digi_config_s *p_di void digipeater (int from_chan, packet_t pp) { int to_chan; - packet_t result; // dw_printf ("digipeater()\n"); @@ -156,6 +155,8 @@ void digipeater (int from_chan, packet_t pp) for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan == from_chan) { + packet_t result; + result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], @@ -179,6 +180,8 @@ void digipeater (int from_chan, packet_t pp) for (to_chan=0; to_chanenabled[from_chan][to_chan]) { if (to_chan != from_chan) { + packet_t result; + result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, save_audio_config_p->achan[to_chan].mycall, &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], @@ -227,6 +230,9 @@ void digipeater (int from_chan, packet_t pp) * filter_str - Filter expression string or NULL. * * Returns: Packet object for transmission or NULL. + * The original packet is not modified. (with one exception, probably obsolete) + * We make a copy and return that modified copy! + * This is very important because we could digipeat from one channel to many. * * Description: The packet will be digipeated if the next unused digipeater * field matches one of the following: @@ -262,7 +268,6 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch int ssid; int r; char repeater[AX25_MAX_ADDR_LEN]; - packet_t result = NULL; int err; char err_msg[100]; @@ -276,6 +281,8 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch if (pfilter(from_chan, to_chan, filter_str, pp) != 1) { // TODO1.2: take out debug message +// Actually it turns out to be useful. +// Maybe add a quiet option to suppress it although no one has complained about it yet. //#if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("Packet was rejected for digipeating from channel %d to %d by filter: %s\n", from_chan, to_chan, filter_str); @@ -289,8 +296,8 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch * * The SSID in the Destination Address field of all packets is coded to specify * the APRS digipeater path. - * If the Destination Address SSID is –0, the packet follows the standard AX.25 - * digipeater (“VIA”) path contained in the Digipeater Addresses field of the + * If the Destination Address SSID is -0, the packet follows the standard AX.25 + * digipeater ("VIA") path contained in the Digipeater Addresses field of the * AX.25 frame. * If the Destination Address SSID is non-zero, the packet follows one of 15 * generic APRS digipeater paths. @@ -317,7 +324,7 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch r = ax25_get_first_not_repeated(pp); if (r < AX25_REPEATER_1) { - return NULL; + return (NULL); } ax25_get_addr_with_ssid(pp, r, repeater); @@ -337,7 +344,11 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch */ if (strcmp(repeater, mycall_rec) == 0) { + packet_t result; + result = ax25_dup (pp); + assert (result != NULL); + /* If using multiple radio channels, they */ /* could have different calls. */ ax25_set_addr (result, r, mycall_xmit); @@ -369,7 +380,6 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch text_color_set(DW_COLOR_INFO); dw_printf ("Digipeater: Drop redundant packet to channel %d.\n", to_chan); //#endif - assert (result == NULL); return NULL; } @@ -379,7 +389,11 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch */ err = regexec(alias,repeater,0,NULL,0); if (err == 0) { + packet_t result; + result = ax25_dup (pp); + assert (result != NULL); + ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); @@ -408,8 +422,11 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch if (strcmp(repeater2, mycall_rec) == 0 || regexec(alias,repeater2,0,NULL,0) == 0) { + packet_t result; result = ax25_dup (pp); + assert (result != NULL); + ax25_set_addr (result, r2, mycall_xmit); ax25_set_h (result, r2); @@ -461,14 +478,22 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch */ if (ssid == 1) { + packet_t result; + result = ax25_dup (pp); + assert (result != NULL); + ax25_set_addr (result, r, mycall_xmit); ax25_set_h (result, r); return (result); } if (ssid >= 2 && ssid <= 7) { + packet_t result; + result = ax25_dup (pp); + assert (result != NULL); + ax25_set_ssid(result, r, ssid-1); // should be at least 1 if (ax25_get_num_repeaters(pp) < AX25_MAX_REPEATERS) { @@ -488,8 +513,8 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch /* * Don't repeat it if we get here. */ - assert (result == NULL); - return NULL; + + return (NULL); } @@ -597,6 +622,7 @@ static void test (char *in, char *out) ax25_delete (pp); pp = ax25_from_frame (frame, frame_len, 50); + assert (pp != NULL); ax25_format_addrs (pp, rec); info_len = ax25_get_info (pp, &pinfo); strcat (rec, (char*)pinfo); diff --git a/direwolf.c b/direwolf.c index be7e497..1390bb9 100644 --- a/direwolf.c +++ b/direwolf.c @@ -58,6 +58,7 @@ #include #ifdef __OpenBSD__ #include +#elif __APPLE__ #else #include #endif @@ -100,6 +101,7 @@ #include "dwgps.h" #include "log.h" #include "recv.h" +#include "morse.h" //static int idx_decoded = 0; @@ -112,7 +114,7 @@ static void cleanup_linux (int); static void usage (char **argv); -#if __SSE__ +#if defined(__SSE__) && !defined(__APPLE__) static void __cpuid(int cpuinfo[4], int infotype){ __asm__ __volatile__ ( @@ -120,7 +122,7 @@ static void __cpuid(int cpuinfo[4], int infotype){ "=a" (cpuinfo[0]), "=b" (cpuinfo[1]), "=c" (cpuinfo[2]), - "=d" (cpuinfo[3]) : + "=d" (cpuinfo[3]): "a" (infotype) ); } @@ -179,11 +181,12 @@ int main (int argc, char *argv[]) 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. */ + int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ - strcpy(l_opt, ""); - strcpy(P_opt, ""); + strlcpy(l_opt, "", sizeof(l_opt)); + strlcpy(P_opt, "", sizeof(P_opt)); #if __WIN32__ @@ -226,13 +229,13 @@ int main (int argc, char *argv[]) } } - // TODO: control development/beta/release by versio.h instead of changing here. + // TODO: control development/beta/release by version.h instead of changing here. text_color_init(t_opt); text_color_set(DW_COLOR_INFO); //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - //dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "F", __DATE__); - dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); + 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__ @@ -250,10 +253,13 @@ int main (int argc, char *argv[]) * Try to warn anyone using a CPU from the previous * century rather than just dying for no apparent reason. * + * Apple computers with Intel processors started with P6. Since the + * cpu test code was giving Clang compiler grief it has been excluded. + * * Now, where can I find a Pentium 2 or earlier to test this? */ -#if __SSE__ +#if defined(__SSE__) && !defined(__APPLE__) int cpuinfo[4]; __cpuid (cpuinfo, 0); if (cpuinfo[0] >= 1) { @@ -292,14 +298,14 @@ int main (int argc, char *argv[]) * TODO: Automatically search other places. */ - strcpy (config_file, "direwolf.conf"); + strlcpy (config_file, "direwolf.conf", sizeof(config_file)); /* * Look at command line options. * So far, the only one is the configuration file location. */ - strcpy (input_file, ""); + strlcpy (input_file, "", sizeof(input_file)); while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; @@ -314,7 +320,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:", + c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:", long_options, &option_index); if (c == -1) break; @@ -330,10 +336,19 @@ int main (int argc, char *argv[]) dw_printf("\n"); break; + case 'a': /* -a for audio statistics interval */ + + a_opt = atoi(optarg); + if (a_opt < 0) a_opt = 0; + if (a_opt < 10) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Setting such a small audio statistics interval will produce inaccurate sample rate display.\n"); + } + break; case 'c': /* -c for configuration file name */ - strcpy (config_file, optarg); + strlcpy (config_file, optarg, sizeof(config_file)); break; #if __WIN32__ @@ -360,7 +375,7 @@ int main (int argc, char *argv[]) case 'P': /* -P for modem profile. */ //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg); - strcpy (P_opt, optarg); + strlcpy (P_opt, optarg, sizeof(P_opt)); break; case 'D': /* -D decrease AFSK demodulator sample rate */ @@ -476,9 +491,16 @@ int main (int argc, char *argv[]) case 'l': /* -l for log file directory name */ - strncpy (l_opt, optarg, sizeof(l_opt)-1); + strlcpy (l_opt, optarg, sizeof(l_opt)); break; + case 'S': /* Print symbol tables and exit. */ + + symbols_init (); + symbols_list (); + exit (0); + break; + default: /* Should not be here. */ @@ -497,7 +519,7 @@ int main (int argc, char *argv[]) dw_printf ("Warning: File(s) beyond the first are ignored.\n"); } - strcpy (input_file, argv[optind]); + strlcpy (input_file, argv[optind], sizeof(input_file)); } @@ -544,10 +566,12 @@ int main (int argc, char *argv[]) } } + audio_config.statistics_interval = a_opt; + if (strlen(P_opt) > 0) { /* -P for modem profile. */ /* TODO: Not yet documented. Should probably since it is consistent with atest. */ - strcpy (audio_config.achan[0].profiles, P_opt); + strlcpy (audio_config.achan[0].profiles, P_opt, sizeof(audio_config.achan[0].profiles)); } if (D_opt != 0) { @@ -556,15 +580,18 @@ int main (int argc, char *argv[]) } if (strlen(l_opt) > 0) { - strncpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir)-1); + strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir)); } misc_config.enable_kiss_pt = enable_pseudo_terminal; if (strlen(input_file) > 0) { - strcpy (audio_config.adev[0].adevice_in, input_file); + + strlcpy (audio_config.adev[0].adevice_in, input_file, sizeof(audio_config.adev[0].adevice_in)); + } + /* * Open the audio source * - soundcard @@ -600,6 +627,7 @@ int main (int argc, char *argv[]) * It is the range of the digital sound representation. */ gen_tone_init (&audio_config, 100); + morse_init (&audio_config, 100); assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16); assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2); @@ -727,9 +755,9 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel assert (subchan >= -1 && subchan < MAX_SUBCHANS); assert (pp != NULL); // 1.1J+ - strcpy (display_retries, ""); + strlcpy (display_retries, "", sizeof(display_retries)); if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) { - sprintf (display_retries, " [%s] ", retry_text[(int)retries]); + snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]); } ax25_format_addrs (pp, stemp); @@ -744,7 +772,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel if (ax25_get_num_addr(pp) == 0) { /* Not AX.25. No station to display below. */ h = -1; - strcpy (heard, ""); + strlcpy (heard, "", sizeof(heard)); } else { h = ax25_get_heard(pp); @@ -760,7 +788,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel dw_printf ("Digipeater "); } - char alevel_text[32]; + char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; ax25_alevel_to_text (alevel, alevel_text); @@ -782,6 +810,10 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel dw_printf ("%s (probably %s) audio level = %s %s %s\n", heard, probably_really, alevel_text, display_retries, spectrum); } + else if (strcmp(heard, "DTMF") == 0) { + + dw_printf ("%s audio level = %s tt\n", heard, alevel_text); + } else { dw_printf ("%s audio level = %s %s %s\n", heard, alevel_text, display_retries, spectrum); @@ -895,6 +927,7 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel /* Send to another application if connected. */ +// TODO1.3: Put a wrapper around this so we only call one function to send by all methods. int flen; unsigned char fbuf[AX25_MAX_PACKET_LEN]; @@ -924,23 +957,20 @@ void app_process_rec_packet (int chan, int subchan, packet_t pp, alevel_t alevel 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); - } + igate_send_rec_packet (chan, pp); } + /* Send out a regenerated copy. Applies to all types, not just APRS. */ +/* This was an experimental feature never documented in the User Guide. */ +/* Initial feedback was positive but it fell by the wayside. */ +/* Should follow up with testers and either document this or clean out the clutter. */ digi_regen (chan, pp); /* - *Note that the digipeater function can modify the packet in place so + * 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. @@ -1000,7 +1030,7 @@ static void usage (char **argv) dw_printf ("\n"); dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); dw_printf ("\n"); - dw_printf ("Usage: direwolf [options]\n"); + dw_printf ("Usage: direwolf [options] [ - | stdin | UDP:nnnn ]\n"); dw_printf ("Options:\n"); dw_printf (" -c fname Configuration file name.\n"); dw_printf (" -l logdir Directory name for log files. Use . for current.\n"); @@ -1024,14 +1054,20 @@ static void usage (char **argv) dw_printf (" h h = Heard line with the audio level.\n"); dw_printf (" d d = Decoding of APRS packets.\n"); dw_printf (" -t n Text colors. 1=normal, 0=disabled.\n"); + dw_printf (" -a n Audio statistics interval in seconds. 0 to disable.\n"); #if __WIN32__ #else dw_printf (" -p Enable pseudo terminal for KISS protocol.\n"); #endif dw_printf (" -x Send Xmit level calibration tones.\n"); dw_printf (" -U Print UTF-8 test string and exit.\n"); + dw_printf (" -S Print symbol tables and exit.\n"); dw_printf ("\n"); + dw_printf ("After any options, there can be a single command line argument for the source of\n"); + dw_printf ("received audio. This can overrides the audio input specified in the configuration file.\n"); + dw_printf ("\n"); + #if __WIN32__ #else dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf.\n"); diff --git a/direwolf.h b/direwolf.h index aeac219..07022a9 100644 --- a/direwolf.h +++ b/direwolf.h @@ -62,7 +62,10 @@ #if __WIN32__ #define PTW32_STATIC_LIB -#include "pthreads/pthread.h" +//#include "pthreads/pthread.h" +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) #else #include #endif @@ -153,9 +156,50 @@ typedef pthread_mutex_t dw_mutex_t; } \ } - #endif +/* Platform differences for string functions. */ + + +#if __WIN32__ +char *strsep(char **stringp, const char *delim); +#endif + +//#if __WIN32__ +char *strcasestr(const char *S, const char *FIND); +//#endif + + +#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) + +// strlcpy and strlcat should be in string.h and the C library. + +#else // Use our own copy + + +#define DEBUG_STRL 1 + +#if DEBUG_STRL + +#define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz,__FILE__,__func__,__LINE__) +#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz,__FILE__,__func__,__LINE__) + +size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line); +size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line); + +#else + +#define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz) +#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz) + +size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz); +size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz); + +#endif /* DEBUG_STRL */ + +#endif /* BSD or Apple */ + + #endif /* ifndef DIREWOLF_H */ \ No newline at end of file diff --git a/dlq.c b/dlq.c index 06c876c..f1703d9 100644 --- a/dlq.c +++ b/dlq.c @@ -248,6 +248,14 @@ void dlq_append (dlq_type_t type, int chan, int subchan, packet_t pp, alevel_t a dlq_init (); } + assert (chan >= 0 && chan < MAX_CHANS); + + if (pp == NULL) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("INTERNAL ERROR: dlq_append NULL packet pointer. Please report this!\n"); + return; + } + #if AX25MEMDEBUG if (ax25memdebug_get()) { diff --git a/doc/README.md b/doc/README.md index c118d99..46eaadd 100644 --- a/doc/README.md +++ b/doc/README.md @@ -62,4 +62,10 @@ and a couple things that can be done about it. receiver de-emphasis will cause the amplitudes of the two tones to be different. This makes it more difficult to demodulate them accurately. - 9600 baud operation is an entirely different animal. ... \ No newline at end of file + 9600 baud operation is an entirely different animal. ... + +- [WA8LMF TNC Test CD Results a.k.a. Battle of the TNCs](WA8LMF-TNC-Test-CD-Results.pdf) + + How can we compare how well the TNCs perform under real world conditions? + The de facto standard of measurement is the number of packets decoded from [WA8LMF’s TNC Test CD](http://wa8lmf.net/TNCtest/index.htm). + Many have published the number of packets they have been able to decode from this test. Here they are, all gathered in one place, for your reading pleasure. \ No newline at end of file diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf index 49182a0..655de1b 100644 Binary files a/doc/Raspberry-Pi-APRS.pdf and b/doc/Raspberry-Pi-APRS.pdf differ diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf index ba4f7fd..f0757b0 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/doc/WA8LMF-TNC-Test-CD-Results.pdf b/doc/WA8LMF-TNC-Test-CD-Results.pdf new file mode 100644 index 0000000..c2d2987 Binary files /dev/null and b/doc/WA8LMF-TNC-Test-CD-Results.pdf differ diff --git a/dtime_now.c b/dtime_now.c index 5e08e34..13c8835 100644 --- a/dtime_now.c +++ b/dtime_now.c @@ -13,6 +13,10 @@ #include +#ifdef __APPLE__ +#include +#endif + #if __WIN32__ #include #endif @@ -37,7 +41,14 @@ double dtime_now (void) struct timespec ts; +#ifdef __APPLE__ + struct timeval tp; + gettimeofday(&tp, NULL); + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec = tp.tv_sec; +#else clock_gettime (CLOCK_REALTIME, &ts); +#endif result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); diff --git a/dtmf.c b/dtmf.c index 0102db4..5f1e0fc 100644 --- a/dtmf.c +++ b/dtmf.c @@ -5,7 +5,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 @@ -42,6 +42,7 @@ #include "direwolf.h" #include "dtmf.h" +#include "hdlc_rec.h" // for dcd_change @@ -269,6 +270,11 @@ char dtmf_sample (int c, float input) if (decoded == D->prev_dec) { D->debounced = decoded; + + // Update Data Carrier Detect Indicator. + + dcd_change (c, MAX_SUBCHANS, decoded != ' '); + /* Reset timeout timer. */ if (decoded != ' ') { D->timeout = ((TIMEOUT_SEC) * D->sample_rate) / D->block_size; diff --git a/dw-start.sh b/dw-start.sh index b4829b4..cec1723 100644 --- a/dw-start.sh +++ b/dw-start.sh @@ -24,7 +24,7 @@ sleep 30 # Nothing to do if it is already running. # -a=`ps -ef | grep direwolf | grep -v grep` +a=`pgrep direwolf` if [ "$a" != "" ] then #date >> /tmp/dw-start.log @@ -62,13 +62,13 @@ echo "Start up application." >> /tmp/dw-start.log if [ -x /usr/bin/lxterminal ] then - /usr/bin/lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf" & + /usr/bin/lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf -a 100" & elif [ -x /usr/bin/xterm ] then - /usr/bin/xterm -bg white -fg black -e /usr/local/bin/direwolf & + /usr/bin/xterm -bg white -fg black -e "/usr/local/bin/direwolf -a 100" & elif [ -x /usr/bin/x-terminal-emulator ] then - /usr/bin/x-terminal-emulator -e /usr/local/bin/direwolf & + /usr/bin/x-terminal-emulator -e "/usr/local/bin/direwolf -a 100" & else echo "Did not find an X terminal emulator." fi diff --git a/encode_aprs.c b/encode_aprs.c index fe39f0f..792c0b8 100644 --- a/encode_aprs.c +++ b/encode_aprs.c @@ -1,3 +1,4 @@ + // // This file is part of Dire Wolf, an amateur radio packet TNC. // @@ -342,7 +343,7 @@ static int cse_spd_data_extension (int course, int speed, char *presult) x = course; if (x < 0) x = 0; if (x > 360) x = 360; - sprintf (stemp, "%03d", x); + snprintf (stemp, sizeof(stemp), "%03d", x); memcpy (r->cse, stemp, 3); r->slash = '/'; @@ -350,7 +351,7 @@ static int cse_spd_data_extension (int course, int speed, char *presult) x = speed; if (x < 0) x = 0; if (x > 999) x = 999; - sprintf (stemp, "%03d", x); + snprintf (stemp, sizeof(stemp), "%03d", x); memcpy (r->spd, stemp, 3); return (sizeof(cs_t)); @@ -403,10 +404,11 @@ static int frequency_spec (float freq, float tone, float offset, char *presult) if (freq != 0) { freq_t *f = (freq_t*)presult; - char stemp[12]; + char stemp[12]; /* Frequency should be exactly 7 characters: 999.999 */ + /* Offset shouldbe exactly 4 characters: +999 */ - /* Should use letters for > 999.999. */ - sprintf (stemp, "%07.3f", freq); + /* TODO: Should use letters for > 999.999. */ + snprintf (stemp, sizeof(stemp), "%07.3f", freq); memcpy (f->f, stemp, 7); memcpy (f->mhz, "MHz", 3); f->space = ' '; @@ -422,11 +424,11 @@ static int frequency_spec (float freq, float tone, float offset, char *presult) memcpy(to->ttt, "off", 3); } else { - sprintf (stemp, "%03d", (int)tone); + snprintf (stemp, sizeof(stemp), "%03d", (int)tone); memcpy (to->ttt, stemp, 3); } to->space1 = ' '; - sprintf (stemp, "%+04d", (int)round(offset * 100)); + snprintf (stemp, sizeof(stemp), "%+04d", (int)round(offset * 100)); memcpy (to->oooo, stemp, 4); to->space2 = ' '; @@ -466,8 +468,12 @@ static int frequency_spec (float freq, float tone, float offset, char *presult) * * comment - Additional comment text. * + * result_size - Ammount of space for result, provideed by + * caller, to avoid buffer overflow. * * Outputs: presult - Stored here. Should be at least ??? bytes. + * Could get into hundreds of characters + * because it includes the comment. * * Returns: Number of characters in result. * @@ -503,7 +509,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int int course, int speed, float freq, float tone, float offset, char *comment, - char *presult) + char *presult, size_t result_size) { int result_len = 0; @@ -551,18 +557,23 @@ int encode_position (int messaging, int compressed, double lat, double lon, int /* Be sure it will be converted to 6 digits. */ if (alt_ft < 0) alt_ft = 0; if (alt_ft > 999999) alt_ft = 999999; - sprintf (salt, "/A=%06d", alt_ft); - strcat (presult, salt); + snprintf (salt, sizeof(salt), "/A=%06d", alt_ft); + strlcat (presult, salt, result_size); result_len += strlen(salt); } /* Finally, comment text. */ if (comment != NULL) { - strcat (presult, comment); + strlcat (presult, comment, result_size); result_len += strlen(comment); } + if (result_len >= result_size) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("encode_position result of %d characters won't fit into space provided.\n", result_len); + } + return (result_len); } /* end encode_position */ @@ -596,7 +607,14 @@ int encode_position (int messaging, int compressed, double lat, double lon, int * * comment - Additional comment text. * + * result_size - Ammount of space for result, provideed by + * caller, to avoid buffer overflow. + * * Outputs: presult - Stored here. Should be at least ??? bytes. + * 36 for fixed part, + * 7 for optional extended data, + * ~20 for freq, etc., + * comment ... * * Returns: Number of characters in result. * @@ -622,7 +640,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double int power, int height, int gain, char *dir, int course, int speed, float freq, float tone, float offset, char *comment, - char *presult) + char *presult, size_t result_size) { aprs_object_t *p = (aprs_object_t *) presult; int result_len = 0; @@ -651,7 +669,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double localtime_r (thyme, &tm); #endif - sprintf (p->o.time_stamp, "%02d%02d%02d", tm.tm_mday, tm.tm_hour, tm.tm_min); + snprintf (p->o.time_stamp, sizeof(p->o.time_stamp), "%02d%02d%02d", tm.tm_mday, tm.tm_hour, tm.tm_min); #if XMIT_UTC p->o.time_stamp[6] = 'z'; #else @@ -695,10 +713,15 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double /* Finally, comment text. */ if (comment != NULL) { - strcat (presult, comment); + strlcat (presult, comment, result_size); result_len += strlen(comment); } + if (result_len >= result_size) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("encode_object result of %d characters won't fit into space provided.\n", result_len); + } + return (result_len); } /* end encode_object */ @@ -729,42 +752,42 @@ int main (int argc, char *argv[]) /*********** Position ***********/ encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', - 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result); + 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&") != 0) dw_printf ("ERROR! line %d\n", __LINE__); /* with PHG. */ encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', - 50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result); + 50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) dw_printf ("ERROR! line %d\n", __LINE__); /* with freq. */ encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', - 0, 0, 0, NULL, 0, 0, 146.955, 74.4, -0.6, NULL, result); + 0, 0, 0, NULL, 0, 0, 146.955, 74.4, -0.6, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) dw_printf ("ERROR! line %d\n", __LINE__); /* with course/speed, freq, and comment! */ encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', - 0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result); + 0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR! line %d\n", __LINE__); /* Course speed, no tone, + offset */ encode_position (0, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', - 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result); + 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 River flooding") != 0) dw_printf ("ERROR! line %d\n", __LINE__); /* Course speed, no tone, + offset + altitude */ encode_position (0, 42+34.61/60, -(71+26.47/60), 12345, 'D', '&', - 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result); + 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 /A=012345River flooding") != 0) dw_printf ("ERROR! line %d\n", __LINE__); @@ -772,7 +795,7 @@ int main (int argc, char *argv[]) /*********** Compressed position. ***********/ encode_position (1, 42+34.61/60, -(71+26.47/60), G_UNKNOWN, 'D', '&', - 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result); + 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!D8yKC 0) { + + morse_send (0, str, g_morse_wpm, 100, 100); + } + else { + pp = ax25_from_text (str, 1); + flen = ax25_pack (pp, fbuf); + for (c=0; c 50) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Morse code speed must be in range of 5 to 50 WPM.\n"); + exit (EXIT_FAILURE); + } + break; + case '?': /* Unknown option message was already printed. */ @@ -365,12 +389,19 @@ int main(int argc, char **argv) } + + gen_tone_init (&modem, amplitude/2); + morse_init (&modem, amplitude/2); + assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16); assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2); assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC); + + + /* * Get user packets(s) from file or stdin if specified. * "-n" option is ignored in this case. diff --git a/gen_tone.c b/gen_tone.c index cbf481f..775ca39 100644 --- a/gen_tone.c +++ b/gen_tone.c @@ -288,7 +288,6 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) * *--------------------------------------------------------------------*/ -static void put_sample (int chan, int a, int sam); void tone_gen_put_bit (int chan, int dat) { @@ -319,7 +318,7 @@ void tone_gen_put_bit (int chan, int dat) 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); + gen_tone_put_sample (chan, a, sam); } else { @@ -335,7 +334,7 @@ void tone_gen_put_bit (int chan, int dat) sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); resample[chan] = 0; - put_sample (chan, a, sam); + gen_tone_put_sample (chan, a, sam); } } @@ -349,7 +348,7 @@ void tone_gen_put_bit (int chan, int dat) } -static void put_sample (int chan, int a, int sam) { +void gen_tone_put_sample (int chan, int a, int sam) { /* Ship out an audio sample. */ diff --git a/gen_tone.h b/gen_tone.h index 86a400e..1937c4a 100644 --- a/gen_tone.h +++ b/gen_tone.h @@ -14,3 +14,4 @@ int gen_tone_init (struct audio_s *pp, int amp); void tone_gen_put_bit (int chan, int dat); +void gen_tone_put_sample (int chan, int a, int sam); \ No newline at end of file diff --git a/generic.conf b/generic.conf new file mode 100644 index 0000000..14ad98f --- /dev/null +++ b/generic.conf @@ -0,0 +1,572 @@ +C############################################################# +C# # +C# Configuration file for Dire Wolf # +C# # +L# Linux version # +W# Windows version # +M# Macintosh version # +C# # +C############################################################# +R +R +R The sample config file was getting pretty messy +R with the Windows and Linux differences. +R It would be a maintenance burden to keep most of +R two different versions in sync. +R This common source is now used to generate the +R two different variations while having only a single +R copy of the common parts. +R +R The first column contains one of the following: +R +R R remark which is discarded. +R C common to both versions. +R W Windows version only. +R L Linux version only. +R M Macintosh version and possibly others (portaudio used). +R +C# +C# Consult the User Guide for more details on configuration options. +C# +C# +C# These are the most likely settings you might change: +C# +C# (1) MYCALL - call sign and SSID for your station. +C# +C# Look for lines starting with MYCALL and +C# change NOCALL to your own. +C# +C# (2) PBEACON - enable position beaconing. +C# +C# Look for lines starting with PBEACON and +C# modify for your call, location, etc. +C# +C# (3) DIGIPEATER - configure digipeating rules. +C# +C# Look for lines starting with DIGIPEATER. +C# Most people will probably use the given example. +C# Just remove the "#" from the start of the line +C# to enable it. +C# +C# (4) IGSERVER, IGLOGIN - IGate server and login +C# +C# Configure an IGate client to relay messages between +C# radio and internet servers. +C# +C# +C# The default location is "direwolf.conf" in the current working directory. +L# On Linux, the user's home directory will also be searched. +C# An alternate configuration file location can be specified with the "-c" command line option. +C# +C# As you probably guessed by now, # indicates a comment line. +C# +C# Remove the # at the beginning of a line if you want to use a sample +C# configuration that is currently commented out. +C# +C# Commands are a keyword followed by parameters. +C# +C# Command key words are case insensitive. i.e. upper and lower case are equivalent. +C# +C# Command parameters are generally case sensitive. i.e. upper and lower case are different. +C# +C +C +C############################################################# +C# # +C# FIRST AUDIO DEVICE PROPERTIES # +C# (Channel 0 + 1 if in stereo) # +C# # +C############################################################# +C +C# +C# Many people will simply use the default sound device. +C# Some might want to use an alternative device by chosing it here. +C# +W# When the Windows version starts up, it displays something like +W# this with the available sound devices and capabilities: +W# +W# Available audio input devices for receive (*=selected): +W# * 0: Microphone (C-Media USB Headpho (channel 2) +W# 1: Microphone (Bluetooth SCO Audio +W# 2: Microphone (Bluetooth AV Audio) +W# * 3: Microphone (Realtek High Defini (channels 0 & 1) +W# Available audio output devices for transmit (*=selected): +W# * 0: Speakers (C-Media USB Headphone (channel 2) +W# 1: Speakers (Bluetooth SCO Audio) +W# 2: Realtek Digital Output(Optical) +W# 3: Speakers (Bluetooth AV Audio) +W# * 4: Speakers (Realtek High Definiti (channels 0 & 1) +W# 5: Realtek Digital Output (Realtek +W# +W# Example: To use the microphone and speaker connections on the +W# system board, either of these forms can be used: +W +W#ADEVICE High +W#ADEVICE 3 4 +W +W +W# Example: To use the USB Audio, use a command like this with +W# the input and output device numbers. (Remove the # comment character.) +W#ADEVICE USB +W +W# The position in the list can change when devices (e.g. USB) are added and removed. +W# You can also specify devices by using part of the name. +W# Here is an example of specifying the USB Audio device. +W# This is case-sensitive. Upper and lower case are not treated the same. +W +W#ADEVICE USB +W +W +L# Linux ALSA is complicated. See User Guide for discussion. +L# To use something other than the default, generally use plughw +L# and a card number reported by "arecord -l" command. Example: +L +L# ADEVICE plughw:1,0 +L +L# Starting with version 1.0, you can also use "-" or "stdin" to +L# pipe stdout from some other application such as a software defined +L# radio. You can also specify "UDP:" and an optional port for input. +L# Something different must be specified for output. +L +M# Macintosh Operating System uses portaudio driver for audio +M# input/output. Default device selection not available. User/OP +M# must configure the sound input/output option. Note that +M# the device names can contain spaces. In this case, the names +M# must be enclosed by quotes. +M# +M# Examples: +M# +M# ADEVICE "USB Audio Codec:6" "USB Audio Codec:5" +M# +M# +W# ADEVICE - 0 +W# ADEVICE UDP:7355 0 +L# ADEVICE - plughw:1,0 +L# ADEVICE UDP:7355 default +M# ADEVICE UDP:7355 default +M# +L +L +C +C# +C# Number of audio channels for this souncard: 1 or 2. +C# +C +CACHANNELS 1 +C#ACHANNELS 2 +C +C +C############################################################# +C# # +C# SECOND AUDIO DEVICE PROPERTIES # +C# (Channel 2 + 3 if in stereo) # +C# # +C############################################################# +C +C#ADEVICE1 ... +C +C +C############################################################# +C# # +C# THIRD AUDIO DEVICE PROPERTIES # +C# (Channel 4 + 5 if in stereo) # +C# # +C############################################################# +C +C#ADEVICE2 ... +C +C +C############################################################# +C# # +C# CHANNEL 0 PROPERTIES # +C# # +C############################################################# +C +CCHANNEL 0 +C +C# +C# The following MYCALL, MODEM, PTT, etc. configuration items +C# apply to the most recent CHANNEL. +C# +C +C# +C# Station identifier for this channel. +C# Multiple channels can have the same or different names. +C# +C# It can be up to 6 letters and digits with an optional ssid. +C# The APRS specification requires that it be upper case. +C# +C# Example (don't use this unless you are me): MYCALL WB2OSZ-5 +C# +C +CMYCALL N0CALL +C +C# +C# Pick a suitable modem speed based on your situation. +C# 1200 Most common for VHF/UHF. Default if not specified. +C# 300 Low speed for HF SSB. +C# 9600 High speed - Can't use Microphone and Speaker connections. +C# +C# In the simplest form, just specify the speed. +C# +C +CMODEM 1200 +C#MODEM 300 +C#MODEM 9600 +C +C# +C# These are the defaults should be fine for most cases. In special situations, +C# you might want to specify different AFSK tones or the baseband mode which does +C# not use AFSK. +C# +C#MODEM 1200 1200:2200 +C#MODEM 300 1600:1800 +C#MODEM 9600 0:0 +C# +C# +C# On HF SSB, you might want to use multiple demodulators on slightly different +C# frequencies to compensate for stations off frequency. Here we have 7 different +C# demodulators at 30 Hz intervals. This takes a lot of CPU power so you will +C# probably need to reduce the audio sampling rate with the /n option. +C +C#MODEM 300 1600:1800 7@30 /4 +C +C +C# +C# Uncomment line below to enable the DTMF decoder for this channel. +C# +C +C#DTMF +C +C# +C# If not using a VOX circuit, the transmitter Push to Talk (PTT) +C# control is usually wired to a serial port with a suitable interface circuit. +C# DON'T connect it directly! +C# +C# For the PTT command, specify the device and either RTS or DTR. +C# RTS or DTR may be preceded by "-" to invert the signal. +C# Both can be used for interfaces that want them driven with opposite polarity. +C# +L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on. +L# +C +C#PTT COM1 RTS +C#PTT COM1 RTS -DTR +L#PTT /dev/ttyUSB0 RTS +C +L# +L# On Linux, you can also use general purpose I/O pins if +L# your system is configured for user access to them. +L# This would apply mostly to microprocessor boards, not a regular PC. +L# See separate Raspberry Pi document for more details. +L# The number may be preceded by "-" to invert the signal. +L# +L +L#PTT GPIO 25 +L +C# The Data Carrier Detect (DCD) signal can be sent to the same places +C# as the PTT signal. This could be used to light up an LED like a normal TNC. +C +C#DCD COM1 -DTR +L#DCD GPIO 24 +C +C +C############################################################# +C# # +C# CHANNEL 1 PROPERTIES # +C# # +C############################################################# +C +C#CHANNEL 1 +C +C# +C# Specify MYCALL, MODEM, PTT, etc. configuration items for +C# CHANNEL 1. Repeat for any other channels. +C +C +C############################################################# +C# # +C# TEXT TO SPEECH COMMAND FILE # +C# # +C############################################################# +C +W#SPEECH dwespeak.bat +L#SPEECH dwespeak.sh +C +C +C############################################################# +C# # +C# VIRTUAL TNC SERVER PROPERTIES # +C# # +C############################################################# +C +C# +C# Dire Wolf acts as a virtual TNC and can communicate with +C# client applications by different protocols: +C# +C# - the "AGW TCPIP Socket Interface" - default port 8000 +C# - KISS protocol over TCP socket - default port 8001 +W# - KISS TNC via serial port +L# - KISS TNC via pseudo terminal (-p command line option) +C# +C +CAGWPORT 8000 +CKISSPORT 8001 +C +W# +W# Some applications are designed to operate with only a physical +W# TNC attached to a serial port. For these, we provide a virtual serial +W# port that appears to be connected to a TNC. +W# +W# Take a look at the User Guide for instructions to set up +W# two virtual serial ports named COM3 and COM4 connected by +W# a null modem. +W# +W# Using the configuration described, Dire Wolf will connect to +W# COM3 and the client application will use COM4. +W# +W# Uncomment following line to use this feature. +W +W#NULLMODEM COM3 +W +W +C# +C# It is sometimes possible to recover frames with a bad FCS. +C# This applies to all channels. +C# +C# 0 [NONE] - Don't try to repair. +C# 1 [SINGLE] - Attempt to fix single bit error. (default) +C# 2 [DOUBLE] - Also attempt to fix two adjacent bits. +C# ... see User Guide for more values and in-depth discussion. +C# +C +C#FIX_BITS 0 +C +C# +C############################################################# +C# # +C# BEACONING PROPERTIES # +C# # +C############################################################# +C +C +C# +C# Beaconing is configured with these two commands: +C# +C# PBEACON - for a position report (usually yourself) +C# OBEACON - for an object report (usually some other entity) +C# +C# Each has a series of keywords and values for options. +C# See User Guide for details. +C# +C# Example: +C# +C# This results in a broadcast once every 10 minutes. +C# Every half hour, it can travel via two digipeater hops. +C# The others are kept local. +C# +C +C#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 +C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" +C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" +C +C +C# With UTM coordinates instead of latitude and longitude. +C +C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 +C +C +C# +C# When the destination field is set to "SPEECH" the information part is +C# converted to speech rather than transmitted as a data frame. +C# +C +C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm." +C +C# Similar for Morse code. If SSID is specified, it is multiplied +C# by 2 to get speed in words per minute (WPM). +C +C#CBEACON dest="MORSE-6" info="de MYCALL" +C +C +C# +C# Modify for your particular situation before removing +C# the # comment character from the beginning of appropriate lines above. +C# +C +C +C############################################################# +C# # +C# DIGIPEATER PROPERTIES # +C# # +C############################################################# +C +C# +C# For most common situations, use something like this by removing +C# the "#" from the beginning of the line below. +C# +C +C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE +C +C# See User Guide for more explanation of what this means and how +C# it can be customized for your particular needs. +C +C# Filtering can be used to limit was is digipeated. +C# For example, only weather weather reports, received on channel 0, +C# will be retransmitted on channel 1. +C# +C +C#FILTER 0 1 t/wn +C +C +C############################################################# +C# # +C# INTERNET GATEWAY # +C# # +C############################################################# +C +C# First you need to specify the name of a Tier 2 server. +C# The current preferred way is to use one of these regional rotate addresses: +C +C# noam.aprs2.net - for North America +C# soam.aprs2.net - for South America +C# euro.aprs2.net - for Europe and Africa +C# asia.aprs2.net - for Asia +C# aunz.aprs2.net - for Oceania +C +C#IGSERVER noam.aprs2.net +C +C# You also need to specify your login name and passcode. +C# Contact the author if you can't figure out how to generate the passcode. +C +C#IGLOGIN WB2OSZ-5 123456 +C +C# That's all you need for a receive only IGate which relays +C# messages from the local radio channel to the global servers. +C +C# Some might want to send an IGate client position directly to a server +C# without sending it over the air and relying on someone else to +C# forward it to an IGate server. This is done by using sendto=IG rather +C# than a radio channel number. Overlay R for receive only, T for two way. +C +C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W +C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W +C +C +C# To relay messages from the Internet to radio, you need to add +C# one more option with the transmit channel number and a VIA path. +C +C#IGTXVIA 0 WIDE1-1 +C +C# You might want to apply a filter for what packets will be obtained from the server. +C# Read about filters here: http://www.aprs-is.net/javaprsfilter.aspx +C# Example, positions and objects within 50 km of my location: +C +C#IGFILTER m/50 +C +C# That is known as a server-side filter. It is processed by the IGate server. +C# You can also apply local filtering to limit what will be transmitted on the +C# RF side. For example, transmit only "messages" on channel 0 and weather +C# reports on channel 1. +C +C#FILTER IG 0 t/m +C#FILTER IG 1 t/wn +C +C# Finally, we don't want to flood the radio channel. +C# The IGate function will limit the number of packets transmitted +C# during 1 minute and 5 minute intervals. If a limit would +C# be exceeded, the packet is dropped and message is displayed in red. +C +CIGTXLIMIT 6 10 +C +C +C############################################################# +C# # +C# APRStt GATEWAY # +C# # +C############################################################# +C +C# +C# Dire Wolf can receive DTMF (commonly known as Touch Tone) +C# messages and convert them to packet objects. +C# +C# See separate "APRStt-Implementation-Notes" document for details. +C# +C +C# +C# Sample gateway configuration based on: +C# +C# http://www.aprs.org/aprstt/aprstt-coding24.txt +C# http://www.aprs.org/aprs-jamboree-2013.html +C# +C +C# Define specific points. +C +CTTPOINT B01 37^55.37N 81^7.86W +CTTPOINT B7495088 42.605237 -71.34456 +CTTPOINT B934 42.605237 -71.34456 +C +CTTPOINT B901 42.661279 -71.364452 +CTTPOINT B902 42.660411 -71.364419 +CTTPOINT B903 42.659046 -71.364452 +CTTPOINT B904 42.657578 -71.364602 +C +C +C# For location at given bearing and distance from starting point. +C +CTTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi +C +C# For location specified by x, y coordinates. +C +CTTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W +C +C# UTM location for Lowell-Dracut-Tyngsborough State Forest. +C +CTTUTM B6xxxyyy 19T 10 300000 4720000 +C +C +C +C# Location for the corral. +C +CTTCORRAL 37^55.50N 81^7.00W 0^0.02N +C +C# Compact messages - Fixed locations xx and object yyy where +C# Object numbers 100 - 199 = bicycle +C# Object numbers 200 - 299 = fire truck +C# Others = dog +C +CTTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy +CTTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy +CTTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy +C +CTTMACRO z Cz +C +C# Receive on channel 0, Transmit object reports on channel 1 with optional via path. +C# You probably want to put in a transmit delay on the APRStt channel so it +C# it doesn't start sending a response before the user releases PTT. +C# This is in 10 ms units so 100 means 1000 ms = 1 second. +C +C#TTOBJ 0 1 WIDE1-1 +C#CHANNEL 0 +C#DWAIT 100 +C +C# Advertise gateway position with beacon. +C +C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway" +C +C +C# Sample speech responses. +C# Default is Morse code "R" for received OK and "?" for all errors. +C +C#TTERR OK SPEECH Message Received. +C#TTERR D_MSG SPEECH D not implemented. +C#TTERR INTERNAL SPEECH Internal error. +C#TTERR MACRO_NOMATCH SPEECH No definition for digit sequence. +C#TTERR BAD_CHECKSUM SPEECH Bad checksum on call. +C#TTERR INVALID_CALL SPEECH Invalid callsign. +C#TTERR INVALID_OBJNAME SPEECH Invalid object name. +C#TTERR INVALID_SYMBOL SPEECH Invalid symbol. +C#TTERR INVALID_LOC SPEECH Invalid location. +C#TTERR NO_CALL SPEECH No call or object name. +C#TTERR SATSQ SPEECH Satellite square must be 4 digits. +C \ No newline at end of file diff --git a/grm_sym.h b/grm_sym.h index c15f122..11fb4fa 100644 --- a/grm_sym.h +++ b/grm_sym.h @@ -87,7 +87,7 @@ sym_user_exit = 177, /* user exit symbol */ sym_flag = 178, /* flag symbol */ sym_circle_x = 179, /* circle with x in the center */ sym_open_24hr = 180, /* open 24 hours symbol */ -sym_fhs_facility = 181, /* U Fishing Hot Spots™ Facility */ +sym_fhs_facility = 181, /* U Fishing Hot Spots(tm) Facility */ sym_bot_cond = 182, /* Bottom Conditions */ sym_tide_pred_stn = 183, /* Tide/Current Prediction Station */ sym_anchor_prohib = 184, /* U anchor prohibited symbol */ diff --git a/hdlc_rec.c b/hdlc_rec.c index e645ee1..249f5e3 100644 --- a/hdlc_rec.c +++ b/hdlc_rec.c @@ -112,7 +112,6 @@ 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); /*********************************************************************************** @@ -558,7 +557,10 @@ int hdlc_rec_gathering (int chan, int subchan) * state for the channel. * * Inputs: chan - * subchan + * + * subchan 0 to MAX_SUBCHANS-1 for HDLC. + * MAX_SUBCHANS for DTMF decoder. + * * state 1 for active, 0 for not. * * Returns: None. Use ??? to retrieve result. @@ -566,17 +568,18 @@ int hdlc_rec_gathering (int chan, int subchan) * 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. + * version 1.3: Add DTMF detection into the final result. + * This is now called from dtmf.c too. * *--------------------------------------------------------------------*/ -static void dcd_change (int chan, int subchan, int state) +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 (subchan >= 0 && subchan <= MAX_SUBCHANS); assert (state == 0 || state == 1); #if DEBUG3 diff --git a/hdlc_rec.h b/hdlc_rec.h index 647dd77..3f390c2 100644 --- a/hdlc_rec.h +++ b/hdlc_rec.h @@ -2,8 +2,6 @@ #include "audio.h" -//#include "rrbb.h" - void hdlc_rec_init (struct audio_s *pa); @@ -11,7 +9,6 @@ void hdlc_rec_init (struct audio_s *pa); void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state); - /* Provided elsewhere to process a complete frame. */ //void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level); @@ -26,5 +23,6 @@ int hdlc_rec_gathering (int chan, int subchan); /* Transmit needs to know when someone else is transmitting. */ +void dcd_change (int chan, int subchan, int state); int hdlc_rec_data_detect_any (int chan); diff --git a/igate.c b/igate.c index 9bdec10..1f019d6 100644 --- a/igate.c +++ b/igate.c @@ -85,9 +85,8 @@ #include #include #include - #include - +#include #include "direwolf.h" #include "ax25_pad.h" @@ -153,7 +152,7 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S 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, + snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1, sa4->sin_addr.S_un.S_un_b.s_b2, sa4->sin_addr.S_un.S_un_b.s_b3, sa4->sin_addr.S_un.S_un_b.s_b4); @@ -164,7 +163,7 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S case AF_INET6: sa6 = (struct sockaddr_in6 *)pAddr; #if __WIN32__ - sprintf (pStringBuf, "%x:%x:%x:%x:%x:%x:%x:%x", + snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x", ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]), ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]), @@ -178,9 +177,9 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S #endif break; default: - sprintf (pStringBuf, "Invalid address family!"); + snprintf (pStringBuf, StringBufSize, "Invalid address family!"); } - assert (strlen(pStringBuf) < StringBufSize); + //assert (strlen(pStringBuf) < StringBufSize); return pStringBuf; } @@ -198,19 +197,19 @@ int main (int argc, char *argv[]) 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"); + strlcpy (audio_config.achan[0].mycall, "WB2OSZ-1", sizeof(audio_config.achan[0].mycall)); + strlcpy (audio_config.achan[1].mycall, "WB2OSZ-2", sizeof(audio_config.achan[0].mycall)); memset (&igate_config, 0, sizeof(igate_config)); - strcpy (igate_config.t2_server_name, "localhost"); + strlcpy (igate_config.t2_server_name, "localhost", sizeof(igate_config.t2_server_name)); igate_config.t2_server_port = 14580; - strcpy (igate_config.t2_login, "WB2OSZ-JL"); - strcpy (igate_config.t2_passcode, "-1"); + strlcpy (igate_config.t2_login, "WB2OSZ-JL", sizeof(igate_config.t2_login)); + strlcpy (igate_config.t2_passcode, "-1", sizeof(igate_config.t2_passcode)); igate_config.t2_filter = strdup ("r/1/2/3"); igate_config.tx_chan = 0; - strcpy (igate_config.tx_via, ",WIDE2-1"); + strlcpy (igate_config.tx_via, ",WIDE2-1", sizeof(igate_config.tx_via)); igate_config.tx_limit_1 = 3; igate_config.tx_limit_5 = 5; @@ -314,6 +313,35 @@ static int stats_tx_igate_packets; /* Number of packets from IGate server. */ static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ /* after rate limiting or other restrictions. */ +/* We have some statistics. What do we do with them? + + + IGate stations often send packets like this: + + t2_server_port); + snprintf (server_port_str, sizeof(server_port_str), "%d", save_igate_config_p->t2_server_port); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("DEBUG: igate connect_thread start, port = %d = '%s'\n", save_igate_config_p->t2_server_port, server_port_str); @@ -704,14 +732,14 @@ static void * connnect_thread (void *arg) */ SLEEP_SEC(3); - sprintf (stemp, "user %s pass %s vers Dire-Wolf %d.%d", + snprintf (stemp, sizeof(stemp), "user %s pass %s vers Dire-Wolf %d.%d", save_igate_config_p->t2_login, save_igate_config_p->t2_passcode, MAJOR_VERSION, MINOR_VERSION); if (save_igate_config_p->t2_filter != NULL) { - strcat (stemp, " filter "); - strcat (stemp, save_igate_config_p->t2_filter); + strlcat (stemp, " filter ", sizeof(stemp)); + strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp)); } - strcat (stemp, "\r\n"); + strlcat (stemp, "\r\n", sizeof(stemp)); send_msg_to_server (stemp); /* Delay until it is ok to start sending packets. */ @@ -739,7 +767,7 @@ static void * connnect_thread (void *arg) char heartbeat[10]; - strcpy (heartbeat, "#\r\n"); + strlcpy (heartbeat, "#\r\n", sizeof(heartbeat)); /* This will close the socket if any error. */ send_msg_to_server (heartbeat); @@ -809,7 +837,8 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) } - /* Count only while connected. */ + /* Gather statistics. */ + stats_rf_recv_packets++; /* @@ -817,6 +846,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) */ pp = ax25_dup (recv_pp); + assert (pp != NULL); /* * Third party frames require special handling to unwrap payload. @@ -915,7 +945,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) /* * Someone around here occasionally sends a packet with no information part. */ - if (strlen(pinfo) == 0) { + if (strlen((char*)pinfo) == 0) { #if DEBUGx text_color_set(DW_COLOR_DEBUG); @@ -946,11 +976,11 @@ 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, save_audio_config_p->achan[chan].mycall); - strcat (msg, ":"); - strcat (msg, (char*)pinfo); - strcat (msg, "\r\n"); + strlcat (msg, ",qAR,", sizeof(msg)); + strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg)); + strlcat (msg, ":", sizeof(msg)); + strlcat (msg, (char*)pinfo, sizeof(msg)); + strlcat (msg, "\r\n", sizeof(msg)); send_msg_to_server (msg); stats_rx_igate_packets++; @@ -1205,10 +1235,11 @@ static void * igate_recv_thread (void *arg) static void xmit_packet (char *message) { packet_t pp3; - char payload[500]; /* what is max len? */ + char payload[AX25_MAX_PACKET_LEN]; /* 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!!! */ + int to_chan = save_igate_config_p->tx_chan; /* Should be -1 if not configured for xmit!!! */ + /* Future: Array of boolean to allow multiple xmit channels? */ /* * Is IGate to Radio direction enabled? @@ -1219,12 +1250,12 @@ static void xmit_packet (char *message) stats_tx_igate_packets++; - assert (save_igate_config_p->tx_chan >= 0 && save_igate_config_p->tx_chan < MAX_CHANS); + assert (to_chan >= 0 && to_chan < MAX_CHANS); /* * Try to parse it into a packet object. * Bug: Up to 8 digipeaters are allowed in radio format. - * There is a potential of finding more here. + * There is a potential of finding a larger number here. */ pp3 = ax25_from_text(message, 0); if (pp3 == NULL) { @@ -1239,8 +1270,6 @@ static void xmit_packet (char *message) * 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) { @@ -1281,9 +1310,11 @@ static void xmit_packet (char *message) /* * Convert to text representation. */ + memset (payload, 0, sizeof(payload)); + ax25_format_addrs (pp3, payload); info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo)); - strcat (payload, pinfo); + strlcat (payload, pinfo, sizeof(payload)); #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("Tx IGate: payload=%s\n", payload); @@ -1296,23 +1327,38 @@ static void xmit_packet (char *message) char radio [500]; packet_t pradio; - sprintf (radio, "%s>%s%d%d%s:}%s", + snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s", save_audio_config_p->achan[save_igate_config_p->tx_chan].mycall, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, save_igate_config_p->tx_via, payload); pradio = ax25_from_text (radio, 1); + + /* Oops. Didn't have a check for NULL here. */ + /* Could this be the cause of rare and elusive crashes in 1.2? */ + + if (pradio != NULL) { + #if ITEST - text_color_set(DW_COLOR_XMIT); - dw_printf ("Xmit: %s\n", radio); - ax25_delete (pradio); + text_color_set(DW_COLOR_XMIT); + dw_printf ("Xmit: %s\n", radio); + ax25_delete (pradio); #else - /* This consumes packet so don't reference it again! */ - tq_append (save_igate_config_p->tx_chan, TQ_PRIO_1_LO, pradio); + /* This consumes packet so don't reference it again! */ + tq_append (save_igate_config_p->tx_chan, TQ_PRIO_1_LO, pradio); #endif - stats_rf_xmit_packets++; - ig_to_tx_remember (pp3); + stats_rf_xmit_packets++; + ig_to_tx_remember (pp3); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Received invalid packet from IGate.\n"); + dw_printf ("%s\n", payload); + dw_printf ("Will not attempt to transmit third party packet.\n"); + dw_printf ("%s\n", radio); + } + } ax25_delete (pp3); diff --git a/kiss.c b/kiss.c index bc81dfc..30f5779 100644 --- a/kiss.c +++ b/kiss.c @@ -84,7 +84,7 @@ * might be limited to these four choices. * * The documentation instucts the user to install the com0com - * “Null-modem emulator” from http://sourceforge.net/projects/com0com/ + * "Null-modem emulator" from http://sourceforge.net/projects/com0com/ * and configure it for COM3 & COM4. * * By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!) @@ -955,7 +955,7 @@ static THREAD_F kiss_listen_thread (void *arg) #if __WIN32__ return(0); #else - return; /* Unreachable but avoids compiler warning. */ + return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */ #endif } diff --git a/kiss_frame.c b/kiss_frame.c index 66efe85..d5da290 100644 --- a/kiss_frame.c +++ b/kiss_frame.c @@ -131,6 +131,12 @@ void kiss_frame_init (struct audio_s *pa) * Inputs: in - Address of input block. * First byte is the "type indicator" with type and * channel but we don't care about that here. + * + * This seems cumbersome and confusing to have this + * one byte offset when encapsulating an AX.25 frame. + * Maybe the type/channel byte should be passed in + * as a separate argument. + * * Note that this is "binary" data and can contain * nul (0x00) values. Don't treat it like a text string! * @@ -176,6 +182,7 @@ int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out) } /* end kiss_encapsulate */ +#ifndef WALK96 /*------------------------------------------------------------------- * @@ -659,4 +666,6 @@ main () #endif +#endif /* WALK96 */ + /* end kiss_frame.c */ diff --git a/kissnet.c b/kissnet.c index f3a7633..90c9f97 100644 --- a/kissnet.c +++ b/kissnet.c @@ -382,6 +382,7 @@ static void * connect_listen_thread (void *arg) socklen_t sockaddr_size = sizeof(struct sockaddr_in); int kiss_port = (int)(long)arg; int listen_sock; + int bcopt = 1; listen_sock= socket(AF_INET,SOCK_STREAM,0); if (listen_sock == -1) { @@ -390,6 +391,14 @@ static void * connect_listen_thread (void *arg) return (NULL); } + /* Version 1.3 - as suggested by G8BPQ. */ + /* Without this, if you kill the application then try to run it */ + /* again quickly the port number is unavailable for a while. */ + /* Don't try doing the same thing On Windows; It has a different meaning. */ + /* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */ + + setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4); + sockaddr.sin_addr.s_addr = INADDR_ANY; sockaddr.sin_port = htons(kiss_port); sockaddr.sin_family = AF_INET; diff --git a/latlong.c b/latlong.c index 696ce5f..98875bb 100644 --- a/latlong.c +++ b/latlong.c @@ -559,7 +559,7 @@ double ll_distance_km (double lat1, double lon1, double lat2, double lon2) * * Purpose: Convert Maidenhead locator to latitude and longitude. * - * Inputs: maidenhead - 4 or 6 character grid square. + * Inputs: maidenhead - 2, 4, 6, or 8 character grid square locator. * * Outputs: dlat, dlon - Latitude and longitude. * Original values unchanged if error. @@ -591,9 +591,9 @@ int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon) char mh[16]; - if (strlen(maidenhead) != 4 && strlen(maidenhead) != 6) { + if (strlen(maidenhead) != 2 && strlen(maidenhead) != 4 && strlen(maidenhead) != 6 && strlen(maidenhead) != 8) { text_color_set(DW_COLOR_ERROR); - dw_printf("Maidenhead locator \"%s\" must 4 or 6 characters in this context.\n", maidenhead); + dw_printf("Maidenhead locator \"%s\" must 2, 4, 6, or 8 characters.\n", maidenhead); return (0); } @@ -607,11 +607,6 @@ int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon) 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 */ @@ -619,55 +614,68 @@ int ll_from_grid_square (char *maidenhead, double *dlat, double *dlon) 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 */ + if (strlen(mh) >= 4) { - 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') { + if ( ! isdigit(mh[2]) || ! isdigit(mh[3]) ) { 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); + dw_printf("The second pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead); return (0); } - /* Lon: 2 deg / 24 squares = 5 minutes / square */ - /* Lat: 1 deg / 24 squares = 2.5 minutes / square */ + /* Lon: 20 deg / 10 squares = 2 deg / square */ + /* Lat: 10 deg / 10 squares = 1 deg / square */ - lon += (mh[4] - 'A') * 5.0 / 60.0; - lat += (mh[5] - 'A') * 2.5 / 60.0; + lon += (mh[2] - '0') * 2; + lat += (mh[3] - '0'); - if (strlen(mh) >= 8) { - if ( ! isdigit(mh[6]) || ! isdigit(mh[7]) ) { + 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 fourth pair of characters in Maidenhead locator \"%s\" must be digits.\n", maidenhead); + dw_printf("The third pair of characters in Maidenhead locator \"%s\" must be in range of A thru X.\n", maidenhead); return (0); } - /* Lon: 5 min / 10 squares = 0.5 minutes / square */ - /* Lat: 2.5 min / 10 squares = 0.25 minutes / square */ + /* Lon: 2 deg / 24 squares = 5 minutes / square */ + /* Lat: 1 deg / 24 squares = 2.5 minutes / square */ - lon += (mh[6] - '0') * 0.50 / 60.0; - lat += (mh[7] - '0') * 0.25 / 60.0; + lon += (mh[4] - 'A') * 5.0 / 60.0; + lat += (mh[5] - 'A') * 2.5 / 60.0; - lon += 0.250 / 60.0; /* Move from corner to center of square */ - lat += 0.125 / 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 += 2.5 / 60.0; /* Move from corner to center of square */ - lat += 1.25 / 60.0; + lon += 1.0; /* Move from corner to center of square */ + lat += 0.5; } } else { - lon += 1.0; /* Move from corner to center of square */ - lat += 0.5; + lon += 10; /* Move from corner to center of square */ + lat += 5; } //text_color_set(DW_COLOR_DEBUG); diff --git a/man1/direwolf.1 b/man1/direwolf.1 index 69859af..750941a 100644 --- a/man1/direwolf.1 +++ b/man1/direwolf.1 @@ -113,6 +113,14 @@ Send Xmit level calibration tones. .B "-U " Print UTF-8 test string and exit. +.TP +.B "-S " +Print Symbol tables and exit. + +.TP +.BI "-a " "n" +Report audio device statistics each n seconds. + .SH EXAMPLES gqrx (2.3 and later) has the ability to send streaming audio through a UDP socket to another application for further processing. diff --git a/mgn_icon.h b/mgn_icon.h index 9ffb6f7..3c96aab 100644 --- a/mgn_icon.h +++ b/mgn_icon.h @@ -5,7 +5,7 @@ * * Waypoint icon codes for use in the $PMGNWPL sentence. * - * Derived from Data Transmission Protocol For Magellan Products – version 2.11 + * Derived from Data Transmission Protocol For Magellan Products - version 2.11 */ #define MGN_crossed_square "a" diff --git a/misc/README-dire-wolf.txt b/misc/README-dire-wolf.txt index 36e7e28..140f960 100644 --- a/misc/README-dire-wolf.txt +++ b/misc/README-dire-wolf.txt @@ -1,9 +1,34 @@ -This is NOT used for the Linux version. -These are part of the standard C library for Linux and Cygwin. + +Files in this directory fill in the gaps missing for some operating systems. + + +-------------------------------------- + +These are part of the standard C library for Linux and similar operating systems. For the Windows version we need to include our own copy. -They were copied from Cygwin source: +They were copied from Cygwin source. +/usr/src/cygwin-1.7.10-1/newlib/libc/string/... - /usr/src/cygwin-1.7.10-1/newlib/libc/string/strsep.c - /usr/src/cygwin-1.7.10-1/newlib/libc/string/strtok_r.c + strsep.c + strtok_r.c +-------------------------------------- + +This was also missing on Windows but available everywhere else. + + strcasestr.c + +-------------------------------------- + + +The are used for the Linux and Windows versions. +They should be part of the standard C library for OpenBSD, FreeBSD, Mac OS X. +These are from OpenBSD. +http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcpy.c +http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcat.c + + + strlcpy.c + strlcat.c + \ No newline at end of file diff --git a/misc/strlcat.c b/misc/strlcat.c new file mode 100644 index 0000000..c08c62d --- /dev/null +++ b/misc/strlcat.c @@ -0,0 +1,127 @@ + + +/*------------------------------------------------------------------ + * + * Module: strlcat.c + * + * Purpose: Safe string functions to guard against buffer overflow. + * + * Description: The size of character strings, especially when coming from the + * outside, can sometimes exceed a fixed size storage area. + * + * There was one case where a MIC-E format packet had an enormous + * comment that exceeded an internal buffer of 256 characters, + * resulting in a crash. + * + * We are not always meticulous about checking sizes to avoid overflow. + * Use of these functions, instead of strcpy and strcat, should + * help avoid issues. + * + * Orgin: From OpenBSD as the copyright notice indicates. + * The GNU folks didn't think it was appropriate for inclusion + * in glibc. https://lwn.net/Articles/507319/ + * + * Modifications: Added extra debug output when strings are truncated. + * Not sure if I will leave this in the release version + * or just let it happen silently. + * + *---------------------------------------------------------------*/ + + + +#include +#include +#include "direwolf.h" +#include "textcolor.h" + + +/* $NetBSD: strlcat.c,v 1.5 2014/10/31 18:59:32 spz Exp $ */ +/* from NetBSD: strlcat.c,v 1.16 2003/10/27 00:12:42 lukem Exp */ +/* from OpenBSD: strlcat.c,v 1.10 2003/04/12 21:56:39 millert Exp */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ + +#if DEBUG_STRL +size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line) +#else +size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz) +#endif +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + size_t retval; + +#if DEBUG_STRL + if (dst == NULL) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("ERROR: strlcat dst is NULL. (%s %s %d)\n", file, func, line); + return (0); + } + if (src == NULL) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("ERROR: strlcat src is NULL. (%s %s %d)\n", file, func, line); + return (0); + } + if (siz == 1 || siz == 4) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Suspicious strlcat siz. Is it using sizeof a pointer variable? (%s %s %d)\n", file, func, line); + } +#endif + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) { + retval = dlen + strlen(s); + goto the_end; + } + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + retval = dlen + (s - src); /* count does not include NUL */ +the_end: + +#if DEBUG_STRL + if (retval >= siz) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("WARNING: strlcat result length %d exceeds maximum length %d. (%s %s %d)\n", + (int)retval, (int)(siz-1), file, func, line); + } +#endif + return (retval); +} \ No newline at end of file diff --git a/misc/strlcpy.c b/misc/strlcpy.c new file mode 100644 index 0000000..64a18c1 --- /dev/null +++ b/misc/strlcpy.c @@ -0,0 +1,119 @@ + + +/*------------------------------------------------------------------ + * + * Module: strlcpy.c + * + * Purpose: Safe string functions to guard against buffer overflow. + * + * Description: The size of character strings, especially when coming from the + * outside, can sometimes exceed a fixed size storage area. + * + * There was one case where a MIC-E format packet had an enormous + * comment that exceeded an internal buffer of 256 characters, + * resulting in a crash. + * + * We are not always meticulous about checking sizes to avoid overflow. + * Use of these functions, instead of strcpy and strcat, should + * help avoid issues. + * + * Orgin: From OpenBSD as the copyright notice indicates. + * The GNU folks didn't think it was appropriate for inclusion + * in glibc. https://lwn.net/Articles/507319/ + * + * Modifications: Added extra debug output when strings are truncated. + * Not sure if I will leave this in the release version + * or just let it happen silently. + * + *---------------------------------------------------------------*/ + + + +/* $NetBSD: strlcpy.c,v 1.5 2014/10/31 18:59:32 spz Exp $ */ +/* from NetBSD: strlcpy.c,v 1.14 2003/10/27 00:12:42 lukem Exp */ +/* from OpenBSD: strlcpy.c,v 1.7 2003/04/12 21:56:39 millert Exp */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#include +#include +#include "direwolf.h" +#include "textcolor.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ + +#if DEBUG_STRL +size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line) +#else +size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz) +#endif +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t retval; + +#if DEBUG_STRL + if (dst == NULL) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("ERROR: strlcpy dst is NULL. (%s %s %d)\n", file, func, line); + return (0); + } + if (src == NULL) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("ERROR: strlcpy src is NULL. (%s %s %d)\n", file, func, line); + return (0); + } + if (siz == 1 || siz == 4) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Suspicious strlcpy siz. Is it using sizeof a pointer variable? (%s %s %d)\n", file, func, line); + } +#endif + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + retval = s - src - 1; /* count does not include NUL */ + +#if DEBUG_STRL + if (retval >= siz) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("WARNING: strlcpy result length %d exceeds maximum length %d. (%s %s %d)\n", + (int)retval, (int)(siz-1), file, func, line); + } +#endif + return (retval); +} + diff --git a/morse.c b/morse.c index bc7ea80..0c490ef 100644 --- a/morse.c +++ b/morse.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 @@ -35,47 +35,32 @@ #include #include #include +#include #include +#include -#if __WIN32__ -#include -#else -#include -#include -#include -#endif #include "direwolf.h" #include "textcolor.h" #include "audio.h" #include "ptt.h" - - -#define WPM 10 -#define TIME_UNITS_TO_MS(tu,wpm) (((tu)*1200)/(wpm)) - - -// TODO : should be in .h file. +#include "gen_tone.h" /* for gen_tone_put_sample */ +#include "morse.h" /* - * Delay from PTT on to start of first character. - * Currently the only anticipated use for this is - * APRStt responses. In this case, we want an adequate - * delay for someone to press the # button, release - * the PTT button, and start listening for a response. + * Might get ambitious and make this adjustable some day. + * Good enough for now. */ -#define MORSE_TXDELAY_MS 1500 -/* - * Delay from end of last character to PTT off. - * Avoid chopping off the last element. - */ -#define MORSE_TXTAIL_MS 200 +#define MORSE_TONE 800 + +#define TIME_UNITS_TO_MS(tu,wpm) (((tu)*1200.0)/(wpm)) + static const struct morse_s { char ch; - char enc[7]; + char enc[8]; /* $ has 7 elements */ } morse[] = { { 'A', ".-" }, { 'B', "-..." }, @@ -117,19 +102,103 @@ static const struct morse_s { { '.', ".-.-.-" }, { ',', "--..--" }, { '?', "..--.." }, - { '/', "-..-." } + { '/', "-..-." }, + + { '=', "-...-" }, /* from ARRL */ + { '-', "-....-" }, + { ')', "-.--.-" }, /* does not distinguish open/close */ + { ':', "---..." }, + { ';', "-.-.-." }, + { '"', ".-..-." }, + { '\'', ".----." }, + { '$', "...-..-" }, + + { '!', "-.-.--" }, /* more from wikipedia */ + { '(', "-.--." }, + { '&', ".-..." }, + { '+', ".-.-." }, + { '_', "..--.-" }, + { '@', ".--.-." }, + }; #define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s)) -static void morse_tone (int tu); -static void morse_quiet (int tu); +static void morse_tone (int chan, int tu, int wpm); +static void morse_quiet (int chan, int tu, int wpm); +static void morse_quiet_ms (int chan, int ms); static int morse_lookup (int ch); static int morse_units_ch (int ch); static int morse_units_str (char *str); +/* + * Properties of the digitized sound stream. + */ + +static struct audio_s *save_audio_config_p; + + +/* Constants after initialization. */ + +#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) + +static short sine_table[256]; + + + +/*------------------------------------------------------------------ + * + * Name: morse_init + * + * Purpose: Initialize for tone generation. + * + * Inputs: audio_config_p - Pointer to audio configuration structure. + * + * The fields we care about are: + * + * samples_per_sec + * + * amp - Signal amplitude on scale of 0 .. 100. + * + * 100 will produce maximum amplitude of +-32k samples. + * + * Returns: 0 for success. + * -1 for failure. + * + * Description: Precompute a sine wave table. + * + *----------------------------------------------------------------*/ + + +int morse_init (struct audio_s *audio_config_p, int amp) +{ + int j; + +/* + * Save away modem parameters for later use. + */ + + save_audio_config_p = audio_config_p; + + for (j=0; j<256; j++) { + double a; + int s; + + a = ((double)(j) / 256.0) * (2 * M_PI); + s = (int) (sin(a) * 32767.0 * amp / 100.0); + + /* 16 bit sound sample is in range of -32768 .. +32767. */ + assert (s >= -32768 && s <= 32767); + sine_table[j] = s; + } + + return (0); + +} /* end morse_init */ + + /*------------------------------------------------------------------- * * Name: morse_send @@ -159,7 +228,11 @@ int morse_send (int chan, char *str, int wpm, int txdelay, int txtail) int time_units; char *p; + time_units = 0; + + morse_quiet_ms (chan, txdelay); + for (p = str; *p != '\0'; p++) { int i; @@ -169,37 +242,38 @@ int morse_send (int chan, char *str, int wpm, int txdelay, int txtail) for (e = morse[i].enc; *e != '\0'; e++) { if (*e == '.') { - morse_tone (1); + morse_tone (chan,1,wpm); time_units++; } else { - morse_tone (3); + morse_tone (chan,3,wpm); time_units += 3; } if (e[1] != '\0') { - morse_quiet (1); + morse_quiet (chan,1,wpm); time_units++; } } } else { - morse_quiet (1); + morse_quiet (chan,1,wpm); time_units++; } if (p[1] != '\0') { - morse_quiet (3); + morse_quiet (chan,3,wpm); time_units += 3; } } + morse_quiet_ms (chan, txtail); + if (time_units != morse_units_str(str)) { dw_printf ("morse: Internal error. Inconsistent length, %d vs. %d calculated.\n", time_units, morse_units_str(str)); } - return (txdelay + - TIME_UNITS_TO_MS(time_units, wpm) + + (int) (TIME_UNITS_TO_MS(time_units, wpm) + 0.5) + txtail); } /* end morse_send */ @@ -212,18 +286,47 @@ int morse_send (int chan, char *str, int wpm, int txdelay, int txtail) * * Purpose: Generate tone for specified number of time units. * - * Inputs: tu - Number of time units. + * Inputs: chan - Radio channel. + * tu - Number of time units. Should be 1 or 3. + * wpm - Speed in WPM. * *--------------------------------------------------------------------*/ -static void morse_tone (int tu) { - int num_cycles; - int n; +static void morse_tone (int chan, int tu, int wpm) { +#if MTEST1 + int n; for (n=0; nachan[chan].valid); + + tone_phase = 0; + + f1_change_per_sample = (int) (((double)MORSE_TONE * TICKS_PER_CYCLE / (double)save_audio_config_p->adev[a].samples_per_sec ) + 0.5); + + nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); + + for (j=0; j> 24) & 0xff]; + gen_tone_put_sample (chan, a, sam); + + }; + +#endif } /* end morse_tone */ @@ -235,20 +338,75 @@ static void morse_tone (int tu) { * * Purpose: Generate silence for specified number of time units. * - * Inputs: tu - Number of time units. + * Inputs: chan - Radio channel. + * tu - Number of time units. + * wpm - Speed in WPM. * *--------------------------------------------------------------------*/ -static void morse_quiet (int tu) { - int n; +static void morse_quiet (int chan, int tu, int wpm) { + +#if MTEST1 + int n; for (n=0; nachan[chan].valid); + + nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); + + for (j=0; jachan[chan].valid); + + nsamples = (int) ((ms * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); + + for (j=0; jnmea_port - name of serial port. * * Global output: nmea_port_fd @@ -112,15 +104,9 @@ void nmea_set_debug (int n) * Description: (1) Open serial port device. * (2) Start a new thread to listen for GPS receiver. * - * Reference: http://www.robbayer.com/files/serial-win.pdf - * *---------------------------------------------------------------*/ - - -static MYFDTYPE nmea_open_port (char *device); - void nmea_init (struct misc_config_s *mc) { @@ -132,24 +118,12 @@ void nmea_init (struct misc_config_s *mc) /* * Open serial port connection. + * 4800 baud is standard for GPS. + * Should add an option to allow changing someday. */ if (strlen(mc->nmea_port) > 0) { -#if ! __WIN32__ - - /* Translate Windows device name into Linux name. */ - /* COM1 -> /dev/ttyS0, etc. */ - - if (strncasecmp(mc->nmea_port, "COM", 3) == 0) { - int n = atoi (mc->nmea_port + 3); - text_color_set(DW_COLOR_INFO); - dw_printf ("Converted NMEA device '%s'", mc->nmea_port); - if (n < 1) n = 1; - sprintf (mc->nmea_port, "/dev/ttyS%d", n-1); - dw_printf (" to Linux equivalent '%s'\n", mc->nmea_port); - } -#endif - nmea_port_fd = nmea_open_port (mc->nmea_port); + nmea_port_fd = serial_port_open (mc->nmea_port, 4800); if (nmea_port_fd != MYFDERROR) { #if __WIN32__ @@ -180,138 +154,6 @@ void nmea_init (struct misc_config_s *mc) } -/* - * Returns fd for serial port or MYFDERROR for error. - */ - - -static MYFDTYPE nmea_open_port (char *devicename) -{ - -#if __WIN32__ - - MYFDTYPE fd; - DCB dcb; - int ok; - char bettername[50]; - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("nmea_open_port ( '%s' )\n", devicename); -#endif - - -// Need to use FILE_FLAG_OVERLAPPED for full duplex operation. -// Without it, write blocks when waiting on read. - -// Read http://support.microsoft.com/kb/156932 - -// Bug fix in release 1.1 - Need to munge name for COM10 and up. -// http://support.microsoft.com/kb/115831 - - strcpy (bettername, devicename); - if (strncasecmp(devicename, "COM", 3) == 0) { - int n; - n = atoi(devicename+3); - if (n >= 10) { - strcpy (bettername, "\\\\.\\"); - strcat (bettername, devicename); - } - } - - fd = CreateFile(bettername, GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - - if (fd == MYFDERROR) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("ERROR - Could not open NMEA port %s.\n", devicename); - return (MYFDERROR); - } - - /* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */ - - memset (&dcb, 0, sizeof(dcb)); - dcb.DCBlength = sizeof(DCB); - - ok = GetCommState (fd, &dcb); - if (! ok) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("nmea_open_port: GetCommState failed.\n"); - } - - /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */ - - dcb.DCBlength = sizeof(DCB); - dcb.BaudRate = CBR_4800; - dcb.fBinary = 1; - dcb.fParity = 0; - dcb.fOutxCtsFlow = 0; - dcb.fOutxDsrFlow = 0; - dcb.fDtrControl = DTR_CONTROL_DISABLE; - dcb.fDsrSensitivity = 0; - dcb.fOutX = 0; - dcb.fInX = 0; - dcb.fErrorChar = 0; - dcb.fNull = 0; /* Don't drop nul characters! */ - dcb.fRtsControl = 0; - dcb.ByteSize = 8; - dcb.Parity = NOPARITY; - dcb.StopBits = ONESTOPBIT; - - ok = SetCommState (fd, &dcb); - if (! ok) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("nmea_open_port: SetCommState failed.\n"); - } - - text_color_set(DW_COLOR_INFO); - dw_printf("NMEA communication started on %s.\n", devicename); - -#else - -/* Linux version. */ - - int fd; - struct termios ts; - int e; - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("nmea_open_port ( '%s' )\n", devicename); -#endif - - fd = open (devicename, O_RDWR); - - if (fd == MYFDERROR) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("ERROR - Could not open NMEA port %s.\n", devicename); - return (MYFDERROR); - } - - e = tcgetattr (fd, &ts); - if (e != 0) { perror ("nm tcgetattr"); } - - cfmakeraw (&ts); - - ts.c_cc[VMIN] = 1; /* wait for at least one character */ - ts.c_cc[VTIME] = 0; /* no fancy timing. */ - - cfsetispeed (&ts, B4800); - cfsetospeed (&ts, B4800); - - e = tcsetattr (fd, TCSANOW, &ts); - if (e != 0) { perror ("nmea tcsetattr"); } - - text_color_set(DW_COLOR_INFO); - dw_printf("NMEA communication started on %s.\n", devicename); - -#endif - - return (fd); -} - - /*------------------------------------------------------------------- * @@ -575,7 +417,7 @@ https://freepository.com:444/50lItuLQ7fW6s-web/browser/Tracker2/trunk/sources/wa -Data Transmission Protocol For Magellan Products – version 2.11 +Data Transmission Protocol For Magellan Products - version 2.11 @@ -637,45 +479,7 @@ static void nmea_send_sentence (char *sent) // TODO: need to append CR LF. - -#if __WIN32__ - - DWORD nwritten; - - /* Without this, write blocks while we are waiting on a read. */ - static OVERLAPPED ov_wr; - memset (&ov_wr, 0, sizeof(ov_wr)); - - if ( ! WriteFile (nmea_port_fd, sent, len, &nwritten, &ov_wr)) - { - err = GetLastError(); - if (err != ERROR_IO_PENDING) - { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError sending NMEA sentence. Error %d.\n\n", (int)GetLastError()); - //CloseHandle (nmea_port_fd); - //nmea_port_fd = MYFDERROR; - } - } - else if (nwritten != len) - { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError sending NMEA sentence. Only %d of %d written.\n\n", (int)nwritten, len); - //CloseHandle (nmea_port_fd); - //nmea_port_fd = MYFDERROR; - } - -#else - err = write (nmea_port_fd, sent, (size_t)len); - if (err != len) - { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nError sending NMEA sentence. err=%d\n\n", err); - //close (nmea_port_fd); - //nmea_port_fd = MYFDERROR; - } -#endif - + serial_port_write (nmea_port_fd, sent, len); } /* nmea_send_sentence */ @@ -695,109 +499,6 @@ static void nmea_send_sentence (char *sent) * *--------------------------------------------------------------------*/ -//TODO: should pass fd by reference so it can be zapped. -//BUG: If we close it here, that fact doesn't get back -// to the main receiving thread. - -/* Return one byte (value 0 - 255) or terminate thread on error. */ - - -static int nmea_get (MYFDTYPE fd) -{ - unsigned char ch; - -#if __WIN32__ /* Native Windows version. */ - - DWORD n; - static OVERLAPPED ov_rd; - - memset (&ov_rd, 0, sizeof(ov_rd)); - ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); - - - /* Overlapped I/O makes reading rather complicated. */ - /* See: http://msdn.microsoft.com/en-us/library/ms810467.aspx */ - - /* It seems that the read completes OK with a count */ - /* of 0 every time we send a message to the serial port. */ - - n = 0; /* Number of characters read. */ - - while (n == 0) { - - if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd)) - { - int err1 = GetLastError(); - - if (err1 == ERROR_IO_PENDING) - { - /* Wait for completion. */ - - if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0) - { - if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1)) - { - int err3 = GetLastError(); - - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nKISS GetOverlappedResult error %d.\n\n", err3); - } - else - { - /* Success! n should be 1 */ - } - } - } - else - { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1); - //CloseHandle (fd); - //fd = MYFDERROR; - //pthread_exit (NULL); - } - } - - } /* end while n==0 */ - - CloseHandle(ov_rd.hEvent); - - if (n != 1) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\nNMEA failed to get one byte. n=%d.\n\n", (int)n); - -#if DEBUG9 - fprintf (log_fp, "n=%d\n", n); -#endif - } - - -#else /* Linux version */ - - int n; - - n = read(fd, &ch, (size_t)1); - - if (n != 1) { - //text_color_set(DW_COLOR_ERROR); - //dw_printf ("\nError receiving kiss message from client application. Closing connection %d.\n\n", fd); - - close (fd); - - fd = MYFDERROR; - pthread_exit (NULL); - } - -#endif - -#if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("nmea_get(%d) returns 0x%02x\n", fd, ch); -#endif - - return (ch); -} - // Maximum length of message from GPS receiver. // 82 according to some people. Larger to be safe. @@ -823,9 +524,25 @@ static void * nmea_listen_thread (void *arg) while (1) { - unsigned char ch; + int ch; + + ch = serial_port_get1(fd); + + +// TODO: if ch < 0, terminate thread ... + + //CloseHandle (fd); + //fd = MYFDERROR; + //pthread_exit (NULL); + + //text_color_set(DW_COLOR_ERROR); + //dw_printf ("\nError trying to read from GPS receiver. Closing connection %d.\n\n", fd); + + //close (fd); -> serial_port_close(fd) + + //fd = MYFDERROR; + //pthread_exit (NULL); - ch = nmea_get(fd); if (ch == '$') { // Start of new sentence. @@ -982,7 +699,14 @@ static void nmea_parse_gps (char *sentence) text_color_set (DW_COLOR_INFO); dw_printf("%d %.6f %.6f %.1f %.0f %.1f\n", fix, g_lat, g_lon, g_speed, g_course, g_alt); - + +#if WALK96 + // TODO: Need to design a proper interface. + + extern void walk96 (int fix, double lat, double lon, float knots, float course, float alt); + + walk96 (fix, g_lat, g_lon, g_speed, g_course, g_alt); +#endif } // $GPGGA has altitude. diff --git a/pfilter.c b/pfilter.c index db5fc65..0578890 100644 --- a/pfilter.c +++ b/pfilter.c @@ -133,7 +133,7 @@ static int filt_s (pfstate_t *pf); * 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. + * filter - String of filter specs and logical operators to combine them. * * pp - Packet object handle. * @@ -152,12 +152,28 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp) char *p; int result; + assert (from_chan >= 0 && from_chan <= MAX_CHANS); + assert (to_chan >= 0 && to_chan <= MAX_CHANS); + + if (pp == NULL) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n"); + return (-1); + } + if (filter == NULL) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n"); + return (-1); + } + pfstate.from_chan = from_chan; pfstate.to_chan = to_chan; /* Copy filter string, removing any control characters. */ + + memset (pfstate.filter_str, 0, sizeof(pfstate.filter_str)); strncpy (pfstate.filter_str, filter, MAX_FILTER_LEN-1); - pfstate.filter_str[MAX_FILTER_LEN-1] = '\0'; + pfstate.nexti = 0; for (p = pfstate.filter_str; *p != '\0'; p++) { if (iscntrl(*p)) { @@ -206,11 +222,11 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp) * 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 '|'. + * Future: Maybe allow words like 'OR' as alternatives to symbols like '|'. * * Unresolved Issue: * - * Adding the special operattors adds a new complication. + * Adding the special operators adds a new complication. * How do we handle the case where we want those characters in * a filter specification? For example how do we know if the * last character of /#& means HF gateway or AND the next part @@ -242,32 +258,32 @@ static void next_token (pfstate_t *pf) if (pf->filter_str[pf->nexti] == '\0') { pf->token_type = TOKEN_EOL; - strcpy (pf->token_str, "end-of-line"); + strlcpy (pf->token_str, "end-of-line", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '&') { pf->nexti++; pf->token_type = TOKEN_AND; - strcpy (pf->token_str, "\"&\""); + strlcpy (pf->token_str, "\"&\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '|') { pf->nexti++; pf->token_type = TOKEN_OR; - strcpy (pf->token_str, "\"|\""); + strlcpy (pf->token_str, "\"|\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '!') { pf->nexti++; pf->token_type = TOKEN_NOT; - strcpy (pf->token_str, "\"!\""); + strlcpy (pf->token_str, "\"!\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == '(') { pf->nexti++; pf->token_type = TOKEN_LPAREN; - strcpy (pf->token_str, "\"(\""); + strlcpy (pf->token_str, "\"(\"", sizeof(pf->token_str)); } else if (pf->filter_str[pf->nexti] == ')') { pf->nexti++; pf->token_type = TOKEN_RPAREN; - strcpy (pf->token_str, "\")\""); + strlcpy (pf->token_str, "\")\"", sizeof(pf->token_str)); } else { char *p = pf->token_str; @@ -488,7 +504,7 @@ static int parse_filter_spec (pfstate_t *pf) else { char stemp[80]; - sprintf (stemp, "Unrecognized filter type '%c'", pf->token_str[0]); + snprintf (stemp, sizeof(stemp), "Unrecognized filter type '%c'", pf->token_str[0]); print_error (pf, stemp); result = -1; } @@ -535,7 +551,7 @@ static int filt_bodgu (pfstate_t *pf, char *arg) char *v; int result = 0; - strcpy (str, pf->token_str); + strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; @@ -590,13 +606,16 @@ static int filt_bodgu (pfstate_t *pf, char *arg) static int filt_t (pfstate_t *pf) { - char src[MAX_TOKEN_LEN]; - char *infop; + char src[AX25_MAX_ADDR_LEN]; + char *infop = NULL; char *f; + memset (src, 0, sizeof(src)); ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src); (void) ax25_get_info (pf->pp, (unsigned char **)(&infop)); + assert (infop != NULL); + for (f = pf->token_str + 2; *f != '\0'; f++) { switch (*f) { @@ -678,6 +697,12 @@ static int filt_t (pfstate_t *pf) infop[2] == src[1] && infop[3] == src[2]) return (1); break; + + default: + + print_error (pf, "Invalid letter in t/ filter.\n"); + return (-1); + break; } } return (0); /* Didn't match anything. Reject */ @@ -690,7 +715,7 @@ static int filt_t (pfstate_t *pf) * * Name: filt_r * - * Purpose: Is it in range of given location. + * Purpose: Is it in range (kilometers) of given location. * * Inputs: pf - Pointer to current state information. * token_str should contain something of format: @@ -718,7 +743,7 @@ static int filt_r (pfstate_t *pf) double dlat, dlon, ddist, km; - strcpy (str, pf->token_str); + strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; @@ -781,9 +806,9 @@ static int filt_r (pfstate_t *pf) * * 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. + * "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. @@ -801,7 +826,7 @@ static int filt_s (pfstate_t *pf) char *pri, *alt, *over; - strcpy (str, pf->token_str); + strlcpy (str, pf->token_str, sizeof(str)); sep[0] = str[1]; sep[1] = '\0'; cp = str + 2; @@ -869,19 +894,19 @@ static void print_error (pfstate_t *pf, char *msg) if (pf->from_chan == MAX_CHANS) { if (pf->to_chan == MAX_CHANS) { - sprintf (intro, "filter[IG,IG]: "); + snprintf (intro, sizeof(intro), "filter[IG,IG]: "); } else { - sprintf (intro, "filter[IG,%d]: ", pf->to_chan); + snprintf (intro, sizeof(intro), "filter[IG,%d]: ", pf->to_chan); } } else { if (pf->to_chan == MAX_CHANS) { - sprintf (intro, "filter[%d,IG]: ", pf->from_chan); + snprintf (intro, sizeof(intro), "filter[%d,IG]: ", pf->from_chan); } else { - sprintf (intro, "filter[%d,%d]: ", pf->from_chan, pf->to_chan); + snprintf (intro, sizeof(intro), "filter[%d,%d]: ", pf->from_chan, pf->to_chan); } } @@ -903,7 +928,7 @@ static void print_error (pfstate_t *pf, char *msg) * * 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 + * 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 tt_text.c misc.a regex.a && ./pftest * * *--------------------------------------------------------------------*/ diff --git a/ptt.c b/ptt.c index f412971..7752f01 100644 --- a/ptt.c +++ b/ptt.c @@ -35,6 +35,12 @@ * * Version 1.1: Add parallel printer port for x86 Linux only. * + * This is hardcoded to use the primary motherboard parallel + * printer port at I/O address 0x378. This might work with + * a PCI card configured to use the same address if the + * motherboard does not have a built in parallel port. + * It won't work with a USB-to-parallel-printer-port adapter. + * * Version 1.2: More than two radio channels. * Generalize for additional signals besides PTT. * diff --git a/serial_port.c b/serial_port.c new file mode 100644 index 0000000..799f4fc --- /dev/null +++ b/serial_port.c @@ -0,0 +1,451 @@ + +// TODO: Needs more clean up and testing of error conditions. + +// TODO: use this in place of other similar code. + + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2014, 2015 John Langner, WB2OSZ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + + +//#define DEBUG 1 + +/*------------------------------------------------------------------ + * + * Module: serial.c + * + * Purpose: Interface to serial port, hiding operating system differences. + * + *---------------------------------------------------------------*/ + +#include + +#if __WIN32__ + +#include +#include + +#else + +#define __USE_XOPEN2KXSI 1 +#define __USE_XOPEN 1 +#include +#include +#include +#include +#include +#include +#include + +#endif + +#include +#include + +#include "direwolf.h" +#include "textcolor.h" +#include "serial_port.h" + + + + +/*------------------------------------------------------------------- + * + * Name: serial_port_open + * + * Purpose: Open serial port. + * + * Inputs: devicename - For Windows, usually like COM5. + * For Linux, usually /dev/tty... + * "COMn" also allowed and converted to /dev/ttyS(n-1) + * + * baud - Speed. 4800, 9600, etc. + * + * Returns Handle for serial port or MYFDERROR for error. + * + *---------------------------------------------------------------*/ + + +MYFDTYPE serial_port_open (char *devicename, int baud) +{ + +#if __WIN32__ + + MYFDTYPE fd; + DCB dcb; + int ok; + char bettername[50]; + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("serial_port_open ( '%s', %d )\n", devicename, baud); +#endif + + +// Reference: http://www.robbayer.com/files/serial-win.pdf + +// Need to use FILE_FLAG_OVERLAPPED for full duplex operation. +// Without it, write blocks when waiting on read. + +// Read http://support.microsoft.com/kb/156932 + +// Bug fix in release 1.1 - Need to munge name for COM10 and up. +// http://support.microsoft.com/kb/115831 + + strcpy (bettername, devicename); + if (strncasecmp(devicename, "COM", 3) == 0) { + int n; + n = atoi(devicename+3); + if (n >= 10) { + strcpy (bettername, "\\\\.\\"); + strcat (bettername, devicename); + } + } + + fd = CreateFile(bettername, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + + if (fd == MYFDERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - Could not open serial port %s.\n", devicename); + return (MYFDERROR); + } + + /* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */ + + memset (&dcb, 0, sizeof(dcb)); + dcb.DCBlength = sizeof(DCB); + + ok = GetCommState (fd, &dcb); + if (! ok) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("serial_port_open: GetCommState failed.\n"); + } + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */ + + dcb.DCBlength = sizeof(DCB); + + switch (baud) { + + case 1200: dcb.BaudRate = CBR_1200; break; + case 2400: dcb.BaudRate = CBR_2400; break; + case 4800: dcb.BaudRate = CBR_4800; break; + case 9600: dcb.BaudRate = CBR_9600; break; + case 19200: dcb.BaudRate = CBR_19200; break; + case 38400: dcb.BaudRate = CBR_38400; break; + case 57600: dcb.BaudRate = CBR_57600; break; + case 115200: dcb.BaudRate = CBR_115200; break; + + default: text_color_set(DW_COLOR_ERROR); + dw_printf ("serial_port_open: Unsupported speed %d. Using 4800.\n", baud); + dcb.BaudRate = CBR_4800; + break; + } + + dcb.fBinary = 1; + dcb.fParity = 0; + dcb.fOutxCtsFlow = 0; + dcb.fOutxDsrFlow = 0; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fDsrSensitivity = 0; + dcb.fOutX = 0; + dcb.fInX = 0; + dcb.fErrorChar = 0; + dcb.fNull = 0; /* Don't drop nul characters! */ + dcb.fRtsControl = 0; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + + ok = SetCommState (fd, &dcb); + if (! ok) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("serial_port_open: SetCommState failed.\n"); + } + + //text_color_set(DW_COLOR_INFO); + //dw_printf("Successful serial port open on %s.\n", devicename); + +#else + +/* Linux version. */ + + int fd; + struct termios ts; + int e; + char linuxname[50]; + + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("serial_port_open ( '%s' )\n", devicename); +#endif + + /* Translate Windows device name into Linux name. */ + /* COM1 -> /dev/ttyS0, etc. */ + + strcpy (linuxname, devicename); + + if (strncasecmp(devicename, "COM", 3) == 0) { + int n = atoi (devicename + 3); + text_color_set(DW_COLOR_INFO); + dw_printf ("Converted serial port name '%s'", devicename); + if (n < 1) n = 1; + sprintf (linuxname, "/dev/ttyS%d", n-1); + dw_printf (" to Linux equivalent '%s'\n", linuxname); + } + + fd = open (linuxname, O_RDWR); + + if (fd == MYFDERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR - Could not open serial port %s.\n", linuxname); + return (MYFDERROR); + } + + e = tcgetattr (fd, &ts); + if (e != 0) { perror ("tcgetattr"); } + + cfmakeraw (&ts); + + ts.c_cc[VMIN] = 1; /* wait for at least one character */ + ts.c_cc[VTIME] = 0; /* no fancy timing. */ + + switch (baud) { + + case 1200: cfsetispeed (&ts, B1200); cfsetospeed (&ts, B1200); break; + case 2400: cfsetispeed (&ts, B2400); cfsetospeed (&ts, B2400); break; + case 4800: cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800); break; + case 9600: cfsetispeed (&ts, B9600); cfsetospeed (&ts, B9600); break; + case 19200: cfsetispeed (&ts, B19200); cfsetospeed (&ts, B19200); break; + case 38400: cfsetispeed (&ts, B38400); cfsetospeed (&ts, B38400); break; + case 57600: cfsetispeed (&ts, B57600); cfsetospeed (&ts, B57600); break; + case 115200: cfsetispeed (&ts, B115200); cfsetospeed (&ts, B115200); break; + + default: text_color_set(DW_COLOR_ERROR); + dw_printf ("serial_port_open: Unsupported speed %d. Using 4800.\n", baud); + cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800); + break; + } + + e = tcsetattr (fd, TCSANOW, &ts); + if (e != 0) { perror ("tcsetattr"); } + + //text_color_set(DW_COLOR_INFO); + //dw_printf("Successfully opened serial port %s.\n", devicename); + +#endif + + return (fd); +} + + + + +/*------------------------------------------------------------------- + * + * Name: serial_port_write + * + * Purpose: Send characters to serial port. + * + * Inputs: fd - Handle from open. + * str - Pointer to array of bytes. + * len - Number of bytes to write. + * + * Returns Number of bytes written. Should be the same as len. + * -1 if error. + * + *---------------------------------------------------------------*/ + + +int serial_port_write (MYFDTYPE fd, char *str, int len) +{ + + if (fd == MYFDERROR) { + return (-1); + } + +#if __WIN32__ + + DWORD nwritten; + + /* Without this, write blocks while we are waiting on a read. */ + static OVERLAPPED ov_wr; + memset (&ov_wr, 0, sizeof(ov_wr)); + + if ( ! WriteFile (fd, str, len, &nwritten, &ov_wr)) + { + int err = GetLastError(); + + if (err != ERROR_IO_PENDING) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error writing to serial port. Error %d.\n\n", err); + } + } + else if (nwritten != len) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len); + } + + return (nwritten); + +#else + int written; + + written = write (fd, str, (size_t)len); + if (written != len) + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error writing to serial port. err=%d\n\n", written); + return (-1); + } + + return (written); +#endif + + +} /* serial_port_write */ + + + +/*------------------------------------------------------------------- + * + * Name: serial_port_get1 + * + * Purpose: Get one byte from the serial port. Wait if not ready. + * + * Inputs: fd - Handle from open. + * + * Returns: Value of byte in range of 0 to 255. + * -1 if error. + * + *--------------------------------------------------------------------*/ + +int serial_port_get1 (MYFDTYPE fd) +{ + unsigned char ch; + +#if __WIN32__ /* Native Windows version. */ + + DWORD n; + static OVERLAPPED ov_rd; + + memset (&ov_rd, 0, sizeof(ov_rd)); + ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); + + + /* Overlapped I/O makes reading rather complicated. */ + /* See: http://msdn.microsoft.com/en-us/library/ms810467.aspx */ + + /* It seems that the read completes OK with a count */ + /* of 0 every time we send a message to the serial port. */ + + n = 0; /* Number of characters read. */ + + while (n == 0) { + + if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd)) + { + int err1 = GetLastError(); + + if (err1 == ERROR_IO_PENDING) + { + /* Wait for completion. */ + + if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0) + { + if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1)) + { + int err3 = GetLastError(); + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Serial Port GetOverlappedResult error %d.\n\n", err3); + } + else + { + /* Success! n should be 1 */ + } + } + } + else + { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Serial port read error %d.\n", err1); + return (-1); + } + } + + } /* end while n==0 */ + + CloseHandle(ov_rd.hEvent); + + if (n != 1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Serial port failed to get one byte. n=%d.\n\n", (int)n); + } + + +#else /* Linux version */ + + int n; + + n = read(fd, &ch, (size_t)1); + + if (n != 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("serial_port_get1(%d) returns -1 for error.\n", fd); + return (-1); + } + +#endif + +#if DEBUGx + text_color_set(DW_COLOR_DEBUG); + if (isprint(ch)) { + dw_printf ("serial_port_get1(%d) returns 0x%02x = '%c'\n", fd, ch, ch); + } + else { + dw_printf ("serial_port_get1(%d) returns 0x%02x\n", fd, ch); + } +#endif + + return (ch); +} + + +/*------------------------------------------------------------------- + * + * Name: serial_port_close + * + * Purpose: Close the device. + * + * Inputs: fd - Handle from open. + * + * Returns: None. + * + *--------------------------------------------------------------------*/ + + +// TODO: + + +/* end serial_port.c */ diff --git a/serial_port.h b/serial_port.h new file mode 100644 index 0000000..6c3287a --- /dev/null +++ b/serial_port.h @@ -0,0 +1,27 @@ +/* serial_port.h */ + + + +#if __WIN32__ + +#include +#include + +typedef HANDLE MYFDTYPE; +#define MYFDERROR INVALID_HANDLE_VALUE + +#else + +typedef int MYFDTYPE; +#define MYFDERROR (-1) + +#endif + + +extern MYFDTYPE serial_port_open (char *devicename, int baud); + +extern int serial_port_write (MYFDTYPE fd, char *str, int len); + +extern int serial_port_get1 (MYFDTYPE fd); + +extern void serial_port_close (MYFDTYPE fd); \ No newline at end of file diff --git a/server.c b/server.c index 6fb3699..68c5703 100644 --- a/server.c +++ b/server.c @@ -256,52 +256,52 @@ static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int switch (fromto) { case FROM_CLIENT: - strcpy (direction, "from"); /* from the client application */ + strlcpy (direction, "from", sizeof(direction)); /* from the client application */ switch (pmsg->kind_lo) { - case 'P': strcpy (datakind, "Application Login"); break; - case 'X': strcpy (datakind, "Register CallSign"); break; - case 'x': strcpy (datakind, "Unregister CallSign"); break; - case 'G': strcpy (datakind, "Ask Port Information"); break; - case 'm': strcpy (datakind, "Enable Reception of Monitoring Frames"); break; - case 'R': strcpy (datakind, "AGWPE Version Info"); break; - case 'g': strcpy (datakind, "Ask Port Capabilities"); break; - case 'H': strcpy (datakind, "Callsign Heard on a Port"); break; - case 'y': strcpy (datakind, "Ask Outstanding frames waiting on a Port"); break; - case 'Y': strcpy (datakind, "Ask Outstanding frames waiting for a connection"); break; - case 'M': strcpy (datakind, "Send UNPROTO Information"); break; - case 'C': strcpy (datakind, "Connect, Start an AX.25 Connection"); break; - case 'D': strcpy (datakind, "Send Connected Data"); break; - case 'd': strcpy (datakind, "Disconnect, Terminate an AX.25 Connection"); break; - case 'v': strcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters"); break; - case 'V': strcpy (datakind, "Send UNPROTO VIA"); break; - case 'c': strcpy (datakind, "Non-Standard Connections, Connection with PID"); break; - case 'K': strcpy (datakind, "Send data in raw AX.25 format"); break; - case 'k': strcpy (datakind, "Activate reception of Frames in raw format"); break; - default: strcpy (datakind, "**INVALID**"); break; + case 'P': strlcpy (datakind, "Application Login", sizeof(datakind)); break; + case 'X': strlcpy (datakind, "Register CallSign", sizeof(datakind)); break; + case 'x': strlcpy (datakind, "Unregister CallSign", sizeof(datakind)); break; + case 'G': strlcpy (datakind, "Ask Port Information", sizeof(datakind)); break; + case 'm': strlcpy (datakind, "Enable Reception of Monitoring Frames", sizeof(datakind)); break; + case 'R': strlcpy (datakind, "AGWPE Version Info", sizeof(datakind)); break; + case 'g': strlcpy (datakind, "Ask Port Capabilities", sizeof(datakind)); break; + case 'H': strlcpy (datakind, "Callsign Heard on a Port", sizeof(datakind)); break; + case 'y': strlcpy (datakind, "Ask Outstanding frames waiting on a Port", sizeof(datakind)); break; + case 'Y': strlcpy (datakind, "Ask Outstanding frames waiting for a connection", sizeof(datakind)); break; + case 'M': strlcpy (datakind, "Send UNPROTO Information", sizeof(datakind)); break; + case 'C': strlcpy (datakind, "Connect, Start an AX.25 Connection", sizeof(datakind)); break; + case 'D': strlcpy (datakind, "Send Connected Data", sizeof(datakind)); break; + case 'd': strlcpy (datakind, "Disconnect, Terminate an AX.25 Connection", sizeof(datakind)); break; + case 'v': strlcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters", sizeof(datakind)); break; + case 'V': strlcpy (datakind, "Send UNPROTO VIA", sizeof(datakind)); break; + case 'c': strlcpy (datakind, "Non-Standard Connections, Connection with PID", sizeof(datakind)); break; + case 'K': strlcpy (datakind, "Send data in raw AX.25 format", sizeof(datakind)); break; + case 'k': strlcpy (datakind, "Activate reception of Frames in raw format", sizeof(datakind)); break; + default: strlcpy (datakind, "**INVALID**", sizeof(datakind)); break; } break; case TO_CLIENT: default: - strcpy (direction, "to"); /* sent to the client application. */ + strlcpy (direction, "to", sizeof(direction)); /* sent to the client application. */ switch (pmsg->kind_lo) { - case 'R': strcpy (datakind, "Version Number"); break; - case 'X': strcpy (datakind, "Callsign Registration"); break; - case 'G': strcpy (datakind, "Port Information"); break; - case 'g': strcpy (datakind, "Capabilities of a Port"); break; - case 'y': strcpy (datakind, "Frames Outstanding on a Port"); break; - case 'Y': strcpy (datakind, "Frames Outstanding on a Connection"); break; - case 'H': strcpy (datakind, "Heard Stations on a Port"); break; - case 'C': strcpy (datakind, "AX.25 Connection Received"); break; - case 'D': strcpy (datakind, "Connected AX.25 Data"); break; - case 'M': strcpy (datakind, "Monitored Connected Information"); break; - case 'S': strcpy (datakind, "Monitored Supervisory Information"); break; - case 'U': strcpy (datakind, "Monitored Unproto Information"); break; - case 'T': strcpy (datakind, "Monitoring Own Information"); break; - case 'K': strcpy (datakind, "Monitored Information in Raw Format"); break; - default: strcpy (datakind, "**INVALID**"); break; + case 'R': strlcpy (datakind, "Version Number", sizeof(datakind)); break; + case 'X': strlcpy (datakind, "Callsign Registration", sizeof(datakind)); break; + case 'G': strlcpy (datakind, "Port Information", sizeof(datakind)); break; + case 'g': strlcpy (datakind, "Capabilities of a Port", sizeof(datakind)); break; + case 'y': strlcpy (datakind, "Frames Outstanding on a Port", sizeof(datakind)); break; + case 'Y': strlcpy (datakind, "Frames Outstanding on a Connection", sizeof(datakind)); break; + case 'H': strlcpy (datakind, "Heard Stations on a Port", sizeof(datakind)); break; + case 'C': strlcpy (datakind, "AX.25 Connection Received", sizeof(datakind)); break; + case 'D': strlcpy (datakind, "Connected AX.25 Data", sizeof(datakind)); break; + case 'M': strlcpy (datakind, "Monitored Connected Information", sizeof(datakind)); break; + case 'S': strlcpy (datakind, "Monitored Supervisory Information", sizeof(datakind)); break; + case 'U': strlcpy (datakind, "Monitored Unproto Information", sizeof(datakind)); break; + case 'T': strlcpy (datakind, "Monitoring Own Information", sizeof(datakind)); break; + case 'K': strlcpy (datakind, "Monitored Information in Raw Format", sizeof(datakind)); break; + default: strlcpy (datakind, "**INVALID**", sizeof(datakind)); break; } } @@ -466,7 +466,7 @@ static THREAD_F connect_listen_thread (void *arg) SOCKET listen_sock; WSADATA wsadata; - sprintf (server_port_str, "%d", (int)(long)arg); + snprintf (server_port_str, sizeof(server_port_str), "%d", (int)(long)arg); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(long)arg, server_port_str); @@ -591,6 +591,7 @@ static THREAD_F connect_listen_thread (void *arg) socklen_t sockaddr_size = sizeof(struct sockaddr_in); int server_port = (int)(long)arg; int listen_sock; + int bcopt = 1; listen_sock= socket(AF_INET,SOCK_STREAM,0); if (listen_sock == -1) { @@ -599,6 +600,15 @@ static THREAD_F connect_listen_thread (void *arg) return (NULL); } + /* Version 1.3 - as suggested by G8BPQ. */ + /* Without this, if you kill the application then try to run it */ + /* again quickly the port number is unavailable for a while. */ + /* Don't try doing the same thing On Windows; It has a different meaning. */ + /* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */ + + setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4); + + sockaddr.sin_addr.s_addr = INADDR_ANY; sockaddr.sin_port = htons(server_port); sockaddr.sin_family = AF_INET; @@ -791,7 +801,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl /* The documentation example includes these 3 extra in the Len= value */ /* but actual observed data uses only the packet info length. */ - sprintf (agwpe_msg.data, " %d:Fm %s To %s [%02d:%02d:%02d]\r%s\r\r", + snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s [%02d:%02d:%02d]\r%s\r\r", chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, ax25_get_pid(pp), info_len, tm->tm_hour, tm->tm_min, tm->tm_sec, @@ -1103,7 +1113,7 @@ static THREAD_F cmd_listen_thread (void *arg) count++; } } - sprintf (reply.info, "%d;", count); + snprintf (reply.info, sizeof(reply.info), "%d;", count); for (j=0; jachan[j].valid) { @@ -1113,22 +1123,22 @@ static THREAD_F cmd_listen_thread (void *arg) 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); + snprintf (stemp, sizeof(stemp), "Port%d %s soundcard mono;", j+1, names[a]); + strlcat (reply.info, stemp, sizeof(reply.info)); } else { - sprintf (stemp, "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left"); - strcat (reply.info, stemp); + snprintf (stemp, sizeof(stemp), "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left"); + strlcat (reply.info, stemp, sizeof(reply.info)); } } } #else if (num_channels == 1) { - sprintf (reply.info, "1;Port1 Single channel;"); + snprintf (reply.info, sizeof(reply.info), "1;Port1 Single channel;"); } else { - sprintf (reply.info, "2;Port1 Left channel;Port2 Right Channel;"); + snprintf (reply.info, sizeof(reply.info), "2;Port1 Left channel;Port2 Right Channel;"); } #endif reply.hdr.data_len = strlen(reply.info) + 1; @@ -1200,9 +1210,9 @@ static THREAD_F cmd_listen_thread (void *arg) reply.hdr.portx = cmd.hdr.portx - strcpy (reply.hdr.call_from, "WB2OSZ-15"); + strlcpy (reply.hdr.call_from, "WB2OSZ-15", sizeof(reply.hdr.call_from)); - strcpy (agwpe_msg.data, ...); + strlcpy (agwpe_msg.data, ..., sizeof(agwpe_msg.data)); reply.hdr.data_len = strlen(reply.info); @@ -1247,21 +1257,21 @@ static THREAD_F cmd_listen_thread (void *arg) // We have already assured these do not exceed 9 characters. - strcpy (stemp, cmd.hdr.call_from); - strcat (stemp, ">"); - strcat (stemp, cmd.hdr.call_to); + strlcpy (stemp, cmd.hdr.call_from, sizeof(stemp)); + strlcat (stemp, ">", sizeof(stemp)); + strlcat (stemp, cmd.hdr.call_to, sizeof(stemp)); cmd.data[cmd.hdr.data_len] = '\0'; ndigi = cmd.data[0]; p = cmd.data + 1; for (k=0; k #include #include + +#include "direwolf.h" #include "textcolor.h" #include "symbols.h" @@ -259,6 +261,17 @@ static const struct { /* ~ 94 */ { "Q4", "TNC Stream Switch" } }; +// Make sure the array is null terminated. +static const char *search_locations[] = { + (const char *) "symbols-new.txt", +#ifndef __WIN32__ + (const char *) "/usr/share/direwolf/symbols-new.txt", + (const char *) "/usr/local/share/direwolf/symbols-new.txt", +#endif + (const char *) NULL +}; + + /*------------------------------------------------------------------ * * Function: symbols_init @@ -306,7 +319,7 @@ static int new_sym_len = 0; /* Number of elements used. */ void symbols_init (void) { - FILE *fp; + FILE *fp = NULL; /* * We only care about lines with this format: @@ -340,13 +353,13 @@ void symbols_init (void) // If search strategy changes, be sure to keep decode_tocall in sync. + fp = NULL; + j = 0; + do { + if (search_locations[j] == NULL) break; + fp = fopen(search_locations[j++], "r"); + } while (fp == NULL); - fp = fopen("symbols-new.txt", "r"); -#ifndef __WIN32__ - if (fp == NULL) { - fp = fopen("/usr/share/direwolf/symbols-new.txt", "r"); - } -#endif if (fp == NULL) { text_color_set(DW_COLOR_ERROR); @@ -401,6 +414,83 @@ void symbols_init (void) } /* end symbols_init */ +/*------------------------------------------------------------------ + * + * Function: symbols_list + * + * Purpose: Print a list of all the symbols. + * + * Inputs: none + * + *------------------------------------------------------------------*/ + +void symbols_list (void) +{ + int n; + + dw_printf ("\n"); + + dw_printf ("\tPRIMARY SYMBOL TABLE\n"); + dw_printf ("\n"); + dw_printf ("sym GPSxy GPSCnn APRStt Icon\n"); + dw_printf ("--- ----- ------ ------ ----\n"); + for (n = 1; n < SYMTAB_SIZE; n++) { + dw_printf (" /%c %s %02d AB1%02d %s\n", n + ' ', primary_symtab[n].xy, n, n, primary_symtab[n].description); + } + + dw_printf ("\n"); + dw_printf ("\tALTERNATE SYMBOL TABLE\n"); + dw_printf ("\n"); + dw_printf ("sym GPSxy GPSEnn APRStt Icon\n"); + dw_printf ("--- ----- ------ ------ ----\n"); + for (n = 1; n < SYMTAB_SIZE; n++) { + dw_printf (" \\%c %s %02d AB2%02d %s\n", n + ' ', alternate_symtab[n].xy, n, n, alternate_symtab[n].description); + } + + dw_printf ("\n"); + dw_printf ("\tNEW SYMBOLS from symbols-new.txt\n"); + dw_printf ("\n"); + dw_printf ("sym GPSxyz GPSxnn APRStt Icon\n"); + dw_printf ("--- ------ ------ ------ ----\n"); + + + for (n = 0; n < new_sym_len; n++) { + + int overlay = new_sym_ptr[n].overlay; + int symbol = new_sym_ptr[n].symbol; + char tones[12]; + + symbols_to_tones (overlay, symbol, tones); + + if (overlay == '/') { + + dw_printf (" %c%c %s%c C%02d %-7s %s\n", overlay, symbol, + primary_symtab[symbol - ' '].xy, ' ', + symbol - ' ', tones, + new_sym_ptr[n].description); + } + else if (isupper(overlay) || isdigit(overlay)) { + + dw_printf (" %c%c %s%c %-7s %s\n", overlay, symbol, + alternate_symtab[symbol - ' '].xy, overlay, + tones, + new_sym_ptr[n].description); + } + else { + + dw_printf (" %c%c %s%c E%02d %-7s %s\n", overlay, symbol, + alternate_symtab[symbol - ' '].xy, ' ', + symbol - ' ', tones, + new_sym_ptr[n].description); + } + } + dw_printf ("\n"); + + +} /* end symbols_list */ + + + /*------------------------------------------------------------------ * * Function: symbols_from_dest_or_src @@ -602,19 +692,19 @@ int symbols_into_dest (char symtab, char symbol, char *dest) if (symbol >= '!' && symbol <= '~' && symtab == '/') { /* Primary Symbol table. */ - sprintf (dest, "GPSC%02d", symbol - ' '); + snprintf (dest, 7, "GPSC%02d", symbol - ' '); return (0); } else if (symbol >= '!' && symbol <= '~' && symtab == '\\') { /* Alternate Symbol table. */ - sprintf (dest, "GPSE%02d", symbol - ' '); + snprintf (dest, 7, "GPSE%02d", symbol - ' '); return (0); } else if (symbol >= '!' && symbol <= '~' && (isupper(symtab) || isdigit(symtab))) { /* Alternate Symbol table with overlay. */ - sprintf (dest, "GPS%s%c", alternate_symtab[symbol - ' '].xy, symtab); + snprintf (dest, 7, "GPS%s%c", alternate_symtab[symbol - ' '].xy, symtab); return (0); } @@ -623,7 +713,7 @@ int symbols_into_dest (char symtab, char symbol, char *dest) dw_printf ("Could not convert symbol \"%c%c\" to GPSxyz destination format.\n", symtab, symbol); - strcpy (dest, "GPS???"); /* Error. */ + strlcpy (dest, "GPS???", sizeof(dest)); /* Error. */ return (1); } @@ -637,16 +727,19 @@ int symbols_into_dest (char symtab, char symbol, char *dest) * Inputs: symtab /, \, 0-9, A-Z * symbol any printable character ! to ~ * + * desc_size Size of description provided by caller + * so we can avoid buffer overflow. + * * Outputs: description Text description. * "--no-symbol--" if error. * - * + * * Description: This is used for the monitoring and the * decode_aprs utility. * *------------------------------------------------------------------*/ -void symbols_get_description (char symtab, char symbol, char *description) +void symbols_get_description (char symtab, char symbol, char *description, size_t desc_size) { char tmp2[2]; int j; @@ -672,7 +765,7 @@ void symbols_get_description (char symtab, char symbol, char *description) /* We do the latter. */ symbol = ' '; - strcpy (description, primary_symtab[symbol-' '].description); + strlcpy (description, primary_symtab[symbol-' '].description, desc_size); return; } @@ -688,7 +781,7 @@ void symbols_get_description (char symtab, char symbol, char *description) for (j=0; j 3) { + print STDERR "A callsign, bit sense string, and optional project title must be provided.\n"; + usage(); +} + +# Separate out call and pad to 9 characters. +$call = shift @ARGV; +$call = substr($call . " ", 0, 9); + +if ( ! ($ARGV[0] =~ m/^[01]{8}$/)) { + print STDERR "The bit-sense value must be 8 binary digits.\n"; + usage(); +} + +print ":$call:BITS." . join (',', @ARGV) . "\n"; +exit 0; + +sub usage () +{ + print STDERR "\n"; + print STDERR "telem-bits.pl - Generate BITS message with bit polarity and optional project title.\n"; + print STDERR "\n"; + print STDERR "Usage: telem-bits.pl call bit-sense [ project-title ]\n"; + print STDERR "\n"; + print STDERR "Bit-sense is string of 8 binary digits.\n"; + print STDERR "If project title contains any spaces, enclose it in quotes.\n"; + + exit 1; +} \ No newline at end of file diff --git a/telemetry-toolkit/telem-data.pl b/telemetry-toolkit/telem-data.pl new file mode 100644 index 0000000..1484f44 --- /dev/null +++ b/telemetry-toolkit/telem-data.pl @@ -0,0 +1,31 @@ +#!/usr/bin/perl + +# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 + +if ($#ARGV+1 < 2 || $#ARGV+1 > 7) { + print STDERR "2 to 7 command line arguments must be provided.\n"; + usage(); +} + +if ($#ARGV+1 == 7) { + if ( ! ($ARGV[6] =~ m/^[01]{8}$/)) { + print STDERR "The sixth value must be 8 binary digits.\n"; + usage(); + } +} + +print "T#" . join (',', @ARGV) . "\n"; +exit 0; + +sub usage () +{ + print STDERR "\n"; + print STDERR "telem-data.pl - Format data into Telemetry Report format.\n"; + print STDERR "\n"; + print STDERR "Usage: telem-data.pl sequence value1 [ value2 ... ]\n"; + print STDERR "\n"; + print STDERR "A sequence number and up to 5 analog values can be specified.\n"; + print STDERR "Any sixth value must be 8 binary digits.\n"; + + exit 1; +} \ No newline at end of file diff --git a/telemetry-toolkit/telem-data91.pl b/telemetry-toolkit/telem-data91.pl new file mode 100644 index 0000000..54da1ad --- /dev/null +++ b/telemetry-toolkit/telem-data91.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl + +# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 + +# For explanation of encoding see: +# http://he.fi/doc/aprs-base91-comment-telemetry.txt + + +if ($#ARGV+1 < 2 || $#ARGV+1 > 7) { + print STDERR "2 to 7 command line arguments must be provided.\n"; + usage(); +} + + +if ($#ARGV+1 == 7) { + if ( ! ($ARGV[6] =~ m/^[01]{8}$/)) { + print STDERR "The sixth value must be 8 binary digits.\n"; + usage(); + } + # Convert binary digits to value. + $ARGV[6] = oct("0b" . reverse($ARGV[6])); +} + +$result = "|"; + +for ($n = 0 ; $n <= $#ARGV; $n++) { + #print $n . " = " . $ARGV[$n] . "\n"; + $v = $ARGV[$n]; + if ($v != int($v) || $v < 0 || $v > 8280) { + print STDERR "$v is not an integer in range of 0 to 8280.\n"; + usage(); + } + + $result .= base91($v); +} + +$result .= "|"; +print "$result\n"; +exit 0; + + +sub base91 () +{ + my $x = @_[0]; + + my $d1 = int ($x / 91); + my $d2 = $x % 91; + + return chr($d1+33) . chr($d2+33); +} + + +sub usage () +{ + print STDERR "\n"; + print STDERR "telem-data91.pl - Format data into compressed base 91 telemetry.\n"; + print STDERR "\n"; + print STDERR "Usage: telem-data91.pl sequence value1 [ value2 ... ]\n"; + print STDERR "\n"; + print STDERR "A sequence number and up to 5 analog values can be specified.\n"; + print STDERR "Any sixth value must be 8 binary digits.\n"; + print STDERR "Values must be integers in range of 0 to 8280.\n"; + + exit 1; +} \ No newline at end of file diff --git a/telemetry-toolkit/telem-eqns.pl b/telemetry-toolkit/telem-eqns.pl new file mode 100644 index 0000000..741ad94 --- /dev/null +++ b/telemetry-toolkit/telem-eqns.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl + +# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 + +if ($#ARGV+1 != 4 && $#ARGV+1 != 7 && $#ARGV+1 != 10 && $#ARGV+1 != 13 && $#ARGV+1 != 16) { + print STDERR "A callsign and 1 to 5 sets of 3 coefficients must be provided.\n"; + usage(); +} + +# Separate out call and pad to 9 characters. +$call = shift @ARGV; +$call = substr($call . " ", 0, 9); + +print ":$call:EQNS." . join (',', @ARGV) . "\n"; +exit 0; + +sub usage () +{ + print STDERR "\n"; + print STDERR "telem-eqns.pl - Generate EQNS message with scaling coefficients\n"; + print STDERR "\n"; + print STDERR "Usage: telem-eqns.pl call a1 b1 c1 [ a2 b2 c2 ... ]\n"; + print STDERR "\n"; + print STDERR "Specify a callsign and 1 to 5 sets of 3 coefficients.\n"; + print STDERR "See APRS protocol reference for their meaning.\n"; + + exit 1; +} \ No newline at end of file diff --git a/telemetry-toolkit/telem-m0xer-3.txt b/telemetry-toolkit/telem-m0xer-3.txt new file mode 100644 index 0000000..93ce5bb --- /dev/null +++ b/telemetry-toolkit/telem-m0xer-3.txt @@ -0,0 +1,7 @@ +2E0TOY>APRS::M0XER-3 :BITS.11111111,10mW research balloon +2E0TOY>APRS::M0XER-3 :PARM.Vbat,Vsolar,Temp,Sat +2E0TOY>APRS::M0XER-3 :EQNS.0,0.001,0,0,0.001,0,0,0.1,-273.2,0,1,0,0,1,0 +2E0TOY>APRS::M0XER-3 :UNIT.V,V,C,,m +M0XER-3>APRS63,WIDE2-1:!//Bap'.ZGO JHAE/A=042496|E@Q0%i;5!-| +M0XER-3>APRS63,WIDE2-1:!/4\;u/)K$O J]YD/A=041216|h`RY(1>q!(| +M0XER-3>APRS63,WIDE2-1:!/23*f/R$UO Jf'x/A=041600|rxR_'J>+!(| \ No newline at end of file diff --git a/telemetry-toolkit/telem-parm.pl b/telemetry-toolkit/telem-parm.pl new file mode 100644 index 0000000..464fa60 --- /dev/null +++ b/telemetry-toolkit/telem-parm.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 + +if ($#ARGV+1 < 2 || $#ARGV+1 > 14) { + print STDERR "A callsign and 1 to 13 channel names must be provided.\n"; + usage(); +} + +# Separate out call and pad to 9 characters. +$call = shift @ARGV; +$call = substr($call . " ", 0, 9); + +print ":$call:PARM." . join (',', @ARGV) . "\n"; +exit 0; + +sub usage () +{ + print STDERR "\n"; + print STDERR "telem-parm.pl - Generate PARM message with channel names.\n"; + print STDERR "\n"; + print STDERR "Usage: telem-parm.pl call aname1 ... aname5 dname1 .,, dname8\n"; + print STDERR "\n"; + print STDERR "Specify a callsign and up to 13 names for the analog & digital channels.\n"; + + exit 1; +} \ No newline at end of file diff --git a/telemetry-toolkit/telem-unit.pl b/telemetry-toolkit/telem-unit.pl new file mode 100644 index 0000000..c1d999d --- /dev/null +++ b/telemetry-toolkit/telem-unit.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 + +if ($#ARGV+1 < 2 || $#ARGV+1 > 14) { + print STDERR "A callsign and 1 to 13 units/labels must be provided.\n"; + usage(); +} + +# Separate out call and pad to 9 characters. +$call = shift @ARGV; +$call = substr($call . " ", 0, 9); + +print ":$call:UNIT." . join (',', @ARGV) . "\n"; +exit 0; + +sub usage () +{ + print STDERR "\n"; + print STDERR "telem-unit.pl - Generate UNIT message with channel units/labels.\n"; + print STDERR "\n"; + print STDERR "Usage: telem-unit.pl call unit1 ... unit5 label1 .,, label8\n"; + print STDERR "\n"; + print STDERR "Specify a callsign and up to 13 names for the units/labels.\n"; + + exit 1; +} \ No newline at end of file diff --git a/telemetry-toolkit/telem-volts.conf b/telemetry-toolkit/telem-volts.conf new file mode 100644 index 0000000..f0d24fa --- /dev/null +++ b/telemetry-toolkit/telem-volts.conf @@ -0,0 +1,25 @@ +# Sample configuration for demonstration of sending telemetry. +# Here we take a voltage from an analog to digital converter (ADC). + +ADEVICE plughw:1,0 + +MYCALL MYCALL-9 + +# For demonstration purposes, the metadata will be sent each minute and +# voltage data every 10 seconds. In actual practice, it would be much less frequent. + +# First the metadata. + +# The "infocmd=..." option means use the result for the info part of the packet. + +CBEACON delay=0:10 every=1:00 via=WIDE2-1 infocmd="telem-parm.pl MYCALL-9 Supply" +CBEACON delay=0:11 every=1:00 via=WIDE2-1 infocmd="telem-unit.pl MYCALL-9 Volts" + +# Now the telemetry data. + +# First we use telem-volts.py to read a volage from the A/D converter. +# This is supplied to telem-data.pl as a command line argument. +# The result is used as the info part of a custom beacon. + +CBEACON delay=0:15 every=0:10 via=WIDE2-1 infocmd="telem-data.pl 0 `PYTHONPATH=~/Adafruit-Raspberry-Pi-Python-Code/Adafruit_ADS1x15 telem-volts.py`" + diff --git a/telemetry-toolkit/telem-volts.py b/telemetry-toolkit/telem-volts.py new file mode 100644 index 0000000..f524968 --- /dev/null +++ b/telemetry-toolkit/telem-volts.py @@ -0,0 +1,36 @@ +#!/usr/bin/python + +# Part of Dire Wolf APRS Telemetry Toolkit, WB2OSZ, 2015 + +# Derived from +# https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_ADS1x15/ads1x15_ex_singleended.py + +import time, signal, sys +from Adafruit_ADS1x15 import ADS1x15 + +ADS1015 = 0x00 # 12-bit ADC +ADS1115 = 0x01 # 16-bit ADC + +# Set ADC full scale to 2048 mV. +# Can't use original 4096 with 3.3V supply. +gain = 2048 + +# Select the sample time, 1/sps second. +# Longer is better to average out noise. +sps = 64 + +# Set this to ADS1015 or ADS1115 depending on the ADC you are using! +adc = ADS1x15(ic=ADS1115) + +# Values for voltage divider on ADC input. +r1 = 1000000. +r2 = 100000. + +# Read channel 0 in single-ended mode using the settings above +volts = adc.readADCSingleEnded(0, gain, sps) * 0.001 * (r1+r2) / r2 + +# Calibration correction specific to my hardware. +# (multiply by expected value, divide by uncalibrated result.) +#volts = volts * 4.98 / 4.889 + +print "%.3f" % (volts) diff --git a/telemetry.c b/telemetry.c index c2e1887..4df40b9 100644 --- a/telemetry.c +++ b/telemetry.c @@ -129,7 +129,7 @@ static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_A * * Inputs: station - Station name with optional SSID. * - * REturns: Pointer to metadata. + * Returns: Pointer to metadata. * *--------------------------------------------------------------------*/ @@ -145,6 +145,10 @@ static struct t_metadata_s * t_get_metadata (char *station) for (p = md_list_head; p != NULL; p = p->pnext) { if (strcmp(station, p->station) == 0) { + + assert (p->magic1 == MAGIC1); + assert (p->magic2 == MAGIC2); + return (p); } } @@ -153,6 +157,7 @@ static struct t_metadata_s * t_get_metadata (char *station) memset (p, 0, sizeof (struct t_metadata_s)); p->magic1 = MAGIC1; + strncpy (p->station, station, sizeof(p->station)-1); for (n = 0; n < T_NUM_ANALOG; n++) { @@ -232,6 +237,7 @@ static int t_ndp (char *str) * quiet - suppress error messages. * * Outputs: output - Decoded telemetry in human readable format. + * TODO: How big does it need to be? (buffer overflow?) * comment - Any comment after the data. * * Description: The first character, after the "T" data type indicator, must be "#" @@ -249,7 +255,7 @@ static int t_ndp (char *str) * KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000 * * Not integers. Not fixed width fields. - * We will accept these but issue a warning that others might not. + * We will accept these but issue a warning that others might not. * *--------------------------------------------------------------------*/ @@ -275,6 +281,10 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output #endif pm = t_get_metadata(station); + + assert (pm->magic1 == MAGIC1); + assert (pm->magic2 == MAGIC2); + seq = 0; for (n = 0; n < T_NUM_ANALOG; n++) { araw[n] = G_UNKNOWN; @@ -294,10 +304,15 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output /* * Make a copy of the input string because this will alter it. + * Remove any trailing CR/LF. */ - strncpy (stemp, info+2, sizeof(stemp)); - stemp[sizeof(stemp)-1] = '\0'; + memset (stemp, 0, sizeof(stemp)); + strncpy (stemp, info+2, sizeof(stemp)-1); + + for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { + *p = '\0'; + } next = stemp; p = strsep(&next,","); @@ -309,13 +324,16 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output araw[n] = atof(p); ndp[n] = t_ndp(p); } - if (strlen(p) != 3 || araw[n] < 0 || araw[n] > 255 || araw[n] != (int)(araw[n])) { - if ( ! quiet) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Telemetry analog values should be 3 digit integer values in range of 000 to 255.\n"); - dw_printf("Some applications might not interpret \"%s\" properly.\n", p); - } - } + // Version 1.3: Suppress this message. + // No one pays attention to the original 000 to 255 range. + // BTW, this doesn't trap values like 0.0 or 1.0 + //if (strlen(p) != 3 || araw[n] < 0 || araw[n] > 255 || araw[n] != (int)(araw[n])) { + // if ( ! quiet) { + // text_color_set(DW_COLOR_ERROR); + // dw_printf("Telemetry analog values should be 3 digit integer values in range of 000 to 255.\n"); + // dw_printf("Some applications might not interpret \"%s\" properly.\n", p); + // } + //} n++; } @@ -331,8 +349,10 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output dw_printf("Expected to find 8 binary digits after \"%s\" for the digital values.\n", p); } } + + // TODO: test this! if (strlen(next) > 8) { - strcpy (comment, next+8); + strlcpy (comment, next+8, sizeof(comment)); next[8] = '\0'; } for (k = 0; k < strlen(next); k++) { @@ -393,7 +413,7 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output * Description: We are expecting from 2 to 7 pairs of base 91 digits. * The first pair is the sequence number. * Next we have 1 to 5 analog values. - * If digital values are present, all 5 analog values must be present. + * If digital values are present, all 5 analog values must be present. * *--------------------------------------------------------------------*/ @@ -447,6 +467,9 @@ void telemetry_data_base91 (char *station, char *cdata, char *output) pm = t_get_metadata(station); + assert (pm->magic1 == MAGIC1); + assert (pm->magic2 == MAGIC2); + seq = 0; for (n = 0; n < T_NUM_ANALOG; n++) { araw[n] = G_UNKNOWN; @@ -513,7 +536,7 @@ void telemetry_data_base91 (char *station, char *cdata, char *output) * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "PARM." and the - * rest is a variable length list of comma separated names. + * rest is a variable length list of comma separated names. * * The original spec has different maximum lengths for different * fields which we will ignore. @@ -540,12 +563,19 @@ void telemetry_name_message (char *station, char *msg) /* * Make a copy of the input string because this will alter it. + * Remove any trailing CR LF. */ - strncpy (stemp, msg, sizeof(stemp)); - stemp[sizeof(stemp)-1] = '\0'; + memset (stemp, 0, sizeof(stemp)); + strncpy (stemp, msg, sizeof(stemp)-1); + + for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { + *p = '\0'; + } pm = t_get_metadata(station); + assert (pm->magic1 == MAGIC1); + assert (pm->magic2 == MAGIC2); next = stemp; @@ -553,6 +583,7 @@ void telemetry_name_message (char *station, char *msg) while ((p = strsep(&next,",")) != NULL) { if (n < T_NUM_ANALOG + T_NUM_DIGITAL) { if (strlen(p) > 0 && strcmp(p,"-") != 0) { + memset (pm->name[n], 0, T_STR_LEN); strncpy (pm->name[n], p, T_STR_LEN-1); } n++; @@ -586,7 +617,7 @@ void telemetry_name_message (char *station, char *msg) * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "UNIT." and the - * rest is a variable length list of comma separated units/labels. + * rest is a variable length list of comma separated units/labels. * * The original spec has different maximum lengths for different * fields which we will ignore. @@ -610,12 +641,19 @@ void telemetry_unit_label_message (char *station, char *msg) /* * Make a copy of the input string because this will alter it. + * Remove any trailing CR LF. */ + + memset (stemp, 0, sizeof(stemp)); + strncpy (stemp, msg, sizeof(stemp)-1); - strncpy (stemp, msg, sizeof(stemp)); - stemp[sizeof(stemp)-1] = '\0'; + for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { + *p = '\0'; + } pm = t_get_metadata(station); + assert (pm->magic1 == MAGIC1); + assert (pm->magic2 == MAGIC2); next = stemp; @@ -623,6 +661,7 @@ void telemetry_unit_label_message (char *station, char *msg) while ((p = strsep(&next,",")) != NULL) { if (n < T_NUM_ANALOG + T_NUM_DIGITAL) { if (strlen(p) > 0) { + memset (pm->unit[n], 0, T_STR_LEN); strncpy (pm->unit[n], p, T_STR_LEN-1); } n++; @@ -657,7 +696,7 @@ void telemetry_unit_label_message (char *station, char *msg) * Outputs: Stored for future use when data values are received. * * Description: The first 5 characters of the message are "EQNS." and the - * rest is a comma separated list of 15 floating point values. + * rest is a comma separated list of 15 floating point values. * * The spec appears to require all 15 so we will issue an * error if fewer found. @@ -681,12 +720,19 @@ void telemetry_coefficents_message (char *station, char *msg, int quiet) /* * Make a copy of the input string because this will alter it. + * Remove any trailing CR LF. */ - strncpy (stemp, msg, sizeof(stemp)); - stemp[sizeof(stemp)-1] = '\0'; + memset (stemp, 0, sizeof(stemp)); + strncpy (stemp, msg, sizeof(stemp)-1); + + for (p = stemp + strlen(stemp) - 1; p >= stemp && (*p == '\r' || *p == '\n') ; p--) { + *p = '\0'; + } pm = t_get_metadata(station); + assert (pm->magic1 == MAGIC1); + assert (pm->magic2 == MAGIC2); next = stemp; @@ -765,6 +811,8 @@ void telemetry_bit_sense_message (char *station, char *msg, int quiet) #endif pm = t_get_metadata(station); + assert (pm->magic1 == MAGIC1); + assert (pm->magic2 == MAGIC2); if (strlen(msg) < 8) { if ( ! quiet) { @@ -831,34 +879,36 @@ void telemetry_bit_sense_message (char *station, char *msg, int quiet) * Outputs: output - Decoded telemetry in human readable format. * * Description: Process raw data according to any metadata available - * and put into human readable form. + * and put into human readable form. * *--------------------------------------------------------------------*/ -static void fval_to_str (float x, int ndp, char *str) +#define VAL_STR_SIZE 64 + +static void fval_to_str (float x, int ndp, char str[VAL_STR_SIZE]) { if (x == G_UNKNOWN) { - strcpy (str, "?"); + strlcpy (str, "?", VAL_STR_SIZE); } else { - sprintf (str, "%.*f", ndp, x); + snprintf (str, VAL_STR_SIZE, "%.*f", ndp, x); } } -static void ival_to_str (int x, char *str) +static void ival_to_str (int x, char str[VAL_STR_SIZE]) { if (x == G_UNKNOWN) { - strcpy (str, "?"); + strlcpy (str, "?", VAL_STR_SIZE); } else { - sprintf (str, "%d", x); + snprintf (str, VAL_STR_SIZE, "%d", x); } } static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_ANALOG], int ndp[T_NUM_ANALOG], int draw[T_NUM_DIGITAL], char *output) { int n; - char stemp[50]; + char val_str[VAL_STR_SIZE]; assert (pm != NULL); @@ -872,9 +922,9 @@ static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_A strcat (output, ": "); } - ival_to_str (seq, stemp); + ival_to_str (seq, val_str); strcat (output, "Seq="); - strcat (output, stemp); + strcat (output, val_str); for (n = 0; n < T_NUM_ANALOG; n++) { @@ -905,8 +955,8 @@ static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_A z = pm->coeff_ndp[n][C_A] == 0 ? 0 : pm->coeff_ndp[n][C_A] + ndp[n] + ndp[n]; fndp = MAX (z, MAX(pm->coeff_ndp[n][C_B] + ndp[n], pm->coeff_ndp[n][C_C])); } - fval_to_str (fval, fndp, stemp); - strcat (output, stemp); + fval_to_str (fval, fndp, val_str); + strcat (output, val_str); if (strlen(pm->unit[n]) > 0) { strcat (output, " "); strcat (output, pm->unit[n]); @@ -936,13 +986,13 @@ static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_A dval = draw[n] ^ ! pm->sense[n]; } - ival_to_str (dval , stemp); + ival_to_str (dval, val_str); if (strlen(pm->unit[T_NUM_ANALOG+n]) > 0) { strcat (output, " "); strcat (output, pm->unit[T_NUM_ANALOG+n]); } - strcat (output, stemp); + strcat (output, val_str); } } diff --git a/textcolor.c b/textcolor.c index e0fd3a1..90b55e3 100644 --- a/textcolor.c +++ b/textcolor.c @@ -1,3 +1,4 @@ + // // This file is part of Dire Wolf, an amateur radio packet TNC. // @@ -38,6 +39,12 @@ * http://en.wikipedia.org/wiki/ANSI_escape_code * http://academic.evergreen.edu/projects/biophysics/technotes/program/ansi_esc.htm * + * + +>>>> READ THIS PART!!! <<<< + + * + * * Problem: The ANSI escape sequences, used on Linux, allow 8 basic colors. * Unfortunately, white is not one of them. We only have dark * white, also known as light gray. To get brighter colors, @@ -111,6 +118,8 @@ static const char clear_eos[] = "\e[0J"; /* expected bright/bold (1) to get bright white background. */ /* Makes no sense but I stumbled across that somewhere. */ +/* If you do get blinking, remove all references to "\e[5;47m" */ + static const char background_white[] = "\e[5;47m"; /* Whenever a dark color is used, the */ diff --git a/tq.c b/tq.c index d7d59dc..0a60f9d 100644 --- a/tq.c +++ b/tq.c @@ -226,6 +226,12 @@ void tq_append (int chan, int prio, packet_t pp) assert (chan >= 0 && chan < MAX_CHANS); assert (prio >= 0 && prio < TQ_NUM_PRIO); + if (pp == NULL) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("INTERNAL ERROR: tq_append NULL packet pointer. Please report this!\n"); + return; + } + #if AX25MEMDEBUG if (ax25memdebug_get()) { @@ -245,16 +251,16 @@ void tq_append (int chan, int prio, packet_t pp) * 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. + * 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. + * 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. + * 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. diff --git a/tt_text.c b/tt_text.c index b40f1d2..be2a96f 100644 --- a/tt_text.c +++ b/tt_text.c @@ -139,7 +139,7 @@ static const char call10encoding[10][4] = { /* - * 4 digit gridsquares to cover "99.99% of the world's population." + * Special satellite 4 digit gridsquares to cover "99.99% of the world's population." */ static const char grid[10][10][3] = @@ -368,6 +368,77 @@ int tt_text_to_two_key (char *text, int quiet, char *buttons) } /* end tt_text_to_two_key */ +/*------------------------------------------------------------------ + * + * Name: tt_letter_to_two_digits + * + * Purpose: Convert one letter to 2 digit representation. + * + * Inputs: c - One letter. + * + * quiet - True to suppress error messages. + * + * Outputs: buttons - Sequence of two buttons to press. + * "00" for error because this is probably + * being used to build up a fixed length + * string where positions are signficant. + * + * Returns: Number of errors detected. + * + *----------------------------------------------------------------*/ + + +// TODO: need to test this. + +int tt_letter_to_two_digits (char c, int quiet, char *buttons) +{ + char *b = buttons; + int row, col; + int errors = 0; + int found; + + *b = '\0'; + + if (islower(c)) { + c = toupper(c); + } + + if ( ! isupper(c)) { + errors++; + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Letter to two digits: \"%c\" found where a letter is required.\n", c); + } + strcpy (buttons, "00"); + return (errors); + } + +/* Search in the translation table. */ + + found = 0; + + for (row=0; row<10 && ! found; row++) { + for (col=0; col<4 && ! found; col++) { + if (c == translate[row][col]) { + *b++ = '0' + row; + *b++ = '1' + col; + *b = '\0'; + found = 1; + } + } + } + if (! found) { + errors++; + text_color_set (DW_COLOR_ERROR); + dw_printf ("Letter to two digits: INTERNAL ERROR. Should not be here.\n"); + strcpy (buttons, "00"); + } + + return (errors); + +} /* end tt_letter_to_two_digits */ + + /*------------------------------------------------------------------ * * Name: tt_text_to_call10 @@ -478,9 +549,9 @@ int tt_text_to_call10 (char *text, int quiet, char *buttons) /*------------------------------------------------------------------ * - * Name: tt_text_to_gridsquare + * Name: tt_text_to_satsq * - * Purpose: Convert Gridsquare to 4 digit DTMF representation. + * Purpose: Convert Special Satellite Gridsquare to 4 digit DTMF representation. * * Inputs: text - Input string. * Should be two letters (A thru R) and two digits. @@ -497,7 +568,7 @@ int tt_text_to_call10 (char *text, int quiet, char *buttons) * *----------------------------------------------------------------*/ -int tt_text_to_gridsquare (char *text, int quiet, char *buttons) +int tt_text_to_satsq (char *text, int quiet, char *buttons) { int row, col; @@ -514,7 +585,7 @@ int tt_text_to_gridsquare (char *text, int quiet, char *buttons) if (! quiet) { text_color_set (DW_COLOR_ERROR); - dw_printf ("Gridsquare to DTMF: Gridsquare \"%s\" must be 4 characters.\n", text); + dw_printf ("Satellite Gridsquare to DTMF: Gridsquare \"%s\" must be 4 characters.\n", text); } errors++; return (errors); @@ -530,7 +601,7 @@ int tt_text_to_gridsquare (char *text, int quiet, char *buttons) 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); + dw_printf ("Satellite Gridsquare to DTMF: First two characters \"%s\" must be letters in range of A to R.\n", text); } errors++; return (errors); @@ -540,7 +611,7 @@ int tt_text_to_gridsquare (char *text, int quiet, char *buttons) if (! quiet) { text_color_set (DW_COLOR_ERROR); - dw_printf ("Gridsquare to DTMF: Last two characters \"%s\" must digits.\n", text); + dw_printf ("Satellite Gridsquare to DTMF: Last two characters \"%s\" must digits.\n", text); } errors++; return (errors); @@ -569,13 +640,13 @@ int tt_text_to_gridsquare (char *text, int quiet, char *buttons) errors++; if (! quiet) { text_color_set (DW_COLOR_ERROR); - dw_printf ("Gridsquare to DTMF: Sorry, your location can't be converted to DTMF.\n"); + dw_printf ("Satellite Gridsquare to DTMF: Sorry, your location can't be converted to DTMF.\n"); } } return (errors); -} /* end tt_text_to_gridsquare */ +} /* end tt_text_to_satsq */ @@ -765,6 +836,79 @@ int tt_two_key_to_text (char *buttons, int quiet, char *text) } /* end tt_two_key_to_text */ +/*------------------------------------------------------------------ + * + * Name: tt_two_digits_to_letter + * + * Purpose: Convert the two digit representation to one letter. + * + * Inputs: buttons - Input string. + * Should contain exactly two digits. + * + * quiet - True to suppress error messages. + * + * Outputs: text - Converted to string which should contain one upper case letter. + * If error, use 'x' as a placeholder because we are probably + * dealing with fixed length strings where position matters. + * + * Returns: Number of errors detected. + * + *----------------------------------------------------------------*/ + +// TODO: need to test + +int tt_two_digits_to_letter (char *buttons, int quiet, char *text) +{ + char c1 = buttons[0]; + char c2 = buttons[1]; + int row, col; + int errors = 0; + + strcpy (text, "x"); + + if (c1 >= '2' && c1 <= '9') { + + if (c2 >= '1' && c2 <= '4') { + + row = c1 - '0'; + col = c2 - '1'; + + if (translate[row][col] != 0) { + text[0] = translate[row][col]; + text[1] = '\0'; + } + else { + errors++; + strcpy (text, "x"); + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Two digits to letter: Invalid combination \"%c%c\".\n", c1, c2); + } + } + } + else { + errors++; + strcpy (text, "x"); + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Two digits to letter: Second character \"%c\" must be in range of 1 through 4.\n", c2); + } + } + } + else { + errors++; + strcpy (text, "x"); + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Two digits to letter: First character \"%c\" must be in range of 2 through 9.\n", c1); + } + } + + return (errors); + +} /* end tt_two_digits_to_letter */ + + /*------------------------------------------------------------------ * * Name: tt_call10_to_text @@ -864,9 +1008,218 @@ int tt_call10_to_text (char *buttons, int quiet, char *text) /*------------------------------------------------------------------ * - * Name: tt_gridsquare_to_text + * Name: tt_mhead_to_text * - * Purpose: Convert the 4 digit DTMF gridsquare to normal 2 letters and 2 digits. + * Purpose: Convert the 4, 6, 10, or 12 digit DTMF representation of Maidenhead + * Grid Square Locator to normal text representation. + * + * Inputs: buttons - Input string. + * Must contain 4, 6, 10, or 12 digits. + * + * quiet - True to suppress error messages. + * + * Outputs: text - Converted to gridsquare with upper case letters and digits. + * Length should be 2, 4, 6, or 8 with alternating letter or digit pairs. + * + * Returns: Number of errors detected. + * + *----------------------------------------------------------------*/ + + +int tt_mhead_to_text (char *buttons, int quiet, char *text) +{ + char *b; + char *t; + int errors = 0; + + strcpy (text, ""); + +/* Validity check. */ + + if (strlen(buttons) != 4 && strlen(buttons) != 6 && strlen(buttons) != 10 && strlen(buttons) != 12) { + + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("DTMF to Maidenhead Gridsquare Locator: Input \"%s\" must be exactly 4, 6, 10, or 12 digits.\n", buttons); + } + errors++; + return (errors); + } + + for (b = buttons; *b != '\0'; b++) { + + if (! isdigit(*b)) { + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("DTMF to Maidenhead Gridsquare Locator: Input \"%s\" can contain only digits.\n", buttons); + } + errors++; + return (errors); + } + } + + b = buttons; + t = text; + + errors += tt_two_digits_to_letter (b, quiet, t); + b += 2; + t++; + + errors += tt_two_digits_to_letter (b, quiet, t); + b += 2; + t++; + + if (strlen(buttons) > 4) { + + *t++ = *b++; + *t++ = *b++; + *t = '\0'; + + if (strlen(buttons) > 6) { + + errors += tt_two_digits_to_letter (b, quiet, t); + b += 2; + t++; + + errors += tt_two_digits_to_letter (b, quiet, t); + b += 2; + t++; + + if (strlen(buttons) > 10) { + + *t++ = *b++; + *t++ = *b++; + *t = '\0'; + } + } + } + + return (errors); + +} /* end tt_mhead_to_text */ + + +/*------------------------------------------------------------------ + * + * Name: tt_text_to_mhead + * + * Purpose: Convert the 2, 4, 6, or 8 character Maidenhead + * Grid Square Locator to DTMF representation. + * + * Outputs: text - Maidenhead Grid Square locator in usual format. + * Length should be 2, 4, 6, or 8 with alternating letter or digit pairs. + * + * Inputs: buttons - Result with 4, 6, 10, or 12 digits. + * Each letter is replaced by two digits. + * + * quiet - True to suppress error messages. + * + * Returns: Number of errors detected. + * + *----------------------------------------------------------------*/ + + +int tt_text_to_mhead (char *text, int quiet, char *buttons) +{ + char *b; + char *t; + int errors = 0; + + strcpy (buttons, ""); + + + if (strlen(text) != 2 && strlen(text) != 4 && strlen(text) != 6 && strlen(text) != 8) { + + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Maidenhead Gridsquare Locator to DTMF: Input \"%s\" must be exactly 2, 4, 6, or 8 characters.\n", text); + } + errors++; + return (errors); + } + + t = text; + b = buttons; + + if (toupper(t[0]) < 'A' || toupper(t[0]) > 'R' || toupper(t[1]) < 'A' || toupper(t[1]) > 'R') { + if (! quiet) { + 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", text); + } + errors++; + return(errors); + } + + errors += tt_letter_to_two_digits (*t, quiet, b); + t++; + b += 2; + + errors += tt_letter_to_two_digits (*t, quiet, b); + t++; + b += 2; + + if (strlen(text) > 2) { + + if ( ! isdigit(t[0]) || ! isdigit(t[1])) { + if (! quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("The second pair of characters in Maidenhead locator \"%s\" must digits 0 thru 9.\n", text); + } + errors++; + return(errors); + } + + *b++ = *t++; + *b++ = *t++; + *b = '\0'; + + if (strlen(text) > 4) { + + if (toupper(t[0]) < 'A' || toupper(t[0]) > 'X' || toupper(t[1]) < 'A' || toupper(t[1]) > 'X') { + if (! quiet) { + 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", text); + } + errors++; + return(errors); + } + + errors += tt_letter_to_two_digits (*t, quiet, b); + t++; + b += 2; + + errors += tt_letter_to_two_digits (*t, quiet, b); + t++; + b += 2; + + if (strlen(text) > 6) { + + if ( ! isdigit(t[0]) || ! isdigit(t[1])) { + if (! quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("The fourth pair of characters in Maidenhead locator \"%s\" must digits 0 thru 9.\n", text); + } + errors++; + return(errors); + } + + *b++ = *t++; + *b++ = *t++; + *b = '\0'; + } + } + } + + return (errors); + +} /* tt_text_to_mhead */ + + +/*------------------------------------------------------------------ + * + * Name: tt_satsq_to_text + * + * Purpose: Convert the 4 digit DTMF special Satellite gridsquare to normal 2 letters and 2 digits. * * Inputs: buttons - Input string. * Should contain 4 digits. @@ -879,7 +1232,7 @@ int tt_call10_to_text (char *buttons, int quiet, char *text) * *----------------------------------------------------------------*/ -int tt_gridsquare_to_text (char *buttons, int quiet, char *text) +int tt_satsq_to_text (char *buttons, int quiet, char *text) { char *b; int row, col; @@ -893,7 +1246,7 @@ int tt_gridsquare_to_text (char *buttons, int quiet, char *text) if (! quiet) { text_color_set (DW_COLOR_ERROR); - dw_printf ("DTMF to Gridsquare: Input \"%s\" must be exactly 4 digits.\n", buttons); + dw_printf ("DTMF to Satellite Gridsquare: Input \"%s\" must be exactly 4 digits.\n", buttons); } errors++; return (errors); @@ -904,7 +1257,7 @@ int tt_gridsquare_to_text (char *buttons, int quiet, char *text) if (! isdigit(*b)) { if (! quiet) { text_color_set (DW_COLOR_ERROR); - dw_printf ("DTMF to Gridsquare: Input \"%s\" can contain only digits.\n", buttons); + dw_printf ("DTMF to Satellite Gridsquare: Input \"%s\" can contain only digits.\n", buttons); } errors++; return (errors); @@ -919,7 +1272,7 @@ int tt_gridsquare_to_text (char *buttons, int quiet, char *text) return (errors); -} /* end tt_gridsquare_to_text */ +} /* end tt_satsq_to_text */ /*------------------------------------------------------------------ @@ -1042,9 +1395,15 @@ int main (int argc, char *argv[]) dw_printf ("\"%s\"\n", buttons); } - n = tt_text_to_gridsquare (text, 1, buttons); + n = tt_text_to_mhead (text, 1, buttons); if (n == 0) { - dw_printf ("Push buttons for gridsquare:\n"); + dw_printf ("Push buttons for Maidenhead Grid Square Locator:\n"); + dw_printf ("\"%s\"\n", buttons); + } + + n = tt_text_to_satsq (text, 1, buttons); + if (n == 0) { + dw_printf ("Push buttons for satellite gridsquare:\n"); dw_printf ("\"%s\"\n", buttons); } @@ -1112,9 +1471,15 @@ int main (int argc, char *argv[]) dw_printf ("\"%s\"\n", text); } - n = tt_gridsquare_to_text (buttons, 1, text); + n = tt_mhead_to_text (buttons, 1, text); if (n == 0) { - dw_printf ("Decoded gridsquare from 4 DTMF digits:\n"); + dw_printf ("Decoded Maidenhead Locator from DTMF digits:\n"); + dw_printf ("\"%s\"\n", text); + } + + n = tt_satsq_to_text (buttons, 1, text); + if (n == 0) { + dw_printf ("Decoded satellite gridsquare from 4 DTMF digits:\n"); dw_printf ("\"%s\"\n", text); } diff --git a/tt_text.h b/tt_text.h index a1690e5..440b827 100644 --- a/tt_text.h +++ b/tt_text.h @@ -2,7 +2,7 @@ /* tt_text.h */ -/* Encode to DTMF representation. */ +/* Encode normal human readable to DTMF representation. */ int tt_text_to_multipress (char *text, int quiet, char *buttons); @@ -10,7 +10,9 @@ 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) ; +int tt_text_to_mhead (char *text, int quiet, char *buttons) ; + +int tt_text_to_satsq (char *text, int quiet, char *buttons) ; /* Decode DTMF to normal human readable form. */ @@ -21,7 +23,9 @@ 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); +int tt_mhead_to_text (char *buttons, int quiet, char *text); + +int tt_satsq_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 2afe23d..4489945 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, 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 @@ -55,6 +55,11 @@ #include "encode_aprs.h" #include "latlong.h" +#include "server.h" +#include "kiss.h" +#include "kissnet.h" + + /* * Information kept about local APRStt users. * @@ -86,6 +91,10 @@ static struct tt_user_s { /* Possibly other tactical call / object label. */ /* Null string indicates table position is not used. */ + int count; /* Number of times we received information for this object. */ + /* Value 1 means first time and could be used to send */ + /* a welcome greeting. */ + int ssid; /* SSID to add. */ /* Default of 12 but not always. */ @@ -108,6 +117,7 @@ static struct tt_user_s { /* 3 within 30 seconds to improve chances of */ /* being heard while using digipeater duplicate */ /* removal. */ + // TODO: I think implementation is different. time_t next_xmit; /* Time for next transmit. Meaningful only */ /* if xmits > 0. */ @@ -116,23 +126,53 @@ static struct tt_user_s { /* Otherwise, this is a display offset position */ /* from the gateway. */ + char loc_text[24]; /* Text representation of location when a single */ + /* lat/lon point would be deceptive. e.g. */ + /* 32TPP8049 */ + /* 32TPP8179549363 */ + /* 32T 681795 4849363 */ + /* EM29QE78 */ + double latitude, longitude; /* Location either from user or generated */ /* position in the corral. */ char freq[12]; /* Frequency in format 999.999MHz */ - char comment[MAX_COMMENT_LEN+1]; /* Free form comment. */ + char comment[MAX_COMMENT_LEN+1]; /* Free form comment from user. */ + /* Comment sent in final object report includes */ + /* other information besides this. */ char mic_e; /* Position status. */ + /* Should be a character in range of '1' to '9' for */ + /* the predefined status strings or '0' for none. */ char dao[8]; /* Enhanced position information. */ + } tt_user[MAX_TT_USERS]; static void clear_user(int i); -static void xmit_object_report (int i, double c_lat, double c_long, int ambiguity, double c_offs); +static void xmit_object_report (int i, int first_time); + +static void tt_setenv (int i); + + +#if __WIN32__ + +// setenv is missing on Windows! + +int setenv(const char *name, const char *value, int overwrite) +{ + char etemp[1000]; + + snprintf (etemp, sizeof(etemp), "%s=%s", name, value); + putenv (etemp); + return (0); +} + +#endif /*------------------------------------------------------------------ @@ -244,7 +284,7 @@ static void clear_user(int i) { assert (i >= 0 && i < MAX_TT_USERS); - memset (&tt_user[i], 0, sizeof (struct tt_user_s)); + memset (&(tt_user[i]), 0, sizeof (struct tt_user_s)); } /* end clear_user */ @@ -341,7 +381,7 @@ static void digit_suffix (char *callsign, char *suffix) char *t; - strcpy (suffix, "000"); + strlcpy (suffix, "000", sizeof(suffix)); tt_text_to_two_key (callsign, 0, two_key); for (t = two_key; *t != '\0'; t++) { if (isdigit(*t)) { @@ -365,6 +405,7 @@ static void digit_suffix (char *callsign, char *suffix) * ssid * overlay - or symbol table identifier * symbol + * loc_text - Original text for non lat/lon location * latitude * longitude * freq @@ -380,11 +421,15 @@ static void digit_suffix (char *callsign, char *suffix) * *----------------------------------------------------------------*/ -int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double latitude, +int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude, double longitude, char *freq, char *comment, char mic_e, char *dao) { int i; - + + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("tt_user_heard (%s, %d, %c, %c, %s, ...)\n", callsign, ssid, overlay, symbol, loc_text); + /* * At this time all messages are expected to contain a callsign. * Other types of messages, not related to a particular person/object @@ -392,7 +437,7 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double l */ if (callsign[0] == '\0') { text_color_set(DW_COLOR_ERROR); - printf ("APRStt message did not include callsign.\n"); + printf ("APRStt tone sequence did not include callsign / object name.\n"); return (TT_ERROR_NO_CALL); } @@ -410,10 +455,13 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double l assert (i >= 0 && i < MAX_TT_USERS); strncpy (tt_user[i].callsign, callsign, MAX_CALLSIGN_LEN); tt_user[i].callsign[MAX_CALLSIGN_LEN] = '\0'; + tt_user[i].count = 1; tt_user[i].ssid = ssid; tt_user[i].overlay = overlay; tt_user[i].symbol = symbol; digit_suffix(tt_user[i].callsign, tt_user[i].digit_suffix); + strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text)); + if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) { /* We have specific location. */ tt_user[i].corral_slot = 0; @@ -425,7 +473,7 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double l tt_user[i].corral_slot = corral_slot(); } - strcpy (tt_user[i].freq, freq); + strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq)); strncpy (tt_user[i].comment, comment, MAX_COMMENT_LEN); tt_user[i].comment[MAX_COMMENT_LEN] = '\0'; tt_user[i].mic_e = mic_e; @@ -437,8 +485,14 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double l */ assert (i >= 0 && i < MAX_TT_USERS); + tt_user[i].count++; + /* Any reason to look at ssid here? */ + if (strlen(loc_text) > 0) { + strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text)); + } + if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) { /* We have specific location. */ tt_user[i].corral_slot = 0; @@ -447,7 +501,7 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double l } if (freq[0] != '\0') { - strcpy (tt_user[i].freq, freq); + strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq)); } if (comment[0] != '\0') { @@ -471,6 +525,19 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double l tt_user[i].xmits = 0; tt_user[i].next_xmit = tt_user[i].last_heard + save_tt_config_p->xmit_delay[0]; +/* + * Send to applications and IGate immediately. + */ + + xmit_object_report (i, 1); + +/* + * Put properties into environment variables in preparation + * for calling a user-specified script. + */ + + tt_setenv (i); + return (0); /* Success! */ } /* end tt_user_heard */ @@ -497,12 +564,23 @@ void tt_user_background (void) time_t now = time(NULL); int i; + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("tt_user_background() now = %d\n", (int)now); + + for (i=0; i= 0 && i < MAX_TT_USERS); + if (tt_user[i].callsign[0] != '\0') { if (tt_user[i].xmits < save_tt_config_p->num_xmits && tt_user[i].next_xmit <= now) { - 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); + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("tt_user_background() now = %d\n", (int)now); + //tt_user_dump (); + + xmit_object_report (i, 0); /* Increase count of number times this one was sent. */ tt_user[i].xmits++; @@ -510,6 +588,8 @@ void tt_user_background (void) /* Schedule next one. */ tt_user[i].next_xmit += save_tt_config_p->xmit_delay[tt_user[i].xmits]; } + + //tt_user_dump (); } } } @@ -521,7 +601,7 @@ void tt_user_background (void) if (tt_user[i].callsign[0] != '\0') { if (tt_user[i].last_heard + save_tt_config_p->retain_time < now) { - // debug - dw_printf ("debug: purging expired user %d\n", i); + //dw_printf ("debug: purging expired user %d\n", i); clear_user (i); } @@ -536,11 +616,13 @@ void tt_user_background (void) * * Purpose: Create object report packet and put into transmit queue. * - * Inputs: i - Index into user table. - * c_lat - Corral latitude. - * c_long - Corral longitude. - * ambiguity - Number of amibiguity digits: 0, 1, 2, or 3. - * c_offs - Corral (latitude) offset. + * Inputs: i - Index into user table. + * + * first_time - Is this being called immediately after the tone sequence + * was received or after some delay? + * For the former, we send to any attached applications + * and the IGate. + * For the latter, we transmit over radio. * * Outputs: Append to transmit queue. * @@ -561,30 +643,20 @@ void tt_user_background (void) * *----------------------------------------------------------------*/ -static const char *mic_e_position_comment[10] = { - "", - "/off duty ", - "/enroute ", - "/in service", - "/returning ", - "/committed ", - "/special ", - "/priority ", - "/emergency ", - "/custom 1 " }; - -static void xmit_object_report (int i, double c_lat, double c_long, int ambiguity, double c_offs) +static void xmit_object_report (int i, int first_time) { - char object_name[20]; - char object_info[50]; - char info_comment[100]; + char object_name[20]; // xxxxxxxxx or xxxxxx-nn + char info_comment[200]; // usercomment [locationtext] /status !DAO! + char object_info[250]; // info part of Object Report packet + char stemp[300]; // src>dest,path:object_info + double olat, olong; - char stemp[200]; packet_t pp; - unsigned char fbuf[AX25_MAX_PACKET_LEN]; - int flen; char c4[4]; + //text_color_set(DW_COLOR_DEBUG); + //printf ("xmit_object_report (index = %d, first_time = %d) rx = %d, tx = %d\n", i, first_time, + // save_tt_config_p->obj_recv_chan, save_tt_config_p->obj_xmit_chan); assert (i >= 0 && i < MAX_TT_USERS); @@ -592,12 +664,12 @@ static void xmit_object_report (int i, double c_lat, double c_long, int ambiguit * Prepare the object name. * Tack on "-12" if it is a callsign. */ - strcpy (object_name, tt_user[i].callsign); + strlcpy (object_name, tt_user[i].callsign, sizeof(object_name)); if (strlen(object_name) <= 6 && tt_user[i].ssid != 0) { char stemp8[8]; - sprintf (stemp8, "-%d", tt_user[i].ssid); - strcat (object_name, stemp8); + snprintf (stemp8, sizeof(stemp8), "-%d", tt_user[i].ssid); + strlcat (object_name, stemp8, sizeof(object_name)); } if (tt_user[i].corral_slot == 0) { @@ -611,61 +683,93 @@ static void xmit_object_report (int i, double c_lat, double c_long, int ambiguit /* * Use made up position in the corral. */ + double c_lat = save_tt_config_p->corral_lat; // Corral latitude. + double c_long = save_tt_config_p->corral_lon; // Corral longitude. + double c_offs = save_tt_config_p->corral_offset; // Corral (latitude) offset. + olat = c_lat - (tt_user[i].corral_slot - 1) * c_offs; olong = c_long; } /* * Build comment field from various information. + * + * usercomment [locationtext] /status !DAO! + * + * Any frequency is inserted at beginning later. */ - strcpy (info_comment, ""); + strlcpy (info_comment, "", sizeof(info_comment)); if (strlen(tt_user[i].comment) != 0) { - strcat (info_comment, tt_user[i].comment); + strlcat (info_comment, tt_user[i].comment, sizeof(info_comment)); } + + if (strlen(tt_user[i].loc_text) > 0) { + if (strlen(info_comment) > 0) { + strlcat (info_comment, " ", sizeof(info_comment)); + } + strlcat (info_comment, "[", sizeof(info_comment)); + strlcat (info_comment, tt_user[i].loc_text, sizeof(info_comment)); + strlcat (info_comment, "]", sizeof(info_comment)); + } + if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') { - strcat (info_comment, mic_e_position_comment[tt_user[i].mic_e - '0']); + + if (strlen(info_comment) > 0) { + strlcat (info_comment, " ", sizeof(info_comment)); + } + + // Insert "/" if status does not already begin with it. + if (save_tt_config_p->status[tt_user[i].mic_e - '0'][0] != '/') { + strlcat (info_comment, "/", sizeof(info_comment)); + } + strlcat (info_comment, save_tt_config_p->status[tt_user[i].mic_e - '0'], sizeof(info_comment)); } + if (strlen(tt_user[i].dao) > 0) { - strcat (info_comment, tt_user[i].dao); + if (strlen(info_comment) > 0) { + strlcat (info_comment, " ", sizeof(info_comment)); + } + strlcat (info_comment, tt_user[i].dao, sizeof(info_comment)); } /* Official limit is 43 characters. */ - info_comment[MAX_COMMENT_LEN] = '\0'; + //info_comment[MAX_COMMENT_LEN] = '\0'; /* * Packet header is built from mycall (of transmit channel) and software version. */ - strcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall); - strcat (stemp, ">"); - strcat (stemp, APP_TOCALL); + if (save_tt_config_p->obj_xmit_chan >= 0) { + strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall, sizeof(stemp)); + } + else { + strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_recv_chan].mycall, sizeof(stemp)); + } + strlcat (stemp, ">", sizeof(stemp)); + strlcat (stemp, APP_TOCALL, sizeof(stemp)); c4[0] = '0' + MAJOR_VERSION; c4[1] = '0' + MINOR_VERSION; c4[2] = '\0'; - strcat (stemp, c4); + strlcat (stemp, c4, sizeof(stemp)); /* - * Append via path if specified. + * Append via path, for transmission, if specified. */ - if (save_tt_config_p->obj_xmit_via[0] != '\0') { - strcat (stemp, ","); - strcat (stemp, save_tt_config_p->obj_xmit_via); + if ( ! first_time && save_tt_config_p->obj_xmit_via[0] != '\0') { + strlcat (stemp, ",", sizeof(stemp)); + strlcat (stemp, save_tt_config_p->obj_xmit_via, sizeof(stemp)); } - strcat (stemp, ":"); + strlcat (stemp, ":", sizeof(stemp)); encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, tt_user[i].overlay, tt_user[i].symbol, 0,0,0,NULL, 0,0, /* PHGD, C/S */ - atof(tt_user[i].freq), 0, 0, info_comment, object_info); - - strcat (stemp, object_info); - - //text_color_set(DW_COLOR_ERROR); - //printf ("\nDEBUG: %s\n\n", stemp); + atof(tt_user[i].freq), 0, 0, info_comment, object_info, sizeof(object_info)); + strlcat (stemp, object_info, sizeof(stemp)); #if TT_MAIN @@ -673,32 +777,201 @@ static void xmit_object_report (int i, double c_lat, double c_long, int ambiguit #else + if (first_time) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("[APRStt] %s\n", stemp); + } + /* - * Convert to packet and append to transmit queue. + * Convert text to packet. */ pp = ax25_from_text (stemp, 1); - flen = ax25_pack (pp, fbuf); + if (pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\"\n", stemp); + return; + } -/* - * Process as if we heard ourself. + +/* + * Send to one or more of the following depending on configuration: + * Transmit queue. + * Any attached application(s). + * IGate. + * + * When transmitting over the radio, it gets sent multipe times, to help + * probablity of being heard, with increasing delays between. + * + * The other methods are reliable so we only want to send it once. */ - // TODO: We need radio channel where this came from. - // It would make a difference if running two radios - // and they have different station identifiers. - - int chan = 0; - igate_send_rec_packet (chan, pp); - /* Remember it so we don't digipeat our own. */ + if (first_time && save_tt_config_p->obj_send_to_app) { + unsigned char fbuf[AX25_MAX_PACKET_LEN]; + int flen; - dedupe_remember (pp, save_tt_config_p->obj_xmit_chan); + // TODO1.3: Put a wrapper around this so we only call one function to send by all methods. + + flen = ax25_pack(pp, fbuf); + + server_send_rec_packet (save_tt_config_p->obj_recv_chan, pp, fbuf, flen); + kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, fbuf, flen); + kiss_send_rec_packet (save_tt_config_p->obj_recv_chan, fbuf, flen); + } + + if (first_time && save_tt_config_p->obj_send_to_ig) { + + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("xmit_object_report (): send to IGate\n"); + + igate_send_rec_packet (save_tt_config_p->obj_recv_chan, pp); + + } + + if ( ! first_time && save_tt_config_p->obj_xmit_chan >= 0) { + + /* Remember it so we don't digipeat our own. */ + + dedupe_remember (pp, save_tt_config_p->obj_xmit_chan); + + tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp); + } + else { + ax25_delete (pp); + } - tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp); #endif + } +static const char *letters[26] = { + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X-ray", + "Yankee", + "Zulu" +}; + +static const char *digits[10] = { + "Zero", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine" +}; + + +/*------------------------------------------------------------------ + * + * Name: tt_setenv + * + * Purpose: Put information in environment variables in preparation + * for calling a user-supplied script for custom processing. + * + * Inputs: i - Index into tt_user table. + * + * Description: Timestamps displayed relative to current time. + * + *----------------------------------------------------------------*/ + + +static void tt_setenv (int i) +{ + char stemp[256]; + char t2[2]; + char *p; + + assert (i >= 0 && i < MAX_TT_USERS); + + setenv ("TTCALL", tt_user[i].callsign, 1); + + strlcpy (stemp, "", sizeof(stemp)); + t2[1] = '\0'; + for (p = tt_user[i].callsign; *p != '\0'; p++) { + t2[0] = *p; + strlcat (stemp, t2, sizeof(stemp)); + if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp)); + } + setenv ("TTCALLSP", stemp, 1); + + strlcpy (stemp, "", sizeof(stemp)); + for (p = tt_user[i].callsign; *p != '\0'; p++) { + if (isupper(*p)) { + strlcat (stemp, letters[*p - 'A'], sizeof(stemp)); + } + else if (islower(*p)) { + strlcat (stemp, letters[*p - 'a'], sizeof(stemp)); + } + else if (isdigit(*p)) { + strlcat (stemp, digits[*p - '0'], sizeof(stemp)); + } + else { + t2[0] = *p; + strlcat (stemp, t2, sizeof(stemp)); + } + if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp)); + } + setenv ("TTCALLPH", stemp, 1); + + snprintf (stemp, sizeof(stemp), "%d", tt_user[i].ssid); + setenv ("TTSSID",stemp , 1); + + snprintf (stemp, sizeof(stemp), "%d", tt_user[i].count); + setenv ("TTCOUNT",stemp , 1); + + snprintf (stemp, sizeof(stemp), "%c%c", tt_user[i].overlay, tt_user[i].symbol); + setenv ("TTSYMBOL",stemp , 1); + + snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].latitude); + setenv ("TTLAT",stemp , 1); + + snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].longitude); + setenv ("TTLON",stemp , 1); + + setenv ("TTFREQ", tt_user[i].freq, 1); + + setenv ("TTCOMMENT", tt_user[i].comment, 1); + + setenv ("TTLOC", tt_user[i].loc_text, 1); + + if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') { + setenv ("TTSTATUS", save_tt_config_p->status[tt_user[i].mic_e - '0'], 1); + } + else { + setenv ("TTSTATUS", "", 1); + } + + setenv ("TTDAO", tt_user[i].dao, 1); + +} /* end tt_setenv */ + /*------------------------------------------------------------------ @@ -770,7 +1043,7 @@ int main (int argc, char *argv[]) memset (&my_audio_config, 0, sizeof(my_audio_config)); - strcpy (my_audio_config.achan[0].mycall, "WB2OSZ-15"); + strlcpy (my_audio_config.achan[0].mycall, "WB2OSZ-15", sizeof(my_audio_config.achan[0].mycall)); /* Fake TT gateway config. */ diff --git a/tt_user.h b/tt_user.h index 913e915..501953f 100644 --- a/tt_user.h +++ b/tt_user.h @@ -6,7 +6,8 @@ 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, +int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude, double longitude, char *freq, char *comment, char mic_e, char *dao); -void tt_user_background (void); \ No newline at end of file +void tt_user_background (void); +void tt_user_dump (void); \ No newline at end of file diff --git a/utm2ll.c b/utm2ll.c index 75cbada..e1d793c 100644 --- a/utm2ll.c +++ b/utm2ll.c @@ -7,7 +7,7 @@ #include #include - +#include "direwolf.h" #include "utm.h" #include "mgrs.h" #include "usng.h" @@ -40,7 +40,7 @@ int main (int argc, char *argv[]) // 3 command line arguments for UTM - strcpy (szone, argv[1]); + strlcpy (szone, argv[1], sizeof(szone)); lzone = strtoul(szone, &zlet, 10); if (*zlet == '\0') { diff --git a/version.h b/version.h index 51cf3f5..97b5594 100644 --- a/version.h +++ b/version.h @@ -1,8 +1,8 @@ -/* Dire Wolf version 1.2 */ +/* Dire Wolf version 1.3 */ #define APP_TOCALL "APDW" #define MAJOR_VERSION 1 -#define MINOR_VERSION 2 -#define EXTRA_VERSION "Beta Test" +#define MINOR_VERSION 3 +//#define EXTRA_VERSION "Beta Test" diff --git a/walk96.c b/walk96.c new file mode 100644 index 0000000..619ed45 --- /dev/null +++ b/walk96.c @@ -0,0 +1,180 @@ +// +// 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 . +// + + +//#define DEBUG 1 + +/*------------------------------------------------------------------ + * + * Module: walk96.c + * + * Purpose: Quick hack to read GPS location and send very frequent + * position reports frames to a KISS TNC. + * + * + *---------------------------------------------------------------*/ + +#include +#include + +#if __WIN32__ +#include +#include +#else +#define __USE_XOPEN2KXSI 1 +#define __USE_XOPEN 1 +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include "direwolf.h" +#include "config.h" +#include "ax25_pad.h" +#include "textcolor.h" +#include "latlong.h" +#include "nmea.h" +#include "encode_aprs.h" +#include "serial_port.h" + + +#define MYCALL "WB2OSZ" /************ Change this if you use it!!! ***************/ + +static MYFDTYPE tnc; + + +main (int argc, char *argv[]) +{ + struct misc_config_s config; + char cmd[100]; + + + // Look for Silicon Labs CP210x + // Just happens to be same on desktop & laptop. + + tnc = serial_port_open ("COM5", 9600); + if (tnc == MYFDERROR) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Can't open serial port to KISS TNC.\n"); + exit (EXIT_FAILURE); // defined in stdlib.h + } + + strcpy (cmd, "\r\rhbaud 9600\rkiss on\rrestart\r"); + + serial_port_write (tnc, cmd, strlen(cmd)); + SLEEP_MS(500); + + memset (&config, 0, sizeof(config)); + strcpy (config.nmea_port, "COM1"); + nmea_init (&config); + + SLEEP_SEC(20); + + // Exit out of KISS mode. + + serial_port_write (tnc, "\xc0\xff\c0", 3); + + SLEEP_MS(100); + +} + + +/* Should be called once per second. */ + +void walk96 (int fix, double lat, double lon, float knots, float course, float alt) +{ + static int sequence = 0; + char comment[50]; + + sequence++; + sprintf (comment, "Sequence number %04d", sequence); + + +/* + * Construct the packet in normal monitoring format. + */ + + int messaging = 0; + int compressed = 0; + + char info[AX25_MAX_INFO_LEN]; + int info_len; + + char position_report[AX25_MAX_PACKET_LEN]; + + info_len = encode_position (messaging, compressed, + lat, lon, (int)(DW_METERS_TO_FEET(alt)), + '/', '?', // TODO: look up code for person. + G_UNKNOWN, G_UNKNOWN, G_UNKNOWN, "", // PHG + (int)course, (int)knots, + 445.925, 0, 0, + comment, + info, sizeof(info)); + + sprintf (position_report, "%s>WALK96:%s", MYCALL, info); + + text_color_set (DW_COLOR_XMIT); + dw_printf ("%s\n", position_report); + +/* + * Convert it into AX.25 frame. + */ + packet_t pp; + unsigned char ax25_frame[AX25_MAX_PACKET_LEN]; + int frame_len; + + pp = ax25_from_text (position_report, 1); + + if (pp == NULL) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Unexpected error in ax25_from_text. Quitting.\n"); + exit (EXIT_FAILURE); // defined in stdlib.h + } + + ax25_frame[0] = 0; // Insert channel before KISS encapsulation. + + frame_len = ax25_pack (pp, ax25_frame+1); + ax25_delete (pp); + +/* + * Encapsulate as KISS and send to TNC. + */ + + unsigned char kiss_frame[AX25_MAX_PACKET_LEN*2]; + int kiss_len; + + kiss_len = kiss_encapsulate (ax25_frame, frame_len+1, (unsigned char *)kiss_frame); + + //text_color_set (DW_COLOR_DEBUG); + //dw_printf ("AX.25 frame length = %d, KISS frame length = %d\n", frame_len, kiss_len); + + //kiss_debug_print (1, NULL, kiss_frame, kiss_len); + + serial_port_write (tnc, kiss_frame, kiss_len); + +} + +/* end walk96.c */ diff --git a/xmit.c b/xmit.c index e94c5da..f468279 100644 --- a/xmit.c +++ b/xmit.c @@ -76,6 +76,7 @@ #include "hdlc_rec.h" #include "ptt.h" #include "dtime_now.h" +#include "morse.h" @@ -98,7 +99,7 @@ static int xmit_persist[MAX_CHANS]; /* Sets probability for transmitting after e static int xmit_txdelay[MAX_CHANS]; /* After turning on the transmitter, */ /* send "flags" for txdelay * 10 mS. */ -static int xmit_txtail[MAX_CHANS]; /* Amount of time to keep transmitting after we */ +static int xmit_txtail[MAX_CHANS]; /* Amount of time to keep transmitting after we */ /* are done sending the data. This is to avoid */ /* dropping PTT too soon and chopping off the end */ /* of the frame. Again 10 mS units. */ @@ -137,6 +138,7 @@ static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS]; static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist); static void xmit_ax25_frames (int c, int p, packet_t pp); static void xmit_speech (int c, packet_t pp); +static void xmit_morse (int c, packet_t pp, int wpm); /*------------------------------------------------------------------- @@ -433,11 +435,18 @@ static void * xmit_thread (void *arg) if (ok) { /* * Channel is clear and we have lock on output device. + * + * If destination is "SPEECH" send info part to speech synthesizer. + * If destination is "MORSE" send as morse code. */ char dest[AX25_MAX_ADDR_LEN]; + int ssid = 0; + if (ax25_is_aprs (pp)) { - ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); + + ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest); + ssid = ax25_get_ssid(pp, AX25_DESTINATION); } else { strcpy (dest, ""); @@ -446,6 +455,24 @@ static void * xmit_thread (void *arg) if (strcmp(dest, "SPEECH") == 0) { xmit_speech (c, pp); } + else if (strcmp(dest, "MORSE") == 0) { + + int wpm = ssid * 2; + if (wpm == 0) wpm = MORSE_DEFAULT_WPM; + + // This is a bit of a hack so we don't respond too quickly for APRStt. + // It will be sent in high priority queue while a beacon wouldn't. + // Add a little delay so user has time release PTT after sending #. + // This and default txdelay would give us a second. + + if (p == TQ_PRIO_0_HI) { + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("APRStt morse xmit delay hack...\n"); + SLEEP_MS (700); + } + + xmit_morse (c, pp, wpm); + } else { xmit_ax25_frames (c, p, pp); } @@ -748,12 +775,13 @@ static void xmit_ax25_frames (int c, int p, packet_t pp) } /* end xmit_ax25_frames */ + /*------------------------------------------------------------------- * - * Name: xmit_ax25_frames + * Name: xmit_speech * * Purpose: After we have a clear channel, and possibly waited a random time, - * we transmit one or more frames. + * we transmit information part of frame as speech. * * Inputs: c - Channel number. * @@ -765,7 +793,6 @@ static void xmit_ax25_frames (int c, int p, packet_t pp) * Invoke the text-to-speech script. * Turn off transmitter. * - * *--------------------------------------------------------------------*/ @@ -861,6 +888,51 @@ int xmit_speak_it (char *script, int c, char *orig_msg) } + +/*------------------------------------------------------------------- + * + * Name: xmit_morse + * + * Purpose: After we have a clear channel, and possibly waited a random time, + * we transmit information part of frame as Morse code. + * + * Inputs: c - Channel number. + * + * pp - Packet object pointer. + * It will be deleted so caller should not try + * to reference it after this. + * + * wpm - Speed in words per minute. + * + * Description: Turn on transmitter. + * Send text as Morse code. + * Turn off transmitter. + * + *--------------------------------------------------------------------*/ + + +static void xmit_morse (int c, packet_t pp, int wpm) +{ + + + int info_len; + unsigned char *pinfo; + + + info_len = ax25_get_info (pp, &pinfo); + text_color_set(DW_COLOR_XMIT); + dw_printf ("[%d.morse] \"%s\"\n", c, pinfo); + + ptt_set (OCTYPE_PTT, c, 1); + + morse_send (c, (char*)pinfo, wpm, xmit_txdelay[c] * 10, xmit_txtail[c] * 10); + + ptt_set (OCTYPE_PTT, c, 0); + ax25_delete (pp); + +} /* end xmit_morse */ + + /*------------------------------------------------------------------- * * Name: wait_for_clear_channel