Add 2400 & 4800 PSK modems. New functions to handle frames besides UI.

This commit is contained in:
WB2OSZ 2016-05-01 18:46:47 -04:00
parent ccae7529bf
commit 4f1918c3af
38 changed files with 3500 additions and 463 deletions

View File

@ -1,6 +1,36 @@
# Revision History # # Revision History #
----------
## Version 1.4 -- Development snapshot B -- April 2016 ##
### New Features: ###
- 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.
### Bugs Fixed: ###
- 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.*
---------- ----------
## Version 1.3 -- Beta Test -- March 2016 ## ## Version 1.3 -- Beta Test -- March 2016 ##
@ -61,7 +91,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.
@ -272,8 +302,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

@ -228,7 +228,7 @@ 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 redecode.o rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o \ fcs_calc.o ax25_pad.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
@ -248,15 +248,18 @@ endif
# Optimization for slow processors. # Optimization for slow processors.
demod.o : fsk_fast_filter.h 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.
@ -327,7 +330,7 @@ gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c
# 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 latlong.o symbols.o tt_text.o textcolor.o \
@ -585,15 +588,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 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,11 +605,28 @@ 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 -L57 -G61 /tmp/test96.wav
./atest -B9600 -F1 -L66 -G67 /tmp/test9.wav ./atest -B9600 -F1 -L64 -G67 /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 -L62 -G64 /tmp/test19.wav
./atest -B19200 -F1 -L69 -G71 /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
@ -679,6 +699,14 @@ 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
# ----------------------------- Manual tests and experiments --------------------------- # ----------------------------- Manual tests and experiments ---------------------------
@ -694,7 +722,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
@ -702,11 +730,19 @@ udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o hdlc_rec
# For demodulator tweaking experiments. # For demodulator tweaking experiments.
# 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
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 latlong.o symbols.o tune.h textcolor.o misc.a fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o latlong.o symbols.o tune.h textcolor.o misc.a
$(CC) $(CFLAGS) -o atest $^ $(LDFLAGS) $(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
@ -757,8 +793,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"
@ -221,7 +222,7 @@ 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_pad.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 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 \
@ -234,15 +235,20 @@ direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beaco
# Optimization for slow processors. # Optimization for slow processors.
demod.o : fsk_fast_filter.h 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 +398,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 +408,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.
@ -438,22 +440,30 @@ log2gpx : log2gpx.c
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 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 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 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 +523,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

@ -28,6 +28,9 @@ AR := ar
CFLAGS += -g CFLAGS += -g
# TODO: Development in progress. Don't try using yet.
#CFLAGS += -DNEW14
# #
# 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,14 +64,22 @@ CFLAGS += -g
# -------------------------------------- Main application -------------------------------- # -------------------------------------- Main application --------------------------------
demod.o : fsk_demod_state.h # Not sure why this is here.
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 \
# later ax25_link.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 redecode.o rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o \ fcs_calc.o ax25_pad.o ax25_pad2.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 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 \
@ -88,10 +99,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.
@ -235,32 +249,59 @@ 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 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
check-modem9600 : gen_packets atest check-modem9600 : gen_packets atest
gen_packets -B9600 -n 100 -o test9.wav gen_packets -B9600 -n 100 -o test96.wav
atest -B9600 -F0 -L57 -G59 test9.wav sleep 1
atest -B9600 -F1 -L66 -G67 test9.wav atest -B9600 -F0 -L57 -G62 test96.wav
rm test9.wav atest -B9600 -F1 -L65 -G67 test96.wav
sleep 1
rm test96.wav
# Unit test for AFSK demodulator check-modem19200 : gen_packets atest
gen_packets -r 96000 -B19200 -n 100 -o test19.wav
sleep 1
atest -B19200 -F0 -L63 -G64 test19.wav
atest -B19200 -F1 -L69 -G70 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 \
@ -272,7 +313,7 @@ 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 misc.a regex.a \
fsk_fast_filter.h fsk_fast_filter.h
echo " " > tune.h echo " " > tune.h
@ -352,17 +393,26 @@ 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
# ------------------------------ Other manual testing & experimenting ------------------------------- # ------------------------------ Other manual testing & experimenting -------------------------------
# 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 regex.a misc.a
@ -375,27 +425,53 @@ 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 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 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 regex.a misc.a \
tune.h tune.h
rm -f atest.exe rm -f atest9.exe
$(CC) $(CFLAGS) -o atest $^ $(CC) $(CFLAGS) -o atest9 $^
./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out ./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
#./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out #./atest9 -B 9600 noisy96.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 \
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 \
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,7 +500,7 @@ 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 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

View File

@ -1706,7 +1706,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);

84
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
@ -291,35 +291,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 +356,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 +369,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,7 +427,7 @@ 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 = time(NULL);
@ -437,12 +457,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 +472,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;
@ -496,8 +530,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++;
@ -532,15 +568,15 @@ int main (int argc, char *argv[])
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 +632,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];
@ -740,7 +776,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");

View File

@ -101,11 +101,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 +131,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. */

View File

@ -891,7 +891,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);
} }
@ -1789,13 +1792,15 @@ 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 * modulo - We often need to know this because context is // TODO: remove this - return cr instead.
* required to know if control is 1 or 2 bytes. * 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 16 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
* *
* nr - N(R) - receive sequence or -1 if not applicable. * nr - N(R) - receive sequence or -1 if not applicable.
@ -1809,7 +1814,8 @@ 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 #define DESC_SIZ 32
ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *desc, int *pf, int *nr, int *ns)
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; // I & S frames can have second Control byte.
@ -1818,6 +1824,7 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char *
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 +1834,30 @@ 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) { 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 +1867,14 @@ 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);
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 +1883,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 +1899,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 +1919,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
@ -2437,7 +2461,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

@ -100,7 +100,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 +110,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 +125,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,6 +156,7 @@ 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;
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */ #ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
@ -154,21 +166,17 @@ 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.
*/ */
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. return (this_p->modulo == 128 ? 2 : 1);
} }
@ -361,7 +369,7 @@ extern void ax25_format_addrs (packet_t pp, char *);
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);

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 <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include "direwolf.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;
int t = -1; // 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.\n", __func__, cr, t);
}
}
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_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[32];
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[32];
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_ERROR);
dw_printf ("Internal error in %s: I frame, Invalid pid value 0x%02x.\n", __func__, pid);
pid = AX25_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[32];
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

@ -914,7 +914,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
/* Simulated reception. */ /* Simulated reception. */
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

@ -1116,15 +1116,15 @@ void config_init (char *fname, struct audio_s *p_audio_config,
n = atoi(t); n = atoi(t);
if (n >= MIN_BAUD && n <= MAX_BAUD) { if (n >= MIN_BAUD && n <= MAX_BAUD) {
p_audio_config->achan[channel].baud = n; p_audio_config->achan[channel].baud = n;
if (n != 300 && n != 1200 && n != 9600) { if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Warning: Non-standard baud rate. Are you sure?\n", line); dw_printf ("Line %d: Warning: Non-standard data rate of %d bits per second. Are you sure?\n", line, n);
} }
} }
else { else {
p_audio_config->achan[channel].baud = DEFAULT_BAUD; p_audio_config->achan[channel].baud = DEFAULT_BAUD;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable baud rate. Using %d.\n", dw_printf ("Line %d: Unreasonable data rate. Using %d bits per second.\n",
line, p_audio_config->achan[channel].baud); line, p_audio_config->achan[channel].baud);
} }
@ -1132,20 +1132,33 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/* Set defaults based on speed. */ /* Set defaults based on speed. */
/* Should be same as -B command line option in direwolf.c. */ /* Should be same as -B command line option in direwolf.c. */
/* 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 (p_audio_config->achan[channel].baud < 600) { if (p_audio_config->achan[channel].baud < 600) {
p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].modem_type = MODEM_AFSK;
p_audio_config->achan[channel].mark_freq = 1600; p_audio_config->achan[channel].mark_freq = 1600;
p_audio_config->achan[channel].space_freq = 1800; p_audio_config->achan[channel].space_freq = 1800;
} }
else if (p_audio_config->achan[channel].baud > 2400) { else if (p_audio_config->achan[channel].baud < 1800) {
p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; p_audio_config->achan[channel].modem_type = MODEM_AFSK;
p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
}
else if (p_audio_config->achan[channel].baud < 3600) {
p_audio_config->achan[channel].modem_type = MODEM_QPSK;
p_audio_config->achan[channel].mark_freq = 0;
p_audio_config->achan[channel].space_freq = 0;
}
else if (p_audio_config->achan[channel].baud < 7200) {
p_audio_config->achan[channel].modem_type = MODEM_8PSK;
p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].mark_freq = 0;
p_audio_config->achan[channel].space_freq = 0; p_audio_config->achan[channel].space_freq = 0;
} }
else { else {
p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
p_audio_config->achan[channel].mark_freq = 1200; p_audio_config->achan[channel].mark_freq = 0;
p_audio_config->achan[channel].space_freq = 2200; p_audio_config->achan[channel].space_freq = 0;
} }
/* Get mark frequency. */ /* Get mark frequency. */

View File

@ -166,7 +166,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? */
@ -254,8 +254,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;
@ -1409,22 +1410,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 +1460,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 +1471,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 +1502,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 +1516,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 +1542,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 () */

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. */

160
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 */
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
@ -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"
@ -225,6 +218,7 @@ int demod_init (struct audio_s *pa)
num_letters = 1; num_letters = 1;
} }
assert (num_letters == strlen(just_letters)); assert (num_letters == strlen(just_letters));
/* /*
@ -403,7 +397,7 @@ 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.2;
@ -459,7 +453,7 @@ 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.2;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2;
@ -512,7 +506,7 @@ 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.2;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2;
@ -521,6 +515,114 @@ int demod_init (struct audio_s *pa)
} }
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:
@ -602,7 +704,7 @@ 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.2;
D->sluggish_decay = D->agc_slow_decay * 0.2; D->sluggish_decay = D->agc_slow_decay * 0.2;
@ -728,17 +830,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);
@ -803,7 +897,22 @@ void demod_process_sample (int chan, int subchan, int sam)
} }
} }
else { else {
demod_afsk_process_sample (chan, subchan, sam, D); demod_afsk_process_sample (chan, subchan, sam, D);
}
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; break;
@ -897,8 +1006,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

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

853
demod_psk.c Normal file
View File

@ -0,0 +1,853 @@
//
// 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 <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "direwolf.h"
#include "audio.h"
#include "tune.h"
#include "fsk_demod_state.h"
#include "fsk_gen_filter.h"
#include "hdlc_rec.h"
#include "textcolor.h"
#include "demod_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. / 12.) * (float)samples_per_sec / (float)correct_baud );
D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud );
D->soffs = (int) round( (13. / 12.) * (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. / 9.) * (float)samples_per_sec / (float)correct_baud );
D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud );
D->soffs = (int) round( (10. / 9.) * (float)samples_per_sec / (float)correct_baud );
}
if (D->psk_use_lo) {
D->lo_step = (int) round( 256. * 256. * 256. * 256. * carrier_freq / (float)samples_per_sec);
assert (MAX_FILTER_SIZE >= 256);
for (j = 0; j < 256; j++) {
D->m_sin_table[j] = sinf(2. * M_PI * j / 256.);
}
}
#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", f1, f2);
#endif
if (f1 <= 0) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("Prefilter of %.0f to %.0f Hz doesn't make sense.\n", f1, 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.
*
*--------------------------------------------------------------------*/
static void inline 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 * 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 * 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, delta;
int id;
a = my_atan2f(I,Q);
id = ((int)((a / (2.f * 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 = ((id - 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))
static void inline 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.
*/
if (demod_bits != D->slicer[slice].prev_demod_data) {
if (hdlc_rec_gathering (chan, subchan, slice)) {
D->slicer[slice].data_clock_pll = (int)floor((double)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia);
}
else {
D->slicer[slice].data_clock_pll = (int)floor((double)(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

@ -239,7 +239,7 @@ int main (int argc, char *argv[])
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, "A", __DATE__); dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "B", __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)
@ -561,22 +561,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;
@ -623,7 +644,7 @@ 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);
@ -884,12 +905,28 @@ 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[32];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
dw_printf ("(%s)", desc);
}
// 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.
// TODO: Might want to use d_u_opt for transmitted frames too. // TODO: Might want to use d_u_opt for transmitted frames too.
ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) ); ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) );
dw_printf ("\n"); dw_printf ("\n");
@ -1068,10 +1105,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");

485
dlq.c
View File

@ -1,8 +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) 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
@ -25,13 +24,17 @@
* *
* Purpose: Received frame queue. * Purpose: Received frame queue.
* *
* Description: In previous versions, the main thread read from the * Description: In earlierversions, the main thread read from the
* audio device and performed the receive demodulation/decoding. * audio device and performed the receive demodulation/decoding.
* In version 1.2 we now have a seprate receive thread *
* Since version 1.2 we have a separate receive thread
* for each audio device. This queue is used to collect * for each audio device. This queue is used to collect
* received frames from all channels and process them * received frames from all channels and process them
* serially. * serially.
* *
* In version 1.4, other types of events go into this
* queue and we use it to drive the data link state machine.
*
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
#include <stdio.h> #include <stdio.h>
@ -39,6 +42,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#if __WIN32__
#else
#include <errno.h>
#endif
#include "direwolf.h" #include "direwolf.h"
#include "ax25_pad.h" #include "ax25_pad.h"
@ -46,36 +53,11 @@
#include "audio.h" #include "audio.h"
#include "dlq.h" #include "dlq.h"
#include "dedupe.h" #include "dedupe.h"
#include "dtime_now.h"
/* The queue is a linked list of these. */ /* The queue is a linked list of these. */
struct dlq_item_s {
struct dlq_item_s *nextp; /* Next item in queue. */
dlq_type_t type; /* Type of item. */
/* Only received frames at this time. */
int chan; /* Radio channel of origin. */
int subchan; /* Winning "subchannel" when using multiple */
/* decoders on one channel. */
/* Special case, -1 means DTMF decoder. */
/* Maybe we should have a different type in this case? */
int slice; /* Winning slicer. */
packet_t pp; /* Pointer to frame structure. */
alevel_t alevel; /* Audio level. */
retry_t retries; /* Effort expended to get a valid CRC. */
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */
};
static struct dlq_item_s *queue_head = NULL; /* Head of linked list for queue. */ static struct dlq_item_s *queue_head = NULL; /* Head of linked list for queue. */
#if __WIN32__ #if __WIN32__
@ -94,14 +76,19 @@ static pthread_cond_t wake_up_cond; /* Notify received packet processing thread
static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */
static int recv_thread_is_waiting = 0; static volatile int recv_thread_is_waiting = 0;
#endif #endif
static int dlq_is_empty (void);
static int was_init = 0; /* was initialization performed? */ static int was_init = 0; /* was initialization performed? */
static void append_to_queue (struct dlq_item_s *pnew);
static volatile int s_new_count = 0; /* To detect memory leak. */
static volatile int s_delete_count = 0; // TODO: need to test.
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
@ -121,8 +108,7 @@ static int was_init = 0; /* was initialization performed? */
void dlq_init (void) void dlq_init (void)
{ {
int c, p; //int c, p;
int err;
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
@ -140,6 +126,7 @@ void dlq_init (void)
#if __WIN32__ #if __WIN32__
InitializeCriticalSection (&dlq_cs); InitializeCriticalSection (&dlq_cs);
#else #else
int err;
err = pthread_mutex_init (&wake_up_mutex, NULL); err = pthread_mutex_init (&wake_up_mutex, NULL);
err = pthread_mutex_init (&dlq_mutex, NULL); err = pthread_mutex_init (&dlq_mutex, NULL);
if (err != 0) { if (err != 0) {
@ -192,17 +179,17 @@ void dlq_init (void)
} /* end dlq_init */ } /* end dlq_init */
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: dlq_append * Name: dlq_rec_frame
* *
* Purpose: Add a packet to the end of the specified receive queue. * Purpose: Add a received packet to the end of the queue.
* Normally this was received over the radio but we can create
* our own from APRStt or beaconing.
* *
* Inputs: type - One of the following:
* *
* DLQ_REC_FRAME - Frame received from radio. * Inputs: chan - Channel, 0 is first.
*
* chan - Channel, 0 is first.
* *
* subchan - Which modem caught it. * subchan - Which modem caught it.
* Special case -1 for APRStt gateway. * Special case -1 for APRStt gateway.
@ -224,38 +211,27 @@ void dlq_init (void)
* spectrum - Display of how well multiple decoders did. * spectrum - Display of how well multiple decoders did.
* *
* *
* Outputs: Information is appended to queue.
*
* Description: Add item to end of linked list.
* Signal the receive processing thread if the queue was formerly empty.
*
* IMPORTANT! Don't make an further references to the packet object after * IMPORTANT! Don't make an further references to the packet object after
* giving it to dlq_append. * giving it to dlq_append.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
{ {
struct dlq_item_s *pnew; struct dlq_item_s *pnew;
struct dlq_item_s *plast;
int err;
int queue_length = 0;
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_append (type=%d, chan=%d, pp=%p, ...)\n", type, chan, pp); dw_printf ("dlq_rec_frame (chan=%d, pp=%p, ...)\n", chan, pp);
#endif #endif
if ( ! was_init) {
dlq_init ();
}
assert (chan >= 0 && chan < MAX_CHANS); assert (chan >= 0 && chan < MAX_CHANS);
if (pp == NULL) { if (pp == NULL) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: dlq_append NULL packet pointer. Please report this!\n"); dw_printf ("INTERNAL ERROR: dlq_rec_frame NULL packet pointer. Please report this!\n");
return; return;
} }
@ -263,16 +239,17 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
if (ax25memdebug_get()) { if (ax25memdebug_get()) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_append (type=%d, chan=%d.%d, seq=%d, ...)\n", type, chan, subchan, ax25memdebug_seq(pp)); dw_printf ("dlq_rec_frame (chan=%d.%d, seq=%d, ...)\n", chan, subchan, ax25memdebug_seq(pp));
} }
#endif #endif
/* Allocate a new queue item. */ /* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->nextp = NULL; pnew->nextp = NULL;
pnew->type = type; pnew->type = DLQ_REC_FRAME;
pnew->chan = chan; pnew->chan = chan;
pnew->slice = slice; pnew->slice = slice;
pnew->subchan = subchan; pnew->subchan = subchan;
@ -284,17 +261,56 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
else else
strlcpy(pnew->spectrum, spectrum, sizeof(pnew->spectrum)); strlcpy(pnew->spectrum, spectrum, sizeof(pnew->spectrum));
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_rec_frame */
/*-------------------------------------------------------------------
*
* Name: append_to_queue
*
* Purpose: Append some type of event to queue.
* This includes frames received over the radio,
* requests from client applications, and notifications
* from the frame transmission process.
*
*
* Inputs: pnew - Pointer to queue element structure.
*
* Outputs: Information is appended to queue.
*
* Description: Add item to end of linked list.
* Signal the receive processing thread if the queue was formerly empty.
*
*--------------------------------------------------------------------*/
static void append_to_queue (struct dlq_item_s *pnew)
{
struct dlq_item_s *plast;
int queue_length = 0;
if ( ! was_init) {
dlq_init ();
}
pnew->nextp = NULL;
#if DEBUG1 #if DEBUG1
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_append: enter critical section\n"); dw_printf ("dlq append_to_queue: enter critical section\n");
#endif #endif
#if __WIN32__ #if __WIN32__
EnterCriticalSection (&dlq_cs); EnterCriticalSection (&dlq_cs);
#else #else
int err;
err = pthread_mutex_lock (&dlq_mutex); err = pthread_mutex_lock (&dlq_mutex);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("dlq_append: pthread_mutex_lock err=%d", err); dw_printf ("dlq append_to_queue: pthread_mutex_lock err=%d", err);
perror (""); perror ("");
exit (1); exit (1);
} }
@ -321,15 +337,15 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
err = pthread_mutex_unlock (&dlq_mutex); err = pthread_mutex_unlock (&dlq_mutex);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("dlq_append: pthread_mutex_unlock err=%d", err); dw_printf ("dlq append_to_queue: pthread_mutex_unlock err=%d", err);
perror (""); perror ("");
exit (1); exit (1);
} }
#endif #endif
#if DEBUG1 #if DEBUG1
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_append: left critical section\n"); dw_printf ("dlq append_to_queue: left critical section\n");
dw_printf ("dlq_append (): about to wake up recv processing thread.\n"); dw_printf ("dlq append_to_queue (): about to wake up recv processing thread.\n");
#endif #endif
@ -394,7 +410,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
err = pthread_mutex_lock (&wake_up_mutex); err = pthread_mutex_lock (&wake_up_mutex);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("dlq_append: pthread_mutex_lock wu err=%d", err); dw_printf ("dlq append_to_queue: pthread_mutex_lock wu err=%d", err);
perror (""); perror ("");
exit (1); exit (1);
} }
@ -402,7 +418,7 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
err = pthread_cond_signal (&wake_up_cond); err = pthread_cond_signal (&wake_up_cond);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("dlq_append: pthread_cond_signal err=%d", err); dw_printf ("dlq append_to_queue: pthread_cond_signal err=%d", err);
perror (""); perror ("");
exit (1); exit (1);
} }
@ -410,14 +426,191 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
err = pthread_mutex_unlock (&wake_up_mutex); err = pthread_mutex_unlock (&wake_up_mutex);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("dlq_append: pthread_mutex_unlock wu err=%d", err); dw_printf ("dlq append_to_queue: pthread_mutex_unlock wu err=%d", err);
perror (""); perror ("");
exit (1); exit (1);
} }
} }
#endif #endif
} } /* end append_to_queue */
/*-------------------------------------------------------------------
*
* Name: dlq_connect_request
*
* Purpose: Client application has requested connection to another station.
*
* Inputs: addrs - Source (owncall), destination (peercall),
* and possibly digipeaters.
*
* num_addr - Number of addresses. 2 to 10.
*
* chan - Channel, 0 is first.
*
* client - Client application instance. We could have multiple
* applications, all on the same channel, connecting
* to different stations. We need to know which one
* should get the results.
*
* pid - Protocol ID for data. Normally 0xf0 but the API
* allows the client app to use something non-standard
* for special situations.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
*--------------------------------------------------------------------*/
void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_connect_request (...)\n");
#endif
assert (chan >= 0 && chan < MAX_CHANS);
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_CONNECT_REQUEST;
pnew->chan = chan;
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
pnew->pid = pid;
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_connect_request */
/*-------------------------------------------------------------------
*
* Name: dlq_disconnect_request
*
* Purpose: Client application has requested to disconnect.
*
* Inputs: addrs - Source (owncall), destination (peercall),
* and possibly digipeaters.
*
* num_addr - Number of addresses. 2 to 10.
* Only first two matter in this case.
*
* chan - Channel, 0 is first.
*
* client - Client application instance. We could have multiple
* applications, all on the same channel, connecting
* to different stations. We need to know which one
* should get the results.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
*--------------------------------------------------------------------*/
void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_disconnect_request (...)\n");
#endif
assert (chan >= 0 && chan < MAX_CHANS);
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_DISCONNECT_REQUEST;
pnew->chan = chan;
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_connect_request */
/*-------------------------------------------------------------------
*
* Name: dlq_xmit_data_request
*
* Purpose: Client application has requested transmission of connected
* data over an established link.
*
* Inputs: addrs - Source (owncall), destination (peercall),
* and possibly digipeaters.
*
* num_addr - Number of addresses. 2 to 10.
* First two are used to uniquely identify link.
* Any digipeaters involved are remembered
* from when the link was established.
*
* chan - Channel, 0 is first.
*
* client - Client application instance.
*
* pid - Protocol ID for data. Normally 0xf0 but the API
* allows the client app to use something non-standard
* for special situations.
*
* xdata_ptr - Pointer to block of data.
*
* xdata_len - Length of data in bytes.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
*--------------------------------------------------------------------*/
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)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_xmit_data_request (...)\n");
#endif
assert (chan >= 0 && chan < MAX_CHANS);
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_XMIT_DATA_REQUEST;
pnew->chan = chan;
memcpy (pnew->addrs, addrs, sizeof(pnew->addrs));
pnew->num_addr = num_addr;
pnew->client = client;
pnew->pid = pid;
/* TODO: haven't thought about user data yet. */
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_xmit_data_request */
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
@ -427,19 +620,24 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp,
* Purpose: Sleep while the received data queue is empty rather than * Purpose: Sleep while the received data queue is empty rather than
* polling periodically. * polling periodically.
* *
* Inputs: None. * Inputs: timeout - Return at this time even if queue is empty.
* Zero for no timeout.
*
* Returns: True if timed out before any event arrived.
*
* Description: In version 1.4, we add timeout option so we can continue after
* some amount of time even if no events are in the queue.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
void dlq_wait_while_empty (void) int dlq_wait_while_empty (double timeout)
{ {
int err; int timed_out_result = 0;
#if DEBUG1 #if DEBUG1
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_wait_while_empty () \n"); dw_printf ("dlq_wait_while_empty (%.3f)\n", timeout);
#endif #endif
if ( ! was_init) { if ( ! was_init) {
@ -448,21 +646,34 @@ void dlq_wait_while_empty (void)
if (queue_head == NULL) { if (queue_head == NULL) {
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_wait_while_empty (): prepare to SLEEP - about to call cond wait\n"); dw_printf ("dlq_wait_while_empty (): prepare to SLEEP...\n");
#endif #endif
#if __WIN32__ #if __WIN32__
WaitForSingleObject (wake_up_event, INFINITE);
if (timeout != 0.0) {
DWORD ms = (timeout - dtime_now()) * 1000;
if (ms <= 0) ms = 1;
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_wait_while_empty (): returned from wait\n"); dw_printf ("WaitForSingleObject: timeout after %d ms\n", ms);
#endif #endif
if (WaitForSingleObject (wake_up_event, ms) == WAIT_TIMEOUT) {
timed_out_result = 1;
}
}
else {
WaitForSingleObject (wake_up_event, INFINITE);
}
#else #else
int err;
err = pthread_mutex_lock (&wake_up_mutex); err = pthread_mutex_lock (&wake_up_mutex);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -472,20 +683,21 @@ void dlq_wait_while_empty (void)
} }
recv_thread_is_waiting = 1; recv_thread_is_waiting = 1;
err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); if (timeout != 0.0) {
recv_thread_is_waiting = 0; struct timespec abstime;
#if DEBUG abstime.tv_sec = (time_t)(long)timeout;
text_color_set(DW_COLOR_DEBUG); abstime.tv_nsec = (long)((timeout - (long)abstime.tv_sec) * 1000000000.0);
dw_printf ("dlq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err);
#endif
if (err != 0) { err = pthread_cond_timedwait (&wake_up_cond, &wake_up_mutex, &abstime);
text_color_set(DW_COLOR_ERROR); if (err == ETIMEDOUT) {
dw_printf ("dlq_wait_while_empty: pthread_cond_wait err=%d", err); timed_out_result = 1;
perror (""); }
exit (1);
} }
else {
err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex);
}
recv_thread_is_waiting = 0;
err = pthread_mutex_unlock (&wake_up_mutex); err = pthread_mutex_unlock (&wake_up_mutex);
if (err != 0) { if (err != 0) {
@ -494,17 +706,18 @@ void dlq_wait_while_empty (void)
perror (""); perror ("");
exit (1); exit (1);
} }
#endif #endif
} }
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_wait_while_empty () returns\n"); dw_printf ("dlq_wait_while_empty () returns timedout=%d\n", timed_out_result);
#endif #endif
return (timed_out_result);
} /* end dlq_wait_while_empty */
}
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
@ -515,27 +728,17 @@ void dlq_wait_while_empty (void)
* *
* Inputs: None. * Inputs: None.
* *
* Outputs: type - type of queue entry. * Returns: Pointer to a queue item. Caller is responsible for deleting it.
* * NULL if queue is empty.
* chan - channel of received frame.
* subchan - which demodulator caught it.
* slice - which slicer caught it.
*
* pp - pointer to packet object when type is DLQ_REC_FRAME.
* Caller should destroy it with ax25_delete when finished with it.
*
* Returns: 1 for success.
* 0 if queue is empty.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t *pp, alevel_t *alevel, retry_t *retries, char *spectrum, size_t spectrumsize) struct dlq_item_s *dlq_remove (void)
{ {
struct dlq_item_s *phead; struct dlq_item_s *result = NULL;
int result; //int err;
int err;
#if DEBUG1 #if DEBUG1
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
@ -549,6 +752,8 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t
#if __WIN32__ #if __WIN32__
EnterCriticalSection (&dlq_cs); EnterCriticalSection (&dlq_cs);
#else #else
int err;
err = pthread_mutex_lock (&dlq_mutex); err = pthread_mutex_lock (&dlq_mutex);
if (err != 0) { if (err != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -558,34 +763,9 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t
} }
#endif #endif
if (queue_head == NULL) { if (queue_head != NULL) {
result = queue_head;
*type = -1;
*chan = -1;
*subchan = -1;
*slice = -1;
*pp = NULL;
memset (alevel, 0xff, sizeof(*alevel));
*retries = -1;
strlcpy(spectrum, "", spectrumsize);
result = 0;
}
else {
phead = queue_head;
queue_head = queue_head->nextp; queue_head = queue_head->nextp;
*type = phead->type;
*chan = phead->chan;
*subchan = phead->subchan;
*slice = phead->slice;
*pp = phead->pp;
*alevel = phead->alevel;
*retries = phead->retries;
strlcpy (spectrum, phead->spectrum, spectrumsize);
result = 1;
} }
#if __WIN32__ #if __WIN32__
@ -602,19 +782,22 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_remove() returns type=%d, chan=%d\n", (int)(*type), *chan); dw_printf ("dlq_remove() returns \n");
#endif #endif
#if AX25MEMDEBUG #if AX25MEMDEBUG
if (ax25memdebug_get() && result) { if (ax25memdebug_get() && result != NULL) {
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", *type, *chan, *subchan, ax25memdebug_seq(*pp)); if (result->pp != NULL) {
// TODO: mnemonics for type.
dw_printf ("dlq_remove (type=%d, chan=%d.%d, seq=%d, ...)\n", result->type, result->chan, result->subchan, ax25memdebug_seq(result->pp));
}
else {
dw_printf ("dlq_remove (type=%d, chan=%d, ...)\n", result->type, result->chan);
}
} }
#endif #endif
if (result) {
free (phead);
}
return (result); return (result);
} }
@ -622,25 +805,23 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: dlq_is_empty * Name: dlq_delete
* *
* Purpose: Test whether queue is empty. * Purpose: Release storage used by a queue item.
* *
* Inputs: None * Inputs: pitem - Pointer to a queue item.
*
* Returns: True if nothing in the queue.
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
#if 0
static int dlq_is_empty (void)
{
if (queue_head == NULL) {
return (1);
}
return (0);
} /* end dlq_is_empty */ void dlq_delete (struct dlq_item_s *pitem)
#endif {
s_delete_count++;
if (pitem->pp != NULL) ax25_delete (pitem->pp)
free (pitem);
} /* end dlq_delete */
/* end dlq.c */ /* end dlq.c */

70
dlq.h
View File

@ -12,17 +12,77 @@
#include "audio.h" #include "audio.h"
void dlq_init (void);
/* 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_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. */
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); // 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. */
/* DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST */
int chan; /* Radio channel of origin. */
// 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.
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
int num_addr; /* Range 2 .. 10. */
int client;
int pid;
/* TODO: xmit data */
} 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 clienti, int pid, char *xdata_ptr, int xdata_len);
int dlq_wait_while_empty (double timeout_val);
struct dlq_item_s *dlq_remove (void);
void dlq_delete (struct dlq_item_s *pitem);
#endif #endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -45,6 +45,17 @@ These dive into more detail for specialized topics or typical usage scenarios.
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)
Double or quadruple your data rate by sending multiple bits at the same time.
- [Going beyond 9600 baud](Going-beyond-9600-baud.pdf)
Why stop at 9600 baud? Go faster if your soundcard and radio can handle it.
## Miscellaneous ## ## Miscellaneous ##

Binary file not shown.

Binary file not shown.

Binary file not shown.

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.
*/ */

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
@ -76,6 +76,17 @@
#include "morse.h" #include "morse.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,7 +106,6 @@ static void send_packet (char *str)
int flen; int flen;
int c; int c;
if (g_morse_wpm > 0) { if (g_morse_wpm > 0) {
morse_send (0, str, g_morse_wpm, 100, 100); morse_send (0, str, g_morse_wpm, 100, 100);
@ -105,6 +115,31 @@ 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);
hdlc_send_flags (c, 2, 1); hdlc_send_flags (c, 2, 1);
@ -123,6 +158,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 +211,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;
@ -220,13 +257,38 @@ int main(int argc, char **argv)
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 (modem.achan[0].baud < 600) { if (modem.achan[0].baud < 600) {
modem.achan[0].mark_freq = 1600; 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;
} }
else if (modem.achan[0].baud <= 2400) { 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;
modem.achan[0].space_freq = DEFAULT_SPACE_FREQ;
}
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 { else {
modem.achan[0].modem_type = MODEM_SCRAMBLE; modem.achan[0].modem_type = MODEM_SCRAMBLE;
@ -353,6 +415,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. */
@ -390,7 +457,11 @@ 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); gen_tone_init (&modem, amplitude/2);
morse_init (&modem, amplitude/2); morse_init (&modem, amplitude/2);
@ -400,6 +471,84 @@ int main(int argc, char **argv)
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.
@ -467,17 +616,26 @@ 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);
}
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);
@ -512,7 +670,7 @@ 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);
@ -691,14 +849,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)
{ {

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, 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
@ -68,18 +68,40 @@ 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];
/* Accumulators. */ /* Accumulators. */
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];
static int prev_symbol[MAX_CHANS]; // Data is conveyed by phase relative to the
// previous symbol. So we need to keep it.
/* /*
* The K9NG/G3RUH output originally took a very simple and lazy approach. * The K9NG/G3RUH output originally took a very simple and lazy approach.
@ -187,19 +209,49 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp)
int a = ACHAN2ADEV(chan); int a = ACHAN2ADEV(chan);
tone_phase[chan] = 0;
bit_len_acc[chan] = 0;
lfsr[chan] = 0;
ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); // 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.
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); switch (save_audio_config_p->achan[chan].modem_type) {
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); case MODEM_QPSK:
tone_phase[chan] = 0; audio_config_p->achan[chan].mark_freq = 1800;
audio_config_p->achan[chan].space_freq = audio_config_p->achan[chan].mark_freq; // Not Used.
bit_len_acc[chan] = 0; // 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.
lfsr[chan] = 0; 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;
}
} }
} }
@ -300,14 +352,18 @@ 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->achan[chan].valid); assert (save_audio_config_p->achan[chan].valid);
@ -317,6 +373,70 @@ 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;
}
#define REV2 1
#if REV2
#else
tone_phase[chan] = PHASE_SHIFT_45;
if (bit_count[chan] & 2) {
tone_phase[chan] += (unsigned)PHASE_SHIFT_180;
}
#endif
// 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;
#if REV2
symbol = gray2phase_v26[dibit];
tone_phase[chan] += symbol * PHASE_SHIFT_90;
#else
symbol = (prev_symbol[chan] + gray2phase_v26[dibit]) & 0x3;
tone_phase[chan] += symbol * PHASE_SHIFT_90;
prev_symbol[chan] = symbol;
#endif
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;
#if 1
symbol = gray2phase_v27[tribit];
tone_phase[chan] += symbol * PHASE_SHIFT_45;
#else
symbol = (prev_symbol[chan] + gray2phase_v27[tribit]) & 0x7;
tone_phase[chan] = symbol * PHASE_SHIFT_45;
prev_symbol[chan] = symbol;
#endif
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;
@ -334,6 +454,14 @@ void tone_gen_put_bit (int chan, int dat)
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);
} }
else if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK ||
save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) {
int sam;
tone_phase[chan] += f1_change_per_sample[chan];
sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
gen_tone_put_sample (chan, a, sam);
}
else { else {
float fsam = dat ? amp16bit : (-amp16bit); float fsam = dat ? amp16bit : (-amp16bit);

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,6 +70,7 @@
* 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
@ -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,7 @@ 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, ""); dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, "");
return; return;
} }
@ -641,7 +649,7 @@ 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, 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),

114
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
@ -80,6 +80,8 @@
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
//#define DEBUG 1
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
@ -104,7 +106,10 @@
#include "recv.h" #include "recv.h"
#include "dtmf.h" #include "dtmf.h"
#include "aprs_tt.h" #include "aprs_tt.h"
#include "dtime_now.h"
#if NEW14
#include "ax25_link.h"
#endif
#if __WIN32__ #if __WIN32__
static unsigned __stdcall recv_adev_thread (void *arg); static unsigned __stdcall recv_adev_thread (void *arg);
@ -283,40 +288,101 @@ 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 #if NEW14
text_color_set(DW_COLOR_DEBUG); double timeout_value = ax25_link_get_next_timer_expiry();
dw_printf ("recv_process: woke up\n");
#endif
ok = dlq_remove (&type, &chan, &subchan, &slice, &pp, &alevel, &retries, spectrum, sizeof(spectrum));
timed_out = dlq_wait_while_empty (timeout_value);
#else
dlq_wait_while_empty (0.0);
timed_out = 0;
#endif
#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
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
#if NEW14
dl_timer_expiry ();
#endif #endif
if (ok) {
app_process_rec_packet (chan, subchan, slice, pp, alevel, retries, spectrum);
} }
#if DEBUG
else { else {
pitem = dlq_remove ();
#if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n"); dw_printf ("recv_process: dlq_remove() returned pitem=%p\n", pitem);
}
#endif #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);
pitem->pp = NULL; // Was consumed by above. Don't try to use or free again.
/*
* Link processing - Can ignore UI frames.
*/
// TODO - ax25_link_rec_frame & delete & remove delete from above.
break;
#if NEW14
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;
#endif
}
dlq_delete (pitem);
}
#if DEBUG
else {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("recv_process: spurious wakeup. (Temp debugging message - not a problem if only occasional.)\n");
}
#endif
}
} }
} /* end recv_process */ } /* end recv_process */

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
@ -135,6 +152,7 @@
#include "textcolor.h" #include "textcolor.h"
#include "audio.h" #include "audio.h"
#include "server.h" #include "server.h"
#include "dlq.h"
@ -1590,7 +1608,6 @@ 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_SOURCE]));
@ -1620,28 +1637,53 @@ static THREAD_F cmd_listen_thread (void *arg)
} }
} }
#if NEW14
dlq_connect_request (callsigns, num_calls, cmd.hdr.portx, client, pid);
#else
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\n"); dw_printf ("\n");
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client); 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"); dw_printf ("Connected packet mode is not implemented.\n");
#endif
} }
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]));
#if NEW14
dlq_xmit_data_request (callsigns, num_calls, cmd.hdr.portx, client, cmd.hdr.pid, cmd.data, netle2host(cmd.hdr.data_len_NETLE));
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client);
dw_printf ("Connected packet mode is not implemented.\n");
#endif
}
break; break;
case 'd': /* Disconnect, Terminate an AX.25 Connection */ case 'd': /* Disconnect, Terminate an AX.25 Connection */
{ {
char callsigns[2][AX25_MAX_ADDR_LEN];
const int num_calls = 2;
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
#if NEW14
dlq_disconnect_request (callsigns, num_calls, cmd.hdr.portx, client);
#else
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("\n"); dw_printf ("\n");
dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client); 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"); dw_printf ("Connected packet mode is not implemented.\n");
#endif
} }
break; break;

View File

@ -18,5 +18,9 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
int server_callsign_registered_by_client (char *callsign); int server_callsign_registered_by_client (char *callsign);
void server_link_established (int chan, int client, char *remote_call, char *own_call, int incoming);
void server_link_terminated (int chan, int client, char *remote_call, char *own_call, int timeout);
/* end server.h */ /* end server.h */

184
xid.c
View File

@ -23,13 +23,24 @@
* *
* Module: xid.c * Module: xid.c
* *
* Purpose: .... * Purpose: Encode and decode the info field of XID frames.
* *
* Description: * Description: If we originate the connection, and the other end is
* capable of AX.25 version 2.2,
* *
* References: ... * - We send an XID command frame with our capabilities.
* - the other sends back an XID response, possibly
* reducing some values to be acceptable there.
* - Both ends use the values in that response.
* *
* If the other end originates the connection,
* *
* - It sends XID command frame with its capabilities.
* - We might decrease some of them to be acceptable.
* - Send XID response.
* - Both ends use values in my response.
*
* References: AX.25 Protocol Spec, sections 4.3.3.7 & 6.3.2.
* *
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
@ -38,6 +49,7 @@
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include "direwolf.h"
#include "textcolor.h" #include "textcolor.h"
//#include "xid.h" //#include "xid.h"
@ -47,17 +59,17 @@ struct ax25_param_s {
int full_duplex; int full_duplex;
// Order is important because negotiation keeps the lower. // Order is important because negotiation keeps the lower.
enum rej_e {implicit_reject, selective_reject, selective_reject_reject } rej; enum rej_e {implicit_reject=1, selective_reject=2, selective_reject_reject=3 } rej;
enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo; enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo;
int i_field_length_rx; /* In bytes. XID has it in bits. */ int i_field_length_rx; /* In bytes. XID has it in bits. */
int window_size; int window_size_rx;
int ack_timer; /* "T1" in mSec. */ int ack_timer; /* "T1" in mSec. */
int retries; /* Inconsistently refered to as "N1" or "N2" */ int retries; /* "N1" */
}; };
@ -98,36 +110,54 @@ struct ax25_param_s {
* *
* Outputs: ... * Outputs: ...
* *
* Description: * Returns: 1 for mostly successful (with possible error messages), 0 for failure.
*
* Description: 6.3.2 "The receipt of an XID response from the other station
* establishes that both stations are using AX.25 version
* 2.2 or higher and enables the use of the segmenter/reassembler
* and selective reject."
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
//Returns: 1 for mostly successful (with possible error messages), 0 for failure.
int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
{ {
unsigned char *p; unsigned char *p;
int group_len; int group_len;
char stemp[64];
char debug_msg[256];
result->full_duplex = 0; result->full_duplex = 0;
// Default is implicit reject for pre version 2.2 but we wouldn't be here in that case.
result->rej = selective_reject; result->rej = selective_reject;
result->modulo = modulo_8; result->modulo = modulo_8;
result->i_field_length_rx = 256;
result->window_size = result->modulo == modulo_128 ? 32 : 4; result->i_field_length_rx = 256; // bytes here but converted to bits during encoding.
// Default is 4 for pre version 2.2 but we wouldn't be here in that case.
result->window_size_rx = result->modulo == modulo_128 ? 32 : 7;
result->ack_timer = 3000; result->ack_timer = 3000;
result->retries = 10; result->retries = 10;
p = info; p = info;
if (*p != FI_Format_Indicator) { if (*p != FI_Format_Indicator) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: First byte of info field should be Format Indicator, %d.\n", FI_Format_Indicator);
return 0; return 0;
} }
p++; p++;
if (*p != GI_Group_Identifier) { if (*p != GI_Group_Identifier) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Second byte of info field should be Group Indicator, %d.\n", GI_Group_Identifier);
return 0; return 0;
} }
p++; p++;
@ -141,6 +171,11 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
pind = *p++; pind = *p++;
plen = *p++; // should have sanity checking plen = *p++; // should have sanity checking
if (plen < 1 || plen > 4) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Length ????? TODO ???? %d.\n", plen);
return (1); // got this far.
}
pval = 0; pval = 0;
for (j=0; j<plen; j++) { for (j=0; j<plen; j++) {
pval = (pval << 8) + *p++; pval = (pval << 8) + *p++;
@ -223,21 +258,20 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
if (pval & 0x7) { if (pval & 0x7) {
text_color_set (DW_COLOR_ERROR); text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: I Field Length Rx is not a whole number of bytes.\n"); dw_printf ("XID error: I Field Length Rx, %d, is not a whole number of bytes.\n", pval);
} }
break; break;
case PI_Window_Size_Rx: case PI_Window_Size_Rx:
result->window_size = pval; result->window_size_rx = pval;
// TODO must be 1-7 for modulo 8 or 1-127 for modulo 128; if (pval < 1 || pval >= result->modulo) {
text_color_set (DW_COLOR_ERROR);
// if (pval & 0x7) { dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru %d.\n", pval, result->modulo-1);
// text_color_set (DW_COLOR_ERROR); result->window_size_rx = result->modulo == modulo_128 ? 32 : 7;
// dw_printf ("XID error: Window Size Rx is not in range of 1 thru ???\n"); }
// }
//continue here. //continue here.
@ -252,7 +286,7 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
break; break;
default: default:
break; break; // Ignore anything we don't recognize.
} }
} }
@ -261,7 +295,7 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
dw_printf ("XID error: Frame / Group Length mismatch.\n"); dw_printf ("XID error: Frame / Group Length mismatch.\n");
} }
return 1; return (1);
} /* end xid_parse */ } /* end xid_parse */
@ -270,16 +304,60 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result)
* *
* Name: xid_encode * Name: xid_encode
* *
* Purpose: ... * Purpose: Encode the information part of an XID frame.
* *
* Inputs: param - * Inputs: param->
* full_duplex - As command, am I capable of full duplex operation?
* When a response, are we both?
* 0 = half duplex.
* 1 = full duplex.
*
* rej - One of: implicit_reject, selective_reject, selective_reject_reject.
* As command, what am I capable of processing?
* As response, take minimum of
*
*
* modulo - 8 or 128.
*
* i_field_length_rx - Maximum number of bytes I can handle in info part.
* Default is 256.
* Up to 8191 will fit into the field.
* Use G_UNKNOWN to omit this.
*
* window_size_rx - Maximum window size ("k") that I can handle.
* Defaults are are 4 for modulo 8 and 32 for modulo 128.
*
* ack_timer - Acknowledge timer in milliseconds.
* *** describe meaning. ***
* Default is 3000.
* Use G_UNKNOWN to omit this.
*
* retries - Allows negotiation of retries.
* Default is 10.
* Use G_UNKNOWN to omit this.
* *
* Outputs: info - Information part of XID frame. * Outputs: info - Information part of XID frame.
* Does not include the control byte. * Does not include the control byte.
* Supply 32 bytes to be safe.
* *
* Returns: Number of bytes in the info part. * Returns: Number of bytes in the info part. Should be at most 27.
* *
* Description: * Description: 6.3.2 "Parameter negotiation occurs at any time. It is accomplished by sending
* the XID command frame and receiving the XID response frame. Implementations of
* AX.25 prior to version 2.2 respond to an XID command frame with a FRMR response
* frame. The TNC receiving the FRMR uses a default set of parameters compatible
* with previous versions of AX.25."
*
* "This version of AX.25 implements the negotiation or notification of six AX.25
* parameters. Notification simply tells the distant TNC some limit that cannot be exceeded.
* The distant TNC can choose to use the limit or some other value that is within the
* limits. Notification is used with the Window Size Receive (k) and Information
* Field Length Receive (N1) parameters. Negotiation involves both TNCs choosing a
* value that is mutually acceptable. The XID command frame contains a set of values
* acceptable to the originating TNC. The distant TNC chooses to accept the values
* offered, or other acceptable values, and places these values in the XID response.
* Both TNCs set themselves up based on the values used in the XID response. Negotiation
* is used by Classes of Procedures, HDLC Optional Functions, Acknowledge Timer and Retries."
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
@ -333,27 +411,35 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
*p++ = (x >> 8) & 0xff; *p++ = (x >> 8) & 0xff;
*p++ = x & 0xff; *p++ = x & 0xff;
*p++ = PI_I_Field_Length_Rx; if (param->i_field_length_rx != G_UNKNOWN) {
*p++ = 2; *p++ = PI_I_Field_Length_Rx;
x = param->i_field_length_rx * 8; *p++ = 2;
*p++ = (x >> 8) & 0xff; x = param->i_field_length_rx * 8;
*p++ = x & 0xff; *p++ = (x >> 8) & 0xff;
*p++ = x & 0xff;
}
*p++ = PI_Window_Size_Rx; if (param->window_size_rx != G_UNKNOWN) {
*p++ = 1; *p++ = PI_Window_Size_Rx;
*p++ = param->window_size; *p++ = 1;
*p++ = param->window_size_rx;
}
*p++ = PI_Ack_Timer; if (param->ack_timer != G_UNKNOWN) {
*p++ = 2; *p++ = PI_Ack_Timer;
*p++ = param->ack_timer >> 8; *p++ = 2;
*p++ = param->ack_timer & 0xff; *p++ = (param->ack_timer >> 8) & 0xff;
*p++ = param->ack_timer & 0xff;
}
*p++ = PI_Retries; if (param->retries != G_UNKNOWN) {
*p++ = 1; *p++ = PI_Retries;
*p++ = param->retries; *p++ = 1;
*p++ = param->retries;
}
len = p - info; len = p - info;
assert (len == 27); assert (len <= 27);
return (len); return (len);
} /* end xid_encode */ } /* end xid_encode */
@ -368,7 +454,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
* *
* Description: Run with: * Description: Run with:
* *
* gcc -DTEST -g xid.c textcolor.c ; ./a * gcc -DXIDTEST -g xid.c textcolor.o && ./a
* *
* Result should be: * Result should be:
* *
@ -379,7 +465,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info)
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
#if TEST #if XIDTEST
/* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */ /* From Figure 4.6. Typical XID frame, from AX.25 protocol spec, v. 2.2 */
/* This is the info part after a control byte of 0xAF. */ /* This is the info part after a control byte of 0xAF. */
@ -417,7 +503,7 @@ static unsigned char example[27] = {
/* PV */ 0x00, /* */ /* PV */ 0x00, /* */
/* PI */ 0x0A, /* Parameter Indicator - Retries (N1) */ /* PI */ 0x0A, /* Parameter Indicator - Retries (N1) */
/* PL */ 0x01, /* Parameter Length */ /* PL */ 0x01, /* Parameter Length */
/* PV */ 0x03 /* Parameter Variable - 3 ret */ /* PV */ 0x03 /* Parameter Variable - 3 retries */
}; };
int main (int argc, char *argv[]) { int main (int argc, char *argv[]) {
@ -438,7 +524,7 @@ int main (int argc, char *argv[]) {
assert (param.rej == selective_reject_reject); assert (param.rej == selective_reject_reject);
assert (param.modulo == modulo_128); assert (param.modulo == modulo_128);
assert (param.i_field_length_rx == 128); assert (param.i_field_length_rx == 128);
assert (param.window_size == 2); assert (param.window_size_rx == 2);
assert (param.ack_timer == 4096); assert (param.ack_timer == 4096);
assert (param.retries == 3); assert (param.retries == 3);
@ -461,7 +547,7 @@ int main (int argc, char *argv[]) {
param.rej = implicit_reject; param.rej = implicit_reject;
param.modulo = modulo_8; param.modulo = modulo_8;
param.i_field_length_rx = 2048; param.i_field_length_rx = 2048;
param.window_size = 3; param.window_size_rx = 3;
param.ack_timer = 3000; param.ack_timer = 3000;
param.retries = 10; param.retries = 10;
@ -472,7 +558,7 @@ int main (int argc, char *argv[]) {
assert (param2.rej == implicit_reject); assert (param2.rej == implicit_reject);
assert (param2.modulo == modulo_8); assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == 2048); assert (param2.i_field_length_rx == 2048);
assert (param2.window_size == 3); assert (param2.window_size_rx == 3);
assert (param2.ack_timer == 3000); assert (param2.ack_timer == 3000);
assert (param2.retries == 10); assert (param2.retries == 10);
@ -482,7 +568,7 @@ int main (int argc, char *argv[]) {
param.rej = selective_reject; param.rej = selective_reject;
param.modulo = modulo_8; param.modulo = modulo_8;
param.i_field_length_rx = 256; param.i_field_length_rx = 256;
param.window_size = 4; param.window_size_rx = 4;
param.ack_timer = 3000; param.ack_timer = 3000;
param.retries = 10; param.retries = 10;
@ -493,11 +579,11 @@ int main (int argc, char *argv[]) {
assert (param2.rej == selective_reject); assert (param2.rej == selective_reject);
assert (param2.modulo == modulo_8); assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == 256); assert (param2.i_field_length_rx == 256);
assert (param2.window_size == 4); assert (param2.window_size_rx == 4);
assert (param2.ack_timer == 3000); assert (param2.ack_timer == 3000);
assert (param2.retries == 10); assert (param2.retries == 10);
text_color_set (DW_COLOR_INFO); text_color_set (DW_COLOR_REC);
dw_printf ("XID test: Success.\n"); dw_printf ("XID test: Success.\n");
exit (0); exit (0);

19
xmit.c
View File

@ -603,6 +603,22 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
text_color_set(DW_COLOR_XMIT); text_color_set(DW_COLOR_XMIT);
dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L');
dw_printf ("%s", stemp); /* stations followed by : */ dw_printf ("%s", stemp); /* stations followed by : */
/* Demystify non-APRS. Use same format for received frames in direwolf.c. */
if ( ! ax25_is_aprs(pp)) {
ax25_frame_type_t ftype;
cmdres_t cr;
char desc[32];
int pf;
int nr;
int ns;
ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns);
dw_printf ("(%s)", desc);
}
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n"); dw_printf ("\n");
(void)ax25_check_addresses (pp); (void)ax25_check_addresses (pp);
@ -623,6 +639,9 @@ static void xmit_ax25_frames (int c, int p, packet_t pp)
*/ */
time_ptt = dtime_now (); time_ptt = dtime_now ();
// TODO: This was written assuming bits/sec = baud.
// Does it is need to be scaled differently for PSK?
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]); dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]);