Merge remote-tracking branch 'origin/dev'

Put version 1.4 into master branch.
This commit is contained in:
WB2OSZ 2017-04-26 20:03:55 -04:00
commit 612c2dc928
119 changed files with 18492 additions and 3122 deletions

View File

@ -1,12 +1,82 @@
# Revision History # # Revision History #
## Version 1.4 -- April 2017 ##
### New Features: ###
- AX.25 v2.2 connected mode. See chapter 10 of User Guide for details.
- New client side packet filter to select "messages" only to stations that have been heard nearby recently. This is now the default if no IS to RF filter is specified.
- New beacon type, IBEACON, for sending IGate statistics.
- Expanded debug options so you can understand what is going on with packet filtering.
- Added new document ***Successful-APRS-IGate-Operation.pdf*** with IGate background, configuration, and troubleshooting tips.
- 2400 & 4800 bps PSK modems. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** in the doc directory for discussion.
- The top speed of 9600 bps has been increased to 38400. You will need a sound card capable of 96k or 192k samples per second for the higher rates. Radios must also have adequate bandwidth. See ***Going-beyond-9600-baud.pdf*** in the doc directory for more details.
- Better decoder performance for 9600 and higher especially for low audio sample rate to baud ratios.
- Generate waypoint sentences for use by AvMap G5 / G6 or other mapping devices or applications. Formats include
- $GPWPL - NMEA generic with only location and name.
- $PGRMW - Garmin, adds altitude, symbol, and comment to previously named waypoint.
- $PMGNWPL - Magellan, more complete for stationary objects.
- $PKWDWPL - Kenwood with APRS style symbol but missing comment.
- DTMF tones can be sent by putting "DTMF" in the destination address, similar to the way that Morse Code is sent.
- Take advantage of new 'gpio' group and new /sys/class/gpio ownership in Raspbian Jessie.
- Handle more complicated gpio naming for CubieBoard, etc.
- More flexible dw-start.sh start up script for both GUI and CLI environments.
### Bugs Fixed: ###
- The transmitter (PTT control) was being turned off too soon when sending Morse Code.
- The -qd (quiet decode) command line option now suppresses errors about improperly formed Telemetry packets.
- Longer tocall.txt files can now be handled.
- Sometimes kissattach would have an issue with the Dire Wolf pseudo terminal. This showed up most often on Raspbian but sometimes occurred with other versions of Linux.
*kissattach: Error setting line discipline: TIOCSETD: Device or resource busy
Are you sure you have enabled MKISS support in the kernel
or, if you made it a module, that the module is loaded?*
- Sometimes writes to a pseudo terminal would block causing the received
frame processing thread to hang. The first thing you will notice is that
received frames are not being printed. After a while this message will appear:
*Received frame queue is out of control. Length=... Reader thread is probably
frozen. This can be caused by using a pseudo terminal (direwolf -p) where
another application is not reading the frames from the other side.*
- -p command line option caused segmentation fault with glibc >= 2.24.
- The Windows version 1.3 would crash when starting to transmit on Windows XP. There have also been some other reports of erratic behavior on Windows. The crashing problem was fixed in in the 1.3.1 patch release. Linux version was not affected.
- IGate did not retain nul characters in the information part of a packet. This should never happen with a valid APRS packet but there are a couple cases where it has. If we encounter these malformed packets, pass them along as-is, rather than truncating.
- Don't digipeat packets when the source is my call.
---------- ----------
## Version 1.3 -- May 2016 ## ## Version 1.3 -- May 2016 ##
This is the same as the 1.3 beta test version with a few minor documentation updates. If you are already using 1.3 beta test, there is no need to install this.
### New Features: ### ### New Features: ###
- Support for Mac OS X. - Support for Mac OS X.
@ -63,7 +133,7 @@ such as PowerPC or MIPS.
- Improved decoder performance. - Improved decoder performance.
Over 1000 error-free frames decoded from WA8LMF TNC Test CD. Over 1000 error-free frames decoded from WA8LMF TNC Test CD.
See "A-Better-APRS-Packet-Demodulator.pdf" for details. See ***A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf*** for details.
- Up to 3 soundcards and 6 radio channels can be handled at the same time. - Up to 3 soundcards and 6 radio channels can be handled at the same time.
@ -274,8 +344,7 @@ to rebuild it from source.
### New Features: ### ### New Features: ###
- Added APRStt gateway capability. For details, see: - Added APRStt gateway capability. For details, see ***APRStt-Implementation-Notes.pdf***
**APRStt-Implementation-Notes.pdf**
----------- -----------

View File

@ -12,9 +12,32 @@ all : $(APPS) direwolf.desktop direwolf.conf
@echo " " @echo " "
CC := gcc CC := gcc
CFLAGS := -O3 -pthread -Igeotranz
LDFLAGS := -lm -lpthread -lrt # Just for fun, let's see how clang compares to gcc. First install like this:
# sudo apt-get update
# apt-cache search clang
# sudo apt-get install clang-3.5
#
# CC := clang-3.5
# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24.
# Explanation here: https://github.com/wb2osz/direwolf/issues/62
# There are a few source files where it had been necessary to define __USE_XOPEN2KXSI,
# __USE_XOPEN, or _POSIX_C_SOURCE. Doesn't seem to be needed after adding this.
# The first assignment to CFLAGS and LDFLAGS uses +=, rather than :=, so
# we will inherit options already set in build environment.
# Explanation - https://github.com/wb2osz/direwolf/pull/38
CFLAGS += -O3 -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 -Wall
# That was fine for a recent Ubuntu and Raspbian Jessie.
# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set.
CFLAGS += -D_BSD_SOURCE
LDFLAGS += -lm -lpthread -lrt
@ -106,7 +129,26 @@ endif
# One article said it was added with gcc 4.2 but I haven't verified that. # 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. # ---------- How does clang compare? - Ubuntu x86_64 ----------
#
# I keep hearing a lot about "clang." Let's see how it compares with gcc.
# Simply use different compiler and keep all options the same.
#
# test case: atest 02_Track_2.wav
#
# gcc 4.8.4: 988 packets decoded in 40.503 seconds. 38.3 x realtime
# 988 packets decoded in 40.403 seconds. 38.4 x realtime
#
# clang 3.8.0-2: 988 packets decoded in 77.454 seconds. 20.0 x realtime
# 988 packets decoded in 77.232 seconds. 20.1 x realtime
#
# I'm not impressed. Almost twice as long. Maybe we need to try some other compiler options.
# -march=native did not help.
# Makefile.macosx, which uses clang, has these:
# -fvectorize -fslp-vectorize -fslp-vectorize-aggressive
# Those did not help.
#
useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math)
ifneq ($(useffast),) ifneq ($(useffast),)
@ -193,10 +235,27 @@ endif
# cause compatibility issues for those with older computers. # cause compatibility issues for those with older computers.
# #
# ---------- How does clang compare? - ARM - Raspberry Pi 2 ----------
#
# I keep hearing a lot about "clang." Let's see how it compares with gcc.
# Simply use different compiler and keep all options the same.
#
# test case: atest 02_Track_2.wav
#
# gcc 4.9.2-10: 988 packets decoded in 353.025 seconds. 4.4 x realtime
# 988 packets decoded in 352.752 seconds. 4.4 x realtime
#
# clang 3.5.0-10: 988 packets decoded in 825.879 seconds. 1.9 x realtime
# 988 packets decoded in 831.469 seconds. 1.9 x realtime
#
# There might be a different set of options which produce faster code
# but the initial test doesn't look good. About 2.3 times as slow.
# If you want to use OSS (for FreeBSD, OpenBSD) instead of # If you want to use OSS (for FreeBSD, OpenBSD) instead of
# ALSA (for Linux), comment out (or remove) the two lines below. # ALSA (for Linux), comment out (or remove) the two lines below.
# TODO: Can we automate this somehow?
CFLAGS += -DUSE_ALSA CFLAGS += -DUSE_ALSA
LDFLAGS += -lasound LDFLAGS += -lasound
@ -213,6 +272,8 @@ endif
# Uncomment following lines to enable hamlib support. # Uncomment following lines to enable hamlib support.
# TODO: automate this too. See if hamlib has been installed.
#CFLAGS += -DUSE_HAMLIB #CFLAGS += -DUSE_HAMLIB
#LDFLAGS += -lhamlib #LDFLAGS += -lhamlib
@ -228,14 +289,14 @@ z := $(notdir ${CURDIR})
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \ direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \
hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \ hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o \ fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.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 audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \ gen_tone.o audio.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \ ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o serial_port.o log.o telemetry.o \ dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o \ dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o \
misc.a geotranz.a misc.a geotranz.a
$(CC) -o $@ $^ $(LDFLAGS) $(CC) -o $@ $^ $(LDFLAGS)
ifneq ($(enable_gpsd),) ifneq ($(enable_gpsd),)
@ -253,10 +314,13 @@ demod.o : fsk_fast_filter.h
demod_afsk.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h
fsk_fast_filter.h : demod_afsk.c fsk_fast_filter.h : gen_fff
$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c $(LDFLAGS)
./gen_fff > fsk_fast_filter.h ./gen_fff > fsk_fast_filter.h
gen_fff : demod_afsk.c dsp.c textcolor.c
echo " " > tune.h
$(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS)
# #
# The destination field is often used to identify the manufacturer/model. # The destination field is often used to identify the manufacturer/model.
@ -322,15 +386,15 @@ log2gpx : log2gpx.c textcolor.o misc.a
# Test application to generate sound. # Test application to generate sound.
gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c misc.a gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Unit test for AFSK demodulator # Unit test for AFSK demodulator
atest : atest.c demod.o demod_afsk.o demod_9600.o \ atest : atest.c demod.o demod_afsk.o demod_psk.o demod_9600.o \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \ fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \
dwgps.o dwgpsd.o serial_port.o telemetry.o latlong.o symbols.o tt_text.o textcolor.o \ dwgps.o dwgpsd.o serial_port.o telemetry.o dtime_now.o latlong.o symbols.o tt_text.o textcolor.o \
misc.a misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
@ -522,7 +586,7 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
# the home directory or other desired location. # the home directory or other desired location.
# #
$(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/examples/direwolf.conf $(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/examples/direwolf.conf
$(INSTALL) -D --mode=644 dw-start.sh $(INSTALLDIR)/share/doc/direwolf/examples/dw-start.sh $(INSTALL) -D --mode=755 dw-start.sh $(INSTALLDIR)/share/doc/direwolf/examples/dw-start.sh
$(INSTALL) -D --mode=644 sdr.conf $(INSTALLDIR)/share/doc/direwolf/examples/sdr.conf $(INSTALL) -D --mode=644 sdr.conf $(INSTALLDIR)/share/doc/direwolf/examples/sdr.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt $(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-balloon.conf $(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-balloon.conf
@ -549,19 +613,22 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
@echo " " @echo " "
# Put sample configuration files in home directory.
# These would be done as ordinary user. # These would be done as ordinary user.
# The Raspberry Pi has ~/Desktop but Ubuntu does not. # The Raspberry Pi has ~/Desktop but Ubuntu does not.
# TODO: Handle Linux variations correctly. # TODO: Handle Linux variations correctly.
# Version 1.4 - Add "-n" option to avoid clobbering existing, probably customized, config files.
.PHONY: install-conf .PHONY: install-conf
install-conf : direwolf.conf install-conf : direwolf.conf
cp direwolf.conf ~ cp -n direwolf.conf ~
cp sdr.conf ~ cp -n sdr.conf ~
cp telemetry-toolkit/telem-m0xer-3.txt ~ cp -n telemetry-toolkit/telem-m0xer-3.txt ~
cp telemetry-toolkit/telem-*.conf ~ cp -n telemetry-toolkit/telem-*.conf ~
ifneq ($(wildcard $(HOME)/Desktop),) ifneq ($(wildcard $(HOME)/Desktop),)
@echo " " @echo " "
@echo "This will add a desktop icon on some systems:" @echo "This will add a desktop icon on some systems:"
@ -571,9 +638,13 @@ ifneq ($(wildcard $(HOME)/Desktop),)
endif endif
# dw-start.sh is greatly improved in version 1.4.
# It should probably be part of install-conf because it is not just for the RPi.
.PHONY: install-rpi .PHONY: install-rpi
install-rpi : dw-start.sh install-rpi : dw-start.sh
cp dw-start.sh ~ chmod +x dw-start.sh
cp -n dw-start.sh ~
ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop
@ -585,15 +656,15 @@ install-rpi : dw-start.sh
# Combine some unit tests into a single regression sanity check. # Combine some unit tests into a single regression sanity check.
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600 check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates? # Can we encode and decode at popular data rates?
check-modem1200 : gen_packets atest check-modem1200 : gen_packets atest
./gen_packets -n 100 -o /tmp/test1.wav ./gen_packets -n 100 -o /tmp/test12.wav
./atest -F0 -PE -L70 -G71 /tmp/test1.wav ./atest -F0 -PE -L63 -G71 /tmp/test12.wav
./atest -F1 -PE -L73 -G75 /tmp/test1.wav ./atest -F1 -PE -L70 -G75 /tmp/test12.wav
#rm /tmp/test1.wav rm /tmp/test12.wav
check-modem300 : gen_packets atest check-modem300 : gen_packets atest
./gen_packets -B300 -n 100 -o /tmp/test3.wav ./gen_packets -B300 -n 100 -o /tmp/test3.wav
@ -602,18 +673,35 @@ check-modem300 : gen_packets atest
rm /tmp/test3.wav rm /tmp/test3.wav
check-modem9600 : gen_packets atest check-modem9600 : gen_packets atest
./gen_packets -B9600 -n 100 -o /tmp/test9.wav ./gen_packets -B9600 -n 100 -o /tmp/test96.wav
./atest -B9600 -F0 -L57 -G59 /tmp/test9.wav ./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav
./atest -B9600 -F1 -L66 -G67 /tmp/test9.wav ./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav
rm /tmp/test9.wav rm /tmp/test96.wav
check-modem19200 : gen_packets atest
./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav
./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav
./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav
rm /tmp/test19.wav
check-modem2400 : gen_packets atest
./gen_packets -B2400 -n 100 -o /tmp/test24.wav
./atest -B2400 -F0 -L70 -G78 /tmp/test24.wav
./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav
rm /tmp/test24.wav
check-modem4800 : gen_packets atest
./gen_packets -B2400 -n 100 -o /tmp/test48.wav
./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav
./atest -B2400 -F1 -L80 -G90 /tmp/test48.wav
rm /tmp/test48.wav
# Unit test for inner digipeater algorithm # Unit test for inner digipeater algorithm
.PHONY : dtest .PHONY : dtest
dtest : digipeater.c dedupe.c \ dtest : digipeater.c dedupe.c pfilter.c \
pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \ ax25_pad.o fcs_calc.o tq.o textcolor.o \
decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a
$(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS)
./dtest ./dtest
@ -679,6 +767,32 @@ kisstest : kiss_frame.c
./kisstest ./kisstest
rm kisstest rm kisstest
# Unit test for constructing frames besides UI.
.PHONY: pad2test
pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a
$(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ $(LDFLAGS)
./pad2test
rm pad2test
# Unit Test for XID frame encode/decode.
.PHONY: xidtest
xidtest : xid.c textcolor.o misc.a
$(CC) $(CFLAGS) -DXIDTEST -o $@ $^ $(LDFLAGS)
./xidtest
rm xidtest
# Unit Test for DTMF encode/decode.
.PHONY: dtmftest
dtmftest : dtmf.c textcolor.o
$(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ $(LDFLAGS)
./dtmftest
rm dtmftest
# ----------------------------- Manual tests and experiments --------------------------- # ----------------------------- Manual tests and experiments ---------------------------
@ -694,7 +808,7 @@ itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a
# Unit test for UDP reception with AFSK demodulator. # Unit test for UDP reception with AFSK demodulator.
# Temporary during development. Might not be useful anymore. # Temporary during development. Might not be useful anymore.
udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
./udptest ./udptest
@ -703,15 +817,38 @@ udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o hdlc_rec
# Dependencies of demod*.c, rather than .o, are intentional. # Dependencies of demod*.c, rather than .o, are intentional.
demod.o : tune.h demod.o : tune.h
demod_afsk.o : tune.h demod_afsk.o : tune.h
demod_9600.o : tune.h demod_9600.o : tune.h
testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ demod_psk.o : tune.h
fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o latlong.o symbols.o tune.h textcolor.o misc.a
tune.h :
echo " " > tune.h
testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o dtime_now.o latlong.o symbols.o tune.h textcolor.o misc.a
$(CC) $(CFLAGS) -o atest $^ $(LDFLAGS) $(CC) $(CFLAGS) -o atest $^ $(LDFLAGS)
./atest 02_Track_2.wav | grep "packets decoded in" > atest.out ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a
rm -f atest96
$(CC) $(CFLAGS) -o atest96 $^ $(LDFLAGS)
./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out
#./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
@ -757,8 +894,7 @@ dist-src : README.md CHANGES.md
.PHONY: clean .PHONY: clean
clean : clean :
rm -f $(APPS) fsk_fast_filter.h *.o *.a direwolf.desktop rm -f $(APPS) gen_fff tune.h fsk_fast_filter.h *.o *.a direwolf.desktop
echo " " > tune.h
depend : $(wildcard *.c) depend : $(wildcard *.c)

View File

@ -24,8 +24,9 @@
# 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having # 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having
# a hissy fit. Not check with GCC. # a hissy fit. Not check with GCC.
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.conf APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
@echo " "
all : $(APPS) direwolf.desktop direwolf.conf @echo " "
@echo "Next step install with: " @echo "Next step install with: "
@echo " " @echo " "
@echo " sudo make install" @echo " sudo make install"
@ -70,7 +71,18 @@ endif
#CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN) #CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN)
CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN) CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN)
CFLAGS := -Os -pthread -Igeotranz $(EXTRA_CFLAGS)
# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24.
# Explanation here: https://github.com/wb2osz/direwolf/issues/62
CFLAGS := -Os -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 $(EXTRA_CFLAGS)
# That was fine for a recent Ubuntu and Raspbian Jessie.
# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set.
CFLAGS += -D_BSD_SOURCE
# $(info $$CC is [${CC}]) # $(info $$CC is [${CC}])
# #
@ -220,15 +232,15 @@ z := $(notdir ${CURDIR})
# Main application. # Main application.
direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \ direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25_pad.o ax25_pad2.o beacon.o \
config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o \ config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o demod_psk.o \
demod.o digipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \ demod.o digipeater.o cdigipeater.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 \ 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 \ 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 \ 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 \ waypoint.o serial_port.o pfilter.o ptt.o rdq.o recv.o rrbb.o server.o \
symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xmit.o \ symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xid.o xmit.o \
dwgps.o dwgpsnmea.o dwgps.o dwgpsnmea.o mheard.o
$(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm $(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm
@ -239,10 +251,15 @@ demod.o : fsk_fast_filter.h
demod_afsk.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h
fsk_fast_filter.h : demod_afsk.c fsk_fast_filter.h : gen_fff
$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm
./gen_fff > fsk_fast_filter.h ./gen_fff > fsk_fast_filter.h
gen_fff : demod_afsk.c dsp.c textcolor.c
echo " " > tune.h
$(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS)
# UTM, USNG, MGRS conversions. # UTM, USNG, MGRS conversions.
geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o
@ -392,10 +409,6 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
# These would be done as ordinary user. # These would be done as ordinary user.
# The Raspberry Pi has ~/Desktop but Ubuntu does not.
# TODO: Handle Linux variations correctly.
.PHONY: install-conf .PHONY: install-conf
install-conf : direwolf.conf install-conf : direwolf.conf
@ -406,7 +419,7 @@ install-conf : direwolf.conf
# Separate application to decode raw data. # Separate application to decode raw data.
decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o
$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm
# Convert between text and touch tone representation. # Convert between text and touch tone representation.
@ -435,25 +448,33 @@ log2gpx : log2gpx.c
# Test application to generate sound. # 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 gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm
demod.o : tune.h demod.o : tune.h
demod_afsk.o : tune.h demod_afsk.o : tune.h
demod_9600.o : tune.h demod_9600.o : tune.h
demod_psk.o : tune.h
tune.h :
echo " " > 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 \ 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 dtime_now.o latlong.c symbols.c tune.h textcolor.c
$(CC) $(CFLAGS) -o atest $^ -lm $(CC) $(CFLAGS) -o atest $^ -lm
./atest 02_Track_2.wav | grep "packets decoded in" > atest.out ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
# Unit test for AFSK demodulator # Unit test for demodulators
atest : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ atest : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c dtest_now.o latlong.c symbols.c textcolor.c tt_text.c
$(CC) $(CFLAGS) -o $@ $^ -lm $(CC) $(CFLAGS) -o $@ $^ -lm
#atest : atest.c fsk_fast_filter.h demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ #atest : atest.c fsk_fast_filter.h demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \
# fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c # fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c
# $(CC) $(CFLAGS) -o $@ $^ -lm # $(CC) $(CFLAGS) -o $@ $^ -lm
@ -513,7 +534,7 @@ depend : $(wildcard *.c)
.PHONY: clean .PHONY: clean
clean : clean :
rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc \ rm -f $(APPS) gen_fff \
fsk_fast_filter.h *.o *.a use_this_sdk fsk_fast_filter.h *.o *.a use_this_sdk
echo " " > tune.h echo " " > tune.h

View File

@ -16,18 +16,41 @@
# #
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest
# People say we need -mthreads option for threads to work properly. # People say we need -mthreads option for threads to work properly.
# They also say it creates a dependency on mingwm10.dll but I'm not seeing that. # They also say it creates a dependency on mingwm10.dll but I'm not seeing that.
# Maybe that is for pthreads. We are using the Windows threads.
# -Ofast was added in gcc 4.6 which was the MinGW version back in 2012.
CC := gcc CC := gcc
CFLAGS := -Wall -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC -Wall -Wlogical-op
AR := ar AR := ar
CFLAGS += -g CFLAGS += -g
# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3.
# gcc 4.8 adds these. Try them just for fun.
# No, it needs libasan which is not on Windows.
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
# Helpful for the demodulators. Overkill for non-hot spots.
#CFLAGS += -Wdouble-promotion
# Don't have the patience for this right now.
#CFLAGS += -Wextra
# Continue working on these.
CFLAGS += -Wsign-compare
CFLAGS += -Wuninitialized
CFLAGS += -Wold-style-declaration
# CFLAGS += -fdelete-null-pointer-checks -Wnull-dereference ---not recognized
#CFLAGS += -Wold-style-definition
#-Wmissing-prototypes
# #
# Let's see impact of various optimization levels. # Let's see impact of various optimization levels.
# Benchmark results with MinGW gcc version 4.6.2. # Benchmark results with MinGW gcc version 4.6.2.
@ -61,19 +84,25 @@ CFLAGS += -g
# -------------------------------------- Main application -------------------------------- # -------------------------------------- Main application --------------------------------
# Not sure why this is here.
demod.o : fsk_demod_state.h demod.o : fsk_demod_state.h
demod_9600.o : fsk_demod_state.h demod_9600.o : fsk_demod_state.h
demod_afsk.o : fsk_demod_state.h demod_afsk.o : fsk_demod_state.h
demod_psk.o : fsk_demod_state.h
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \
hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o dlq.o \ direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \
fcs_calc.o ax25_pad.o \ hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o pfilter.o dedupe.o tq.o xmit.o \ gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o \
ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \ ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o nmea.o serial_port.o log.o telemetry.o \ dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
dwgps.o dwgpsnmea.o dtime_now.o \ dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o \
dw-icon.o regex.a misc.a geotranz.a dw-icon.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
@ -88,10 +117,13 @@ demod.o : fsk_fast_filter.h
demod_afsk.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h
fsk_fast_filter.h : demod_afsk.c fsk_fast_filter.h : gen_fff
$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c
./gen_fff > fsk_fast_filter.h ./gen_fff > fsk_fast_filter.h
gen_fff : demod_afsk.c dsp.c textcolor.c
echo " " > tune.h
$(CC) $(CFLAGS) -DGEN_FFF -o $@ $^
# #
# The destination field is often used to identify the manufacturer/model. # The destination field is often used to identify the manufacturer/model.
@ -157,7 +189,7 @@ log2gpx : log2gpx.c textcolor.o misc.a
# Test application to generate sound. # Test application to generate sound.
gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o textcolor.o dsp.o misc.a regex.a gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o dtmf.o textcolor.o dsp.o misc.a regex.a
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
@ -198,7 +230,7 @@ utm.o : geotranz/utm.c
# functions supplied by the gnu C library. # functions supplied by the gnu C library.
# For the native WIN32 version, we need to use our own copy. # For the native WIN32 version, we need to use our own copy.
# These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm # These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm
# # Consider upgrading from https://www.gnu.org/software/libc/sources.html
regex.a : regex.o regex.a : regex.o
ar -cr $@ $^ ar -cr $@ $^
@ -207,7 +239,8 @@ regex.o : regex/regex.c
$(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^ $(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^
# There are several string functios found in Linux
# There are several string functions found in Linux
# but not on Windows. Need to provide our own copy. # but not on Windows. Need to provide our own copy.
misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o
@ -235,36 +268,78 @@ strlcat.o : misc/strlcat.c
# Combine some unit tests into a single regression sanity check. # Combine some unit tests into a single regression sanity check.
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest check-modem1200 check-modem300 check-modem9600 check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates? # Can we encode and decode at popular data rates?
# Verify that single bit fixup increases the count. # Verify that single bit fixup increases the count.
check-modem1200 : gen_packets atest check-modem1200 : gen_packets atest
gen_packets -n 100 -o test1.wav gen_packets -n 100 -o test12.wav
atest -F0 -PE -L70 -G71 test1.wav atest -F0 -PE -L64 -G72 test12.wav
atest -F1 -PE -L73 -G75 test1.wav atest -F1 -PE -L70 -G75 test12.wav
#rm test1.wav rm test12.wav
check-modem300 : gen_packets atest check-modem300 : gen_packets atest
gen_packets -B300 -n 100 -o test3.wav gen_packets -B300 -n 100 -o test3.wav
atest -B300 -F0 -L68 -G69 test3.wav atest -B300 -F0 -L68 -G69 test3.wav
atest -B300 -F1 -L73 -G75 test3.wav atest -B300 -F1 -L71 -G75 test3.wav
rm test3.wav rm test3.wav
#FIXME: test full amplitude.
check-modem9600 : gen_packets atest check-modem9600 : gen_packets atest
gen_packets -B9600 -n 100 -o test9.wav gen_packets -B9600 -a 170 -o test96.wav
atest -B9600 -F0 -L57 -G59 test9.wav sleep 1
atest -B9600 -F1 -L66 -G67 test9.wav atest -B9600 -F0 -L4 -G4 test96.wav
rm test9.wav sleep 1
rm test96.wav
sleep 1
gen_packets -B9600 -n 100 -o test96.wav
sleep 1
atest -B9600 -F0 -L50 -G54 test96.wav
atest -B9600 -F1 -L55 -G59 test96.wav
sleep 1
rm test96.wav
# Unit test for AFSK demodulator check-modem19200 : gen_packets atest
gen_packets -r 96000 -B19200 -a 170 -o test19.wav
sleep 1
atest -B19200 -F0 -L4 test19.wav
sleep 1
rm test19.wav
sleep 1
gen_packets -r 96000 -B19200 -n 100 -o test19.wav
sleep 1
atest -B19200 -F0 -L55 -G59 test19.wav
atest -B19200 -F1 -L60 -G64 test19.wav
sleep 1
rm test19.wav
atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_9600.c \ check-modem2400 : gen_packets atest
gen_packets -B2400 -n 100 -o test24.wav
sleep 1
atest -B2400 -F0 -L70 -G78 test24.wav
atest -B2400 -F1 -L80 -G87 test24.wav
sleep 1
rm test24.wav
check-modem4800 : gen_packets atest
gen_packets -B4800 -n 100 -o test48.wav
sleep 1
atest -B4800 -F0 -L70 -G74 test48.wav
atest -B4800 -F1 -L79 -G84 test48.wav
sleep 1
rm test48.wav
# Unit test for demodulators
atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.c \ dwgpsnmea.o dwgps.o serial_port.o latlong.c \
symbols.c tt_text.c textcolor.c telemetry.c \ symbols.c tt_text.c textcolor.c telemetry.c dtime_now.o \
decode_aprs.o log.o \
misc.a regex.a misc.a regex.a
echo " " > tune.h echo " " > tune.h
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
@ -272,8 +347,8 @@ atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_9600.c \
#atest -B 9600 z9.wav #atest -B 9600 z9.wav
#atest za100.wav #atest za100.wav
atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ atest9 : atest.c demod.c dsp.c demod_afsk.c demod_psk.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 textcolor.c telemetry.c dtime_now.o misc.a regex.a \
fsk_fast_filter.h fsk_fast_filter.h
echo " " > tune.h echo " " > tune.h
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
@ -284,8 +359,8 @@ atest9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c
# Unit test for inner digipeater algorithm # Unit test for inner digipeater algorithm
.PHONY: dtest .PHONY: dtest
dtest : digipeater.c dedupe.c \ dtest : digipeater.c dedupe.c pfilter.c \
pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \ ax25_pad.o fcs_calc.o tq.o textcolor.o \
decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a
$(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(CC) $(CFLAGS) -DDIGITEST -o $@ $^
./dtest ./dtest
@ -352,20 +427,49 @@ kisstest : kiss_frame.c
rm kisstest.exe rm kisstest.exe
# Unit test for constructing frames besides UI.
.PHONY: pad2test
pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a
$(CC) $(CFLAGS) -DPAD2TEST -o $@ $^
./pad2test
rm pad2test.exe
# Unit Test for XID frame encode/decode.
.PHONY: xidtest
xidtest : xid.c textcolor.o misc.a
$(CC) $(CFLAGS) -DXIDTEST -o $@ $^
./xidtest
rm xidtest.exe
# Unit Test for DTMF encode/decode.
.PHONY: dtmftest
dtmftest : dtmf.c textcolor.o
$(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^
./dtmftest
rm dtmftest.exe
# ------------------------------ Other manual testing & experimenting ------------------------------- # ------------------------------ Other manual testing & experimenting -------------------------------
tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# For tweaking the demodulator. # For tweaking the demodulator.
demod.o : tune.h demod.o : tune.h
demod_9600.o : tune.h demod_9600.o : tune.h
demod_afsk.o : tune.h demod_afsk.o : tune.h
demod_psk.o : tune.h
testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_agc.h \
testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \
hdlc_rec.o hdlc_rec2.o multi_modem.o \ hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \
dwgpsnmea.o dwgps.o serial_port.o tt_text.o regex.a misc.a dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a
rm -f atest.exe rm -f atest.exe
$(CC) $(CFLAGS) -o atest $^ $(CC) $(CFLAGS) -o atest $^
./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out ./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out
@ -375,27 +479,60 @@ testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \
noisy3.wav : gen_packets noisy3.wav : gen_packets
./gen_packets -B 300 -n 100 -o noisy3.wav ./gen_packets -B 300 -n 100 -o noisy3.wav
testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \ rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o regex.a misc.a \
tune.h tune.h
rm -f atest.exe rm -f atest3.exe
$(CC) $(CFLAGS) -o atest $^ $(CC) $(CFLAGS) -o atest3 $^
./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out ./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out
echo " " > tune.h echo " " > tune.h
noisy96.wav : gen_packets noisy96.wav : gen_packets
./gen_packets -B 9600 -n 100 -o noisy96.wav ./gen_packets -B 9600 -n 100 -o noisy96.wav
testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c regex.a misc.a \ dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
tune.h rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
rm -f atest.exe dwgpsnmea.o dwgps.o serial_port.o latlong.o \
$(CC) $(CFLAGS) -o atest $^ symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out misc.a regex.a
#./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out rm -f atest96.exe
$(CC) $(CFLAGS) -o atest96 $^
./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out
#./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out
echo " " > tune.h echo " " > tune.h
testagc24 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a regex.a
rm -f atest24.exe
sleep 1
$(CC) $(CFLAGS) -o atest24 $^
./atest24 -B 2400 test2400.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
testagc48 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a regex.a
rm -f atest48.exe
sleep 1
$(CC) $(CFLAGS) -o atest48 $^
./atest48 -B 4800 test4800.wav | grep "packets decoded in" >atest.out
#./atest48 -B 4800 test4800.wav
echo " " > tune.h
# Unit test for IGate # Unit test for IGate
itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a
@ -424,8 +561,8 @@ walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \
ax25_pad.o fcs_calc.o \ ax25_pad.o fcs_calc.o \
xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \ xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \
hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \ hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \
multi_modem.o demod.o demod_afsk.o demod_9600.o rdq.o \ multi_modem.o demod.o demod_afsk.o demod_psk.c demod_9600.o rdq.o \
server.o morse.o audio_stats.o dtime_now.o dlq.o \ server.o morse.o dtmf.o audio_stats.o dtime_now.o dlq.o \
regex.a misc.a regex.a misc.a
$(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32 $(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32
@ -494,67 +631,6 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l
dwespeak.bat \ dwespeak.bat \
telemetry-toolkit/* telemetry-toolkit/*
.PHONY: dist-src
dist-src : README.md CHANGES.md \
doc/User-Guide.pdf \
doc/Raspberry-Pi-APRS.pdf \
doc/Raspberry-Pi-APRS-Tracker.pdf \
doc/APRStt-Implementation-Notes.pdf \
doc/APRS-Telemetry-Toolkit.pdf \
dw-start.sh dwespeak.bat dwespeak.sh \
tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
rm -f fsk_fast_filter.h
echo " " > tune.h
rm -f ../$z-src.zip
dos2unix generic.conf
dos2unix Makefile
dos2unix Makefile.linux
dos2unix Makefile.macosx
dos2unix telemetry-toolkit/telem-balloon.conf
dos2unix telemetry-toolkit/telem-balloon.pl
dos2unix telemetry-toolkit/telem-bits.pl
dos2unix telemetry-toolkit/telem-data.pl
dos2unix telemetry-toolkit/telem-data91.pl
dos2unix telemetry-toolkit/telem-eqns.pl
dos2unix telemetry-toolkit/telem-m0xer-3.txt
dos2unix telemetry-toolkit/telem-parm.pl
dos2unix telemetry-toolkit/telem-unit.pl
dos2unix telemetry-toolkit/telem-volts.py
dos2unix telemetry-toolkit/telem-volts.conf
(cd .. ; zip $z-src.zip \
$z/README.md \
$z/CHANGES.md \
$z/LICENSE* \
$z/doc/User-Guide.pdf \
$z/doc/Raspberry-Pi-APRS.pdf \
$z/doc/Raspberry-Pi-APRS-Tracker.pdf \
$z/doc/APRStt-Implementation-Notes.pdf \
$z/doc/APRS-Telemetry-Toolkit.pdf \
$z/Makefile.win $z/Makefile.linux $z/Makefile.macosx $z/Makefile \
$z/*.c $z/*.h \
$z/regex/* $z/misc/* $z/geotranz/* \
$z/man1/* \
$z/generic.conf \
$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
$z/dw-start.sh $z/direwolf.spec \
$z/dwespeak.bat $z/dwespeak.sh \
$z/telemetry-toolkit/* )
unix2dos Makefile
unix2dos Makefile.linux
unix2dos Makefile.macosx
unix2dos telemetry-toolkit/telem-balloon.conf
unix2dos telemetry-toolkit/telem-balloon.pl
dos2unix telemetry-toolkit/telem-bits.pl
unix2dos telemetry-toolkit/telem-data.pl
unix2dos telemetry-toolkit/telem-data91.pl
unix2dos telemetry-toolkit/telem-eqns.pl
unix2dos telemetry-toolkit/telem-m0xer-3.txt
unix2dos telemetry-toolkit/telem-parm.pl
unix2dos telemetry-toolkit/telem-unit.pl
unix2dos telemetry-toolkit/telem-volts.py
unix2dos telemetry-toolkit/telem-volts.conf
# Reminders if pdf files are not up to date. # Reminders if pdf files are not up to date.

View File

@ -3,24 +3,24 @@
### Decoded Information from Radio Emissions for Windows Or Linux Fans ### ### Decoded Information from Radio Emissions for Windows Or Linux Fans ###
In the early days of Amateur Packet Radio, it was necessary to use a “Terminal Node Controller” (TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard” interface of a computer and using software to decode the signals. In the early days of Amateur Packet Radio, it was necessary to use an expensive “Terminal Node Controller” (TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard” interface of a computer and using software to decode the signals.
Dire Wolf is a software "soundcard" modem/TNC and [APRS](http://www.aprs.org/) encoder/decoder. It can be used stand-alone to observe APRS traffic, as a digipeater, [APRStt](http://www.aprs.org/aprstt.html) gateway, or Internet Gateway (IGate). It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [RMS Express](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/) and many others. Dire Wolf is a software "soundcard" modem/TNC and [APRS](http://www.aprs.org/) encoder/decoder. It can be used stand-alone to observe APRS traffic, as a digipeater, [APRStt](http://www.aprs.org/aprstt.html) gateway, or Internet Gateway (IGate). It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [RMS Express](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/) and many others.
## Features ## ## Features & Benefits ##
- Lower cost, higher performance alternative to hardware TNC. - Lower cost, higher performance alternative to hardware TNC.
Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf.net/TNCtest/). Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf.net/TNCtest/).
- Ideal for building a Raspberry Pi digipeater & IGate. - Ideal for building a Raspberry Pi digipeater & IGate.
- Data rates: 300 AFSK, 1200 AFSK, 2400 QPSK, 4800 8PSK, and 9600/19200/38400 K9NG/G3RUH. - Data rates: 300 AFSK, 1200 AFSK, 2400 QPSK, 4800 8PSK, and 9600/19200/38400 bps K9NG/G3RUH.
- Interface with applications by - Interface with applications by
- [AGW](http://uz7.ho.ua/includes/agwpeapi.htm) network protocol - [AGW](http://uz7.ho.ua/includes/agwpeapi.htm) network protocol
- [KISS](http://www.ax25.net/kiss.aspx) serial port - [KISS](http://www.ax25.net/kiss.aspx) serial port
- [KISS](http://www.ax25.net/kiss.aspx) network protocol - [KISS](http://www.ax25.net/kiss.aspx) TCP network protocol
- Decoding of received information for troubleshooting. - Decoding of received information for troubleshooting.
@ -48,30 +48,28 @@ Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf
- Runs in 3 different environments: - Runs in 3 different environments:
- Microsoft Windows XP or later - Microsoft Windows XP or later
- Linux, regular PC or single board computer such as Raspberry Pi, BeagleBone Black, or cubieboard 2 - Linux, regular PC/laptop or single board computer such as Raspberry Pi, BeagleBone Black, cubieboard 2, or C.H.I.P.
- Mac OS X - Mac OS X
## Documentation ##
[Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc)
[Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc)
## Installation ## ## Installation ##
### Windows ### ### Windows ###
Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window. Go to the [**releases** page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window.
For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc). For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc).
### Linux - Download with web browser ###
Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then:
cd direwolf-* ### Linux - Using git clone (recommended) ###
make
sudo make install
make install-conf
For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
### Linux - Using git clone ###
cd ~ cd ~
git clone https://www.github.com/wb2osz/direwolf git clone https://www.github.com/wb2osz/direwolf
@ -80,13 +78,42 @@ For more details see the **User Guide** in the [doc directory](https://github.co
sudo make install sudo make install
make install-conf make install-conf
This should give you the most recent stable release. If you want the latest (unstable) development version, use "git checkout dev" instead before the first "make" command. This should give you the most recent stable release. If you want the latest (possibly unstable) development version, use "git checkout dev" before the first "make" command.
For more details see the **User Guide** in the [doc directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
### Linux - Using apt-get (Debian flavor operating systems) ###
Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging.
sudo apt-get update
apt-cache showpkg direwolf
sudo apt-get install direwolf
### Linux - Using yum (Red Hat flavor operating systems) ###
Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging.
sudo yum check-update
sudo yum list direwolf
sudo yum install direwolf
### Linux - Download source in tar or zip file ###
Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then:
cd direwolf-*
make
sudo make install
make install-conf
For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
## Join the conversation ## ## Join the conversation ##
Here are some good places to share information: Here are some good places to ask questions and share your experience:
- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) - [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info)

View File

@ -51,17 +51,14 @@
* Linux: Use the BSD socket interface. * Linux: Use the BSD socket interface.
*/ */
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__ #if __WIN32__
#include <winsock2.h> #include <winsock2.h>
// default is 0x0400 #include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */
#include <ws2tcpip.h>
#else #else
//#define __USE_XOPEN2KXSI 1
//#define __USE_XOPEN 1
#include <stdlib.h> #include <stdlib.h>
#include <netdb.h> #include <netdb.h>
#include <sys/types.h> #include <sys/types.h>
@ -83,7 +80,6 @@
#include <time.h> #include <time.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "version.h" #include "version.h"
@ -468,7 +464,11 @@ static void * client_thread_net (void *arg)
// Try each address until we find one that is successful. // Try each address until we find one that is successful.
for (n=0; n<num_hosts; n++) { for (n=0; n<num_hosts; n++) {
#if __WIN32__
SOCKET is;
#else
int is; int is;
#endif
ai = hosts[n]; ai = hosts[n];
@ -578,7 +578,7 @@ static void * client_thread_net (void *arg)
printf ("client %d received '%c' data, data_len = %d\n", printf ("client %d received '%c' data, data_len = %d\n",
my_index, mon_cmd.kind_lo, mon_cmd.data_len); my_index, mon_cmd.kind_lo, mon_cmd.data_len);
#endif #endif
assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < sizeof(data)); assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data)));
if (mon_cmd.data_len > 0) { if (mon_cmd.data_len > 0) {
#if __WIN32__ #if __WIN32__

View File

@ -39,6 +39,8 @@
#define APRS_TT_C 1 #define APRS_TT_C 1
#include "direwolf.h"
// TODO: clean up terminolgy. // TODO: clean up terminolgy.
// "Message" has a specific meaning in APRS and this is not it. // "Message" has a specific meaning in APRS and this is not it.
@ -56,7 +58,6 @@
#include <ctype.h> #include <ctype.h>
#include <assert.h> #include <assert.h>
#include "direwolf.h"
#include "version.h" #include "version.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "hdlc_rec2.h" /* for process_rec_frame */ #include "hdlc_rec2.h" /* for process_rec_frame */
@ -105,7 +106,9 @@ static int parse_aprstt3_call (char *e);
static int parse_location (char *e); static int parse_location (char *e);
static int parse_comment (char *e); static int parse_comment (char *e);
static int expand_macro (char *e); static int expand_macro (char *e);
#ifndef TT_MAIN
static void raw_tt_data_to_app (int chan, char *msg); static void raw_tt_data_to_app (int chan, char *msg);
#endif
static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize); static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize);
#if TT_MAIN #if TT_MAIN
@ -378,6 +381,7 @@ void aprs_tt_sequence (int chan, char *msg)
#endif #endif
#if TT_MAIN #if TT_MAIN
(void)err; // suppress variable set but not used warning.
check_result (); // for unit testing. check_result (); // for unit testing.
#else #else
@ -1443,7 +1447,7 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *
len = strlen(tt_config.ttloc_ptr[ipat].pattern); len = strlen(tt_config.ttloc_ptr[ipat].pattern);
if (strlen(e) == len) { if ((int)(strlen(e)) == len) {
match = 1; match = 1;
strlcpy (xstr, "", valstrsize); strlcpy (xstr, "", valstrsize);
@ -1658,6 +1662,7 @@ static int parse_comment (char *e)
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
#ifndef TT_MAIN
static void raw_tt_data_to_app (int chan, char *msg) static void raw_tt_data_to_app (int chan, char *msg)
{ {
@ -1668,9 +1673,6 @@ static void raw_tt_data_to_app (int chan, char *msg)
char src[10], dest[10]; char src[10], dest[10];
char raw_tt_msg[256]; char raw_tt_msg[256];
packet_t pp; packet_t pp;
char *c, *s;
int i;
int err;
alevel_t alevel; alevel_t alevel;
@ -1706,7 +1708,7 @@ static void raw_tt_data_to_app (int chan, char *msg)
alevel.mark = -2; alevel.mark = -2;
alevel.space = -2; alevel.space = -2;
dlq_append (DLQ_REC_FRAME, chan, -1, 0, pp, alevel, RETRY_NONE, "tt"); dlq_rec_frame (chan, -1, 0, pp, alevel, RETRY_NONE, "tt");
} }
else { else {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -1716,6 +1718,7 @@ static void raw_tt_data_to_app (int chan, char *msg)
#endif #endif
} }
#endif
/*------------------------------------------------------------------ /*------------------------------------------------------------------

132
atest.c
View File

@ -2,7 +2,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -59,6 +59,7 @@
// #define X 1 // #define X 1
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@ -79,6 +80,7 @@
#include "hdlc_rec2.h" #include "hdlc_rec2.h"
#include "dlq.h" #include "dlq.h"
#include "ptt.h" #include "ptt.h"
#include "dtime_now.h"
@ -178,7 +180,10 @@ int main (int argc, char *argv[])
int err; int err;
int c; int c;
int channel; int channel;
time_t start_time;
double start_time; // Time when we started so we can measure elapsed time.
double duration; // Length of the audio file in seconds.
double elapsed; // Time it took us to process it.
#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H) #if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
@ -291,35 +296,55 @@ int main (int argc, char *argv[])
case 'B': /* -B for data Bit rate */ case 'B': /* -B for data Bit rate */
/* 300 implies 1600/1800 AFSK. */ /* 300 implies 1600/1800 AFSK. */
/* 1200 implies 1200/2200 AFSK. */ /* 1200 implies 1200/2200 AFSK. */
/* 2400 implies V.26 */
/* 9600 implies scrambled. */ /* 9600 implies scrambled. */
my_audio_config.achan[0].baud = atoi(optarg); my_audio_config.achan[0].baud = atoi(optarg);
dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud); dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud);
if (my_audio_config.achan[0].baud < 100 || my_audio_config.achan[0].baud > 10000) { if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
if (my_audio_config.achan[0].baud < 600) { if (my_audio_config.achan[0].baud < 600) {
my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1600; my_audio_config.achan[0].mark_freq = 1600;
my_audio_config.achan[0].space_freq = 1800; my_audio_config.achan[0].space_freq = 1800;
strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
} }
else if (my_audio_config.achan[0].baud > 2400) { else if (my_audio_config.achan[0].baud < 1800) {
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ;
my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ;
// Should default to E+ or something similar later.
}
else if (my_audio_config.achan[0].baud < 3600) {
my_audio_config.achan[0].modem_type = MODEM_QPSK;
my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
dw_printf ("Using V.26 QPSK rather than AFSK.\n");
}
else if (my_audio_config.achan[0].baud < 7200) {
my_audio_config.achan[0].modem_type = MODEM_8PSK;
my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
dw_printf ("Using V.27 8PSK rather than AFSK.\n");
}
else {
my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
my_audio_config.achan[0].mark_freq = 0; my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0; my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later.
dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
} }
else {
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1200;
my_audio_config.achan[0].space_freq = 2200;
}
break; break;
case 'P': /* -P for modem profile. */ case 'P': /* -P for modem profile. */
@ -336,7 +361,7 @@ int main (int argc, char *argv[])
if (decimate < 1 || decimate > 8) { if (decimate < 1 || decimate > 8) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Unreasonable value for -D.\n"); dw_printf ("Unreasonable value for -D.\n");
exit (1); exit (EXIT_FAILURE);
} }
dw_printf ("Divide audio sample rate by %d\n", decimate); dw_printf ("Divide audio sample rate by %d\n", decimate);
my_audio_config.achan[0].decimate = decimate; my_audio_config.achan[0].decimate = decimate;
@ -349,7 +374,7 @@ int main (int argc, char *argv[])
if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) { if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid Fix Bits level.\n"); dw_printf ("Invalid Fix Bits level.\n");
exit (1); exit (EXIT_FAILURE);
} }
break; break;
@ -407,11 +432,10 @@ int main (int argc, char *argv[])
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't open file for read: %s\n", argv[optind]); dw_printf ("Couldn't open file for read: %s\n", argv[optind]);
//perror ("more info?"); //perror ("more info?");
exit (1); exit (EXIT_FAILURE);
} }
start_time = time(NULL); start_time = dtime_now();
/* /*
* Read the file header. * Read the file header.
@ -437,12 +461,12 @@ int main (int argc, char *argv[])
if (strncmp(chunk.id, "fmt ", 4) != 0) { if (strncmp(chunk.id, "fmt ", 4) != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id); dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id);
exit(1); exit(EXIT_FAILURE);
} }
if (chunk.datasize != 16 && chunk.datasize != 18) { if (chunk.datasize != 16 && chunk.datasize != 18) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18. Found %d.\n", chunk.datasize); dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18. Found %d.\n", chunk.datasize);
exit(1); exit(EXIT_FAILURE);
} }
err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp); err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp);
@ -452,12 +476,26 @@ int main (int argc, char *argv[])
if (strncmp(wav_data.data, "data", 4) != 0) { if (strncmp(wav_data.data, "data", 4) != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data); dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data);
exit(1); exit(EXIT_FAILURE);
} }
// TODO: Should have proper message, not abort. if (format.wformattag != 1) {
assert (format.nchannels == 1 || format.nchannels == 2); text_color_set(DW_COLOR_ERROR);
assert (format.wbitspersample == 8 || format.wbitspersample == 16); dw_printf ("Sorry, I only understand audio format 1 (PCM). This file has %d.\n", format.wformattag);
exit (EXIT_FAILURE);
}
if (format.nchannels != 1 && format.nchannels != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Sorry, I only understand 1 or 2 channels. This file has %d.\n", format.nchannels);
exit (EXIT_FAILURE);
}
if (format.wbitspersample != 8 && format.wbitspersample != 16) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Sorry, I only understand 8 or 16 bits per sample. This file has %d.\n", format.wbitspersample);
exit (EXIT_FAILURE);
}
my_audio_config.adev[0].samples_per_sec = format.nsamplespersec; my_audio_config.adev[0].samples_per_sec = format.nsamplespersec;
my_audio_config.adev[0].bits_per_sample = format.wbitspersample; my_audio_config.adev[0].bits_per_sample = format.wbitspersample;
@ -467,13 +505,17 @@ int main (int argc, char *argv[])
if (format.nchannels == 2) my_audio_config.achan[1].valid = 1; if (format.nchannels == 2) my_audio_config.achan[1].valid = 1;
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("%d samples per second\n", my_audio_config.adev[0].samples_per_sec); dw_printf ("%d samples per second. %d bits per sample. %d audio channels.\n",
dw_printf ("%d bits per sample\n", my_audio_config.adev[0].bits_per_sample); my_audio_config.adev[0].samples_per_sec,
dw_printf ("%d audio channels\n", my_audio_config.adev[0].num_channels); my_audio_config.adev[0].bits_per_sample,
dw_printf ("%d audio bytes in file\n", (int)(wav_data.datasize)); my_audio_config.adev[0].num_channels);
duration = (double) wav_data.datasize /
((my_audio_config.adev[0].bits_per_sample / 8) * my_audio_config.adev[0].num_channels * my_audio_config.adev[0].samples_per_sec);
dw_printf ("%d audio bytes in file. Duration = %.1f seconds.\n",
(int)(wav_data.datasize),
duration);
dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits); dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits);
/* /*
* Initialize the AFSK demodulator and HDLC decoder. * Initialize the AFSK demodulator and HDLC decoder.
*/ */
@ -496,8 +538,10 @@ int main (int argc, char *argv[])
audio_sample = demod_get_sample (ACHAN2ADEV(c)); audio_sample = demod_get_sample (ACHAN2ADEV(c));
if (audio_sample >= 256 * 256) if (audio_sample >= 256 * 256) {
e_o_f = 1; e_o_f = 1;
continue;
}
if (c == 0) sample_number++; if (c == 0) sample_number++;
@ -527,20 +571,24 @@ int main (int argc, char *argv[])
dw_printf ("%d\n", count[j]); dw_printf ("%d\n", count[j]);
} }
#endif #endif
dw_printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time));
elapsed = dtime_now() - start_time;
dw_printf ("%d packets decoded in %.3f seconds. %.1f x realtime\n", packets_decoded, elapsed, duration/elapsed);
if (error_if_less_than != -1 && packets_decoded < error_if_less_than) { if (error_if_less_than != -1 && packets_decoded < error_if_less_than) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\n * * * TEST FAILED: number decoded is less than %d * * * \n", error_if_less_than); dw_printf ("\n * * * TEST FAILED: number decoded is less than %d * * * \n", error_if_less_than);
exit (1); exit (EXIT_FAILURE);
} }
if (error_if_greater_than != -1 && packets_decoded > error_if_greater_than) { if (error_if_greater_than != -1 && packets_decoded > error_if_greater_than) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than); dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than);
exit (1); exit (EXIT_FAILURE);
} }
exit (0); exit (EXIT_SUCCESS);
} }
@ -596,7 +644,7 @@ void rdq_append (rrbb_t rrbb)
* This is called when we have a good frame. * This is called when we have a good frame.
*/ */
void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
{ {
char stemp[500]; char stemp[500];
@ -695,6 +743,24 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
ax25_safe_print ((char *)pinfo, info_len, 0); ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n"); dw_printf ("\n");
#if 1 // temp experiment TODO: remove this.
#include "decode_aprs.h"
#include "log.h"
if (ax25_is_aprs(pp)) {
decode_aprs_t A;
decode_aprs (&A, pp, 0);
// Temp experiment to see how different systems set the RR bits in the source and destination.
// log_rr_bits (&A, pp);
}
#endif
ax25_delete (pp); ax25_delete (pp);
} /* end fake dlq_append */ } /* end fake dlq_append */
@ -740,7 +806,7 @@ static void usage (void) {
dw_printf ("\n"); dw_printf ("\n");
dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n"); dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n");
dw_printf (" -1 use channel 1 (right) of stereo audio.\n"); dw_printf (" -1 use channel 1 (right) of stereo audio.\n");
dw_printf (" -1 decode both channels of stereo audio.\n"); dw_printf (" -2 decode both channels of stereo audio.\n");
dw_printf ("\n"); dw_printf ("\n");
dw_printf (" wav-file-in is a WAV format audio file.\n"); dw_printf (" wav-file-in is a WAV format audio file.\n");
dw_printf ("\n"); dw_printf ("\n");

11
audio.c
View File

@ -60,6 +60,7 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@ -88,7 +89,6 @@
#endif #endif
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "audio_stats.h" #include "audio_stats.h"
#include "textcolor.h" #include "textcolor.h"
@ -375,8 +375,8 @@ int audio_open (struct audio_s *pa)
{ {
struct sockaddr_in si_me; struct sockaddr_in si_me;
int slen=sizeof(si_me); //int slen=sizeof(si_me);
int data_size = 0; //int data_size = 0;
//Create UDP Socket //Create UDP Socket
if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
@ -1003,7 +1003,7 @@ int audio_get (int a)
case AUDIO_IN_TYPE_SDR_UDP: case AUDIO_IN_TYPE_SDR_UDP:
while (adev[a].inbuf_next >= adev[a].inbuf_len) { while (adev[a].inbuf_next >= adev[a].inbuf_len) {
int ch, res,i; int res;
assert (adev[a].udp_sock > 0); assert (adev[a].udp_sock > 0);
res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
@ -1038,7 +1038,8 @@ int audio_get (int a)
case AUDIO_IN_TYPE_STDIN: case AUDIO_IN_TYPE_STDIN:
while (adev[a].inbuf_next >= adev[a].inbuf_len) { while (adev[a].inbuf_next >= adev[a].inbuf_len) {
int ch, res,i; //int ch, res,i;
int res;
res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes); res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
if (res <= 0) { if (res <= 0) {

66
audio.h
View File

@ -89,6 +89,14 @@ struct audio_s {
/* statistics reports. This is set by */ /* statistics reports. This is set by */
/* the "-a" option. 0 to disable feature. */ /* the "-a" option. 0 to disable feature. */
int xmit_error_rate; /* For testing purposes, we can generate frames with an invalid CRC */
/* to simulate corruption while going over the air. */
/* This is the probability, in per cent, of randomly corrupting it. */
/* Normally this is 0. 25 would mean corrupt it 25% of the time. */
int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */
/* Properties for each audio channel, common to receive and transmit. */ /* Properties for each audio channel, common to receive and transmit. */
/* Can be different for each radio channel. */ /* Can be different for each radio channel. */
@ -101,11 +109,12 @@ struct audio_s {
/* Could all be the same or different. */ /* Could all be the same or different. */
enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_OFF } modem_type; enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF } modem_type;
/* Usual AFSK. */ /* Usual AFSK. */
/* Baseband signal. Not used yet. */ /* Baseband signal. Not used yet. */
/* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */ /* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */
/* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */
/* No modem. Might want this for DTMF only channel. */ /* No modem. Might want this for DTMF only channel. */
@ -130,8 +139,9 @@ struct audio_s {
int mark_freq; /* Two tones for AFSK modulation, in Hz. */ int mark_freq; /* Two tones for AFSK modulation, in Hz. */
int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */
int baud; /* Data bits (more accurately, symbols) per second. */ int baud; /* Data bits per second. */
/* Standard rates are 1200 for VHF and 300 for HF. */ /* Standard rates are 1200 for VHF and 300 for HF. */
/* This should really be called bits per second. */
/* Next 3 come from config file or command line. */ /* Next 3 come from config file or command line. */
@ -164,18 +174,20 @@ struct audio_s {
int passall; /* Allow thru even with bad CRC. */ int passall; /* Allow thru even with bad CRC. */
/* Additional properties for transmit. */ /* Additional properties for transmit. */
/* Originally we had control outputs only for PTT. */ /* Originally we had control outputs only for PTT. */
/* In version 1.2, we generalize this to allow others such as DCD. */ /* In version 1.2, we generalize this to allow others such as DCD. */
/* In version 1.4 we add CON for connected to another station. */
/* Index following structure by one of these: */ /* Index following structure by one of these: */
#define OCTYPE_PTT 0 #define OCTYPE_PTT 0
#define OCTYPE_DCD 1 #define OCTYPE_DCD 1
#define OCTYPE_FUTURE 2 #define OCTYPE_CON 2
#define NUM_OCTYPES 4 /* number of values above */ #define NUM_OCTYPES 3 /* number of values above. i.e. last value +1. */
struct { struct {
@ -187,7 +199,18 @@ struct audio_s {
ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */ ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */
ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */ ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */
int ptt_gpio; /* GPIO number. */ int out_gpio_num; /* GPIO number. Originally this was only for PTT. */
/* It is now more general. */
/* octrl array is indexed by PTT, DCD, or CONnected indicator. */
#define MAX_GPIO_NAME_LEN 16 // 12 would cover any case I've seen so this should be safe
char out_gpio_name[MAX_GPIO_NAME_LEN];
/* orginally, gpio number NN was assumed to simply */
/* have the name gpioNN but this turned out not to be */
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */
int ptt_lpt_bit; /* Bit number for parallel printer port. */ int ptt_lpt_bit; /* Bit number for parallel printer port. */
/* Bit 0 = pin 2, ..., bit 7 = pin 9. */ /* Bit 0 = pin 2, ..., bit 7 = pin 9. */
@ -202,13 +225,26 @@ struct audio_s {
} octrl[NUM_OCTYPES]; } octrl[NUM_OCTYPES];
/* Each channel can also have associated input lines. */
/* So far, we just have one for transmit inhibit. */
#define ICTYPE_TXINH 0 #define ICTYPE_TXINH 0
#define NUM_ICTYPES 1 #define NUM_ICTYPES 1 /* number of values above. i.e. last value +1. */
struct { struct {
ptt_method_t method; /* none, serial port, GPIO, LPT. */ ptt_method_t method; /* none, serial port, GPIO, LPT. */
int gpio; /* GPIO number */
int in_gpio_num; /* GPIO number */
char in_gpio_name[MAX_GPIO_NAME_LEN];
/* orginally, gpio number NN was assumed to simply */
/* have the name gpioNN but this turned out not to be */
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */
int invert; /* 1 = active low */ int invert; /* 1 = active low */
} ictrl[NUM_ICTYPES]; } ictrl[NUM_ICTYPES];
@ -278,8 +314,12 @@ struct audio_s {
/* 44100 works a little better than 22050. */ /* 44100 works a little better than 22050. */
/* If you have a reasonable machine, use the highest rate. */ /* If you have a reasonable machine, use the highest rate. */
#define MIN_SAMPLES_PER_SEC 8000 #define MIN_SAMPLES_PER_SEC 8000
#define MAX_SAMPLES_PER_SEC 48000 /* Formerly 44100. */ //#define MAX_SAMPLES_PER_SEC 48000 /* Originally 44100. Later increased because */
/* Software defined radio often uses 48000. */ /* Software Defined Radio often uses 48000. */
#define MAX_SAMPLES_PER_SEC 192000 /* The cheap USB-audio adapters (e.g. CM108) can handle 44100 and 48000. */
/* The "soundcard" in my desktop PC can do 96kHz or even 192kHz. */
/* We will probably need to increase the sample rate to go much above 9600 baud. */
#define DEFAULT_BITS_PER_SAMPLE 16 #define DEFAULT_BITS_PER_SAMPLE 16
@ -301,12 +341,12 @@ struct audio_s {
#define DEFAULT_BAUD 1200 #define DEFAULT_BAUD 1200
/* Used for sanity checking in config file and command line options. */ /* Used for sanity checking in config file and command line options. */
/* 9600 is known to work. */ /* 9600 baud is known to work. */
/* TODO: Is 19200 possible with a soundcard at 44100 samples/sec? */ /* TODO: Is 19200 possible with a soundcard at 44100 samples/sec or do we need a higher sample rate? */
#define MIN_BAUD 100 #define MIN_BAUD 100
#define MAX_BAUD 10000 //#define MAX_BAUD 10000
#define MAX_BAUD 40000 // Anyone want to try 38.4 k baud?
/* /*
* Typical transmit timings for VHF. * Typical transmit timings for VHF.

View File

@ -37,6 +37,8 @@
#if defined(USE_PORTAUDIO) #if defined(USE_PORTAUDIO)
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -54,7 +56,6 @@
#include <errno.h> #include <errno.h>
#include <pthread.h> #include <pthread.h>
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "audio_stats.h" #include "audio_stats.h"
#include "textcolor.h" #include "textcolor.h"

View File

@ -50,6 +50,9 @@
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -57,8 +60,9 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <assert.h> #include <assert.h>
#include <time.h>
#include "direwolf.h"
#include "audio_stats.h" #include "audio_stats.h"
#include "textcolor.h" #include "textcolor.h"
#include "dtime_now.h" #include "dtime_now.h"

View File

@ -39,6 +39,10 @@
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
// Also includes windows.h.
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
@ -48,7 +52,6 @@
#include <io.h> #include <io.h>
#include <fcntl.h> #include <fcntl.h>
#include <windows.h>
#include <mmsystem.h> #include <mmsystem.h>
#ifndef WAVE_FORMAT_96M16 #ifndef WAVE_FORMAT_96M16
@ -57,11 +60,9 @@
#endif #endif
#include <winsock2.h> #include <winsock2.h>
#define _WIN32_WINNT 0x0501 #include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#include <ws2tcpip.h>
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "audio_stats.h" #include "audio_stats.h"
#include "textcolor.h" #include "textcolor.h"
@ -286,7 +287,7 @@ int audio_open (struct audio_s *pa)
A->udp_sock = INVALID_SOCKET; A->udp_sock = INVALID_SOCKET;
in_dev_no[a] = WAVE_MAPPER; /* = -1 */ in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */
out_dev_no[a] = WAVE_MAPPER; out_dev_no[a] = WAVE_MAPPER;
/* /*
@ -319,16 +320,16 @@ int audio_open (struct audio_s *pa)
/* Otherwise, does it have search string? */ /* Otherwise, does it have search string? */
if (in_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) { if ((UINT)(in_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) {
num_devices = waveInGetNumDevs(); num_devices = waveInGetNumDevs();
for (n=0 ; n<num_devices && in_dev_no[a] == WAVE_MAPPER ; n++) { for (n=0 ; n<num_devices && (UINT)(in_dev_no[a]) == WAVE_MAPPER ; n++) {
if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) { if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
if (strstr(wic.szPname, pa->adev[a].adevice_in) != NULL) { if (strstr(wic.szPname, pa->adev[a].adevice_in) != NULL) {
in_dev_no[a] = n; in_dev_no[a] = n;
} }
} }
} }
if (in_dev_no[a] == WAVE_MAPPER) { if ((UINT)(in_dev_no[a]) == WAVE_MAPPER) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in); dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in);
} }
@ -344,16 +345,16 @@ int audio_open (struct audio_s *pa)
out_dev_no[a] = atoi(pa->adev[a].adevice_out); out_dev_no[a] = atoi(pa->adev[a].adevice_out);
} }
if (out_dev_no[a] == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
num_devices = waveOutGetNumDevs(); num_devices = waveOutGetNumDevs();
for (n=0 ; n<num_devices && out_dev_no[a] == WAVE_MAPPER ; n++) { for (n=0 ; n<num_devices && (UINT)(out_dev_no[a]) == WAVE_MAPPER ; n++) {
if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) { if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
if (strstr(woc.szPname, pa->adev[a].adevice_out) != NULL) { if (strstr(woc.szPname, pa->adev[a].adevice_out) != NULL) {
out_dev_no[a] = n; out_dev_no[a] = n;
} }
} }
} }
if (out_dev_no[a] == WAVE_MAPPER) { if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out);
} }
@ -798,7 +799,7 @@ int audio_get (int a)
p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */ p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */
if (p->dwUser == -1) { if (p->dwUser == (DWORD)(-1)) {
waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
p->dwUser = 0; /* Index for next byte. */ p->dwUser = 0; /* Index for next byte. */
@ -959,11 +960,11 @@ int audio_put (int a, int c)
/* Should never be full at this point. */ /* Should never be full at this point. */
assert (p->dwBufferLength >= 0); assert (p->dwBufferLength >= 0);
assert (p->dwBufferLength < A->outbuf_size); assert (p->dwBufferLength < (DWORD)(A->outbuf_size));
p->lpData[p->dwBufferLength++] = c; p->lpData[p->dwBufferLength++] = c;
if (p->dwBufferLength == A->outbuf_size) { if (p->dwBufferLength == (DWORD)(A->outbuf_size)) {
return (audio_flush(a)); return (audio_flush(a));
} }

6523
ax25_link.c Normal file

File diff suppressed because it is too large Load Diff

82
ax25_link.h Normal file
View File

@ -0,0 +1,82 @@
/* ax25_link.h */
#ifndef AX25_LINK_H
#define AX25_LINK_H 1
#include "ax25_pad.h" // for AX25_MAX_INFO_LEN
#include "dlq.h" // for dlq_item_t
#include "config.h" // for struct misc_config_s
// Limits and defaults for parameters.
#define AX25_N1_PACLEN_MIN 1 // Max bytes in Information part of frame.
#define AX25_N1_PACLEN_DEFAULT 256 // some v2.0 implementations have 128
#define AX25_N1_PACLEN_MAX AX25_MAX_INFO_LEN // from ax25_pad.h
#define AX25_N2_RETRY_MIN 1 // Number of times to retry before giving up.
#define AX25_N2_RETRY_DEFAULT 10
#define AX25_N2_RETRY_MAX 15
#define AX25_T1V_FRACK_MIN 1 // Number of seconds to wait before retrying.
#define AX25_T1V_FRACK_DEFAULT 3 // KPC-3+ has 4. TM-D710A has 3.
#define AX25_T1V_FRACK_MAX 15
#define AX25_K_MAXFRAME_BASIC_MIN 1 // Window size - number of I frames to send before waiting for ack.
#define AX25_K_MAXFRAME_BASIC_DEFAULT 4
#define AX25_K_MAXFRAME_BASIC_MAX 7
#define AX25_K_MAXFRAME_EXTENDED_MIN 1
#define AX25_K_MAXFRAME_EXTENDED_DEFAULT 32
#define AX25_K_MAXFRAME_EXTENDED_MAX 63 // In theory 127 but I'm restricting as explained in SREJ handling.
// Call once at startup time.
void ax25_link_init (struct misc_config_s *pconfig);
// IMPORTANT:
// These functions must be called on a single thread, one at a time.
// The Data Link Queue (DLQ) is used to serialize events from multiple sources.
void dl_connect_request (dlq_item_t *E);
void dl_disconnect_request (dlq_item_t *E);
void dl_data_request (dlq_item_t *E);
void dl_register_callsign (dlq_item_t *E);
void dl_unregister_callsign (dlq_item_t *E);
void dl_client_cleanup (dlq_item_t *E);
void lm_data_indication (dlq_item_t *E);
void lm_channel_busy (dlq_item_t *E);
void dl_timer_expiry (void);
double ax25_link_get_next_timer_expiry (void);
#endif
/* end ax25_link.h */

View File

@ -24,6 +24,10 @@
* *
* Purpose: Packet assembler and disasembler. * Purpose: Packet assembler and disasembler.
* *
* This was written when I was only concerned about APRS which
* uses only UI frames. ax25_pad2.c, added years later, has
* functions for dealing with other types of frames.
*
* We can obtain AX.25 packets from different sources: * We can obtain AX.25 packets from different sources:
* *
* (a) from an HDLC frame. * (a) from an HDLC frame.
@ -77,11 +81,24 @@
* SSID = substation ID * SSID = substation ID
* 0 = zero * 0 = zero
* *
* The AX.25 spec states that the RR bits should be 11 if not used.
* There are a couple documents talking about possible uses for APRS.
* I'm ignoring them for now.
* http://www.aprs.org/aprs12/preemptive-digipeating.txt
* http://www.aprs.org/aprs12/RR-bits.txt
*
* I don't recall why I originally intended to set the source/destination C bits both to 1.
* Reviewing this 5 years later, after spending more time delving into the
* AX.25 spec, I think it should be 1 for destination and 0 for source.
* In practice you see all four combinations being used by APRS stations
* and no one really cares about these two bits.
*
* The final octet of the Source has the form: * The final octet of the Source has the form:
* *
* C R R SSID 0, where, * C R R SSID 0, where,
* *
* C = command/response = 1 * C = command/response = 1 (originally, now I think it should be 0 for source.)
* (Haven't gone back to check to see what code actually does.)
* R R = Reserved = 1 1 * R R = Reserved = 1 1
* SSID = substation ID * SSID = substation ID
* 0 = zero (or 1 if no repeaters) * 0 = zero (or 1 if no repeaters)
@ -146,16 +163,13 @@
#define AX25_PAD_C /* this will affect behavior of ax25_pad.h */ #define AX25_PAD_C /* this will affect behavior of ax25_pad.h */
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 1
#endif
#include "regex.h" #include "regex.h"
@ -163,9 +177,9 @@
char *strtok_r(char *str, const char *delim, char **saveptr); char *strtok_r(char *str, const char *delim, char **saveptr);
#endif #endif
#include "direwolf.h"
#include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "ax25_pad.h"
#include "fcs_calc.h" #include "fcs_calc.h"
/* /*
@ -235,7 +249,13 @@ packet_t ax25_new (void)
/* /*
* check for memory leak. * check for memory leak.
*/ */
if (new_count > delete_count + 100) {
// version 1.4 push up the threshold. We could have considerably more with connected mode.
//if (new_count > delete_count + 100) {
if (new_count > delete_count + 256) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Report to WB2OSZ - Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count); dw_printf ("Report to WB2OSZ - Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count);
#if AX25MEMDEBUG #if AX25MEMDEBUG
@ -315,9 +335,18 @@ void ax25_delete (packet_t this_p)
* Purpose: Parse a frame in human-readable monitoring format and change * Purpose: Parse a frame in human-readable monitoring format and change
* to internal representation. * to internal representation.
* *
* Input: monitor - "TNC-2" format of a monitored packet. i.e. * Input: monitor - "TNC-2" monitor format for packet. i.e.
* source>dest[,repeater1,repeater2,...]:information * source>dest[,repeater1,repeater2,...]:information
* *
* The information part can have non-printable characters
* in the form of <0xff>. This will be converted to single
* bytes. e.g. <0x0d> is carriage return.
* In version 1.4H we will allow nul characters which means
* we have to maintain a length rather than using strlen().
* I maintain that it violates the spec but want to handle it
* because it does happen and we want to preserve it when
* acting as an IGate rather than corrupting it.
*
* strict - True to enforce rules for packets sent over the air. * strict - True to enforce rules for packets sent over the air.
* False to be more lenient for packets from IGate server. * False to be more lenient for packets from IGate server.
* *
@ -349,17 +378,11 @@ packet_t ax25_from_text (char *monitor, int strict)
char *pa; char *pa;
char *saveptr; /* Used with strtok_r because strtok is not thread safe. */ char *saveptr; /* Used with strtok_r because strtok is not thread safe. */
static int first_time = 1;
static regex_t unhex_re;
int e;
char emsg[100];
#define MAXMATCH 1
regmatch_t match[MAXMATCH];
int keep_going;
char temp[512];
int ssid_temp, heard_temp; int ssid_temp, heard_temp;
char atemp[AX25_MAX_ADDR_LEN]; char atemp[AX25_MAX_ADDR_LEN];
char info_part[AX25_MAX_INFO_LEN+1];
int info_len;
packet_t this_p = ax25_new (); packet_t this_p = ax25_new ();
@ -372,58 +395,15 @@ packet_t ax25_from_text (char *monitor, int strict)
/* Is it possible to have a nul character (zero byte) in the */ /* Is it possible to have a nul character (zero byte) in the */
/* information field of an AX.25 frame? */ /* information field of an AX.25 frame? */
/* Yes, but it would be difficult in the from-text case. */ /* At this point, we have a normal C string. */
/* It is possible that will convert <0x00> to a nul character later. */
/* There we need to maintain a separate length and not use normal C string functions. */
strlcpy (stuff, monitor, sizeof(stuff)); strlcpy (stuff, monitor, sizeof(stuff));
/*
* Translate hexadecimal values like <0xff> to non-printing characters.
* MIC-E message type uses 5 different non-printing characters.
*/
if (first_time)
{
e = regcomp (&unhex_re, "<0x[0-9a-fA-F][0-9a-fA-F]>", 0);
if (e) {
regerror (e, &unhex_re, emsg, sizeof(emsg));
text_color_set(DW_COLOR_ERROR);
dw_printf ("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
first_time = 0;
}
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf ("BEFORE: %s\n", stuff);
ax25_safe_print (stuff, -1, 0);
dw_printf ("\n");
#endif
keep_going = 1;
while (keep_going) {
if (regexec (&unhex_re, stuff, MAXMATCH, match, 0) == 0) {
int n;
char *p;
stuff[match[0].rm_so + 5] = '\0';
n = strtol (stuff + match[0].rm_so + 3, &p, 16);
stuff[match[0].rm_so] = n;
strlcpy (temp, stuff + match[0].rm_eo, sizeof(temp));
strlcpy (stuff + match[0].rm_so + 1, temp, sizeof(stuff)-match[0].rm_so-1);
}
else {
keep_going = 0;
}
}
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf ("AFTER: %s\n", stuff);
ax25_safe_print (stuff, -1, 0);
dw_printf ("\n");
#endif
/* /*
* Initialize the packet with two addresses and control/pid * Initialize the packet structure with two addresses and control/pid
* for APRS. * for APRS.
*/ */
memset (this_p->frame_data + AX25_DESTINATION*7, ' ' << 1, 6); memset (this_p->frame_data + AX25_DESTINATION*7, ' ' << 1, 6);
@ -433,7 +413,7 @@ packet_t ax25_from_text (char *monitor, int strict)
this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK; this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK;
this_p->frame_data[14] = AX25_UI_FRAME; this_p->frame_data[14] = AX25_UI_FRAME;
this_p->frame_data[15] = AX25_NO_LAYER_3; this_p->frame_data[15] = AX25_PID_NO_LAYER_3;
this_p->frame_len = 7 + 7 + 1 + 1; this_p->frame_len = 7 + 7 + 1 + 1;
this_p->num_addr = (-1); this_p->num_addr = (-1);
@ -453,12 +433,6 @@ packet_t ax25_from_text (char *monitor, int strict)
*pinfo = '\0'; *pinfo = '\0';
pinfo++; pinfo++;
if (strlen(pinfo) > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Information part truncated to %d characters.\n", AX25_MAX_INFO_LEN);
pinfo[AX25_MAX_INFO_LEN] = '\0';
}
/* /*
* Separate the addresses. * Separate the addresses.
* Note that source and destination order is swappped. * Note that source and destination order is swappped.
@ -515,7 +489,6 @@ packet_t ax25_from_text (char *monitor, int strict)
*/ */
while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) { while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) {
//char *last;
int k; int k;
k = this_p->num_addr; k = this_p->num_addr;
@ -541,11 +514,61 @@ packet_t ax25_from_text (char *monitor, int strict)
} }
} }
/*
* Finally, process the information part.
*
* Translate hexadecimal values like <0xff> to single bytes.
* MIC-E format uses 5 different non-printing characters.
* We might want to manually generate UTF-8 characters such as degree.
*/
//#define DEBUG14H 1
#if DEBUG14H
text_color_set(DW_COLOR_DEBUG);
dw_printf ("BEFORE: %s\nSAFE: ", pinfo);
ax25_safe_print (pinfo, -1, 0);
dw_printf ("\n");
#endif
info_len = 0;
while (*pinfo != '\0' && info_len < AX25_MAX_INFO_LEN) {
if (strlen(pinfo) >= 6 &&
pinfo[0] == '<' &&
pinfo[1] == '0' &&
pinfo[2] == 'x' &&
isxdigit(pinfo[3]) &&
isxdigit(pinfo[4]) &&
pinfo[5] == '>') {
char *p;
info_part[info_len] = strtol (pinfo + 3, &p, 16);
info_len++;
pinfo += 6;
}
else {
info_part[info_len] = *pinfo;
info_len++;
pinfo++;
}
}
info_part[info_len] = '\0';
#if DEBUG14H
text_color_set(DW_COLOR_DEBUG);
dw_printf ("AFTER: %s\nSAFE: ", info_part);
ax25_safe_print (info_part, info_len, 0);
dw_printf ("\n");
#endif
/* /*
* Append the info part. * Append the info part.
*/ */
strlcpy ((char*)(this_p->frame_data+this_p->frame_len), pinfo, sizeof(this_p->frame_data)-this_p->frame_len); memcpy ((char*)(this_p->frame_data+this_p->frame_len), info_part, info_len);
this_p->frame_len += strlen(pinfo); this_p->frame_len += info_len;
return (this_p); return (this_p);
} }
@ -891,7 +914,10 @@ packet_t ax25_unwrap_third_party (packet_t from_pp)
(void) ax25_get_info (from_pp, &info_p); (void) ax25_get_info (from_pp, &info_p);
result_pp = ax25_from_text((char *)info_p + 1, 0); // Want strict because addresses should conform to AX.25 here.
// That's not the case for something from an Internet Server.
result_pp = ax25_from_text((char *)info_p + 1, 1);
return (result_pp); return (result_pp);
} }
@ -1513,15 +1539,48 @@ int ax25_get_first_not_repeated(packet_t this_p)
} }
/*------------------------------------------------------------------------------
*
* Name: ax25_get_rr
*
* Purpose: Return the two reserved "RR" bits in the specified address field.
*
* Inputs: pp - Packet object.
*
* n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Returns: 0, 1, 2, or 3.
*
*------------------------------------------------------------------------------*/
int ax25_get_rr (packet_t this_p, int n)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= 0 && n < this_p->num_addr);
if (n >= 0 && n < this_p->num_addr) {
return ((this_p->frame_data[n*7+6] & SSID_RR_MASK) >> SSID_RR_SHIFT);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_get_rr(%d), num_addr=%d\n", n, this_p->num_addr);
return (0);
}
}
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* *
* Name: ax25_get_info * Name: ax25_get_info
* *
* Purpose: Obtain Information part of current packet. * Purpose: Obtain Information part of current packet.
* *
* Inputs: None. * Inputs: this_p - Packet object pointer.
* *
* Outputs: paddr - Starting address is returned here. * Outputs: paddr - Starting address of information part is returned here.
* *
* Assumption: ax25_from_text or ax25_from_frame was called first. * Assumption: ax25_from_text or ax25_from_frame was called first.
* *
@ -1562,6 +1621,56 @@ int ax25_get_info (packet_t this_p, unsigned char **paddr)
*paddr = info_ptr; *paddr = info_ptr;
return (info_len); return (info_len);
} /* end ax25_get_info */
/*------------------------------------------------------------------------------
*
* Name: ax25_cut_at_crlf
*
* Purpose: Truncate the information part at the first CR or LF.
* This is used for the RF>IS IGate function.
* CR/LF is used as record separator so we must remove it
* before packaging up packet to sending to server.
*
* Inputs: this_p - Packet object pointer.
*
* Outputs: Packet is modified in place.
*
* Returns: Number of characters removed from the end.
* 0 if not changed.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
*------------------------------------------------------------------------------*/
int ax25_cut_at_crlf (packet_t this_p)
{
unsigned char *info_ptr;
int info_len;
int j;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
info_len = ax25_get_info (this_p, &info_ptr);
// Can't use strchr because there is potential of nul character.
for (j = 0; j < info_len; j++) {
if (info_ptr[j] == '\r' || info_ptr[j] == '\n') {
int chop = info_len - j;
this_p->frame_len -= chop;
return (chop);
}
}
return (0);
} }
@ -1674,6 +1783,25 @@ double ax25_get_release_time (packet_t this_p)
} }
/*------------------------------------------------------------------------------
*
* Name: ax25_set_modulo
*
* Purpose: Set modulo value for I and S frame sequence numbers.
*
*------------------------------------------------------------------------------*/
void ax25_set_modulo (packet_t this_p, int modulo)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
this_p->modulo = modulo;
}
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
@ -1745,6 +1873,64 @@ void ax25_format_addrs (packet_t this_p, char *result)
} }
/*------------------------------------------------------------------
*
* Function: ax25_format_via_path
*
* Purpose: Format via path addresses suitable for printing.
*
* Inputs: Current packet.
*
* result_size - Number of bytes available for result.
* We can have up to 8 addresses x 9 characters
* plus 7 commas, possible *, and nul = 81 minimum.
*
* Outputs: result - Digipeater field addresses combined into a single string of the form:
*
* "repeater, repeater ..."
*
* An asterisk is displayed after the last digipeater
* with the "H" bit set. e.g. If we hear RPT2,
*
* RPT1,RPT2*,RPT3
*
* No asterisk means the source is being heard directly.
*
*------------------------------------------------------------------*/
void ax25_format_via_path (packet_t this_p, char *result, size_t result_size)
{
int i;
int heard;
char stemp[AX25_MAX_ADDR_LEN];
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
*result = '\0';
/* Don't get upset if no addresses. */
/* This will allow packets that do not comply to AX.25 format. */
if (this_p->num_addr == 0) {
return;
}
heard = ax25_get_heard(this_p);
for (i=(int)AX25_REPEATER_1; i<this_p->num_addr; i++) {
if (i > (int)AX25_REPEATER_1) {
strlcat (result, ",", result_size);
}
ax25_get_addr_with_ssid (this_p, i, stemp);
strlcat (result, stemp, result_size);
if (i == heard) {
strlcat (result, "*", result_size);
}
}
} /* end ax25_format_via_path */
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Function: ax25_pack * Function: ax25_pack
@ -1789,12 +1975,11 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
* *
* Inputs: this_p - pointer to packet object. * Inputs: this_p - pointer to packet object.
* *
* modulo - We often need to know this because context is
* required to know if control is 1 or 2 bytes.
*
* Outputs: desc - Text description such as "I frame" or * Outputs: desc - Text description such as "I frame" or
* "U frame SABME". * "U frame SABME".
* Supply 16 bytes to be safe. * Supply 40 bytes to be safe.
*
* cr - Command or response?
* *
* pf - P/F - Poll/Final or -1 if not applicable * pf - P/F - Poll/Final or -1 if not applicable
* *
@ -1807,17 +1992,20 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
// TODO: need someway to ensure caller allocated enough space. // TODO: need someway to ensure caller allocated enough space.
#define DESC_SIZ 32 // Should pass in as parameter.
#define DESC_SIZ 40
ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns)
ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns)
{ {
int c; // U frames are always one control byte. int c; // U frames are always one control byte.
int c2; // I & S frames can have second Control byte. int c2 = 0; // I & S frames can have second Control byte.
assert (this_p->magic1 == MAGIC); assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC); assert (this_p->magic2 == MAGIC);
strlcpy (desc, "????", DESC_SIZ); strlcpy (desc, "????", DESC_SIZ);
*cr = cr_11;
*pf = -1; *pf = -1;
*nr = -1; *nr = -1;
*ns = -1; *ns = -1;
@ -1827,16 +2015,65 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *
strlcpy (desc, "Not AX.25", DESC_SIZ); strlcpy (desc, "Not AX.25", DESC_SIZ);
return (frame_not_AX25); return (frame_not_AX25);
} }
if (modulo == modulo_128) {
/*
* TERRIBLE HACK :-( for display purposes.
*
* I and S frames can have 1 or 2 control bytes but there is
* no good way to determine this without dipping into the data
* link state machine. Can we guess?
*
* S frames have no protocol id or information so if there is one
* more byte beyond the control field, we could assume there are
* two control bytes.
*
* For I frames, the protocol id will usually be 0xf0. If we find
* that as the first byte of the information field, it is probably
* the pid and not part of the information. Ditto for segments 0x08.
* Not fool proof but good enough for troubleshooting text out.
*
* If we have a link to the peer station, this will be set properly
* before it needs to be used for other reasons.
*
* Setting one of the RR bits (find reference!) is sounding better and better.
* It's in common usage so I should lobby to get that in the official protocol spec.
*/
if (this_p->modulo == 0 && (c & 3) == 1 && ax25_get_c2(this_p) != -1) {
this_p->modulo = modulo_128;
}
else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0xF0) {
this_p->modulo = modulo_128;
}
else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0x08) { // same for segments
this_p->modulo = modulo_128;
}
if (this_p->modulo == modulo_128) {
c2 = ax25_get_c2 (this_p); c2 = ax25_get_c2 (this_p);
} }
int dst_c = this_p->frame_data[AX25_DESTINATION * 7 + 6] & SSID_H_MASK;
int src_c = this_p->frame_data[AX25_SOURCE * 7 + 6] & SSID_H_MASK;
char cr_text[8];
char pf_text[8];
if (dst_c) {
if (src_c) { *cr = cr_11; strcpy(cr_text,"cc=11"); strcpy(pf_text,"p/f"); }
else { *cr = cr_cmd; strcpy(cr_text,"cmd"); strcpy(pf_text,"p"); }
}
else {
if (src_c) { *cr = cr_res; strcpy(cr_text,"res"); strcpy(pf_text,"f"); }
else { *cr = cr_00; strcpy(cr_text,"cc=00"); strcpy(pf_text,"p/f"); }
}
if ((c & 1) == 0) { if ((c & 1) == 0) {
// Information rrr p sss 0 or sssssss 0 rrrrrrr p // Information rrr p sss 0 or sssssss 0 rrrrrrr p
if (modulo == modulo_128) { if (this_p->modulo == modulo_128) {
*ns = (c >> 1) & 0x7f; *ns = (c >> 1) & 0x7f;
*pf = c2 & 1; *pf = c2 & 1;
*nr = (c2 >> 1) & 0x7f; *nr = (c2 >> 1) & 0x7f;
@ -1846,14 +2083,16 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *
*pf = (c >> 4) & 1; *pf = (c >> 4) & 1;
*nr = (c >> 5) & 7; *nr = (c >> 5) & 7;
} }
snprintf (desc, DESC_SIZ, "I frame, n(s)= %d, n(r)=%d, p=%d", *ns, *nr, *pf);
//snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d, pid=0x%02x", cr_text, *ns, *nr, pf_text, *pf, ax25_get_pid(this_p));
return (frame_type_I); return (frame_type_I);
} }
else if ((c & 2) == 0) { else if ((c & 2) == 0) {
// Supervisory rrr p/f ss 0 1 or 0000 ss 0 1 rrrrrrr p/f // Supervisory rrr p/f ss 0 1 or 0000 ss 0 1 rrrrrrr p/f
if (modulo == modulo_128) { if (this_p->modulo == modulo_128) {
*pf = c2 & 1; *pf = c2 & 1;
*nr = (c2 >> 1) & 0x7f; *nr = (c2 >> 1) & 0x7f;
} }
@ -1862,11 +2101,12 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *
*nr = (c >> 5) & 7; *nr = (c >> 5) & 7;
} }
switch ((c >> 2) & 3) { switch ((c >> 2) & 3) {
case 0: snprintf (desc, DESC_SIZ, "S frame RR, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_RR); break; case 0: snprintf (desc, DESC_SIZ, "RR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RR); break;
case 1: snprintf (desc, DESC_SIZ, "S frame RNR, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_RNR); break; case 1: snprintf (desc, DESC_SIZ, "RNR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RNR); break;
case 2: snprintf (desc, DESC_SIZ, "S frame REJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_REJ); break; case 2: snprintf (desc, DESC_SIZ, "REJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_REJ); break;
case 3: snprintf (desc, DESC_SIZ, "S frame SREJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_SREJ); break; case 3: snprintf (desc, DESC_SIZ, "SREJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_SREJ); break;
} }
} }
else { else {
@ -1877,16 +2117,16 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *
switch (c & 0xef) { switch (c & 0xef) {
case 0x6f: snprintf (desc, DESC_SIZ, "U frame SABME, p=%d", *pf); return (frame_type_U_SABME); break; case 0x6f: snprintf (desc, DESC_SIZ, "SABME %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABME); break;
case 0x2f: snprintf (desc, DESC_SIZ, "U frame SABM, p=%d", *pf); return (frame_type_U_SABM); break; case 0x2f: snprintf (desc, DESC_SIZ, "SABM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABM); break;
case 0x43: snprintf (desc, DESC_SIZ, "U frame DISC, p=%d", *pf); return (frame_type_U_DISC); break; case 0x43: snprintf (desc, DESC_SIZ, "DISC %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DISC); break;
case 0x0f: snprintf (desc, DESC_SIZ, "U frame DM, f=%d", *pf); return (frame_type_U_DM); break; case 0x0f: snprintf (desc, DESC_SIZ, "DM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DM); break;
case 0x63: snprintf (desc, DESC_SIZ, "U frame UA, f=%d", *pf); return (frame_type_U_UA); break; case 0x63: snprintf (desc, DESC_SIZ, "UA %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UA); break;
case 0x87: snprintf (desc, DESC_SIZ, "U frame FRMR, f=%d", *pf); return (frame_type_U_FRMR); break; case 0x87: snprintf (desc, DESC_SIZ, "FRMR %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_FRMR); break;
case 0x03: snprintf (desc, DESC_SIZ, "U frame UI, pf=%d", *pf); return (frame_type_U_UI); break; case 0x03: snprintf (desc, DESC_SIZ, "UI %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UI); break;
case 0xaf: snprintf (desc, DESC_SIZ, "U frame XID, pf=%d", *pf); return (frame_type_U_XID); break; case 0xaf: snprintf (desc, DESC_SIZ, "XID %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_XID); break;
case 0xe3: snprintf (desc, DESC_SIZ, "U frame TEST, pf=%d", *pf); return (frame_type_U_TEST); break; case 0xe3: snprintf (desc, DESC_SIZ, "TEST %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_TEST); break;
default: snprintf (desc, DESC_SIZ, "U frame ???"); return (frame_type_U); break; default: snprintf (desc, DESC_SIZ, "U other???"); return (frame_type_U); break;
} }
} }
@ -1897,6 +2137,8 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *
} /* end ax25_frame_type */ } /* end ax25_frame_type */
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Function: ax25_hex_dump * Function: ax25_hex_dump
@ -1935,6 +2177,7 @@ static void hex_dump (unsigned char *p, int len)
} }
/* Text description of control octet. */ /* Text description of control octet. */
// FIXME: this is wrong. It doesn't handle modulo 128.
// TODO: use ax25_frame_type() instead. // TODO: use ax25_frame_type() instead.
@ -2083,10 +2326,12 @@ int ax25_is_aprs (packet_t this_p)
assert (this_p->magic1 == MAGIC); assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC); assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(0);
ctrl = ax25_get_control(this_p); ctrl = ax25_get_control(this_p);
pid = ax25_get_pid(this_p); pid = ax25_get_pid(this_p);
is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_NO_LAYER_3; is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_PID_NO_LAYER_3;
#if 0 #if 0
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -2095,6 +2340,41 @@ int ax25_is_aprs (packet_t this_p)
return (is_aprs); return (is_aprs);
} }
/*------------------------------------------------------------------
*
* Function: ax25_is_null_frame
*
* Purpose: Is this packet structure empty?
*
* Inputs: this_p - pointer to packet object.
*
* Returns: True if frame data length is 0.
*
* Description: This is used when we want to wake up the
* transmit queue processing thread but don't
* want to transmit a frame.
*
*------------------------------------------------------------------*/
int ax25_is_null_frame (packet_t this_p)
{
int is_null;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
is_null = this_p->frame_len == 0;
#if 0
text_color_set(DW_COLOR_ERROR);
dw_printf ("ax25_is_null_frame(): is_null=%d\n", is_null);
#endif
return (is_null);
}
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Function: ax25_get_control * Function: ax25_get_control
@ -2115,6 +2395,8 @@ int ax25_get_control (packet_t this_p)
assert (this_p->magic1 == MAGIC); assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC); assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) { if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_control_offset(this_p)]); return (this_p->frame_data[ax25_get_control_offset(this_p)]);
} }
@ -2126,10 +2408,19 @@ int ax25_get_c2 (packet_t this_p)
assert (this_p->magic1 == MAGIC); assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC); assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) { if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_control_offset(this_p)+1]); int offset2 = ax25_get_control_offset(this_p)+1;
if (offset2 < this_p->frame_len) {
return (this_p->frame_data[offset2]);
} }
return (-1); else {
return (-1); /* attempt to go beyond the end of frame. */
}
}
return (-1); /* not AX.25 */
} }
@ -2159,6 +2450,8 @@ int ax25_get_pid (packet_t this_p)
// TODO: handle 2 control byte case. // TODO: handle 2 control byte case.
// TODO: sanity check: is it I or UI frame? // TODO: sanity check: is it I or UI frame?
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) { if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_pid_offset(this_p)]); return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
} }
@ -2296,7 +2589,7 @@ unsigned short ax25_m_m_crc (packet_t pp)
* *
* Inputs: pstr - Pointer to string. * Inputs: pstr - Pointer to string.
* *
* len - Maximum length if not -1. * len - Number of bytes. If < 0 we use strlen().
* *
* ascii_only - Restrict output to only ASCII. * ascii_only - Restrict output to only ASCII.
* Normally we allow UTF-8. * Normally we allow UTF-8.
@ -2347,7 +2640,7 @@ void ax25_safe_print (char *pstr, int len, int ascii_only)
if (len > MAXSAFE) if (len > MAXSAFE)
len = MAXSAFE; len = MAXSAFE;
while (len > 0 && *pstr != '\0') while (len > 0)
{ {
ch = *((unsigned char *)pstr); ch = *((unsigned char *)pstr);
@ -2437,7 +2730,11 @@ int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE])
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%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 */ else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec);
}
else if (alevel.mark == -2 && alevel.space == -2) { /* DTMF - single number. */
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec);
} }

View File

@ -64,8 +64,10 @@
*/ */
#define AX25_UI_FRAME 3 /* Control field value. */ #define AX25_UI_FRAME 3 /* Control field value. */
#define AX25_NO_LAYER_3 0xf0 /* protocol ID */
#define AX25_PID_NO_LAYER_3 0xf0 /* protocol ID used for APRS */
#define AX25_PID_SEGMENTATION_FRAGMENT 0x08
#define AX25_PID_ESCAPE_CHARACTER 0xff
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */ #ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
@ -100,7 +102,8 @@ struct packet_s {
* Changed to 1 when position has been used. * Changed to 1 when position has been used.
* *
* for source & destination it is called * for source & destination it is called
* command/response and is normally 1. * command/response. Normally both 1 for APRS.
* They should be opposites for connected mode.
* *
* R R Reserved. Normally set to 1 1. * R R Reserved. Normally set to 1 1.
* *
@ -109,6 +112,7 @@ struct packet_s {
* 0 Usually 0 but 1 for last address. * 0 Usually 0 but 1 for last address.
*/ */
#define SSID_H_MASK 0x80 #define SSID_H_MASK 0x80
#define SSID_H_SHIFT 7 #define SSID_H_SHIFT 7
@ -123,6 +127,15 @@ struct packet_s {
int frame_len; /* Frame length without CRC. */ int frame_len; /* Frame length without CRC. */
int modulo; /* I & S frames have sequence numbers of either 3 bits (modulo 8) */
/* or 7 bits (modulo 128). This is conveyed by either 1 or 2 */
/* control bytes. Unfortunately, we can't determine this by looking */
/* at an isolated frame. We need to know about the context. If we */
/* are part of the conversation, we would know. But if we are */
/* just listening to others, this would be more difficult to determine. */
/* For U frames: set to 0 - not applicable */
/* For I & S frames: 8 or 128 if known. 0 if unknown. */
unsigned char frame_data[AX25_MAX_PACKET_LEN+1]; unsigned char frame_data[AX25_MAX_PACKET_LEN+1];
/* Raw frame contents, without the CRC. */ /* Raw frame contents, without the CRC. */
@ -145,30 +158,53 @@ struct packet_s {
typedef struct packet_s *packet_t; typedef struct packet_s *packet_t;
typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t;
extern packet_t ax25_new (void);
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */ #ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
extern packet_t ax25_new (void);
/* /*
* APRS always has one control octet of 0x03 but the more * APRS always has one control octet of 0x03 but the more
* general AX.25 case is one or two control bytes depending on * general AX.25 case is one or two control bytes depending on
* "modulo 128 operation" is in effect. Unfortunately, it seems * whether "modulo 128 operation" is in effect.
* this can be determined only by examining the XID frames and
* keeping this information for each connection.
* We can assume 1 for our current purposes.
*/ */
//#define DEBUGX 1
static inline int ax25_get_control_offset (packet_t this_p) static inline int ax25_get_control_offset (packet_t this_p)
{ {
//return (0);
return (this_p->num_addr*7); return (this_p->num_addr*7);
} }
static inline int ax25_get_num_control (packet_t this_p) static inline int ax25_get_num_control (packet_t this_p)
{ {
return (1); // TODO: always be 1 for U frame. More complicated for I and S. int c;
c = this_p->frame_data[ax25_get_control_offset(this_p)];
if ( (c & 0x01) == 0 ) { /* I xxxx xxx0 */
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
#endif
return ((this_p->modulo == 128) ? 2 : 1);
}
if ( (c & 0x03) == 1 ) { /* S xxxx xx01 */
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
#endif
return ((this_p->modulo == 128) ? 2 : 1);
}
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is U frame, always returns 1.\n", c);
#endif
return (1); /* U xxxx xx11 */
} }
@ -194,11 +230,17 @@ static int ax25_get_num_pid (packet_t this_p)
c == 0x03 || c == 0x13) { /* UI 000x 0011 */ c == 0x03 || c == 0x13) { /* UI 000x 0011 */
pid = this_p->frame_data[ax25_get_pid_offset(this_p)]; pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
if (pid == 0xff) { #if DEBUGX
dw_printf ("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid==AX25_PID_ESCAPE_CHARACTER) ? 2 : 1);
#endif
if (pid == AX25_PID_ESCAPE_CHARACTER) {
return (2); /* pid 1111 1111 means another follows. */ return (2); /* pid 1111 1111 means another follows. */
} }
return (1); return (1);
} }
#if DEBUGX
dw_printf ("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c);
#endif
return (0); return (0);
} }
@ -217,10 +259,14 @@ static int ax25_get_num_pid (packet_t this_p)
static inline int ax25_get_info_offset (packet_t this_p) static inline int ax25_get_info_offset (packet_t this_p)
{ {
return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p)); int offset = ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
#if DEBUGX
dw_printf ("ax25_get_info_offset, returns %d\n", offset);
#endif
return (offset);
} }
static int ax25_get_num_info (packet_t this_p) static inline int ax25_get_num_info (packet_t this_p)
{ {
int len; int len;
@ -237,11 +283,11 @@ static int ax25_get_num_info (packet_t this_p)
#endif #endif
typedef enum ax25_modulo_e { modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t; typedef enum ax25_modulo_e { modulo_unknown = 0, modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
typedef enum ax25_frame_type_e { typedef enum ax25_frame_type_e {
frame_type_I, // Information frame_type_I = 0, // Information
frame_type_S_RR, // Receive Ready - System Ready To Receive frame_type_S_RR, // Receive Ready - System Ready To Receive
frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full
@ -260,6 +306,8 @@ typedef enum ax25_frame_type_e {
frame_type_U, // other Unnumbered, not used by AX.25. frame_type_U, // other Unnumbered, not used by AX.25.
frame_not_AX25 // Could not get control byte from frame. frame_not_AX25 // Could not get control byte from frame.
// This must be last because value plus 1 is
// for the size of an array.
} ax25_frame_type_t; } ax25_frame_type_t;
@ -346,7 +394,10 @@ extern int ax25_get_heard(packet_t this_p);
extern int ax25_get_first_not_repeated(packet_t pp); extern int ax25_get_first_not_repeated(packet_t pp);
extern int ax25_get_rr (packet_t this_p, int n);
extern int ax25_get_info (packet_t pp, unsigned char **paddr); extern int ax25_get_info (packet_t pp, unsigned char **paddr);
extern int ax25_cut_at_crlf (packet_t this_p);
extern void ax25_set_nextp (packet_t this_p, packet_t next_p); extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
@ -357,15 +408,19 @@ extern packet_t ax25_get_nextp (packet_t this_p);
extern void ax25_set_release_time (packet_t this_p, double release_time); extern void ax25_set_release_time (packet_t this_p, double release_time);
extern double ax25_get_release_time (packet_t this_p); extern double ax25_get_release_time (packet_t this_p);
extern void ax25_set_modulo (packet_t this_p, int modulo);
extern void ax25_format_addrs (packet_t pp, char *); extern void ax25_format_addrs (packet_t pp, char *);
extern void ax25_format_via_path (packet_t this_p, char *result, size_t result_size);
extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]); extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
extern ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns); extern ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns);
extern void ax25_hex_dump (packet_t this_p); extern void ax25_hex_dump (packet_t this_p);
extern int ax25_is_aprs (packet_t pp); extern int ax25_is_aprs (packet_t pp);
extern int ax25_is_null_frame (packet_t this_p);
extern int ax25_get_control (packet_t this_p); extern int ax25_get_control (packet_t this_p);
extern int ax25_get_c2 (packet_t this_p); extern int ax25_get_c2 (packet_t this_p);

889
ax25_pad2.c Normal file
View File

@ -0,0 +1,889 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Name: ax25_pad2.c
*
* Purpose: Packet assembler and disasembler, part 2.
*
* Description:
*
* The original ax25_pad.c was written with APRS in mind.
* It handles UI frames and transparency for a KISS TNC.
* Here we add new functions that can handle the
* more general cases of AX.25 frames.
*
*
* * Destination Address (note: opposite order in printed format)
*
* * Source Address
*
* * 0-8 Digipeater Addresses
* (The AX.25 v2.2 spec reduced this number to
* a maximum of 2 but I allow the original 8.)
*
* Each address is composed of:
*
* * 6 upper case letters or digits, blank padded.
* These are shifted left one bit, leaving the LSB always 0.
*
* * a 7th octet containing the SSID and flags.
* The LSB is always 0 except for the last octet of the address field.
*
* The final octet of the Destination has the form:
*
* C R R SSID 0, where,
*
* C = command/response. Set to 1 for command.
* R R = Reserved = 1 1 (See RR note, below)
* SSID = substation ID
* 0 = zero
*
* The final octet of the Source has the form:
*
* C R R SSID 0, where,
*
* C = command/response. Must be inverse of destination C bit.
* R R = Reserved = 1 1 (See RR note, below)
* SSID = substation ID
* 0 = zero (or 1 if no repeaters)
*
* The final octet of each repeater has the form:
*
* H R R SSID 0, where,
*
* H = has-been-repeated = 0 initially.
* Set to 1 after this address has been used.
* R R = Reserved = 1 1
* SSID = substation ID
* 0 = zero (or 1 if last repeater in list)
*
* A digipeater would repeat this frame if it finds its address
* with the "H" bit set to 0 and all earlier repeater addresses
* have the "H" bit set to 1.
* The "H" bit would be set to 1 in the repeated frame.
*
* In standard monitoring format, an asterisk is displayed after the last
* digipeater with the "H" bit set. That indicates who you are hearing
* over the radio.
*
*
* Next we have:
*
* * One or two byte Control Field - A U frame always has one control byte.
* When using modulo 128 sequence numbers, the
* I and S frames can have a second byte allowing
* 7 bit fields instead of 3 bit fields.
* Unfortunately, we can't tell which we have by looking
* at a frame out of context. :-(
* If we are one end of the link, we would know this
* from SABM/SABME and possible later negotiation
* with XID. But if we start monitoring two other
* stations that are already conversing, we don't know.
*
* RR note: It seems that some implementations put a hint
* in the "RR" reserved bits.
* http://www.tapr.org/pipermail/ax25-layer2/2005-October/000297.html
* The RR bits can also be used for "DAMA" which is
* some sort of channel access coordination scheme.
* http://internet.freepage.de/cgi-bin/feets/freepage_ext/41030x030A/rewrite/hennig/afu/afudoc/afudama.html
* Neither is part of the official protocol spec.
*
* * One byte Protocol ID - Only for I and UI frames.
* Normally we would use 0xf0 for no layer 3.
*
* Finally the Information Field. The initial max size is 256 but it
* can be negotiated higher if both ends agree.
*
* Only these types of frames can have an information part:
* - I
* - UI
* - XID
* - TEST
* - FRMR
*
* The 2 byte CRC is not stored here.
*
*
* Constructors:
* ax25_u_frame - Construct a U frame.
* ax25_s_frame - Construct a S frame.
* ax25_i_frame - Construct a I frame.
*
* Get methods: .... ???
*
*------------------------------------------------------------------*/
#define AX25_PAD_C /* this will affect behavior of ax25_pad.h */
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include "textcolor.h"
#include "ax25_pad.h"
#include "ax25_pad2.h"
extern int ax25memdebug;
static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr);
//#if AX25MEMDEBUG
//#undef AX25MEMDEBUG
//#endif
/*------------------------------------------------------------------------------
*
* Name: ax25_u_frame
*
* Purpose: Construct a U frame.
*
* Input: addrs - Array of addresses.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* ftype - One of:
* frame_type_U_SABME // Set Async Balanced Mode, Extended
* frame_type_U_SABM // Set Async Balanced Mode
* frame_type_U_DISC // Disconnect
* frame_type_U_DM // Disconnect Mode
* frame_type_U_UA // Unnumbered Acknowledge
* frame_type_U_FRMR // Frame Reject
* frame_type_U_UI // Unnumbered Information
* frame_type_U_XID // Exchange Identification
* frame_type_U_TEST // Test
*
* pf - Poll/Final flag.
*
* pid - Protocol ID. >>> Used ONLY for the UI type. <<<
* Normally 0xf0 meaning no level 3.
* Could be other values for NET/ROM, etc.
*
* pinfo - Pointer to data for Info field. Allowed only for UI, XID, TEST, FRMR.
*
* info_len - Length for Info field.
*
*
* Returns: Pointer to new packet object.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line)
#else
packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len)
#endif
{
packet_t this_p;
unsigned char *p;
int ctrl = 0;
unsigned int t = 999; // 1 = must be cmd, 0 = must be response, 2 = can be either.
int i = 0; // Is Info part allowed?
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_u_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
if (this_p == NULL) return (NULL);
this_p->modulo = 0;
if ( ! set_addrs (this_p, addrs, num_addr, cr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
switch (ftype) {
// 1 = cmd only, 0 = res only, 2 = either
case frame_type_U_SABME: ctrl = 0x6f; t = 1; break;
case frame_type_U_SABM: ctrl = 0x2f; t = 1; break;
case frame_type_U_DISC: ctrl = 0x43; t = 1; break;
case frame_type_U_DM: ctrl = 0x0f; t = 0; break;
case frame_type_U_UA: ctrl = 0x63; t = 0; break;
case frame_type_U_FRMR: ctrl = 0x87; t = 0; i = 1; break;
case frame_type_U_UI: ctrl = 0x03; t = 2; i = 1; break;
case frame_type_U_XID: ctrl = 0xaf; t = 2; i = 1; break;
case frame_type_U_TEST: ctrl = 0xe3; t = 2; i = 1; break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid ftype %d for U frame.\n", __func__, ftype);
ax25_delete (this_p);
return (NULL);
break;
}
if (pf) ctrl |= 0x10;
if (t != 2) {
if (cr != t) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, cr is %d but must be %d. ftype=%d\n", __func__, cr, t, ftype);
}
}
p = this_p->frame_data + this_p->frame_len;
*p++ = ctrl;
this_p->frame_len++;
if (ftype == frame_type_U_UI) {
// Definitely don't want pid value of 0 (not in valid list)
// or 0xff (which means more bytes follow).
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, Invalid pid value 0x%02x.\n", __func__, pid);
pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
}
if (i) {
if (pinfo != NULL && info_len > 0) {
if (info_len > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, Invalid information field length %d.\n", __func__, info_len);
info_len = AX25_MAX_INFO_LEN;
}
memcpy (p, pinfo, info_len);
p += info_len;
this_p->frame_len += info_len;
}
}
else {
if (pinfo != NULL && info_len > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Info part not allowed for U frame type.\n", __func__);
}
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d\n", check_ftype, check_desc, check_pf);
assert (check_cr == cr);
assert (check_ftype == ftype);
assert (check_pf == pf);
assert (check_nr == -1);
assert (check_ns == -1);
#endif
return (this_p);
} /* end ax25_u_frame */
/*------------------------------------------------------------------------------
*
* Name: ax25_s_frame
*
* Purpose: Construct an S frame.
*
* Input: addrs - Array of addresses.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* ftype - One of:
* frame_type_S_RR, // Receive Ready - System Ready To Receive
* frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full
* frame_type_S_REJ, // Reject Frame - Out of Sequence or Duplicate
* frame_type_S_SREJ, // Selective Reject - Request single frame repeat
*
* modulo - 8 or 128. Determines if we have 1 or 2 control bytes.
*
* nr - N(R) field --- describe.
*
* pf - Poll/Final flag.
*
*
* Returns: Pointer to new packet object.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, char *src_file, int src_line)
#else
packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf)
#endif
{
packet_t this_p;
unsigned char *p;
int ctrl = 0;
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_s_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
if (this_p == NULL) return (NULL);
if ( ! set_addrs (this_p, addrs, num_addr, cr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
if (modulo != 8 && modulo != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid modulo %d for S frame.\n", __func__, modulo);
modulo = 8;
}
this_p->modulo = modulo;
if (nr < 0 || nr >= modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid N(R) %d for S frame.\n", __func__, nr);
nr &= (modulo - 1);
}
switch (ftype) {
case frame_type_S_RR: ctrl = 0x01; break;
case frame_type_S_RNR: ctrl = 0x05; break;
case frame_type_S_REJ: ctrl = 0x09; break;
case frame_type_S_SREJ: ctrl = 0x0d; break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid ftype %d for S frame.\n", __func__, ftype);
ax25_delete (this_p);
return (NULL);
break;
}
p = this_p->frame_data + this_p->frame_len;
if (modulo == 8) {
if (pf) ctrl |= 0x10;
ctrl |= nr << 5;
*p++ = ctrl;
this_p->frame_len++;
}
else {
*p++ = ctrl;
this_p->frame_len++;
ctrl = pf & 1;
ctrl |= nr << 1;
*p++ = ctrl;
this_p->frame_len++;
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
// todo modulo must be input.
check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d\n", check_ftype, check_desc, check_pf, check_nr);
assert (check_cr == cr);
assert (check_ftype == ftype);
assert (check_pf == pf);
assert (check_nr == nr);
assert (check_ns == -1);
#endif
return (this_p);
} /* end ax25_s_frame */
/*------------------------------------------------------------------------------
*
* Name: ax25_i_frame
*
* Purpose: Construct an I frame.
*
* Input: addrs - Array of addresses.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* modulo - 8 or 128.
*
* nr - N(R) field --- describe.
*
* ns - N(S) field --- describe.
*
* pf - Poll/Final flag.
*
* pid - Protocol ID.
* Normally 0xf0 meaning no level 3.
* Could be other values for NET/ROM, etc.
*
* pinfo - Pointer to data for Info field.
*
* info_len - Length for Info field.
*
*
* Returns: Pointer to new packet object.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line)
#else
packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len)
#endif
{
packet_t this_p;
unsigned char *p;
int ctrl = 0;
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_i_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
if (this_p == NULL) return (NULL);
if ( ! set_addrs (this_p, addrs, num_addr, cr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Could not set addresses for I frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
if (modulo != 8 && modulo != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid modulo %d for I frame.\n", __func__, modulo);
modulo = 8;
}
this_p->modulo = modulo;
if (nr < 0 || nr >= modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid N(R) %d for I frame.\n", __func__, nr);
nr &= (modulo - 1);
}
if (ns < 0 || ns >= modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid N(S) %d for I frame.\n", __func__, ns);
ns &= (modulo - 1);
}
p = this_p->frame_data + this_p->frame_len;
if (modulo == 8) {
ctrl = (nr << 5) | (ns << 1);
if (pf) ctrl |= 0x10;
*p++ = ctrl;
this_p->frame_len++;
}
else {
ctrl = ns << 1;
*p++ = ctrl;
this_p->frame_len++;
ctrl = nr << 1;
if (pf) ctrl |= 0x01;
*p++ = ctrl;
this_p->frame_len++;
}
// Definitely don't want pid value of 0 (not in valid list)
// or 0xff (which means more bytes follow).
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Warning: Client application provided invalid PID value, 0x%02x, for I frame.\n", pid);
pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
if (pinfo != NULL && info_len > 0) {
if (info_len > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: I frame, Invalid information field length %d.\n", __func__, info_len);
info_len = AX25_MAX_INFO_LEN;
}
memcpy (p, pinfo, info_len);
p += info_len;
this_p->frame_len += info_len;
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
unsigned char *check_pinfo;
int check_info_len;
check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d, ns=%d\n", check_ftype, check_desc, check_pf, check_nr, check_ns);
check_info_len = ax25_get_info (this_p, &check_pinfo);
assert (check_cr == cr);
assert (check_ftype == frame_type_I);
assert (check_pf == pf);
assert (check_nr == nr);
assert (check_ns == ns);
assert (check_info_len == info_len);
assert (strcmp((char*)check_pinfo,(char*)pinfo) == 0);
#endif
return (this_p);
} /* end ax25_i_frame */
/*------------------------------------------------------------------------------
*
* Name: set_addrs
*
* Purpose: Set address fields
*
* Input: pp - Packet object.
*
* addrs - Array of addresses. Same order as in frame.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* Output: pp->frame_data - 7 bytes for each address.
*
* pp->frame_len - num_addr * 7
*
* p->num_addr - num_addr
*
* Returns: 1 for success. 0 for failure.
*
*------------------------------------------------------------------------------*/
static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr)
{
int n;
assert (pp->frame_len == 0);
assert (cr == cr_cmd || cr == cr_res);
if (num_addr < AX25_MIN_ADDRS || num_addr > AX25_MAX_ADDRS) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("INTERNAL ERROR: %s %s %d, num_addr = %d\n", __FILE__, __func__, __LINE__, num_addr);
return (0);
}
for (n = 0; n < num_addr; n++) {
unsigned char *pa = pp->frame_data + n * 7;
int ok;
int strict = 1;
char oaddr[AX25_MAX_ADDR_LEN];
int ssid;
int heard;
int j;
ok = ax25_parse_addr (n, addrs[n], strict, oaddr, &ssid, &heard);
if (! ok) return (0);
// Fill in address.
memset (pa, ' ' << 1, 6);
for (j = 0; oaddr[j]; j++) {
pa[j] = oaddr[j] << 1;
}
pa += 6;
// Fill in SSID.
*pa = 0x60 | ((ssid & 0xf) << 1);
// Command / response flag.
switch (n) {
case AX25_DESTINATION:
if (cr == cr_cmd) *pa |= 0x80;
break;
case AX25_SOURCE:
if (cr == cr_res) *pa |= 0x80;
break;
default:
break;
}
// Is this the end of address field?
if (n == num_addr - 1) {
*pa |= 1;
}
pp->frame_len += 7;
}
pp->num_addr = num_addr;
return (1);
} /* end set_addrs */
/*------------------------------------------------------------------------------
*
* Name: main
*
* Purpose: Quick unit test for this file.
*
* Description: Generate a variety of frames.
* Each function calls ax25_frame_type to verify results.
*
* $ gcc -DPAD2TEST -DUSE_REGEX_STATIC -Iregex ax25_pad.c ax25_pad2.c fcs_calc.o textcolor.o regex.a misc.a
*
*------------------------------------------------------------------------------*/
#if PAD2TEST
int main ()
{
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
int num_addr = 2;
cmdres_t cr;
ax25_frame_type_t ftype;
int pf = 0;
int pid = 0xf0;
int modulo;
int nr, ns;
unsigned char *pinfo = NULL;
int info_len = 0;
packet_t pp;
strcpy (addrs[0], "W2UB");
strcpy (addrs[1], "WB2OSZ-15");
num_addr = 2;
/* U frame */
for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) {
for (pf = 0; pf <= 1; pf++) {
int cmin, cmax;
switch (ftype) {
// 0 = response, 1 = command
case frame_type_U_SABME: cmin = 1; cmax = 1; break;
case frame_type_U_SABM: cmin = 1; cmax = 1; break;
case frame_type_U_DISC: cmin = 1; cmax = 1; break;
case frame_type_U_DM: cmin = 0; cmax = 0; break;
case frame_type_U_UA: cmin = 0; cmax = 0; break;
case frame_type_U_FRMR: cmin = 0; cmax = 0; break;
case frame_type_U_UI: cmin = 0; cmax = 1; break;
case frame_type_U_XID: cmin = 0; cmax = 1; break;
case frame_type_U_TEST: cmin = 0; cmax = 1; break;
default: break; // avoid compiler warning.
}
for (cr = cmin; cr <= cmax; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len);
ax25_hex_dump (pp);
ax25_delete (pp);
}
}
}
dw_printf ("\n----------\n\n");
/* S frame */
strcpy (addrs[2], "DIGI1-1");
num_addr = 3;
for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) {
for (pf = 0; pf <= 1; pf++) {
modulo = 8;
nr = modulo / 2 + 1;
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf);
ax25_hex_dump (pp);
ax25_delete (pp);
}
modulo = 128;
nr = modulo / 2 + 1;
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf);
ax25_hex_dump (pp);
ax25_delete (pp);
}
}
}
dw_printf ("\n----------\n\n");
/* I frame */
pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain.";
info_len = strlen((char*)pinfo);
for (pf = 0; pf <= 1; pf++) {
modulo = 8;
nr = 0x55 & (modulo - 1);
ns = 0xaa & (modulo - 1);
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len);
ax25_hex_dump (pp);
ax25_delete (pp);
}
modulo = 128;
nr = 0x55 & (modulo - 1);
ns = 0xaa & (modulo - 1);
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len);
ax25_hex_dump (pp);
ax25_delete (pp);
}
}
text_color_set(DW_COLOR_REC);
dw_printf ("\n----------\n\n");
dw_printf ("\nSUCCESS!\n");
exit (EXIT_SUCCESS);
} /* end main */
#endif
/* end ax25_pad2.c */

55
ax25_pad2.h Normal file
View File

@ -0,0 +1,55 @@
/*-------------------------------------------------------------------
*
* Name: ax25_pad2.h
*
* Purpose: Header file for using ax25_pad2.c
* ax25_pad dealt only with UI frames.
* This adds a facility for the other types: U, s, I.
*
*------------------------------------------------------------------*/
#ifndef AX25_PAD2_H
#define AX25_PAD2_H 1
#include "ax25_pad.h"
#if AX25MEMDEBUG // to investigate a memory leak problem
packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line);
packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, char *src_file, int src_line);
packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line);
#define ax25_u_frame(a,n,c,f,p,q,i,l) ax25_u_frame_debug(a,n,c,f,p,q,i,l,__FILE__,__LINE__)
#define ax25_s_frame(a,n,c,f,m,r,p) ax25_s_frame_debug(a,n,c,f,m,r,p,__FILE__,__LINE__)
#define ax25_i_frame(a,n,c,m,r,s,p,q,i,l) ax25_i_frame_debug(a,n,c,m,r,s,p,q,i,l,__FILE__,__LINE__)
#else
packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len);
packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf);
packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len);
#endif
#endif /* AX25_PAD2_H */
/* end ax25_pad2.h */

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -30,6 +30,8 @@
//#define DEBUG 1 //#define DEBUG 1
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@ -40,7 +42,6 @@
#include <time.h> #include <time.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "audio.h" #include "audio.h"
@ -55,6 +56,7 @@
#include "log.h" #include "log.h"
#include "dlq.h" #include "dlq.h"
#include "aprs_tt.h" // for dw_run_cmd - should relocate someday. #include "aprs_tt.h" // for dw_run_cmd - should relocate someday.
#include "mheard.h"
#if __WIN32__ #if __WIN32__
@ -83,6 +85,7 @@ struct tm *localtime_r(time_t *clock, struct tm *res)
static struct audio_s *g_modem_config_p; static struct audio_s *g_modem_config_p;
static struct misc_config_s *g_misc_config_p; static struct misc_config_s *g_misc_config_p;
static struct igate_config_s *g_igate_config_p;
#if __WIN32__ #if __WIN32__
@ -118,6 +121,10 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo);
* Used only to find valid channels. * Used only to find valid channels.
* *
* pconfig - misc. configuration from config file. * pconfig - misc. configuration from config file.
* Beacon stuff ended up here.
*
* pigate - IGate configuration.
* Need this for calculating IGate statistics.
* *
* *
* Outputs: Remember required information for future use. * Outputs: Remember required information for future use.
@ -131,7 +138,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo);
void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig) void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate)
{ {
time_t now; time_t now;
int j; int j;
@ -156,6 +163,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig)
*/ */
g_modem_config_p = pmodem; g_modem_config_p = pmodem;
g_misc_config_p = pconfig; g_misc_config_p = pconfig;
g_igate_config_p = pigate;
/* /*
* Precompute the packet contents so any errors are * Precompute the packet contents so any errors are
@ -228,6 +236,22 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig)
} }
break; break;
case BEACON_IGATE:
/* Doesn't make sense if IGate is not configured. */
if (strlen(g_igate_config_p->t2_server_name) == 0 ||
strlen(g_igate_config_p->t2_login) == 0 ||
strlen(g_igate_config_p->t2_passcode) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Doesn't make sense to use IBEACON without IGate Configured.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("IBEACON has been disabled.\n");
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
break;
case BEACON_IGNORE: case BEACON_IGNORE:
break; break;
} }
@ -870,6 +894,25 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
} }
break; break;
case BEACON_IGATE:
{
int last_minutes = 30;
char stuff[256];
snprintf (stuff, sizeof(stuff), "<IGATE,MSG_CNT=%d,PKT_CNT=%d,DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d,UPL_CNT=%d,DNL_CNT=%d",
igate_get_msg_cnt(),
igate_get_pkt_cnt(),
mheard_count(0,last_minutes),
mheard_count(g_igate_config_p->max_digi_hops,last_minutes),
mheard_count(8,last_minutes),
igate_get_upl_cnt(),
igate_get_dnl_cnt());
strlcat (beacon_text, stuff, sizeof(beacon_text));
}
break;
case BEACON_IGNORE: case BEACON_IGNORE:
default: default:
break; break;
@ -911,10 +954,10 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
case SENDTO_RECV: case SENDTO_RECV:
/* Simulated reception. */ /* Simulated reception from radio. */
memset (&alevel, 0xff, sizeof(alevel)); memset (&alevel, 0xff, sizeof(alevel));
dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); dlq_rec_frame (g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, "");
break; break;
} }
} }

View File

@ -1,6 +1,6 @@
/* beacon.h */ /* beacon.h */
void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig); void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate);
void beacon_tracker_set_debug (int level); void beacon_tracker_set_debug (int level);

317
cdigipeater.c Normal file
View File

@ -0,0 +1,317 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Name: cdigipeater.c
*
* Purpose: Act as an digital repeater for connected AX.25 mode.
* Similar digipeater.c is for APRS.
*
*
* Description: Decide whether the specified packet should
* be digipeated. Put my callsign in the digipeater field used.
*
* APRS and connected mode were two split into two
* separate files. Yes, there is duplicate code but they
* are significantly different and I thought it would be
* too confusing to munge them together.
*
* References: The Ax.25 protcol barely mentions digipeaters and
* and doesn't describe how they should work.
*
*------------------------------------------------------------------*/
#define CDIGIPEATER_C
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h> /* for isdigit, isupper */
#include "regex.h"
#include <sys/unistd.h>
#include "ax25_pad.h"
#include "cdigipeater.h"
#include "textcolor.h"
#include "tq.h"
#include "pfilter.h"
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str);
/*
* Keep pointer to configuration options.
* Set by cdigipeater_init and used later.
*/
static struct audio_s *save_audio_config_p;
static struct cdigi_config_s *save_cdigi_config_p;
/*
* Maintain count of packets digipeated for each combination of from/to channel.
*/
static int cdigi_count[MAX_CHANS][MAX_CHANS];
int cdigipeater_get_count (int from_chan, int to_chan) {
return (cdigi_count[from_chan][to_chan]);
}
/*------------------------------------------------------------------------------
*
* Name: cdigipeater_init
*
* Purpose: Initialize with stuff from configuration file.
*
* Inputs: p_audio_config - Configuration for audio channels.
*
* p_cdigi_config - Connected Digipeater configuration details.
*
* Outputs: Save pointers to configuration for later use.
*
* Description: Called once at application startup time.
*
*------------------------------------------------------------------------------*/
void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config)
{
save_audio_config_p = p_audio_config;
save_cdigi_config_p = p_cdigi_config;
}
/*------------------------------------------------------------------------------
*
* Name: cdigipeater
*
* Purpose: Re-transmit packet if it matches the rules.
*
* Inputs: chan - Radio channel where it was received.
*
* pp - Packet object.
*
* Returns: None.
*
*------------------------------------------------------------------------------*/
void cdigipeater (int from_chan, packet_t pp)
{
int to_chan;
if ( from_chan < 0 || from_chan >= MAX_CHANS || ( ! save_audio_config_p->achan[from_chan].valid) ) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
return;
}
/*
* First pass: Look at packets being digipeated to same channel.
*
* There was a reason for two passes for APRS.
* Might not have a benefit here.
*/
for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
if (save_cdigi_config_p->enabled[from_chan][to_chan]) {
if (to_chan == from_chan) {
packet_t result;
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
}
}
}
}
/*
* Second pass: Look at packets being digipeated to different channel.
*/
for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
if (save_cdigi_config_p->enabled[from_chan][to_chan]) {
if (to_chan != from_chan) {
packet_t result;
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
}
}
}
}
} /* end cdigipeater */
/*------------------------------------------------------------------------------
*
* Name: cdigipeat_match
*
* Purpose: A simple digipeater for connected mode AX.25.
*
* Input: pp - Pointer to a packet object.
*
* mycall_rec - Call of my station, with optional SSID,
* associated with the radio channel where the
* packet was received.
*
* mycall_xmit - Call of my station, with optional SSID,
* associated with the radio channel where the
* packet is to be transmitted. Could be the same as
* mycall_rec or different.
*
* alias - Compiled pattern for my station aliases.
* Could be NULL if no aliases.
*
* to_chan - Channel number that we are transmitting to.
*
* filter_str - Filter expression string or NULL.
*
* Returns: Packet object for transmission or NULL.
* The original packet is not modified. The caller is responsible for freeing it.
* We make a copy and return that modified copy!
* This is very important because we could digipeat from one channel to many.
*
* Description: The packet will be digipeated if the next unused digipeater
* field matches one of the following:
*
* - mycall_rec
* - alias list
*
* APRS digipeating drops duplicates within 30 seconds but we don't do that here.
*
*------------------------------------------------------------------------------*/
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str)
{
int r;
char repeater[AX25_MAX_ADDR_LEN];
int err;
char err_msg[100];
/*
* First check if filtering has been configured.
*/
if (filter_str != NULL) {
if (pfilter(from_chan, to_chan, filter_str, pp, 0) != 1) {
return(NULL);
}
}
/*
* Find the first repeater station which doesn't have "has been repeated" set.
*
* r = index of the address position in the frame.
*/
r = ax25_get_first_not_repeated(pp);
if (r < AX25_REPEATER_1) {
return (NULL); // Nothing to do.
}
ax25_get_addr_with_ssid(pp, r, repeater);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("First unused digipeater is %s\n", repeater);
#endif
/*
* First check for explicit use of my call.
* Note that receive and transmit channels could have different callsigns.
*/
if (strcmp(repeater, mycall_rec) == 0) {
packet_t result;
result = ax25_dup (pp);
assert (result != NULL);
/* If using multiple radio channels, they could have different calls. */
ax25_set_addr (result, r, mycall_xmit);
ax25_set_h (result, r);
return (result);
}
/*
* If we have an alias match, substitute MYCALL.
*/
if (alias != NULL) {
err = regexec(alias,repeater,0,NULL,0);
if (err == 0) {
packet_t result;
result = ax25_dup (pp);
assert (result != NULL);
ax25_set_addr (result, r, mycall_xmit);
ax25_set_h (result, r);
return (result);
}
else if (err != REG_NOMATCH) {
regerror(err, alias, err_msg, sizeof(err_msg));
text_color_set (DW_COLOR_ERROR);
dw_printf ("%s\n", err_msg);
}
}
/*
* Don't repeat it if we get here.
*/
return (NULL);
} /* end cdigipeat_match */
/* end cdigipeater.c */

59
cdigipeater.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef CDIGIPEATER_H
#define CDIGIPEATER_H 1
#include "regex.h"
#include "direwolf.h" /* for MAX_CHANS */
#include "ax25_pad.h" /* for packet_t */
#include "audio.h" /* for radio channel properties */
/*
* Information required for Connected mode digipeating.
*
* The configuration file reader fills in this information
* and it is passed to cdigipeater_init at application start up time.
*/
struct cdigi_config_s {
/*
* Rules for each of the [from_chan][to_chan] combinations.
*/
regex_t alias[MAX_CHANS][MAX_CHANS];
int enabled[MAX_CHANS][MAX_CHANS];
char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
// NULL or optional Packet Filter strings such as "t/m".
// Notice the size of arrays is one larger than normal.
// That extra position is for the IGate.
};
/*
* Call once at application start up time.
*/
extern void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config);
/*
* Call this for each packet received.
* Suitable packets will be queued for transmission.
*/
extern void cdigipeater (int from_chan, packet_t pp);
/* Make statistics available. */
int cdigipeater_get_count (int from_chan, int to_chan);
#endif
/* end cdigipeater.h */

691
config.c

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
#include "audio.h" /* for struct audio_s */ #include "audio.h" /* for struct audio_s */
#include "digipeater.h" /* for struct digi_config_s */ #include "digipeater.h" /* for struct digi_config_s */
#include "cdigipeater.h" /* for struct cdigi_config_s */
#include "aprs_tt.h" /* for struct tt_config_s */ #include "aprs_tt.h" /* for struct tt_config_s */
#include "igate.h" /* for struct igate_config_s */ #include "igate.h" /* for struct igate_config_s */
@ -23,7 +24,7 @@
* This wasn't thought out. It just happened. * This wasn't thought out. It just happened.
*/ */
enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM }; enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM, BEACON_IGATE };
enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV }; enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV };
@ -43,6 +44,7 @@ struct misc_config_s {
char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */ char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */
/* e.g. COM22, /dev/ttyACM0 */ /* e.g. COM22, /dev/ttyACM0 */
/* Currently no option for setting non-standard speed. */
char gpsd_host[20]; /* Host for gpsd server. */ char gpsd_host[20]; /* Host for gpsd server. */
/* e.g. localhost, 192.168.1.2 */ /* e.g. localhost, 192.168.1.2 */
@ -50,9 +52,19 @@ struct misc_config_s {
int gpsd_port; /* Port number for gpsd server. */ int gpsd_port; /* Port number for gpsd server. */
/* Default is 2947. */ /* Default is 2947. */
char waypoint_port[20]; /* Serial port name for sending NMEA waypoint sentences */
/* to a GPS map display or other mapping application. */
/* e.g. COM22, /dev/ttyACM0 */ /* e.g. COM22, /dev/ttyACM0 */
char nmea_port[20]; /* Serial port name for NMEA communication with GPS */ /* Currently no option for setting non-standard speed. */
/* receiver and/or mapping application. Change this. */
int waypoint_formats; /* Which sentence formats should be generated? */
#define WPT_FORMAT_NMEA_GENERIC 0x01 /* N $GPWPT */
#define WPT_FORMAT_GARMIN 0x02 /* G $PGRMW */
#define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */
#define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */
char logdir[80]; /* Directory for saving activity logs. */ char logdir[80]; /* Directory for saving activity logs. */
@ -65,6 +77,25 @@ struct misc_config_s {
int sb_turn_angle; /* degrees */ int sb_turn_angle; /* degrees */
int sb_turn_slope; /* degrees * MPH */ int sb_turn_slope; /* degrees * MPH */
// AX.25 connected mode.
int frack; /* Number of seconds to wait for ack to transmission. */
int retry; /* Number of times to retry before giving up. */
int paclen; /* Max number of bytes in information part of frame. */
int maxframe_basic; /* Max frames to send before ACK. mod 8 "Window" size. */
int maxframe_extended; /* Max frames to send before ACK. mod 128 "Window" size. */
int maxv22; /* Maximum number of unanswered SABME frames sent before */
/* switching to SABM. This is to handle the case of an old */
/* TNC which simply ignores SABME rather than replying with FRMR. */
// Beacons.
int num_beacons; /* Number of beacons defined. */ int num_beacons; /* Number of beacons defined. */
@ -155,6 +186,7 @@ struct misc_config_s {
extern void config_init (char *fname, struct audio_s *p_modem, extern void config_init (char *fname, struct audio_s *p_modem,
struct digi_config_s *digi_config, struct digi_config_s *digi_config,
struct cdigi_config_s *cdigi_config,
struct tt_config_s *p_tt_config, struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config, struct igate_config_s *p_igate_config,
struct misc_config_s *misc_config); struct misc_config_s *misc_config);

View File

@ -33,6 +33,8 @@
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include <assert.h> #include <assert.h>
@ -43,12 +45,8 @@
#include <ctype.h> /* for isdigit */ #include <ctype.h> /* for isdigit */
#include <fcntl.h> #include <fcntl.h>
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 1
#endif
#include "regex.h" #include "regex.h"
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "symbols.h" #include "symbols.h"
@ -110,7 +108,7 @@ 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_status_report (decode_aprs_t *A, char *, int);
static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet); 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_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_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet);
static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int); static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int);
static void aprs_morse_code (decode_aprs_t *A, char *, int); static void aprs_morse_code (decode_aprs_t *A, char *, int);
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int); static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
@ -166,7 +164,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
A->g_quiet = quiet; A->g_quiet = quiet;
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown message type %c", *pinfo); snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown APRS Data Type Indicator \"%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_symbol_code = ' '; /* What should we have for default symbol? */
@ -201,6 +199,33 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src); ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest); ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
/*
* Report error if the information part contains a nul character.
* There are two known cases where this can happen.
*
* - The Kenwood TM-D710A sometimes sends packets like this:
*
* VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast=
* K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV =
*
* Notice that the data type indicator of "4" is not valid. If we remove
* 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format.
* This same thing has been observed from others and is intermittent.
*
* - AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes.
* This is wrong, it should be using UTF-8.
*/
if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) {
text_color_set(DW_COLOR_ERROR);
dw_printf("'nul' character found in Information part. This should never happen.\n");
dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src);
if (strcmp((char*)pinfo, "4P") == 0) {
dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n");
}
}
switch (*pinfo) { /* "DTI" data type identifier. */ switch (*pinfo) { /* "DTI" data type identifier. */
@ -254,8 +279,9 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
break; break;
case ':': /* Message */ case ':': /* Message: for one person, a group, or a bulletin. */
/* Directed Station Query */ /* Directed Station Query */
/* Telemetry metadata. */
aprs_message (A, pinfo, info_len, quiet); aprs_message (A, pinfo, info_len, quiet);
break; break;
@ -638,14 +664,14 @@ void decode_aprs_print (decode_aprs_t *A) {
if ( ! A->g_quiet) { if ( ! A->g_quiet) {
for (j=0; j<n; j++) { for (j=0; j<n; j++) {
if ((unsigned)A->g_comment[j] == (char)0xb0 && (j == 0 || ! (A->g_comment[j-1] & 0x80))) { if ((unsigned char)(A->g_comment[j]) == 0xb0 && (j == 0 || ! (A->g_comment[j-1] & 0x80))) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n"); dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n");
dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n"); dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
} }
} }
for (j=0; j<n; j++) { for (j=0; j<n; j++) {
if ((unsigned)A->g_comment[j] == (char)0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) { if ((unsigned char)(A->g_comment[j]) == 0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n"); dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n");
dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n"); dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
@ -974,6 +1000,7 @@ N1ZZN-9>T2SP0W:`c_Vm6hk/ "49}Originl Mic-E (leading space)
N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D7A walkie Talkie N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D7A walkie Talkie
N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D72 walkie Talkie= N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D72 walkie Talkie=
W6GPS>S4PT3R:`p(1oR0K\>TH-D74A^
N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D700 MObile Radio N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D700 MObile Radio
N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D710 Mobile Radio= N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D710 Mobile Radio=
@ -1328,12 +1355,14 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'') #define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
// Last updated Sept. 2016 for TH-D74A
if (isT(*pfirst)) { if (isT(*pfirst)) {
if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; }
else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; }
else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; }
@ -1347,19 +1376,23 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
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 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 == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; }
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 == '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 (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; }
else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "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, "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, "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; } else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; }
// Should Original Mic-E and Kenwood be moved down to here?
else if (*pfirst == '`') { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'') { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; plast-=2; }
} }
/* /*
@ -1409,22 +1442,45 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
* *
* Function: aprs_message * Function: aprs_message
* *
* Purpose: Decode "Message Format" * Purpose: Decode "Message Format."
* The word message is used loosely all over the place, but it has a very specific meaning here.
* *
* Inputs: info - Pointer to Information field. * Inputs: info - Pointer to Information field.
* ilen - Information field length. * ilen - Information field length.
* quiet - supress error messages. * quiet - supress error messages.
* *
* Outputs: ??? TBD * Outputs: A->g_msg_type Text description for screen display.
*
* A->g_addressee To whom is it addressed.
* Could be a specific station, alias, bulletin, etc.
* For telemetry metadata is is about this station,
* not being sent to it.
*
* A->g_message_subtype Subtype so caller might avoid replicating
* all the code to distinguish them.
*
* A->g_message_number Message number if any. Required for ack/rej.
* *
* Description: An APRS message is a text string with a specifed addressee. * Description: An APRS message is a text string with a specifed addressee.
* *
* It's a lot more complicated with different types of addressees * It's a lot more complicated with different types of addressees
* and replies with acknowledgement or rejection. * and replies with acknowledgement or rejection.
* *
* There is even a special case for telemetry metadata.
* *
* Examples: ...
* *
* Cases: :xxxxxxxxx:PARM. Telemetry metadata, parameter name
* :xxxxxxxxx:UNIT. Telemetry metadata, unit/label
* :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficents
* :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name
* :xxxxxxxxx:? Directed Station Query
* :xxxxxxxxx:ack Message acknowledged (received)
* :xxxxxxxxx:rej Message rejected (unable to accept)
*
* :xxxxxxxxx: ... Message with no message number.
* (Text may not contain the { character because
* it indicates beginning of optional message number.)
* :xxxxxxxxx: ... {num Message with message number.
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
@ -1436,7 +1492,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
char addressee[9]; char addressee[9];
char colon; /* : */ char colon; /* : */
char message[73]; /* 0-67 characters for message */ char message[73]; /* 0-67 characters for message */
/* { followed by 1-5 characters for message number */ /* Optional { followed by 1-5 characters for message number */
/* If the first chracter is '?' it is a Directed Station Query. */ /* If the first chracter is '?' it is a Directed Station Query. */
} *p; } *p;
@ -1447,20 +1503,23 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
p = (struct aprs_message_s *)info; p = (struct aprs_message_s *)info;
strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type)); strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type));
A->g_message_subtype = message_subtype_message; /* until found otherwise */
if (ilen < 11) { if (ilen < 11) {
if (! quiet) { if (! quiet) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Message must have a minimum of 11 characters for : addressee :\n"); dw_printf("APRS Message must have a minimum of 11 characters for : 9 character addressee :\n");
} }
A->g_message_subtype = message_subtype_invalid;
return; return;
} }
if (p->colon != ':') { if (p->colon != ':') {
if (! quiet) { if (! quiet) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Message must begin with : addressee :\n"); dw_printf("APRS Message must begin with : 9 character addressee :\n");
} }
A->g_message_subtype = message_subtype_invalid;
return; return;
} }
@ -1475,6 +1534,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee)); strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee));
/* /*
* Special message formats contain telemetry metadata. * Special message formats contain telemetry metadata.
* It applies to the addressee, not the sender. * It applies to the addressee, not the sender.
@ -1488,18 +1548,22 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
if (strncmp(p->message,"PARM.",5) == 0) { if (strncmp(p->message,"PARM.",5) == 0) {
snprintf (A->g_msg_type, sizeof(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);
A->g_message_subtype = message_subtype_telem_parm;
telemetry_name_message (addressee, p->message+5); telemetry_name_message (addressee, p->message+5);
} }
else if (strncmp(p->message,"UNIT.",5) == 0) { else if (strncmp(p->message,"UNIT.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee); snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_unit;
telemetry_unit_label_message (addressee, p->message+5); telemetry_unit_label_message (addressee, p->message+5);
} }
else if (strncmp(p->message,"EQNS.",5) == 0) { else if (strncmp(p->message,"EQNS.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee); snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_eqns;
telemetry_coefficents_message (addressee, p->message+5, quiet); telemetry_coefficents_message (addressee, p->message+5, quiet);
} }
else if (strncmp(p->message,"BITS.",5) == 0) { else if (strncmp(p->message,"BITS.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_bits;
telemetry_bit_sense_message (addressee, p->message+5, quiet); telemetry_bit_sense_message (addressee, p->message+5, quiet);
} }
@ -1510,11 +1574,33 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
else if (p->message[0] == '?') { else if (p->message[0] == '?') {
strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type)); strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type));
A->g_message_subtype = message_subtype_directed_query;
aprs_directed_station_query (A, addressee, p->message+1, quiet); aprs_directed_station_query (A, addressee, p->message+1, quiet);
} }
/* ack or rej? Message number is required for these. */
else if (strncmp(p->message,"ack",3) == 0) {
strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "ACK message %s for \"%s\"", A->g_message_number, addressee);
A->g_message_subtype = message_subtype_ack;
}
else if (strncmp(p->message,"rej",3) == 0) {
strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "REJ message %s for \"%s\"", A->g_message_number, addressee);
A->g_message_subtype = message_subtype_ack;
}
/* message number is optional here. */
else { else {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message for \"%s\"", addressee); char *pno = strchr(p->message, '{');
if (pno != NULL) {
strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number));
}
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message %s for \"%s\"", A->g_message_number, addressee);
A->g_message_subtype = message_subtype_message;
/* No location so don't use process_comment () */ /* No location so don't use process_comment () */
@ -1666,25 +1752,27 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen)
{ {
struct aprs_item_s { struct aprs_item_s {
char dti; /* ) */ char dti; /* ')' */
char name[9]; /* Actually variable length 3 - 9 bytes. */ char name[10]; /* Actually variable length 3 - 9 bytes. */
/* DON'T refer to the rest of this structure; */ /* DON'T refer to the rest of this structure; */
/* the offsets will be wrong! */ /* the offsets will be wrong! */
/* We make it 10 here so we don't get subscript out of bounds */
/* warning when looking for following '!' or '_' character. */
char live_killed; /* ! for live or _ for killed */ char live_killed__; /* ! for live or _ for killed */
position_t pos; position_t pos__;
char comment[43]; /* First 7 bytes could be data extension. */ char comment__[43]; /* First 7 bytes could be data extension. */
} *p; } *p;
struct aprs_compressed_item_s { struct aprs_compressed_item_s {
char dti; /* ) */ char dti; /* ')' */
char name[9]; /* Actually variable length 3 - 9 bytes. */ char name[10]; /* Actually variable length 3 - 9 bytes. */
/* DON'T refer to the rest of this structure; */ /* DON'T refer to the rest of this structure; */
/* the offsets will be wrong! */ /* the offsets will be wrong! */
char live_killed; /* ! for live or _ for killed */ char live_killed__; /* ! for live or _ for killed */
compressed_position_t cpos; compressed_position_t cpos__;
char comment[40]; /* No data extension in this case. */ char comment__[40]; /* No data extension in this case. */
} *q; } *q;
@ -3014,7 +3102,7 @@ double get_latitude_8 (char *p, int quiet)
return (G_UNKNOWN); return (G_UNKNOWN);
} }
if (plat->minn[0] >= '0' || plat->minn[0] <= '5') if (plat->minn[0] >= '0' && plat->minn[0] <= '5')
result += ((plat->minn[0]) - '0') * (10. / 60.); result += ((plat->minn[0]) - '0') * (10. / 60.);
else if (plat->minn[0] == ' ') else if (plat->minn[0] == ' ')
; ;
@ -3177,7 +3265,7 @@ double get_longitude_9 (char *p, int quiet)
return (G_UNKNOWN); return (G_UNKNOWN);
} }
if (plon->minn[0] >= '0' || plon->minn[0] <= '5') if (plon->minn[0] >= '0' && plon->minn[0] <= '5')
result += ((plon->minn[0]) - '0') * (10. / 60.); result += ((plon->minn[0]) - '0') * (10. / 60.);
else if (plon->minn[0] == ' ') else if (plon->minn[0] == ' ')
; ;
@ -3621,7 +3709,15 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext)
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#define MAX_TOCALLS 150 // If I was more ambitious, this would dynamically allocate enough
// storage based on the file contents. Just stick in a constant for
// now. This takes an insignificant amount of space and
// I don't anticipate tocalls.txt growing that quickly.
// Version 1.4 - add message if too small instead of silently ignoring the rest.
// Dec. 2016 tocalls.txt has 153 destination addresses.
#define MAX_TOCALLS 200
static struct tocalls_s { static struct tocalls_s {
unsigned char len; unsigned char len;
@ -3718,7 +3814,7 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
if (strlen(tocalls[num_tocalls].prefix) > 2) { if (strlen(tocalls[num_tocalls].prefix) > 2) {
tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].description = strdup(stuff+14);
tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
// dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
num_tocalls++; num_tocalls++;
} }
@ -3742,11 +3838,15 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
if (strlen(tocalls[num_tocalls].prefix) > 2) { if (strlen(tocalls[num_tocalls].prefix) > 2) {
tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].description = strdup(stuff+14);
tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
// dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
num_tocalls++; num_tocalls++;
} }
} }
if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some.
text_color_set(DW_COLOR_ERROR);
dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS);
}
} }
fclose(fp); fclose(fp);
@ -3771,8 +3871,11 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
} }
} }
first_time = 0; first_time = 0;
//for (n=0; n<num_tocalls; n++) {
// dw_printf("sorted %d: %d '%s' -> '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description);
//}
} }
@ -3862,6 +3965,49 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
* *
* /A=123456 Altitude * /A=123456 Altitude
* *
* What can appear in a comment?
*
* Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says:
*
* "The comment may contain any printable ASCII characters (except | and ~,
* which are reserved for TNC channel switching)."
*
* "Printable" would exclude character values less than space (00100000), e.g.
* tab, carriage return, line feed, nul. Sometimes we see carriage return
* (00001010) at the end of APRS packets. This would be in violation of the
* specification.
*
* The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ),
* which is not part of the APRS spec, uses the | character in the comment to delimit encoded
* telemetry data. This would be in violation of the original spec.
*
* The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt)
* adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in
* messages and comments. It can't be used in the fixed width fields.
*
* Non-ASCII characters are represented by multi-byte sequences. All bytes in these
* multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not
* add any nul (00000000) bytes to the stream.
*
* There are two known cases where we can have a nul character value.
*
* * The Kenwood TM-D710A sometimes sends packets like this:
*
* VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast=
* K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV =
*
* Notice that the data type indicator of "4" is not valid. If we remove
* 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format.
* This same thing has been observed from others and is intermittent.
*
* * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes.
* This is wrong. It should be using UTF-8 and I'm not going to accomodate it here.
*
*
* The digipeater and IGate functions should pass along anything exactly the
* we received it, even if it is invalid. If different implementations try to fix it up
* somehow, like changing unprintable characters to spaces, we will only make things
* worse and thwart the duplicate detection.
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
@ -4026,7 +4172,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
* digipeated, causing the comment to be hundreds of characters long. * digipeated, causing the comment to be hundreds of characters long.
*/ */
if (clen > sizeof(A->g_comment) - 1) { if (clen > (int)(sizeof(A->g_comment) - 1)) {
if ( ! A->g_quiet) { if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Comment is extremely long, %d characters.\n", clen); dw_printf("Comment is extremely long, %d characters.\n", clen);

View File

@ -26,7 +26,9 @@ typedef struct decode_aprs_s {
char g_src[AX25_MAX_ADDR_LEN]; char g_src[AX25_MAX_ADDR_LEN];
char g_msg_type[60]; /* Message type. Telemetry descriptions get pretty long. */ char g_msg_type[60]; /* APRS data type. Telemetry descriptions get pretty long. */
/* Putting msg in the name was a poor choice because */
/* "message" has a specific meaning. Rename it someday. */
char g_symbol_table; /* The Symbol Table Identifier character selects one */ char g_symbol_table; /* The Symbol Table Identifier character selects one */
/* of the two Symbol Tables, or it may be used as */ /* of the two Symbol Tables, or it may be used as */
@ -64,6 +66,19 @@ typedef struct decode_aprs_s {
/* Also for Directed Station Query which is a */ /* Also for Directed Station Query which is a */
/* special case of message. */ /* special case of message. */
enum message_subtype_e { message_subtype_invalid = 0,
message_subtype_message,
message_subtype_ack,
message_subtype_rej,
message_subtype_telem_parm,
message_subtype_telem_unit,
message_subtype_telem_eqns,
message_subtype_telem_bits,
message_subtype_directed_query
} g_message_subtype; /* Various cases of the overloaded "message." */
char g_message_number[8]; /* Message number. Should be 1 - 5 characters if used. */
float g_speed_mph; /* Speed in MPH. */ float g_speed_mph; /* Speed in MPH. */
float g_course; /* 0 = North, 90 = East, etc. */ float g_course; /* 0 = North, 90 = East, etc. */

View File

@ -96,6 +96,7 @@
#define DEDUPE_C #define DEDUPE_C
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>

253
demod.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -18,14 +18,6 @@
// //
// #define DEBUG1 1 /* display debugging info */
// #define DEBUG3 1 /* print carrier detect changes. */
// #define DEBUG4 1 /* capture AFSK demodulator output to log files */
// #define DEBUG5 1 /* capture 9600 output to log files */
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
@ -39,6 +31,7 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -49,7 +42,6 @@
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "demod.h" #include "demod.h"
#include "tune.h" #include "tune.h"
@ -60,6 +52,7 @@
#include "textcolor.h" #include "textcolor.h"
#include "demod_9600.h" #include "demod_9600.h"
#include "demod_afsk.h" #include "demod_afsk.h"
#include "demod_psk.h"
@ -68,13 +61,16 @@
static struct audio_s *save_audio_config_p; static struct audio_s *save_audio_config_p;
// TODO: temp experiment.
static int upsample = 2; // temp experiment.
static int zerostuff = 1; // temp experiment.
// Current state of all the decoders. // Current state of all the decoders.
static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS]; static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS];
#define UPSAMPLE 2
static int sample_sum[MAX_CHANS][MAX_SUBCHANS]; static int sample_sum[MAX_CHANS][MAX_SUBCHANS];
static int sample_count[MAX_CHANS][MAX_SUBCHANS]; static int sample_count[MAX_CHANS][MAX_SUBCHANS];
@ -188,7 +184,7 @@ int demod_init (struct audio_s *pa)
} }
} }
assert (num_letters == strlen(just_letters)); assert (num_letters == (int)(strlen(just_letters)));
/* /*
* Pick a good default demodulator if none specified. * Pick a good default demodulator if none specified.
@ -225,7 +221,8 @@ int demod_init (struct audio_s *pa)
num_letters = 1; num_letters = 1;
} }
assert (num_letters == strlen(just_letters));
assert (num_letters == (int)(strlen(just_letters)));
/* /*
* Put it back together again. * Put it back together again.
@ -403,11 +400,11 @@ int demod_init (struct audio_s *pa)
D->num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS;
} }
/* For siginal level reporting, we want a longer term view. */ /* For signal level reporting, we want a longer term view. */
// TODO: Should probably move this into the init functions. // TODO: Should probably move this into the init functions.
D->quick_attack = D->agc_fast_attack * 0.2; D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2f;
} }
} }
else if (have_plus) { else if (have_plus) {
@ -459,10 +456,10 @@ int demod_init (struct audio_s *pa)
D->num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS;
} }
/* For siginal level reporting, we want a longer term view. */ /* For signal level reporting, we want a longer term view. */
D->quick_attack = D->agc_fast_attack * 0.2; D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2f;
} }
else { else {
int d; int d;
@ -512,15 +509,123 @@ int demod_init (struct audio_s *pa)
D->num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS;
} }
/* For siginal level reporting, we want a longer term view. */ /* For signal level reporting, we want a longer term view. */
D->quick_attack = D->agc_fast_attack * 0.2; D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2f;
} /* for each freq pair */ } /* for each freq pair */
} }
break; break;
case MODEM_QPSK: // New for 1.4
// TODO: See how much CPU this takes on ARM and decide if we should have different defaults.
if (strlen(save_audio_config_p->achan[chan].profiles) == 0) {
//#if __arm__
// strlcpy (save_audio_config_p->achan[chan].profiles, "R", sizeof(save_audio_config_p->achan[chan].profiles));
//#else
strlcpy (save_audio_config_p->achan[chan].profiles, "PQRS", sizeof(save_audio_config_p->achan[chan].profiles));
//#endif
}
save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles);
save_audio_config_p->achan[chan].decimate = 1; // think about this later.
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d bps, QPSK, %s, %d sample rate",
chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
if (save_audio_config_p->achan[chan].decimate != 1)
dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled");
dw_printf (".\n");
int d;
for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
assert (d >= 0 && d < MAX_SUBCHANS);
struct demodulator_state_s *D;
D = &demodulator_state[chan][d];
profile = save_audio_config_p->achan[chan].profiles[d];
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("About to call demod_psk_init for Q-PSK case, modem_type=%d, profile='%c'\n",
// save_audio_config_p->achan[chan].modem_type, profile);
demod_psk_init (save_audio_config_p->achan[chan].modem_type,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
save_audio_config_p->achan[chan].baud,
profile,
D);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Returned from demod_psk_init\n");
/* For signal level reporting, we want a longer term view. */
/* Guesses based on 9600. Maybe revisit someday. */
D->quick_attack = 0.080 * 0.2;
D->sluggish_decay = 0.00012 * 0.2;
}
break;
case MODEM_8PSK: // New for 1.4
// TODO: See how much CPU this takes on ARM and decide if we should have different defaults.
if (strlen(save_audio_config_p->achan[chan].profiles) == 0) {
//#if __arm__
// strlcpy (save_audio_config_p->achan[chan].profiles, "V", sizeof(save_audio_config_p->achan[chan].profiles));
//#else
strlcpy (save_audio_config_p->achan[chan].profiles, "TUVW", sizeof(save_audio_config_p->achan[chan].profiles));
//#endif
}
save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles);
save_audio_config_p->achan[chan].decimate = 1; // think about this later
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d bps, 8PSK, %s, %d sample rate",
chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
if (save_audio_config_p->achan[chan].decimate != 1)
dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled");
dw_printf (".\n");
//int d;
for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
assert (d >= 0 && d < MAX_SUBCHANS);
struct demodulator_state_s *D;
D = &demodulator_state[chan][d];
profile = save_audio_config_p->achan[chan].profiles[d];
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("About to call demod_psk_init for 8-PSK case, modem_type=%d, profile='%c'\n",
// save_audio_config_p->achan[chan].modem_type, profile);
demod_psk_init (save_audio_config_p->achan[chan].modem_type,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
save_audio_config_p->achan[chan].baud,
profile,
D);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Returned from demod_psk_init\n");
/* For signal level reporting, we want a longer term view. */
/* Guesses based on 9600. Maybe revisit someday. */
D->quick_attack = 0.080 * 0.2;
D->sluggish_decay = 0.00012 * 0.2;
}
break;
//TODO: how about MODEM_OFF case? //TODO: how about MODEM_OFF case?
case MODEM_BASEBAND: case MODEM_BASEBAND:
@ -539,11 +644,20 @@ int demod_init (struct audio_s *pa)
#endif #endif
} }
#ifdef TUNE_UPSAMPLE
upsample = TUNE_UPSAMPLE;
#endif
#ifdef TUNE_ZEROSTUFF
zerostuff = TUNE_ZEROSTUFF;
#endif
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d", dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d",
chan, save_audio_config_p->achan[chan].baud, chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].profiles, save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, UPSAMPLE); save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, upsample);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled"); dw_printf (", DTMF decoder enabled");
dw_printf (".\n"); dw_printf (".\n");
@ -562,7 +676,36 @@ int demod_init (struct audio_s *pa)
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS; save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
} }
demod_9600_init (UPSAMPLE * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D);
/* We need a minimum number of audio samples per bit time for good performance. */
/* Easier to check here because demod_9600_init might have an adjusted sample rate. */
float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec)
/ (float)(save_audio_config_p->achan[chan].baud);
text_color_set(DW_COLOR_INFO);
dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n",
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
save_audio_config_p->achan[chan].baud,
(double)ratio);
if (ratio < 3) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("There is little hope of success with such a low ratio. Use a higher sample rate.\n");
}
else if (ratio < 5) {
dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n");
}
else if (ratio < 6) {
dw_printf ("Increasing the sample rate should improve decoder performance.\n");
}
else if (ratio > 15) {
dw_printf ("Sample rate is more than adequate. You might lower it if CPU load is a concern.\n");
}
else {
dw_printf ("This is a suitable ratio for good performance.\n");
}
demod_9600_init (upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D);
if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
@ -573,10 +716,10 @@ int demod_init (struct audio_s *pa)
D->num_slicers = MAX_SLICERS; D->num_slicers = MAX_SLICERS;
} }
/* For siginal level reporting, we want a longer term view. */ /* For signal level reporting, we want a longer term view. */
D->quick_attack = D->agc_fast_attack * 0.2; D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2f;
} }
break; break;
@ -699,17 +842,9 @@ __attribute__((hot))
void demod_process_sample (int chan, int subchan, int sam) void demod_process_sample (int chan, int subchan, int sam)
{ {
float fsam; float fsam;
//float abs_fsam;
int k; int k;
#if DEBUG4
static FILE *demod_log_fp = NULL;
static int seq = 0; /* for log file name */
#endif
//int j;
//int demod_data;
struct demodulator_state_s *D; struct demodulator_state_s *D;
assert (chan >= 0 && chan < MAX_CHANS); assert (chan >= 0 && chan < MAX_CHANS);
@ -778,30 +913,44 @@ void demod_process_sample (int chan, int subchan, int sam)
} }
break; break;
case MODEM_QPSK:
case MODEM_8PSK:
if (save_audio_config_p->achan[chan].decimate > 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid combination of options. Exiting.\n");
// Would probably work but haven't thought about it or tested yet.
exit (1);
}
else {
demod_psk_process_sample (chan, subchan, sam, D);
}
break;
case MODEM_BASEBAND: case MODEM_BASEBAND:
case MODEM_SCRAMBLE: case MODEM_SCRAMBLE:
default: default:
#define ZEROSTUFF 1 if (zerostuff) {
#if ZEROSTUFF
/* Literature says this is better if followed */ /* Literature says this is better if followed */
/* by appropriate low pass filter. */ /* by appropriate low pass filter. */
/* So far, both are same in tests with different */ /* So far, both are same in tests with different */
/* optimal low pass filter parameters. */ /* optimal low pass filter parameters. */
for (k=1; k<UPSAMPLE; k++) { for (k=1; k<upsample; k++) {
demod_9600_process_sample (chan, 0, D); demod_9600_process_sample (chan, 0, D);
} }
demod_9600_process_sample (chan, sam*UPSAMPLE, D); demod_9600_process_sample (chan, sam * upsample, D);
#else }
else {
/* Linear interpolation. */ /* Linear interpolation. */
static int prev_sam; static int prev_sam;
switch (UPSAMPLE) {
case 1:
demod_9600_process_sample (chan, sam);
switch (upsample) {
case 1:
demod_9600_process_sample (chan, sam, D);
break; break;
case 2: case 2:
demod_9600_process_sample (chan, (prev_sam + sam) / 2, D); demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
@ -823,9 +972,10 @@ void demod_process_sample (int chan, int subchan, int sam)
break; break;
} }
prev_sam = sam; prev_sam = sam;
#endif
break;
} }
break;
} /* switch modem_type */
return; return;
} /* end demod_process_sample */ } /* end demod_process_sample */
@ -868,8 +1018,11 @@ alevel_t demod_get_audio_level (int chan, int subchan)
alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f); alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f);
alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f); alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f);
}
//alevel.ms_ratio = D->alevel_mark_peak / D->alevel_space_peak; // TODO: remove after temp test else if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK ||
save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) {
alevel.mark = -1;
alevel.space = -1;
} }
else { else {

View File

@ -18,7 +18,7 @@
// //
// #define DEBUG5 1 /* capture 9600 output to log files */ //#define DEBUG4 1 /* capture 9600 output to log files */
/*------------------------------------------------------------------ /*------------------------------------------------------------------
@ -33,6 +33,8 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
@ -42,7 +44,6 @@
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include "direwolf.h"
#include "tune.h" #include "tune.h"
#include "fsk_demod_state.h" #include "fsk_demod_state.h"
#include "hdlc_rec.h" #include "hdlc_rec.h"
@ -72,25 +73,10 @@ static inline float convolve (const float *__restrict__ data, const float *__res
float sum = 0.0f; float sum = 0.0f;
int j; int j;
#if 0 //#pragma GCC ivdep // ignored until gcc 4.9
// As suggested here, http://locklessinc.com/articles/vectorize/
// Unfortunately, older compilers don't recognize it.
// Get more information by using -ftree-vectorizer-verbose=5
float *d = __builtin_assume_aligned(data, 16);
float *f = __builtin_assume_aligned(filter, 16);
#pragma GCC ivdep
for (j=0; j<filter_size; j++) {
sum += f[j] * d[j];
}
#else
#pragma GCC ivdep // ignored until gcc 4.9
for (j=0; j<filter_size; j++) { for (j=0; j<filter_size; j++) {
sum += filter[j] * data[j]; sum += filter[j] * data[j];
} }
#endif
return (sum); return (sum);
} }
@ -101,21 +87,21 @@ __attribute__((hot)) __attribute__((always_inline))
static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley) static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
{ {
if (in >= *ppeak) { if (in >= *ppeak) {
*ppeak = in * fast_attack + *ppeak * (1. - fast_attack); *ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
} }
else { else {
*ppeak = in * slow_decay + *ppeak * (1. - slow_decay); *ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
} }
if (in <= *pvalley) { if (in <= *pvalley) {
*pvalley = in * fast_attack + *pvalley * (1. - fast_attack); *pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
} }
else { else {
*pvalley = in * slow_decay + *pvalley * (1. - slow_decay); *pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
} }
if (*ppeak > *pvalley) { if (*ppeak > *pvalley) {
return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
} }
return (0.0); return (0.0);
} }
@ -125,7 +111,7 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
* *
* Name: demod_9600_init * Name: demod_9600_init
* *
* Purpose: Initialize the 9600 baud demodulator. * Purpose: Initialize the 9600 (or higher) baud demodulator.
* *
* Inputs: samples_per_sec - Number of samples per second. * Inputs: samples_per_sec - Number of samples per second.
* Might be upsampled in hopes of * Might be upsampled in hopes of
@ -147,21 +133,33 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s
memset (D, 0, sizeof(struct demodulator_state_s)); memset (D, 0, sizeof(struct demodulator_state_s));
D->num_slicers = 1; D->num_slicers = 1;
//dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud); // Multiple profiles in future?
D->pll_step_per_sample = // switch (profile) {
(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
// case 'J': // upsample x2 with filtering.
// case 'K': // upsample x3 with filtering.
// case 'L': // upsample x4 with filtering.
D->lp_filter_len_bits = 76 * 9600.0 / (44100.0 * 2.0);
// Works best with odd number in some tests. Even is better in others.
//D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ))) * 2 + 1;
D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f);
D->lp_filter_len_bits = 72 * 9600.0 / (44100.0 * 2.0);
D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5);
D->lp_window = BP_WINDOW_HAMMING; D->lp_window = BP_WINDOW_HAMMING;
D->lpf_baud = 0.59; D->lpf_baud = 0.62;
D->agc_fast_attack = 0.080; D->agc_fast_attack = 0.080;
D->agc_slow_decay = 0.00012; D->agc_slow_decay = 0.00012;
D->pll_locked_inertia = 0.88; D->pll_locked_inertia = 0.89;
D->pll_searching_inertia = 0.67; D->pll_searching_inertia = 0.67;
// break;
// }
D->pll_step_per_sample =
(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
#ifdef TUNE_LP_WINDOW #ifdef TUNE_LP_WINDOW
@ -184,8 +182,11 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s
D->agc_slow_decay = TUNE_AGC_SLOW; D->agc_slow_decay = TUNE_AGC_SLOW;
#endif #endif
#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING) #if defined(TUNE_PLL_LOCKED)
D->pll_locked_inertia = TUNE_PLL_LOCKED; D->pll_locked_inertia = TUNE_PLL_LOCKED;
#endif
#if defined(TUNE_PLL_SEARCHING)
D->pll_searching_inertia = TUNE_PLL_SEARCHING; D->pll_searching_inertia = TUNE_PLL_SEARCHING;
#endif #endif
@ -198,7 +199,7 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s
/* Version 1.2: Experiment with different slicing levels. */ /* Version 1.2: Experiment with different slicing levels. */
for (j = 0; j < MAX_SUBCHANS; j++) { for (j = 0; j < MAX_SUBCHANS; j++) {
slice_point[j] = 0.02 * (j - 0.5 * (MAX_SUBCHANS-1)); slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS-1));
//dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]); //dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]);
} }
@ -260,23 +261,22 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); inline static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D);
__attribute__((hot)) __attribute__((hot))
void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D) void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D)
{ {
float fsam; float fsam;
//float abs_fsam;
float amp; float amp;
float demod_out; float demod_out;
#if DEBUG5 #if DEBUG4
static FILE *demod_log_fp = NULL; static FILE *demod_log_fp = NULL;
static int seq = 0; /* for log file name */ static int log_file_seq = 0; /* Part of log file name */
#endif #endif
//int j;
int subchan = 0; int subchan = 0;
int demod_data; /* Still scrambled. */ int demod_data; /* Still scrambled. */
@ -306,6 +306,12 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
fsam = sam / 16384.0; fsam = sam / 16384.0;
#if defined(TUNE_ZEROSTUFF) && TUNE_ZEROSTUFF == 0
// experiment - no filtering.
amp = fsam;
#else
push_sample (fsam, D->raw_cb, D->lp_filter_size); push_sample (fsam, D->raw_cb, D->lp_filter_size);
/* /*
@ -313,7 +319,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
*/ */
amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size); amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size);
#endif
/* /*
* Version 1.2: Capture the post-filtering amplitude for display. * Version 1.2: Capture the post-filtering amplitude for display.
@ -324,18 +330,20 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
* Here we keep + and - peaks because there could be a DC bias. * Here we keep + and - peaks because there could be a DC bias.
*/ */
// TODO: probably no need for this. Just use D->m_peak, D->m_valley
if (amp >= D->alevel_mark_peak) { if (amp >= D->alevel_mark_peak) {
D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1. - D->quick_attack); D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
} }
else { else {
D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1. - D->sluggish_decay); D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
} }
if (amp <= D->alevel_space_peak) { if (amp <= D->alevel_space_peak) {
D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1. - D->quick_attack); D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
} }
else { else {
D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1. - D->sluggish_decay); D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
} }
/* /*
@ -349,28 +357,10 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley)); demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
// TODO: There is potential for multiple decoders with one filter. // TODO: There is potential for multiple decoders with one filter.
//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm); //dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm);
/* Throw in a little Hysteresis??? */
/* (Not to be confused with Hysteria.) */
/* Doesn't seem to have any value. */
/* Using a level of .02 makes things worse. */
/* Might want to experiment with this again someday. */
// if (demod_out > 0.03) {
// demod_data = 1;
// }
// else if (demod_out < -0.03) {
// demod_data = 0;
// }
// else {
// demod_data = D->slicer[subchan].prev_demod_data;
// }
if (D->num_slicers <= 1) { if (D->num_slicers <= 1) {
/* Normal case of one demodulator to one HDLC decoder. */ /* Normal case of one demodulator to one HDLC decoder. */
@ -378,7 +368,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
/* AGC should generally keep this around -1 to +1 range. */ /* AGC should generally keep this around -1 to +1 range. */
demod_data = demod_out > 0; demod_data = demod_out > 0;
nudge_pll (chan, subchan, 0, demod_data, D); nudge_pll (chan, subchan, 0, demod_out, D);
} }
else { else {
int slice; int slice;
@ -386,20 +376,82 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
/* Multiple slicers each feeding its own HDLC decoder. */ /* Multiple slicers each feeding its own HDLC decoder. */
for (slice=0; slice<D->num_slicers; slice++) { for (slice=0; slice<D->num_slicers; slice++) {
demod_data = demod_out > slice_point[slice]; demod_data = demod_out - slice_point[slice] > 0;
nudge_pll (chan, subchan, slice, demod_data, D); nudge_pll (chan, subchan, slice, demod_out - slice_point[slice], D);
} }
} }
// demod_data is used only for debug out.
// suppress compiler warning about it not being used.
(void) demod_data;
#if DEBUG4
if (chan == 0) {
if (1) {
//if (hdlc_rec_gathering (chan, subchan, slice)) {
char fname[30];
int slice = 0;
if (demod_log_fp == NULL) {
log_file_seq++;
snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq);
//if (log_file_seq == 1) mkdir ("demod", 0777);
if (log_file_seq == 1) mkdir ("demod");
demod_log_fp = fopen (fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Starting demodulator log file %s\n", fname);
fprintf (demod_log_fp, "Audio, Filtered, Max, Min, Normalized, Sliced, Clock\n");
}
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n",
fsam + 6,
amp + 4,
D->m_peak + 4,
D->m_valley + 4,
demod_out + 2,
demod_data + 2,
(D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0);
fflush (demod_log_fp);
}
else {
if (demod_log_fp != NULL) {
fclose (demod_log_fp);
demod_log_fp = NULL;
}
}
}
#endif
} /* end demod_9600_process_sample */ } /* end demod_9600_process_sample */
__attribute__((hot)) /*-------------------------------------------------------------------
static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) *
{ * Name: nudge_pll
*
/* * Purpose: Update the PLL state for each audio sample.
* Next, a PLL is used to sample near the centers of the data bits. *
* (2) Descramble it.
* (2) Recover clock and data.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
*
* subchan - Which demodulator. We could have several running in parallel.
*
* slice - Determines which Slicing level & HDLC decoder to use.
*
* demod_out_f - Demodulator output, possibly shifted by slicing level
* It will be compared with 0.0 to bit binary value out.
*
* D - Demodulator state for this channel / subchannel.
*
* Returns: None
*
* Descripton: A PLL is used to sample near the centers of the data bits.
* *
* D->data_clock_pll is a SIGNED 32 bit variable. * D->data_clock_pll is a SIGNED 32 bit variable.
* When it overflows from a large positive value to a negative value, we * When it overflows from a large positive value to a negative value, we
@ -420,42 +472,51 @@ static void inline nudge_pll (int chan, int subchan, int slice, int demod_data,
* I don't think the optimal value will depend on the audio sample rate * I don't think the optimal value will depend on the audio sample rate
* because this happens for each transition from the demodulator. * because this happens for each transition from the demodulator.
* *
* This was optimized for 1200 baud AFSK. There might be some opportunity * Version 1.4: Previously, we would always pull the PLL phase toward 0 after
* for improvement here. * after a zero crossing was detetected. This adds extra jitter,
* especially when the ratio of audio sample rate to baud is low.
* Now, we interpolate between the two samples to get an estimate
* on when the zero crossing happened. The PLL is pulled toward
* this point.
*
* Results??? TBD
*
*--------------------------------------------------------------------*/
__attribute__((hot))
inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D)
{
/*
*/ */
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample; D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { if ( D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) {
/* Overflow. */ /* Overflow. Was large positive, wrapped around, now large negative. */
/* hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr);
* At this point, we need to descramble the data as
* in hardware based designs by G3RUH and K9NG.
*
* Future Idea: allow unscrambled baseband data.
*
* http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif
*/
// Warning: 'descram' set but not used.
// It's used in conditional debug code below.
// descram =
descramble (demod_data, &(D->slicer[slice].lfsr));
hdlc_rec_bit (chan, subchan, slice, demod_data, 1, D->slicer[slice].lfsr);
} }
if (demod_data != D->slicer[slice].prev_demod_data) { /*
* Zero crossing?
*/
if ((D->slicer[slice].prev_demod_out_f < 0 && demod_out_f > 0) ||
(D->slicer[slice].prev_demod_out_f > 0 && demod_out_f < 0)) {
// Note: Test for this demodulator, not overall for channel. // Note: Test for this demodulator, not overall for channel.
float target = 0;
target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f);
if (hdlc_rec_gathering (chan, subchan, slice)) { if (hdlc_rec_gathering (chan, subchan, slice)) {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia) );
} }
else { else {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia) );
} }
} }
@ -483,10 +544,10 @@ static void inline nudge_pll (int chan, int subchan, int slice, int demod_data,
fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n"); fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n");
} }
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n",
0.5 * fsam + 3.5, 0.5f * fsam + 3.5,
0.5 * D->m_peak + 3.5, 0.5f * D->m_peak + 3.5,
0.5 * D->m_valley + 3.5, 0.5f * D->m_valley + 3.5,
0.5 * demod_out + 2.0, 0.5f * demod_out + 2.0,
demod_data ? 1.35 : 1.0, demod_data ? 1.35 : 1.0,
descram ? .9 : .55, descram ? .9 : .55,
(D->data_clock_pll & 0x80000000) ? .1 : .45); (D->data_clock_pll & 0x80000000) ? .1 : .45);
@ -506,12 +567,9 @@ static void inline nudge_pll (int chan, int subchan, int slice, int demod_data,
* Remember demodulator output (pre-descrambling) so we can compare next time * Remember demodulator output (pre-descrambling) so we can compare next time
* for the DPLL sync. * for the DPLL sync.
*/ */
D->slicer[slice].prev_demod_data = demod_data; D->slicer[slice].prev_demod_out_f = demod_out_f;
} /* end nudge_pll */ } /* end nudge_pll */
/* end demod_9600.c */ /* end demod_9600.c */

View File

@ -39,7 +39,7 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -50,9 +50,7 @@
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "tune.h" #include "tune.h"
#include "fsk_demod_state.h" #include "fsk_demod_state.h"
#include "fsk_gen_filter.h" #include "fsk_gen_filter.h"
@ -65,7 +63,7 @@
#define MAX(a,b) ((a)>(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b))
#ifndef GEN_FFF
/* Quick approximation to sqrt(x*x+y*y) */ /* Quick approximation to sqrt(x*x+y*y) */
/* No benefit for regular PC. */ /* No benefit for regular PC. */
@ -105,7 +103,7 @@ static inline float convolve (const float *__restrict__ data, const float *__res
int j; int j;
#pragma GCC ivdep // ignored until gcc 4.9 //#pragma GCC ivdep // ignored until gcc 4.9
for (j=0; j<filter_size; j++) { for (j=0; j<filter_size; j++) {
sum += filter[j] * data[j]; sum += filter[j] * data[j];
} }
@ -139,6 +137,8 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
return (0.0f); return (0.0f);
} }
#endif // ifndef GEN_FFF
/* /*
* for multi-slicer experiment. * for multi-slicer experiment.
@ -530,7 +530,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
for (j=0; j<D->ms_filter_size; j++) { for (j=0; j<D->ms_filter_size; j++) {
float am; float am;
float center; float center;
float shape = 1; /* Shape is an attempt to smooth out the */ float shape = 1.0f; /* Shape is an attempt to smooth out the */
/* abrupt edges in hopes of reducing */ /* abrupt edges in hopes of reducing */
/* overshoot and ringing. */ /* overshoot and ringing. */
/* My first thought was to use a cosine shape. */ /* My first thought was to use a cosine shape. */
@ -538,16 +538,16 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
/* windows mentioned in the literature. */ /* windows mentioned in the literature. */
/* http://en.wikipedia.org/wiki/Window_function */ /* http://en.wikipedia.org/wiki/Window_function */
center = 0.5 * (D->ms_filter_size - 1); center = 0.5f * (D->ms_filter_size - 1);
am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI); am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2.0f * (float)M_PI);
shape = window (D->ms_window, D->ms_filter_size, j); shape = window (D->ms_window, D->ms_filter_size, j);
D->m_sin_table[j] = sin(am) * shape; D->m_sin_table[j] = sinf(am) * shape;
D->m_cos_table[j] = cos(am) * shape; D->m_cos_table[j] = cosf(am) * shape;
Gs += D->m_sin_table[j] * sin(am); Gs += D->m_sin_table[j] * sinf(am);
Gc += D->m_cos_table[j] * cos(am); Gc += D->m_cos_table[j] * cosf(am);
#if DEBUG1 #if DEBUG1
dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ; dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ;
@ -578,18 +578,18 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
for (j=0; j<D->ms_filter_size; j++) { for (j=0; j<D->ms_filter_size; j++) {
float as; float as;
float center; float center;
float shape = 1; float shape = 1.0f;
center = 0.5 * (D->ms_filter_size - 1); center = 0.5 * (D->ms_filter_size - 1);
as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI); as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI);
shape = window (D->ms_window, D->ms_filter_size, j); shape = window (D->ms_window, D->ms_filter_size, j);
D->s_sin_table[j] = sin(as) * shape; D->s_sin_table[j] = sinf(as) * shape;
D->s_cos_table[j] = cos(as) * shape; D->s_cos_table[j] = cosf(as) * shape;
Gs += D->s_sin_table[j] * sin(as); Gs += D->s_sin_table[j] * sinf(as);
Gc += D->s_cos_table[j] * cos(as); Gc += D->s_cos_table[j] * cosf(as);
#if DEBUG1 #if DEBUG1
dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ; dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ;
@ -756,7 +756,7 @@ int main (void)
emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table); emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table);
emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table); emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table);
exit(0); exit(EXIT_SUCCESS);
} }
#endif #endif
@ -808,7 +808,7 @@ int main (void)
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D); inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D);
__attribute__((hot)) __attribute__((hot))
void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D) void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D)
@ -1088,7 +1088,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat
__attribute__((hot)) __attribute__((hot))
static void inline nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D) inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D)
{ {
/* /*

856
demod_psk.c Normal file
View File

@ -0,0 +1,856 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
//#define DEBUG1 1 /* display debugging info */
//#define DEBUG3 1 /* print carrier detect changes. */
//#define DEBUG4 1 /* capture PSK demodulator output to log files */
/*------------------------------------------------------------------
*
* Module: demod_psk.c
*
* Purpose: Demodulator for Phase Shift Keying (PSK).
*
* This is my initial attempt at implementing a 2400 bps mode.
* The MFJ-2400 & AEA PK232-2400 used V.26 / Bell 201 so I will follow that precedent.
*
*
* Input: Audio samples from either a file or the "sound card."
*
* Outputs: Calls hdlc_rec_bit() for each bit demodulated.
*
* Current Status: New for Version 1.4.
*
* Don't know if this is correct and/or compatible with
* other implementations.
* There is a lot of stuff going on here with phase
* shifting, gray code, bit order for the dibit, NRZI and
* bit-stuffing for HDLC. Plenty of opportunity for
* misinterpreting a protocol spec or just stupid mistakes.
*
* References: MFJ-2400 Product description and manual:
*
* http://www.mfjenterprises.com/Product.php?productid=MFJ-2400
* http://www.mfjenterprises.com/Downloads/index.php?productid=MFJ-2400&filename=MFJ-2400.pdf&company=mfj
*
* AEA had a 2400 bps packet modem, PK232-2400.
*
* http://www.repeater-builder.com/aea/pk232/pk232-2400-baud-dpsk-modem.pdf
*
* There was also a Kantronics KPC-2400 that had 2400 bps.
*
* http://www.brazoriacountyares.org/winlink-collection/TNC%20manuals/Kantronics/2400_modem_operators_guide@rgf.pdf
*
*
* The MFJ and AEA both use the EXAR XR-2123 PSK modem chip.
* The Kantronics has a P423 ???
*
* Can't find the chip specs on the EXAR website so Google it.
*
* http://www.komponenten.es.aau.dk/fileadmin/komponenten/Data_Sheet/Linear/XR2123.pdf
*
* The XR-2123 implements the V.26 / Bell 201 standard:
*
* https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26-198811-I!!PDF-E&type=items
* https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26bis-198811-I!!PDF-E&type=items
* https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.26ter-198811-I!!PDF-E&type=items
*
* "bis" and "ter" are from Latin for second and third.
* I used the "ter" version which has phase shifts of 0, 90, 180, and 270 degrees.
*
* There are other references to an alternative B which uses other multiples of 45.
* The XR-2123 data sheet mentions only multiples of 90. That's what I went with.
*
* The XR-2123 does not perform the scrambling as specified in V.26 so I wonder if
* the vendors implemented it in software or just left it out.
* I left out scrambling for now. Eventually, I'd like to get my hands on an old
* 2400 bps TNC for compatibility testing.
*
* After getting QPSK working, it was not much more effort to add V.27 with 8 phases.
*
* https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27bis-198811-I!!PDF-E&type=items
* https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27ter-198811-I!!PDF-E&type=items
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "audio.h"
#include "tune.h"
#include "fsk_demod_state.h"
#include "fsk_gen_filter.h"
#include "hdlc_rec.h"
#include "textcolor.h"
#include "demod_psk.h"
#include "dsp.h"
/* Add sample to buffer and shift the rest down. */
__attribute__((hot)) __attribute__((always_inline))
static inline void push_sample (float val, float *buff, int size)
{
memmove(buff+1,buff,(size-1)*sizeof(float));
buff[0] = val;
}
/* FIR filter kernel. */
__attribute__((hot)) __attribute__((always_inline))
static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
{
float sum = 0.0;
int j;
for (j=0; j<filter_size; j++) {
sum += filter[j] * data[j];
}
return (sum);
}
/* Might replace this with faster, lower precision version someday. */
static inline float my_atan2f (float y, float x)
{
if ( y == 0 && x == 0) return (0.0); // different atan2 implementations behave differently.
return (atan2f(y,x));
}
/*------------------------------------------------------------------
*
* Name: demod_psk_init
*
* Purpose: Initialization for an psk demodulator.
* Select appropriate parameters and set up filters.
*
* Inputs: modem_type - MODEM_QPSK or MODEM_8PSK.
*
* samples_per_sec - Audio sample rate.
*
* bps - Bits per second.
* Should be 2400 for V.26 but we don't enforce it.
* The carrier frequency will be proportional.
*
* profile - Select different variations. For QPSK:
*
* P - Using self-correlation technique.
* Q - Same preceded by bandpass filter.
* R - Using local oscillator to derive phase.
* S - Same with bandpass filter.
*
* For 8-PSK:
*
* T, U, V, W same as above.
*
* D - Pointer to demodulator state for given channel.
*
* Outputs: D->ms_filter_size
*
* Returns: None.
*
* Bugs: This doesn't do much error checking so don't give it
* anything crazy.
*
*----------------------------------------------------------------*/
void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D)
{
int correct_baud; // baud is not same as bits/sec here!
int carrier_freq;
int j;
memset (D, 0, sizeof(struct demodulator_state_s));
D->modem_type = modem_type;
D->num_slicers = 1; // Haven't thought about this yet. Is it even applicable?
#ifdef TUNE_PROFILE
profile = TUNE_PROFILE;
#endif
if (modem_type == MODEM_QPSK) {
correct_baud = bps / 2;
// Originally I thought of scaling it to the data rate,
// e.g. 2400 bps -> 1800 Hz, but decided to make it a
// constant since it is the same for V.26 and V.27.
carrier_freq = 1800;
#if DEBUG1
dw_printf ("demod_psk_init QPSK (sample rate=%d, bps=%d, baud=%d, carrier=%d, profile=%c\n",
samples_per_sec, bps, correct_baud, carrier_freq, profile);
#endif
switch (toupper(profile)) {
case 'P': /* Self correlation technique. */
D->use_prefilter = 0; /* No bandpass filter. */
D->lpf_baud = 0.60;
D->lp_filter_len_bits = 39. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.95;
D->pll_searching_inertia = 0.50;
break;
case 'Q': /* Self correlation technique. */
D->use_prefilter = 1; /* Add a bandpass filter. */
D->prefilter_baud = 1.3;
D->pre_filter_len_bits = 55. * 1200. / 44100.;
D->pre_window = BP_WINDOW_COSINE;
D->lpf_baud = 0.60;
D->lp_filter_len_bits = 39. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.87;
D->pll_searching_inertia = 0.50;
break;
default:
text_color_set (DW_COLOR_ERROR);
dw_printf ("Invalid demodulator profile %c for v.26 QPSK. Valid choices are P, Q, R, S. Using default.\n", profile);
// fall thru.
case 'R': /* Mix with local oscillator. */
D->psk_use_lo = 1;
D->use_prefilter = 0; /* No bandpass filter. */
D->lpf_baud = 0.70;
D->lp_filter_len_bits = 37. * 1200. / 44100.;
D->lp_window = BP_WINDOW_TRUNCATED;
D->pll_locked_inertia = 0.925;
D->pll_searching_inertia = 0.50;
break;
case 'S': /* Mix with local oscillator. */
D->psk_use_lo = 1;
D->use_prefilter = 1; /* Add a bandpass filter. */
D->prefilter_baud = 0.55;
D->pre_filter_len_bits = 74. * 1200. / 44100.;
D->pre_window = BP_WINDOW_FLATTOP;
D->lpf_baud = 0.60;
D->lp_filter_len_bits = 39. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.925;
D->pll_searching_inertia = 0.50;
break;
}
D->ms_filter_len_bits = 1.25; // Delay line > 13/12 * symbol period
D->coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)correct_baud );
D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud );
D->soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)correct_baud );
}
else {
correct_baud = bps / 3;
carrier_freq = 1800;
#if DEBUG1
dw_printf ("demod_psk_init 8-PSK (sample rate=%d, bps=%d, baud=%d, carrier=%d, profile=%c\n",
samples_per_sec, bps, correct_baud, carrier_freq, profile);
#endif
switch (toupper(profile)) {
case 'T': /* Self correlation technique. */
D->use_prefilter = 0; /* No bandpass filter. */
D->lpf_baud = 1.15;
D->lp_filter_len_bits = 32. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.95;
D->pll_searching_inertia = 0.50;
break;
case 'U': /* Self correlation technique. */
D->use_prefilter = 1; /* Add a bandpass filter. */
D->prefilter_baud = 0.9;
D->pre_filter_len_bits = 21. * 1200. / 44100.;
D->pre_window = BP_WINDOW_FLATTOP;
D->lpf_baud = 1.15;
D->lp_filter_len_bits = 32. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.87;
D->pll_searching_inertia = 0.50;
break;
default:
text_color_set (DW_COLOR_ERROR);
dw_printf ("Invalid demodulator profile %c for v.27 8PSK. Valid choices are T, U, V, W. Using default.\n", profile);
// fall thru.
case 'V': /* Mix with local oscillator. */
D->psk_use_lo = 1;
D->use_prefilter = 0; /* No bandpass filter. */
D->lpf_baud = 0.85;
D->lp_filter_len_bits = 31. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.925;
D->pll_searching_inertia = 0.50;
break;
case 'W': /* Mix with local oscillator. */
D->psk_use_lo = 1;
D->use_prefilter = 1; /* Add a bandpass filter. */
D->prefilter_baud = 0.85;
D->pre_filter_len_bits = 31. * 1200. / 44100.;
D->pre_window = BP_WINDOW_COSINE;
D->lpf_baud = 0.85;
D->lp_filter_len_bits = 31. * 1200. / 44100.;
D->lp_window = BP_WINDOW_COSINE;
D->pll_locked_inertia = 0.925;
D->pll_searching_inertia = 0.50;
break;
}
D->ms_filter_len_bits = 1.25; // Delay line > 10/9 * symbol period
D->coffs = (int) round( (8.f / 9.f) * (float)samples_per_sec / (float)correct_baud );
D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud );
D->soffs = (int) round( (10.f / 9.f) * (float)samples_per_sec / (float)correct_baud );
}
if (D->psk_use_lo) {
D->lo_step = (int) round( 256. * 256. * 256. * 256. * carrier_freq / (double)samples_per_sec);
assert (MAX_FILTER_SIZE >= 256);
for (j = 0; j < 256; j++) {
D->m_sin_table[j] = sinf(2.f * (float)M_PI * j / 256.f);
}
}
#ifdef TUNE_PRE_BAUD
D->prefilter_baud = TUNE_PRE_BAUD;
#endif
#ifdef TUNE_PRE_WINDOW
D->pre_window = TUNE_PRE_WINDOW;
#endif
#ifdef TUNE_LPF_BAUD
D->lpf_baud = TUNE_LPF_BAUD;
#endif
#ifdef TUNE_LP_WINDOW
D->lp_window = TUNE_LP_WINDOW;
#endif
#ifdef TUNE_HYST
D->hysteresis = TUNE_HYST;
#endif
#if defined(TUNE_PLL_SEARCHING)
D->pll_searching_inertia = TUNE_PLL_SEARCHING;
#endif
#if defined(TUNE_PLL_LOCKED)
D->pll_locked_inertia = TUNE_PLL_LOCKED;
#endif
/*
* Calculate constants used for timing.
* The audio sample rate must be at least a few times the data rate.
*/
D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)correct_baud) / ((double)samples_per_sec));
/*
* Convert number of symbol times to number of taps.
*/
D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)correct_baud );
D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)correct_baud );
D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)correct_baud );
#ifdef TUNE_PRE_FILTER_SIZE
D->pre_filter_size = TUNE_PRE_FILTER_SIZE;
#endif
#ifdef TUNE_LP_FILTER_SIZE
D->lp_filter_size = TUNE_LP_FILTER_SIZE;
#endif
if (D->pre_filter_size > MAX_FILTER_SIZE)
{
text_color_set (DW_COLOR_ERROR);
dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
MAX_FILTER_SIZE);
exit (1);
}
if (D->ms_filter_size > MAX_FILTER_SIZE)
{
text_color_set (DW_COLOR_ERROR);
dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size);
dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
MAX_FILTER_SIZE);
exit (1);
}
if (D->lp_filter_size > MAX_FILTER_SIZE)
{
text_color_set (DW_COLOR_ERROR);
dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
MAX_FILTER_SIZE);
exit (1);
}
/*
* Optionally apply a bandpass ("pre") filter to attenuate
* frequencies outside the range of interest.
*/
if (D->use_prefilter) {
float f1, f2;
f1 = carrier_freq - D->prefilter_baud * correct_baud;
f2 = carrier_freq + D->prefilter_baud * correct_baud;
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", (double)f1, (double)f2);
#endif
if (f1 <= 0) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("Prefilter of %.0f to %.0f Hz doesn't make sense.\n", (double)f1, (double)f2);
f1 = 10;
}
f1 = f1 / (float)samples_per_sec;
f2 = f2 / (float)samples_per_sec;
gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window);
}
/*
* Now the lowpass filter.
*/
float fc = correct_baud * D->lpf_baud / (float)samples_per_sec;
gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
/*
* No point in having multiple numbers for signal level.
*/
D->alevel_mark_peak = -1;
D->alevel_space_peak = -1;
} /* demod_psk_init */
/*-------------------------------------------------------------------
*
* Name: demod_psk_process_sample
*
* Purpose: (1) Demodulate the psk signal into I & Q components.
* (2) Recover clock and sample data at the right time.
* (3) Produce two bits per symbol based on phase change from previous.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
* subchan - modem of the channel.
* sam - One sample of audio.
* Should be in range of -32768 .. 32767.
*
* Outputs: For each recovered data bit, we call:
*
* hdlc_rec (channel, demodulated_bit);
*
* to decode HDLC frames from the stream of bits.
*
* Returns: None
*
* Descripion: All the literature, that I could find, described mixing
* with a local oscillator. First we multiply the input by
* cos and sin then low pass filter each. This gives us
* correlation to the different phases. The signs of these two
* results produces two data bits per symbol period.
*
* An 1800 Hz local oscillator was derived from the 1200 Hz
* PLL used to sample the data.
* This worked wonderfully for the ideal condition where
* we start off with the proper phase and all the timing
* is perfect. However, when random delays were added
* before the frame, the PLL would lock on only about
* half the time.
*
* Late one night, it dawned on me that there is no
* need for a local oscillator (LO) at the carrier frequency.
* Simply correlate the signal with the previous symbol,
* phase shifted by + and - 45 degrees.
* The code is much simpler and very reliable.
*
* Later, I realized it was not necessary to synchronize the LO
* because we only care about the phase shift between symbols.
*
* This works better under noisy conditions because we are
* including the noise from only the current symbol and not
* the previous one.
*
* Finally, once we know how to distinguish 4 different phases,
* it is not much effort to use 8 phases to double the bit rate.
*
*--------------------------------------------------------------------*/
inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D);
__attribute__((hot))
void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D)
{
float fsam;
float sam_x_cos, sam_x_sin;
float I, Q;
int demod_phase_shift; // Phase shift relative to previous symbol.
// range 0-3, 1 unit for each 90 degrees.
int slice = 0;
#if DEBUG4
static FILE *demod_log_fp = NULL;
static int log_file_seq = 0; /* Part of log file name */
#endif
assert (chan >= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
/* Scale to nice number for plotting during debug. */
fsam = sam / 16384.0f;
/*
* Optional bandpass filter before the phase detector.
*/
if (D->use_prefilter) {
push_sample (fsam, D->raw_cb, D->pre_filter_size);
fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size);
}
if (D->psk_use_lo) {
float a, delta;
int id;
/*
* Mix with local oscillator to obtain phase.
* The absolute phase doesn't matter.
* We are just concerned with the change since the previous symbol.
*/
sam_x_cos = fsam * D->m_sin_table[((D->lo_phase >> 24) + 64) & 0xff];
sam_x_sin = fsam * D->m_sin_table[(D->lo_phase >> 24) & 0xff];
push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size);
I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size);
push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size);
Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size);
a = my_atan2f(I,Q);
push_sample (a, D->ms_in_cb, D->ms_filter_size);
delta = a - D->ms_in_cb[D->boffs];
/* 256 units/cycle makes modulo processing easier. */
/* Make sure it is positive before truncating to integer. */
id = ((int)((delta / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff;
if (D->modem_type == MODEM_QPSK) {
demod_phase_shift = ((id + 32) >> 6) & 0x3;
}
else {
demod_phase_shift = ((id + 16) >> 5) & 0x7;
}
nudge_pll (chan, subchan, slice, demod_phase_shift, D);
D->lo_phase += D->lo_step;
}
else {
/*
* Correlate with previous symbol. We are looking for the phase shift.
*/
push_sample (fsam, D->ms_in_cb, D->ms_filter_size);
sam_x_cos = fsam * D->ms_in_cb[D->coffs];
sam_x_sin = fsam * D->ms_in_cb[D->soffs];
push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size);
I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size);
push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size);
Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size);
if (D->modem_type == MODEM_QPSK) {
#if 1 // Speed up special case.
if (I > 0) {
if (Q > 0)
demod_phase_shift = 0; /* 0 to 90 degrees, etc. */
else
demod_phase_shift = 1;
}
else {
if (Q > 0)
demod_phase_shift = 3;
else
demod_phase_shift = 2;
}
#else
a = my_atan2f(I,Q);
int id = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff;
// 128 compensates for 180 degree phase shift due
// to 1 1/2 carrier cycles per symbol period.
demod_phase_shift = ((id + 128) >> 6) & 0x3;
#endif
}
else {
float a;
int idelta;
a = my_atan2f(I,Q);
idelta = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff;
// 32 (90 degrees) compensates for 1800 carrier vs. 1800 baud.
// 16 is to set threshold between constellation points.
demod_phase_shift = ((idelta - 32 - 16) >> 5) & 0x7;
}
nudge_pll (chan, subchan, slice, demod_phase_shift, D);
}
#if DEBUG4
if (chan == 0) {
if (1) {
//if (hdlc_rec_gathering (chan, subchan, slice)) {
char fname[30];
if (demod_log_fp == NULL) {
log_file_seq++;
snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq);
//if (log_file_seq == 1) mkdir ("demod", 0777);
if (log_file_seq == 1) mkdir ("demod");
demod_log_fp = fopen (fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Starting demodulator log file %s\n", fname);
fprintf (demod_log_fp, "Audio, sin, cos, *cos, *sin, I, Q, phase, Clock\n");
}
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n",
fsam + 2,
- D->ms_in_cb[D->soffs] + 6,
- D->ms_in_cb[D->coffs] + 6,
sam_x_cos + 8,
sam_x_sin + 10,
2 * I + 12,
2 * Q + 12,
demod_phase_shift * 2. / 3. + 14.,
(D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0);
fflush (demod_log_fp);
}
else {
if (demod_log_fp != NULL) {
fclose (demod_log_fp);
demod_log_fp = NULL;
}
}
}
#endif
} /* end demod_psk_process_sample */
static const int phase_to_gray_v26[4] = {0, 1, 3, 2};
static const int phase_to_gray_v27[8] = {1, 0, 2, 3, 7, 6, 4, 5};
__attribute__((hot))
inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D)
{
/*
* Finally, a PLL is used to sample near the centers of the data bits.
*
* D points to a demodulator for a channel/subchannel pair so we don't
* have to keep recalculating it.
*
* D->data_clock_pll is a SIGNED 32 bit variable.
* When it overflows from a large positive value to a negative value, we
* sample a data bit from the demodulated signal.
*
* Ideally, the the demodulated signal transitions should be near
* zero we we sample mid way between the transitions.
*
* Nudge the PLL by removing some small fraction from the value of
* data_clock_pll, pushing it closer to zero.
*
* This adjustment will never change the sign so it won't cause
* any erratic data bit sampling.
*
* If we adjust it too quickly, the clock will have too much jitter.
* If we adjust it too slowly, it will take too long to lock on to a new signal.
*
* Be a little more agressive about adjusting the PLL
* phase when searching for a signal.
* Don't change it as much when locked on to a signal.
*
* I don't think the optimal value will depend on the audio sample rate
* because this happens for each transition from the demodulator.
*/
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll >= 0) {
/* Overflow of PLL counter. */
/* This is where we sample the data. */
if (D->modem_type == MODEM_QPSK) {
int gray = phase_to_gray_v26[ demod_bits ];
#if DEBUG4
text_color_set(DW_COLOR_DEBUG);
dw_printf ("a=%.2f deg, delta=%.2f deg, phaseshift=%d, bits= %d %d \n",
a * 360 / (2*M_PI), delta * 360 / (2*M_PI), demod_bits, (gray >> 1) & 1, gray & 1);
//dw_printf ("phaseshift=%d, bits= %d %d \n", demod_bits, (gray >> 1) & 1, gray & 1);
#endif
hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1);
hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1);
}
else {
int gray = phase_to_gray_v27[ demod_bits ];
hdlc_rec_bit (chan, subchan, slice, (gray >> 2) & 1, 0, -1);
hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1);
hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1);
}
}
/*
* If demodulated data has changed,
* Pull the PLL phase closer to zero.
* Use "floor" instead of simply casting so the sign won't flip.
* For example if we had -0.7 we want to end up with -1 rather than 0.
*/
// TODO: demod_9600 has an improved technique. Would it help us here?
if (demod_bits != D->slicer[slice].prev_demod_data) {
if (hdlc_rec_gathering (chan, subchan, slice)) {
D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia);
}
else {
D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_searching_inertia);
}
}
/*
* Remember demodulator output so we can compare next time.
*/
D->slicer[slice].prev_demod_data = demod_bits;
} /* end nudge_pll */
/* end demod_psk.c */

7
demod_psk.h Normal file
View File

@ -0,0 +1,7 @@
/* demod_psk.h */
void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D);
void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D);

View File

@ -23,6 +23,7 @@
* Name: digipeater.c * Name: digipeater.c
* *
* Purpose: Act as an APRS digital repeater. * Purpose: Act as an APRS digital repeater.
* Similar cdigipeater.c is for connected mode.
* *
* *
* Description: Decide whether the specified packet should * Description: Decide whether the specified packet should
@ -53,6 +54,7 @@
#define DIGIPEATER_C #define DIGIPEATER_C
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -62,7 +64,6 @@
#include "regex.h" #include "regex.h"
#include <sys/unistd.h> #include <sys/unistd.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "digipeater.h" #include "digipeater.h"
#include "textcolor.h" #include "textcolor.h"
@ -74,8 +75,6 @@
static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter); regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter);
//static int filter_by_type (char *source, char *infop, char *type_filter);
/* /*
* Keep pointer to configuration options. * Keep pointer to configuration options.
@ -87,6 +86,18 @@ static struct audio_s *save_audio_config_p;
static struct digi_config_s *save_digi_config_p; static struct digi_config_s *save_digi_config_p;
/*
* Maintain count of packets digipeated for each combination of from/to channel.
*/
static int digi_count[MAX_CHANS][MAX_CHANS];
int digipeater_get_count (int from_chan, int to_chan) {
return (digi_count[from_chan][to_chan]);
}
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* *
* Name: digipeater_init * Name: digipeater_init
@ -165,6 +176,7 @@ void digipeater (int from_chan, packet_t pp)
if (result != NULL) { if (result != NULL) {
dedupe_remember (pp, to_chan); dedupe_remember (pp, to_chan);
tq_append (to_chan, TQ_PRIO_0_HI, result); tq_append (to_chan, TQ_PRIO_0_HI, result);
digi_count[from_chan][to_chan]++;
} }
} }
} }
@ -190,6 +202,7 @@ void digipeater (int from_chan, packet_t pp)
if (result != NULL) { if (result != NULL) {
dedupe_remember (pp, to_chan); dedupe_remember (pp, to_chan);
tq_append (to_chan, TQ_PRIO_1_LO, result); tq_append (to_chan, TQ_PRIO_1_LO, result);
digi_count[from_chan][to_chan]++;
} }
} }
} }
@ -243,6 +256,10 @@ void digipeater (int from_chan, packet_t pp)
* *
*------------------------------------------------------------------------------*/ *------------------------------------------------------------------------------*/
#define OBSOLETE14 1
#ifndef OBSOLETE14
static char *dest_ssid_path[16] = { static char *dest_ssid_path[16] = {
"", /* Use VIA path */ "", /* Use VIA path */
"WIDE1-1", "WIDE1-1",
@ -260,11 +277,13 @@ static char *dest_ssid_path[16] = {
"WIDE2-2", /* South */ "WIDE2-2", /* South */
"WIDE2-2", /* East */ "WIDE2-2", /* East */
"WIDE2-2" }; /* West */ "WIDE2-2" }; /* West */
#endif
static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit, static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str) regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str)
{ {
char source[AX25_MAX_ADDR_LEN];
int ssid; int ssid;
int r; int r;
char repeater[AX25_MAX_ADDR_LEN]; char repeater[AX25_MAX_ADDR_LEN];
@ -274,19 +293,9 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
/* /*
* First check if filtering has been configured. * First check if filtering has been configured.
*/ */
if (filter_str != NULL) { if (filter_str != NULL) {
if (pfilter(from_chan, to_chan, filter_str, pp) != 1) { if (pfilter(from_chan, to_chan, filter_str, pp, 1) != 1) {
// TODO1.2: take out debug message
// Actually it turns out to be useful.
// Maybe add a quiet option to suppress it although no one has complained about it yet.
//#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Packet was rejected for digipeating from channel %d to %d by filter: %s\n", from_chan, to_chan, filter_str);
//#endif
return(NULL); return(NULL);
} }
} }
@ -310,11 +319,15 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
* Otherwise we don't want to modify the input because this could be called multiple times. * Otherwise we don't want to modify the input because this could be called multiple times.
*/ */
#ifndef OBSOLETE14 // Took it out in 1.4
if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) { if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]); ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
ax25_set_ssid(pp, AX25_DESTINATION, 0); ax25_set_ssid(pp, AX25_DESTINATION, 0);
/* Continue with general case, below. */ /* Continue with general case, below. */
} }
#endif
/* /*
* Find the first repeater station which doesn't have "has been repeated" set. * Find the first repeater station which doesn't have "has been repeated" set.
@ -337,10 +350,13 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
/* /*
* First check for explicit use of my call. * First check for explicit use of my call, including SSID.
* Someone might explicitly specify a particular path for testing purposes.
* This will bypass the usual checks for duplicates and my call in the source.
*
* In this case, we don't check the history so it would be possible * In this case, we don't check the history so it would be possible
* to have a loop (of limited size) if someone constructed the digipeater paths * to have a loop (of limited size) if someone constructed the digipeater paths
* correctly. * correctly. I would expect it only for testing purposes.
*/ */
if (strcmp(repeater, mycall_rec) == 0) { if (strcmp(repeater, mycall_rec) == 0) {
@ -356,13 +372,24 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
return (result); return (result);
} }
/*
* Don't digipeat my own. Fixed in 1.4 dev H.
* Alternatively we might feed everything transmitted into
* dedupe_remember rather than only frames out of digipeater.
*/
ax25_get_addr_with_ssid(pp, AX25_SOURCE, source);
if (strcmp(source, mycall_rec) == 0) {
return (NULL);
}
/* /*
* Next try to avoid retransmitting redundant information. * Next try to avoid retransmitting redundant information.
* Duplicates are detected by comparing only: * Duplicates are detected by comparing only:
* - source * - source
* - destination * - destination
* - info part * - info part
* - but none of the digipeaters * - but not the via path. (digipeater addresses)
* A history is kept for some amount of time, typically 30 seconds. * A history is kept for some amount of time, typically 30 seconds.
* For efficiency, only a checksum, rather than the complete fields * For efficiency, only a checksum, rather than the complete fields
* might be kept but the result is the same. * might be kept but the result is the same.
@ -371,7 +398,6 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
* *
*/ */
if (dedupe_check(pp, to_chan)) { if (dedupe_check(pp, to_chan)) {
//#if DEBUG //#if DEBUG
/* Might be useful if people are wondering why */ /* Might be useful if people are wondering why */
@ -385,7 +411,10 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
/* /*
* For the alias pattern, we unconditionally digipeat it once. * For the alias pattern, we unconditionally digipeat it once.
* i.e. Just replace it with MYCALL don't even look at the ssid. * i.e. Just replace it with MYCALL.
*
* My call should be an implied member of this set.
* In this implementation, we already caught it further up.
*/ */
err = regexec(alias,repeater,0,NULL,0); err = regexec(alias,repeater,0,NULL,0);
if (err == 0) { if (err == 0) {
@ -584,13 +613,11 @@ static int failed;
static enum preempt_e preempt = PREEMPT_OFF; static enum preempt_e preempt = PREEMPT_OFF;
static char typefilter[20] = "";
static void test (char *in, char *out) static void test (char *in, char *out)
{ {
packet_t pp, result; packet_t pp, result;
//int should_repeat;
char rec[256]; char rec[256];
char xmit[256]; char xmit[256];
unsigned char *pinfo; unsigned char *pinfo;
@ -610,6 +637,7 @@ static void test (char *in, char *out)
ax25_format_addrs (pp, rec); ax25_format_addrs (pp, rec);
info_len = ax25_get_info (pp, &pinfo); info_len = ax25_get_info (pp, &pinfo);
(void)info_len;
strlcat (rec, (char*)pinfo, sizeof(rec)); strlcat (rec, (char*)pinfo, sizeof(rec));
if (strcmp(in, rec) != 0) { if (strcmp(in, rec) != 0) {
@ -784,17 +812,22 @@ int main (int argc, char *argv[])
/* /*
* Change destination SSID to normal digipeater if none specified. * Change destination SSID to normal digipeater if none specified. (Obsolete, removed.)
*/ */
test ( "W1ABC>TEST-3:",
"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
test ( "W1ABC>TEST-3:",
#ifndef OBSOLETE14
"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
#else
"");
#endif
test ( "W1DEF>TEST-3,WIDE2-2:", test ( "W1DEF>TEST-3,WIDE2-2:",
"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:"); "W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
/* /*
* Drop duplicates within specified time interval. * Drop duplicates within specified time interval.
* Only the first 1 of 3 should be retransmitted. * Only the first 1 of 3 should be retransmitted.
* The 4th case might be controversial.
*/ */
test ( "W1XYZ>TEST,R1*,WIDE3-2:info1", test ( "W1XYZ>TEST,R1*,WIDE3-2:info1",
@ -806,6 +839,10 @@ int main (int argc, char *argv[])
test ( "W1XYZ>TEST,R3*,WIDE3-2:info1", test ( "W1XYZ>TEST,R3*,WIDE3-2:info1",
""); "");
test ( "W1XYZ>TEST,R1*,WB2OSZ-9:has explicit routing",
"W1XYZ>TEST,R1,WB2OSZ-9*:has explicit routing");
/* /*
* Allow same thing after adequate time. * Allow same thing after adequate time.
*/ */
@ -863,73 +900,25 @@ int main (int argc, char *argv[])
""); "");
#if 0 /* changed strategy */
/*
* New in version 1.2.
*/
// no filter.
if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "") != 1)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 1\n"); failed++; }
// message should not match psqt
if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "pqst") != 0)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 2\n"); failed++; }
// This should match position
if (filter_by_type ("N3LEE-7", "`cHDl <0x1c>[/\"5j}", "qstp") != 1)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 3\n"); failed++; }
// This should match nws
if (filter_by_type ("CWAPID", ":NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 1)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 4\n"); failed++; }
// But not this.
if (filter_by_type ("CWAPID", ":zzz-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", "n") != 0)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 5\n"); failed++; }
// This should match nws
if (filter_by_type ("CWAPID", ";CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 1)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 6\n"); failed++; }
// But not this due do addressee prefix mismatch
if (filter_by_type ("CWAPID", ";NWSttttz *DDHHMMzLATLONICONADVISETYPE{seq#", "n") != 0)
{ text_color_set(DW_COLOR_ERROR); dw_printf ("filter_by_type case 7\n"); failed++; }
/*
* Filtering integrated with rest of process...
*/
strlcpy (typefilter, "w", sizeof(typefilter));
test ( "N8VIM>APN391,WIDE2-1:$ULTW00000000010E097D2884FFF389DC000102430002033400000000",
"N8VIM>APN391,WB2OSZ-9*:$ULTW00000000010E097D2884FFF389DC000102430002033400000000");
test ( "AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational",
"");
strlcpy (typefilter, "s", sizeof(typefilter));
test ( "AB1OC-10>APWW10,WIDE1-1,WIDE2-1:>FN42er/# Hollis, NH iGate Operational",
"AB1OC-10>APWW10,WB2OSZ-9*,WIDE2-1:>FN42er/# Hollis, NH iGate Operational");
test ( "K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%",
"");
strlcpy (typefilter, "up", sizeof(typefilter));
test ( "K1ABC-9>TR4R8R,WIDE1-1:`c6LlIb>/`\"4K}_%",
"K1ABC-9>TR4R8R,WB2OSZ-9*:`c6LlIb>/`\"4K}_%");
strlcpy (typefilter, "", sizeof(typefilter));
#endif
/* /*
* Did I miss any cases? * Did I miss any cases?
* Yes. Don't retransmit my own. 1.4H
*/ */
test ( "WB2OSZ-7>TEST14,WIDE1-1,WIDE1-1:stuff",
"WB2OSZ-7>TEST14,WB2OSZ-9*,WIDE1-1:stuff");
test ( "WB2OSZ-9>TEST14,WIDE1-1,WIDE1-1:from myself",
"");
test ( "WB2OSZ-9>TEST14,WIDE1-1*,WB2OSZ-9:from myself but explicit routing",
"WB2OSZ-9>TEST14,WIDE1-1,WB2OSZ-9*:from myself but explicit routing");
test ( "WB2OSZ-15>TEST14,WIDE1-1,WIDE1-1:stuff",
"WB2OSZ-15>TEST14,WB2OSZ-9*,WIDE1-1:stuff");
if (failed == 0) { if (failed == 0) {
dw_printf ("SUCCESS -- All digipeater tests passed.\n"); dw_printf ("SUCCESS -- All digipeater tests passed.\n");
} }

View File

@ -65,6 +65,11 @@ extern void digipeater (int from_chan, packet_t pp);
void digi_regen (int from_chan, packet_t pp); void digi_regen (int from_chan, packet_t pp);
/* Make statistics available. */
int digipeater_get_count (int from_chan, int to_chan);
#endif #endif
/* end digipeater.h */ /* end digipeater.h */

View File

@ -35,6 +35,13 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#define DIREWOLF_C 1
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <stdlib.h> #include <stdlib.h>
@ -72,9 +79,7 @@
#endif #endif
#define DIREWOLF_C 1
#include "direwolf.h"
#include "version.h" #include "version.h"
#include "audio.h" #include "audio.h"
#include "config.h" #include "config.h"
@ -83,29 +88,34 @@
#include "hdlc_rec.h" #include "hdlc_rec.h"
#include "hdlc_rec2.h" #include "hdlc_rec2.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "xid.h"
#include "decode_aprs.h" #include "decode_aprs.h"
#include "textcolor.h" #include "textcolor.h"
#include "server.h" #include "server.h"
#include "kiss.h" #include "kiss.h"
#include "kissnet.h" #include "kissnet.h"
#include "kiss_frame.h" #include "kiss_frame.h"
#include "nmea.h" #include "waypoint.h"
#include "gen_tone.h" #include "gen_tone.h"
#include "digipeater.h" #include "digipeater.h"
#include "cdigipeater.h"
#include "tq.h" #include "tq.h"
#include "xmit.h" #include "xmit.h"
#include "ptt.h" #include "ptt.h"
#include "beacon.h" #include "beacon.h"
#include "redecode.h"
#include "dtmf.h" #include "dtmf.h"
#include "aprs_tt.h" #include "aprs_tt.h"
#include "tt_user.h" #include "tt_user.h"
#include "igate.h" #include "igate.h"
#include "pfilter.h"
#include "symbols.h" #include "symbols.h"
#include "dwgps.h" #include "dwgps.h"
#include "waypoint.h"
#include "log.h" #include "log.h"
#include "recv.h" #include "recv.h"
#include "morse.h" #include "morse.h"
#include "mheard.h"
#include "ax25_link.h"
//static int idx_decoded = 0; //static int idx_decoded = 0;
@ -152,14 +162,19 @@ static void __cpuid(int cpuinfo[4], int infotype){
static struct audio_s audio_config; static struct audio_s audio_config;
static struct tt_config_s tt_config; static struct tt_config_s tt_config;
struct digi_config_s digi_config; //struct digi_config_s digi_config;
//struct cdigi_config_s cdigi_config;
static const int audio_amplitude = 100; /* % of audio sample range. */
/* This translates to +-32k for 16 bit samples. */
/* Currently no option to change this. */
static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */ static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */
static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */
static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */
static int q_d_opt = 0; /* "-q d" Quiet, suppress the decoding of APRS packets. */ static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */
static struct misc_config_s misc_config; static struct misc_config_s misc_config;
@ -174,6 +189,7 @@ int main (int argc, char *argv[])
int xmit_calibrate_option = 0; int xmit_calibrate_option = 0;
int enable_pseudo_terminal = 0; int enable_pseudo_terminal = 0;
struct digi_config_s digi_config; struct digi_config_s digi_config;
struct cdigi_config_s cdigi_config;
struct igate_config_s igate_config; struct igate_config_s igate_config;
int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */ int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */
char P_opt[16]; char P_opt[16];
@ -189,9 +205,13 @@ int main (int argc, char *argv[])
int d_g_opt = 0; /* "-d g" option for GPS. Can be repeated for more detail. */ int d_g_opt = 0; /* "-d g" option for GPS. Can be repeated for more detail. */
int d_o_opt = 0; /* "-d o" option for output control such as PTT and DCD. */ int d_o_opt = 0; /* "-d o" option for output control such as PTT and DCD. */
int d_i_opt = 0; /* "-d i" option for IGate. Repeat for more detail */ int d_i_opt = 0; /* "-d i" option for IGate. Repeat for more detail */
int d_m_opt = 0; /* "-d m" option for mheard list. */
int d_f_opt = 0; /* "-d f" option for filtering. Repeat for more detail. */
#if USE_HAMLIB #if USE_HAMLIB
int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */ int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */
#endif #endif
int E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */
int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */
strlcpy(l_opt, "", sizeof(l_opt)); strlcpy(l_opt, "", sizeof(l_opt));
strlcpy(P_opt, "", sizeof(P_opt)); strlcpy(P_opt, "", sizeof(P_opt));
@ -234,10 +254,13 @@ int main (int argc, char *argv[])
// TODO: control development/beta/release by version.h instead of changing here. // TODO: control development/beta/release by version.h instead of changing here.
// Print platform. This will provide more information when people send a copy the information displayed. // Print platform. This will provide more information when people send a copy the information displayed.
// Might want to print OS version here. For Windows, see:
// https://msdn.microsoft.com/en-us/library/ms724451(v=VS.85).aspx
text_color_init(t_opt); text_color_init(t_opt);
text_color_set(DW_COLOR_INFO); 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 version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
//dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "K", __DATE__); //dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "H", __DATE__);
dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) #if defined(ENABLE_GPSD) || defined(USE_HAMLIB)
@ -322,7 +345,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */ /* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:", c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:E:",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -367,9 +390,9 @@ int main (int argc, char *argv[])
case 'B': /* -B baud rate and modem properties. */ case 'B': /* -B baud rate and modem properties. */
B_opt = atoi(optarg); B_opt = atoi(optarg);
if (B_opt < 100 || B_opt > 10000) { if (B_opt < MIN_BAUD || B_opt > MAX_BAUD) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Use a more reasonable data baud rate in range of 100 - 10000.\n"); dw_printf ("Use a more reasonable data baud rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
break; break;
@ -451,15 +474,17 @@ int main (int argc, char *argv[])
// separate out gps & waypoints. // separate out gps & waypoints.
case 'g': d_g_opt++; break; case 'g': d_g_opt++; break;
case 'w': waypoint_set_debug (1); break; // not documented yet.
case 't': d_t_opt++; beacon_tracker_set_debug (d_t_opt); break; case 't': d_t_opt++; beacon_tracker_set_debug (d_t_opt); break;
case 'w': nmea_set_debug (1); break; // not documented yet.
case 'p': d_p_opt = 1; break; // TODO: packet dump for xmit side. case 'p': d_p_opt = 1; break; // TODO: packet dump for xmit side.
case 'o': d_o_opt++; ptt_set_debug(d_o_opt); break; case 'o': d_o_opt++; ptt_set_debug(d_o_opt); break;
case 'i': d_i_opt++; break; case 'i': d_i_opt++; break;
case 'm': d_m_opt++; break;
case 'f': d_f_opt++; break;
#if AX25MEMDEBUG #if AX25MEMDEBUG
case 'm': ax25memdebug_set(); break; // Track down memory leak. Not documented. case 'l': ax25memdebug_set(); break; // Track down memory Leak. Not documented.
#endif #endif // Previously 'm' but that is now used for mheard.
#if USE_HAMLIB #if USE_HAMLIB
case 'h': d_h_opt++; break; // Hamlib verbose level. case 'h': d_h_opt++; break; // Hamlib verbose level.
#endif #endif
@ -508,6 +533,27 @@ int main (int argc, char *argv[])
exit (0); exit (0);
break; break;
case 'E': /* -E Error rate (%) for corrupting frames. */
/* Just a number is transmit. Precede by R for receive. */
if (*optarg == 'r' || *optarg == 'R') {
E_rx_opt = atoi(optarg+1);
if (E_rx_opt < 1 || E_rx_opt > 99) {
text_color_set(DW_COLOR_ERROR);
dw_printf("-ER must be in range of 1 to 99.\n");
E_rx_opt = 10;
}
}
else {
E_tx_opt = atoi(optarg);
if (E_tx_opt < 1 || E_tx_opt > 99) {
text_color_set(DW_COLOR_ERROR);
dw_printf("-E must be in range of 1 to 99.\n");
E_tx_opt = 10;
}
}
break;
default: default:
/* Should not be here. */ /* Should not be here. */
@ -542,7 +588,7 @@ int main (int argc, char *argv[])
symbols_init (); symbols_init ();
config_init (config_file, &audio_config, &digi_config, &tt_config, &igate_config, &misc_config); config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config);
if (r_opt != 0) { if (r_opt != 0) {
audio_config.adev[0].samples_per_sec = r_opt; audio_config.adev[0].samples_per_sec = r_opt;
@ -559,22 +605,43 @@ int main (int argc, char *argv[])
if (B_opt != 0) { if (B_opt != 0) {
audio_config.achan[0].baud = B_opt; audio_config.achan[0].baud = B_opt;
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
if (audio_config.achan[0].baud < 600) { if (audio_config.achan[0].baud < 600) {
audio_config.achan[0].modem_type = MODEM_AFSK; audio_config.achan[0].modem_type = MODEM_AFSK;
audio_config.achan[0].mark_freq = 1600; audio_config.achan[0].mark_freq = 1600; // Typical for HF SSB.
audio_config.achan[0].space_freq = 1800; audio_config.achan[0].space_freq = 1800;
audio_config.achan[0].decimate = 3; audio_config.achan[0].decimate = 3; // Reduce CPU load.
} }
else if (audio_config.achan[0].baud > 2400) { else if (audio_config.achan[0].baud < 1800) {
audio_config.achan[0].modem_type = MODEM_AFSK;
audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ;
audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ;
}
else if (audio_config.achan[0].baud < 3600) {
audio_config.achan[0].modem_type = MODEM_QPSK;
audio_config.achan[0].mark_freq = 0;
audio_config.achan[0].space_freq = 0;
if (audio_config.achan[0].baud != 2400) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Bit rate should be standard 2400 rather than specified %d.\n", audio_config.achan[0].baud);
}
}
else if (audio_config.achan[0].baud < 7200) {
audio_config.achan[0].modem_type = MODEM_8PSK;
audio_config.achan[0].mark_freq = 0;
audio_config.achan[0].space_freq = 0;
if (audio_config.achan[0].baud != 4800) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", audio_config.achan[0].baud);
}
}
else {
audio_config.achan[0].modem_type = MODEM_SCRAMBLE; audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
audio_config.achan[0].mark_freq = 0; audio_config.achan[0].mark_freq = 0;
audio_config.achan[0].space_freq = 0; audio_config.achan[0].space_freq = 0;
} }
else {
audio_config.achan[0].modem_type = MODEM_AFSK;
audio_config.achan[0].mark_freq = 1200;
audio_config.achan[0].space_freq = 2200;
}
} }
audio_config.statistics_interval = a_opt; audio_config.statistics_interval = a_opt;
@ -590,6 +657,12 @@ int main (int argc, char *argv[])
audio_config.achan[0].decimate = D_opt; audio_config.achan[0].decimate = D_opt;
} }
// temp - only xmit errors.
audio_config.xmit_error_rate = E_tx_opt;
audio_config.recv_error_rate = E_rx_opt;
if (strlen(l_opt) > 0) { if (strlen(l_opt) > 0) {
strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir)); strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir));
} }
@ -621,14 +694,14 @@ int main (int argc, char *argv[])
} }
/* /*
* Initialize the AFSK demodulator and HDLC decoder. * Initialize the demodulator(s) and HDLC decoder.
*/ */
multi_modem_init (&audio_config); multi_modem_init (&audio_config);
/* /*
* Initialize the touch tone decoder & APRStt gateway. * Initialize the touch tone decoder & APRStt gateway.
*/ */
dtmf_init (&audio_config); dtmf_init (&audio_config, audio_amplitude);
aprs_tt_init (&tt_config); aprs_tt_init (&tt_config);
tt_user_init (&audio_config, &tt_config); tt_user_init (&audio_config, &tt_config);
@ -637,8 +710,8 @@ int main (int argc, char *argv[])
* Note: This is not the same as a volume control you would see on the screen. * Note: This is not the same as a volume control you would see on the screen.
* It is the range of the digital sound representation. * It is the range of the digital sound representation.
*/ */
gen_tone_init (&audio_config, 100); gen_tone_init (&audio_config, audio_amplitude, 0);
morse_init (&audio_config, 100); morse_init (&audio_config, audio_amplitude);
assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16); assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16);
assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2); assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2);
@ -680,6 +753,9 @@ int main (int argc, char *argv[])
*/ */
digipeater_init (&audio_config, &digi_config); digipeater_init (&audio_config, &digi_config);
igate_init (&audio_config, &igate_config, &digi_config, d_i_opt); igate_init (&audio_config, &igate_config, &digi_config, d_i_opt);
cdigipeater_init (&audio_config, &cdigi_config);
pfilter_init (&igate_config, d_f_opt);
ax25_link_init (&misc_config);
/* /*
* Provide the AGW & KISS socket interfaces for use by a client application. * Provide the AGW & KISS socket interfaces for use by a client application.
@ -698,12 +774,7 @@ int main (int argc, char *argv[])
*/ */
dwgps_init (&misc_config, d_g_opt); dwgps_init (&misc_config, d_g_opt);
nmea_init (&misc_config); // TODO: revisit. waypoint_init (&misc_config);
/*
* Create thread for trying to salvage frames with bad FCS.
*/
redecode_init (&audio_config);
/* /*
* Enable beaconing. * Enable beaconing.
@ -712,7 +783,8 @@ int main (int argc, char *argv[])
*/ */
log_init(misc_config.logdir); log_init(misc_config.logdir);
beacon_init (&audio_config, &misc_config); mheard_init (d_m_opt);
beacon_init (&audio_config, &misc_config, &igate_config);
/* /*
@ -863,7 +935,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
text_color_set(DW_COLOR_REC); text_color_set(DW_COLOR_REC);
} }
else { else {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DECODED);
} }
if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) { if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) {
@ -882,6 +954,36 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
dw_printf ("%s", stemp); /* stations followed by : */ dw_printf ("%s", stemp); /* stations followed by : */
/* Demystify non-APRS. Use same format for transmitted frames in xmit.c. */
if ( ! ax25_is_aprs(pp)) {
ax25_frame_type_t ftype;
cmdres_t cr;
char desc[80];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
/* Could change by 1, since earlier call, if we guess at modulo 128. */
info_len = ax25_get_info (pp, &pinfo);
dw_printf ("(%s)", desc);
if (ftype == frame_type_U_XID) {
struct xid_param_s param;
char info2text[100];
xid_parse (pinfo, info_len, &param, info2text, sizeof(info2text));
dw_printf (" %s\n", info2text);
}
else {
ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
dw_printf ("\n");
}
}
else {
// for APRS we generally want to display non-ASCII to see UTF-8. // for APRS we generally want to display non-ASCII to see UTF-8.
// for other, probably want to restrict to ASCII only because we are // for other, probably want to restrict to ASCII only because we are
// more likely to have compressed data than UTF-8 text. // more likely to have compressed data than UTF-8 text.
@ -890,6 +992,8 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) ); ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
dw_printf ("\n"); dw_printf ("\n");
}
// Also display in pure ASCII if non-ASCII characters and "-d u" option specified. // Also display in pure ASCII if non-ASCII characters and "-d u" option specified.
@ -920,18 +1024,28 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
} }
/* Decode the contents of APRS frames and display in human-readable form. */ /*
/* Suppress decoding if "-q d" option used. */ * Decode the contents of UI frames and display in human-readable form.
* Could be APRS or anything random for old fashioned packet beacons.
*
* Suppress printed decoding if "-q d" option used.
*/
if ( ( ! q_d_opt ) && ax25_is_aprs(pp)) { if (ax25_is_aprs(pp)) {
decode_aprs_t A; decode_aprs_t A;
decode_aprs (&A, pp, 0); // we still want to decode it for logging and other processing.
// Just be quiet about errors if "-qd" is set.
//Print it all out in human readable format. decode_aprs (&A, pp, q_d_opt);
if ( ! q_d_opt ) {
// Print it all out in human readable format unless "-q d" option used.
decode_aprs_print (&A); decode_aprs_print (&A);
}
/* /*
* Perform validity check on each address. * Perform validity check on each address.
@ -943,10 +1057,18 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
log_write (chan, &A, pp, alevel, retries); log_write (chan, &A, pp, alevel, retries);
// temp experiment.
//log_rr_bits (&A, pp);
// Add to list of stations heard over the radio.
mheard_save_rf (chan, &A, pp, alevel, retries);
// Convert to NMEA waypoint sentence if we have a location. // Convert to NMEA waypoint sentence if we have a location.
if (A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) { if (A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) {
nmea_send_waypoint (strlen(A.g_name) > 0 ? A.g_name : A.g_src, waypoint_send_sentence (strlen(A.g_name) > 0 ? A.g_name : A.g_src,
A.g_lat, A.g_lon, A.g_symbol_table, A.g_symbol_code, A.g_lat, A.g_lon, A.g_symbol_table, A.g_symbol_code,
DW_FEET_TO_METERS(A.g_altitude_ft), A.g_course, DW_MPH_TO_KNOTS(A.g_speed_mph), DW_FEET_TO_METERS(A.g_altitude_ft), A.g_course, DW_MPH_TO_KNOTS(A.g_speed_mph),
A.g_comment); A.g_comment);
@ -955,7 +1077,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
/* Send to another application if connected. */ /* Send to another application if connected. */
// TODO1.3: Put a wrapper around this so we only call one function to send by all methods. // TODO: Put a wrapper around this so we only call one function to send by all methods.
int flen; int flen;
unsigned char fbuf[AX25_MAX_PACKET_LEN]; unsigned char fbuf[AX25_MAX_PACKET_LEN];
@ -998,23 +1120,30 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
/* /*
* Note that the digipeater function can modify the packet in place so * APRS digipeater.
* this is the last thing we should do with it. * Use only those with correct CRC; We don't want to spread corrupted data!
* Again, use only those with correct CRC; We don't want to spread corrupted data!
* Single bit change appears to be safe from observations so far but be cautious.
*/ */
if (ax25_is_aprs(pp) && retries == RETRY_NONE) { if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
digipeater (chan, pp); digipeater (chan, pp);
} }
/*
* Connected mode digipeater.
* Use only those with correct CRC.
*/
if (retries == RETRY_NONE) {
cdigipeater (chan, pp);
}
} }
ax25_delete (pp);
} /* end app_process_rec_packet */ } /* end app_process_rec_packet */
/* Process control C and window close events. */ /* Process control C and window close events. */
#if __WIN32__ #if __WIN32__
@ -1026,6 +1155,7 @@ static BOOL cleanup_win (int ctrltype)
dw_printf ("\nQRT\n"); dw_printf ("\nQRT\n");
log_term (); log_term ();
ptt_term (); ptt_term ();
waypoint_term ();
dwgps_term (); dwgps_term ();
SLEEP_SEC(1); SLEEP_SEC(1);
ExitProcess (0); ExitProcess (0);
@ -1065,10 +1195,12 @@ static void usage (char **argv)
dw_printf (" -r n Audio sample rate, per sec.\n"); dw_printf (" -r n Audio sample rate, per sec.\n");
dw_printf (" -n n Number of audio channels, 1 or 2.\n"); dw_printf (" -n n Number of audio channels, 1 or 2.\n");
dw_printf (" -b n Bits per audio sample, 8 or 16.\n"); dw_printf (" -b n Bits per audio sample, 8 or 16.\n");
dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 9600.\n"); dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 2400, 4800, 9600.\n");
dw_printf (" If < 600, AFSK tones are set to 1600 & 1800.\n"); dw_printf (" 300 bps defaults to AFSK tones of 1600 & 1800.\n");
dw_printf (" If > 2400, K9NG/G3RUH style encoding is used.\n"); dw_printf (" 1200 bps uses AFSK tones of 1200 & 2200.\n");
dw_printf (" Otherwise, AFSK tones are set to 1200 & 2200.\n"); dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n");
dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n");
dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n");
dw_printf (" -D n Divide audio sample rate by n for channel 0.\n"); dw_printf (" -D n Divide audio sample rate by n for channel 0.\n");
dw_printf (" -d Debug options:\n"); dw_printf (" -d Debug options:\n");
dw_printf (" a a = AGWPE network protocol client.\n"); dw_printf (" a a = AGWPE network protocol client.\n");
@ -1077,9 +1209,12 @@ static void usage (char **argv)
dw_printf (" u u = Display non-ASCII text in hexadecimal.\n"); dw_printf (" u u = Display non-ASCII text in hexadecimal.\n");
dw_printf (" p p = dump Packets in hexadecimal.\n"); dw_printf (" p p = dump Packets in hexadecimal.\n");
dw_printf (" g g = GPS interface.\n"); dw_printf (" g g = GPS interface.\n");
dw_printf (" w w = Waypoints for Position or Object Reports.\n");
dw_printf (" t t = Tracker beacon.\n"); dw_printf (" t t = Tracker beacon.\n");
dw_printf (" o o = output controls such as PTT and DCD.\n"); dw_printf (" o o = output controls such as PTT and DCD.\n");
dw_printf (" i i = IGate.\n"); dw_printf (" i i = IGate.\n");
dw_printf (" m m = Monitor heard station list.\n");
dw_printf (" f f = packet Filtering.\n");
#if USE_HAMLIB #if USE_HAMLIB
dw_printf (" h h = hamlib increase verbose level.\n"); dw_printf (" h h = hamlib increase verbose level.\n");
#endif #endif

View File

@ -1,7 +1,37 @@
/* direwolf.h - Common stuff used many places. */
// TODO: include this file first before anything else in each .c file.
#ifndef DIREWOLF_H #ifndef DIREWOLF_H
#define DIREWOLF_H 1 #define DIREWOLF_H 1
/*
* Support Windows XP and later.
*
* We need this before "#include <ws2tcpip.h>".
*
* Don't know what other impact it might have on others.
*/
#if __WIN32__
#ifdef _WIN32_WINNT
#error Include "direwolf.h" before any windows system files.
#endif
#ifdef WINVER
#error Include "direwolf.h" before any windows system files.
#endif
#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */
#define WINVER 0x0501 /* Minimum OS version is XP. */
#include <windows.h>
#endif
/* /*
* Previously, we could handle only a single audio device. * Previously, we could handle only a single audio device.
@ -9,6 +39,8 @@
* In version 1.2, we relax this restriction and allow more audio devices. * In version 1.2, we relax this restriction and allow more audio devices.
* Three is probably adequate for standard version. * Three is probably adequate for standard version.
* Larger reasonable numbers should also be fine. * Larger reasonable numbers should also be fine.
*
* For example, if you wanted to use 4 audio devices at once, change this to 4.
*/ */
#define MAX_ADEVS 3 #define MAX_ADEVS 3
@ -69,7 +101,6 @@
#if __WIN32__ #if __WIN32__
#include <windows.h>
#define SLEEP_SEC(n) Sleep((n)*1000) #define SLEEP_SEC(n) Sleep((n)*1000)
#define SLEEP_MS(n) Sleep(n) #define SLEEP_MS(n) Sleep(n)
#else #else
@ -191,9 +222,8 @@ char *strsep(char **stringp, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr); char *strtok_r(char *str, const char *delim, char **saveptr);
#endif #endif
//#if __WIN32__ // Don't recall why for everyone.
char *strcasestr(const char *S, const char *FIND); char *strcasestr(const char *S, const char *FIND);
//#endif
#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
@ -203,6 +233,11 @@ char *strcasestr(const char *S, const char *FIND);
#else // Use our own copy #else // Use our own copy
// These prevent /usr/include/gps.h from providing its own definition.
#define HAVE_STRLCAT 1
#define HAVE_STRLCPY 1
#define DEBUG_STRL 1 #define DEBUG_STRL 1
#if DEBUG_STRL #if DEBUG_STRL

817
dlq.c

File diff suppressed because it is too large Load Diff

121
dlq.h
View File

@ -12,17 +12,128 @@
#include "audio.h" #include "audio.h"
void dlq_init (void); /* A transmit or receive data block for connected mode. */
typedef struct cdata_s {
int magic; /* For integrity checking. */
#define TXDATA_MAGIC 0x09110911
struct cdata_s *next; /* Pointer to next when part of a list. */
int pid; /* Protocol id. */
int size; /* Number of bytes allocated. */
int len; /* Number of bytes actually used. */
char data[]; /* Variable length data. */
} cdata_t;
/* Types of things that can be in queue. */ /* Types of things that can be in queue. */
typedef enum dlq_type_e {DLQ_REC_FRAME} dlq_type_t; typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_CLIENT_CLEANUP} dlq_type_t;
void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum);
void dlq_wait_while_empty (void); /* A queue item. */
// TODO: call this event rather than item.
// TODO: should add fences.
typedef struct dlq_item_s {
struct dlq_item_s *nextp; /* Next item in queue. */
dlq_type_t type; /* Type of item. */
/* See enum definition above. */
int chan; /* Radio channel of origin. */
// I'm not worried about amount of memory used but this might be a
// little clearer if a union was used for the different event types.
// Used for received frame.
int subchan; /* Winning "subchannel" when using multiple */
/* decoders on one channel. */
/* Special case, -1 means DTMF decoder. */
/* Maybe we should have a different type in this case? */
int slice; /* Winning slicer. */
packet_t pp; /* Pointer to frame structure. */
alevel_t alevel; /* Audio level. */
retry_t retries; /* Effort expended to get a valid CRC. */
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */
// Used by requests from a client application, connect, etc.
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
int num_addr; /* Range 2 .. 10. */
int client;
// Used only by client request to transmit connected data.
cdata_t *txdata;
// Used for channel activity change.
// It is useful to know when the channel is busy either for carrier detect
// or when we are transmitting.
int activity; /* OCTYPE_PTT for my transmission start/end. */
/* OCTYPE_DCD if we hear someone else. */
int status; /* 1 for active or 0 for quiet. */
} dlq_item_t;
void dlq_init (void);
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum);
void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid);
void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client);
void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len);
void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
void dlq_channel_busy (int chan, int activity, int status);
void dlq_client_cleanup (int client);
int dlq_wait_while_empty (double timeout_val);
struct dlq_item_s *dlq_remove (void);
void dlq_delete (struct dlq_item_s *pitem);
cdata_t *cdata_new (int pid, char *data, int len);
void cdata_delete (cdata_t *txdata);
void cdata_check_leak (void);
int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize);
#endif #endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,13 +1,14 @@
# Documentation for Dire Wolf # # Documentation for Dire Wolf #
Click on the document name to view in your web browser or the link following to download the PDF file.
## Essential Reading ## ## Essential Reading ##
- [User Guide](User-Guide.pdf) - [**User Guide**](User-Guide.pdf) [ [*download*](../../../raw/dev/doc/User-Guide.pdf) ]
This is your primary source of information about installation, operation, and configuration. This is your primary source of information about installation, operation, and configuration.
- [Raspberry Pi APRS](Raspberry-Pi-APRS.pdf) - [**Raspberry Pi APRS**](Raspberry-Pi-APRS.pdf) [ [*download*](../../../raw/dev/doc/Raspberry-Pi-APRS.pdf) ]
The Raspberry Pi has some special considerations that The Raspberry Pi has some special considerations that
make it different from other generic Linux systems. make it different from other generic Linux systems.
@ -18,37 +19,60 @@
These dive into more detail for specialized topics or typical usage scenarios. These dive into more detail for specialized topics or typical usage scenarios.
- [**Successful APRS IGate Operation**](Successful-APRS-IGate-Operation.pdf) [ [*download*](../../../raw/dev/doc/Successful-APRS-IGate-Operation.pdf) ]
- [APRStt Implementation Notes](APRStt-Implementation-Notes.pdf) Dire Wolf can serve as a gateway between the APRS radio network and APRS-IS servers on the Internet.
This explains how it all works, proper configuration, and troubleshooting.
- [**APRStt Implementation Notes**](APRStt-Implementation-Notes.pdf) [ [*download*](../../../raw/dev/doc/APRStt-Implementation-Notes.pdf) ]
Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network. Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network.
This document explains how the APRStt concept was implemented in the Dire Wolf application. This document explains how the APRStt concept was implemented in the Dire Wolf application.
- [APRStt Interface for SARTrack](APRStt-interface-for-SARTrack.pdf) - [**APRStt Interface for SARTrack**](APRStt-interface-for-SARTrack.pdf) [ [*download*](../../../raw/dev/doc/APRStt-interface-for-SARTrack.pdf) ]
This example illustrates how APRStt can be integrated with other applications such as SARTrack, APRSISCE/32, YAAC, or Xastir. This example illustrates how APRStt can be integrated with other applications such as SARTrack, APRSISCE/32, YAAC, or Xastir.
- [APRStt Listening Example](APRStt-Listening-Example.pdf) - [**APRStt Listening Example**](APRStt-Listening-Example.pdf) [ [*download*](../../../raw/dev/doc/APRStt-Listening-Example.pdf) ]
WB4APR described a useful application for the [QIKCOM-2 Satallite Transponder](http://www.tapr.org/pipermail/aprssig/2015-November/045035.html). WB4APR described a useful application for the [QIKCOM-2 Satallite Transponder](http://www.tapr.org/pipermail/aprssig/2015-November/045035.html).
Dont have your own QIKCOM-2 Satellite Transponder? No Problem. You can do the same thing with an ordinary computer and the APRStt gateway built into Dire Wolf. Heres how. Dont have your own QIKCOM-2 Satellite Transponder? No Problem. You can do the same thing with an ordinary computer and the APRStt gateway built into Dire Wolf. Heres how.
- [Raspberry Pi SDR IGate](Raspberry-Pi-SDR-IGate.pdf) - [**Raspberry Pi APRS Tracker**](Raspberry-Pi-APRS-Tracker.pdf) [ [*download*](../../../raw/dev/doc/Raspberry-Pi-APRS-Tracker.pdf) ]
Build a tracking device which transmits position from a GPS receiver.
- [**Raspberry Pi SDR IGate**](Raspberry-Pi-SDR-IGate.pdf) [ [*download*](../../../raw/dev/doc/Raspberry-Pi-SDR-IGate.pdf) ]
It's easy to build a receive-only APRS Internet Gateway (IGate) with only a Raspberry Pi and a software defined radio (RTL-SDR) dongle. Heres how. It's easy to build a receive-only APRS Internet Gateway (IGate) with only a Raspberry Pi and a software defined radio (RTL-SDR) dongle. Heres how.
- [APRS Telemetry Toolkit](APRS-Telemetry-Toolkit.pdf) - [**APRS Telemetry Toolkit**](APRS-Telemetry-Toolkit.pdf) [ [*download*](../../../raw/dev/doc/APRS-Telemetry-Toolkit.pdf) ]
Describes scripts and methods to generate telemetry. Describes scripts and methods to generate telemetry.
Includes a complete example of attaching an analog to Includes a complete example of attaching an analog to
digital converter to a Raspberry Pi and transmitting digital converter to a Raspberry Pi and transmitting
a measured voltage. a measured voltage.
- [**2400 & 4800 bps PSK for APRS / Packet Radio**](2400-4800-PSK-for-APRS-Packet-Radio.pdf) [ [*download*](../../../raw/dev/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf) ]
Double or quadruple your data rate by sending multiple bits at the same time.
- [**Going beyond 9600 baud**](Going-beyond-9600-baud.pdf) [ [*download*](../../../raw/dev/doc/Going-beyond-9600-baud.pdf) ]
Why stop at 9600 baud? Go faster if your soundcard and radio can handle it.
## Miscellaneous ## ## Miscellaneous ##
- [A Better APRS Packet Demodulator, part 1, 1200 baud](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) - [**A Better APRS Packet Demodulator, part 1, 1200 baud**](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) [ [*download*](../../../raw/dev/doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) ]
Sometimes it's a little mystifying why an Sometimes it's a little mystifying why an
APRS / AX.25 Packet TNC will decode some signals APRS / AX.25 Packet TNC will decode some signals
@ -60,7 +84,7 @@ and a couple things that can be done about it.
- [A Better APRS Packet Demodulator, part 2, 9600 baud](A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) - [**A Better APRS Packet Demodulator, part 2, 9600 baud**](A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) [ [*download*](../../../raw/dev/doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf) ]
In the first part of this series we discussed 1200 baud audio frequency shift keying (AFSK). The mismatch In the first part of this series we discussed 1200 baud audio frequency shift keying (AFSK). The mismatch
between FM transmitter pre-emphasis and the between FM transmitter pre-emphasis and the
@ -69,13 +93,28 @@ and a couple things that can be done about it.
This makes it more difficult to demodulate them accurately. This makes it more difficult to demodulate them accurately.
9600 baud operation is an entirely different animal. ... 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) - [**WA8LMF TNC Test CD Results a.k.a. Battle of the TNCs**](WA8LMF-TNC-Test-CD-Results.pdf) [ [*download*](../../../raw/dev/doc/WA8LMF-TNC-Test-CD-Results.pdf) ]
How can we compare how well the TNCs perform under real world conditions? 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 [WA8LMFs TNC Test CD](http://wa8lmf.net/TNCtest/index.htm). The de facto standard of measurement is the number of packets decoded from [WA8LMFs 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. Many have published the number of packets they have been able to decode from this test. Here they are, all gathered in one place, for your reading pleasure.
- [A Closer Look at the WA8LMF TNC Test CD](A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) - [**A Closer Look at the WA8LMF TNC Test CD**](A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) [ [*download*](../../../raw/dev/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf) ]
Here, we take a closer look at some of the frames on the TNC Test CD in hopes of gaining some insights into why some are easily decoded and others are more difficult. Here, we take a closer look at some of the frames on the TNC Test CD in hopes of gaining some insights into why some are easily decoded and others are more difficult.
There are a lot of ugly signals out there. Many can be improved by decreasing the transmit volume. Others are just plain weird and you have to wonder how they are being generated. There are a lot of ugly signals out there. Many can be improved by decreasing the transmit volume. Others are just plain weird and you have to wonder how they are being generated.
## Questions? Experiences to share? ##
Here are some good places to ask questions and share your experiences:
- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info)
- [Raspberry Pi 4 Ham Radio](https://groups.yahoo.com/neo/groups/Raspberry_Pi_4-Ham_RADIO/info)
- [linuxham](https://groups.yahoo.com/neo/groups/linuxham/info)
- [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/)
The github "issues" section is for reporting software defects and enhancement requests. It is NOT a place to ask questions or have general discussions. Please use one of the locations above.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

5
dsp.c
View File

@ -26,6 +26,8 @@
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
@ -34,7 +36,6 @@
#include <ctype.h> #include <ctype.h>
#include <assert.h> #include <assert.h>
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "fsk_demod_state.h" #include "fsk_demod_state.h"
#include "fsk_gen_filter.h" #include "fsk_gen_filter.h"
@ -51,7 +52,7 @@
// Don't remove this. It serves as a reminder that an experiment is underway. // Don't remove this. It serves as a reminder that an experiment is underway.
#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) #if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE)
#define DEBUG1 1 #define DEBUG1 1 // Don't remove this.
#endif #endif

360
dtmf.c
View File

@ -5,7 +5,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -32,23 +32,28 @@
* References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm * References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm
* http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt * http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt
* *
* Revisions: 1.4 - Added transmit capability.
*
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#include <string.h>
#include "direwolf.h"
#include "dtmf.h" #include "dtmf.h"
#include "hdlc_rec.h" // for dcd_change #include "hdlc_rec.h" // for dcd_change
#include "textcolor.h"
#include "gen_tone.h"
#if DTMF_TEST #if DTMF_TEST
#define TIMEOUT_SEC 1 /* short for unit test below. */ #define TIMEOUT_SEC 1 /* short for unit test below. */
#define DEBUG 1 #define DEBUG 1 // Don't remove this. We want more output for test.
#else #else
#define TIMEOUT_SEC 5 /* for normal operation. */ #define TIMEOUT_SEC 5 /* for normal operation. */
#endif #endif
@ -78,8 +83,11 @@ static struct dd_s { /* Separate for each audio channel. */
} dd[MAX_CHANS]; } dd[MAX_CHANS];
static int s_amplitude = 100; // range of 0 .. 100
static void push_button (int chan, char button, int ms);
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
@ -99,17 +107,23 @@ static struct dd_s { /* Separate for each audio channel. */
* In version 1.2, we can have multiple soundcards * In version 1.2, we can have multiple soundcards
* with potentially different sample rates. * with potentially different sample rates.
* *
* amp - Signal amplitude, for transmit, on scale of 0 .. 100.
*
* 100 will produce maximum amplitude of +-32k samples.
*
* Returns: None. * Returns: None.
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
void dtmf_init (struct audio_s *p_audio_config) void dtmf_init (struct audio_s *p_audio_config, int amp)
{ {
int j; /* Loop over all tones frequencies. */ int j; /* Loop over all tones frequencies. */
int c; /* Loop over all audio channels. */ int c; /* Loop over all audio channels. */
s_amplitude = amp;
/* /*
* Pick a suitable processing block size. * Pick a suitable processing block size.
* Larger = narrower bandwidth, slower response. * Larger = narrower bandwidth, slower response.
@ -118,16 +132,17 @@ void dtmf_init (struct audio_s *p_audio_config)
for (c=0; c<MAX_CHANS; c++) { for (c=0; c<MAX_CHANS; c++) {
struct dd_s *D = &(dd[c]); struct dd_s *D = &(dd[c]);
int a = ACHAN2ADEV(c); int a = ACHAN2ADEV(c);
D->sample_rate = p_audio_config->adev[a].samples_per_sec;
if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) { if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) {
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("channel %d:\n", c); dw_printf ("channel %d:\n", c);
#endif #endif
D->sample_rate = p_audio_config->adev[a].samples_per_sec;
D->block_size = (205 * D->sample_rate) / 8000; D->block_size = (205 * D->sample_rate) / 8000;
#if DEBUG #if DEBUG
dw_printf (" freq k coef \n"); dw_printf (" freq k coef \n");
#endif #endif
@ -142,9 +157,9 @@ void dtmf_init (struct audio_s *p_audio_config)
k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate); k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate);
D->coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D->block_size)); D->coef[j] = 2.0f * cosf(2.0f * (float)M_PI * (float)k / (float)(D->block_size));
assert (D->coef[j] > 0 && D->coef[j] < 2.0); assert (D->coef[j] > 0.0f && D->coef[j] < 2.0f);
#if DEBUG #if DEBUG
dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]); dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]);
#endif #endif
@ -240,7 +255,7 @@ char dtmf_sample (int c, float input)
*/ */
#define THRESHOLD 1.74 #define THRESHOLD 1.74f
if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0; if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0;
else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1; else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1;
@ -273,7 +288,9 @@ char dtmf_sample (int c, float input)
// Update Data Carrier Detect Indicator. // Update Data Carrier Detect Indicator.
#ifndef DTMF_TEST
dcd_change (c, MAX_SUBCHANS, 0, decoded != ' '); dcd_change (c, MAX_SUBCHANS, 0, decoded != ' ');
#endif
/* Reset timeout timer. */ /* Reset timeout timer. */
if (decoded != ' ') { if (decoded != ' ') {
@ -312,6 +329,182 @@ char dtmf_sample (int c, float input)
} }
/*-------------------------------------------------------------------
*
* Name: dtmf_send
*
* Purpose: Generate DTMF tones from text string.
*
* Inputs: chan - Radio channel number.
* str - Character string to send. 0-9, A-D, *, #
* speed - Number of tones per second. Range 1 to 10.
* txdelay - Delay (ms) from PTT to start.
* txtail - Delay (ms) from end to PTT off.
*
* Returns: Total number of milliseconds to activate PTT.
* This includes delays before the first tone
* and after the last to avoid chopping off part of it.
*
* Description: xmit_thread calls this instead of the usual hdlc_send
* when we have a special packet that means send DTMF.
*
*--------------------------------------------------------------------*/
int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail)
{
char *p;
int len_ms; // Length of tone or gap between.
len_ms = (int) ( ( 500.0f / (float)speed ) + 0.5f);
push_button (chan, ' ', txdelay);
for (p = str; *p != '\0'; p++) {
push_button (chan, *p, len_ms);
push_button (chan, ' ', len_ms);
}
push_button (chan, ' ', txtail);
#ifndef DTMF_TEST
audio_flush(ACHAN2ADEV(chan));
#endif
return (txdelay +
(int) (1000.0f * (float)strlen(str) / (float)speed + 0.5f) +
txtail);
} /* end dtmf_send */
/*------------------------------------------------------------------
*
* Name: push_button
*
* Purpose: Generate DTMF tone for a button push.
*
* Inputs: chan - Radio channel number.
*
* button - One of 0-9, A-D, *, #. Others result in silence.
* '?' is a special case used only for unit testing.
*
* ms - Duration in milliseconds.
* Use 50 ms for tone and 50 ms of silence for max rate of 10 per second.
*
* Outputs: Audio is sent to radio.
*
*----------------------------------------------------------------*/
static void push_button (int chan, char button, int ms)
{
float phasea = 0;
float phaseb = 0;
float fa = 0;
float fb = 0;
int i;
float dtmf; // Audio. Sum of two sine waves.
#if DTMF_TEST
char x;
static char result[100];
static int result_len = 0;
#endif
switch (button) {
case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
case 'a':
case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
case 'b':
case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
case 'c':
case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
case 'd':
case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
#if DTMF_TEST
case '?': /* check result */
if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
text_color_set(DW_COLOR_REC);
dw_printf ("\nSuccess!\n");
}
else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n * Time-out failed, otherwise OK *\n");
dw_printf ("\"%s\"\n", result);
exit (EXIT_FAILURE);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n *** TEST FAILED ***\n");
dw_printf ("\"%s\"\n", result);
exit (EXIT_FAILURE);
}
break;
#endif
}
//dw_printf ("push_button (%d, '%c', %d), fa=%.0f, fb=%.0f. %d samples\n", chan, button, ms, fa, fb, (ms*dd[chan].sample_rate)/1000);
for (i = 0; i < (ms*dd[chan].sample_rate)/1000; i++) {
// This could be more efficient with a precomputed sine wave table
// but I'm not that worried about it.
// With a Raspberry Pi, model 2, default 1200 receiving takes about 14% of one CPU core.
// When transmitting tones, it briefly shoots up to about 33%.
if (fa > 0 && fb > 0) {
dtmf = sinf(phasea) + sinf(phaseb);
phasea += 2.0f * (float)M_PI * fa / dd[chan].sample_rate;
phaseb += 2.0f * (float)M_PI * fb / dd[chan].sample_rate;
}
else {
dtmf = 0;
}
#if DTMF_TEST
/* Make sure it is insensitive to signal amplitude. */
/* (Uncomment each of below when testing.) */
x = dtmf_sample (0, dtmf);
//x = dtmf_sample (0, dtmf * 1000);
//x = dtmf_sample (0, dtmf * 0.001);
if (x != ' ' && x != '.') {
result[result_len] = x;
result_len++;
result[result_len] = '\0';
}
#else
// 'dtmf' can be in range of +-2.0 because it is sum of two sine waves.
// Amplitude of 100 would use full +-32k range.
int sam = (int)(dtmf * 16383.0f * (float)s_amplitude / 100.0f);
gen_tone_put_sample (chan, ACHAN2ADEV(chan), sam);
#endif
}
}
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Name: main * Name: main
@ -319,142 +512,79 @@ char dtmf_sample (int c, float input)
* Purpose: Unit test for functions above. * Purpose: Unit test for functions above.
* *
* Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe * Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe
* or
* make dtmftest
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
#if DTMF_TEST #if DTMF_TEST
push_button (char button, int ms)
{
static float phasea = 0;
static float phaseb = 0;
float fa, fb;
int i;
float input;
char x;
static char result[100];
static int result_len = 0;
int c = 0; // fake channel number.
switch (button) {
case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
case '?':
// TODO: why are timeouts failing. Do we care?
if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
dw_printf ("\nSuccess!\n");
}
else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
dw_printf ("\n * Time-out failed, otherwise OK *\n");
dw_printf ("\"%s\"\n", result);
}
else {
dw_printf ("\n *** TEST FAILED ***\n");
dw_printf ("\"%s\"\n", result);
}
break;
default: fa = 0; fb = 0;
}
for (i = 0; i < (ms*dd[c].sample_rate)/1000; i++) {
input = sin(phasea) + sin(phaseb);
phasea += 2 * M_PI * fa / dd[c].sample_rate;
phaseb += 2 * M_PI * fb / dd[c].sample_rate;
/* Make sure it is insensitive to signal amplitude. */
x = dtmf_sample (0, input);
//x = dtmf_sample (0, input * 1000);
//x = dtmf_sample (0, input * 0.001);
if (x != ' ' && x != '.') {
result[result_len] = x;
result_len++;
result[result_len] = '\0';
}
}
}
static struct audio_s my_audio_config; static struct audio_s my_audio_config;
main () int main ()
{ {
int c = 0; // radio channel.
memset (&my_audio_config, 0, sizeof(my_audio_config)); memset (&my_audio_config, 0, sizeof(my_audio_config));
my_audio_config.adev[0].defined = 1; my_audio_config.adev[ACHAN2ADEV(c)].defined = 1;
my_audio_config.adev[0].samples_per_sec = 44100; my_audio_config.adev[ACHAN2ADEV(c)].samples_per_sec = 44100;
my_audio_config.achan[0].valid = 1; my_audio_config.achan[c].valid = 1;
my_audio_config.achan[0].dtmf_decode = DTMF_DECODE_ON; my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON;
dtmf_init(&my_audio_config); dtmf_init(&my_audio_config, 50);
text_color_set(DW_COLOR_INFO);
dw_printf ("\nFirst, check all button tone pairs. \n\n"); dw_printf ("\nFirst, check all button tone pairs. \n\n");
/* Max auto dialing rate is 10 per second. */ /* Max auto dialing rate is 10 per second. */
push_button ('1', 50); push_button (' ', 50); push_button (c, '1', 50); push_button (c, ' ', 50);
push_button ('2', 50); push_button (' ', 50); push_button (c, '2', 50); push_button (c, ' ', 50);
push_button ('3', 50); push_button (' ', 50); push_button (c, '3', 50); push_button (c, ' ', 50);
push_button ('A', 50); push_button (' ', 50); push_button (c, 'A', 50); push_button (c, ' ', 50);
push_button ('4', 50); push_button (' ', 50); push_button (c, '4', 50); push_button (c, ' ', 50);
push_button ('5', 50); push_button (' ', 50); push_button (c, '5', 50); push_button (c, ' ', 50);
push_button ('6', 50); push_button (' ', 50); push_button (c, '6', 50); push_button (c, ' ', 50);
push_button ('B', 50); push_button (' ', 50); push_button (c, 'B', 50); push_button (c, ' ', 50);
push_button ('7', 50); push_button (' ', 50); push_button (c, '7', 50); push_button (c, ' ', 50);
push_button ('8', 50); push_button (' ', 50); push_button (c, '8', 50); push_button (c, ' ', 50);
push_button ('9', 50); push_button (' ', 50); push_button (c, '9', 50); push_button (c, ' ', 50);
push_button ('C', 50); push_button (' ', 50); push_button (c, 'C', 50); push_button (c, ' ', 50);
push_button ('*', 50); push_button (' ', 50); push_button (c, '*', 50); push_button (c, ' ', 50);
push_button ('0', 50); push_button (' ', 50); push_button (c, '0', 50); push_button (c, ' ', 50);
push_button ('#', 50); push_button (' ', 50); push_button (c, '#', 50); push_button (c, ' ', 50);
push_button ('D', 50); push_button (' ', 50); push_button (c, 'D', 50); push_button (c, ' ', 50);
text_color_set(DW_COLOR_INFO);
dw_printf ("\nShould reject very short pulses.\n\n"); dw_printf ("\nShould reject very short pulses.\n\n");
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
text_color_set(DW_COLOR_INFO);
dw_printf ("\nTest timeout after inactivity.\n\n"); dw_printf ("\nTest timeout after inactivity.\n\n");
/* For this test we use 1 second. */ /* For this test we use 1 second. */
/* In practice, it will probably more like 5. */ /* In practice, it will probably more like 5. */
push_button ('1', 250); push_button (' ', 500); push_button (c, '1', 250); push_button (c, ' ', 500);
push_button ('2', 250); push_button (' ', 500); push_button (c, '2', 250); push_button (c, ' ', 500);
push_button ('3', 250); push_button (' ', 1200); push_button (c, '3', 250); push_button (c, ' ', 1200);
push_button ('7', 250); push_button (' ', 500); push_button (c, '7', 250); push_button (c, ' ', 500);
push_button ('8', 250); push_button (' ', 500); push_button (c, '8', 250); push_button (c, ' ', 500);
push_button ('9', 250); push_button (' ', 1200); push_button (c, '9', 250); push_button (c, ' ', 1200);
/* Check for expected results. */ /* Check for expected results. */
push_button ('?', 0); push_button (c, '?', 0);
exit (EXIT_SUCCESS);
} /* end main */ } /* end main */

4
dtmf.h
View File

@ -3,10 +3,12 @@
#include "audio.h" #include "audio.h"
void dtmf_init (struct audio_s *p_audio_config); void dtmf_init (struct audio_s *p_audio_config, int amp);
char dtmf_sample (int c, float input); char dtmf_sample (int c, float input);
int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail);
/* end dtmf.h */ /* end dtmf.h */

239
dw-start.sh Normal file → Executable file
View File

@ -1,100 +1,185 @@
#!/bin/bash #!/bin/bash
#
# Run this from crontab periodically to start up # Run this from crontab periodically to start up
# Dire Wolf automatically. # Dire Wolf automatically.
#
# I prefer this method instead of putting something
# in ~/.config/autostart. That would start an application
# only when the desktop first starts up.
#
# This method will restart the application if it
# crashes or stops for any other reason.
#
# This script has some specifics the Raspberry Pi.
# Some adjustments might be needed for other Linux variations.
#
# See User Guide for more discussion.
# For release 1.4 it is section 5.7 "Automatic Start Up After Reboot"
# but it could change in the future as more information is added.
# Versioning (this file, not direwolf version)
#-----------
# v1.3 - KI6ZHD - added variable support for direwolf binary location
# v1.2 - KI6ZHD - support different versions of VNC
# v1.1 - KI6ZHD - expanded version to support running on text-only displays with
# auto support; log placement change
# v1.0 - WB2OSZ - original version for Xwindow displays only
#How are you running Direwolf : within a GUI (Xwindows / VNC) or CLI mode
# #
# When running from cron, we have a very minimal environment # AUTO mode is design to try starting direwolf with GUI support and then
# including PATH=/usr/bin:/bin. # if no GUI environment is available, it reverts to CLI support with screen
# #
# GUI mode is suited for users with the machine running LXDE/Gnome/KDE or VNC
export PATH=/usr/local/bin:$PATH # which auto-logs on (sitting at a login prompt won't work)
# First wait a little while in case we just rebooted
# and the desktop hasn't started up yet.
# #
# CLI mode is suited for say a Raspberry Pi running the Jessie LITE version
# where it will run from the CLI w/o requiring Xwindows - uses screen
sleep 30 RUNMODE=AUTO
LOGFILE=/tmp/dw-start.log
# Location of the direwolf binary. Depends on $PATH as shown.
# change this if you want to use some other specific location.
# e.g. DIREWOLF="/usr/local/bin/direwolf"
DIREWOLF="direwolf"
#Direwolf start up command :: two examples where example one is enabled
# #
# Nothing to do if it is already running. # 1. For normal operation as TNC, digipeater, IGate, etc.
#
a=`pgrep direwolf`
if [ "$a" != "" ]
then
#date >> /tmp/dw-start.log
#echo "Already running." >> $LOGFILE
exit
fi
#
# In my case, the Raspberry Pi is not connected to a monitor.
# I access it remotely using VNC as described here:
# http://learn.adafruit.com/adafruit-raspberry-pi-lesson-7-remote-control-with-vnc
#
# If VNC server is running, use its display number.
# Otherwise default to :0.
#
date >> /tmp/dw-start.log
export DISPLAY=":0"
v=`ps -ef | grep Xtightvnc | grep -v grep`
if [ "$v" != "" ]
then
d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'`
export DISPLAY="$d"
fi
echo "DISPLAY=$DISPLAY" >> $LOGFILE
echo "Start up application." >> $LOGFILE
#
# For normal operation as TNC, digipeater, IGate, etc.
# Print audio statistics each 100 seconds for troubleshooting. # Print audio statistics each 100 seconds for troubleshooting.
# Change this command to however you wish to start Direwolf
DWCMD="$DIREWOLF -a 100"
#---------------------------------------------------------------
# #
# 2. Alternative for running with SDR receiver.
DWCMD="direwolf -a 100"
# Alternative for running with SDR receiver.
# Piping one application into another makes it a little more complicated. # Piping one application into another makes it a little more complicated.
# We need to use bash for the | to be recognized. # We need to use bash for the | to be recognized.
#DWCMD="bash -c 'rtl_fm -f 144.39M - | direwolf -c sdr.conf -r 24000 -D 1 -'" #DWCMD="bash -c 'rtl_fm -f 144.39M - | direwolf -c sdr.conf -r 24000 -D 1 -'"
#
# Adjust for your particular situation: gnome-terminal, xterm, etc. #Where will logs go - needs to be writable by non-root users
# LOGFILE=/var/tmp/dw-start.log
if [ -x /usr/bin/lxterminal ] #-------------------------------------
then # Main functions of the script
/usr/bin/lxterminal -t "Dire Wolf" -e "$DWCMD" & #-------------------------------------
elif [ -x /usr/bin/xterm ]
then #Status variables
/usr/bin/xterm -bg white -fg black -e "$DWCMD" & SUCCESS=0
elif [ -x /usr/bin/x-terminal-emulator ]
then function CLI {
/usr/bin/x-terminal-emulator -e "$DWCMD" & SCREEN=`which screen`
else if [ $? -ne 0 ]; then
echo "Did not find an X terminal emulator." echo -e "Error: screen is not installed but is required for CLI mode. Aborting"
exit 1
fi fi
echo "-----------------------" >> $LOGFILE echo "Direwolf in CLI mode start up"
echo "Direwolf in CLI mode start up" >> $LOGFILE
# Screen commands
# -d m :: starts the command in detached mode
# -S :: name the session
$SCREEN -d -m -S direwolf $DWCMD >> $LOGFILE
SUCCESS=1
$SCREEN -list direwolf
$SCREEN -list direwolf >> $LOGFILE
echo "-----------------------"
echo "-----------------------" >> $LOGFILE
}
function GUI {
# In this case
# In my case, the Raspberry Pi is not connected to a monitor.
# I access it remotely using VNC as described here:
# http://learn.adafruit.com/adafruit-raspberry-pi-lesson-7-remote-control-with-vnc
#
# If VNC server is running, use its display number.
# Otherwise default to :0 (the Xwindows on the HDMI display)
#
export DISPLAY=":0"
#Reviewing for RealVNC sessions (stock in Raspbian Pixel)
if [ -n "`ps -ef | grep vncserver-x11-serviced | grep -v grep`" ]; then
sleep 0.1
echo -e "\nRealVNC found - defaults to connecting to the :0 root window"
elif [ -n "`ps -ef | grep Xtightvnc | grep -v grep`" ]; then
#Reviewing for TightVNC sessions
echo -e "\nTightVNC found - defaults to connecting to the :1 root window"
v=`ps -ef | grep Xtightvnc | grep -v grep`
d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'`
export DISPLAY="$d"
fi
echo "Direwolf in GUI mode start up"
echo "Direwolf in GUI mode start up" >> $LOGFILE
echo "DISPLAY=$DISPLAY"
echo "DISPLAY=$DISPLAY" >> $LOGFILE
#
# Auto adjust the startup for your particular environment: gnome-terminal, xterm, etc.
#
if [ -x /usr/bin/lxterminal ]; then
/usr/bin/lxterminal -t "Dire Wolf" -e "$DWCMD" &
SUCCESS=1
elif [ -x /usr/bin/xterm ]; then
/usr/bin/xterm -bg white -fg black -e "$DWCMD" &
SUCCESS=1
elif [ -x /usr/bin/x-terminal-emulator ]; then
/usr/bin/x-terminal-emulator -e "$DWCMD" &
SUCCESS=1
else
echo "Did not find an X terminal emulator. Reverting to CLI mode"
SUCCESS=0
fi
echo "-----------------------"
echo "-----------------------" >> $LOGFILE
}
# -----------------------------------------------------------
# Main Script start
# -----------------------------------------------------------
# When running from cron, we have a very minimal environment
# including PATH=/usr/bin:/bin.
#
export PATH=/usr/local/bin:$PATH
#Log the start of the script run and re-run
date >> $LOGFILE
# First wait a little while in case we just rebooted
# and the desktop hasn't started up yet.
#
sleep 30
#
# Nothing to do if Direwolf is already running.
#
a=`ps ax | grep direwolf | grep -vi -e bash -e screen -e grep | awk '{print $1}'`
if [ -n "$a" ]
then
#date >> /tmp/dw-start.log
#echo "Direwolf already running." >> $LOGFILE
exit
fi
# Main execution of the script
if [ $RUNMODE == "AUTO" ];then
GUI
if [ $SUCCESS -eq 0 ]; then
CLI
fi
elif [ $RUNMODE == "GUI" ];then
GUI
elif [ $RUNMODE == "CLI" ];then
CLI
else
echo -e "ERROR: illegal run mode given. Giving up"
exit 1
fi

View File

@ -48,13 +48,14 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "dwgps.h" #include "dwgps.h"
#include "dwgpsnmea.h" #include "dwgpsnmea.h"

View File

@ -34,6 +34,9 @@
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -48,6 +51,7 @@
#endif #endif
#if ENABLE_GPSD #if ENABLE_GPSD
#include <gps.h> #include <gps.h>
// Debian bug report: direwolf (1.2-1) FTBFS with libgps22 as part of the gpsd transition (#803605): // Debian bug report: direwolf (1.2-1) FTBFS with libgps22 as part of the gpsd transition (#803605):
@ -57,15 +61,21 @@
#error libgps API version might be incompatible. #error libgps API version might be incompatible.
#endif #endif
#endif /*
* Information for interface to gpsd daemon.
*/
static struct gps_data_t gpsdata;
#endif /* ENABLE_GPSD */
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "dwgps.h" #include "dwgps.h"
#include "dwgpsd.h" #include "dwgpsd.h"
#if ENABLE_GPSD
static int s_debug = 0; /* Enable debug output. */ static int s_debug = 0; /* Enable debug output. */
/* >= 1 show results from dwgps_read. */ /* >= 1 show results from dwgps_read. */
@ -73,11 +83,8 @@ static int s_debug = 0; /* Enable debug output. */
static void * read_gpsd_thread (void *arg); static void * read_gpsd_thread (void *arg);
/* #endif
* Information for interface to gpsd daemon.
*/
static struct gps_data_t gpsdata;
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
@ -136,7 +143,7 @@ static struct gps_data_t gpsdata;
* *
* Update: January 2016. * Update: January 2016.
* *
* I'm told that it might work in Raspian, Jessie version. * I'm told that the shared memory interface might work in Raspian, Jessie version.
* Haven't tried it yet. * Haven't tried it yet.
*/ */
@ -152,7 +159,6 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug)
int err; int err;
int arg = 0; int arg = 0;
char sport[12]; char sport[12];
dwgps_info_t info;
s_debug = debug; s_debug = debug;
@ -174,7 +180,6 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug)
snprintf (sport, sizeof(sport), "%d", pconfig->gpsd_port); snprintf (sport, sizeof(sport), "%d", pconfig->gpsd_port);
err = gps_open (pconfig->gpsd_host, sport, &gpsdata); err = gps_open (pconfig->gpsd_host, sport, &gpsdata);
if (err != 0) { if (err != 0) {
dwgps_info_t info;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Unable to connect to GPSD stream at %s:%s.\n", pconfig->gpsd_host, sport); dw_printf ("Unable to connect to GPSD stream at %s:%s.\n", pconfig->gpsd_host, sport);
@ -330,7 +335,7 @@ static void * read_gpsd_thread (void *arg)
return(0); // Terminate thread on serious error. return(0); // Terminate thread on serious error.
} /* end read_gps_thread */ } /* end read_gpsd_thread */
#endif #endif

View File

@ -29,6 +29,9 @@
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -38,7 +41,6 @@
#include <time.h> #include <time.h>
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "dwgps.h" #include "dwgps.h"
#include "dwgpsnmea.h" #include "dwgpsnmea.h"
@ -48,6 +50,7 @@
static int s_debug = 0; /* Enable debug output. */ static int s_debug = 0; /* Enable debug output. */
/* See dwgpsnmea_init description for values. */ /* See dwgpsnmea_init description for values. */
static struct misc_config_s *s_save_configp;
@ -105,10 +108,12 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug)
HANDLE read_gps_th; HANDLE read_gps_th;
#else #else
pthread_t read_gps_tid; pthread_t read_gps_tid;
int e; //int e;
#endif #endif
s_debug = debug; s_debug = debug;
s_save_configp = pconfig;
if (s_debug >= 2) { if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
@ -160,6 +165,19 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug)
} /* end dwgpsnmea_init */ } /* end dwgpsnmea_init */
/* Return fd to share if waypoint wants same device. */
/* Currently both are fixed speed at 4800. */
/* If that ever becomes configurable, that needs to be compared too. */
MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed)
{
if (strcmp(s_save_configp->gpsnmea_port, wp_port_name) == 0 && speed == 4800) {
return (s_gpsnmea_port_fd);
}
return (MYFDERROR);
}
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: read_gpsnmea_thread * Name: read_gpsnmea_thread

View File

@ -1,5 +1,5 @@
/* dwgpsnmea.h - For NMEA sentences over serial port */ /* dwgpsnmea.h - For reading NMEA sentences over serial port */
@ -8,10 +8,13 @@
#include "dwgps.h" /* for dwfix_t */ #include "dwgps.h" /* for dwfix_t */
#include "config.h" #include "config.h"
#include "serial_port.h" /* for MYFDTYPE */
int dwgpsnmea_init (struct misc_config_s *pconfig, int debug); int dwgpsnmea_init (struct misc_config_s *pconfig, int debug);
MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed);
void dwgpsnmea_term (void); void dwgpsnmea_term (void);
@ -19,6 +22,7 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon
dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat); dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat);
#endif #endif

View File

@ -33,6 +33,8 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@ -42,7 +44,6 @@
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#include "direwolf.h"
#include "encode_aprs.h" #include "encode_aprs.h"
#include "latlong.h" #include "latlong.h"
#include "textcolor.h" #include "textcolor.h"
@ -598,7 +599,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
result_len += strlen(comment); result_len += strlen(comment);
} }
if (result_len >= result_size) { if (result_len >= (int)result_size) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("encode_position result of %d characters won't fit into space provided.\n", result_len); dw_printf ("encode_position result of %d characters won't fit into space provided.\n", result_len);
} }
@ -682,7 +683,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
memset (p->o.name, ' ', sizeof(p->o.name)); memset (p->o.name, ' ', sizeof(p->o.name));
n = strlen(name); n = strlen(name);
if (n > sizeof(p->o.name)) n = sizeof(p->o.name); if (n > (int)(sizeof(p->o.name))) n = sizeof(p->o.name);
memcpy (p->o.name, name, n); memcpy (p->o.name, name, n);
p->o.live_killed = '*'; p->o.live_killed = '*';
@ -692,7 +693,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
#define XMIT_UTC 1 #define XMIT_UTC 1
#if XMIT_UTC #if XMIT_UTC
gmtime_r (&thyme, &tm); (void)gmtime_r (&thyme, &tm);
#else #else
/* Using local time, for this application, would make more sense to me. */ /* Using local time, for this application, would make more sense to me. */
/* On Windows, localtime_r produces UTC. */ /* On Windows, localtime_r produces UTC. */
@ -748,7 +749,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
result_len += strlen(comment); result_len += strlen(comment);
} }
if (result_len >= result_size) { if (result_len >= (int)result_size) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("encode_object result of %d characters won't fit into space provided.\n", result_len); dw_printf ("encode_object result of %d characters won't fit into space provided.\n", result_len);
} }

View File

@ -18,6 +18,8 @@
// //
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
/* /*

View File

@ -4,9 +4,11 @@
#include "rpack.h" #include "rpack.h"
#include "audio.h" // for enum modem_t
/* /*
* Demodulator state. * Demodulator state.
* The name of the file is from we only had FSK. Now we have other techniques.
* Different copy is required for each channel & subchannel being processed concurrently. * Different copy is required for each channel & subchannel being processed concurrently.
*/ */
@ -24,6 +26,7 @@ struct demodulator_state_s
/* /*
* These are set once during initialization. * These are set once during initialization.
*/ */
enum modem_t modem_type; // MODEM_AFSK, MODEM_8PSK, etc.
char profile; // 'A', 'B', etc. Upper case. char profile; // 'A', 'B', etc. Upper case.
// Only needed to see if we are using 'F' to take fast path. // Only needed to see if we are using 'F' to take fast path.
@ -132,10 +135,28 @@ struct demodulator_state_s
float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
/*
* These are for PSK only.
* They are number of delay line taps into previous symbol.
* They are one symbol period and + or - 45 degrees of the carrier frequency.
*/
int boffs; /* symbol length based on sample rate and baud. */
int coffs; /* to get cos component of previous symbol. */
int soffs; /* to get sin component of previous symbol. */
unsigned int lo_step; /* How much to advance the local oscillator */
/* phase for each audio sample. */
int psk_use_lo; /* Use local oscillator rather than self correlation. */
/* /*
* The rest are continuously updated. * The rest are continuously updated.
*/ */
unsigned int lo_phase; /* Local oscillator for PSK. */
/* /*
* Most recent raw audio samples, before/after prefiltering. * Most recent raw audio samples, before/after prefiltering.
*/ */
@ -213,6 +234,7 @@ struct demodulator_state_s
int prev_demod_data; // Previous data bit detected. int prev_demod_data; // Previous data bit detected.
// Used to look for transitions. // Used to look for transitions.
float prev_demod_out_f;
/* This is used only for "9600" baud data. */ /* This is used only for "9600" baud data. */

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -60,7 +60,7 @@
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -74,8 +74,20 @@
#include "gen_tone.h" #include "gen_tone.h"
#include "textcolor.h" #include "textcolor.h"
#include "morse.h" #include "morse.h"
#include "dtmf.h"
/* Own random number generator so we can get */
/* same results on Windows and Linux. */
#define MY_RAND_MAX 0x7fffffff
static int seed = 1;
static int my_rand (void) {
seed = ((seed * 1103515245) + 12345) & MY_RAND_MAX;
return (seed);
}
static void usage (char **argv); static void usage (char **argv);
static int audio_file_open (char *fname, struct audio_s *pa); static int audio_file_open (char *fname, struct audio_s *pa);
static int audio_file_close (void); static int audio_file_close (void);
@ -95,9 +107,10 @@ static void send_packet (char *str)
int flen; int flen;
int c; int c;
if (g_morse_wpm > 0) { if (g_morse_wpm > 0) {
// TODO: Why not use the destination field instead of command line option?
morse_send (0, str, g_morse_wpm, 100, 100); morse_send (0, str, g_morse_wpm, 100, 100);
} }
else { else {
@ -105,8 +118,33 @@ static void send_packet (char *str)
flen = ax25_pack (pp, fbuf); flen = ax25_pack (pp, fbuf);
for (c=0; c<modem.adev[0].num_channels; c++) for (c=0; c<modem.adev[0].num_channels; c++)
{ {
#if 1
int samples_per_symbol, n, j;
/* Insert random amount of quiet time, approx. 0 to 10 symbol times, to test */
/* how well the clock recovery PLL can regain lock after random phase shifts. */
if (modem.achan[c].modem_type == MODEM_QPSK) {
samples_per_symbol = modem.adev[0].samples_per_sec / (modem.achan[c].baud / 2);
}
else if (modem.achan[c].modem_type == MODEM_8PSK) {
samples_per_symbol = modem.adev[0].samples_per_sec / (modem.achan[c].baud / 3);
}
else {
samples_per_symbol = modem.adev[0].samples_per_sec / modem.achan[c].baud;
}
// for 1200 baud, 44100/sec, this should be 0 to 360.
n = samples_per_symbol * 10 * (float)my_rand() / (float)MY_RAND_MAX;
//dw_printf ("Random 0-360 = %d\n", n);
for (j=0; j<n; j++) {
gen_tone_put_sample (c, 0, 0);
}
#endif
hdlc_send_flags (c, 8, 0); hdlc_send_flags (c, 8, 0);
hdlc_send_frame (c, fbuf, flen); hdlc_send_frame (c, fbuf, flen, 0);
hdlc_send_flags (c, 2, 1); hdlc_send_flags (c, 2, 1);
} }
ax25_delete (pp); ax25_delete (pp);
@ -123,6 +161,8 @@ int main(int argc, char **argv)
int packet_count = 0; int packet_count = 0;
int i; int i;
int chan; int chan;
int experiment = 0;
/* /*
* Set up default values for the modem. * Set up default values for the modem.
@ -174,7 +214,7 @@ int main(int argc, char **argv)
/* ':' following option character means arg is required. */ /* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "gm:s:a:b:B:r:n:o:z:82M:", c = getopt_long(argc, argv, "gm:s:a:b:B:r:n:o:z:82M:X",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -196,9 +236,9 @@ int main(int argc, char **argv)
modem.achan[0].baud = atoi(optarg); modem.achan[0].baud = atoi(optarg);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud); dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) { if (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
break; break;
@ -208,29 +248,55 @@ int main(int argc, char **argv)
/* 1200 implies 1200/2200 AFSK. */ /* 1200 implies 1200/2200 AFSK. */
/* 9600 implies scrambled. */ /* 9600 implies scrambled. */
/* If you want something else, specify -B first */
/* then anything to override these defaults. */
modem.achan[0].baud = atoi(optarg); modem.achan[0].baud = atoi(optarg);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud); dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
if (modem.achan[0].baud < 100 || modem.achan[0].baud > 10000) { if (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n"); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
switch (modem.achan[0].baud) { /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
case 300: /* that need to be kept in sync. Maybe it could be a common function someday. */
modem.achan[0].mark_freq = 1600;
if (modem.achan[0].baud < 600) {
modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1600; // Typical for HF SSB
modem.achan[0].space_freq = 1800; modem.achan[0].space_freq = 1800;
break; }
case 1200: else if (modem.achan[0].baud < 1800) {
modem.achan[0].mark_freq = 1200; modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].space_freq = 2200; modem.achan[0].mark_freq = DEFAULT_MARK_FREQ;
break; modem.achan[0].space_freq = DEFAULT_SPACE_FREQ;
case 9600: }
else if (modem.achan[0].baud < 3600) {
modem.achan[0].modem_type = MODEM_QPSK;
modem.achan[0].mark_freq = 0;
modem.achan[0].space_freq = 0;
dw_printf ("Using V.26 QPSK rather than AFSK.\n");
if (modem.achan[0].baud != 2400) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Bit rate should be standard 2400 rather than specified %d.\n", modem.achan[0].baud);
}
}
else if (modem.achan[0].baud < 7200) {
modem.achan[0].modem_type = MODEM_8PSK;
modem.achan[0].mark_freq = 0;
modem.achan[0].space_freq = 0;
dw_printf ("Using V.27 8PSK rather than AFSK.\n");
if (modem.achan[0].baud != 4800) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", modem.achan[0].baud);
}
}
else {
modem.achan[0].modem_type = MODEM_SCRAMBLE; modem.achan[0].modem_type = MODEM_SCRAMBLE;
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
break;
} }
break; break;
@ -341,6 +407,7 @@ int main(int argc, char **argv)
case 'M': /* -M for morse code speed */ case 'M': /* -M for morse code speed */
//TODO: document this. //TODO: document this.
// Why not base it on the destination field instead?
g_morse_wpm = atoi(optarg); g_morse_wpm = atoi(optarg);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
@ -352,6 +419,11 @@ int main(int argc, char **argv)
} }
break; break;
case 'X':
experiment = 1;
break;
case '?': case '?':
/* Unknown option message was already printed. */ /* Unknown option message was already printed. */
@ -389,16 +461,99 @@ int main(int argc, char **argv)
} }
if (experiment) {
modem.achan[0].modem_type = MODEM_QPSK;
modem.achan[0].baud = 2400; // really bps not baud.
amplitude = 100;
}
gen_tone_init (&modem, amplitude/2, 1);
gen_tone_init (&modem, amplitude/2);
morse_init (&modem, amplitude/2); morse_init (&modem, amplitude/2);
dtmf_init (&modem, amplitude/2);
assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16); 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].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); assert (modem.adev[0].samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC);
if (experiment) {
int chan = 0;
int n;
// 6 cycles of 1800 Hz.
for (n=0; n<8; n++) {
tone_gen_put_bit (chan, 0);
}
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 90
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
// Shift 180
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
// Shift 270
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// HDLC flag - six 1 in a row.
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0); // reverse even/odd position
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 1);
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
// Shift 0
tone_gen_put_bit (chan, 0);
tone_gen_put_bit (chan, 0);
audio_file_close ();
return (EXIT_SUCCESS);
}
/* /*
* Get user packets(s) from file or stdin if specified. * Get user packets(s) from file or stdin if specified.
* "-n" option is ignored in this case. * "-n" option is ignored in this case.
@ -466,17 +621,28 @@ int main(int argc, char **argv)
char stemp[80]; char stemp[80];
if (modem.achan[0].modem_type == MODEM_SCRAMBLE) { if (modem.achan[0].baud < 600) {
g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count); /* e.g. 300 bps AFSK - About 2/3 should be decoded properly. */
}
else if (modem.achan[0].baud < 600) {
/* About 2/3 should be decoded properly. */
g_noise_level = amplitude *.0048 * ((float)i / packet_count); g_noise_level = amplitude *.0048 * ((float)i / packet_count);
} }
else { else if (modem.achan[0].baud < 1800) {
/* About 2/3 should be decoded properly. */ /* e.g. 1200 bps AFSK - About 2/3 should be decoded properly. */
g_noise_level = amplitude *.0023 * ((float)i / packet_count); g_noise_level = amplitude *.0023 * ((float)i / packet_count);
} }
else if (modem.achan[0].baud < 3600) {
/* e.g. 2400 bps QPSK - T.B.D. */
g_noise_level = amplitude *.0015 * ((float)i / packet_count);
}
else if (modem.achan[0].baud < 7200) {
/* e.g. 4800 bps - T.B.D. */
g_noise_level = amplitude *.0007 * ((float)i / packet_count);
}
else {
/* e.g. 9600 */
g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count);
// temp test
//g_noise_level = 0.20 * (amplitude / 200.0) * ((float)i / packet_count);
}
snprintf (stemp, sizeof(stemp), "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! %04d of %04d", i, packet_count); snprintf (stemp, sizeof(stemp), "WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! %04d of %04d", i, packet_count);
@ -511,15 +677,15 @@ static void usage (char **argv)
dw_printf ("Options:\n"); dw_printf ("Options:\n");
dw_printf (" -a <number> Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -a <number> Signal amplitude in range of 0 - 200%%. Default 50.\n");
dw_printf (" -b <number> Bits / second for data. Default is %d.\n", DEFAULT_BAUD); dw_printf (" -b <number> Bits / second for data. Default is %d.\n", DEFAULT_BAUD);
dw_printf (" -B <number> Bits / second for data. Proper modem selected for 300, 1200, 9600.\n"); dw_printf (" -B <number> Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n");
dw_printf (" -g Scrambled baseband rather than AFSK.\n"); dw_printf (" -g Scrambled baseband rather than AFSK.\n");
dw_printf (" -m <number> Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ); dw_printf (" -m <number> Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ);
dw_printf (" -s <number> Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ); dw_printf (" -s <number> Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ);
dw_printf (" -r <number> Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC); dw_printf (" -r <number> Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);
dw_printf (" -n <number> Generate specified number of frames with increasing noise.\n"); dw_printf (" -n <number> Generate specified number of frames with increasing noise.\n");
dw_printf (" -o <file> Send output to .wav file.\n"); dw_printf (" -o <file> Send output to .wav file.\n");
// dw_printf (" -8 8 bit audio rather than 16.\n"); dw_printf (" -8 8 bit audio rather than 16.\n");
// dw_printf (" -2 2 channels of audio rather than 1.\n"); dw_printf (" -2 2 channels (stereo) audio rather than one channel.\n");
// dw_printf (" -z <number> Number of leading zero bits before frame.\n"); // dw_printf (" -z <number> Number of leading zero bits before frame.\n");
// dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n"); // dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n");
@ -690,14 +856,6 @@ static int audio_file_open (char *fname, struct audio_s *pa)
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
#define MY_RAND_MAX 0x7fffffff
static int seed = 1;
static int my_rand (void) {
seed = ((seed * 1103515245) + 12345) & MY_RAND_MAX;
return (seed);
}
int audio_put (int a, int c) int audio_put (int a, int c)
{ {
@ -803,3 +961,11 @@ static int audio_file_close (void)
} /* end audio_close */ } /* end audio_close */
// To keep dtmf.c happy.
#include "hdlc_rec.h" // for dcd_change
void dcd_change (int chan, int subchan, int slice, int state)
{
}

View File

@ -1,7 +1,10 @@
//#define DEBUG 1
//#define DEBUG2 1
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2011, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -28,6 +31,9 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <unistd.h> #include <unistd.h>
@ -35,7 +41,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "gen_tone.h" #include "gen_tone.h"
#include "textcolor.h" #include "textcolor.h"
@ -47,7 +53,7 @@
// Properties of the digitized sound stream & modem. // Properties of the digitized sound stream & modem.
static struct audio_s *save_audio_config_p; static struct audio_s *save_audio_config_p = NULL;
/* /*
* 8 bit samples are unsigned bytes in range of 0 .. 255. * 8 bit samples are unsigned bytes in range of 0 .. 255.
@ -68,6 +74,7 @@ static int ticks_per_bit[MAX_CHANS];
static int f1_change_per_sample[MAX_CHANS]; static int f1_change_per_sample[MAX_CHANS];
static int f2_change_per_sample[MAX_CHANS]; static int f2_change_per_sample[MAX_CHANS];
static short sine_table[256]; static short sine_table[256];
@ -76,10 +83,28 @@ static short sine_table[256];
static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation. static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation.
// Upper bits are used as index into sine table. // Upper bits are used as index into sine table.
#define PHASE_SHIFT_180 ( 128u << 24 )
#define PHASE_SHIFT_90 ( 64u << 24 )
#define PHASE_SHIFT_45 ( 32u << 24 )
static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit. static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit.
static int lfsr[MAX_CHANS]; // Shift register for scrambler. static int lfsr[MAX_CHANS]; // Shift register for scrambler.
static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted
// on the channel. This is only used for QPSK.
// The LSB determines if we save the bit until
// next time, or send this one with the previously saved.
// The LSB+1 position determines if we add an
// extra 180 degrees to the phase to compensate
// for having 1.5 carrier cycles per symbol time.
// For 8PSK, it has a different meaning. It is the
// number of bits in 'save_bit' so we can accumulate
// three for each symbol.
static int save_bit[MAX_CHANS];
/* /*
* The K9NG/G3RUH output originally took a very simple and lazy approach. * The K9NG/G3RUH output originally took a very simple and lazy approach.
@ -155,6 +180,9 @@ static int resample[MAX_CHANS];
* *
* amp - Signal amplitude on scale of 0 .. 100. * amp - Signal amplitude on scale of 0 .. 100.
* *
* gen_packets - True if being called from "gen_packets" utility
* rather than the "direwolf" application.
*
* Returns: 0 for success. * Returns: 0 for success.
* -1 for failure. * -1 for failure.
* *
@ -166,11 +194,17 @@ static int resample[MAX_CHANS];
static int amp16bit; /* for 9600 baud */ static int amp16bit; /* for 9600 baud */
int gen_tone_init (struct audio_s *audio_config_p, int amp) int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
{ {
int j; int j;
int chan = 0; int chan = 0;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("gen_tone_init ( audio_config_p=%p, amp=%d, gen_packets=%d )\n",
audio_config_p, amp, gen_packets);
#endif
/* /*
* Save away modem parameters for later use. * Save away modem parameters for later use.
*/ */
@ -187,19 +221,58 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
int a = ACHAN2ADEV(chan); int a = ACHAN2ADEV(chan);
ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); #if DEBUG
text_color_set(DW_COLOR_DEBUG);
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); dw_printf ("gen_tone_init: chan=%d, modem_type=%d, bps=%d, samples_per_sec=%d\n",
chan,
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); save_audio_config_p->achan[chan].modem_type,
audio_config_p->achan[chan].baud,
f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); audio_config_p->adev[a].samples_per_sec);
#endif
tone_phase[chan] = 0; tone_phase[chan] = 0;
bit_len_acc[chan] = 0; bit_len_acc[chan] = 0;
lfsr[chan] = 0; lfsr[chan] = 0;
ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
// The terminology is all wrong here. Didn't matter with 1200 and 9600.
// The config speed should be bits per second rather than baud.
// ticks_per_bit should be ticks_per_symbol.
switch (save_audio_config_p->achan[chan].modem_type) {
case MODEM_QPSK:
audio_config_p->achan[chan].mark_freq = 1800;
audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used.
// symbol time is 1 / (half of bps)
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud * 0.5)) + 0.5);
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
tone_phase[chan] = PHASE_SHIFT_45; // Just to mimic first attempt.
break;
case MODEM_8PSK:
audio_config_p->achan[chan].mark_freq = 1800;
audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used.
// symbol time is 1 / (third of bps)
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud / 3.)) + 0.5);
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
break;
default:
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5);
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
break;
}
} }
} }
@ -210,10 +283,18 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
a = ((double)(j) / 256.0) * (2 * M_PI); a = ((double)(j) / 256.0) * (2 * M_PI);
s = (int) (sin(a) * 32767 * amp / 100.0); s = (int) (sin(a) * 32767 * amp / 100.0);
/* 16 bit sound sample is in range of -32768 .. +32767. */ /* 16 bit sound sample must fit in range of -32768 .. +32767. */
assert (s >= -32768 && s <= 32767);
if (s < -32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("gen_tone_init: Excessive amplitude is being clipped.\n");
s = -32768;
}
else if (s > 32767) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("gen_tone_init: Excessive amplitude is being clipped.\n");
s = 32767;
}
sine_table[j] = s; sine_table[j] = s;
} }
@ -236,11 +317,26 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
float filter_len_bits = 88 * 9600.0 / (44100.0 * 2.0); float filter_len_bits = 88 * 9600.0 / (44100.0 * 2.0);
/* Filter length in number of data bits. */ /* Filter length in number of data bits. */
/* Currently 9.58 */
float lpf_baud = 0.8; /* Lowpass cutoff freq as fraction of baud rate */ float lpf_baud = 0.8; /* Lowpass cutoff freq as fraction of baud rate */
float fc; /* Cutoff frequency as fraction of sampling frequency. */ float fc; /* Cutoff frequency as fraction of sampling frequency. */
/*
* Normally, we want to generate the same thing whether sending over the air
* or putting it into a file for other testing.
* (There is an important exception. gen_packets can introduce random noise.)
* In this case, we want more aggressive low pass filtering so it looks more like
* what we see coming out of a receiver.
* Specifically, single bits of the same state have considerably reduced amplitude
* below several same values in a row.
*/
if (gen_packets) {
filter_len_bits = 4;
lpf_baud = 0.55; /* Lowpass cutoff freq as fraction of baud rate */
}
samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE; samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE;
baud = audio_config_p->achan[chan].baud; baud = audio_config_p->achan[chan].baud;
@ -250,10 +346,15 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5); lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5);
if (lp_filter_size[chan] < 10 || lp_filter_size[chan] > MAX_FILTER_SIZE) { if (lp_filter_size[chan] < 10) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_DEBUG);
dw_printf ("gen_tone_init: INTERNAL ERROR, chan %d, lp_filter_size %d\n", chan, lp_filter_size[chan]); dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d < 10\n", chan, lp_filter_size[chan]);
lp_filter_size[chan] = MAX_FILTER_SIZE / 2; lp_filter_size[chan] = 10;
}
else if (lp_filter_size[chan] > MAX_FILTER_SIZE) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d > %d\n", chan, lp_filter_size[chan], MAX_FILTER_SIZE);
lp_filter_size[chan] = MAX_FILTER_SIZE;
} }
fc = (float)baud * lpf_baud / (float)samples_per_sec; fc = (float)baud * lpf_baud / (float)samples_per_sec;
@ -273,7 +374,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: gen_tone_put_bit * Name: tone_gen_put_bit
* *
* Purpose: Generate tone of proper duration for one data bit. * Purpose: Generate tone of proper duration for one data bit.
* *
@ -286,14 +387,19 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
* *
* Assumption: fp is open to a file for write. * Assumption: fp is open to a file for write.
* *
* Version 1.4: Attempt to implement 2400 and 4800 bps PSK modes.
*
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static const int gray2phase_v26[4] = {0, 1, 3, 2};
static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4};
void tone_gen_put_bit (int chan, int dat) void tone_gen_put_bit (int chan, int dat)
{ {
int a = ACHAN2ADEV(chan); /* device for channel. */ int a = ACHAN2ADEV(chan); /* device for channel. */
assert (save_audio_config_p != NULL);
assert (save_audio_config_p->achan[chan].valid); assert (save_audio_config_p->achan[chan].valid);
@ -303,6 +409,55 @@ void tone_gen_put_bit (int chan, int dat)
dat = 0; dat = 0;
} }
if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) {
int dibit;
int symbol;
dat &= 1; // Keep only LSB to be extra safe.
if ( ! (bit_count[chan] & 1)) {
save_bit[chan] = dat;
bit_count[chan]++;
return;
}
// All zero bits should give us steady 1800 Hz.
// All one bits should flip phase by 180 degrees each time.
dibit = (save_bit[chan] << 1) | dat;
symbol = gray2phase_v26[dibit];
tone_phase[chan] += symbol * PHASE_SHIFT_90;
bit_count[chan]++;
}
if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) {
int tribit;
int symbol;
dat &= 1; // Keep only LSB to be extra safe.
if (bit_count[chan] < 2) {
save_bit[chan] = (save_bit[chan] << 1) | dat;
bit_count[chan]++;
return;
}
// The bit pattern 001 should give us steady 1800 Hz.
// All one bits should flip phase by 180 degrees each time.
tribit = (save_bit[chan] << 1) | dat;
symbol = gray2phase_v27[tribit];
tone_phase[chan] += symbol * PHASE_SHIFT_45;
save_bit[chan] = 0;
bit_count[chan] = 0;
}
if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) { if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) {
int x; int x;
@ -311,18 +466,44 @@ void tone_gen_put_bit (int chan, int dat)
dat = x; dat = x;
} }
do { do { /* until enough audio samples for this symbol. */
if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) {
int sam; int sam;
float fsam;
switch (save_audio_config_p->achan[chan].modem_type) {
case MODEM_AFSK:
#if DEBUG2
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tone_gen_put_bit %d AFSK\n", __LINE__);
#endif
tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan]; tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan];
sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
gen_tone_put_sample (chan, a, sam); gen_tone_put_sample (chan, a, sam);
} break;
else {
float fsam = dat ? amp16bit : (-amp16bit); case MODEM_QPSK:
case MODEM_8PSK:
#if DEBUG2
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__);
#endif
tone_phase[chan] += f1_change_per_sample[chan];
sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
gen_tone_put_sample (chan, a, sam);
break;
case MODEM_BASEBAND:
case MODEM_SCRAMBLE:
#if DEBUG2
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tone_gen_put_bit %d SCR\n", __LINE__);
#endif
fsam = dat ? amp16bit : (-amp16bit);
/* version 1.2 - added a low pass filter instead of square wave out. */ /* version 1.2 - added a low pass filter instead of square wave out. */
@ -330,12 +511,18 @@ void tone_gen_put_bit (int chan, int dat)
resample[chan]++; resample[chan]++;
if (resample[chan] >= UPSAMPLE) { if (resample[chan] >= UPSAMPLE) {
int sam;
sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]);
resample[chan] = 0; resample[chan] = 0;
gen_tone_put_sample (chan, a, sam); gen_tone_put_sample (chan, a, sam);
} }
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: %s %d achan[%d].modem_type = %d\n",
__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].modem_type);
exit (EXIT_FAILURE);
} }
/* Enough for the bit time? */ /* Enough for the bit time? */
@ -351,12 +538,16 @@ void tone_gen_put_bit (int chan, int dat)
void gen_tone_put_sample (int chan, int a, int sam) { void gen_tone_put_sample (int chan, int a, int sam) {
/* Ship out an audio sample. */ /* Ship out an audio sample. */
/* 16 bit is signed, little endian, range -32768 .. +32767 */
/* 8 bit is unsigned, range 0 .. 255 */
assert (save_audio_config_p != NULL);
assert (save_audio_config_p->adev[a].num_channels == 1 || save_audio_config_p->adev[a].num_channels == 2); assert (save_audio_config_p->adev[a].num_channels == 1 || save_audio_config_p->adev[a].num_channels == 2);
/* Generalize to allow 8 bits someday? */ assert (save_audio_config_p->adev[a].bits_per_sample == 16 || save_audio_config_p->adev[a].bits_per_sample == 8);
assert (save_audio_config_p->adev[a].bits_per_sample == 16); // TODO: Should print message telling user to reduce output level.
if (sam < -32767) sam = -32767; if (sam < -32767) sam = -32767;
else if (sam > 32767) sam = 32767; else if (sam > 32767) sam = 32767;
@ -365,25 +556,41 @@ void gen_tone_put_sample (int chan, int a, int sam) {
/* Mono */ /* Mono */
if (save_audio_config_p->adev[a].bits_per_sample == 8) {
audio_put (a, ((sam+32768) >> 8) & 0xff);
}
else {
audio_put (a, sam & 0xff); audio_put (a, sam & 0xff);
audio_put (a, (sam >> 8) & 0xff); audio_put (a, (sam >> 8) & 0xff);
} }
}
else { else {
if (chan == ADEVFIRSTCHAN(a)) { if (chan == ADEVFIRSTCHAN(a)) {
/* Stereo, left channel. */ /* Stereo, left channel. */
if (save_audio_config_p->adev[a].bits_per_sample == 8) {
audio_put (a, ((sam+32768) >> 8) & 0xff);
audio_put (a, 0);
}
else {
audio_put (a, sam & 0xff); audio_put (a, sam & 0xff);
audio_put (a, (sam >> 8) & 0xff); audio_put (a, (sam >> 8) & 0xff);
audio_put (a, 0); audio_put (a, 0);
audio_put (a, 0); audio_put (a, 0);
} }
}
else { else {
/* Stereo, right channel. */ /* Stereo, right channel. */
if (save_audio_config_p->adev[a].bits_per_sample == 8) {
audio_put (a, 0);
audio_put (a, ((sam+32768) >> 8) & 0xff);
}
else {
audio_put (a, 0); audio_put (a, 0);
audio_put (a, 0); audio_put (a, 0);
@ -392,6 +599,7 @@ void gen_tone_put_sample (int chan, int a, int sam) {
} }
} }
} }
}

View File

@ -3,7 +3,7 @@
*/ */
int gen_tone_init (struct audio_s *pp, int amp); int gen_tone_init (struct audio_s *pp, int amp, int gen_packets);
//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname); //int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);

View File

@ -577,6 +577,9 @@ long UTM_To_USNG (long Zone,
Northing = 0.0; Northing = 0.0;
} }
ltr2_low_value = LETTER_A; // Make compiler shut up about possibly uninitialized value.
// It should be set by the following but compiler doesn't know.
USNG_Get_Grid_Values(Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset); USNG_Get_Grid_Values(Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
error_code = USNG_Get_Latitude_Letter(Latitude, &letters[0]); error_code = USNG_Get_Latitude_Letter(Latitude, &letters[0]);
@ -960,6 +963,9 @@ long Convert_USNG_To_UTM (char *USNG,
else else
*Hemisphere = 'N'; *Hemisphere = 'N';
ltr2_low_value = LETTER_A; // Make compiler shut up about possibly uninitialized values.
ltr2_high_value = LETTER_Z; // They should be set by the following but compiler doesn't know.
USNG_Get_Grid_Values(*Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset); USNG_Get_Grid_Values(*Zone, &ltr2_low_value, &ltr2_high_value, &pattern_offset);
/* Check that the second letter of the USNG string is within /* Check that the second letter of the USNG string is within

View File

@ -27,11 +27,12 @@
* *
*******************************************************************************/ *******************************************************************************/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "demod.h" #include "demod.h"
#include "hdlc_rec.h" #include "hdlc_rec.h"
#include "hdlc_rec2.h" #include "hdlc_rec2.h"
@ -46,6 +47,7 @@
//#define TEST 1 /* Define for unit testing. */ //#define TEST 1 /* Define for unit testing. */
//#define DEBUG3 1 /* monitor the data detect signal. */ //#define DEBUG3 1 /* monitor the data detect signal. */
@ -258,8 +260,15 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
* with the special "flag" characters. * with the special "flag" characters.
* *
* Idle time of all zero bits (alternating tones at maximum rate) * Idle time of all zero bits (alternating tones at maximum rate)
* has also been observed rarely. * has also been observed rarely. It is easy to understand the reasoning.
* Recognize zero(s) followed by a flag even though it vilolates the spec. * The tones alternate at the maximum rate, making it symmetrical and providing
* the most opportunity for the PLL to lock on to the edges.
* It also violates the published protocol spec.
*
* Recognize zero(s) followed by a single flag even though it violates the spec.
*
* It has been reported that the TinyTrak4 does this.
* https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/1207
*/ */
/* /*
@ -278,6 +287,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
* when listening to live signals. Let's try 3 and see how that works out. * when listening to live signals. Let's try 3 and see how that works out.
*/ */
//if (H->flag4_det == 0x7e7e7e7e) { //if (H->flag4_det == 0x7e7e7e7e) {
if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) { if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) {
//if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) { //if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) {
@ -301,6 +311,15 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
/* /*
* Loss of signal should result in lack of transitions. * Loss of signal should result in lack of transitions.
* (all '1' bits) for at least a little while. * (all '1' bits) for at least a little while.
*
* When this was written, I was only concerned about 1200 baud.
* For 9600, added later, there is a (de)scrambling function.
* So if there is no change in the signal, we would get pseudo random bits here.
* Maybe we need to put in another check earlier so DCD is not held on too long
* after loss of signal for 9600.
* No, that would not be a good idea. part of a valid frame, when scrambled,
* could have seven or more "1" bits in a row.
* Needs more study.
*/ */

View File

@ -84,13 +84,15 @@
* *
*******************************************************************************/ *******************************************************************************/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <string.h>
//Optimize processing by accessing directly to decoded bits //Optimize processing by accessing directly to decoded bits
#define RRBB_C 1 #define RRBB_C 1
#include "direwolf.h"
#include "hdlc_rec2.h" #include "hdlc_rec2.h"
#include "fcs_calc.h" #include "fcs_calc.h"
#include "textcolor.h" #include "textcolor.h"
@ -242,6 +244,8 @@ void hdlc_rec2_block (rrbb_t block)
/* Create an empty retry configuration */ /* Create an empty retry configuration */
retry_conf_t retry_cfg; retry_conf_t retry_cfg;
memset (&retry_cfg, 0, sizeof(retry_cfg));
/* /*
* For our first attempt we don't try to alter any bits. * For our first attempt we don't try to alter any bits.
* Still let it thru if passall AND no retries are desired. * Still let it thru if passall AND no retries are desired.
@ -327,7 +331,7 @@ void hdlc_rec2_block (rrbb_t block)
static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel) static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
{ {
int ok; int ok;
int len, i,j; int len, i;
retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
//int passall = save_audio_config_p->achan[chan].passall; //int passall = save_audio_config_p->achan[chan].passall;
@ -335,6 +339,9 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice,
len = rrbb_get_len(block); len = rrbb_get_len(block);
/* Prepare the retry configuration */ /* Prepare the retry configuration */
retry_conf_t retry_cfg; retry_conf_t retry_cfg;
memset (&retry_cfg, 0, sizeof(retry_cfg));
/* Will modify only contiguous bits*/ /* Will modify only contiguous bits*/
retry_cfg.mode = RETRY_MODE_CONTIGUOUS; retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
/* /*
@ -464,14 +471,17 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice,
int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel) int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
{ {
int ok; int ok;
int len, i, j; //int len;
retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits; //retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
int passall = save_audio_config_p->achan[chan].passall; int passall = save_audio_config_p->achan[chan].passall;
#if DEBUG_LATER #if DEBUG_LATER
double tstart, tend; double tstart, tend;
#endif #endif
retry_conf_t retry_cfg; retry_conf_t retry_cfg;
len = rrbb_get_len(block);
memset (&retry_cfg, 0, sizeof(retry_cfg));
//len = rrbb_get_len(block);
/* /*
@ -575,7 +585,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t
struct hdlc_state_s H; struct hdlc_state_s H;
int blen; /* Block length in bits. */ int blen; /* Block length in bits. */
int i; int i;
unsigned int raw; /* From demodulator. */ int raw; /* From demodulator. Should be 0 or 1. */
#if DEBUGx #if DEBUGx
int crc_failed = 1; int crc_failed = 1;
#endif #endif

View File

@ -1,3 +1,4 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
@ -17,10 +18,10 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include "direwolf.h"
#include "hdlc_send.h" #include "hdlc_send.h"
#include "audio.h" #include "audio.h"
#include "gen_tone.h" #include "gen_tone.h"
@ -33,7 +34,9 @@ static void send_bit (int, int);
static int number_of_bits_sent[MAX_CHANS]; static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags"
@ -49,6 +52,8 @@ static int number_of_bits_sent[MAX_CHANS];
* *
* flen - Frame length, not including the FCS. * flen - Frame length, not including the FCS.
* *
* bad_fcs - Append an invalid FCS for testing purposes.
*
* Outputs: Bits are shipped out by calling tone_gen_put_bit(). * Outputs: Bits are shipped out by calling tone_gen_put_bit().
* *
* Returns: Number of bits sent including "flags" and the * Returns: Number of bits sent including "flags" and the
@ -70,8 +75,7 @@ static int number_of_bits_sent[MAX_CHANS];
* *
*--------------------------------------------------------------*/ *--------------------------------------------------------------*/
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs)
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
{ {
int j, fcs; int j, fcs;
@ -81,7 +85,7 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen); dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs);
fflush (stdout); fflush (stdout);
#endif #endif
@ -94,8 +98,15 @@ int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
fcs = fcs_calc (fbuf, flen); fcs = fcs_calc (fbuf, flen);
if (bad_fcs) {
/* For testing only - Simulate a frame getting corrupted along the way. */
send_data (chan, (~fcs) & 0xff);
send_data (chan, ((~fcs) >> 8) & 0xff);
}
else {
send_data (chan, fcs & 0xff); send_data (chan, fcs & 0xff);
send_data (chan, (fcs >> 8) & 0xff); send_data (chan, (fcs >> 8) & 0xff);
}
send_control (chan, 0x7e); /* End frame */ send_control (chan, 0x7e); /* End frame */
@ -162,7 +173,10 @@ int hdlc_send_flags (int chan, int nflags, int finish)
static int stuff = 0; static int stuff[MAX_CHANS]; // Count number of "1" bits to keep track of when we
// need to break up a long run by "bit stuffing."
// Needs to be array because we could be transmitting
// on multiple channels at the same time.
static void send_control (int chan, int x) static void send_control (int chan, int x)
{ {
@ -173,7 +187,7 @@ static void send_control (int chan, int x)
x >>= 1; x >>= 1;
} }
stuff = 0; stuff[chan] = 0;
} }
static void send_data (int chan, int x) static void send_data (int chan, int x)
@ -183,13 +197,13 @@ static void send_data (int chan, int x)
for (i=0; i<8; i++) { for (i=0; i<8; i++) {
send_bit (chan, x & 1); send_bit (chan, x & 1);
if (x & 1) { if (x & 1) {
stuff++; stuff[chan]++;
if (stuff == 5) { if (stuff[chan] == 5) {
send_bit (chan, 0); send_bit (chan, 0);
stuff = 0; stuff[chan] = 0;
} }
} else { } else {
stuff = 0; stuff[chan] = 0;
} }
x >>= 1; x >>= 1;
} }
@ -203,13 +217,13 @@ static void send_data (int chan, int x)
static void send_bit (int chan, int b) static void send_bit (int chan, int b)
{ {
static int output; static int output[MAX_CHANS];
if (b == 0) { if (b == 0) {
output = ! output; output[chan] = ! output[chan];
} }
tone_gen_put_bit (chan, output); tone_gen_put_bit (chan, output[chan]);
number_of_bits_sent[chan]++; number_of_bits_sent[chan]++;
} }

View File

@ -1,7 +1,7 @@
/* hdlc_send.h */ /* hdlc_send.h */
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen); int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs);
int hdlc_send_flags (int chan, int flags, int finish); int hdlc_send_flags (int chan, int flags, int finish);

558
igate.c
View File

@ -63,18 +63,15 @@
* Cygwin: Can use either one. * Cygwin: Can use either one.
*/ */
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__ #if __WIN32__
/* The goal is to support Windows XP and later. */ /* The goal is to support Windows XP and later. */
#include <winsock2.h> #include <winsock2.h>
// default is 0x0400 #include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */
//#define _WIN32_WINNT 0x0502 /* Minimum OS version is XP with SP2. */
//#define _WIN32_WINNT 0x0600 /* Minimum OS version is Vista. */
#include <ws2tcpip.h>
#else #else
#include <stdlib.h> #include <stdlib.h>
#include <netdb.h> #include <netdb.h>
@ -102,6 +99,7 @@
#include "latlong.h" #include "latlong.h"
#include "pfilter.h" #include "pfilter.h"
#include "dtime_now.h" #include "dtime_now.h"
#include "mheard.h"
#if __WIN32__ #if __WIN32__
@ -120,8 +118,8 @@ static packet_t dp_queue_head;
static void satgate_delay_packet (packet_t pp, int chan); static void satgate_delay_packet (packet_t pp, int chan);
static void send_packet_to_server (packet_t pp, int chan); static void send_packet_to_server (packet_t pp, int chan);
static void send_msg_to_server (const char *msg); static void send_msg_to_server (const char *msg, int msg_len);
static void xmit_packet (char *message, int chan); static void maybe_xmit_packet_from_igate (char *message, int chan);
static void rx_to_ig_init (void); static void rx_to_ig_init (void);
static void rx_to_ig_remember (packet_t pp); static void rx_to_ig_remember (packet_t pp);
@ -268,7 +266,7 @@ int main (int argc, char *argv[])
SLEEP_SEC (20); SLEEP_SEC (20);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Send received packet\n"); dw_printf ("Send received packet\n");
send_msg_to_server ("W1ABC>APRS:?"); send_msg_to_server ("W1ABC>APRS:?", strlen("W1ABC>APRS:?");
} }
#endif #endif
return 0; return 0;
@ -293,8 +291,10 @@ static int s_debug;
/* /*
* Statistics. * Statistics for IGate function.
* TODO: need print function. * Note that the RF related counters are just a subset of what is happening on radio channels.
*
* TODO: should have debug option to print these occasionally.
*/ */
static int stats_failed_connect; /* Number of times we tried to connect to */ static int stats_failed_connect; /* Number of times we tried to connect to */
@ -312,8 +312,10 @@ static time_t stats_connect_at; /* Most recent time connection was established.
/* can be used to determine elapsed connect time. */ /* can be used to determine elapsed connect time. */
static int stats_rf_recv_packets; /* Number of candidate packets from the radio. */ static int stats_rf_recv_packets; /* Number of candidate packets from the radio. */
/* This is not the total number of AX.25 frames received */
/* over the radio; only APRS packets get this far. */
static int stats_rx_igate_packets; /* Number of packets passed along to the IGate */ static int stats_uplink_packets; /* Number of packets passed along to the IGate */
/* server after filtering. */ /* server after filtering. */
static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */
@ -322,41 +324,45 @@ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */
static int stats_downlink_bytes; /* Total number of bytes from IGate server including */ static int stats_downlink_bytes; /* Total number of bytes from IGate server including */
/* packets, heartbeats, other messages. */ /* packets, heartbeats, other messages. */
static int stats_tx_igate_packets; /* Number of packets from IGate server. */ static int stats_downlink_packets; /* Number of packets from IGate server for possible transmission. */
/* Fewer might be transmitted due to filtering or rate limiting. */
static int stats_rf_xmit_packets; /* Number of packets passed along to radio */ static int stats_rf_xmit_packets; /* Number of packets passed along to radio, for the IGate function, */
/* after rate limiting or other restrictions. */ /* after filtering, rate limiting, or other restrictions. */
/* Number of packets transmitted for beacons, digipeating, */
/* or client applications are not included here. */
/* We have some statistics. What do we do with them? static int stats_msg_cnt; /* Number of "messages" transmitted. Subset of above. */
/* A "message" has the data type indicator of ":" and it is */
/* not the special case of telemetry metadata. */
IGate stations often send packets like this: /*
* Make some of these available for IGate statistics beacon like
<IGATE MSG_CNT=1238 LOC_CNT=0 FILL_CNT=0 *
<IGATE,MSG_CNT=1,LOC_CNT=25 * WB2OSZ>APDW14,WIDE1-1:<IGATE,MSG_CNT=2,PKT_CNT=0,DIR_CNT=10,LOC_CNT=35,RF_CNT=45
<IGATE,MSG_CNT=0,LOC_CNT=46,DIR_CNT=13,RF_CNT=49,RFPORT_ID=0 *
* MSG_CNT is only "messages." From original spec.
What does it all mean? * PKT_CNT is other (non-message) packets. Followed precedent of APRSISCE32.
Why do some have spaces instead of commas between the capabilities?
The APRS Protocol Reference ( http://www.aprs.org/doc/APRS101.PDF ),
section 15, briefly discusses station capabilities and gives the example
IGATE,MSG_CNT=n,LOC_CNT=n
IGate Design ( http://www.aprs-is.net/IGating.aspx ) barely mentions
<IGATE,MSG_CNT=n,LOC_CNT=n
This leaves many questions. Does "number of messages transmitted" mean only
the APRS "Message" (data type indicator ":") or does it mean any type of
APRS packet? What are "local" stations? Those we hear directly without
going thru a digipeater?
What are DIR_CNT, RF_CNT, and so on?
Are the counts since the system started up or are they for some interval?
*/ */
int igate_get_msg_cnt (void) {
return (stats_msg_cnt);
}
int igate_get_pkt_cnt (void) {
return (stats_rf_xmit_packets - stats_msg_cnt);
}
int igate_get_upl_cnt (void) {
return (stats_uplink_packets);
}
int igate_get_dnl_cnt (void) {
return (stats_downlink_packets);
}
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
@ -424,11 +430,12 @@ void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_
stats_connects = 0; stats_connects = 0;
stats_connect_at = 0; stats_connect_at = 0;
stats_rf_recv_packets = 0; stats_rf_recv_packets = 0;
stats_rx_igate_packets = 0; stats_uplink_packets = 0;
stats_uplink_bytes = 0; stats_uplink_bytes = 0;
stats_downlink_bytes = 0; stats_downlink_bytes = 0;
stats_tx_igate_packets = 0; stats_downlink_packets = 0;
stats_rf_xmit_packets = 0; stats_rf_xmit_packets = 0;
stats_msg_cnt = 0;
rx_to_ig_init (); rx_to_ig_init ();
ig_to_tx_init (); ig_to_tx_init ();
@ -685,8 +692,11 @@ static void * connnect_thread (void *arg)
// Try each address until we find one that is successful. // Try each address until we find one that is successful.
for (n=0; n<num_hosts; n++) { for (n=0; n<num_hosts; n++) {
#if __WIN32__
SOCKET is;
#else
int is; int is;
#endif
ai = hosts[n]; ai = hosts[n];
ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str)); ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
@ -787,7 +797,7 @@ static void * connnect_thread (void *arg)
strlcat (stemp, " filter ", sizeof(stemp)); strlcat (stemp, " filter ", sizeof(stemp));
strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp)); strlcat (stemp, save_igate_config_p->t2_filter, sizeof(stemp));
} }
send_msg_to_server (stemp); send_msg_to_server (stemp, strlen(stemp));
/* Delay until it is ok to start sending packets. */ /* Delay until it is ok to start sending packets. */
@ -817,10 +827,13 @@ static void * connnect_thread (void *arg)
strlcpy (heartbeat, "#", sizeof(heartbeat)); strlcpy (heartbeat, "#", sizeof(heartbeat));
/* This will close the socket if any error. */ /* This will close the socket if any error. */
send_msg_to_server (heartbeat); send_msg_to_server (heartbeat, strlen(heartbeat));
} }
} }
exit(0); // Unreachable but stops compiler from complaining
// about function not returning a value.
} /* end connnect_thread */ } /* end connnect_thread */
@ -848,14 +861,15 @@ static void * connnect_thread (void *arg)
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
#define IGATE_MAX_MSG 520 /* Message to IGate max 512 characters. */ #define IGATE_MAX_MSG 512 /* "All 'packets' sent to APRS-IS must be in the TNC2 format terminated */
/* by a carriage return, line feed sequence. No line may exceed 512 bytes */
/* including the CR/LF sequence." */
void igate_send_rec_packet (int chan, packet_t recv_pp) void igate_send_rec_packet (int chan, packet_t recv_pp)
{ {
packet_t pp; packet_t pp;
int n; int n;
unsigned char *pinfo; unsigned char *pinfo;
char *p;
int info_len; int info_len;
@ -867,26 +881,34 @@ void igate_send_rec_packet (int chan, packet_t recv_pp)
return; /* Login not complete. */ return; /* Login not complete. */
} }
/* Gather statistics. */
stats_rf_recv_packets++;
/* /*
* Check for filtering from specified channel to the IGate server. * Check for filtering from specified channel to the IGate server.
*
* Should we do this after unwrapping the payload from a third party packet?
* In my experience, third party packets have only been seen coming from IGates.
* In that case, the payload will have TCPIP in the path and it will be dropped.
*/ */
if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) {
if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp) != 1) { if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) {
// Is this useful troubleshooting information or just distracting noise?
// Originally this was always printed but there was a request to add a "quiet" option to suppress this.
// version 1.4: Instead, make the default off and activate it only with the debug igate option.
if (s_debug >= 1) {
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]); dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]);
}
return; return;
} }
} }
/* Gather statistics. */
stats_rf_recv_packets++;
/* /*
* First make a copy of it because it might be modified in place. * First make a copy of it because it might be modified in place.
*/ */
@ -972,32 +994,25 @@ void igate_send_rec_packet (int chan, packet_t recv_pp)
/* /*
* Cut the information part at the first CR or LF. * Cut the information part at the first CR or LF.
* This is required because CR/LF is used as record separator when sending to server.
* Do NOT trim trailing spaces.
* Starting in 1.4 we preserve any nul characters in the information part.
*/ */
info_len = ax25_get_info (pp, &pinfo); if (ax25_cut_at_crlf (pp) > 0) {
(void)(info_len);
if ((p = strchr ((char*)pinfo, '\r')) != NULL) {
if (s_debug >= 1) { if (s_debug >= 1) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("Rx IGate: Truncated information part at CR.\n"); dw_printf ("Rx IGate: Truncated information part at CR.\n");
} }
*p = '\0';
} }
if ((p = strchr ((char*)pinfo, '\n')) != NULL) { info_len = ax25_get_info (pp, &pinfo);
if (s_debug >= 1) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Rx IGate: Truncated information part at LF.\n");
}
*p = '\0';
}
/* /*
* Someone around here occasionally sends a packet with no information part. * Someone around here occasionally sends a packet with no information part.
*/ */
if (strlen((char*)pinfo) == 0) { if (info_len == 0) {
if (s_debug >= 1) { if (s_debug >= 1) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
@ -1053,10 +1068,15 @@ static void send_packet_to_server (packet_t pp, int chan)
info_len = ax25_get_info (pp, &pinfo); info_len = ax25_get_info (pp, &pinfo);
(void)(info_len);
/* /*
* Do not relay if a duplicate of something sent recently. * We will often see the same packet multiple times close together due to digipeating.
* The consensus seems to be that we should just send the first and drop the later duplicates.
* There is some dissent on this issue. http://www.tapr.org/pipermail/aprssig/2016-July/045907.html
* There could be some value to sending them all to provide information about digipeater paths.
* However, the servers should drop all duplicates so we wasting everyone's time but sending duplicates.
* If you feel strongly about this issue, you could remove the following section.
* Currently rx_to_ig_allow only checks for recent duplicates.
*/ */
if ( ! rx_to_ig_allow(pp)) { if ( ! rx_to_ig_allow(pp)) {
@ -1070,17 +1090,111 @@ static void send_packet_to_server (packet_t pp, int chan)
/* /*
* Finally, append ",qAR," and my call to the path. * Finally, append ",qAR," and my call to the path.
*/
/*
* It seems that the specification has changed recently.
* http://www.tapr.org/pipermail/aprssig/2016-December/046456.html
*
* We can see the history at the Internet Archive Wayback Machine.
*
* http://www.aprs-is.net/Connecting.aspx
* captured Oct 19, 2016:
* ... Only the qAR construct may be generated by a client (IGate) on APRS-IS.
* Captured Dec 1, 2016:
* ... Only the qAR and qAO constructs may be generated by a client (IGate) on APRS-IS.
*
* http://www.aprs-is.net/q.aspx
* Captured April 23, 2016:
* (no mention of client generating qAO.)
* Captured July 19, 2016:
* qAO - (letter O) Packet is placed on APRS-IS by a receive-only IGate from RF.
* The callSSID following the qAO is the callSSID of the IGate. Note that receive-only
* IGates are discouraged on standard APRS frequencies. Please consider a bidirectional
* IGate that only gates to RF messages for stations heard directly.
*/ */
ax25_format_addrs (pp, msg); ax25_format_addrs (pp, msg);
msg[strlen(msg)-1] = '\0'; /* Remove trailing ":" */ msg[strlen(msg)-1] = '\0'; /* Remove trailing ":" */
if (save_igate_config_p->tx_chan >= 0) {
strlcat (msg, ",qAR,", sizeof(msg)); strlcat (msg, ",qAR,", sizeof(msg));
}
else {
strlcat (msg, ",qAO,", sizeof(msg)); // new for version 1.4.
}
strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg)); strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg));
strlcat (msg, ":", sizeof(msg)); strlcat (msg, ":", sizeof(msg));
strlcat (msg, (char*)pinfo, sizeof(msg));
send_msg_to_server (msg);
stats_rx_igate_packets++;
// It was reported that APRS packets, containing a nul byte in the information part,
// are being truncated. https://github.com/wb2osz/direwolf/issues/84
//
// One might argue that the packets are invalid and the proper behavior would be
// to simply discard them, the same way we do if the CRC is bad. One might argue
// that we should simply pass along whatever we receive even if we don't like it.
// We really shouldn't modify it and make the situation even worse.
//
// Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says:
//
// "The comment may contain any printable ASCII characters (except | and ~,
// which are reserved for TNC channel switching)."
//
// "Printable" would exclude character values less than space (00100000), e.g.
// tab, carriage return, line feed, nul. Sometimes we see carriage return
// (00001010) at the end of APRS packets. This would be in violation of the
// specification.
//
// The MIC-E position format can have non printable characters (0x1c ... 0x1f, 0x7f)
// in the information part. An unfortunate decision, but it is not in the comment part.
//
// The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ),
// which is not part of the APRS spec, uses the | character in the comment to delimit encoded
// telemetry data. This would be in violation of the original spec. No one cares.
//
// The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt)
// adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in
// messages and comments. It can't be used in the fixed width fields.
//
// Non-ASCII characters are represented by multi-byte sequences. All bytes in these
// multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not
// add any nul (00000000) bytes to the stream.
//
// Based on all of that, we would not expect to see a nul character in the information part.
//
// There are two known cases where we can have a nul character value.
//
// * The Kenwood TM-D710A sometimes sends packets like this:
//
// VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast=
// K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV =
//
// Notice that the data type indicator of "4" is not valid. If we remove
// 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format.
// This same thing has been observed from others and is intermittent.
//
// * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes.
// This is wrong, it should be using UTF-8.
//
// Rather than using strlcat here, we need to use memcpy and maintain our
// own lengths, being careful to avoid buffer overflow.
int msg_len = strlen(msg); // What we have so far before info part.
if (info_len > IGATE_MAX_MSG - msg_len - 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Rx IGate: Too long. Truncating.\n");
info_len = IGATE_MAX_MSG - msg_len - 2;
}
if (info_len > 0) {
memcpy (msg + msg_len, pinfo, info_len);
msg_len += info_len;
}
send_msg_to_server (msg, msg_len);
stats_uplink_packets++;
/* /*
* Remember what was sent to avoid duplicates in near future. * Remember what was sent to avoid duplicates in near future.
@ -1097,58 +1211,74 @@ static void send_packet_to_server (packet_t pp, int chan)
* *
* Name: send_msg_to_server * Name: send_msg_to_server
* *
* Purpose: Send to the IGate server. * Purpose: Send something to the IGate server.
* This one function should be used for login, hearbeats, * This one function should be used for login, hearbeats,
* and packets. * and packets.
* *
* Inputs: imsg - Message. We will add CR/LF. * Inputs: imsg - Message. We will add CR/LF here.
* *
* imsg_len - Length of imsg in bytes.
* It could contain nul characters so we can't
* use the normal C string functions.
* *
* Description: Send message to IGate Server if connected. * Description: Send message to IGate Server if connected.
* Disconnect from server, and notify user, if any error. * Disconnect from server, and notify user, if any error.
* Should use a word other than message because that has
* a specific meaning for APRS.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static void send_msg_to_server (const char *imsg) static void send_msg_to_server (const char *imsg, int imsg_len)
{ {
int err; int err;
char stemp[IGATE_MAX_MSG]; char stemp[IGATE_MAX_MSG+1];
int stemp_len;
if (igate_sock == -1) { if (igate_sock == -1) {
return; /* Silently discard if not connected. */ return; /* Silently discard if not connected. */
} }
strlcpy(stemp, imsg, sizeof(stemp)); stemp_len = imsg_len;
if (stemp_len + 2 > IGATE_MAX_MSG) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Rx IGate: Too long. Truncating.\n");
stemp_len = IGATE_MAX_MSG - 2;
}
memcpy (stemp, imsg, stemp_len);
if (s_debug >= 1) { if (s_debug >= 1) {
text_color_set(DW_COLOR_XMIT); text_color_set(DW_COLOR_XMIT);
dw_printf ("[rx>ig] "); dw_printf ("[rx>ig] ");
ax25_safe_print (stemp, strlen(stemp), 0); ax25_safe_print (stemp, stemp_len, 0);
dw_printf ("\n"); dw_printf ("\n");
} }
strlcat (stemp, "\r\n", sizeof(stemp)); stemp[stemp_len++] = '\r';
stemp[stemp_len++] = '\n';
stemp[stemp_len] = '\0';
stats_uplink_bytes += stemp_len;
stats_uplink_bytes += strlen(stemp);
#if __WIN32__ #if __WIN32__
err = send (igate_sock, stemp, strlen(stemp), 0); err = send (igate_sock, stemp, stemp_len, 0);
if (err == SOCKET_ERROR) if (err == SOCKET_ERROR)
{ {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError %d sending message to IGate server. Closing connection.\n\n", WSAGetLastError()); dw_printf ("\nError %d sending to IGate server. Closing connection.\n\n", WSAGetLastError());
//dw_printf ("DEBUG: igate_sock=%d, line=%d\n", igate_sock, __LINE__); //dw_printf ("DEBUG: igate_sock=%d, line=%d\n", igate_sock, __LINE__);
closesocket (igate_sock); closesocket (igate_sock);
igate_sock = -1; igate_sock = -1;
WSACleanup(); WSACleanup();
} }
#else #else
err = write (igate_sock, stemp, strlen(stemp)); err = write (igate_sock, stemp, stemp_len);
if (err <= 0) if (err <= 0)
{ {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending message to IGate server. Closing connection.\n\n"); dw_printf ("\nError sending to IGate server. Closing connection.\n\n");
close (igate_sock); close (igate_sock);
igate_sock = -1; igate_sock = -1;
} }
@ -1240,7 +1370,7 @@ static void * igate_recv_thread (void *arg)
#endif #endif
{ {
unsigned char ch; unsigned char ch;
unsigned char message[1000]; // Spec says max 500 or so. unsigned char message[1000]; // Spec says max 512.
int len; int len;
@ -1258,14 +1388,27 @@ static void * igate_recv_thread (void *arg)
ch = get1ch(); ch = get1ch();
stats_downlink_bytes++; stats_downlink_bytes++;
if (len < sizeof(message)) // I never expected to see a nul character but it can happen.
{ // If found, change it to <0x00> and ax25_from_text will change it back to a single byte.
message[len] = ch; // Along the way we can use the normal C string handling.
if (ch == 0 && len < (int)(sizeof(message)) - 5) {
message[len++] = '<';
message[len++] = '0';
message[len++] = 'x';
message[len++] = '0';
message[len++] = '0';
message[len++] = '>';
}
else if (len < (int)(sizeof(message)))
{
message[len++] = ch;
} }
len++;
} while (ch != '\n'); } while (ch != '\n');
message[sizeof(message)-1] = '\0';
/* /*
* We have a complete message terminated by LF. * We have a complete message terminated by LF.
* *
@ -1280,10 +1423,13 @@ static void * igate_recv_thread (void *arg)
* I've seen a case where the original RF packet had a trailing CR but * I've seen a case where the original RF packet had a trailing CR but
* after someone else sent it to the server and it came back to me, that * after someone else sent it to the server and it came back to me, that
* CR was now a trailing space. * CR was now a trailing space.
*
* At first I was tempted to trim a trailing space as well. * At first I was tempted to trim a trailing space as well.
* By fixing this one case it might corrupt the data in other cases. * By fixing this one case it might corrupt the data in other cases.
* We compensate for this by ignoring trailing spaces when performing * We compensate for this by ignoring trailing spaces when performing
* the duplicate detection and removal. * the duplicate detection and removal.
*
* We need to transmit exactly as we get it.
*/ */
/* /*
@ -1329,10 +1475,33 @@ static void * igate_recv_thread (void *arg)
ax25_safe_print ((char *)message, len, 0); ax25_safe_print ((char *)message, len, 0);
dw_printf ("\n"); dw_printf ("\n");
if ((int)strlen((char*)message) != len) {
// Invalid. Either drop it or pass it along as-is. Don't change.
text_color_set(DW_COLOR_ERROR);
dw_printf("'nul' character found in packet from IS. This should never happen.\n");
dw_printf("The source station is probably transmitting with defective software.\n");
//if (strcmp((char*)pinfo, "4P") == 0) {
// dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n");
//}
}
/*
* Record that we heard from the source address.
*/
mheard_save_is ((char *)message);
stats_downlink_packets++;
/*
* Possibly transmit if so configured.
*/
int to_chan = save_igate_config_p->tx_chan; int to_chan = save_igate_config_p->tx_chan;
if (to_chan >= 0) { if (to_chan >= 0) {
xmit_packet ((char*)message, to_chan); maybe_xmit_packet_from_igate ((char*)message, to_chan);
} }
} }
@ -1464,10 +1633,10 @@ static void * satgate_delay_thread (void *arg)
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: xmit_packet * Name: maybe_xmit_packet_from_igate
* *
* Purpose: Convert text string, from IGate server, to third party * Purpose: Convert text string, from IGate server, to third party
* packet and send to transmit queue. * packet and send to transmit queue if appropriate.
* *
* Inputs: message - As sent by the server. * Inputs: message - As sent by the server.
* Any trailing CRLF should have been removed. * Any trailing CRLF should have been removed.
@ -1485,18 +1654,23 @@ static void * satgate_delay_thread (void *arg)
* repackaging to go over the radio. * repackaging to go over the radio.
* *
* The "q construct" ( http://www.aprs-is.net/q.aspx ) provides * The "q construct" ( http://www.aprs-is.net/q.aspx ) provides
* a clue about the journey taken but I don't think we care here. * a clue about the journey taken. "qAX" means that the station sending
* the packet to the server did not login properly as a ham radio
* operator so we don't want to put this on to RF.
* *
* to_chan - Radio channel for transmitting. * to_chan - Radio channel for transmitting.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static void xmit_packet (char *message, int to_chan) static void maybe_xmit_packet_from_igate (char *message, int to_chan)
{ {
packet_t pp3; packet_t pp3;
char payload[AX25_MAX_PACKET_LEN]; /* what is max len? */ char payload[AX25_MAX_PACKET_LEN]; /* what is max len? */
char src[AX25_MAX_ADDR_LEN]; /* Source address. */
char *pinfo = NULL; char *pinfo = NULL;
int info_len; int info_len;
int n;
assert (to_chan >= 0 && to_chan < MAX_CHANS); assert (to_chan >= 0 && to_chan < MAX_CHANS);
@ -1518,6 +1692,32 @@ static void xmit_packet (char *message, int to_chan)
return; return;
} }
ax25_get_addr_with_ssid (pp3, AX25_SOURCE, src);
/*
* Drop if path contains:
* NOGATE or RFONLY - means IGate should not pass them.
* TCPXX or qAX - means it came from somewhere that did not identify itself correctly.
*/
for (n = 0; n < ax25_get_num_repeaters(pp3); n++) {
char via[AX25_MAX_ADDR_LEN]; /* includes ssid. Do we want to ignore it? */
ax25_get_addr_with_ssid (pp3, n + AX25_REPEATER_1, via);
if (strcmp(via, "qAX") == 0 ||
strcmp(via, "TCPXX") == 0 ||
strcmp(via, "RFONLY") == 0 ||
strcmp(via, "NOGATE") == 0) {
if (s_debug >= 1) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Tx IGate: Do not transmit with %s in path.\n", via);
}
ax25_delete (pp3);
return;
}
}
/* /*
* Apply our own packet filtering if configured. * Apply our own packet filtering if configured.
@ -1528,17 +1728,60 @@ static void xmit_packet (char *message, int to_chan)
assert (to_chan >= 0 && to_chan < MAX_CHANS); assert (to_chan >= 0 && to_chan < MAX_CHANS);
/*
* We have a rather strange special case here.
* If we recently transmitted a 'message' from some station,
* send the position of the message sender when it comes along later.
*
* If we have a position report, look up the sender and see if we should
* bypass the normal filtering.
*/
// TODO: Not quite this simple. Should have a function to check for position.
// $ raw gps could be a position. @ could be weather data depending on symbol.
info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo));
int msp_special_case = 0;
if (info_len >= 1 && strchr("!=/@'`", *pinfo) != NULL) {
int n = mheard_get_msp(src);
if (n > 0) {
msp_special_case = 1;
if (s_debug >= 1) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Special case, allow position from message sender %s, %d remaining.\n", src, n - 1);
}
mheard_set_msp (src, n - 1);
}
}
if ( ! msp_special_case) {
if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) { if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) {
if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3) != 1) { if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3, 1) != 1) {
text_color_set(DW_COLOR_INFO); // Previously there was a debug message here about the packet being dropped by filtering.
dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]); // This is now handled better by the "-df" command line option for filtering details.
// TODO: clean up - remove these lines.
//if (s_debug >= 1) {
// text_color_set(DW_COLOR_INFO);
// dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]);
//}
ax25_delete (pp3); ax25_delete (pp3);
return; return;
} }
} }
}
/* /*
@ -1549,6 +1792,34 @@ static void xmit_packet (char *message, int to_chan)
* *
* We want to reduce it to this before wrapping it as third party traffic. * We want to reduce it to this before wrapping it as third party traffic.
* K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a> * K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a>
*/
/*
* These are typical examples where we see TCPIP*,qAC,<server>
*
* N3LLO-4>APRX28,TCPIP*,qAC,T2NUENGLD:T#474,21.4,0.3,114.0,4.0,0.0,00000000
* N1WJO>APWW10,TCPIP*,qAC,T2MAINE:)147.120!4412.27N/07033.27WrW1OCA repeater136.5 Tone Norway Me
* AB1OC-10>APWW10,TCPIP*,qAC,T2IAD2:=4242.70N/07135.41W#(Time 0:00:00)!INSERVICE!!W60!
*
* But sometimes we get a different form:
*
* N1YG-1>T1SY9P,WIDE1-1,WIDE2-2,qAR,W2DAN-15:'c&<0x7f>l <0x1c>-/>
* W1HS-8>TSSP9T,WIDE1-1,WIDE2-1,qAR,N3LLO-2:`d^Vl"W>/'"85}|*&%_'[|!wLK!|3
* N1RCW-1>APU25N,MA2-2,qAR,KA1VCQ-1:=4140.41N/07030.21W-Home Station/Fill-in Digi {UIV32N}
* N1IEJ>T4PY3U,W1EMA-1,WIDE1*,WIDE2-2,qAR,KD1KE:`a5"l!<0x7f>-/]"4f}Retired & Busy=
*
* Oh! They have qAR rather than qAC. What does that mean?
* From http://www.aprs-is.net/q.aspx
*
* qAC - Packet was received from the client directly via a verified connection (FROMCALL=login).
* The callSSID following the qAC is the server's callsign-SSID.
*
* qAR - Packet was received directly (via a verified connection) from an IGate using the ,I construct.
* The callSSID following the qAR it the callSSID of the IGate.
*
* What is the ",I" construct?
* Do we care here?
* Is is something new and improved that we should be using in the other direction?
*/ */
while (ax25_get_num_repeaters(pp3) > 0) { while (ax25_get_num_repeaters(pp3) > 0) {
@ -1581,6 +1852,16 @@ static void xmit_packet (char *message, int to_chan)
/* /*
* Encapsulate for sending over radio if no reason to drop it. * Encapsulate for sending over radio if no reason to drop it.
*/
/*
* We don't want to suppress duplicate "messages" within a short time period.
* Suppose we transmitted a "message" for some station and it did not respond with an ack.
* 25 seconds later the sender retries. Wouldn't we want to pass along that retry?
*
* "Messages" get preferential treatment because they are high value and very rare.
* -> Bypass the duplicate suppression.
* -> Raise the rate limiting value.
*/ */
if (ig_to_tx_allow (pp3, to_chan)) { if (ig_to_tx_allow (pp3, to_chan)) {
char radio [500]; char radio [500];
@ -1599,8 +1880,6 @@ static void xmit_packet (char *message, int to_chan)
if (pradio != NULL) { if (pradio != NULL) {
stats_tx_igate_packets++;
#if ITEST #if ITEST
text_color_set(DW_COLOR_XMIT); text_color_set(DW_COLOR_XMIT);
dw_printf ("Xmit: %s\n", radio); dw_printf ("Xmit: %s\n", radio);
@ -1609,7 +1888,21 @@ static void xmit_packet (char *message, int to_chan)
/* This consumes packet so don't reference it again! */ /* This consumes packet so don't reference it again! */
tq_append (to_chan, TQ_PRIO_1_LO, pradio); tq_append (to_chan, TQ_PRIO_1_LO, pradio);
#endif #endif
stats_rf_xmit_packets++; stats_rf_xmit_packets++; // Any type of packet.
// TEMP TEST: metadata temporarily allowed during testing.
if (*pinfo == ':' && ! is_telem_metadata(pinfo)) {
// temp test // if (*pinfo == ':') {
// We transmitted a "message." Telemetry metadata is excluded.
// Remember to pass along address of the sender later.
stats_msg_cnt++; // Update statistics.
mheard_set_msp (src, save_igate_config_p->igmsp);
}
ig_to_tx_remember (pp3, save_igate_config_p->tx_chan, 0); // correct. version before encapsulating it. ig_to_tx_remember (pp3, save_igate_config_p->tx_chan, 0); // correct. version before encapsulating it.
} }
else { else {
@ -1624,7 +1917,7 @@ static void xmit_packet (char *message, int to_chan)
ax25_delete (pp3); ax25_delete (pp3);
} /* end xmit_packet */ } /* end maybe_xmit_packet_from_igate */
@ -1701,6 +1994,7 @@ static void rx_to_ig_remember (packet_t pp)
ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
info_len = ax25_get_info (pp, &pinfo); info_len = ax25_get_info (pp, &pinfo);
(void)info_len;
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("rx_to_ig_remember [%d] = %d %d \"%s>%s:%s\"\n", dw_printf ("rx_to_ig_remember [%d] = %d %d \"%s>%s:%s\"\n",
@ -1731,6 +2025,7 @@ static int rx_to_ig_allow (packet_t pp)
ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
info_len = ax25_get_info (pp, &pinfo); info_len = ax25_get_info (pp, &pinfo);
(void)info_len;
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo); dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo);
@ -1798,7 +2093,7 @@ static int rx_to_ig_allow (packet_t pp)
* duplicate of another sent recently. * duplicate of another sent recently.
* *
* This is the essentially the same as the pair of functions * This is the essentially the same as the pair of functions
* above with one addition restriction. * above, for RF to IS, with one additional restriction.
* *
* The typical residential Internet connection is around 10,000 * The typical residential Internet connection is around 10,000
* to 50,000 times faster than the radio links we are using. It would * to 50,000 times faster than the radio links we are using. It would
@ -1847,10 +2142,12 @@ static int rx_to_ig_allow (packet_t pp)
* At first I thought duplicate removal was broken but it turns out they * At first I thought duplicate removal was broken but it turns out they
* are not exactly the same. * are not exactly the same.
* *
* The receive IGate spec says a packet should be cut at a CR. * >>> The receive IGate spec says a packet should be cut at a CR. <<<
*
* In one case it is removed as expected In another case, it is replaced by a trailing * In one case it is removed as expected In another case, it is replaced by a trailing
* space character. Maybe someone thought non printable characters should be * space character. Maybe someone thought non printable characters should be
* replaced by spaces??? * replaced by spaces??? (I have since been told someone thought it would be a good
* idea to replace unprintable characters with spaces. How's that working out for MIC-E position???)
* *
* At first I was tempted to remove any trailing spaces to make up for the other * At first I was tempted to remove any trailing spaces to make up for the other
* IGate adding it. Two wrongs don't make a right. Trailing spaces are not that * IGate adding it. Two wrongs don't make a right. Trailing spaces are not that
@ -1986,6 +2283,7 @@ void ig_to_tx_remember (packet_t pp, int chan, int bydigi)
ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
info_len = ax25_get_info (pp, &pinfo); info_len = ax25_get_info (pp, &pinfo);
(void)info_len;
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("ig_to_tx_remember [%d] = ch%d d%d %d %d \"%s>%s:%s\"\n", dw_printf ("ig_to_tx_remember [%d] = ch%d d%d %d %d \"%s>%s:%s\"\n",
@ -2012,16 +2310,20 @@ static int ig_to_tx_allow (packet_t pp, int chan)
time_t now = time(NULL); time_t now = time(NULL);
int j; int j;
int count_1, count_5; int count_1, count_5;
int increase_limit;
unsigned char *pinfo;
int info_len;
info_len = ax25_get_info (pp, &pinfo);
(void)info_len;
if (s_debug >= 2) { if (s_debug >= 2) {
char src[AX25_MAX_ADDR_LEN]; char src[AX25_MAX_ADDR_LEN];
char dest[AX25_MAX_ADDR_LEN]; char dest[AX25_MAX_ADDR_LEN];
unsigned char *pinfo;
int info_len;
ax25_get_addr_with_ssid(pp, AX25_SOURCE, src); ax25_get_addr_with_ssid(pp, AX25_SOURCE, src);
ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest); ax25_get_addr_with_ssid(pp, AX25_DESTINATION, dest);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("ig_to_tx_allow? ch%d %d \"%s>%s:%s\"\n", chan, crc, src, dest, pinfo); dw_printf ("ig_to_tx_allow? ch%d %d \"%s>%s:%s\"\n", chan, crc, src, dest, pinfo);
@ -2031,16 +2333,37 @@ static int ig_to_tx_allow (packet_t pp, int chan)
for (j=0; j<IG2TX_HISTORY_MAX; j++) { for (j=0; j<IG2TX_HISTORY_MAX; j++) {
if (ig2tx_checksum[j] == crc && ig2tx_chan[j] == chan && ig2tx_time_stamp[j] >= now - IG2TX_DEDUPE_TIME) { if (ig2tx_checksum[j] == crc && ig2tx_chan[j] == chan && ig2tx_time_stamp[j] >= now - IG2TX_DEDUPE_TIME) {
/* We have a duplicate within some time period. */
if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) {
/* I think I want to avoid the duplicate suppression for "messages." */
/* Suppose we transmit a message from station X and it doesn't get an ack back. */
/* Station X then sends exactly the same thing 20 seconds later. */
/* We don't want to suppress the retry. */
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ig_to_tx_allow? Yes for duplicate message sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]);
}
}
else {
/* Normal (non-message) case. */
if (s_debug >= 2) { if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
// could be multiple entries and this might not be the most recent. // could be multiple entries and this might not be the most recent.
dw_printf ("ig_to_tx_allow? NO. Sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]); dw_printf ("ig_to_tx_allow? NO. Duplicate sent %d seconds ago. bydigi=%d\n", (int)(now - ig2tx_time_stamp[j]), ig2tx_bydigi[j]);
} }
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n"); dw_printf ("Tx IGate: Drop duplicate packet transmitted recently.\n");
return 0; return 0;
} }
} }
}
/* IGate transmit counts must not include digipeater transmissions. */ /* IGate transmit counts must not include digipeater transmissions. */
@ -2053,12 +2376,25 @@ static int ig_to_tx_allow (packet_t pp, int chan)
} }
} }
if (count_1 >= save_igate_config_p->tx_limit_1) { /* "Messages" (special APRS data type ":") are intentional and more */
/* important than all of the other mostly repetitive useless junk */
/* flowing thru here. */
/* It would be unfortunate to discard a message because we already */
/* hit our limit. I don't want to completely eliminate limiting for */
/* messages, in case something goes terribly wrong, but we can triple */
/* the normal limit for them. */
increase_limit = 1;
if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) {
increase_limit = 3;
}
if (count_1 >= save_igate_config_p->tx_limit_1 * increase_limit) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", save_igate_config_p->tx_limit_1); dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 1 minute.\n", save_igate_config_p->tx_limit_1);
return 0; return 0;
} }
if (count_5 >= save_igate_config_p->tx_limit_5) { if (count_5 >= save_igate_config_p->tx_limit_5 * increase_limit) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5); dw_printf ("Tx IGate: Already transmitted maximum of %d packets in 5 minutes.\n", save_igate_config_p->tx_limit_5);
return 0; return 0;

29
igate.h
View File

@ -45,15 +45,30 @@ struct igate_config_s {
*/ */
int tx_chan; /* Radio channel for transmitting. */ int tx_chan; /* Radio channel for transmitting. */
/* 0=first, etc. -1 for none. */ /* 0=first, etc. -1 for none. */
/* Presently IGate can transmit on only a single channel. */
/* A future version might generalize this. */
/* Each transmit channel would have its own client side filtering. */
char tx_via[80]; /* VIA path for transmitting third party packets. */ char tx_via[80]; /* VIA path for transmitting third party packets. */
/* Usual text representation. */ /* Usual text representation. */
/* Must start with "," if not empty so it can */ /* Must start with "," if not empty so it can */
/* simply be inserted after the destination address. */ /* simply be inserted after the destination address. */
int max_digi_hops; /* Maximum number of digipeater hops possible for via path. */
/* Derived from the SSID when last character of address is a digit. */
/* e.g. "WIDE1-1,WIDE5-2" would be 3. */
/* This is useful to know so we can determine how many */
/* stations we might be able to reach. */
int tx_limit_1; /* Max. packets to transmit in 1 minute. */ int tx_limit_1; /* Max. packets to transmit in 1 minute. */
int tx_limit_5; /* Max. packets to transmit in 5 minutes. */ int tx_limit_5; /* Max. packets to transmit in 5 minutes. */
int igmsp; /* Number of message sender position reports to allow. */
/* Common practice is to default to 1. */
/* We allow additional flexibility of 0 to disable feature */
/* or a small number to allow more. */
/* /*
* Special SATgate mode to delay packets heard directly. * Special SATgate mode to delay packets heard directly.
*/ */
@ -84,4 +99,18 @@ void igate_send_rec_packet (int chan, packet_t recv_pp);
void ig_to_tx_remember (packet_t pp, int chan, int bydigi); void ig_to_tx_remember (packet_t pp, int chan, int bydigi);
/* Get statistics for IGATE status beacon. */
int igate_get_msg_cnt (void);
int igate_get_pkt_cnt (void);
int igate_get_upl_cnt (void);
int igate_get_dnl_cnt (void);
#endif #endif

187
kiss.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2013, 2014 John Langner, WB2OSZ // Copyright (C) 2011, 2013, 2014, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -24,14 +24,13 @@
* Module: kiss.c * Module: kiss.c
* *
* Purpose: Act as a virtual KISS TNC for use by other packet radio applications. * Purpose: Act as a virtual KISS TNC for use by other packet radio applications.
* On Windows, it is a serial port. On Linux, a pseudo terminal.
* *
* Input: * Input:
* *
* Outputs: * Outputs:
* *
* Description: This provides a pseudo terminal for communication with a client application. * Description: It implements the KISS TNC protocol as described in:
*
* It implements the KISS TNC protocol as described in:
* http://www.ka9q.net/papers/kiss.html * http://www.ka9q.net/papers/kiss.html
* *
* Briefly, a frame is composed of * Briefly, a frame is composed of
@ -113,20 +112,19 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#if __WIN32__ #if __WIN32__
#include <stdlib.h> #include <stdlib.h>
#include <windows.h>
#else #else
#define __USE_XOPEN2KXSI 1
#define __USE_XOPEN 1
//#define __USE_POSIX 1
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <fcntl.h> #include <fcntl.h>
#include <termios.h> #include <termios.h>
#include <sys/select.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#ifdef __OpenBSD__ #ifdef __OpenBSD__
@ -139,7 +137,7 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "tq.h" #include "tq.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
@ -242,17 +240,22 @@ void hex_dump (unsigned char *p, int len);
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static MYFDTYPE kiss_open_pt (void); #if __WIN32__
static MYFDTYPE kiss_open_nullmodem (char *device); static MYFDTYPE kiss_open_nullmodem (char *device);
#else
static MYFDTYPE kiss_open_pt (void);
#endif
void kiss_init (struct misc_config_s *mc) void kiss_init (struct misc_config_s *mc)
{ {
int e;
#if __WIN32__ #if __WIN32__
HANDLE kiss_nullmodem_listen_th; HANDLE kiss_nullmodem_listen_th;
#else #else
pthread_t kiss_pterm_listen_tid; pthread_t kiss_pterm_listen_tid;
pthread_t kiss_nullmodem_listen_tid; //pthread_t kiss_nullmodem_listen_tid;
int e;
#endif #endif
memset (&kf, 0, sizeof(kf)); memset (&kf, 0, sizeof(kf));
@ -355,7 +358,7 @@ static MYFDTYPE kiss_open_pt (void)
char *pts; char *pts;
struct termios ts; struct termios ts;
int e; int e;
//int flags;
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
@ -397,39 +400,31 @@ static MYFDTYPE kiss_open_pt (void)
} }
/* /*
* After running for a while on Linux, the write eventually * We had a problem here since the beginning.
* blocks if no one is reading from the other side of * If no one was reading from the other end of the pseudo
* the pseudo terminal. We get stuck on the kiss data * terminal, the buffer space would eventually fill up,
* write and reception stops. * the write here would block, and the receive decode
* thread would get stuck.
* *
* I tried using ioctl(,TIOCOUTQ,) to see how much was in * March 2016 - A "select" was put before the read to
* the queue but that always returned zero. (Ubuntu) * solve a different problem. With that in place, we can
* * now use non-blocking I/O and detect the buffer full
* Let's try using non-blocking writes and see if we get * condition here.
* the EWOULDBLOCK status instead of hanging.
*/ */
#if 0 // this is worse. all writes fail. errno = 0 bad file descriptor // text_color_set(DW_COLOR_DEBUG);
flags = fcntl(fd, F_GETFL, 0); // dw_printf("Debug: Try using non-blocking mode for pseudo terminal.\n");
int flags = fcntl(fd, F_GETFL, 0);
e = fcntl (fd, F_SETFL, flags | O_NONBLOCK); e = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
if (e != 0) { if (e != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno); dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno);
perror ("pt fcntl"); perror ("pt fcntl");
} }
#endif
#if 0 // same
flags = 1;
e = ioctl (fd, FIONBIO, &flags);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno);
perror ("pt ioctl");
}
#endif
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf("Virtual KISS TNC is available on %s\n", pt_slave_name); dw_printf("Virtual KISS TNC is available on %s\n", pt_slave_name);
dw_printf("WARNING - Dire Wolf will hang eventually if nothing is reading from it.\n");
#if 1 #if 1
@ -633,12 +628,10 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
* flen - Length of raw received frame not including the FCS * flen - Length of raw received frame not including the FCS
* or -1 for a text string. * or -1 for a text string.
* *
*
* Description: Send message to client. * Description: Send message to client.
* We really don't care if anyone is listening or not. * We really don't care if anyone is listening or not.
* I don't even know if we can find out. * I don't even know if we can find out.
* *
*
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
@ -646,7 +639,6 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
{ {
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2]; unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
int kiss_len; int kiss_len;
int j;
int err; int err;
#if ! __WIN32__ #if ! __WIN32__
@ -675,7 +667,7 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
assert (flen < sizeof(stemp)); assert (flen < (int)(sizeof(stemp)));
stemp[0] = (chan << 4) + 0; stemp[0] = (chan << 4) + 0;
memcpy (stemp+1, fbuf, flen); memcpy (stemp+1, fbuf, flen);
@ -702,14 +694,12 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
/* Pseudo terminal for Cygwin and Linux. */ /* Pseudo terminal for Cygwin and Linux. */
err = write (pt_master_fd, kiss_buff, (size_t)kiss_len); err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
if (err == -1 && errno == EWOULDBLOCK) { if (err == -1 && errno == EWOULDBLOCK) {
#if DEBUG
text_color_set (DW_COLOR_INFO); text_color_set (DW_COLOR_INFO);
dw_printf ("KISS SEND - discarding message because write would block.\n"); dw_printf ("KISS SEND - Discarding message because no one is listening.\n");
#endif dw_printf ("This happens when you use the -p option and don't read from the pseudo terminal.\n");
} }
else if (err != kiss_len) else if (err != kiss_len)
{ {
@ -764,7 +754,7 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
//nullmodem_fd = MYFDERROR; //nullmodem_fd = MYFDERROR;
} }
} }
else if (nwritten != kiss_len) else if ((int)nwritten != kiss_len)
{ {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len); dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len);
@ -772,13 +762,6 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
//nullmodem_fd = MYFDERROR; //nullmodem_fd = MYFDERROR;
} }
#if DEBUG
/* Could wait with GetOverlappedResult but we never */
/* have an issues in this direction. */
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("KISS SEND completed. wrote %d / %d\n", nwritten, kiss_len);
#endif
#else #else
err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len); err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len);
if (err != len) if (err != len)
@ -796,20 +779,25 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: kiss_listen_thread * Name: kiss_get
* *
* Purpose: Wait for messages from an application. * Purpose: Read one byte from the KISS client app.
* *
* Global In: nullmodem_fd or pt_master_fd * Global In: nullmodem_fd (Windows) or pt_master_fd (Linux)
* *
* Description: Process messages from the client application. * Returns: one byte (value 0 - 255) or terminate thread on error.
*
* Description: There is room for improvment here. Reading one byte
* at a time is inefficient. We could read a large block
* into a local buffer and return a byte from that most of the time.
* Is it worth the effort? I don't know. With GHz processors and
* the low data rate here it might not make a noticable difference.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
/* Return one byte (value 0 - 255) or terminate thread on error. */
static int kiss_get (/* MYFDTYPE fd*/ void ) static int kiss_get (/* MYFDTYPE fd*/ void )
{ {
@ -884,25 +872,76 @@ static int kiss_get (/* MYFDTYPE fd*/ void )
#else /* Linux/Cygwin version */ #else /* Linux/Cygwin version */
int n = 0; int n = 0;
fd_set fd_in, fd_ex;
int rc;
while ( n == 0 ) { while ( n == 0 ) {
n = read(pt_master_fd, &ch, (size_t)1); /*
* Since the beginning we've always had a couple annoying problems with
* the pseudo terminal KISS interface.
* When using "kissattach" we would sometimes get the error message:
*
* kissattach: Error setting line discipline: TIOCSETD: Device or resource busy
* Are you sure you have enabled MKISS support in the kernel
* or, if you made it a module, that the module is loaded?
*
* martinhpedersen came up with the interesting idea of putting in a "select"
* before the "read" and explained it like this:
*
* "Reading from master fd of the pty before the client has connected leads
* to trouble with kissattach. Use select to check if the slave has sent
* any data before trying to read from it."
*
* "This fix resolves the issue by not reading from the pty's master fd, until
* kissattach has opened and configured the slave. This is implemented using
* select() to wait for data before reading from the master fd."
*
* The submitted code looked like this:
*
* FD_ZERO(&fd_in);
* rc = select(pt_master_fd + 1, &fd_in, NULL, &fd_in, NULL);
*
* That doesn't look right to me for a couple reasons.
* First, I would expect to use FD_SET for the fd.
* Second, using the same bit mask for two arguments doesn't seem
* like a good idea because select modifies them.
* When I tried running it, we don't get the failure message
* anymore but the select never returns so we can't read data from
* the KISS client app.
*
* I think this is what we want.
*
* Tested on Raspian (ARM) and Ubuntu (x86_64).
* We don't get the error from kissattach anymore.
*/
if (n != 1) { FD_ZERO(&fd_in);
FD_SET(pt_master_fd, &fd_in);
FD_ZERO(&fd_ex);
FD_SET(pt_master_fd, &fd_ex);
rc = select(pt_master_fd + 1, &fd_in, NULL, &fd_ex, NULL);
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf ("select returns %d, errno=%d, fd=%d, fd_in=%08x, fd_ex=%08x\n", rc, errno, pt_master_fd, *((int*)(&fd_in)), *((int*)(&fd_in)));
#endif
if (rc == 0)
{
continue; // When could we get a 0?
}
if (rc == MYFDERROR
|| (n = read(pt_master_fd, &ch, (size_t)1)) != 1)
{
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError receiving kiss message from client application. Closing %s.\n\n", pt_slave_name); dw_printf ("\nError receiving KISS message from client application. Closing %s.\n\n", pt_slave_name);
perror (""); perror ("");
/* Message added between 1.1 beta test and final version 1.1 */
/* TODO: Determine root cause and find proper solution. */
dw_printf ("This is a known problem that sometimes shows up when using with kissattach.\n");
dw_printf ("There are a couple work-arounds described in the Dire Wolf User Guide\n");
dw_printf ("and the Raspberry Pi APRS documents.\n");
close (pt_master_fd); close (pt_master_fd);
pt_master_fd = MYFDERROR; pt_master_fd = MYFDERROR;
@ -935,6 +974,18 @@ static int kiss_get (/* MYFDTYPE fd*/ void )
} }
/*-------------------------------------------------------------------
*
* Name: kiss_listen_thread
*
* Purpose: Read messages from serial port KISS client application.
*
* Global In: nullmodem_fd (Windows) or pt_master_fd (Linux)
*
* Description: Reads bytes from the KISS client app and
* sends them to kiss_rec_byte for processing.
*
*--------------------------------------------------------------------*/
static THREAD_F kiss_listen_thread (void *arg) static THREAD_F kiss_listen_thread (void *arg)

View File

@ -68,6 +68,8 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -75,7 +77,6 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "kiss_frame.h" #include "kiss_frame.h"
@ -86,7 +87,7 @@
void hex_dump (unsigned char *p, int len); void hex_dump (unsigned char *p, int len);
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug);
#if KISSTEST #if KISSTEST
@ -98,6 +99,10 @@ void text_color_set (dw_color_t c)
return; return;
} }
#else
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug);
#endif #endif
@ -561,9 +566,15 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
break; break;
default: default:
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Invalid command %d\n", cmd); dw_printf ("KISS Invalid command %d\n", cmd);
kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len);
text_color_set(DW_COLOR_INFO);
dw_printf ("Troubleshooting tip:\n");
dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n");
dw_printf ("all communication with the client application.\n");
break; break;
} }
@ -630,7 +641,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
#if KISSTEST #if KISSTEST
main () int main ()
{ {
unsigned char din[512]; unsigned char din[512];
unsigned char kissed[520]; unsigned char kissed[520];

View File

@ -95,10 +95,12 @@
*/ */
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__ #if __WIN32__
#include <winsock2.h> #include <winsock2.h>
#define _WIN32_WINNT 0x0501 #include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#include <ws2tcpip.h>
#else #else
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
@ -115,11 +117,9 @@
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "tq.h" #include "tq.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
@ -128,6 +128,8 @@
#include "kiss_frame.h" #include "kiss_frame.h"
#include "xmit.h" #include "xmit.h"
void hex_dump (unsigned char *p, int len); // This should be in a .h file.
static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */ static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
// TODO: multiple instances if multiple KISS network clients! // TODO: multiple instances if multiple KISS network clients!
@ -139,8 +141,15 @@ static int client_sock; /* File descriptor for socket for */
/* (Don't use SOCKET type because it is unsigned.) */ /* (Don't use SOCKET type because it is unsigned.) */
static void * connect_listen_thread (void *arg); // TODO: define in one place, use everywhere.
static void * kissnet_listen_thread (void *arg); #if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
static THREAD_F connect_listen_thread (void *arg);
static THREAD_F kissnet_listen_thread (void *arg);
@ -184,8 +193,8 @@ void kissnet_init (struct misc_config_s *mc)
#else #else
pthread_t connect_listen_tid; pthread_t connect_listen_tid;
pthread_t cmd_listen_tid; pthread_t cmd_listen_tid;
#endif
int e; int e;
#endif
int kiss_port = mc->kiss_port; int kiss_port = mc->kiss_port;
@ -208,7 +217,7 @@ void kissnet_init (struct misc_config_s *mc)
* This waits for a client to connect and sets client_sock. * This waits for a client to connect and sets client_sock.
*/ */
#if __WIN32__ #if __WIN32__
connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL); connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL);
if (connect_listen_th == NULL) { if (connect_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create KISS socket connect listening thread\n"); dw_printf ("Could not create KISS socket connect listening thread\n");
@ -227,7 +236,7 @@ void kissnet_init (struct misc_config_s *mc)
* This reads messages from client when client_sock is valid. * This reads messages from client when client_sock is valid.
*/ */
#if __WIN32__ #if __WIN32__
cmd_listen_th = _beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL); cmd_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL);
if (cmd_listen_th == NULL) { if (cmd_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create KISS socket command listening thread\n"); dw_printf ("Could not create KISS socket command listening thread\n");
@ -263,7 +272,7 @@ void kissnet_init (struct misc_config_s *mc)
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static void * connect_listen_thread (void *arg) static THREAD_F connect_listen_thread (void *arg)
{ {
#if __WIN32__ #if __WIN32__
@ -284,7 +293,7 @@ static void * connect_listen_thread (void *arg)
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed: %d\n", err); dw_printf("WSAStartup failed: %d\n", err);
return (NULL); return (0);
} }
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
@ -292,7 +301,7 @@ static void * connect_listen_thread (void *arg)
dw_printf("Could not find a usable version of Winsock.dll\n"); dw_printf("Could not find a usable version of Winsock.dll\n");
WSACleanup(); WSACleanup();
//sleep (1); //sleep (1);
return (NULL); return (0);
} }
memset (&hints, 0, sizeof(hints)); memset (&hints, 0, sizeof(hints));
@ -307,14 +316,14 @@ static void * connect_listen_thread (void *arg)
dw_printf("getaddrinfo failed: %d\n", err); dw_printf("getaddrinfo failed: %d\n", err);
//sleep (1); //sleep (1);
WSACleanup(); WSACleanup();
return (NULL); return (0);
} }
listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (listen_sock == INVALID_SOCKET) { if (listen_sock == INVALID_SOCKET) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError()); dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
return (NULL); return (0);
} }
#if DEBUG #if DEBUG
@ -331,7 +340,7 @@ static void * connect_listen_thread (void *arg)
freeaddrinfo(ai); freeaddrinfo(ai);
closesocket(listen_sock); closesocket(listen_sock);
WSACleanup(); WSACleanup();
return (NULL); return (0);
} }
freeaddrinfo(ai); freeaddrinfo(ai);
@ -353,7 +362,7 @@ static void * connect_listen_thread (void *arg)
{ {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Listen failed with error: %d\n", WSAGetLastError()); dw_printf("Listen failed with error: %d\n", WSAGetLastError());
return (NULL); return (0);
} }
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
@ -366,7 +375,7 @@ static void * connect_listen_thread (void *arg)
dw_printf("Accept failed with error: %d\n", WSAGetLastError()); dw_printf("Accept failed with error: %d\n", WSAGetLastError());
closesocket(listen_sock); closesocket(listen_sock);
WSACleanup(); WSACleanup();
return (NULL); return (0);
} }
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
@ -481,7 +490,6 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
{ {
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
int kiss_len; int kiss_len;
int j;
int err; int err;
@ -501,7 +509,7 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
unsigned char stemp[AX25_MAX_PACKET_LEN + 1]; unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
assert (flen < sizeof(stemp)); assert (flen < (int)(sizeof(stemp)));
stemp[0] = (chan << 4) + 0; stemp[0] = (chan << 4) + 0;
memcpy (stemp+1, fbuf, flen); memcpy (stemp+1, fbuf, flen);
@ -580,6 +588,7 @@ static int read_from_socket (int fd, char *ptr, int len)
#if __WIN32__ #if __WIN32__
//TODO: any flags for send/recv? //TODO: any flags for send/recv?
//TODO: Would be useful to have more detailed explanation from the error code.
n = recv (fd, ptr + got_bytes, len - got_bytes, 0); n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
#else #else
@ -659,7 +668,7 @@ static int kiss_get (void)
} }
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError reading KISS byte from clent application. Closing connection.\n\n"); dw_printf ("\nError reading KISS byte from client application. Closing connection.\n\n");
#if __WIN32__ #if __WIN32__
closesocket (client_sock); closesocket (client_sock);
#else #else
@ -671,7 +680,7 @@ static int kiss_get (void)
static void * kissnet_listen_thread (void *arg) static THREAD_F kissnet_listen_thread (void *arg)
{ {
unsigned char ch; unsigned char ch;
@ -685,7 +694,11 @@ static void * kissnet_listen_thread (void *arg)
kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet); kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet);
} }
return (NULL); /* to suppress compiler warning. */ #if __WIN32__
return(0);
#else
return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */
#endif
} /* end kissnet_listen_thread */ } /* end kissnet_listen_thread */

View File

@ -30,6 +30,7 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -40,7 +41,6 @@
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#include "direwolf.h"
#include "latlong.h" #include "latlong.h"
#include "textcolor.h" #include "textcolor.h"

View File

@ -1,5 +1,7 @@
/* Latitude / Longitude to UTM conversion */ /* Latitude / Longitude to UTM conversion */
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>

87
log.c
View File

@ -31,6 +31,8 @@
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include <assert.h> #include <assert.h>
@ -42,8 +44,6 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "decode_aprs.h" #include "decode_aprs.h"
@ -341,6 +341,89 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
/*------------------------------------------------------------------
*
* Function: log_rr_bits
*
* Purpose: Quick hack to look at the C and RR bits just to see what is there.
* This seems like a good place because it is a small subset of the function above.
*
* Inputs: A - Explode information from APRS packet.
*
* pp - Received packet object.
*
*------------------------------------------------------------------*/
void log_rr_bits (decode_aprs_t *A, packet_t pp)
{
if (1) {
char heard[AX25_MAX_ADDR_LEN+1];
char smfr[60];
char *p;
int src_c, dst_c;
int src_rr, dst_rr;
// Sanitize system type (manufacturer) changing any comma to period.
strlcpy (smfr, A->g_mfr, sizeof(smfr));
for (p=smfr; *p!='\0'; p++) {
if (*p == ',') *p = '.';
}
/* Who are we hearing? Original station or digipeater? */
/* Similar code in direwolf.c. Combine into one function? */
strlcpy(heard, "", sizeof(heard));
if (pp != NULL) {
int h;
if (ax25_get_num_addr(pp) == 0) {
/* Not AX.25. No station to display below. */
h = -1;
strlcpy (heard, "", sizeof(heard));
}
else {
h = ax25_get_heard(pp);
ax25_get_addr_with_ssid(pp, h, heard);
}
if (h >= AX25_REPEATER_2 &&
strncmp(heard, "WIDE", 4) == 0 &&
isdigit(heard[4]) &&
heard[5] == '\0') {
ax25_get_addr_with_ssid(pp, h-1, heard);
strlcat (heard, "?", sizeof(heard));
}
src_c = ax25_get_h (pp, AX25_SOURCE);
dst_c = ax25_get_h (pp, AX25_DESTINATION);
src_rr = ax25_get_rr (pp, AX25_SOURCE);
dst_rr = ax25_get_rr (pp, AX25_DESTINATION);
// C RR for source
// C RR for destination
// system type
// source
// station heard
text_color_set(DW_COLOR_INFO);
dw_printf ("%d %d%d %d %d%d,%s,%s,%s\n",
src_c, (src_rr >> 1) & 1, src_rr & 1,
dst_c, (dst_rr >> 1) & 1, dst_rr & 1,
smfr, A->g_src, heard);
}
}
} /* end log_rr_bits */
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Function: log_term * Function: log_term

2
log.h
View File

@ -14,4 +14,6 @@ void log_init (char *path);
void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
void log_rr_bits (decode_aprs_t *A, packet_t pp);
void log_term (void); void log_term (void);

View File

@ -18,17 +18,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#if __WIN32__
char *strsep(char **stringp, const char *delim);
#endif
#include "direwolf.h"
/* /*
* Information we gather for each thing. * Information we gather for each thing.

View File

@ -79,6 +79,8 @@ p = Packet dump in hexadecimal.
.P .P
g = GPS interface. g = GPS interface.
.P .P
W = Waypoints for position or object reports.
.P
t = Tracker beacon. t = Tracker beacon.
.P .P
o = Output controls such as PTT and DCD. o = Output controls such as PTT and DCD.

View File

@ -5,9 +5,18 @@
* *
* Waypoint icon codes for use in the $PMGNWPL sentence. * 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, March 2003
*
* http://www.gpsinformation.org/mag-proto-2-11.pdf
*
*
* That's 13 years ago. There should be something newer available but I can't find it.
*
* The is based on the newer models at the time. Earlier models had shorter incompatible icon lists.
*/ */
#define MGN_crossed_square "a" #define MGN_crossed_square "a"
#define MGN_box "b" #define MGN_box "b"
#define MGN_house "c" #define MGN_house "c"
@ -110,7 +119,7 @@ static const char mgn_primary_symtab[SYMTAB_SIZE][3] = {
MGN_default, // E 37 EYEBALL (Eye catcher!) MGN_default, // E 37 EYEBALL (Eye catcher!)
MGN_default, // F 38 Farm Vehicle (tractor) MGN_default, // F 38 Farm Vehicle (tractor)
MGN_default, // G 39 Grid Square (6 digit) MGN_default, // G 39 Grid Square (6 digit)
MGN_default, // H 40 HOTEL (blue bed symbol) MGN_hotel, // H 40 HOTEL (blue bed symbol)
MGN_aerial, // I 41 TcpIp on air network stn MGN_aerial, // I 41 TcpIp on air network stn
MGN_default, // J 42 MGN_default, // J 42
MGN_default, // K 43 School MGN_default, // K 43 School
@ -150,7 +159,7 @@ static const char mgn_primary_symtab[SYMTAB_SIZE][3] = {
MGN_aerial, // m 77 Mic-E Repeater MGN_aerial, // m 77 Mic-E Repeater
MGN_default, // n 78 Node (black bulls-eye) MGN_default, // n 78 Node (black bulls-eye)
MGN_default, // o 79 EOC MGN_default, // o 79 EOC
MGN_default, // p 80 ROVER (puppy, or dog) MGN_zoo, // p 80 ROVER (puppy, or dog)
MGN_default, // q 81 GRID SQ shown above 128 m MGN_default, // q 81 GRID SQ shown above 128 m
MGN_aerial, // r 82 Repeater MGN_aerial, // r 82 Repeater
MGN_default, // s 83 SHIP (pwr boat) MGN_default, // s 83 SHIP (pwr boat)
@ -202,7 +211,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_default, // > 30 OVERLAYED CAR MGN_default, // > 30 OVERLAYED CAR
MGN_tourist_info, // ? 31 INFO Kiosk (Blue box with ?) MGN_tourist_info, // ? 31 INFO Kiosk (Blue box with ?)
MGN_default, // @ 32 HURICANE/Trop-Storm MGN_default, // @ 32 HURICANE/Trop-Storm
MGN_default, // A 33 overlayBOX DTMF & RFID & XO MGN_box, // A 33 overlayBOX DTMF & RFID & XO
MGN_default, // B 34 Blwng Snow (& future codes) MGN_default, // B 34 Blwng Snow (& future codes)
MGN_boating, // C 35 Coast Guard MGN_boating, // C 35 Coast Guard
MGN_default, // D 36 Drizzle (proposed APRStt) MGN_default, // D 36 Drizzle (proposed APRStt)
@ -215,7 +224,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_default, // K 43 Kenwood HT (W) MGN_default, // K 43 Kenwood HT (W)
MGN_lighthouse, // L 44 Lighthouse MGN_lighthouse, // L 44 Lighthouse
MGN_default, // M 45 MARS (A=Army,N=Navy,F=AF) MGN_default, // M 45 MARS (A=Army,N=Navy,F=AF)
MGN_nav_aid, // N 46 Navigation Buoy MGN_buoy, // N 46 Navigation Buoy
MGN_airport, // O 47 Rocket MGN_airport, // O 47 Rocket
MGN_default, // P 48 Parking MGN_default, // P 48 Parking
MGN_default, // Q 49 QUAKE MGN_default, // Q 49 QUAKE
@ -223,7 +232,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_aerial, // S 51 Satellite/Pacsat MGN_aerial, // S 51 Satellite/Pacsat
MGN_default, // T 52 Thunderstorm MGN_default, // T 52 Thunderstorm
MGN_default, // U 53 SUNNY MGN_default, // U 53 SUNNY
MGN_default, // V 54 VORTAC Nav Aid MGN_nav_aid, // V 54 VORTAC Nav Aid
MGN_default, // W 55 # NWS site (NWS options) MGN_default, // W 55 # NWS site (NWS options)
MGN_default, // X 56 Pharmacy Rx (Apothicary) MGN_default, // X 56 Pharmacy Rx (Apothicary)
MGN_aerial, // Y 57 Radios and devices MGN_aerial, // Y 57 Radios and devices
@ -242,7 +251,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_default, // f 70 Funnel Cloud MGN_default, // f 70 Funnel Cloud
MGN_default, // g 71 Gale Flags MGN_default, // g 71 Gale Flags
MGN_default, // h 72 Store. or HAMFST Hh=HAM store MGN_default, // h 72 Store. or HAMFST Hh=HAM store
MGN_default, // i 73 BOX or points of Interest MGN_box, // i 73 BOX or points of Interest
MGN_default, // j 74 WorkZone (Steam Shovel) MGN_default, // j 74 WorkZone (Steam Shovel)
MGN_default, // k 75 Special Vehicle SUV,ATV,4x4 MGN_default, // k 75 Special Vehicle SUV,ATV,4x4
MGN_default, // l 76 Areas (box,circles,etc) MGN_default, // l 76 Areas (box,circles,etc)

797
mheard.c Normal file
View File

@ -0,0 +1,797 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* File: mheard.c
*
* Purpose: Maintain a list of all stations heard.
*
* Description: This was added for IGate statistics and checking if a user is local
* but would also be useful for the AGW network protocol 'H' request.
*
* This application has no GUI and is not interactive so
* I'm not sure what else we might do with the information.
*
* Why mheard instead of just heard? The KPC-3+ has an MHEARD command
* to list stations heard. I guess that stuck in my mind.
* It should be noted that here "heard" refers to the AX.25 source station.
* Before printing the received packet, the "heard" line refers to who
* we heard over the radio. This would be the digipeater with "*" after
* its name.
*
* Future Ideas: Someone suggested using SQLite to store the information
* so other applications could access it.
*
*------------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "textcolor.h"
#include "decode_aprs.h"
#include "ax25_pad.h"
#include "hdlc_rec2.h" // for retry_t
#include "mheard.h"
#include "latlong.h"
// This is getting updated from two different threads so we need a critical region
// for adding new nodes.
static dw_mutex_t mheard_mutex;
// I think we can get away without a critical region for reading if we follow these
// rules:
//
// (1) When adding a new node, make sure it is complete, including next ptr,
// before adding it to the list.
// (2) Update the start of list pointer last.
// (2) Nothing gets deleted.
// If we ever decide to start cleaning out very old data, all access would then
// need to use the mutex.
/*
* Information for each station heard over the radio or from Internet Server.
*/
typedef struct mheard_s {
struct mheard_s *pnext; // Pointer to next in list.
char callsign[AX25_MAX_ADDR_LEN]; // Callsign from the AX.25 source field.
int count; // Number of times heard.
// We don't use this for anything.
// Just something potentially interesting when looking at data dump.
int chan; // Most recent channel where heard.
int num_digi_hops; // Number of digipeater hops before we heard it.
// over radio. Zero when heard directly.
time_t last_heard_rf; // Timestamp when last heard over the radio.
time_t last_heard_is; // Timestamp when last heard from Internet Server.
double dlat, dlon; // Last position. G_UNKNOWN for unknown.
int msp; // Allow message sender positon report.
// When non zero, an IS>RF position report is allowed.
// Then decremented.
// What else would be useful?
// The AGW protocol is by channel and returns
// first heard in addition to last heard.
} mheard_t;
/*
* The list could be quite long and we hit this a lot so use a hash table.
*/
#define MHEARD_HASH_SIZE 73 // Best if prime number.
static mheard_t *mheard_hash[MHEARD_HASH_SIZE];
static inline int hash_index(char *callsign) {
int n = 0;
char *p = callsign;
while (*p != '\0') {
n += *p++;
}
return (n % MHEARD_HASH_SIZE);
}
static mheard_t *mheard_ptr(char *callsign) {
int n = hash_index(callsign);
mheard_t *p = mheard_hash[n];
while (p != NULL) {
if (strcmp(callsign,p->callsign) == 0) return (p);
p = p->pnext;
}
return (NULL);
}
static int mheard_debug = 0;
/*------------------------------------------------------------------
*
* Function: mheard_init
*
* Purpose: Initialization at start of application.
*
* Inputs: debug - Debug level.
*
* Description: Clear pointer table.
* Save debug level for later use.
*
*------------------------------------------------------------------*/
void mheard_init (int debug)
{
int i;
mheard_debug = debug;
for (i = 0; i < MHEARD_HASH_SIZE; i++) {
mheard_hash[i] = NULL;
}
/*
* Mutex to coordinate adding new nodes.
*/
dw_mutex_init(&mheard_mutex);
} /* end mheard_init */
/*------------------------------------------------------------------
*
* Function: mheard_dump
*
* Purpose: Print list of stations heard for debugging.
*
*------------------------------------------------------------------*/
/* convert some time in past to hours:minutes text format. */
static void age(char *result, time_t now, time_t t)
{
int s, h, m;
if (t == 0) {
strcpy (result, "- ");
return;
}
s = (int)(now - t);
m = s / 60;
h = m / 60;
m -= h * 60;
sprintf (result, "%4d:%02d", h, m);
}
/* Convert latitude, longitude to text or - if not defined. */
static void latlon (char * result, double dlat, double dlon)
{
if (dlat != G_UNKNOWN && dlon != G_UNKNOWN) {
sprintf (result, "%6.2f %7.2f", dlat, dlon);
}
else {
strcpy (result, " - - ");
}
}
/* Compare last heard time for use with qsort. */
#define MAXX(x,y) (((x)>(y))?(x):(y))
static int compar(const void *a, const void *b)
{
mheard_t *ma = *((mheard_t **)a);
mheard_t *mb = *((mheard_t **)b);
time_t ta = MAXX(ma->last_heard_rf, ma->last_heard_is);
time_t tb = MAXX(mb->last_heard_rf, mb->last_heard_is);
return (tb - ta);
}
#define MAXDUMP 1000
static void mheard_dump (void)
{
int i;
mheard_t *mptr;
time_t now = time(NULL);
char stuff[80];
char rf[16]; // hours:minutes
char is[16];
char position[40];
mheard_t *station[MAXDUMP];
int num_stations = 0;
/* Get linear array of node pointers so they can be sorted easily. */
num_stations = 0;
for (i = 0; i < MHEARD_HASH_SIZE; i++) {
for (mptr = mheard_hash[i]; mptr != NULL; mptr = mptr->pnext) {
if (num_stations < MAXDUMP) {
station[num_stations] = mptr;
num_stations++;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("mheard_dump - max number of stations exceeded.\n");
}
}
}
/* Sort most recently heard to the top then print. */
qsort (station, num_stations, sizeof(mheard_t *), compar);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("callsign cnt chan hops RF IS lat long msp\n");
for (i = 0; i < num_stations; i++) {
mptr = station[i];
age (rf, now, mptr->last_heard_rf);
age (is, now, mptr->last_heard_is);
latlon (position, mptr->dlat, mptr->dlon);
snprintf (stuff, sizeof(stuff), "%-9s %3d %d %d %7s %7s %s %d\n",
mptr->callsign, mptr->count, mptr->chan, mptr->num_digi_hops, rf, is, position, mptr->msp);
dw_printf ("%s", stuff);
}
} /* end mheard_dump */
/*------------------------------------------------------------------
*
* Function: mheard_save_rf
*
* Purpose: Save information about station heard over the radio.
*
* Inputs: chan - Radio channel where heard.
*
* A - Exploded information from APRS packet.
*
* pp - Received packet object.
*
* alevel - audio level.
*
* retries - Amount of effort to get a good CRC.
*
* Description: Calling sequence was copied from "log_write."
* It has a lot more than what we currently keep but the
* hooks are there so it will be easy to capture additional
* information when the need arises.
*
*------------------------------------------------------------------*/
void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries)
{
time_t now = time(NULL);
char source[AX25_MAX_ADDR_LEN];
int hops;
mheard_t *mptr;
ax25_get_addr_with_ssid (pp, AX25_SOURCE, source);
/*
* How many digipeaters has it gone thru before we hear it?
* We can count the number of digi addresses that are marked as "has been used."
* This is not always accurate because there is inconsistency in digipeater behavior.
* The base AX.25 spec seems clear in this regard. The used digipeaters should
* should accurately reflict the path taken by the packet. Sometimes we see excess
* stuff in there. Even when you understand what is going on, it is still an ambiguous
* situation. Look for my rant in the User Guide.
*/
hops = ax25_get_heard(pp) - AX25_SOURCE;
mptr = mheard_ptr(source);
if (mptr == NULL) {
int i;
/*
* Not heard before. Add it.
*/
if (mheard_debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard_save_rf: %s %d - added new\n", source, hops);
}
mptr = calloc(sizeof(mheard_t),1);
strlcpy (mptr->callsign, source, sizeof(mptr->callsign));
mptr->count = 1;
mptr->chan = chan;
mptr->num_digi_hops = hops;
mptr->last_heard_rf = now;
mptr->dlat = G_UNKNOWN;
mptr->dlon = G_UNKNOWN;
i = hash_index(source);
dw_mutex_lock (&mheard_mutex);
mptr->pnext = mheard_hash[i]; // before inserting into list.
mheard_hash[i] = mptr;
dw_mutex_unlock (&mheard_mutex);
}
else {
/*
* Update existing entry.
* The only tricky part here is that we might hear the same transmission
* several times. First direct, then thru various digipeater paths.
* We are interested in the shortest path if heard very recently.
*/
if (hops > mptr->num_digi_hops && (int)(now - mptr->last_heard_rf) < 15) {
if (mheard_debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard_save_rf: %s %d - skip because hops was %d %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf) );
}
}
else {
if (mheard_debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard_save_rf: %s %d - update time, was %d hops %d seconds ago.\n", source, hops, mptr->num_digi_hops, (int)(now - mptr->last_heard_rf));
}
mptr->count++;
mptr->chan = chan;
mptr->num_digi_hops = hops;
mptr->last_heard_rf = now;
}
}
if (A->g_lat != G_UNKNOWN && A->g_lon != G_UNKNOWN) {
mptr->dlat = A->g_lat;
mptr->dlon = A->g_lon;
}
if (mheard_debug >= 2) {
int limit = 10; // normally 30 or 60. more frequent when debugging.
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit));
}
if (mheard_debug) {
mheard_dump ();
}
} /* end mheard_save_rf */
/*------------------------------------------------------------------
*
* Function: mheard_save_is
*
* Purpose: Save information about station heard via Internet Server.
*
* Inputs: ptext - Packet in monitoring text form as sent by the Internet server.
*
* Any trailing CRLF should have been removed.
* Typical examples:
*
* KA1BTK-5>APDR13,TCPIP*,qAC,T2IRELAND:=4237.62N/07040.68W$/A=-00054 http://aprsdroid.org/
* N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:<IGATE,MSG_CNT=0,LOC_CNT=0
* K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148
* KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile
*
* Notice how the final address in the header might not
* be a valid AX.25 address. We see a 9 character address
* (with no ssid) and an ssid of two letters.
*
* The "q construct" ( http://www.aprs-is.net/q.aspx ) provides
* a clue about the journey taken but I don't think we care here.
*
* All we should care about here is the the source address.
*
* Description:
*
*------------------------------------------------------------------*/
void mheard_save_is (char *ptext)
{
packet_t pp;
time_t now = time(NULL);
char source[AX25_MAX_ADDR_LEN];
mheard_t *mptr;
/*
* Try to parse it into a packet object.
* This will contain "q constructs" and we might see an address
* with two alphnumeric characters in the SSID so we must use
* the non-strict parsing.
*
* Bug: Up to 8 digipeaters are allowed in radio format.
* There is a potential of finding a larger number here.
*/
pp = ax25_from_text(ptext, 0);
if (pp == NULL) {
if (mheard_debug) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("mheard_save_is: Could not parse message from server.\n");
dw_printf ("%s\n", ptext);
}
return;
}
ax25_get_addr_with_ssid (pp, AX25_SOURCE, source);
mptr = mheard_ptr(source);
if (mptr == NULL) {
int i;
/*
* Not heard before. Add it.
*/
if (mheard_debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard_save_is: %s - added new\n", source);
}
mptr = calloc(sizeof(mheard_t),1);
strlcpy (mptr->callsign, source, sizeof(mptr->callsign));
mptr->count = 1;
mptr->last_heard_is = now;
mptr->dlat = G_UNKNOWN;
mptr->dlon = G_UNKNOWN;
i = hash_index(source);
dw_mutex_lock (&mheard_mutex);
mptr->pnext = mheard_hash[i]; // before inserting into list.
mheard_hash[i] = mptr;
dw_mutex_unlock (&mheard_mutex);
}
else {
/* Already there. UPdate last heard from IS time. */
if (mheard_debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard_save_is: %s - update time, was %d seconds ago.\n", source, (int)(now - mptr->last_heard_rf));
}
mptr->count++;
mptr->last_heard_is = now;
}
// Is is desirable to save any location in this case?
// I don't think it would help.
// The whole purpose of keeping the location is for message sending filter.
// We wouldn't want to try sending a message to the station if we didn't hear it over the radio.
// On the other hand, I don't think it would hurt.
// The filter always includes a time since last heard over the radi.
if (mheard_debug >= 2) {
int limit = 10; // normally 30 or 60
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard debug, %d min, DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d\n", limit, mheard_count(0,limit), mheard_count(2,limit), mheard_count(8,limit));
}
if (mheard_debug) {
mheard_dump ();
}
ax25_delete (pp);
} /* end mheard_save_is */
/*------------------------------------------------------------------
*
* Function: mheard_count
*
* Purpose: Count local stations for IGate statistics report like this:
*
* <IGATE,MSG_CNT=1,LOC_CNT=25
*
* Inputs: max_hops - Include only stations heard with this number of
* digipeater hops or less. For reporting, we might use:
*
* 0 for DIR_CNT (heard directly)
* IGate transmit path for LOC_CNT.
* e.g. 3 for WIDE1-1,WIDE2-2
* 8 for RF_CNT.
*
* time_limit - Include only stations heard within this many minutes.
* Typically 30 or 60.
*
* Returns: Number to be used in the statistics report.
*
* Description: Look for discussion here: http://www.tapr.org/pipermail/aprssig/2016-June/045837.html
*
* Lynn KJ4ERJ:
*
* For APRSISCE/32, "Local" is defined as those stations to which messages
* would be gated if any are received from the APRS-IS. This currently
* means unique stations heard within the past 30 minutes with at most two
* used path hops.
*
* I added DIR_CNT and RF_CNT with comma delimiters to APRSISCE/32's IGate
* status. DIR_CNT is the count of unique stations received on RF in the
* past 30 minutes with no used hops. RF_CNT is the total count of unique
* stations received on RF in the past 30 minutes.
*
* Steve K4HG:
*
* The number of hops defining local should match the number of hops of the
* outgoing packets from the IGate. So if the path is only WIDE, then local
* should only be stations heard direct or through one hop. From the beginning
* I was very much against on a standardization of the outgoing IGate path,
* hams should be free to manage their local RF network in a way that works
* for them. Busy areas one hop may be best, I lived in an area where three was
* a much better choice. I avoided as much as possible prescribing anything
* that might change between locations.
*
* The intent was how many stations are there for which messages could be IGated.
* IGate software keeps an internal list of the 'local' stations so it knows
* when to IGate a message, and this number should be the length of that list.
* Some IGates have a parameter for local timeout, 1 hour was the original default,
* so if in an hour the IGate has not heard another local packet the station is
* dropped from the local list. Messages will no longer be IGated to that station
* and the station count would drop by one. The number should not just continue to rise.
*
*
*------------------------------------------------------------------*/
int mheard_count (int max_hops, int time_limit)
{
time_t since = time(NULL) - time_limit * 60;
int count = 0;
int i;
mheard_t *p;
for (i = 0; i < MHEARD_HASH_SIZE; i++) {
for (p = mheard_hash[i]; p != NULL; p = p->pnext) {
if (p->last_heard_rf >= since && p->num_digi_hops <= max_hops) {
count++;
}
}
}
if (mheard_debug == 1) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("mheard_count(<= %d digi hops, last %d minutes) returns %d\n", max_hops, time_limit, count);
}
return (count);
} /* end mheard_count */
/*------------------------------------------------------------------
*
* Function: mheard_was_recently_nearby
*
* Purpose: Determine whether given station was heard recently on the radio.
*
* Inputs: role - "addressee" or "source" if debug out is desired.
* Otherwise empty string.
*
* callsign - Callsign for station.
*
* time_limit - Include only stations heard within this many minutes.
* Typically 30 or 60.
*
* max_hops - Include only stations heard with this number of
* digipeater hops or less. For reporting, we might use:
*
* dlat, dlon, km - Include only stations within distance of location.
* Not used if G_UNKNOWN is supplied.
*
* Returns: 1 for true, 0 for false.
*
*------------------------------------------------------------------*/
int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km)
{
mheard_t *mptr;
time_t now;
int heard_ago;
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN) {
dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops, and within %.1f km of %.2f %.2f?\n", role, callsign, time_limit, max_hops, km, dlat, dlon);
}
else {
dw_printf ("Was message %s %s heard in the past %d minutes, with %d or fewer digipeater hops?\n", role, callsign, time_limit, max_hops);
}
}
mptr = mheard_ptr(callsign);
if (mptr == NULL || mptr->last_heard_rf == 0) {
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("No, we have not heard %s over the radio.\n", callsign);
}
return (0);
}
now = time(NULL);
heard_ago = (int)(now - mptr->last_heard_rf) / 60;
if (heard_ago > time_limit) {
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("No, %s was last heard over the radio %d minutes ago with %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops);
}
return (0);
}
if (mptr->num_digi_hops > max_hops) {
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("No, %s was last heard over the radio with %d digipeater hops %d minutes ago.\n", callsign, mptr->num_digi_hops, heard_ago);
}
return (0);
}
// Apply physical distance check?
if (dlat != G_UNKNOWN && dlon != G_UNKNOWN && km != G_UNKNOWN && mptr->dlat != G_UNKNOWN && mptr->dlon != G_UNKNOWN) {
double dist = ll_distance_km (mptr->dlat, mptr->dlon, dlat, dlon);
if (dist > km) {
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("No, %s was %.1f km away although it was %d digipeater hops %d minutes ago.\n", callsign, dist, mptr->num_digi_hops, heard_ago);
}
return (0);
}
else {
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops. Last location %.1f km away.\n", callsign, heard_ago, mptr->num_digi_hops, dist);
}
return (1);
}
}
// Passed all the tests.
if (role != NULL && strlen(role) > 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Yes, %s last heard over radio %d minutes ago, %d digipeater hops.\n", callsign, heard_ago, mptr->num_digi_hops);
}
return (1);
} /* end mheard_was_recently_nearby */
/*------------------------------------------------------------------
*
* Function: mheard_set_msp
*
* Purpose: Set the "message sender position" count for specified station.
*
* Inputs: callsign - Callsign for station which sent the "message."
*
* num - Number of position reports to allow. Typically 1.
*
*------------------------------------------------------------------*/
void mheard_set_msp (char *callsign, int num)
{
mheard_t *mptr;
mptr = mheard_ptr(callsign);
if (mptr != NULL) {
mptr->msp = num;
if (mheard_debug) {
text_color_set(DW_COLOR_INFO);
dw_printf ("MSP for %s set to %d\n", callsign, num);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: Can't find %s to set MSP.\n", callsign);
}
} /* end mheard_set_msp */
/*------------------------------------------------------------------
*
* Function: mheard_get_msp
*
* Purpose: Get the "message sender position" count for specified station.
*
* Inputs: callsign - Callsign for station which sent the "message."
*
* Returns: The cound for the specified station.
* 0 if not found.
*
*------------------------------------------------------------------*/
int mheard_get_msp (char *callsign)
{
mheard_t *mptr;
mptr = mheard_ptr(callsign);
if (mptr != NULL) {
if (mheard_debug) {
text_color_set(DW_COLOR_INFO);
dw_printf ("MSP for %s is %d\n", callsign, mptr->msp);
}
return (mptr->msp); // Should we have a time limit?
}
return (0);
} /* end mheard_get_msp */
/* end mheard.c */

20
mheard.h Normal file
View File

@ -0,0 +1,20 @@
/* mheard.h */
#include "decode_aprs.h" // for decode_aprs_t
void mheard_init (int debug);
void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);
void mheard_save_is (char *ptext);
int mheard_count (int max_hops, int time_limit);
int mheard_was_recently_nearby (char *role, char *callsign, int time_limit, int max_hops, double dlat, double dlon, double km);
void mheard_set_msp (char *callsign, int num);
int mheard_get_msp (char *callsign);

View File

@ -43,8 +43,7 @@
* Find the first occurrence of find in s, ignore case. * Find the first occurrence of find in s, ignore case.
*/ */
char * char *
strcasestr(s, find) strcasestr(const char *s, const char *find)
const char *s, *find;
{ {
char c, sc; char c, sc;
size_t len; size_t len;

View File

@ -30,6 +30,8 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -39,8 +41,6 @@
#include <time.h> #include <time.h>
#include <math.h> #include <math.h>
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "audio.h" #include "audio.h"
#include "ptt.h" #include "ptt.h"
@ -122,7 +122,7 @@ static const struct morse_s {
}; };
#define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s)) #define NUM_MORSE ((int)(sizeof(morse) / sizeof(struct morse_s)))
static void morse_tone (int chan, int tu, int wpm); static void morse_tone (int chan, int tu, int wpm);
static void morse_quiet (int chan, int tu, int wpm); static void morse_quiet (int chan, int tu, int wpm);
@ -272,6 +272,8 @@ int morse_send (int chan, char *str, int wpm, int txdelay, int txtail)
time_units, morse_units_str(str)); time_units, morse_units_str(str));
} }
audio_flush(ACHAN2ADEV(chan));
return (txdelay + return (txdelay +
(int) (TIME_UNITS_TO_MS(time_units, wpm) + 0.5) + (int) (TIME_UNITS_TO_MS(time_units, wpm) + 0.5) +
txtail); txtail);

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -70,9 +70,11 @@
* Set limit on number of packets in fix up later queue. * Set limit on number of packets in fix up later queue.
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
//#define DEBUG 1 //#define DEBUG 1
#define DIGIPEATER_C #define DIGIPEATER_C
#include "direwolf.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -80,7 +82,6 @@
#include <stdio.h> #include <stdio.h>
#include <sys/unistd.h> #include <sys/unistd.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "multi_modem.h" #include "multi_modem.h"
@ -108,7 +109,10 @@ static struct {
#define PROCESS_AFTER_BITS 2 //#define PROCESS_AFTER_BITS 2 // version 1.4. Was a little short for skew of PSK with different modem types, optional pre-filter
#define PROCESS_AFTER_BITS 3
static int process_age[MAX_CHANS]; static int process_age[MAX_CHANS];
@ -154,7 +158,11 @@ void multi_modem_init (struct audio_s *pa)
dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__); dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__);
save_audio_config_p->achan[chan].baud = DEFAULT_BAUD; save_audio_config_p->achan[chan].baud = DEFAULT_BAUD;
} }
process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].baud; int real_baud = save_audio_config_p->achan[chan].baud;
if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) real_baud = save_audio_config_p->achan[chan].baud / 2;
if (save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) real_baud = save_audio_config_p->achan[chan].baud / 3;
process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / real_baud ;
//crc_queue_of_last_to_app[chan] = NULL; //crc_queue_of_last_to_app[chan] = NULL;
} }
} }
@ -462,7 +470,27 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
if (save_audio_config_p->achan[chan].num_subchan == 1 && if (save_audio_config_p->achan[chan].num_subchan == 1 &&
save_audio_config_p->achan[chan].num_slicers == 1) { save_audio_config_p->achan[chan].num_slicers == 1) {
dlq_append (DLQ_REC_FRAME, chan, subchan, slice, pp, alevel, retries, "");
int drop_it = 0;
if (save_audio_config_p->recv_error_rate != 0) {
float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
//text_color_set(DW_COLOR_INFO);
//dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
if (save_audio_config_p->recv_error_rate / 100.0 > r) {
drop_it = 1;
text_color_set(DW_COLOR_INFO);
dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
}
}
if (drop_it ) {
ax25_delete (pp);
}
else {
dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
}
return; return;
} }
@ -641,7 +669,26 @@ static void pick_best_candidate (int chan)
j = subchan_from_n(best_n); j = subchan_from_n(best_n);
k = slice_from_n(best_n); k = slice_from_n(best_n);
dlq_append (DLQ_REC_FRAME, chan, j, k, int drop_it = 0;
if (save_audio_config_p->recv_error_rate != 0) {
float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0
//text_color_set(DW_COLOR_INFO);
//dw_printf ("TEMP DEBUG. recv error rate = %d\n", save_audio_config_p->recv_error_rate);
if (save_audio_config_p->recv_error_rate / 100.0 > r) {
drop_it = 1;
text_color_set(DW_COLOR_INFO);
dw_printf ("Intentionally dropping incoming frame. Recv Error rate = %d per cent.\n", save_audio_config_p->recv_error_rate);
}
}
if ( drop_it ) {
ax25_delete (candidate[chan][j][k].packet_p);
candidate[chan][j][k].packet_p = NULL;
}
else {
dlq_rec_frame (chan, j, k,
candidate[chan][j][k].packet_p, candidate[chan][j][k].packet_p,
candidate[chan][j][k].alevel, candidate[chan][j][k].alevel,
(int)(candidate[chan][j][k].retries), (int)(candidate[chan][j][k].retries),
@ -649,6 +696,7 @@ static void pick_best_candidate (int chan)
/* Someone else owns it now and will delete it later. */ /* Someone else owns it now and will delete it later. */
candidate[chan][j][k].packet_p = NULL; candidate[chan][j][k].packet_p = NULL;
}
/* Clear in preparation for next time. */ /* Clear in preparation for next time. */

465
nmea.c
View File

@ -1,465 +0,0 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
//#define DEBUG 1
// TODO: rename this to waypoint & integrate.
/*------------------------------------------------------------------
*
* Module: nmea.c
*
* Purpose: Receive NMEA sentences from a GPS receiver.
* Send NMEA waypoint sentences to GPS display or mapping application.
*
*---------------------------------------------------------------*/
#include <stdio.h>
#include <unistd.h>
#if __WIN32__
#include <stdlib.h>
#include <windows.h>
#else
#define __USE_XOPEN2KXSI 1
#define __USE_XOPEN 1
//#define __USE_POSIX 1
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#endif
#include <assert.h>
#include <string.h>
#if __WIN32__
char *strsep(char **stringp, const char *delim);
#endif
#include "direwolf.h"
#include "config.h"
#include "ax25_pad.h"
#include "textcolor.h"
//#include "xmit.h"
#include "latlong.h"
#include "nmea.h"
#include "grm_sym.h" /* Garmin symbols */
#include "mgn_icon.h" /* Magellan icons */
#include "serial_port.h"
// TODO: receive buffer... static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
static MYFDTYPE nmea_port_fd = MYFDERROR;
static void nmea_send_sentence (char *sent);
//static void nmea_parse_gps (char *sentence);
static int nmea_debug = 0; /* Print information flowing from and to attached device. */
void nmea_set_debug (int n)
{
nmea_debug = n;
}
/*-------------------------------------------------------------------
*
* Name: nmea_init
*
* Purpose: Initialization for NMEA communication port.
*
* Inputs: mc->nmea_port - name of serial port.
*
* Global output: nmea_port_fd
*
*
* Description: (1) Open serial port device.
*
*---------------------------------------------------------------*/
void nmea_init (struct misc_config_s *mc)
{
/*
* Open serial port connection.
* 4800 baud is standard for GPS.
* Should add an option to allow changing someday.
*/
if (strlen(mc->nmea_port) > 0) {
nmea_port_fd = serial_port_open (mc->nmea_port, 4800);
}
#if DEBUG
text_color_set (DW_COLOR_DEBUG);
dw_printf ("end of nmea_init: nmea_port_fd = %d\n", nmea_port_fd);
#endif
}
/*-------------------------------------------------------------------
*
* Name: append_checksum
*
* Purpose: Append checksum to the sentence.
*
* In/out: sentence - NMEA sentence beginning with '$'.
*
* Description: Checksum is exclusive of characters except leading '$'.
* We append '*' and an upper case two hexadecimal value.
*
*--------------------------------------------------------------------*/
static void append_checksum (char *sentence)
{
char *p;
int cs;
assert (sentence[0] == '$');
cs = 0;
for (p = sentence+1; *p != '\0'; p++) {
cs ^= *p;
}
sprintf (p, "*%02X", cs & 0xff);
// Add crlf too?
} /* end append_checksum */
/*-------------------------------------------------------------------
*
* Name: nema_send_waypoint
*
* Purpose: Convert APRS position or object into NMEA waypoint sentence
* for use by a GPS display or other mapping application.
*
* Inputs: wname_in - Name of waypoint.
* dlat - Latitude.
* dlong - Longitude.
* symtab - Symbol table or overlay character.
* symbol - Symbol code.
* alt - Altitude in meters or G_UNKOWN.
* course - Course in degrees or ??? for unknown.
* speed - Speed in knots ?? or ??
* comment - Description or message.
*
*
* Description: Currently we send multiple styles. Maybe someday there might
* be an option to send a selected subset.
*
* $GPWPL - Generic with only location and name.
* $PGRMW - Garmin, adds altitude, symbol, and comment
* to previously named waypoint.
* $PMGNWPL - Magellan, more complete for stationary objects.
* $PKWDWPL - Kenwood with APRS style symbol but missing comment.
*
*--------------------------------------------------------------------*/
void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol,
float alt, float course, float speed, char *comment)
{
char wname[12]; /* Waypoint name. Any , or * removed. */
char slat[12]; /* DDMM.mmmm */
char slat_ns[2]; /* N or S */
char slong[12]; /* DDDMM.mmmm */
char slong_ew[2]; /* E or W */
char sentence[500];
char salt[12]; /* altitude as string, empty if unknown */
char sspeed[12]; /* speed as string, empty if unknown */
char scourse[12]; /* course as string, empty if unknown */
int grm_sym; /* Garmin symbol code. */
char sicon[5]; /* Magellan icon string */
char stime[8];
char sdate[8];
char *p;
// Remove any comma from name.
// TODO: remove any , or * from comment.
strcpy (wname, wname_in);
for (p=wname; *p != '\0'; p++) {
if (*p == ',') *p = ' ';
if (*p == '*') *p = ' ';
}
// Convert position to character form.
latitude_to_nmea (dlat, slat, slat_ns);
longitude_to_nmea (dlong, slong, slong_ew);
/*
* Generic.
*
* $GPWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,wname*99
*
* Where,
* ddmm.mmmm,ns is latitude
* dddmm.mmmm,ew is longitude
* wname is the waypoint name
* *99 is checksum
*/
snprintf (sentence, sizeof(sentence), "$GPWPL,%s,%s,%s,%s,%s", slat, slat_ns, slong, slong_ew, wname);
append_checksum (sentence);
nmea_send_sentence (sentence);
/*
* Garmin - https://www8.garmin.com/support/pdf/NMEA_0183.pdf
* http://gpsinformation.net/mag-proto.htm
*
* $PGRMW,wname,alt,symbol,comment*99
*
* Where,
*
* wname is waypoint name. Must match existing waypoint.
* alt is altitude in meters.
* symbol is symbol code. Hexadecimal up to FFFF.
* See Garmin Device Interface Specification 001-0063-00.
* comment is comment for the waypoint.
* *99 is checksum
*/
if (alt == G_UNKNOWN) {
strcpy (salt, "");
}
else {
snprintf (salt, sizeof(salt), "%.1f", alt);
}
grm_sym = 0x1234; // TODO
snprintf (sentence, sizeof(sentence), "$PGRMW,%s,%s,%04X,%s", wname, salt, grm_sym, comment);
append_checksum (sentence);
nmea_send_sentence (sentence);
/*
* Magellan - http://www.gpsinformation.org/mag-proto-2-11.pdf
*
* $PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99
*
* Where,
* ddmm.mmmm,ns is latitude
* dddmm.mmmm,ew is longitude
* alt is altitude
* unit is M for meters or F for feet
* wname is the waypoint name
* comment is message or comment
* icon is one or two letters for icon code
* xx is waypoint type which is optional, not well
* defined, and not used in their example.
* *99 is checksum
*/
// TODO: icon
snprintf (sicon, sizeof(sicon), "??");
snprintf (sentence, sizeof(sentence), "$PMGNWPL,%s,%s,%s,%s,%s,M,%s,%s,%s",
slat, slat_ns, slong, slong_ew, salt, wname, comment, sicon);
append_checksum (sentence);
nmea_send_sentence (sentence);
/*
* Kenwood - Speculation due to no official spec found so far.
*
* $PKWDWPL,hhmmss,v,ddmm.mmmm,ns,dddmm.mmmm,ew,speed,course,ddmmyy,alt,wname,ts*99
*
* Where,
* hhmmss is time in UTC. Should we supply current
* time or only pass along time from
* received signal?
* v indicates valid data ?????????????????
* Why would we send if not valid?
* ddmm.mmmm,ns is latitude
* dddmm.mmmm,ew is longitude
* speed is speed in UNITS ??? knots ?????
* course is course in degrees
* ddmmyy is date. Same question as time.
* alt is altitude. in UNITS ??? meters ???
* wname is the waypoint name
* ts are the table and symbol.
* Non-standard parsing would be required
* to deal with these for symbol:
* , Boy Scouts / Girl Scouts
* * SnowMobile / Snow
* *99 is checksum
*
* Oddly, there is not place for comment.
*/
if (speed == G_UNKNOWN) {
strcpy (sspeed, "");
}
else {
snprintf (sspeed, sizeof(sspeed), "%.1f", speed);
}
if (course == G_UNKNOWN) {
strcpy (scourse, "");
}
else {
snprintf (scourse, sizeof(scourse), "%.1f", course);
}
// TODO: how to handle time & date ???
strcpy (stime, "123456");
strcpy (sdate, "123456");
snprintf (sentence, sizeof(sentence), "$PKWDWPL,%s,V,%s,%s,%s,%s,%s,%s,%s,%s,%s,%c%c",
stime, slat, slat_ns, slong, slong_ew,
sspeed, scourse, sdate, salt, wname, symtab, symbol);
append_checksum (sentence);
nmea_send_sentence (sentence);
/*
* One application recognizes these. Not implemented at this time.
*
* $GPTLL,01,ddmm.mmmm,ns,dddmm.mmmm,ew,tname,000000.00,T,R*99
*
* Where,
* ddmm.mmmm,ns is latitude
* dddmm.mmmm,ew is longitude
* tname is the target name
* 000000.00 is timestamp ???
* T is target status (S for need help)
* R is reference target ???
* *99 is checksum
*
*
* $GPTXT,01,01,tname,message*99
*
* Where,
*
* 01 is total number of messages in transmission
* 01 is message number in this transmission
* tname is target name. Should match name in WPL or TTL.
* message is the message.
* *99 is checksum
*
*/
} /* end nmea_send_waypoint */
#if 0
* d710a menu 603 $GPWPL $PMGNWPL $PKWDWPL
symbol mapping
https://freepository.com:444/50lItuLQ7fW6s-web/browser/Tracker2/trunk/sources/waypoint.c?rev=108
Data Transmission Protocol For Magellan Products - version 2.11
$PMGNWPL,4651.529,N,07111.425,W,0000000,M,GC5A5F, GC. The straight line,a*13
$PMGNWPL,3549.499,N,08650.827,W,0000257,M,HOME,HOME,c*4D
http://gpsbabel.sourcearchive.com/documentation/1.3.7~cvs1/magproto_8c-source.html
sscanf(trkmsg,"$PMGNWPL,%lf,%c,%lf,%c,%d,%c,%[^,],%[^,]",
&latdeg,&latdir,
&lngdeg,&lngdir,
&alt,&altunits,shortname,descr); then icon
snprintf(obuf, sizeof(), "PMGNWPL,%4.3f,%c,%09.3f,%c,%07.0f,M,%-.*s,%-.46s,%s",
lat, ilat < 0 ? 'S' : 'N',
lon, ilon < 0 ? 'W' : 'E',
waypointp->altitude == unknown_alt ?
0 : waypointp->altitude,
wpt_len,
owpt,
odesc,
icon_token);
https://freepository.com:444/50lItuLQ7fW6s-web/changeset/108/Tracker2/trunk/sources/waypoint.c
$PMGNWPL,4106.003,S,14640.214,E,0000069,M,KISSING SPOT,,a*3C
*
https://freepository.com:444/50lItuLQ7fW6s-web/changeset/325
#endif
static void nmea_send_sentence (char *sent)
{
int err;
int len = strlen(sent);
if (nmea_port_fd == MYFDERROR) {
return;
}
text_color_set(DW_COLOR_XMIT);
dw_printf ("%s\n", sent);
if (nmea_debug) {
// TODO: debugg out... nmea_debug_print (TO_CLIENT, NULL, nmea_buff+1, nmea_len-2);
}
// TODO: need to append CR LF.
serial_port_write (nmea_port_fd, sent, len);
} /* nmea_send_sentence */
/* end nmea.c */

19
nmea.h
View File

@ -1,19 +0,0 @@
/*
* Name: nmea.h
*/
#include "ax25_pad.h" /* for packet_t */
#include "config.h" /* for struct misc_config_s */
void nmea_init (struct misc_config_s *misc_config);
void nmea_set_debug (int n);
void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol,
float alt, float course, float speed, char *comment);
/* end nmea.h */

524
pfilter.c
View File

@ -37,6 +37,7 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <unistd.h> #include <unistd.h>
#include <assert.h> #include <assert.h>
@ -45,16 +46,48 @@
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#if __WIN32__
char *strsep(char **stringp, const char *delim);
#endif
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "decode_aprs.h" #include "decode_aprs.h"
#include "latlong.h" #include "latlong.h"
#include "pfilter.h" #include "pfilter.h"
#include "mheard.h"
/*
* Global stuff (to this file)
*
* These are set by init function.
*/
static struct igate_config_s *save_igate_config_p;
static int s_debug = 0;
/*-------------------------------------------------------------------
*
* Name: pfilter_init
*
* Purpose: One time initialization when main application starts up.
*
* Inputs: p_igate_config - IGate configuration.
*
* debug_level - 0 no debug output.
* 1 single summary line with final result. Indent by 1.
* 2 details from each filter specification. Indent by 3.
* 3 Logical operators. Indent by 2.
*
*--------------------------------------------------------------------*/
void pfilter_init (struct igate_config_s *p_igate_config, int debug_level)
{
s_debug = debug_level;
save_igate_config_p = p_igate_config;
}
@ -69,9 +102,6 @@ typedef struct pfstate_s {
int from_chan; /* From and to channels. MAX_CHANS is used for IGate. */ int from_chan; /* From and to channels. MAX_CHANS is used for IGate. */
int to_chan; /* Used only for debug and error messages. */ int to_chan; /* Used only for debug and error messages. */
// TODO: might want to put channels and packet here so we only pass one thing around.
/* /*
* Original filter string from config file. * Original filter string from config file.
* All control characters should be replaced by spaces. * All control characters should be replaced by spaces.
@ -85,9 +115,15 @@ typedef struct pfstate_s {
packet_t pp; packet_t pp;
/* /*
* Packet split into separate parts. * Are we processing APRS or connected mode?
* This determines whch types of filters are available.
*/
int is_aprs;
/*
* Packet split into separate parts if APRS.
* Most interesting fields are: * Most interesting fields are:
* g_src - source address *
* g_symbol_table - / \ or overlay * g_symbol_table - / \ or overlay
* g_symbol_code * g_symbol_code
* g_lat, g_lon - Location * g_lat, g_lon - Location
@ -118,8 +154,17 @@ static void print_error (pfstate_t *pf, char *msg);
static int filt_bodgu (pfstate_t *pf, char *pattern); static int filt_bodgu (pfstate_t *pf, char *pattern);
static int filt_t (pfstate_t *pf); static int filt_t (pfstate_t *pf);
static int filt_r (pfstate_t *pf); static int filt_r (pfstate_t *pf, char *sdist);
static int filt_s (pfstate_t *pf); static int filt_s (pfstate_t *pf);
static int filt_i (pfstate_t *pf);
static char *bool2text (int val)
{
if (val == 1) return "TRUE";
if (val == 0) return "FALSE";
if (val == -1) return "ERROR";
return "OOPS!";
}
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
@ -137,6 +182,10 @@ static int filt_s (pfstate_t *pf);
* *
* pp - Packet object handle. * pp - Packet object handle.
* *
* is_aprs - True for APRS, false for connected mode digipeater.
* Connected mode allows a subset of the filter types, only
* looking at the addresses, not information part contents.
*
* Returns: 1 = yes * Returns: 1 = yes
* 0 = no * 0 = no
* -1 = error detected * -1 = error detected
@ -146,7 +195,7 @@ static int filt_s (pfstate_t *pf);
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp) int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs)
{ {
pfstate_t pfstate; pfstate_t pfstate;
char *p; char *p;
@ -155,13 +204,15 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
assert (from_chan >= 0 && from_chan <= MAX_CHANS); assert (from_chan >= 0 && from_chan <= MAX_CHANS);
assert (to_chan >= 0 && to_chan <= MAX_CHANS); assert (to_chan >= 0 && to_chan <= MAX_CHANS);
memset (&pfstate, 0, sizeof(pfstate));
if (pp == NULL) { if (pp == NULL) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n"); dw_printf ("INTERNAL ERROR in pfilter: NULL packet pointer. Please report this!\n");
return (-1); return (-1);
} }
if (filter == NULL) { if (filter == NULL) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n"); dw_printf ("INTERNAL ERROR in pfilter: NULL filter string pointer. Please report this!\n");
return (-1); return (-1);
} }
@ -169,10 +220,9 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
pfstate.from_chan = from_chan; pfstate.from_chan = from_chan;
pfstate.to_chan = to_chan; pfstate.to_chan = to_chan;
/* Copy filter string, removing any control characters. */ /* Copy filter string, changing any control characers to spaces. */
memset (pfstate.filter_str, 0, sizeof(pfstate.filter_str)); strlcpy (pfstate.filter_str, filter, sizeof(pfstate.filter_str));
strncpy (pfstate.filter_str, filter, MAX_FILTER_LEN-1);
pfstate.nexti = 0; pfstate.nexti = 0;
for (p = pfstate.filter_str; *p != '\0'; p++) { for (p = pfstate.filter_str; *p != '\0'; p++) {
@ -182,7 +232,11 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
} }
pfstate.pp = pp; pfstate.pp = pp;
pfstate.is_aprs = is_aprs;
if (is_aprs) {
decode_aprs (&pfstate.decoded, pp, 1); decode_aprs (&pfstate.decoded, pp, 1);
}
next_token(&pfstate); next_token(&pfstate);
@ -201,6 +255,23 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp)
result = -1; result = -1;
} }
} }
if (s_debug >= 1) {
text_color_set(DW_COLOR_DEBUG);
if (from_chan == MAX_CHANS) {
dw_printf (" Packet filter from IGate to radio channel %d returns %s\n", to_chan, bool2text(result));
}
else if (to_chan == MAX_CHANS) {
dw_printf (" Packet filter from radio channel %d to IGate returns %s\n", from_chan, bool2text(result));
}
else if (is_aprs) {
dw_printf (" Packet filter for APRS digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result));
}
else {
dw_printf (" Packet filter for traditional digipeater from radio channel %d to %d returns %s\n", from_chan, to_chan, bool2text(result));
}
}
return (result); return (result);
} /* end pfilter */ } /* end pfilter */
@ -339,6 +410,12 @@ static int parse_or_expr (pfstate_t *pf)
next_token (pf); next_token (pf);
e = parse_and_expr (pf); e = parse_and_expr (pf);
if (s_debug >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s | %s\n", bool2text(result), bool2text(e));
}
if (e < 0) return (-1); if (e < 0) return (-1);
result |= e; result |= e;
} }
@ -360,6 +437,12 @@ static int parse_and_expr (pfstate_t *pf)
next_token (pf); next_token (pf);
e = parse_primary (pf); e = parse_primary (pf);
if (s_debug >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s & %s\n", bool2text(result), bool2text(e));
}
if (e < 0) return (-1); if (e < 0) return (-1);
result &= e; result &= e;
} }
@ -393,6 +476,12 @@ static int parse_primary (pfstate_t *pf)
next_token (pf); next_token (pf);
e = parse_primary (pf); e = parse_primary (pf);
if (s_debug >= 3) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" ! %s\n", bool2text(e));
}
if (e < 0) result = -1; if (e < 0) result = -1;
else result = ! e; else result = ! e;
} }
@ -419,14 +508,29 @@ static int parse_primary (pfstate_t *pf)
* 0 = no * 0 = no
* -1 = error detected * -1 = error detected
* *
* Description: All filter specifications are allowed for APRS.
* Only those dealing with addresses are allowed for connected digipeater.
*
* b - budlist (source)
* d - digipeaters used
* v - digipeaters not used
* u - unproto (destination)
*
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
static int parse_filter_spec (pfstate_t *pf) static int parse_filter_spec (pfstate_t *pf)
{ {
int result = -1; int result = -1;
char addr[AX25_MAX_ADDR_LEN];
if ( ( ! pf->is_aprs) && strchr ("01bdvu", pf->token_str[0]) == NULL) {
print_error (pf, "Only b, d, v, and u specifications are allowed for connected mode digipeater filtering.");
result = -1;
next_token (pf);
return (result);
}
/* undocumented: can use 0 or 1 for testing. */ /* undocumented: can use 0 or 1 for testing. */
@ -439,14 +543,33 @@ static int parse_filter_spec (pfstate_t *pf)
/* simple string matching */ /* simple string matching */
/* b - budlist */
else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) {
/* Budlist - source address */ /* Budlist - source address */
result = filt_bodgu (pf, pf->decoded.g_src); char addr[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr);
result = filt_bodgu (pf, addr);
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr);
} }
}
/* o - object or item name */
else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'o' && ispunct(pf->token_str[1])) {
/* Object or item name */
result = filt_bodgu (pf, pf->decoded.g_name); result = filt_bodgu (pf, pf->decoded.g_name);
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_name);
} }
}
/* d - was digipeated by */
else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) {
int n; int n;
// loop on all digipeaters // loop on all digipeaters
@ -454,11 +577,26 @@ static int parse_filter_spec (pfstate_t *pf)
for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
// Consider only those with the H (has-been-used) bit set. // Consider only those with the H (has-been-used) bit set.
if (ax25_get_h (pf->pp, n)) { if (ax25_get_h (pf->pp, n)) {
char addr[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pf->pp, n, addr); ax25_get_addr_with_ssid (pf->pp, n, addr);
result = filt_bodgu (pf, addr); result = filt_bodgu (pf, addr);
} }
} }
if (s_debug >= 2) {
char path[100];
ax25_format_via_path (pf->pp, path, sizeof(path));
if (strlen(path) == 0) {
strcpy (path, "no digipeater path");
} }
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path);
}
}
/* v - via not used */
else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) {
int n; int n;
// loop on all digipeaters (mnemonic Via) // loop on all digipeaters (mnemonic Via)
@ -467,54 +605,139 @@ static int parse_filter_spec (pfstate_t *pf)
// This is different than the previous "d" filter. // This is different than the previous "d" filter.
// Consider only those where the the H (has-been-used) bit is NOT set. // Consider only those where the the H (has-been-used) bit is NOT set.
if ( ! ax25_get_h (pf->pp, n)) { if ( ! ax25_get_h (pf->pp, n)) {
char addr[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pf->pp, n, addr); ax25_get_addr_with_ssid (pf->pp, n, addr);
result = filt_bodgu (pf, addr); result = filt_bodgu (pf, addr);
} }
} }
if (s_debug >= 2) {
char path[100];
ax25_format_via_path (pf->pp, path, sizeof(path));
if (strlen(path) == 0) {
strcpy (path, "no digipeater path");
} }
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), path);
}
}
/* g - Addressee of message. */
else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) {
/* Addressee of message. */
if (ax25_get_dti(pf->pp) == ':') { if (ax25_get_dti(pf->pp) == ':') {
result = filt_bodgu (pf, pf->decoded.g_addressee); result = filt_bodgu (pf, pf->decoded.g_addressee);
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee);
}
} }
else { else {
result = 0; result = 0;
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "not a message");
} }
} }
}
/* u - unproto (destination) */
else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
/* Unproto (destination) - probably want to exclude mic-e types */ /* Probably want to exclude mic-e types */
/* because destintation is used for part of location. */ /* because destination is used for part of location. */
if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') { if (ax25_get_dti(pf->pp) != '\'' && ax25_get_dti(pf->pp) != '`') {
char addr[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr); ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
result = filt_bodgu (pf, addr); result = filt_bodgu (pf, addr);
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), addr);
}
} }
else { else {
result = 0; result = 0;
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), "MIC-E packet type");
}
} }
} }
/* type: position, weather, etc. */ /* t - type: position, weather, etc. */
else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) {
ax25_get_addr_with_ssid (pf->pp, AX25_DESTINATION, addr);
result = filt_t (pf); result = filt_t (pf);
if (s_debug >= 2) {
char *infop = NULL;
(void) ax25_get_info (pf->pp, (unsigned char **)(&infop));
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %c data type indicator\n", pf->token_str, bool2text(result), *infop);
}
} }
/* range */ /* r - range */
else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 'r' && ispunct(pf->token_str[1])) {
/* range */ /* range */
result = filt_r (pf); char sdist[30];
strcpy (sdist, "unknown distance");
result = filt_r (pf, sdist);
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %s returns %s for %s\n", pf->token_str, bool2text(result), sdist);
}
} }
/* symbol */ /* s - symbol */
else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) { else if (pf->token_str[0] == 's' && ispunct(pf->token_str[1])) {
/* symbol */ /* symbol */
result = filt_s (pf); result = filt_s (pf);
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
if (pf->decoded.g_symbol_table == '/') {
dw_printf (" %s returns %s for symbol %c in primary table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code);
} }
else if (pf->decoded.g_symbol_table == '\\') {
dw_printf (" %s returns %s for symbol %c in alternate table\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code);
}
else {
dw_printf (" %s returns %s for symbol %c with overlay %c\n", pf->token_str, bool2text(result), pf->decoded.g_symbol_code, pf->decoded.g_symbol_table);
}
}
}
/* i - IGate messaging default */
else if (pf->token_str[0] == 'i' && ispunct(pf->token_str[1])) {
/* IGatge messaging */
result = filt_i (pf);
if (s_debug >= 2) {
char *infop = NULL;
(void) ax25_get_info (pf->pp, (unsigned char **)(&infop));
text_color_set(DW_COLOR_DEBUG);
if (*infop == ':' && ! is_telem_metadata(infop)) {
dw_printf (" %s returns %s for message to %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee);
}
else {
dw_printf (" %s returns %s for not an APRS 'message'\n", pf->token_str, bool2text(result));
}
}
}
/* unrecognized filter type */
else { else {
char stemp[80]; char stemp[80];
@ -543,7 +766,7 @@ static int parse_filter_spec (pfstate_t *pf)
* Digipeater d/digi1/digi2... * Digipeater d/digi1/digi2...
* Group Msg g/call1/call2... * Group Msg g/call1/call2...
* Unproto u/unproto1/unproto2... * Unproto u/unproto1/unproto2...
* Via-not-yet v/digi1/digi2... * Via-not-yet v/digi1/digi2...noteapd
* *
* arg - Value to match from source addr, destination, * arg - Value to match from source addr, destination,
* used digipeater, object name, etc. * used digipeater, object name, etc.
@ -580,7 +803,7 @@ static int filt_bodgu (pfstate_t *pf, char *arg)
/* Wildcarding. Should have single * on end. */ /* Wildcarding. Should have single * on end. */
mlen = w - v; mlen = w - v;
if (mlen != strlen(v) - 1) { if (mlen != (int)(strlen(v) - 1)) {
print_error (pf, "Any wildcard * must be at the end of pattern.\n"); print_error (pf, "Any wildcard * must be at the end of pattern.\n");
return (-1); return (-1);
} }
@ -622,7 +845,7 @@ static int filt_bodgu (pfstate_t *pf, char *arg)
/* Telemetry metadata is a special case of message. */ /* Telemetry metadata is a special case of message. */
/* We want to categorize it as telemetry rather than message. */ /* We want to categorize it as telemetry rather than message. */
static int is_telem_metadata (char *infop) int is_telem_metadata (char *infop)
{ {
if (*infop != ':') return (0); if (*infop != ':') return (0);
if (strlen(infop) < 16) return (0); if (strlen(infop) < 16) return (0);
@ -757,6 +980,8 @@ static int filt_t (pfstate_t *pf)
* *
* decoded.g_lat & decoded.g_lon * decoded.g_lat & decoded.g_lon
* *
* Outputs: sdist - Distance as a string for troubleshooting.
*
* Returns: 1 = yes * Returns: 1 = yes
* 0 = no * 0 = no
* -1 = error detected * -1 = error detected
@ -765,7 +990,7 @@ static int filt_t (pfstate_t *pf)
* *
*------------------------------------------------------------------------------*/ *------------------------------------------------------------------------------*/
static int filt_r (pfstate_t *pf) static int filt_r (pfstate_t *pf, char *sdist)
{ {
char str[MAX_TOKEN_LEN]; char str[MAX_TOKEN_LEN];
char *cp; char *cp;
@ -806,10 +1031,7 @@ static int filt_r (pfstate_t *pf)
km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon); km = ll_distance_km (dlat, dlon, pf->decoded.g_lat, pf->decoded.g_lon);
sprintf (sdist, "%.2f km", km);
text_color_set (DW_COLOR_DEBUG);
dw_printf ("Calculated distance = %.3f km\n", km);
if (km <= ddist) { if (km <= ddist) {
return (1); return (1);
@ -862,7 +1084,10 @@ static int filt_s (pfstate_t *pf)
sep[1] = '\0'; sep[1] = '\0';
cp = str + 2; cp = str + 2;
// TODO: check here.
pri = strsep (&cp, sep); pri = strsep (&cp, sep);
if (pri == NULL) { if (pri == NULL) {
print_error (pf, "Missing arguments for Symbol filter."); print_error (pf, "Missing arguments for Symbol filter.");
return (-1); return (-1);
@ -906,6 +1131,212 @@ static int filt_s (pfstate_t *pf)
} }
/*------------------------------------------------------------------------------
*
* Name: filt_i
*
* Purpose: IGate messaging default behavior.
*
* Inputs: pf - Pointer to current state information.
* token_str should contain something of format:
*
* i/time/hops/lat/lon/km
*
* Returns: 1 = yes
* 0 = no
* -1 = error detected
*
* Description: Selection is based on time since last heard on RF, and distance
* in terms of digipeater hops and/or phyiscal location.
*
* i/time
* i/time/hops
* i/time/hops/lat/lon/km
*
*
* "time" is maximum number of minutes since message addressee was last heard.
* This is required.
*
* "hops" is maximum number of digpeater hops. (i.e. 0 for heard directly).
* If hops is not specified, the maximum transmit digipeater hop count,
* from the IGTXVIA configuration will be used.
* The rest is distanced, in kilometers, from given point.
*
* Examples:
* i/60/0 Heard in past 60 minutes directly.
* i/45 Past 45 minutes, default max digi hops.
* i/30/3 Default time, max 3 digi hops.
* i/30/8/42.6/-71.3/50.
*
*
* It only makes sense to use this for the IS>RF direction.
* The basic idea is that we want to transmit a "message" only if the
* addressee has been heard recently and is not too far away.
*
* After passing along a "message" we will also allow the next
* position report from the sender of the "message."
* That is done somewhere else. We are not concerned with it here.
*
*------------------------------------------------------------------------------*/
static int filt_i (pfstate_t *pf)
{
char str[MAX_TOKEN_LEN];
char *cp;
char sep[2];
char *v;
int heardtime = 30;
#if PFTEST
int maxhops = 2;
#else
int maxhops = save_igate_config_p->max_digi_hops; // from IGTXVIA config.
#endif
double dlat = G_UNKNOWN;
double dlon = G_UNKNOWN;
double km = G_UNKNOWN;
char src[AX25_MAX_ADDR_LEN];
char *infop = NULL;
int info_len;
//char *f;
//char addressee[AX25_MAX_ADDR_LEN];
strlcpy (str, pf->token_str, sizeof(str));
sep[0] = str[1];
sep[1] = '\0';
cp = str + 2;
// Get parameters or defaults.
v = strsep (&cp, sep);
if (v != NULL && strlen(v) > 0) {
heardtime = atoi(v);
}
else {
print_error (pf, "Missing time limit for IGate message filter.");
return (-1);
}
v = strsep (&cp, sep);
if (v != NULL) {
if (strlen(v) > 0) {
maxhops = atoi(v);
}
else {
print_error (pf, "Missing max digipeater hops for IGate message filter.");
return (-1);
}
v = strsep (&cp, sep);
if (v != NULL && strlen(v) > 0) {
dlat = atof(v);
v = strsep (&cp, sep);
if (v != NULL && strlen(v) > 0) {
dlon = atof(v);
}
else {
print_error (pf, "Missing longitude for IGate message filter.");
return (-1);
}
v = strsep (&cp, sep);
if (v != NULL && strlen(v) > 0) {
km = atof(v);
}
else {
print_error (pf, "Missing distance, in km, for IGate message filter.");
return (-1);
}
}
v = strsep (&cp, sep);
if (v != NULL) {
print_error (pf, "Something unexpected after distance for IGate message filter.");
return (-1);
}
}
#if PFTEST
text_color_set(DW_COLOR_DEBUG);
dw_printf ("debug: IGate message filter, %d minutes, %d hops, %.2f %.2f %.2f km\n",
heardtime, maxhops, dlat, dlon, km);
#endif
/*
* Get source address and info part.
* Addressee has already been extracted into pf->decoded.g_addressee.
*/
memset (src, 0, sizeof(src));
ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src);
info_len = ax25_get_info (pf->pp, (unsigned char **)(&infop));
if (infop == NULL) return (0);
if (info_len < 1) return (0);
// Determine packet type. We are interested only in "message."
// Telemetry metadata is not considered a message.
if (*infop != ':') return (0);
if (is_telem_metadata(infop)) return (0);
#if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax.
(void)dlat; // Suppress set and not used warning.
(void)dlon;
(void)km;
(void)maxhops;
(void)heardtime;
return (1);
#else
/*
* Condition 1:
* "the receiving station has been heard within range within a predefined time
* period (range defined as digi hops, distance, or both)."
*/
int was_heard = mheard_was_recently_nearby ("addressee", pf->decoded.g_addressee, heardtime, maxhops, dlat, dlon, km);
if ( ! was_heard) return (0);
/*
* Condition 2:
* "the sending station has not been heard via RF within a predefined time period
* (packets gated from the Internet by other stations are excluded from this test)."
*
* This is the part I'm not so sure about.
* I guess the intention is that if the sender can be heard over RF, then the addressee
* might hear the sender without the help of Igate stations.
* Suppose the sender was 1 digipeater hop to the west and the addressee was 1 digipeater hop to the east.
* I can communicate with each of them with 1 digipeater hop but for them to reach each other, they
* might need 3 hops and using that many is generally frowned upon and rare.
*
* Maybe we could compromise here and say the sender must have been heard directly.
* It sent the message currently being processed so we must have heard it very recently, i.e. in
* the past minute, rather than the usual 30 or 60 minutes for the addressee.
*/
was_heard = mheard_was_recently_nearby ("source", src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN);
if (was_heard) return (0);
return (1);
#endif
} /* end filt_i */
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: print_error * Name: print_error
@ -1101,6 +1532,21 @@ int main ()
pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1); pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1);
pftest (220, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (222, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
pftest (223, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
pftest (224, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
pftest (225, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
pftest (226, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (227, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
// FIXME: behaves differently on Windows and Linux
//pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (229, "i/30", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (230, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
// TODO: to be continued...
if (error_count > 0) { if (error_count > 0) {
text_color_set (DW_COLOR_ERROR); text_color_set (DW_COLOR_ERROR);
@ -1124,7 +1570,7 @@ static void pftest (int test_num, char *filter, char *monitor, int expected)
pp = ax25_from_text (monitor, 1); pp = ax25_from_text (monitor, 1);
assert (pp != NULL); assert (pp != NULL);
result = pfilter (0, 0, filter, pp); result = pfilter (0, 0, filter, pp, 1);
if (result != expected) { if (result != expected) {
text_color_set (DW_COLOR_ERROR); text_color_set (DW_COLOR_ERROR);
dw_printf ("Unexpected result for test number %d\n", test_num); dw_printf ("Unexpected result for test number %d\n", test_num);

View File

@ -1,4 +1,13 @@
/* pfilter.h */ /* pfilter.h */
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp);
#include "igate.h" // for igate_config_s
void pfilter_init (struct igate_config_s *p_igate_config, int debug_level);
int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs);
int is_telem_metadata (char *infop);

514
ptt.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -46,6 +46,12 @@
* *
* Version 1.3: HAMLIB support. * Version 1.3: HAMLIB support.
* *
* Version 1.4: The spare "future" indicator is now used when connected to another station.
*
* Take advantage of the new 'gpio' group and new /sys/class/gpio protections in Raspbian Jessie.
*
* Handle more complicated gpio node names for CubieBoard, etc.
*
* References: http://www.robbayer.com/files/serial-win.pdf * References: http://www.robbayer.com/files/serial-win.pdf
* *
* https://www.kernel.org/doc/Documentation/gpio.txt * https://www.kernel.org/doc/Documentation/gpio.txt
@ -94,6 +100,9 @@
Maybe even for Windows. ;-) Maybe even for Windows. ;-)
*/ */
#include "direwolf.h" // should be first. This includes windows.h.
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -102,7 +111,6 @@
#include <time.h> #include <time.h>
#if __WIN32__ #if __WIN32__
#include <windows.h>
#else #else
#include <sys/termios.h> #include <sys/termios.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@ -111,6 +119,8 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <grp.h>
#include <dirent.h>
#ifdef USE_HAMLIB #ifdef USE_HAMLIB
#include <hamlib/rig.h> #include <hamlib/rig.h>
@ -122,10 +132,10 @@ typedef int HANDLE;
#endif #endif
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "audio.h" #include "audio.h"
#include "ptt.h" #include "ptt.h"
#include "dlq.h"
#if __WIN32__ #if __WIN32__
@ -152,6 +162,8 @@ typedef int HANDLE;
#endif #endif
static struct audio_s *save_audio_config_p; /* Save config information for later use. */
static int ptt_debug_level = 0; static int ptt_debug_level = 0;
void ptt_set_debug(int debug) void ptt_set_debug(int debug)
@ -159,6 +171,198 @@ void ptt_set_debug(int debug)
ptt_debug_level = debug; ptt_debug_level = debug;
} }
/*-------------------------------------------------------------------
*
* Name: get_access_to_gpio
*
* Purpose: Try to get access to the GPIO device.
*
* Inputs: path - Path to device node.
* /sys/class/gpio/export
* /sys/class/gpio/unexport
* /sys/class/gpio/gpio??/direction
* /sys/class/gpio/gpio??/value
*
* Description: First see if we have access thru the usual uid/gid/mode method.
* If that fails, we try a hack where we use "sudo chmod ..." to open up access.
* That requires that sudo be configured to work without a password.
* That's the case for 'pi' user in Raspbian but not not be for other boards / operating systems.
*
* Debug: Use the "-doo" command line option.
*
*------------------------------------------------------------------*/
#ifndef __WIN32__
#define MAX_GROUPS 50
static void get_access_to_gpio (const char *path)
{
static int my_uid = -1;
static int my_gid = -1;
static gid_t my_groups[MAX_GROUPS];
static int num_groups = 0;
static int first_time = 1;
struct stat finfo;
int i;
char cmd[80];
int err;
/*
* Does path even exist?
*/
if (stat(path, &finfo) < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't get properties of %s.\n", path);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
if (first_time) {
// No need to fetch same information each time. Cache it.
my_uid = geteuid();
my_gid = getegid();
num_groups = getgroups (MAX_GROUPS, my_groups);
if (num_groups < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("getgroups() failed to get supplementary groups, errno=%d\n", errno);
num_groups = 0;
}
first_time = 0;
}
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("%s: uid=%d, gid=%d, mode=o%o\n", path, finfo.st_uid, finfo.st_gid, finfo.st_mode);
dw_printf ("my uid=%d, gid=%d, supplementary groups=", my_uid, my_gid);
for (i = 0; i < num_groups; i++) {
dw_printf (" %d", my_groups[i]);
}
dw_printf ("\n");
}
/*
* Do we have permission to access it?
*
* On Debian 7 (Wheezy) we see this:
*
* $ ls -l /sys/class/gpio/export
* --w------- 1 root root 4096 Feb 27 12:31 /sys/class/gpio/export
*
*
* Only root can write to it.
* Our work-around is change the protection so that everyone can write.
* This requires that the current user can use sudo without a password.
* This has been the case for the predefined "pi" user but can be a problem
* when people add new user names.
* Other operating systems could have different default configurations.
*
* A better solution is available in Debian 8 (Jessie). The group is now "gpio"
* so anyone in that group can now write to it.
*
* $ ls -l /sys/class/gpio/export
* -rwxrwx--- 1 root gpio 4096 Mar 4 21:12 /sys/class/gpio/export
*
*
* First see if we can access it by the usual file protection rules.
* If not, we will try the "sudo chmod go+rw ..." hack.
*
*/
/*
* Do I have access?
* We could just try to open for write but this gives us more debugging information.
*/
if ((my_uid == finfo.st_uid) && (finfo.st_mode & S_IWUSR)) { // user write 00200
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("My uid matches and we have user write permission.\n");
}
return;
}
if ((my_gid == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("My primary gid matches and we have group write permission.\n");
}
return;
}
for (i = 0; i < num_groups; i++) {
if ((my_groups[i] == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("My supplemental group %d matches and we have group write permission.\n", my_groups[i]);
}
return;
}
}
if (finfo.st_mode & S_IWOTH) { // other write 00002
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("We have other write permission.\n");
}
return;
}
/*
* We don't have permission.
* Try a hack which requires that the user be set up to use sudo without a password.
*/
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_ERROR); // debug message but different color so it stands out.
dw_printf ("Trying 'sudo chmod go+rw %s' hack.\n", path);
}
snprintf (cmd, sizeof(cmd), "sudo chmod go+rw %s", path);
err = system (cmd);
(void)err; // suppress warning about not using result.
/*
* I don't trust status coming back from system() so we will check the mode again.
*/
if (stat(path, &finfo) < 0) {
/* Unexpected because we could do it before. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/* Did we succeed in changing the protection? */
if ( (finfo.st_mode & 0266) != 0266) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("You don't have the necessary permission to access GPIO.\n");
dw_printf ("There are three different solutions: \n");
dw_printf (" 1. Run as root. (not recommended)\n");
dw_printf (" 2. If operating system has 'gpio' group, add your user id to it.\n");
dw_printf (" 3. Configure your user id for sudo without a password.\n");
dw_printf ("\n");
dw_printf ("Read the documentation and try -doo command line option for debugging details.\n");
exit (1);
}
}
#endif
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: export_gpio * Name: export_gpio
@ -166,39 +370,63 @@ void ptt_set_debug(int debug)
* Purpose: Tell the GPIO subsystem to export a GPIO line for * Purpose: Tell the GPIO subsystem to export a GPIO line for
* us to use, and set the initial state of the GPIO. * us to use, and set the initial state of the GPIO.
* *
* Inputs: gpio - GPIO line to export * Inputs: ch - Radio Channel.
* ot - Output type.
* invert: - Is the GPIO active low? * invert: - Is the GPIO active low?
* direction: - 0 for input, 1 for output * direction: - 0 for input, 1 for output
* *
* Outputs: None. * Outputs: out_gpio_name - in the audio configuration structure.
* in_gpio_name
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#ifndef __WIN32__ #ifndef __WIN32__
void export_gpio(int gpio, int invert, int direction)
void export_gpio(int ch, int ot, int invert, int direction)
{ {
HANDLE fd; HANDLE fd;
char stemp[80]; const char gpio_export_path[] = "/sys/class/gpio/export";
struct stat finfo; char gpio_direction_path[80];
int err; char gpio_value_path[80];
char stemp[16];
int gpio_num;
char *gpio_name;
fd = open("/sys/class/gpio/export", O_WRONLY); // Raspberry Pi was easy. GPIO 24 has the name gpio24.
// Others, such as the Cubieboard, take a little more effort.
// The name might be gpio24_ph11 meaning connector H, pin 11.
// When we "export" GPIO number, we will store the corresponding
// device name for future use when we want to access it.
if (direction) {
gpio_num = save_audio_config_p->achan[ch].octrl[ot].out_gpio_num;
gpio_name = save_audio_config_p->achan[ch].octrl[ot].out_gpio_name;
}
else {
gpio_num = save_audio_config_p->achan[ch].ictrl[ot].in_gpio_num;
gpio_name = save_audio_config_p->achan[ch].ictrl[ot].in_gpio_name;
}
get_access_to_gpio (gpio_export_path);
fd = open(gpio_export_path, O_WRONLY);
if (fd < 0) { if (fd < 0) {
// Not expected. Above should have obtained permission or exited.
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); dw_printf ("Permissions do not allow access to GPIO.\n");
dw_printf ("Log in as root and type this command:\n");
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
exit (1); exit (1);
} }
snprintf (stemp, sizeof(stemp), "%d", gpio);
snprintf (stemp, sizeof(stemp), "%d", gpio_num);
if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) { if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
int e = errno; int e = errno;
/* Ignore EBUSY error which seems to mean */ /* Ignore EBUSY error which seems to mean */
/* the device node already exists. */ /* the device node already exists. */
if (e != EBUSY) { if (e != EBUSY) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e); dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e);
dw_printf ("%s\n", strerror(e)); dw_printf ("%s\n", strerror(e));
exit (1); exit (1);
} }
@ -206,62 +434,118 @@ void export_gpio(int gpio, int invert, int direction)
close (fd); close (fd);
/* /*
Idea for future: * Added in release 1.4.
*
On the RPi, the device path for GPIO number XX is /sys/class/gpio/gpioXX. * On the RPi, the device path for GPIO number XX is simply /sys/class/gpio/gpioXX.
There was a report that it is different for the Cubieboard. For instance *
GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13. * There was a report that it is different for the CubieBoard. For instance
* GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13.
For another similar single board computer, we find the same thing: * https://github.com/cubieplayer/Cubian/wiki/GPIO-Introduction
https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux *
* For another similar single board computer, we find the same thing:
How should we deal with this? Some possibilities: * https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux
*
(1) The user might explicitly mention the name in direwolf.conf. * How should we deal with this? Some possibilities:
(2) We might be able to find the names in some system device config file. *
(3) Get a directory listing of /sys/class/gpio then search for a * (1) The user might explicitly mention the name in direwolf.conf.
matching name. Suppose we wanted GPIO 61. First look for an exact * (2) We might be able to find the names in some system device config file.
match to "gpio61". If that is not found, look for something * (3) Get a directory listing of /sys/class/gpio then search for a
matching the pattern "gpio61_*". * matching name. Suppose we wanted GPIO 61. First look for an exact
* match to "gpio61". If that is not found, look for something
* matching the pattern "gpio61_*".
*
* We are finally implementing the third choice.
*/ */
struct dirent **file_list;
int num_files;
int i;
int ok = 0;
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Contents of /sys/class/gpio:\n");
}
num_files = scandir ("/sys/class/gpio", &file_list, NULL, alphasort);
if (num_files < 0) {
// Something went wrong. Fill in the simple expected name and keep going.
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! Could not get directory listing for /sys/class/gpio\n");
snprintf (gpio_name, MAX_GPIO_NAME_LEN, "gpio%d", gpio_num);
num_files = 0;
ok = 1;
}
else {
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
for (i = 0; i < num_files; i++) {
dw_printf("\t%s\n", file_list[i]->d_name);
}
}
// Look for exact name gpioNN
char lookfor[16];
snprintf (lookfor, sizeof(lookfor), "gpio%d", gpio_num);
for (i = 0; i < num_files && ! ok; i++) {
if (strcmp(lookfor, file_list[i]->d_name) == 0) {
strlcpy (gpio_name, file_list[i]->d_name, MAX_GPIO_NAME_LEN);
ok = 1;
}
}
// If not found, Look for gpioNN_*
snprintf (lookfor, sizeof(lookfor), "gpio%d_", gpio_num);
for (i = 0; i < num_files && ! ok; i++) {
if (strncmp(lookfor, file_list[i]->d_name, strlen(lookfor)) == 0) {
strlcpy (gpio_name, file_list[i]->d_name, MAX_GPIO_NAME_LEN);
ok = 1;
}
}
// Free the storage allocated by scandir().
for (i = 0; i < num_files; i++) {
free (file_list[i]);
}
free (file_list);
}
/* /*
* We will have the same permission problem if not root. * We should now have the corresponding node name.
* We only care about "direction" and "value".
*/ */
snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", gpio); if (ok) {
err = system (stemp);
snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/value", gpio);
err = system (stemp);
(void)err;
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", gpio); if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
if (stat(stemp, &finfo) < 0) { dw_printf ("Path for gpio number %d is /sys/class/gpio/%s\n", gpio_num, gpio_name);
int e = errno;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to get status for %s \n", stemp);
dw_printf ("%s\n", strerror(e));
exit (1);
} }
if (geteuid() != 0) {
if ( ! (finfo.st_mode & S_IWOTH)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
dw_printf ("Log in as root and type these commands:\n");
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", gpio);
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", gpio);
exit (1);
} }
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! Could not find Path for gpio number %d.n", gpio_num);
exit (1);
} }
/* /*
* Set output direction and initial state * Set output direction and initial state
*/ */
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/direction", gpio); snprintf (gpio_direction_path, sizeof(gpio_direction_path), "/sys/class/gpio/%s/direction", gpio_name);
fd = open(stemp, O_WRONLY); get_access_to_gpio (gpio_direction_path);
fd = open(gpio_direction_path, O_WRONLY);
if (fd < 0) { if (fd < 0) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -290,6 +574,14 @@ void export_gpio(int gpio, int invert, int direction)
exit (1); exit (1);
} }
close (fd); close (fd);
/*
* Make sure that we have access to 'value'.
* Do it once here, rather than each time we want to use it.
*/
snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", gpio_name);
get_access_to_gpio (gpio_value_path);
} }
#endif /* not __WIN32__ */ #endif /* not __WIN32__ */
@ -318,7 +610,7 @@ void export_gpio(int gpio, int invert, int direction)
* *
* ptt_line RTS or DTR when using serial port. * ptt_line RTS or DTR when using serial port.
* *
* ptt_gpio GPIO number. Only used for Linux. * out_gpio_num GPIO number. Only used for Linux.
* Valid only when ptt_method is PTT_METHOD_GPIO. * Valid only when ptt_method is PTT_METHOD_GPIO.
* *
* ptt_lpt_bit Bit number for parallel printer port. * ptt_lpt_bit Bit number for parallel printer port.
@ -341,7 +633,6 @@ void export_gpio(int gpio, int invert, int direction)
static struct audio_s *save_audio_config_p; /* Save config information for later use. */
static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES]; static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES];
/* Serial port handle or fd. */ /* Serial port handle or fd. */
@ -371,7 +662,7 @@ void ptt_init (struct audio_s *audio_config_p)
strlcpy (otnames[OCTYPE_PTT], "PTT", sizeof(otnames[OCTYPE_PTT])); strlcpy (otnames[OCTYPE_PTT], "PTT", sizeof(otnames[OCTYPE_PTT]));
strlcpy (otnames[OCTYPE_DCD], "DCD", sizeof(otnames[OCTYPE_DCD])); strlcpy (otnames[OCTYPE_DCD], "DCD", sizeof(otnames[OCTYPE_DCD]));
strlcpy (otnames[OCTYPE_FUTURE], "FUTURE", sizeof(otnames[OCTYPE_FUTURE])); strlcpy (otnames[OCTYPE_CON], "CON", sizeof(otnames[OCTYPE_CON]));
for (ch = 0; ch < MAX_CHANS; ch++) { for (ch = 0; ch < MAX_CHANS; ch++) {
@ -392,7 +683,7 @@ void ptt_init (struct audio_s *audio_config_p)
audio_config_p->achan[ch].octrl[ot].ptt_method, audio_config_p->achan[ch].octrl[ot].ptt_method,
audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].ptt_device,
audio_config_p->achan[ch].octrl[ot].ptt_line, audio_config_p->achan[ch].octrl[ot].ptt_line,
audio_config_p->achan[ch].octrl[ot].ptt_gpio, audio_config_p->achan[ch].octrl[ot].out_gpio_num,
audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit, audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit,
audio_config_p->achan[ch].octrl[ot].ptt_invert); audio_config_p->achan[ch].octrl[ot].ptt_invert);
} }
@ -535,58 +826,9 @@ void ptt_init (struct audio_s *audio_config_p)
} }
if (using_gpio) { if (using_gpio) {
get_access_to_gpio ("/sys/class/gpio/export");
struct stat finfo;
/*
* Normally the device nodes are set up for access
* only by root. Try to change it here so we don't
* burden user with another configuration step.
*
* Does /sys/class/gpio/export even exist?
*/
if (stat("/sys/class/gpio/export", &finfo) < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
} }
/*
* Do we have permission to access it?
*
* pi@raspberrypi /sys/class/gpio $ ls -l
* total 0
* --w------- 1 root root 4096 Aug 20 07:59 export
* lrwxrwxrwx 1 root root 0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
* --w------- 1 root root 4096 Aug 20 07:59 unexport
*/
if (geteuid() != 0) {
if ( ! (finfo.st_mode & S_IWOTH)) {
int err;
/* Try to change protection. */
err = system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport");
if (stat("/sys/class/gpio/export", &finfo) < 0) {
/* Unexpected because we could do it before. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/* Did we succeed in changing the protection? */
if ( ! (finfo.st_mode & S_IWOTH)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
dw_printf ("Log in as root and type this command:\n");
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
exit (1);
}
}
}
}
/* /*
* We should now be able to create the device nodes for * We should now be able to create the device nodes for
* the pins we want to use. * the pins we want to use.
@ -594,15 +836,18 @@ void ptt_init (struct audio_s *audio_config_p)
for (ch = 0; ch < MAX_CHANS; ch++) { for (ch = 0; ch < MAX_CHANS; ch++) {
if (save_audio_config_p->achan[ch].valid) { if (save_audio_config_p->achan[ch].valid) {
int ot;
int ot; // output control type, PTT, DCD, CON, ...
int it; // input control type
for (ot = 0; ot < NUM_OCTYPES; ot++) { for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
export_gpio(audio_config_p->achan[ch].octrl[ot].ptt_gpio, audio_config_p->achan[ch].octrl[ot].ptt_invert, 1); export_gpio(ch, ot, audio_config_p->achan[ch].octrl[ot].ptt_invert, 1);
} }
} }
for (ot = 0; ot < NUM_ICTYPES; ot++) { for (it = 0; it < NUM_ICTYPES; it++) {
if (audio_config_p->achan[ch].ictrl[ot].method == PTT_METHOD_GPIO) { if (audio_config_p->achan[ch].ictrl[it].method == PTT_METHOD_GPIO) {
export_gpio(audio_config_p->achan[ch].ictrl[ot].gpio, audio_config_p->achan[ch].ictrl[ot].invert, 0); export_gpio(ch, it, audio_config_p->achan[ch].ictrl[it].invert, 0);
} }
} }
} }
@ -772,7 +1017,7 @@ void ptt_init (struct audio_s *audio_config_p)
* probably be renamed something like octrl_set. * probably be renamed something like octrl_set.
* *
* Inputs: ot - Output control type: * Inputs: ot - Output control type:
* OCTYPE_PTT, OCTYPE_DCD, OCTYPE_HAMLIB, OCTYPE_FUTURE * OCTYPE_PTT, OCTYPE_DCD, OCTYPE_FUTURE
* *
* chan - channel, 0 .. (number of channels)-1 * chan - channel, 0 .. (number of channels)-1
* *
@ -809,6 +1054,13 @@ void ptt_set (int ot, int chan, int ptt_signal)
return; return;
} }
/*
* The data link state machine has an interest in activity on the radio channel.
* This is a very convenient place to get that information.
*/
dlq_channel_busy (chan, ot, ptt_signal);
/* /*
* Inverted output? * Inverted output?
*/ */
@ -880,11 +1132,12 @@ void ptt_set (int ot, int chan, int ptt_signal)
if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) { if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
int fd; int fd;
char stemp[80]; char gpio_value_path[80];
char stemp[16];
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio); snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", save_audio_config_p->achan[chan].octrl[ot].out_gpio_name);
fd = open(stemp, O_WRONLY); fd = open(gpio_value_path, O_WRONLY);
if (fd < 0) { if (fd < 0) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -898,7 +1151,7 @@ void ptt_set (int ot, int chan, int ptt_signal)
if (write (fd, stemp, 1) != 1) { if (write (fd, stemp, 1) != 1) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio, otnames[ot]); dw_printf ("Error setting GPIO %d for %s\n", save_audio_config_p->achan[chan].octrl[ot].out_gpio_num, otnames[ot]);
dw_printf ("%s\n", strerror(e)); dw_printf ("%s\n", strerror(e));
} }
close (fd); close (fd);
@ -999,15 +1252,17 @@ int get_input (int it, int chan)
#else #else
if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) { if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) {
int fd; int fd;
char stemp[80]; char gpio_value_path[80];
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].ictrl[it].gpio); snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", save_audio_config_p->achan[chan].ictrl[it].in_gpio_name);
fd = open(stemp, O_RDONLY); get_access_to_gpio (gpio_value_path);
fd = open(gpio_value_path, O_RDONLY);
if (fd < 0) { if (fd < 0) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error opening %s to check input.\n", stemp); dw_printf ("Error opening %s to check input.\n", gpio_value_path);
dw_printf ("%s\n", strerror(e)); dw_printf ("%s\n", strerror(e));
return -1; return -1;
} }
@ -1016,11 +1271,12 @@ int get_input (int it, int chan)
if (read (fd, vtemp, 1) != 1) { if (read (fd, vtemp, 1) != 1) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error getting GPIO %d value\n", save_audio_config_p->achan[chan].ictrl[it].gpio); dw_printf ("Error getting GPIO %d value\n", save_audio_config_p->achan[chan].ictrl[it].in_gpio_num);
dw_printf ("%s\n", strerror(e)); dw_printf ("%s\n", strerror(e));
} }
close (fd); close (fd);
vtemp[1] = '\0';
if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) { if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) {
return 1; return 1;
} }
@ -1201,9 +1457,9 @@ main ()
my_audio_config.adev[0].num_channels = 1; my_audio_config.adev[0].num_channels = 1;
my_audio_config.valid[0] = 1; my_audio_config.valid[0] = 1;
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO; my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO;
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_gpio = 25; my_audio_config.adev[0].octrl[OCTYPE_PTT].out_gpio_num = 25;
dw_printf ("Try GPIO %d a few times...\n", my_audio_config.ptt_gpio[0]); dw_printf ("Try GPIO %d a few times...\n", my_audio_config.out_gpio_num[0]);
ptt_init (&my_audio_config); ptt_init (&my_audio_config);

6
rdq.c
View File

@ -29,13 +29,14 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "audio.h" #include "audio.h"
@ -336,9 +337,6 @@ rrbb_t rdq_remove (void)
{ {
rrbb_t result_p; rrbb_t result_p;
#ifndef __WIN32__
int err;
#endif
#if DEBUG #if DEBUG

124
recv.c
View File

@ -2,7 +2,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -68,7 +68,7 @@
* *
* The difference is that app_process_rec_frame * The difference is that app_process_rec_frame
* is no longer called directly. Instead * is no longer called directly. Instead
* the frame is appended to a queue with dlq_append. * the frame is appended to a queue with dlq_rec_frame.
* *
* Received frames can now be processed one at * Received frames can now be processed one at
* a time and we don't need to worry about later * a time and we don't need to worry about later
@ -80,6 +80,9 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
//#define DEBUG 1
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@ -95,7 +98,6 @@
#include <errno.h> #include <errno.h>
#endif #endif
#include "direwolf.h"
#include "audio.h" #include "audio.h"
#include "demod.h" #include "demod.h"
#include "multi_modem.h" #include "multi_modem.h"
@ -104,6 +106,8 @@
#include "recv.h" #include "recv.h"
#include "dtmf.h" #include "dtmf.h"
#include "aprs_tt.h" #include "aprs_tt.h"
#include "dtime_now.h"
#include "ax25_link.h"
#if __WIN32__ #if __WIN32__
@ -261,7 +265,7 @@ static void * recv_adev_thread (void *arg)
} }
/* When a complete frame is accumulated, */ /* When a complete frame is accumulated, */
/* dlq_append, is called. */ /* dlq_rec_frame, is called. */
/* recv_process, below, drains the queue. */ /* recv_process, below, drains the queue. */
@ -283,33 +287,105 @@ static void * recv_adev_thread (void *arg)
void recv_process (void) void recv_process (void)
{ {
int ok; struct dlq_item_s *pitem;
dlq_type_t type;
int chan;
int subchan;
int slice;
packet_t pp;
alevel_t alevel;
retry_t retries;
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];
while (1) { while (1) {
dlq_wait_while_empty (); int timed_out;
#if DEBUG
text_color_set(DW_COLOR_DEBUG); double timeout_value = ax25_link_get_next_timer_expiry();
dw_printf ("recv_process: woke up\n");
#endif timed_out = dlq_wait_while_empty (timeout_value);
ok = dlq_remove (&type, &chan, &subchan, &slice, &pp, &alevel, &retries, spectrum, sizeof(spectrum));
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("recv_process: dlq_remove() returned ok=%d, type=%d, chan=%d, pp=%p\n", dw_printf ("recv_process: woke up, timed_out=%d\n", timed_out);
ok, (int)type, chan, pp);
#endif #endif
if (ok) {
app_process_rec_packet (chan, subchan, slice, pp, alevel, retries, spectrum); if (timed_out) {
#if DEBUG
text_color_set(DW_COLOR_ERROR);
dw_printf ("recv_process: time waiting on dlq. call dl_timer_expiry.\n");
#endif
dl_timer_expiry ();
}
else {
pitem = dlq_remove ();
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("recv_process: dlq_remove() returned pitem=%p\n", pitem);
#endif
if (pitem != NULL) {
switch (pitem->type) {
case DLQ_REC_FRAME:
/*
* This is the traditional processing.
* For all frames:
* - Print in standard monitoring format.
* - Send to KISS client applications.
* - Send to AGw client applications in raw mode.
* For APRS frames:
* - Explain what it means.
* - Send to Igate.
* - Digipeater.
*/
app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum);
/*
* Link processing.
*/
lm_data_indication(pitem);
break;
case DLQ_CONNECT_REQUEST:
dl_connect_request (pitem);
break;
case DLQ_DISCONNECT_REQUEST:
dl_disconnect_request (pitem);
break;
case DLQ_XMIT_DATA_REQUEST:
dl_data_request (pitem);
break;
case DLQ_REGISTER_CALLSIGN:
dl_register_callsign (pitem);
break;
case DLQ_UNREGISTER_CALLSIGN:
dl_unregister_callsign (pitem);
break;
case DLQ_CHANNEL_BUSY:
lm_channel_busy (pitem);
break;
case DLQ_CLIENT_CLEANUP:
dl_client_cleanup (pitem);
break;
}
dlq_delete (pitem);
} }
#if DEBUG #if DEBUG
else { else {
@ -319,6 +395,8 @@ void recv_process (void)
#endif #endif
} }
}
} /* end recv_process */ } /* end recv_process */

View File

@ -1,255 +0,0 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: redecode.c
*
* Purpose: Retry decoding frames that have a bad FCS.
*
* Description:
*
*
* Usage: (1) The main application calls redecode_init.
*
* This will initialize the retry decoding queue
* and create a thread to work on contents of the queue.
*
* (2) The application queues up frames by calling rdq_append.
*
*
* (3) redecode_thread removes raw frames from the queue and
* tries to recover from errors.
*
*---------------------------------------------------------------*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#if __WIN32__
#include <windows.h>
#endif
#include "direwolf.h"
#include "ax25_pad.h"
#include "textcolor.h"
#include "audio.h"
#include "rdq.h"
#include "redecode.h"
#include "hdlc_send.h"
#include "hdlc_rec2.h"
#include "ptt.h"
/* Audio configuration for the fix_bits / passall optiions. */
static struct audio_s *save_audio_config_p;
#if __WIN32__
static unsigned redecode_thread (void *arg);
#else
static void * redecode_thread (void *arg);
#endif
/*-------------------------------------------------------------------
*
* Name: redecode_init
*
* Purpose: Initialize the process to try fixing bits in frames with bad FCS.
*
* Inputs: none.
*
* Outputs: none.
*
* Description: Initialize the queue to be empty and set up other
* mechanisms for sharing it between different threads.
*
* Start up redecode_thread to actually process the
* raw frames from the queue.
*
*--------------------------------------------------------------------*/
void redecode_init (struct audio_s *p_audio_config)
{
#if 0
#if __WIN32__
HANDLE redecode_th;
#else
pthread_t redecode_tid;
int e;
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_init ( ... )\n");
#endif
save_audio_config_p = p_audio_config;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_init: about to call rdq_init \n");
#endif
rdq_init ();
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_init: about to create thread \n");
#endif
#if __WIN32__
redecode_th = _beginthreadex (NULL, 0, redecode_thread, NULL, 0, NULL);
if (redecode_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create redecode thread\n");
return;
}
#else
//TODO: Give thread lower priority.
e = pthread_create (&redecode_tid, NULL, redecode_thread, (void *)0);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create redecode thread");
return;
}
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_init: finished \n");
#endif
#endif
} /* end redecode_init */
/*-------------------------------------------------------------------
*
* Name: redecode_thread
*
* Purpose: Try to decode frames with a bad FCS.
*
* Inputs: None.
*
* Outputs:
*
* Description: Initialize the queue to be empty and set up other
* mechanisms for sharing it between different threads.
*
*
*--------------------------------------------------------------------*/
#if 0
#if __WIN32__
static unsigned redecode_thread (void *arg)
#else
static void * redecode_thread (void *arg)
#endif
{
#if __WIN32__
HANDLE tid = GetCurrentThread();
//int tp;
//tp = GetThreadPriority (tid);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Starting redecode thread priority=%d\n", tp);
SetThreadPriority (tid, THREAD_PRIORITY_LOWEST);
//tp = GetThreadPriority (tid);
//dw_printf ("New redecode thread priority=%d\n", tp);
#endif
while (1) {
rrbb_t block;
rdq_wait_while_empty ();
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_thread: woke up\n");
#endif
block = rdq_remove ();
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_thread: rdq_remove() returned %p\n", block);
#endif
/* Don't expect null ever but be safe. */
if (block != NULL) {
int chan = rrbb_get_chan(block);
int subchan = rrbb_get_subchan(block);
int blen = rrbb_get_len(block);
alevel_t alevel = rrbb_get_audio_level(block);
//retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
//int passall = save_audio_config_p->achan[chan].passall;
int ok;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen);
#endif
ok = hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("redecode_thread: finished processing %p\n", block);
#endif
rrbb_delete (block);
}
}
return 0;
} /* end redecode_thread */
#endif
/* end redecode.c */

View File

@ -5,3 +5,7 @@ For the Windows version, we need to include our own version.
The source was obtained from: The source was obtained from:
http://gnuwin32.sourceforge.net/packages/regex.htm http://gnuwin32.sourceforge.net/packages/regex.htm
That is very old with loads of compile warnings. Should we upgrade from here?
https://www.gnu.org/software/libc/sources.html

View File

@ -3036,7 +3036,9 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
while (1) while (1)
{ {
bracket_elem_t start_elem, end_elem; // Got warnings about being used uninitialized.
// bracket_elem_t start_elem, end_elem;
bracket_elem_t start_elem = {.type=0, .opr.name=NULL}, end_elem = {.type=0, .opr.name=NULL};
unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE]; unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE];
unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE]; unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE];
reg_errcode_t ret; reg_errcode_t ret;

View File

@ -675,9 +675,9 @@ re_string_reconstruct (re_string_t *pstr, int idx, int eflags)
else else
{ {
/* No, skip all characters until IDX. */ /* No, skip all characters until IDX. */
#ifdef RE_ENABLE_I18N
int prev_valid_len = pstr->valid_len; int prev_valid_len = pstr->valid_len;
#ifdef RE_ENABLE_I18N
if (BE (pstr->offsets_needed, 0)) if (BE (pstr->offsets_needed, 0))
{ {
pstr->len = pstr->raw_len - idx + offset; pstr->len = pstr->raw_len - idx + offset;
@ -1396,7 +1396,9 @@ static int
internal_function internal_function
re_dfa_add_node (re_dfa_t *dfa, re_token_t token) re_dfa_add_node (re_dfa_t *dfa, re_token_t token)
{ {
#ifdef RE_ENABLE_I18N
int type = token.type; int type = token.type;
#endif
if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0))
{ {
size_t new_nodes_alloc = dfa->nodes_alloc * 2; size_t new_nodes_alloc = dfa->nodes_alloc * 2;

View File

@ -1,3 +1,9 @@
/* this is very old and has massive numbers of compiler warnings. */
/* Maybe try upgrading to a newer version such as */
/* https://fossies.org/dox/glibc-2.24/regexec_8c_source.html */
/* Extended regular expression matching and search library. /* Extended regular expression matching and search library.
Copyright (C) 2002, 2003, 2004, 2005, 2007 Free Software Foundation, Inc. Copyright (C) 2002, 2003, 2004, 2005, 2007 Free Software Foundation, Inc.
This file is part of the GNU C Library. This file is part of the GNU C Library.
@ -18,6 +24,14 @@
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */ 02111-1307 USA. */
/* Added 12/2016 to remove warning: */
/* incompatible implicit declaration of built-in function 'alloca' */
#if __WIN32__
#include <malloc.h>
#else
#include <alloca.h>
#endif
static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags,
int n) internal_function; int n) internal_function;
static void match_ctx_clean (re_match_context_t *mctx) internal_function; static void match_ctx_clean (re_match_context_t *mctx) internal_function;
@ -228,6 +242,7 @@ regexec (preg, string, nmatch, pmatch, eflags)
reg_errcode_t err; reg_errcode_t err;
int start, length; int start, length;
re_dfa_t *dfa = (re_dfa_t *) preg->buffer; re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
(void)dfa;
if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND)) if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND))
return REG_BADPAT; return REG_BADPAT;
@ -419,6 +434,7 @@ re_search_stub (bufp, string, length, start, range, stop, regs, ret_len)
int nregs, rval; int nregs, rval;
int eflags = 0; int eflags = 0;
re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
(void)dfa;
/* Check for out-of-range. */ /* Check for out-of-range. */
if (BE (start < 0 || start > length, 0)) if (BE (start < 0 || start > length, 0))
@ -2894,7 +2910,10 @@ check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node,
sizeof (re_dfastate_t *) * (path->alloc - old_alloc)); sizeof (re_dfastate_t *) * (path->alloc - old_alloc));
} }
str_idx = path->next_idx ?: top_str; // Original:
// str_idx = path->next_idx ?: top_str;
// Copied following from another version when cleaning up compiler warnings.
str_idx = path->next_idx ? path->next_idx : top_str;
/* Temporary modify MCTX. */ /* Temporary modify MCTX. */
backup_state_log = mctx->state_log; backup_state_log = mctx->state_log;
@ -3032,7 +3051,9 @@ check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx,
const re_dfa_t *const dfa = mctx->dfa; const re_dfa_t *const dfa = mctx->dfa;
int result; int result;
int cur_idx; int cur_idx;
#ifdef RE_ENABLE_I18N
reg_errcode_t err = REG_NOERROR; reg_errcode_t err = REG_NOERROR;
#endif
re_node_set union_set; re_node_set union_set;
re_node_set_init_empty (&union_set); re_node_set_init_empty (&union_set);
for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx) for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx)

3
rrbb.c
View File

@ -35,12 +35,13 @@
#define RRBB_C #define RRBB_C
#include "direwolf.h"
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "rrbb.h" #include "rrbb.h"

View File

@ -34,17 +34,16 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include "direwolf.h" // should be first
#include <stdio.h> #include <stdio.h>
#if __WIN32__ #if __WIN32__
#include <stdlib.h> #include <stdlib.h>
#include <windows.h>
#else #else
#define __USE_XOPEN2KXSI 1
#define __USE_XOPEN 1
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <fcntl.h> #include <fcntl.h>
@ -58,7 +57,7 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
#include "serial_port.h" #include "serial_port.h"
@ -183,6 +182,10 @@ MYFDTYPE serial_port_open (char *devicename, int baud)
//text_color_set(DW_COLOR_INFO); //text_color_set(DW_COLOR_INFO);
//dw_printf("Successful serial port open on %s.\n", devicename); //dw_printf("Successful serial port open on %s.\n", devicename);
// Some devices, e.g. KPC-3+, can't turn off hardware flow control and need RTS.
EscapeCommFunction(fd,SETRTS);
EscapeCommFunction(fd,SETDTR);
#else #else
/* Linux version. */ /* Linux version. */
@ -300,7 +303,7 @@ int serial_port_write (MYFDTYPE fd, char *str, int len)
dw_printf ("Error writing to serial port. Error %d.\n\n", err); dw_printf ("Error writing to serial port. Error %d.\n\n", err);
} }
} }
else if (nwritten != len) else if ((int)nwritten != len)
{ {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len); dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len);

View File

@ -1,11 +1,13 @@
/* serial_port.h */ /* serial_port.h */
#ifndef SERIAL_PORT_H
#define SERIAL_PORT_H 1
#if __WIN32__ #if __WIN32__
#include <stdlib.h> #include <stdlib.h>
#include <windows.h>
typedef HANDLE MYFDTYPE; typedef HANDLE MYFDTYPE;
#define MYFDERROR INVALID_HANDLE_VALUE #define MYFDERROR INVALID_HANDLE_VALUE
@ -25,3 +27,6 @@ extern int serial_port_write (MYFDTYPE fd, char *str, int len);
extern int serial_port_get1 (MYFDTYPE fd); extern int serial_port_get1 (MYFDTYPE fd);
extern void serial_port_close (MYFDTYPE fd); extern void serial_port_close (MYFDTYPE fd);
#endif

300
server.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -59,6 +59,17 @@
* *
* 'y' Ask Outstanding frames waiting on a Port (new in 1.2) * 'y' Ask Outstanding frames waiting on a Port (new in 1.2)
* *
* 'C' Connect, Start an AX.25 Connection (new in 1.4)
*
* 'v' Connect VIA, Start an AX.25 circuit thru digipeaters (new in 1.4)
*
* 'c' Connection with non-standard PID (new in 1.4)
*
* 'D' Send Connected Data (new in 1.4)
*
* 'd' Disconnect, Terminate an AX.25 Connection (new in 1.4)
*
*
* A message is printed if any others are received. * A message is printed if any others are received.
* *
* TODO: Should others be implemented? * TODO: Should others be implemented?
@ -81,6 +92,12 @@
* *
* 'y' Outstanding frames waiting on a Port (new in 1.2) * 'y' Outstanding frames waiting on a Port (new in 1.2)
* *
* 'C' AX.25 Connection Received (new in 1.4)
*
* 'D' Connected AX.25 Data (new in 1.4)
*
* 'd' Disconnected (new in 1.4)
*
* *
* *
* References: AGWPE TCP/IP API Tutorial * References: AGWPE TCP/IP API Tutorial
@ -104,11 +121,11 @@
* Cygwin: Can use either one. * Cygwin: Can use either one.
*/ */
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__ #if __WIN32__
#include <winsock2.h> #include <winsock2.h>
#define _WIN32_WINNT 0x0501 #include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#include <ws2tcpip.h>
#else #else
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
@ -129,18 +146,21 @@
#include <time.h> #include <time.h>
#include <ctype.h> #include <ctype.h>
#include "direwolf.h"
#include "tq.h" #include "tq.h"
#include "ax25_pad.h" #include "ax25_pad.h"
#include "textcolor.h" #include "textcolor.h"
#include "audio.h" #include "audio.h"
#include "server.h" #include "server.h"
#include "dlq.h"
/* /*
* Previously, we allowed only one network connection at a time to each port. * Previously, we allowed only one network connection at a time to each port.
* In version 1.1, we allow multiple concurrent client apps to connect. * In version 1.1, we allow multiple concurrent client apps to attach with the AGW network protocol.
* The default is a limit of 3 client applications at the same time.
* You can increase the limit by changing the line below.
* A larger number consumes more resources so don't go crazy by making it larger than needed.
*/ */
#define MAX_NET_CLIENTS 3 #define MAX_NET_CLIENTS 3
@ -162,23 +182,6 @@ static int enable_send_monitor_to_client[MAX_NET_CLIENTS];
/* the client app must send a command to enable this. */ /* the client app must send a command to enable this. */
/*
* Registered callsigns from 'X' command.
* For simplicity just use a fixed size array until there
* is evidence that a larger number would be needed.
*
* Also keep track of which client did the registration.
* For example client 0 might register the callsign ABC
* and client 1 register DEF. If something comes addressed
* to DEF, we would want it going only to client 1.
*/
#define MAX_REG_CALLSIGNS 20
static char registered_callsigns[MAX_REG_CALLSIGNS][AX25_MAX_ADDR_LEN];
static int registered_by_client[MAX_REG_CALLSIGNS];
// TODO: define in one place, use everywhere. // TODO: define in one place, use everywhere.
// TODO: Macro to terminate thread when no point to go on. // TODO: Macro to terminate thread when no point to go on.
@ -226,7 +229,7 @@ static THREAD_F cmd_listen_thread (void *arg);
#if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
// gcc >= 4.2 has __builtin_swap32() but be compatible with older versions. // gcc >= 4.2 has __builtin_swap32() but might not be compatible with older versions or other compilers.
#define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) #define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) )
#define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) #define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) )
@ -447,8 +450,6 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc)
enable_send_monitor_to_client[client] = 0; enable_send_monitor_to_client[client] = 0;
} }
memset (registered_callsigns, 0, sizeof(registered_callsigns));
if (server_port == 0) { if (server_port == 0) {
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Disabled AGW network client port.\n"); dw_printf ("Disabled AGW network client port.\n");
@ -636,6 +637,7 @@ static THREAD_F connect_listen_thread (void *arg)
} }
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
// TODO: "attached" or some other term would be better because "connected" has a different meaning for AX.25.
dw_printf("\nConnected to AGW client application %d ...\n\n", client); dw_printf("\nConnected to AGW client application %d ...\n\n", client);
/* /*
@ -819,6 +821,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
closesocket (client_sock[client]); closesocket (client_sock[client]);
client_sock[client] = -1; client_sock[client] = -1;
WSACleanup(); WSACleanup();
dlq_client_cleanup (client);
} }
#else #else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@ -828,6 +831,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n"); dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n");
close (client_sock[client]); close (client_sock[client]);
client_sock[client] = -1; client_sock[client] = -1;
dlq_client_cleanup (client);
} }
#endif #endif
} }
@ -843,6 +847,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
time_t clock; time_t clock;
struct tm *tm; struct tm *tm;
int num_digi;
clock = time(NULL); clock = time(NULL);
tm = localtime(&clock); // TODO: should use localtime_r tm = localtime(&clock); // TODO: should use localtime_r
@ -867,13 +872,46 @@ 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 */ /* The documentation example includes these 3 extra in the Len= value */
/* but actual observed data uses only the packet info length. */ /* but actual observed data uses only the packet info length. */
// Documentation doesn't mention anything about including the via path.
// In version 1.4, we add that to match observed behaviour.
// This inconsistency was reported:
// Direwolf:
// [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 [08:25:07]`I1*l V>/"9<}[:Barts Tracker 3.83V X
// AGWPE:
// [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 Via WIDE3-3 [08:32:14]`I0*l V>/"98}[:Barts Tracker 3.83V X
num_digi = ax25_get_num_repeaters(pp);
if (num_digi > 0) {
char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)];
char stemp[AX25_MAX_ADDR_LEN+1];
int j;
ax25_get_addr_with_ssid (pp, AX25_REPEATER_1, via);
for (j = 1; j < num_digi; j++) {
ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, stemp);
strlcat (via, ",", sizeof(via));
strlcat (via, stemp, sizeof(via));
}
snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s Via %s <UI pid=%02X Len=%d >[%02d:%02d:%02d]\r%s\r\r",
chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, via,
ax25_get_pid(pp), info_len,
tm->tm_hour, tm->tm_min, tm->tm_sec,
pinfo);
}
else {
snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s <UI pid=%02X Len=%d >[%02d:%02d:%02d]\r%s\r\r", snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s <UI pid=%02X Len=%d >[%02d:%02d:%02d]\r%s\r\r",
chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to,
ax25_get_pid(pp), info_len, ax25_get_pid(pp), info_len,
tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_hour, tm->tm_min, tm->tm_sec,
pinfo); pinfo);
}
agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* include null */ ; agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* +1 to include terminating null */ ;
if (debug_client) { if (debug_client) {
debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@ -888,6 +926,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
closesocket (client_sock[client]); closesocket (client_sock[client]);
client_sock[client] = -1; client_sock[client] = -1;
WSACleanup(); WSACleanup();
dlq_client_cleanup (client);
} }
#else #else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE)); err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@ -897,6 +936,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dw_printf ("\nError sending message to AGW client application %d. Closing connection.\n\n", client); dw_printf ("\nError sending message to AGW client application %d. Closing connection.\n\n", client);
close (client_sock[client]); close (client_sock[client]);
client_sock[client] = -1; client_sock[client] = -1;
dlq_client_cleanup (client);
} }
#endif #endif
} }
@ -913,6 +953,8 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
* Purpose: Send notification to client app when a link has * Purpose: Send notification to client app when a link has
* been established with another station. * been established with another station.
* *
* DL-CONNECT Confirm or DL-CONNECT Indication in the protocol spec.
*
* Inputs: chan - Which radio channel. * Inputs: chan - Which radio channel.
* *
* client - Which one of potentially several clients. * client - Which one of potentially several clients.
@ -942,6 +984,8 @@ void server_link_established (int chan, int client, char *remote_call, char *own
strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from)); strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from));
strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to)); strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to));
// Question: Should the via path be provided too?
if (incoming) { if (incoming) {
// Other end initiated the connection. // Other end initiated the connection.
snprintf (reply.info, sizeof(reply.info), "*** CONNECTED To Station %s\r", remote_call); snprintf (reply.info, sizeof(reply.info), "*** CONNECTED To Station %s\r", remote_call);
@ -966,6 +1010,8 @@ void server_link_established (int chan, int client, char *remote_call, char *own
* another station has been terminated or a connection * another station has been terminated or a connection
* attempt failed. * attempt failed.
* *
* DL-DISCONNECT Confirm or DL-DISCONNECT Indication in the protocol spec.
*
* Inputs: chan - Which radio channel. * Inputs: chan - Which radio channel.
* *
* client - Which one of potentially several clients. * client - Which one of potentially several clients.
@ -976,7 +1022,7 @@ void server_link_established (int chan, int client, char *remote_call, char *own
* *
* timeout - true when no answer from other station. * timeout - true when no answer from other station.
* How do we distinguish who asked for the * How do we distinguish who asked for the
* termination of an existing linkn? * termination of an existing link?
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
@ -1009,6 +1055,66 @@ void server_link_terminated (int chan, int client, char *remote_call, char *own_
} /* end server_link_terminated */ } /* end server_link_terminated */
/*-------------------------------------------------------------------
*
* Name: server_rec_conn_data
*
* Purpose: Send received connected data to the application.
*
* DL-DATA Indication in the protocol spec.
*
* Inputs: chan - Which radio channel.
*
* client - Which one of potentially several clients.
*
* remote_call - Callsign[-ssid] of remote station.
*
* own_call - Callsign[-ssid] of my end.
*
* pid - Protocol ID from I frame.
*
* data_ptr - Pointer to a block of bytes.
*
* data_len - Number of bytes. Could be zero.
*
*--------------------------------------------------------------------*/
void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len)
{
struct {
struct agwpe_s hdr;
char info[AX25_MAX_INFO_LEN]; // I suppose there is potential for something larger.
// We'll cross that bridge if we ever come to it.
} reply;
memset (&reply.hdr, 0, sizeof(reply.hdr));
reply.hdr.portx = chan;
reply.hdr.datakind = 'D';
reply.hdr.pid = pid;
strlcpy (reply.hdr.call_from, remote_call, sizeof(reply.hdr.call_from));
strlcpy (reply.hdr.call_to, own_call, sizeof(reply.hdr.call_to));
if (data_len < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client);
data_len = 0;
}
else if (data_len > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid length %d for connected data to client %d.\n", data_len, client);
data_len = AX25_MAX_INFO_LEN;
}
memcpy (reply.info, data_ptr, data_len);
reply.hdr.data_len_NETLE = host2netle(data_len);
send_to_client (client, &reply);
} /* end server_rec_conn_data */
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
@ -1111,6 +1217,7 @@ static void send_to_client (int client, void *reply_p)
send (client_sock[client], (char*)(ph), len, 0); send (client_sock[client], (char*)(ph), len, 0);
#else #else
err = write (client_sock[client], ph, len); err = write (client_sock[client], ph, len);
(void)err;
#endif #endif
} }
@ -1148,6 +1255,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]); close (client_sock[client]);
#endif #endif
client_sock[client] = -1; client_sock[client] = -1;
dlq_client_cleanup (client);
continue; continue;
} }
@ -1172,7 +1280,7 @@ static THREAD_F cmd_listen_thread (void *arg)
int data_len = netle2host(cmd.hdr.data_len_NETLE); int data_len = netle2host(cmd.hdr.data_len_NETLE);
if (data_len < 0 || data_len > sizeof(cmd.data) - 1) { if (data_len < 0 || data_len > (int)(sizeof(cmd.data) - 1)) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\nInvalid message from AGW client application %d.\n", client); dw_printf ("\nInvalid message from AGW client application %d.\n", client);
@ -1189,6 +1297,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]); close (client_sock[client]);
#endif #endif
client_sock[client] = -1; client_sock[client] = -1;
dlq_client_cleanup (client);
return (0); return (0);
} }
@ -1207,6 +1316,7 @@ static THREAD_F cmd_listen_thread (void *arg)
close (client_sock[client]); close (client_sock[client]);
#endif #endif
client_sock[client] = -1; client_sock[client] = -1;
dlq_client_cleanup (client);
return (0); return (0);
} }
if (n >= 0) { if (n >= 0) {
@ -1326,7 +1436,7 @@ static THREAD_F cmd_listen_thread (void *arg)
{ {
struct { struct {
struct agwpe_s hdr; struct agwpe_s hdr;
unsigned char on_air_baud_rate; /* 0=1200, 3=9600 */ unsigned char on_air_baud_rate; /* 0=1200, 1=2400, 2=4800, 3=9600, ... */
unsigned char traffic_level; /* 0xff if not in autoupdate mode */ unsigned char traffic_level; /* 0xff if not in autoupdate mode */
unsigned char tx_delay; unsigned char tx_delay;
unsigned char tx_tail; unsigned char tx_tail;
@ -1365,10 +1475,14 @@ static THREAD_F cmd_listen_thread (void *arg)
break; break;
case 'H': /* Ask about recently heard stations. */ case 'H': /* Ask about recently heard stations on given port. */
/* This should send back 20 'H' frames for the most recently heard stations. */
/* If there are less available, empty frames are sent to make a total of 20. */
/* Each contains the first and last heard times. */
{ {
#if 0 /* This information is not being collected. */ #if 0 /* Currently, this information is not being collected. */
struct { struct {
struct agwpe_s hdr; struct agwpe_s hdr;
char info[100]; char info[100];
@ -1382,7 +1496,8 @@ static THREAD_F cmd_listen_thread (void *arg)
reply.hdr.portx = cmd.hdr.portx reply.hdr.portx = cmd.hdr.portx
strlcpy (reply.hdr.call_from, "WB2OSZ-15", sizeof(reply.hdr.call_from)); strlcpy (reply.hdr.call_from, "WB2OSZ-15 Mon,01Jan2000 01:02:03 Tue,31Dec2099 23:45:56", sizeof(reply.hdr.call_from));
// or 00:00:00 00:00:00
strlcpy (agwpe_msg.data, ..., sizeof(agwpe_msg.data)); strlcpy (agwpe_msg.data, ..., sizeof(agwpe_msg.data));
@ -1525,38 +1640,34 @@ static THREAD_F cmd_listen_thread (void *arg)
{ {
struct { struct {
struct agwpe_s hdr; struct agwpe_s hdr;
char data; char data; /* 1 = success, 0 = failure */
} reply; } reply;
int j, ok; int ok = 1;
// The protocol spec says it is an error to register the same one more than once.
// Too much trouble. Report success if the channel is valid.
int chan = cmd.hdr.portx;
if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) {
ok = 1;
dlq_register_callsign (cmd.hdr.call_from, chan, client);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("AGW protocol error. Register callsign for invalid channel %d.\n", chan);
ok = 0;
}
memset (&reply, 0, sizeof(reply)); memset (&reply, 0, sizeof(reply));
reply.hdr.datakind = 'X'; reply.hdr.datakind = 'X';
reply.hdr.portx = cmd.hdr.portx;
memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from)); memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from));
reply.hdr.data_len_NETLE = host2netle(1); reply.hdr.data_len_NETLE = host2netle(1);
reply.data = ok;
// Version 1.0.
// Previously used sizeof(reply) but compiler rounded it up to next byte boundary.
// That's why more cumbersome size expression is used.
// The protocol spec says it is an error to register the same one more than once.
// First make sure is it not already in there. Add if space available.
if (server_callsign_registered_by_client(cmd.hdr.call_from) >= 0) {
ok = 0;
}
else {
ok = 0;
for (j = 0; j < MAX_REG_CALLSIGNS && ok == 0; j++) {
if (registered_callsigns[j][0] == '\0') {
strlcpy (registered_callsigns[j], cmd.hdr.call_from, sizeof(registered_callsigns[j]));
registered_by_client[j] = client;
ok = 1;
}
}
}
reply.data = ok; /* 1 = success, 0 = failure */
send_to_client (client, &reply); send_to_client (client, &reply);
} }
break; break;
@ -1564,13 +1675,15 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'x': /* Unregister CallSign */ case 'x': /* Unregister CallSign */
{ {
int j;
for (j = 0; j < MAX_REG_CALLSIGNS; j++) { int chan = cmd.hdr.portx;
if (strcmp(registered_callsigns[j], cmd.hdr.call_from) == 0) {
registered_callsigns[j][0] = '\0'; if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) {
registered_by_client[j] = -1; dlq_unregister_callsign (cmd.hdr.call_from, chan, client);
} }
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("AGW protocol error. Unregister callsign for invalid channel %d.\n", chan);
} }
} }
/* No reponse is expected. */ /* No reponse is expected. */
@ -1590,10 +1703,9 @@ static THREAD_F cmd_listen_thread (void *arg)
int num_calls = 2; /* 2 plus any digipeaters. */ int num_calls = 2; /* 2 plus any digipeaters. */
int pid = 0xf0; /* normal for AX.25 I frames. */ int pid = 0xf0; /* normal for AX.25 I frames. */
int j; int j;
char stemp[256];
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_DESTINATION]));
if (cmd.hdr.datakind == 'c') { if (cmd.hdr.datakind == 'c') {
pid = cmd.hdr.pid; /* non standard for NETROM, TCP/IP, etc. */ pid = cmd.hdr.pid; /* non standard for NETROM, TCP/IP, etc. */
@ -1620,28 +1732,38 @@ static THREAD_F cmd_listen_thread (void *arg)
} }
} }
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n"); dlq_connect_request (callsigns, num_calls, cmd.hdr.portx, client, pid);
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
dw_printf ("Connected packet mode is not implemented.\n");
} }
break; break;
case 'D': /* Send Connected Data */ case 'D': /* Send Connected Data */
text_color_set(DW_COLOR_ERROR); {
dw_printf ("\n"); char callsigns[2][AX25_MAX_ADDR_LEN];
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client); const int num_calls = 2;
dw_printf ("Connected packet mode is not implemented.\n");
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
dlq_xmit_data_request (callsigns, num_calls, cmd.hdr.portx, client, cmd.hdr.pid, cmd.data, netle2host(cmd.hdr.data_len_NETLE));
}
break; break;
case 'd': /* Disconnect, Terminate an AX.25 Connection */ case 'd': /* Disconnect, Terminate an AX.25 Connection */
{ {
text_color_set(DW_COLOR_ERROR); char callsigns[2][AX25_MAX_ADDR_LEN];
dw_printf ("\n"); const int num_calls = 2;
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
dw_printf ("Connected packet mode is not implemented.\n"); strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
dlq_disconnect_request (callsigns, num_calls, cmd.hdr.portx, client);
} }
break; break;
@ -1756,30 +1878,4 @@ static THREAD_F cmd_listen_thread (void *arg)
} /* end send_to_client */ } /* end send_to_client */
/*-------------------------------------------------------------------
*
* Name: server_callsign_registered_by_client
*
* Purpose: See if given callsign was registered.
*
* Inputs: callsign
*
* Returns: >= 0 for the client number.
* -1 for not found.
*
*--------------------------------------------------------------------*/
int server_callsign_registered_by_client (char *callsign)
{
int j;
for (j = 0; j < MAX_REG_CALLSIGNS; j++) {
if (strcmp(registered_callsigns[j], callsign) == 0) {
return (registered_by_client[j]);
}
}
return (-1);
} /* end server_callsign_registered_by_client */
/* end server.c */ /* end server.c */

Some files were not shown because too many files have changed in this diff Show More