diff --git a/CHANGES.md b/CHANGES.md index 3e64700..65279ef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,36 @@ # 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 ## @@ -61,7 +91,7 @@ such as PowerPC or MIPS. - Improved decoder performance. 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. @@ -272,8 +302,7 @@ to rebuild it from source. ### New Features: ### -- Added APRStt gateway capability. For details, see: -**APRStt-Implementation-Notes.pdf** +- Added APRStt gateway capability. For details, see ***APRStt-Implementation-Notes.pdf*** ----------- diff --git a/Makefile.linux b/Makefile.linux index db78d16..e32be77 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -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 \ 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 \ @@ -248,15 +248,18 @@ endif # Optimization for slow processors. -demod.o : fsk_fast_filter.h +demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h -fsk_fast_filter.h : demod_afsk.c - $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c $(LDFLAGS) +fsk_fast_filter.h : gen_fff ./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. @@ -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 -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 \ 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 \ @@ -585,15 +588,15 @@ install-rpi : dw-start.sh # 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? check-modem1200 : gen_packets atest - ./gen_packets -n 100 -o /tmp/test1.wav - ./atest -F0 -PE -L70 -G71 /tmp/test1.wav - ./atest -F1 -PE -L73 -G75 /tmp/test1.wav - #rm /tmp/test1.wav + ./gen_packets -n 100 -o /tmp/test12.wav + ./atest -F0 -PE -L63 -G71 /tmp/test12.wav + ./atest -F1 -PE -L70 -G75 /tmp/test12.wav + rm /tmp/test12.wav check-modem300 : gen_packets atest ./gen_packets -B300 -n 100 -o /tmp/test3.wav @@ -602,11 +605,28 @@ check-modem300 : gen_packets atest rm /tmp/test3.wav check-modem9600 : gen_packets atest - ./gen_packets -B9600 -n 100 -o /tmp/test9.wav - ./atest -B9600 -F0 -L57 -G59 /tmp/test9.wav - ./atest -B9600 -F1 -L66 -G67 /tmp/test9.wav - rm /tmp/test9.wav + ./gen_packets -B9600 -n 100 -o /tmp/test96.wav + ./atest -B9600 -F0 -L57 -G61 /tmp/test96.wav + ./atest -B9600 -F1 -L64 -G67 /tmp/test96.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 @@ -679,6 +699,14 @@ kisstest : kiss_frame.c ./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 --------------------------- @@ -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. # 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 $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) ./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. # Dependencies of demod*.c, rather than .o, are intentional. -demod.o : tune.h +demod.o : tune.h + demod_afsk.o : tune.h + demod_9600.o : tune.h -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ +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 $(CC) $(CFLAGS) -o atest $^ $(LDFLAGS) ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out @@ -757,8 +793,7 @@ dist-src : README.md CHANGES.md .PHONY: clean clean : - rm -f $(APPS) fsk_fast_filter.h *.o *.a direwolf.desktop - echo " " > tune.h + rm -f $(APPS) gen_fff tune.h fsk_fast_filter.h *.o *.a direwolf.desktop depend : $(wildcard *.c) diff --git a/Makefile.macosx b/Makefile.macosx index 12c94c1..ced3b20 100644 --- a/Makefile.macosx +++ b/Makefile.macosx @@ -24,8 +24,9 @@ # 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having # a hissy fit. Not check with GCC. -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc direwolf.conf - @echo " " +APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc + +all : $(APPS) direwolf.desktop direwolf.conf @echo " " @echo "Next step install with: " @echo " " @echo " sudo make install" @@ -221,7 +222,7 @@ z := $(notdir ${CURDIR}) # Main application. direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beacon.o \ - config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o \ + 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 \ 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 \ @@ -234,15 +235,20 @@ direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_pad.o beaco # Optimization for slow processors. -demod.o : fsk_fast_filter.h +demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h -fsk_fast_filter.h : demod_afsk.c - $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm +fsk_fast_filter.h : gen_fff ./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. 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. -# The Raspberry Pi has ~/Desktop but Ubuntu does not. - -# TODO: Handle Linux variations correctly. - .PHONY: install-conf install-conf : direwolf.conf @@ -406,7 +408,7 @@ install-conf : direwolf.conf # Separate application to decode raw data. -decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o +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 # 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 $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm -demod.o : tune.h +demod.o : tune.h + demod_afsk.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 \ fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c latlong.c symbols.c tune.h textcolor.c $(CC) $(CFLAGS) -o atest $^ -lm ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out -# Unit test for AFSK demodulator +# 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 $(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 # $(CC) $(CFLAGS) -o $@ $^ -lm @@ -513,7 +523,7 @@ depend : $(wildcard *.c) .PHONY: 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 echo " " > tune.h diff --git a/Makefile.win b/Makefile.win index d62e414..5007702 100644 --- a/Makefile.win +++ b/Makefile.win @@ -28,6 +28,9 @@ AR := ar CFLAGS += -g +# TODO: Development in progress. Don't try using yet. +#CFLAGS += -DNEW14 + # # Let's see impact of various optimization levels. # Benchmark results with MinGW gcc version 4.6.2. @@ -61,14 +64,22 @@ CFLAGS += -g # -------------------------------------- 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_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 \ - 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 \ 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 \ @@ -88,10 +99,13 @@ demod.o : fsk_fast_filter.h demod_afsk.o : fsk_fast_filter.h -fsk_fast_filter.h : demod_afsk.c - $(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c +fsk_fast_filter.h : gen_fff ./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. @@ -235,32 +249,59 @@ strlcat.o : misc/strlcat.c # 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? # Verify that single bit fixup increases the count. check-modem1200 : gen_packets atest - gen_packets -n 100 -o test1.wav - atest -F0 -PE -L70 -G71 test1.wav - atest -F1 -PE -L73 -G75 test1.wav - #rm test1.wav + gen_packets -n 100 -o test12.wav + atest -F0 -PE -L64 -G72 test12.wav + atest -F1 -PE -L70 -G75 test12.wav + rm test12.wav check-modem300 : gen_packets atest gen_packets -B300 -n 100 -o test3.wav atest -B300 -F0 -L68 -G69 test3.wav - atest -B300 -F1 -L73 -G75 test3.wav + atest -B300 -F1 -L71 -G75 test3.wav rm test3.wav check-modem9600 : gen_packets atest - gen_packets -B9600 -n 100 -o test9.wav - atest -B9600 -F0 -L57 -G59 test9.wav - atest -B9600 -F1 -L66 -G67 test9.wav - rm test9.wav + gen_packets -B9600 -n 100 -o test96.wav + sleep 1 + atest -B9600 -F0 -L57 -G62 test96.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 \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ 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 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 \ fsk_fast_filter.h echo " " > tune.h @@ -352,17 +393,26 @@ kisstest : kiss_frame.c 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 ------------------------------- # For tweaking the demodulator. -demod.o : tune.h +demod.o : tune.h demod_9600.o : tune.h demod_afsk.o : tune.h +demod_psk.o : tune.h - -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.o fsk_demod_agc.h \ +testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_agc.h \ hdlc_rec.o hdlc_rec2.o multi_modem.o \ rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \ dwgpsnmea.o dwgps.o serial_port.o tt_text.o regex.a misc.a @@ -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 ./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 \ tune.h - rm -f atest.exe - $(CC) $(CFLAGS) -o atest $^ - ./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out + rm -f atest3.exe + $(CC) $(CFLAGS) -o atest3 $^ + ./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out echo " " > tune.h noisy96.wav : gen_packets ./gen_packets -B 9600 -n 100 -o noisy96.wav -testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ +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 \ tune.h - rm -f atest.exe - $(CC) $(CFLAGS) -o atest $^ - ./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out - #./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out + rm -f atest9.exe + $(CC) $(CFLAGS) -o atest9 $^ + ./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out + #./atest9 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out 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 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 \ xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \ hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \ - multi_modem.o demod.o demod_afsk.o demod_9600.o rdq.o \ + 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 \ regex.a misc.a $(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32 diff --git a/aprs_tt.c b/aprs_tt.c index cf4e1c7..6642cef 100644 --- a/aprs_tt.c +++ b/aprs_tt.c @@ -1706,7 +1706,7 @@ static void raw_tt_data_to_app (int chan, char *msg) alevel.mark = -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 { text_color_set(DW_COLOR_ERROR); diff --git a/atest.c b/atest.c index 7ab60a8..e3d47b6 100644 --- a/atest.c +++ b/atest.c @@ -2,7 +2,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 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 // 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 */ /* 300 implies 1600/1800 AFSK. */ /* 1200 implies 1200/2200 AFSK. */ + /* 2400 implies V.26 */ /* 9600 implies scrambled. */ my_audio_config.achan[0].baud = atoi(optarg); dw_printf ("Data rate set to %d bits / second.\n", my_audio_config.achan[0].baud); - if (my_audio_config.achan[0].baud < 100 || my_audio_config.achan[0].baud > 10000) { + if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) { 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); } + + /* 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) { my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].mark_freq = 1600; my_audio_config.achan[0].space_freq = 1800; strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles)); } - else if (my_audio_config.achan[0].baud > 2400) { + 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].mark_freq = 0; my_audio_config.achan[0].space_freq = 0; strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); } - else { - my_audio_config.achan[0].modem_type = MODEM_AFSK; - my_audio_config.achan[0].mark_freq = 1200; - my_audio_config.achan[0].space_freq = 2200; - } break; case 'P': /* -P for modem profile. */ @@ -336,7 +356,7 @@ int main (int argc, char *argv[]) if (decimate < 1 || decimate > 8) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unreasonable value for -D.\n"); - exit (1); + exit (EXIT_FAILURE); } dw_printf ("Divide audio sample rate by %d\n", 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) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid Fix Bits level.\n"); - exit (1); + exit (EXIT_FAILURE); } break; @@ -407,7 +427,7 @@ int main (int argc, char *argv[]) text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for read: %s\n", argv[optind]); //perror ("more info?"); - exit (1); + exit (EXIT_FAILURE); } start_time = time(NULL); @@ -437,12 +457,12 @@ int main (int argc, char *argv[]) if (strncmp(chunk.id, "fmt ", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id); - exit(1); + exit(EXIT_FAILURE); } if (chunk.datasize != 16 && chunk.datasize != 18) { text_color_set(DW_COLOR_ERROR); dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18. Found %d.\n", chunk.datasize); - exit(1); + exit(EXIT_FAILURE); } 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) { text_color_set(DW_COLOR_ERROR); 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. - assert (format.nchannels == 1 || format.nchannels == 2); - assert (format.wbitspersample == 8 || format.wbitspersample == 16); + if (format.wformattag != 1) { + text_color_set(DW_COLOR_ERROR); + 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].bits_per_sample = format.wbitspersample; @@ -496,8 +530,10 @@ int main (int argc, char *argv[]) audio_sample = demod_get_sample (ACHAN2ADEV(c)); - if (audio_sample >= 256 * 256) + if (audio_sample >= 256 * 256) { e_o_f = 1; + continue; + } 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) { text_color_set(DW_COLOR_ERROR); 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) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than); - exit (1); + exit (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. */ -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]; @@ -740,7 +776,7 @@ static void usage (void) { dw_printf ("\n"); dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n"); dw_printf (" -1 use channel 1 (right) of stereo audio.\n"); - dw_printf (" -1 decode both channels of stereo audio.\n"); + dw_printf (" -2 decode both channels of stereo audio.\n"); dw_printf ("\n"); dw_printf (" wav-file-in is a WAV format audio file.\n"); dw_printf ("\n"); diff --git a/audio.h b/audio.h index 33a5d2d..746d96d 100644 --- a/audio.h +++ b/audio.h @@ -101,11 +101,12 @@ struct audio_s { /* 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. */ /* Baseband signal. Not used yet. */ /* 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. */ @@ -130,8 +131,9 @@ struct audio_s { int mark_freq; /* Two tones for AFSK modulation, in Hz. */ int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ - int baud; /* Data bits (more accurately, symbols) per second. */ + int baud; /* Data bits per second. */ /* 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. */ diff --git a/ax25_pad.c b/ax25_pad.c index 37a9508..90fba0b 100644 --- a/ax25_pad.c +++ b/ax25_pad.c @@ -891,7 +891,10 @@ packet_t ax25_unwrap_third_party (packet_t from_pp) (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); } @@ -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. * - * 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. * * Outputs: desc - Text description such as "I frame" or * "U frame SABME". * Supply 16 bytes to be safe. * + * cr - Command or response? + * * pf - P/F - Poll/Final 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. #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 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); strlcpy (desc, "????", DESC_SIZ); + *cr = cr_11; *pf = -1; *nr = -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); return (frame_not_AX25); } - if (modulo == modulo_128) { + if (this_p->modulo == modulo_128) { 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) { // Information rrr p sss 0 or sssssss 0 rrrrrrr p - if (modulo == modulo_128) { + if (this_p->modulo == modulo_128) { *ns = (c >> 1) & 0x7f; *pf = c2 & 1; *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; *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); } else if ((c & 2) == 0) { // 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; *nr = (c2 >> 1) & 0x7f; } @@ -1861,12 +1882,13 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * *pf = (c >> 4) & 1; *nr = (c >> 5) & 7; } - + + switch ((c >> 2) & 3) { - case 0: snprintf (desc, DESC_SIZ, "S frame RR, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_RR); break; - case 1: snprintf (desc, DESC_SIZ, "S frame RNR, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_RNR); break; - case 2: snprintf (desc, DESC_SIZ, "S frame REJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_REJ); break; - case 3: snprintf (desc, DESC_SIZ, "S frame SREJ, n(r)=%d, p/f=%d", *nr, *pf); return (frame_type_S_SREJ); break; + 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, "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, "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, "SREJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_SREJ); break; } } else { @@ -1877,16 +1899,16 @@ ax25_frame_type_t ax25_frame_type (packet_t this_p, ax25_modulo_t modulo, char * switch (c & 0xef) { - case 0x6f: snprintf (desc, DESC_SIZ, "U frame SABME, p=%d", *pf); return (frame_type_U_SABME); break; - case 0x2f: snprintf (desc, DESC_SIZ, "U frame SABM, p=%d", *pf); return (frame_type_U_SABM); break; - case 0x43: snprintf (desc, DESC_SIZ, "U frame DISC, p=%d", *pf); return (frame_type_U_DISC); break; - case 0x0f: snprintf (desc, DESC_SIZ, "U frame DM, f=%d", *pf); return (frame_type_U_DM); break; - case 0x63: snprintf (desc, DESC_SIZ, "U frame UA, f=%d", *pf); return (frame_type_U_UA); break; - case 0x87: snprintf (desc, DESC_SIZ, "U frame FRMR, f=%d", *pf); return (frame_type_U_FRMR); break; - case 0x03: snprintf (desc, DESC_SIZ, "U frame UI, pf=%d", *pf); return (frame_type_U_UI); break; - case 0xaf: snprintf (desc, DESC_SIZ, "U frame XID, pf=%d", *pf); return (frame_type_U_XID); break; - case 0xe3: snprintf (desc, DESC_SIZ, "U frame TEST, pf=%d", *pf); return (frame_type_U_TEST); break; - default: snprintf (desc, DESC_SIZ, "U frame ???"); return (frame_type_U); break; + 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, "SABM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABM); 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, "DM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DM); 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, "FRMR %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_FRMR); 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, "XID %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_XID); 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 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 */ + + /*------------------------------------------------------------------ * * 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); } - 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); } diff --git a/ax25_pad.h b/ax25_pad.h index 75ede36..a579a83 100644 --- a/ax25_pad.h +++ b/ax25_pad.h @@ -100,7 +100,8 @@ struct packet_s { * Changed to 1 when position has been used. * * 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. * @@ -109,6 +110,7 @@ struct packet_s { * 0 Usually 0 but 1 for last address. */ + #define SSID_H_MASK 0x80 #define SSID_H_SHIFT 7 @@ -123,6 +125,15 @@ struct packet_s { 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]; /* Raw frame contents, without the CRC. */ @@ -145,6 +156,7 @@ struct packet_s { 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. */ @@ -154,21 +166,17 @@ extern packet_t ax25_new (void); /* * APRS always has one control octet of 0x03 but the more * general AX.25 case is one or two control bytes depending on - * "modulo 128 operation" is in effect. Unfortunately, it seems - * this can be determined only by examining the XID frames and - * keeping this information for each connection. - * We can assume 1 for our current purposes. + * whether "modulo 128 operation" is in effect. */ static inline int ax25_get_control_offset (packet_t this_p) { - //return (0); return (this_p->num_addr*7); } static inline int ax25_get_num_control (packet_t this_p) { - return (1); // TODO: always be 1 for U frame. More complicated for I and S. + 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 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); diff --git a/ax25_pad2.c b/ax25_pad2.c new file mode 100644 index 0000000..60e0f1b --- /dev/null +++ b/ax25_pad2.c @@ -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 . +// + + + +/*------------------------------------------------------------------ + * + * 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 +#include +#include +#include +#include + + +#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 */ diff --git a/ax25_pad2.h b/ax25_pad2.h new file mode 100644 index 0000000..4860941 --- /dev/null +++ b/ax25_pad2.h @@ -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 */ + + diff --git a/beacon.c b/beacon.c index 89ef30d..9837676 100644 --- a/beacon.c +++ b/beacon.c @@ -914,7 +914,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) /* Simulated reception. */ memset (&alevel, 0xff, sizeof(alevel)); - dlq_append (DLQ_REC_FRAME, g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); + dlq_rec_frame (g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, ""); break; } } diff --git a/config.c b/config.c index 3e35590..a1bd076 100644 --- a/config.c +++ b/config.c @@ -1116,15 +1116,15 @@ void config_init (char *fname, struct audio_s *p_audio_config, n = atoi(t); if (n >= MIN_BAUD && n <= MAX_BAUD) { 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); - 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 { p_audio_config->achan[channel].baud = DEFAULT_BAUD; 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); } @@ -1132,20 +1132,33 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Set defaults based on speed. */ /* 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) { p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = 1600; p_audio_config->achan[channel].space_freq = 1800; } - else if (p_audio_config->achan[channel].baud > 2400) { - p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; + else if (p_audio_config->achan[channel].baud < 1800) { + 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].space_freq = 0; } else { - p_audio_config->achan[channel].modem_type = MODEM_AFSK; - p_audio_config->achan[channel].mark_freq = 1200; - p_audio_config->achan[channel].space_freq = 2200; + p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; + p_audio_config->achan[channel].mark_freq = 0; + p_audio_config->achan[channel].space_freq = 0; } /* Get mark frequency. */ diff --git a/decode_aprs.c b/decode_aprs.c index b259aa4..075fc72 100644 --- a/decode_aprs.c +++ b/decode_aprs.c @@ -166,7 +166,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int 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_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; - case ':': /* Message */ + case ':': /* Message: for one person, a group, or a bulletin. */ /* Directed Station Query */ + /* Telemetry metadata. */ aprs_message (A, pinfo, info_len, quiet); break; @@ -1409,22 +1410,45 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * * 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. * ilen - Information field length. * 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. * * It's a lot more complicated with different types of addressees * 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 colon; /* : */ 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. */ } *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; 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 (! quiet) { 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; } if (p->colon != ':') { if (! quiet) { 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; } @@ -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)); + /* * Special message formats contain telemetry metadata. * 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) { 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); } 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); + A->g_message_subtype = message_subtype_telem_unit; telemetry_unit_label_message (addressee, p->message+5); } else if (strncmp(p->message,"EQNS.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee); + A->g_message_subtype = message_subtype_telem_eqns; telemetry_coefficents_message (addressee, p->message+5, quiet); } else if (strncmp(p->message,"BITS.",5) == 0) { snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee); + A->g_message_subtype = message_subtype_telem_bits; 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] == '?') { 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); } + +/* 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 { - 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 () */ diff --git a/decode_aprs.h b/decode_aprs.h index a767ed2..cdbb167 100644 --- a/decode_aprs.h +++ b/decode_aprs.h @@ -26,7 +26,9 @@ typedef struct decode_aprs_s { 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 */ /* 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 */ /* 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_course; /* 0 = North, 90 = East, etc. */ diff --git a/demod.c b/demod.c index 320cde0..bdea694 100644 --- a/demod.c +++ b/demod.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 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 // 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 "demod_9600.h" #include "demod_afsk.h" +#include "demod_psk.h" @@ -225,6 +218,7 @@ int demod_init (struct audio_s *pa) num_letters = 1; } + assert (num_letters == strlen(just_letters)); /* @@ -403,7 +397,7 @@ int demod_init (struct audio_s *pa) 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. 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; } - /* 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->sluggish_decay = D->agc_slow_decay * 0.2; @@ -512,7 +506,7 @@ int demod_init (struct audio_s *pa) 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->sluggish_decay = D->agc_slow_decay * 0.2; @@ -521,6 +515,114 @@ int demod_init (struct audio_s *pa) } 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? case MODEM_BASEBAND: @@ -602,7 +704,7 @@ int demod_init (struct audio_s *pa) 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->sluggish_decay = D->agc_slow_decay * 0.2; @@ -728,17 +830,9 @@ __attribute__((hot)) void demod_process_sample (int chan, int subchan, int sam) { float fsam; - //float abs_fsam; int k; -#if DEBUG4 - static FILE *demod_log_fp = NULL; - static int seq = 0; /* for log file name */ -#endif - - //int j; - //int demod_data; struct demodulator_state_s *D; assert (chan >= 0 && chan < MAX_CHANS); @@ -803,7 +897,22 @@ void demod_process_sample (int chan, int subchan, int sam) } } 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; @@ -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.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 { diff --git a/demod_afsk.c b/demod_afsk.c index b769e21..dba8317 100644 --- a/demod_afsk.c +++ b/demod_afsk.c @@ -756,7 +756,7 @@ int main (void) emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table); emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table); - exit(0); + exit(EXIT_SUCCESS); } #endif diff --git a/demod_psk.c b/demod_psk.c new file mode 100644 index 0000000..d750ecc --- /dev/null +++ b/demod_psk.c @@ -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 . +// + + +//#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 +#include +#include +#include +#include +#include +#include +#include + +#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; jms_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 */ diff --git a/demod_psk.h b/demod_psk.h new file mode 100644 index 0000000..0f5830d --- /dev/null +++ b/demod_psk.h @@ -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); diff --git a/direwolf.c b/direwolf.c index 743db90..280acba 100644 --- a/direwolf.c +++ b/direwolf.c @@ -239,7 +239,7 @@ int main (int argc, char *argv[]) text_color_init(t_opt); text_color_set(DW_COLOR_INFO); //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "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); #if defined(ENABLE_GPSD) || defined(USE_HAMLIB) @@ -561,22 +561,43 @@ int main (int argc, char *argv[]) if (B_opt != 0) { 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) { 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].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].mark_freq = 0; audio_config.achan[0].space_freq = 0; } - else { - audio_config.achan[0].modem_type = MODEM_AFSK; - audio_config.achan[0].mark_freq = 1200; - audio_config.achan[0].space_freq = 2200; - } } audio_config.statistics_interval = a_opt; @@ -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); @@ -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 : */ +/* 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 other, probably want to restrict to ASCII only because we are // more likely to have compressed data than UTF-8 text. // TODO: Might want to use d_u_opt for transmitted frames too. + ax25_safe_print ((char *)pinfo, info_len, ( ! ax25_is_aprs(pp)) && ( ! d_u_opt) ); dw_printf ("\n"); @@ -1068,10 +1105,12 @@ static void usage (char **argv) dw_printf (" -r n Audio sample rate, per sec.\n"); dw_printf (" -n n Number of audio channels, 1 or 2.\n"); dw_printf (" -b n Bits per audio sample, 8 or 16.\n"); - dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 9600.\n"); - dw_printf (" If < 600, AFSK tones are set to 1600 & 1800.\n"); - dw_printf (" If > 2400, K9NG/G3RUH style encoding is used.\n"); - dw_printf (" Otherwise, AFSK tones are set to 1200 & 2200.\n"); + dw_printf (" -B n Data rate in bits/sec for channel 0. Standard values are 300, 1200, 2400, 4800, 9600.\n"); + dw_printf (" 300 bps defaults to AFSK tones of 1600 & 1800.\n"); + dw_printf (" 1200 bps uses AFSK tones of 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 Debug options:\n"); dw_printf (" a a = AGWPE network protocol client.\n"); diff --git a/dlq.c b/dlq.c index 06157c8..5f01ff9 100644 --- a/dlq.c +++ b/dlq.c @@ -1,8 +1,7 @@ - // // 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 // it under the terms of the GNU General Public License as published by @@ -25,13 +24,17 @@ * * 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. - * 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 * received frames from all channels and process them * 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 @@ -39,6 +42,10 @@ #include #include #include +#if __WIN32__ +#else +#include +#endif #include "direwolf.h" #include "ax25_pad.h" @@ -46,36 +53,11 @@ #include "audio.h" #include "dlq.h" #include "dedupe.h" +#include "dtime_now.h" /* The queue is a linked list of these. */ -struct dlq_item_s { - - struct dlq_item_s *nextp; /* Next item in queue. */ - - dlq_type_t type; /* Type of item. */ - /* Only received frames at this time. */ - - int chan; /* Radio channel of origin. */ - - int subchan; /* Winning "subchannel" when using multiple */ - /* decoders on one channel. */ - /* Special case, -1 means DTMF decoder. */ - /* Maybe we should have a different type in this case? */ - - int slice; /* Winning slicer. */ - - packet_t pp; /* Pointer to frame structure. */ - - alevel_t alevel; /* Audio level. */ - - retry_t retries; /* Effort expended to get a valid CRC. */ - - char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */ -}; - - static struct dlq_item_s *queue_head = NULL; /* Head of linked list for queue. */ #if __WIN32__ @@ -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 int recv_thread_is_waiting = 0; +static volatile int recv_thread_is_waiting = 0; #endif -static int dlq_is_empty (void); - 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) { - int c, p; - int err; + //int c, p; #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -140,6 +126,7 @@ void dlq_init (void) #if __WIN32__ InitializeCriticalSection (&dlq_cs); #else + int err; err = pthread_mutex_init (&wake_up_mutex, NULL); err = pthread_mutex_init (&dlq_mutex, NULL); if (err != 0) { @@ -192,17 +179,17 @@ void dlq_init (void) } /* 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. - * - * chan - Channel, 0 is first. + * Inputs: chan - Channel, 0 is first. * * subchan - Which modem caught it. * Special case -1 for APRStt gateway. @@ -224,38 +211,27 @@ void dlq_init (void) * spectrum - Display of how well multiple decoders did. * * - * Outputs: Information is appended to queue. - * - * Description: Add item to end of linked list. - * Signal the receive processing thread if the queue was formerly empty. - * * IMPORTANT! Don't make an further references to the packet object after * giving it to dlq_append. * *--------------------------------------------------------------------*/ -void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +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 *plast; - int err; - int queue_length = 0; + #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append (type=%d, chan=%d, pp=%p, ...)\n", type, chan, pp); + dw_printf ("dlq_rec_frame (chan=%d, pp=%p, ...)\n", chan, pp); #endif - if ( ! was_init) { - dlq_init (); - } - assert (chan >= 0 && chan < MAX_CHANS); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("INTERNAL ERROR: dlq_append NULL packet pointer. Please report this!\n"); + dw_printf ("INTERNAL ERROR: dlq_rec_frame NULL packet pointer. Please report this!\n"); return; } @@ -263,16 +239,17 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, if (ax25memdebug_get()) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append (type=%d, chan=%d.%d, seq=%d, ...)\n", type, chan, subchan, ax25memdebug_seq(pp)); + dw_printf ("dlq_rec_frame (chan=%d.%d, seq=%d, ...)\n", chan, subchan, ax25memdebug_seq(pp)); } #endif /* Allocate a new queue item. */ pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); + s_new_count++; pnew->nextp = NULL; - pnew->type = type; + pnew->type = DLQ_REC_FRAME; pnew->chan = chan; pnew->slice = slice; pnew->subchan = subchan; @@ -284,17 +261,56 @@ void dlq_append (dlq_type_t type, int chan, int subchan, int slice, packet_t pp, else 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 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 #if __WIN32__ EnterCriticalSection (&dlq_cs); #else + int err; err = pthread_mutex_lock (&dlq_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_append: pthread_mutex_lock err=%d", err); + dw_printf ("dlq append_to_queue: pthread_mutex_lock err=%d", err); perror (""); 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); if (err != 0) { 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 (""); exit (1); } #endif #if DEBUG1 text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_append: left critical section\n"); - dw_printf ("dlq_append (): about to wake up recv processing thread.\n"); + dw_printf ("dlq append_to_queue: left critical section\n"); + dw_printf ("dlq append_to_queue (): about to wake up recv processing thread.\n"); #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); if (err != 0) { 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 (""); 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); if (err != 0) { 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 (""); 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); if (err != 0) { 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 (""); exit (1); } } #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 * 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 text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty () \n"); + dw_printf ("dlq_wait_while_empty (%.3f)\n", timeout); #endif if ( ! was_init) { @@ -448,21 +646,34 @@ void dlq_wait_while_empty (void) if (queue_head == NULL) { + #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty (): prepare to SLEEP - about to call cond wait\n"); + dw_printf ("dlq_wait_while_empty (): prepare to SLEEP...\n"); #endif #if __WIN32__ - WaitForSingleObject (wake_up_event, INFINITE); + if (timeout != 0.0) { + + DWORD ms = (timeout - dtime_now()) * 1000; + if (ms <= 0) ms = 1; #if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty (): returned from wait\n"); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("WaitForSingleObject: timeout after %d ms\n", ms); #endif + if (WaitForSingleObject (wake_up_event, ms) == WAIT_TIMEOUT) { + timed_out_result = 1; + } + } + else { + WaitForSingleObject (wake_up_event, INFINITE); + } #else + int err; + err = pthread_mutex_lock (&wake_up_mutex); if (err != 0) { text_color_set(DW_COLOR_ERROR); @@ -472,20 +683,21 @@ void dlq_wait_while_empty (void) } recv_thread_is_waiting = 1; - err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); - recv_thread_is_waiting = 0; + if (timeout != 0.0) { + struct timespec abstime; -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("dlq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err); -#endif + abstime.tv_sec = (time_t)(long)timeout; + abstime.tv_nsec = (long)((timeout - (long)abstime.tv_sec) * 1000000000.0); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_wait_while_empty: pthread_cond_wait err=%d", err); - perror (""); - exit (1); + err = pthread_cond_timedwait (&wake_up_cond, &wake_up_mutex, &abstime); + if (err == ETIMEDOUT) { + timed_out_result = 1; + } } + else { + err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); + } + recv_thread_is_waiting = 0; err = pthread_mutex_unlock (&wake_up_mutex); if (err != 0) { @@ -494,17 +706,18 @@ void dlq_wait_while_empty (void) perror (""); exit (1); } - #endif } #if 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 + return (timed_out_result); + +} /* end dlq_wait_while_empty */ -} /*------------------------------------------------------------------- @@ -515,27 +728,17 @@ void dlq_wait_while_empty (void) * * Inputs: None. * - * Outputs: type - type of queue entry. - * - * chan - channel of received frame. - * subchan - which demodulator caught it. - * slice - which slicer caught it. - * - * pp - pointer to packet object when type is DLQ_REC_FRAME. - * Caller should destroy it with ax25_delete when finished with it. - * - * Returns: 1 for success. - * 0 if queue is empty. + * Returns: Pointer to a queue item. Caller is responsible for deleting it. + * NULL 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; - int result; - int err; + struct dlq_item_s *result = NULL; + //int err; #if DEBUG1 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__ EnterCriticalSection (&dlq_cs); #else + int err; + err = pthread_mutex_lock (&dlq_mutex); if (err != 0) { 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 - if (queue_head == NULL) { - - *type = -1; - *chan = -1; - *subchan = -1; - *slice = -1; - *pp = NULL; - - memset (alevel, 0xff, sizeof(*alevel)); - - *retries = -1; - strlcpy(spectrum, "", spectrumsize); - result = 0; - } - else { - - phead = queue_head; + if (queue_head != NULL) { + result = queue_head; queue_head = queue_head->nextp; - - *type = phead->type; - *chan = phead->chan; - *subchan = phead->subchan; - *slice = phead->slice; - *pp = phead->pp; - *alevel = phead->alevel; - *retries = phead->retries; - strlcpy (spectrum, phead->spectrum, spectrumsize); - result = 1; } #if __WIN32__ @@ -602,19 +782,22 @@ int dlq_remove (dlq_type_t *type, int *chan, int *subchan, int *slice, packet_t #if 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 #if AX25MEMDEBUG - if (ax25memdebug_get() && result) { + if (ax25memdebug_get() && result != NULL) { 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 - if (result) { - free (phead); - } 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 - * - * Returns: True if nothing in the queue. + * Inputs: pitem - Pointer to a queue item. * *--------------------------------------------------------------------*/ -#if 0 -static int dlq_is_empty (void) -{ - if (queue_head == NULL) { - return (1); - } - return (0); -} /* end dlq_is_empty */ -#endif +void dlq_delete (struct dlq_item_s *pitem) +{ + s_delete_count++; + if (pitem->pp != NULL) ax25_delete (pitem->pp) + free (pitem); + +} /* end dlq_delete */ + + /* end dlq.c */ diff --git a/dlq.h b/dlq.h index acfbce3..f4f0a90 100644 --- a/dlq.h +++ b/dlq.h @@ -12,17 +12,77 @@ #include "audio.h" -void dlq_init (void); /* Types of things that can be in queue. */ -typedef enum dlq_type_e {DLQ_REC_FRAME} dlq_type_t; +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 diff --git a/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf b/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf new file mode 100644 index 0000000..6fb09be Binary files /dev/null and b/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf differ diff --git a/doc/APRS-Telemetry-Toolkit.pdf b/doc/APRS-Telemetry-Toolkit.pdf index 68568bf..1fa97bc 100644 Binary files a/doc/APRS-Telemetry-Toolkit.pdf and b/doc/APRS-Telemetry-Toolkit.pdf differ diff --git a/doc/Going-beyond-9600-baud.pdf b/doc/Going-beyond-9600-baud.pdf new file mode 100644 index 0000000..ce3545f Binary files /dev/null and b/doc/Going-beyond-9600-baud.pdf differ diff --git a/doc/README.md b/doc/README.md index 67527f2..3355f64 100644 --- a/doc/README.md +++ b/doc/README.md @@ -45,6 +45,17 @@ These dive into more detail for specialized topics or typical usage scenarios. digital converter to a Raspberry Pi and transmitting 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 ## diff --git a/doc/Raspberry-Pi-APRS-Tracker.pdf b/doc/Raspberry-Pi-APRS-Tracker.pdf index 3cb84e2..0d61dab 100644 Binary files a/doc/Raspberry-Pi-APRS-Tracker.pdf and b/doc/Raspberry-Pi-APRS-Tracker.pdf differ diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf index dc6120f..61cecd6 100644 Binary files a/doc/Raspberry-Pi-APRS.pdf and b/doc/Raspberry-Pi-APRS.pdf differ diff --git a/doc/WA8LMF-TNC-Test-CD-Results.pdf b/doc/WA8LMF-TNC-Test-CD-Results.pdf index cf7c523..7f883ac 100644 Binary files a/doc/WA8LMF-TNC-Test-CD-Results.pdf and b/doc/WA8LMF-TNC-Test-CD-Results.pdf differ diff --git a/fsk_demod_state.h b/fsk_demod_state.h index 74a723c..85b970a 100644 --- a/fsk_demod_state.h +++ b/fsk_demod_state.h @@ -4,9 +4,11 @@ #include "rpack.h" +#include "audio.h" // for enum modem_t /* * 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. */ @@ -24,6 +26,7 @@ struct demodulator_state_s /* * These are set once during initialization. */ + enum modem_t modem_type; // MODEM_AFSK, MODEM_8PSK, etc. char profile; // 'A', 'B', etc. Upper case. // 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_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. */ + unsigned int lo_phase; /* Local oscillator for PSK. */ + + /* * Most recent raw audio samples, before/after prefiltering. */ diff --git a/gen_packets.c b/gen_packets.c index c042802..bee4de8 100644 --- a/gen_packets.c +++ b/gen_packets.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 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 // it under the terms of the GNU General Public License as published by @@ -76,6 +76,17 @@ #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 int audio_file_open (char *fname, struct audio_s *pa); static int audio_file_close (void); @@ -95,7 +106,6 @@ static void send_packet (char *str) int flen; int c; - if (g_morse_wpm > 0) { morse_send (0, str, g_morse_wpm, 100, 100); @@ -105,6 +115,31 @@ static void send_packet (char *str) flen = ax25_pack (pp, fbuf); for (c=0; c 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 %d - %d.\n", MIN_BAUD, MAX_BAUD); exit (EXIT_FAILURE); } @@ -215,18 +252,43 @@ int main(int argc, char **argv) text_color_set(DW_COLOR_INFO); dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud); if (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD) { - text_color_set(DW_COLOR_ERROR); + text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); 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) { - 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; } - else if (modem.achan[0].baud <= 2400) { - modem.achan[0].mark_freq = 1200; - modem.achan[0].space_freq = 2200; + else if (modem.achan[0].baud < 1800) { + modem.achan[0].modem_type = MODEM_AFSK; + 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 { modem.achan[0].modem_type = MODEM_SCRAMBLE; @@ -353,6 +415,11 @@ int main(int argc, char **argv) } break; + case 'X': + + experiment = 1; + break; + case '?': /* 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); 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].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. * "-n" option is ignored in this case. @@ -467,17 +616,26 @@ int main(int argc, char **argv) char stemp[80]; - if (modem.achan[0].modem_type == MODEM_SCRAMBLE) { - g_noise_level = 0.33 * (amplitude / 200.0) * ((float)i / packet_count); - } - else if (modem.achan[0].baud < 600) { - /* About 2/3 should be decoded properly. */ + if (modem.achan[0].baud < 600) { + /* e.g. 300 bps AFSK - About 2/3 should be decoded properly. */ g_noise_level = amplitude *.0048 * ((float)i / packet_count); } - else { - /* About 2/3 should be decoded properly. */ + else if (modem.achan[0].baud < 1800) { + /* e.g. 1200 bps AFSK - About 2/3 should be decoded properly. */ 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); @@ -512,7 +670,7 @@ static void usage (char **argv) dw_printf ("Options:\n"); dw_printf (" -a Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD); - dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 9600.\n"); + dw_printf (" -B 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 (" -m Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ); dw_printf (" -s 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) { diff --git a/gen_tone.c b/gen_tone.c index 5860911..d81a669 100644 --- a/gen_tone.c +++ b/gen_tone.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 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 // 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 f2_change_per_sample[MAX_CHANS]; + static short sine_table[256]; /* Accumulators. */ static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation. - // Upper bits are used as index into sine table. + // 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 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. @@ -187,19 +209,49 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp) 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_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; - - bit_len_acc[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. - lfsr[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. + + 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. * + * 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) { int a = ACHAN2ADEV(chan); /* device for channel. */ - assert (save_audio_config_p->achan[chan].valid); @@ -317,6 +373,70 @@ void tone_gen_put_bit (int chan, int dat) 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) { int x; @@ -334,6 +454,14 @@ void tone_gen_put_bit (int chan, int dat) sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; 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 { float fsam = dat ? amp16bit : (-amp16bit); diff --git a/multi_modem.c b/multi_modem.c index f9d57fd..e1d9f55 100644 --- a/multi_modem.c +++ b/multi_modem.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 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 // 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. * *------------------------------------------------------------------*/ + //#define DEBUG 1 #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]; @@ -154,7 +158,11 @@ void multi_modem_init (struct audio_s *pa) dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__); save_audio_config_p->achan[chan].baud = DEFAULT_BAUD; } - process_age[chan] = PROCESS_AFTER_BITS * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].baud; + 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; } } @@ -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 && 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; } @@ -641,7 +649,7 @@ static void pick_best_candidate (int chan) j = subchan_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].alevel, (int)(candidate[chan][j][k].retries), diff --git a/recv.c b/recv.c index a2dba4d..05c90b7 100644 --- a/recv.c +++ b/recv.c @@ -2,7 +2,7 @@ // // 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 // it under the terms of the GNU General Public License as published by @@ -80,6 +80,8 @@ * *---------------------------------------------------------------*/ +//#define DEBUG 1 + #include #include @@ -104,7 +106,10 @@ #include "recv.h" #include "dtmf.h" #include "aprs_tt.h" - +#include "dtime_now.h" +#if NEW14 +#include "ax25_link.h" +#endif #if __WIN32__ static unsigned __stdcall recv_adev_thread (void *arg); @@ -283,40 +288,101 @@ static void * recv_adev_thread (void *arg) void recv_process (void) { - int ok; - dlq_type_t type; - int chan; - int subchan; - int slice; - packet_t pp; - alevel_t alevel; - retry_t retries; - char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; + struct dlq_item_s *pitem; while (1) { - dlq_wait_while_empty (); + int timed_out; +#if NEW14 + double timeout_value = ax25_link_get_next_timer_expiry(); + + + timed_out = dlq_wait_while_empty (timeout_value); +#else + dlq_wait_while_empty (0.0); + timed_out = 0; +#endif + #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("recv_process: woke up\n"); + dw_printf ("recv_process: woke up, timed_out=%d\n", timed_out); #endif - ok = dlq_remove (&type, &chan, &subchan, &slice, &pp, &alevel, &retries, spectrum, sizeof(spectrum)); + if (timed_out) { #if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("recv_process: dlq_remove() returned ok=%d, type=%d, chan=%d, pp=%p\n", - ok, (int)type, chan, pp); + 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 - if (ok) { - app_process_rec_packet (chan, subchan, slice, pp, alevel, retries, spectrum); } -#if DEBUG else { + + pitem = dlq_remove (); + +#if 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 + + 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 */ diff --git a/server.c b/server.c index 13e7980..f4cf5d7 100644 --- a/server.c +++ b/server.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 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 // it under the terms of the GNU General Public License as published by @@ -58,6 +58,17 @@ * 'x' Unregister CallSign * * '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. * @@ -80,7 +91,13 @@ * (Enabled with 'm' command.) * * '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 @@ -135,6 +152,7 @@ #include "textcolor.h" #include "audio.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 pid = 0xf0; /* normal for AX.25 I frames. */ int j; - char stemp[256]; strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); @@ -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); 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; + case 'D': /* Send Connected Data */ - text_color_set(DW_COLOR_ERROR); - dw_printf ("\n"); - dw_printf ("Can't process command '%c' from AGW client app %d.\n", cmd.hdr.datakind, client); - dw_printf ("Connected packet mode is not implemented.\n"); + { + 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_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; 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); 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; diff --git a/server.h b/server.h index 94ad068..dda80f3 100644 --- a/server.h +++ b/server.h @@ -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); +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 */ diff --git a/xid.c b/xid.c index 7580c94..9db66f8 100644 --- a/xid.c +++ b/xid.c @@ -23,13 +23,24 @@ * * 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 #include +#include "direwolf.h" #include "textcolor.h" //#include "xid.h" @@ -47,17 +59,17 @@ struct ax25_param_s { int full_duplex; // Order is important because negotiation keeps the lower. - enum rej_e {implicit_reject, selective_reject, selective_reject_reject } rej; + enum rej_e {implicit_reject=1, selective_reject=2, selective_reject_reject=3 } rej; enum modulo_e {modulo_8 = 8, modulo_128 = 128} modulo; int i_field_length_rx; /* In bytes. XID has it in bits. */ - int window_size; + int window_size_rx; 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: ... * - * 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) { unsigned char *p; int group_len; + char stemp[64]; + char debug_msg[256]; 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->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->retries = 10; p = info; 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; } p++; 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; } p++; @@ -141,6 +171,11 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) pind = *p++; 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; for (j=0; jwindow_size = pval; + result->window_size_rx = pval; -// TODO must be 1-7 for modulo 8 or 1-127 for modulo 128; - -// if (pval & 0x7) { -// text_color_set (DW_COLOR_ERROR); -// dw_printf ("XID error: Window Size Rx is not in range of 1 thru ???\n"); -// } + if (pval < 1 || pval >= result->modulo) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("XID error: Window Size Rx, %d, is not in range of 1 thru %d.\n", pval, result->modulo-1); + result->window_size_rx = result->modulo == modulo_128 ? 32 : 7; + } //continue here. @@ -252,7 +286,7 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) break; 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"); } - return 1; + return (1); } /* end xid_parse */ @@ -270,16 +304,60 @@ int xid_parse (unsigned char *info, int info_len, struct ax25_param_s *result) * * 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. * 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 & 0xff; - *p++ = PI_I_Field_Length_Rx; - *p++ = 2; - x = param->i_field_length_rx * 8; - *p++ = (x >> 8) & 0xff; - *p++ = x & 0xff; + if (param->i_field_length_rx != G_UNKNOWN) { + *p++ = PI_I_Field_Length_Rx; + *p++ = 2; + x = param->i_field_length_rx * 8; + *p++ = (x >> 8) & 0xff; + *p++ = x & 0xff; + } - *p++ = PI_Window_Size_Rx; - *p++ = 1; - *p++ = param->window_size; + if (param->window_size_rx != G_UNKNOWN) { + *p++ = PI_Window_Size_Rx; + *p++ = 1; + *p++ = param->window_size_rx; + } - *p++ = PI_Ack_Timer; - *p++ = 2; - *p++ = param->ack_timer >> 8; - *p++ = param->ack_timer & 0xff; + if (param->ack_timer != G_UNKNOWN) { + *p++ = PI_Ack_Timer; + *p++ = 2; + *p++ = (param->ack_timer >> 8) & 0xff; + *p++ = param->ack_timer & 0xff; + } - *p++ = PI_Retries; - *p++ = 1; - *p++ = param->retries; + if (param->retries != G_UNKNOWN) { + *p++ = PI_Retries; + *p++ = 1; + *p++ = param->retries; + } len = p - info; - assert (len == 27); + assert (len <= 27); return (len); } /* end xid_encode */ @@ -368,7 +454,7 @@ int xid_encode (struct ax25_param_s *param, unsigned char *info) * * Description: Run with: * - * gcc -DTEST -g xid.c textcolor.c ; ./a + * gcc -DXIDTEST -g xid.c textcolor.o && ./a * * 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 */ /* This is the info part after a control byte of 0xAF. */ @@ -417,7 +503,7 @@ static unsigned char example[27] = { /* PV */ 0x00, /* */ /* PI */ 0x0A, /* Parameter Indicator - Retries (N1) */ /* PL */ 0x01, /* Parameter Length */ - /* PV */ 0x03 /* Parameter Variable - 3 ret */ + /* PV */ 0x03 /* Parameter Variable - 3 retries */ }; int main (int argc, char *argv[]) { @@ -438,7 +524,7 @@ int main (int argc, char *argv[]) { assert (param.rej == selective_reject_reject); assert (param.modulo == modulo_128); assert (param.i_field_length_rx == 128); - assert (param.window_size == 2); + assert (param.window_size_rx == 2); assert (param.ack_timer == 4096); assert (param.retries == 3); @@ -461,7 +547,7 @@ int main (int argc, char *argv[]) { param.rej = implicit_reject; param.modulo = modulo_8; param.i_field_length_rx = 2048; - param.window_size = 3; + param.window_size_rx = 3; param.ack_timer = 3000; param.retries = 10; @@ -472,7 +558,7 @@ int main (int argc, char *argv[]) { assert (param2.rej == implicit_reject); assert (param2.modulo == modulo_8); 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.retries == 10); @@ -482,7 +568,7 @@ int main (int argc, char *argv[]) { param.rej = selective_reject; param.modulo = modulo_8; param.i_field_length_rx = 256; - param.window_size = 4; + param.window_size_rx = 4; param.ack_timer = 3000; param.retries = 10; @@ -493,11 +579,11 @@ int main (int argc, char *argv[]) { assert (param2.rej == selective_reject); assert (param2.modulo == modulo_8); 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.retries == 10); - text_color_set (DW_COLOR_INFO); + text_color_set (DW_COLOR_REC); dw_printf ("XID test: Success.\n"); exit (0); diff --git a/xmit.c b/xmit.c index 21d4b67..b80d844 100644 --- a/xmit.c +++ b/xmit.c @@ -603,6 +603,22 @@ static void xmit_ax25_frames (int c, int p, packet_t pp) text_color_set(DW_COLOR_XMIT); dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); dw_printf ("%s", stemp); /* stations followed by : */ + +/* 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)); dw_printf ("\n"); (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 (); +// TODO: This was written assuming bits/sec = baud. +// Does it is need to be scaled differently for PSK? + #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]);