Release 1.5.

This commit is contained in:
wb2osz 2018-10-08 10:15:21 -04:00
commit c0abb4b216
103 changed files with 7335 additions and 1878 deletions

2
.gitignore vendored
View File

@ -105,3 +105,5 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
/use_this_sdk
*.dSYM

30
99-direwolf-cmedia.rules Normal file
View File

@ -0,0 +1,30 @@
# Normally, all of /dev/hidraw* are accessible only by root.
#
# $ ls -l /dev/hidraw*
# crw------- 1 root root 247, 0 Sep 24 09:40 /dev/hidraw0
#
# An ordinary user, trying to acccess it will be denied.
#
# Unnecessarily running applications as root is generally a bad idea because it makes it too easy
# to accidentally trash your system. We need to relax the restrictions so ordinary users can use these devices.
#
# If all went well with installation, the /etc/udev/rules.d directory should contain a file called
# 99-direwolf-cmedia.rules containing:
#
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", GROUP="audio", MODE="0660"
#
# I used the "audio" group, mimicking the permissions on the sound side of the device.
#
# $ ls -l /dev/snd/pcm*
# crw-rw----+ 1 root audio 116, 16 Sep 24 09:40 /dev/snd/pcmC0D0p
# crw-rw----+ 1 root audio 116, 17 Sep 24 09:40 /dev/snd/pcmC0D1p
#
# You should see something similar to this where someone in the "audio" group has read-write access.
#
# $ ls -l /dev/hidraw*
# crw-rw---- 1 root audio 247, 0 Oct 6 19:24 /dev/hidraw0
#
# Read the User Guide and run the "cm108" application for more information.
#

View File

@ -2,6 +2,64 @@
# Revision History #
## Version 1.5 -- September 2018 ##
### New Features: ###
- PTT using GPIO pin of CM108/CM119 (e.g. DMK URI, RB-USB RIM), Linux only.
- More efficient error recovery for AX.25 connected mode. Better generation and processing of REJ and SREJ to reduce unnecessary duplicate "**I**" frames.
- New configuration option, "**V20**", for listing stations known to not understand AX.25 v2.2. This will speed up connection by going right to SABM and not trying SABME first and failing.
- New "**NOXID**" configuration file option to avoid sending XID command to listed station(s). If other end is a partial v2.2 implementation, which recognizes SABME, but not XID, we would waste a lot of time resending XID many times before giving up. This is less drastic than the "**V20**" option which doesn't even attempt to use v2.2 with listed station(s).
- New application "**kissutil**" for troubleshooting a KISS TNC or interfacing to an application via files.
- KISS "Set Hardware" command to report transmit queue length.
- TCP KISS can now handle multiple concurrent applications.
- Linux can use serial port for KISS in addition to a pseudo terminal.
- decode_aprs utility can now accept KISS frames and AX.25 frames as series of two digit hexadecimal numbers.
- Full Duplex operation. (Put "FULLDUP ON" in channel section of configuration file.)
- Time slots for beaconing.
- Allow single log file with fixed name rather than starting a new one each day.
### Bugs Fixed: ###
- Possible crash when CDIGIPEAT did not include the optional alias.
- PACLEN configuration item no longer restricts length of received frames.
- Strange failures when trying to use multiple KISS client applications over TCP. Only Linux was affected.
- Under certain conditions, outgoing connected mode data would get stuck in a queue and not be transmitted. This could happen if client application sends a burst of data larger than the "window" size (MAXFRAME or EMAXFRAME option).
- Little typographical / spelling errors in messages.
### Documentation: ###
- New document ***Bluetooth-KISS-TNC.pdf*** explaining how to use KISS over Bluetooth.
- Updates describing cheap SDR frequency inaccuracy and how to compensate for it.
### Notes: ###
Windows binary distribution now uses gcc (MinGW) version 6.3.0.
----------
## Version 1.4 -- April 2017 ##

View File

@ -2,7 +2,9 @@
# Makefile for Linux version of Dire Wolf.
#
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil cm108
all : $(APPS) direwolf.desktop direwolf.conf
@echo " "
@ -208,7 +210,7 @@ endif
# If you compile with the RPi 2 specific options above and try to run it on the RPi
# model B (pre version 2), it will die with "illegal instruction."
#
# Dire Wolf is known to work on the BeagleBone, CubieBoard2, etc.
# Dire Wolf is known to work on the BeagleBone, CubieBoard2, CHIP, etc.
# The best compiler options will depend on the specific type of processor
# and the compiler target defaults.
#
@ -252,12 +254,22 @@ endif
# but the initial test doesn't look good. About 2.3 times as slow.
# If you want to use OSS (for FreeBSD, OpenBSD) instead of
# ALSA (for Linux), comment out (or remove) the two lines below.
# ALSA (for Linux), comment out (or remove) the line below.
# TODO: Can we automate this somehow?
alsa = 1
ifeq ($(wildcard /usr/include/pthread.h),)
$(error /usr/include/pthread.h does not exist. Install it with "sudo apt-get install libc6-dev" or "sudo yum install glibc-headers" )
endif
ifneq ($(alsa),)
CFLAGS += -DUSE_ALSA
LDFLAGS += -lasound
ifeq ($(wildcard /usr/include/alsa/asoundlib.h),)
$(error /usr/include/alsa/asoundlib.h does not exist. Install it with "sudo apt-get install libasound2-dev" or "sudo yum install alsa-lib-devel" )
endif
endif
# Enable GPS if header file is present.
@ -271,11 +283,32 @@ LDFLAGS += -lgps
endif
# Uncomment following lines to enable hamlib support.
# TODO: automate this too. See if hamlib has been installed.
# Enable hamlib support if header file is present.
#CFLAGS += -DUSE_HAMLIB
#LDFLAGS += -lhamlib
enable_hamlib := $(wildcard /usr/include/hamlib/rig.h /usr/local/include/hamlib/rig.h)
ifneq ($(enable_hamlib),)
CFLAGS += -DUSE_HAMLIB
LDFLAGS += -lhamlib
endif
# Should enabling of this feature be strongly encouraged or
# is it quite specialized and of interest to a small audience?
# If, for some reason, can obtain the libudev-dev package, or
# don't want to install it, comment out the next 3 lines.
#ifeq ($(wildcard /usr/include/libudev.h),)
#$(error /usr/include/libudev.h does not exist. Install it with "sudo apt-get install libudev-dev" or "sudo yum install libudev-devel" )
#endif
# Enable cm108 PTT support if libudev header file is present.
enable_cm108 := $(wildcard /usr/include/libudev.h)
ifneq ($(enable_cm108),)
CFLAGS += -DUSE_CM108
LDFLAGS += -ludev
endif
# Name of current directory.
@ -292,20 +325,30 @@ z := $(notdir ${CURDIR})
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 rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
decode_aprs.o symbols.o server.o kiss.o kissserial.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
gen_tone.o audio.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \
ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o \
dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o cm108.o \
misc.a geotranz.a
$(CC) -o $@ $^ $(LDFLAGS)
@echo " "
ifneq ($(enable_gpsd),)
@echo " "
@echo "This includes support for gpsd."
@echo "\t>\tThis includes support for gpsd."
else
@echo " "
@echo "This does NOT include support for gpsd."
@echo "\t>\tThis does NOT include support for gpsd."
endif
ifneq ($(enable_hamlib),)
@echo "\t>\tThis includes support for hamlib."
else
@echo "\t>\tThis does NOT include support for hamlib."
endif
ifneq ($(enable_cm108),)
@echo "\t>\tThis includes support for CM108/CM119 PTT."
else
@echo "\t>\tThis does NOT include support for CM108/CM119 PTT."
endif
@echo " "
# Optimization for slow processors.
@ -355,7 +398,9 @@ tocalls-symbols :
# Separate application to decode raw data.
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 misc.a
# First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a
$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS)
@ -405,6 +450,19 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a
$(CC) $(CFLAGS) -g -o $@ $^
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o misc.a
$(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^ $(LDFLAGS)
# List USB audio adapters than can use GPIO for PTT.
cm108 : cm108.c textcolor.o misc.a
$(CC) $(CFLAGS) -g -DCM108_MAIN -o $@ $^ $(LDFLAGS)
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a
@ -474,17 +532,17 @@ direwolf.conf : generic.conf
# Where should we install it?
# My understanding, of the convention, is that something you compile
# from source, that is not a standard part of the operating system,
# should go in /usr/local/bin.
# Something built from source and installed locally would normally go in /usr/local/...
# If not specified on the make command line, this is our default.
# However, if you are preparing a "binary" DEB or RPM package, the
# installation location should be /usr/bin.
DESTDIR ?= /usr/local
# However, if you are preparing a "binary" DEB or RPM package, the installation location
# would normally be /usr/... instead. In this case, use a command line like this:
#
# make DESTDIR=/usr install
# This is a step in the right direction but not sufficient to use /usr instead.
# Eventually I'd like to have targets here to build the .DEB and .RPM packages.
INSTALLDIR := /usr/local
# Command to "install" to system directories. Use "ginstall" for Mac.
@ -502,22 +560,22 @@ direwolf.desktop :
@echo '[Desktop Entry]' > $@
@echo 'Type=Application' >> $@
ifneq ($(wildcard /usr/bin/lxterminal),)
@echo "Exec=lxterminal -t \"Dire Wolf\" -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@
@echo "Exec=lxterminal -t \"Dire Wolf\" -e \"$(DESTDIR)/bin/direwolf\"" >> $@
else ifneq ($(wildcard /usr/bin/lxterm),)
@echo "Exec=lxterm -hold -title \"Dire Wolf\" -bg white -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@
@echo "Exec=lxterm -hold -title \"Dire Wolf\" -bg white -e \"$(DESTDIR)/bin/direwolf\"" >> $@
else
@echo "Exec=xterm -hold -title \"Dire Wolf\" -bg white -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@
@echo "Exec=xterm -hold -title \"Dire Wolf\" -bg white -e \"$(DESTDIR)/bin/direwolf\"" >> $@
endif
@echo 'Name=Dire Wolf' >> $@
@echo 'Comment=APRS Soundcard TNC' >> $@
@echo 'Icon=/usr/share/direwolf/dw-icon.png' >> $@
@echo 'Icon=$(DESTDIR)/share/direwolf/pixmaps/dw-icon.png' >> $@
@echo "Path=$(HOME)" >> $@
@echo '#Terminal=true' >> $@
@echo 'Categories=HamRadio' >> $@
@echo 'Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25' >> $@
# Installation into /usr/local/...
# Installation into $(DESTDIR), usually /usr/local/... or /usr/...
# Needs to be run as root or with sudo.
@ -527,83 +585,102 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
# Applications, not installed with package manager, normally go in /usr/local/bin.
# /usr/bin is used instead when installing from .DEB or .RPM package.
#
$(INSTALL) direwolf $(INSTALLDIR)/bin
$(INSTALL) decode_aprs $(INSTALLDIR)/bin
$(INSTALL) text2tt $(INSTALLDIR)/bin
$(INSTALL) tt2text $(INSTALLDIR)/bin
$(INSTALL) ll2utm $(INSTALLDIR)/bin
$(INSTALL) utm2ll $(INSTALLDIR)/bin
$(INSTALL) aclients $(INSTALLDIR)/bin
$(INSTALL) log2gpx $(INSTALLDIR)/bin
$(INSTALL) gen_packets $(INSTALLDIR)/bin
$(INSTALL) atest $(INSTALLDIR)/bin
$(INSTALL) ttcalc $(INSTALLDIR)/bin
$(INSTALL) dwespeak.sh $(INSTALLDIR)/bin
$(INSTALL) -D --mode=755 direwolf $(DESTDIR)/bin/direwolf
$(INSTALL) -D --mode=755 decode_aprs $(DESTDIR)/bin/decode_aprs
$(INSTALL) -D --mode=755 text2tt $(DESTDIR)/bin/text2tt
$(INSTALL) -D --mode=755 tt2text $(DESTDIR)/bin/tt2text
$(INSTALL) -D --mode=755 ll2utm $(DESTDIR)/bin/ll2utm
$(INSTALL) -D --mode=755 utm2ll $(DESTDIR)/bin/utm2ll
$(INSTALL) -D --mode=755 aclients $(DESTDIR)/bin/aclients
$(INSTALL) -D --mode=755 log2gpx $(DESTDIR)/bin/log2gpx
$(INSTALL) -D --mode=755 gen_packets $(DESTDIR)/bin/gen_packets
$(INSTALL) -D --mode=755 atest $(DESTDIR)/bin/atest
$(INSTALL) -D --mode=755 ttcalc $(DESTDIR)/bin/ttcalc
$(INSTALL) -D --mode=755 kissutil $(DESTDIR)/bin/kissutil
$(INSTALL) -D --mode=755 cm108 $(DESTDIR)/bin/cm108
$(INSTALL) -D --mode=755 dwespeak.sh $(DESTDIR)/bin/dwspeak.sh
#
# Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory.
#
$(INSTALL) telemetry-toolkit/telem-balloon.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-bits.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-data.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-data91.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-eqns.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-parm.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-seq.sh $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-unit.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-volts.py $(INSTALLDIR)/bin
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-balloon.pl $(DESTDIR)/bin/telem-balloon.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-bits.pl $(DESTDIR)/bin/telem-bits.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-data.pl $(DESTDIR)/bin/telem-data.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-data91.pl $(DESTDIR)/bin/telem-data91.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-eqns.pl $(DESTDIR)/bin/telem-eqns.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-parm.pl $(DESTDIR)/bin/telem-parm.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-seq.sh $(DESTDIR)/bin/telem-seq.sh
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-unit.pl $(DESTDIR)/bin/telem-unit.pl
$(INSTALL) -D --mode=755 telemetry-toolkit/telem-volts.py $(DESTDIR)/bin/telem-volts.py
#
# Misc. data such as "tocall" to system mapping.
#
$(INSTALL) -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt
$(INSTALL) -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt
$(INSTALL) -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt
$(INSTALL) -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png
$(INSTALL) -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop
$(INSTALL) -D --mode=644 tocalls.txt $(DESTDIR)/share/direwolf/tocalls.txt
$(INSTALL) -D --mode=644 symbols-new.txt $(DESTDIR)/share/direwolf/symbols-new.txt
$(INSTALL) -D --mode=644 symbolsX.txt $(DESTDIR)/share/direwolf/symbolsX.txt
#
# For desktop icon.
#
$(INSTALL) -D --mode=644 dw-icon.png $(DESTDIR)/share/direwolf/pixmaps/dw-icon.png
$(INSTALL) -D --mode=644 direwolf.desktop $(DESTDIR)/share/applications/direwolf.desktop
#
# Documentation. Various plain text files and PDF.
#
$(INSTALL) -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md
$(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
$(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt
$(INSTALL) -D --mode=644 CHANGES.md $(DESTDIR)/share/doc/direwolf/CHANGES.md
$(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(DESTDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
$(INSTALL) -D --mode=644 LICENSE-other.txt $(DESTDIR)/share/doc/direwolf/LICENSE-other.txt
#
# ./README.md is an overview for the project main page.
# Maybe we could stick it in some other place.
# doc/README.md contains an overview of the PDF file contents and is more useful here.
#
$(INSTALL) -D --mode=644 doc/README.md $(INSTALLDIR)/share/doc/direwolf/README.md
$(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf
$(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
$(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf
$(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
$(INSTALL) -D --mode=644 doc/README.md $(DESTDIR)/share/doc/direwolf/README.md
$(INSTALL) -D --mode=644 doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(DESTDIR)/share/doc/direwolf/2400-4800-PSK-for-APRS-Packet-Radio.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(DESTDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(DESTDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
$(INSTALL) -D --mode=644 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(DESTDIR)/share/doc/direwolf/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf
$(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(DESTDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf
$(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(DESTDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
$(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(DESTDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf
$(INSTALL) -D --mode=644 doc/APRStt-Listening-Example.pdf $(DESTDIR)/share/doc/direwolf/APRStt-Listening-Example.pdf
$(INSTALL) -D --mode=644 doc/Bluetooth-KISS-TNC.pdf $(DESTDIR)/share/doc/direwolf/Bluetooth-KISS-TNC.pdf
$(INSTALL) -D --mode=644 doc/Going-beyond-9600-baud.pdf $(DESTDIR)/share/doc/direwolf/Going-beyond-9600-baud.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf
$(INSTALL) -D --mode=644 doc/Successful-APRS-IGate-Operation.pdf $(DESTDIR)/share/doc/direwolf/Successful-APRS-IGate-Operation.pdf
$(INSTALL) -D --mode=644 doc/User-Guide.pdf $(DESTDIR)/share/doc/direwolf/User-Guide.pdf
$(INSTALL) -D --mode=644 doc/WA8LMF-TNC-Test-CD-Results.pdf $(DESTDIR)/share/doc/direwolf/WA8LMF-TNC-Test-CD-Results.pdf
#
# Various sample config and other files go into examples under the doc directory.
# When building from source, these can be put in home directory with "make install-conf".
# When installed from .DEB or .RPM package, the user will need to copy these to
# the home directory or other desired location.
#
$(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/examples/direwolf.conf
$(INSTALL) -D --mode=755 dw-start.sh $(INSTALLDIR)/share/doc/direwolf/examples/dw-start.sh
$(INSTALL) -D --mode=644 sdr.conf $(INSTALLDIR)/share/doc/direwolf/examples/sdr.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-balloon.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(INSTALLDIR)/share/doc/direwolf/examples/telem-volts.conf
$(INSTALL) -D --mode=644 direwolf.conf $(DESTDIR)/share/doc/direwolf/examples/direwolf.conf
$(INSTALL) -D --mode=755 dw-start.sh $(DESTDIR)/share/doc/direwolf/examples/dw-start.sh
$(INSTALL) -D --mode=644 sdr.conf $(DESTDIR)/share/doc/direwolf/examples/sdr.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(DESTDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(DESTDIR)/share/doc/direwolf/examples/telem-balloon.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(DESTDIR)/share/doc/direwolf/examples/telem-volts.conf
#
# "man" pages
#
$(INSTALL) -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1
$(INSTALL) -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1
$(INSTALL) -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1
$(INSTALL) -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1
$(INSTALL) -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1
$(INSTALL) -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1
$(INSTALL) -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1
$(INSTALL) -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1
$(INSTALL) -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1
$(INSTALL) -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1
$(INSTALL) -D --mode=644 man1/aclients.1 $(DESTDIR)/share/man/man1/aclients.1
$(INSTALL) -D --mode=644 man1/atest.1 $(DESTDIR)/share/man/man1/atest.1
$(INSTALL) -D --mode=644 man1/decode_aprs.1 $(DESTDIR)/share/man/man1/decode_aprs.1
$(INSTALL) -D --mode=644 man1/direwolf.1 $(DESTDIR)/share/man/man1/direwolf.1
$(INSTALL) -D --mode=644 man1/gen_packets.1 $(DESTDIR)/share/man/man1/gen_packets.1
$(INSTALL) -D --mode=644 man1/kissutil.1 $(DESTDIR)/share/man/man1/kissutil.1
$(INSTALL) -D --mode=644 man1/ll2utm.1 $(DESTDIR)/share/man/man1/ll2utm.1
$(INSTALL) -D --mode=644 man1/log2gpx.1 $(DESTDIR)/share/man/man1/log2gpx.1
$(INSTALL) -D --mode=644 man1/text2tt.1 $(DESTDIR)/share/man/man1/text2tt.1
$(INSTALL) -D --mode=644 man1/tt2text.1 $(DESTDIR)/share/man/man1/tt2text.1
$(INSTALL) -D --mode=644 man1/utm2ll.1 $(DESTDIR)/share/man/man1/utm2ll.1
#
# Set group and mode of HID devices corresponding to C-Media USB Audio adapters.
# This will allow us to use the CM108/CM119 GPIO pins for PTT.
#
$(INSTALL) -D --mode=644 99-direwolf-cmedia.rules /etc/udev/rules.d/99-direwolf-cmedia.rules
#
@echo " "
@echo "If this is your first install, not an upgrade, type this to put a copy"
@ -613,8 +690,10 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
@echo " "
# Put sample configuration files in home directory.
# These would be done as ordinary user.
# Put sample configuration & startup files in home directory.
# This step would be done as ordinary user.
# Some people like to put the direwolf config file in /etc/ax25.
# Note that all of these are also in $(DESTDIR)/share/doc/direwolf/examples/.
# The Raspberry Pi has ~/Desktop but Ubuntu does not.
@ -622,6 +701,8 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
# Version 1.4 - Add "-n" option to avoid clobbering existing, probably customized, config files.
# dw-start.sh is greatly improved in version 1.4.
# It was moved from isntall-rpi to install-conf because it is not just for the RPi.
.PHONY: install-conf
install-conf : direwolf.conf
@ -629,23 +710,21 @@ install-conf : direwolf.conf
cp -n sdr.conf ~
cp -n telemetry-toolkit/telem-m0xer-3.txt ~
cp -n telemetry-toolkit/telem-*.conf ~
chmod +x dw-start.sh
cp -n dw-start.sh ~
ifneq ($(wildcard $(HOME)/Desktop),)
@echo " "
@echo "This will add a desktop icon on some systems:"
@echo "This will add a desktop icon on some systems."
@echo "This is known to work on Raspberry Pi but might not be compatible with other desktops."
@echo " "
@echo " make install-rpi"
@echo " "
endif
# dw-start.sh is greatly improved in version 1.4.
# It should probably be part of install-conf because it is not just for the RPi.
.PHONY: install-rpi
install-rpi : dw-start.sh
chmod +x dw-start.sh
cp -n dw-start.sh ~
ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop
install-rpi :
ln -f -s $(DESTDIR)/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop

View File

@ -24,9 +24,10 @@
# 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having
# a hissy fit. Not check with GCC.
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil
all : $(APPS) direwolf.desktop direwolf.conf @echo " "
all : $(APPS) direwolf.conf
@echo " "
@echo "Next step install with: "
@echo " "
@echo " sudo make install"
@ -35,27 +36,6 @@ all : $(APPS) direwolf.desktop direwolf.conf @echo " "
SYS_LIBS :=
SYS_MIN :=
#SDK := $(shell find /Developer -maxdepth 1 -type d -name "SDKs")
#$(info $$SDK = ${SDK})
#ifeq (${SDK},/Developer/SDKs)
# SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.8.sdk")
# ifeq (${SDK},/Developer/SDKs/MacOSX10.8.sdk)
# SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.8.sdk
# SYS_MIN := -mmacosx-version-min=10.8
# else
# SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.9.sdk")
# ifeq (${SDK},/Developer/SDKs/MacOSX10.9.sdk)
# SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.9.sdk
# SYS_MIN := -mmacosx-version-min=10.9
# else
# SDK := $(shell find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.10.sdk")
# ifeq (${SDK},/Developer/SDKs/MacOSX10.10.sdk)
# SYS_LIBS := -isystem /Developer/SDKs/MacOSX10.10.sdk
# SYS_MIN := -mmacosx-version-min=10.10
# endif
# endif
# endif
#endif
SYS_LIBS := $(shell ./search_sdks.sh)
EXTRA_CFLAGS :=
@ -69,8 +49,12 @@ endif
# Change as required in support of the available libraries
#CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN)
UNAME_M := $(shell uname -m)
ifeq (${UNAME_M},x86_64)
CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN)
else
CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN)
endif
# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24.
# Explanation here: https://github.com/wb2osz/direwolf/issues/62
@ -85,86 +69,14 @@ CFLAGS += -D_BSD_SOURCE
# $(info $$CC is [${CC}])
#
# The DSP filters spend a lot of time spinning around in little
# loops multiplying and adding arrays of numbers. The Intel "SSE"
# instructions, introduced in 1999 with the Pentium III series,
# can speed this up considerably.
#
# SSE2 instructions, added in 2000, don't seem to offer any advantage.
#
#
# Let's take a look at the effect of the compile options.
#
#
# Times are elapsed time to process Track 2 of the TNC test CD.
#
# i.e. "./atest 02_Track_2.wav"
# Default demodulator type is new "E" added for version 1.2.
#
#
# ---------- x86 (32 bit) ----------
#
#
# gcc 4.6.3 running on Ubuntu 12.04.05.
# Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions.
# Probably from around 2004 or 2005.
#
# When gcc is generating code for a 32 bit x86 target, it assumes the ancient
# i386 processor. This is good for portability but bad for performance.
#
# The code can run considerably faster by taking advantage of the SSE instructions
# available in the Pentium 3 or later.
#
# seconds options comments
# ------ ------- --------
# 524
# 183 -O2
# 182 -O3
# 183 -O3 -ffast-math (should be same as -Ofast)
# 184 -Ofast
# 189 -O3 -ffast-math -march=pentium
# 122 -O3 -ffast-math -msse
# 122 -O3 -ffast-math -march=pentium -msse
# 121 -O3 -ffast-math -march=pentium3 (this implies -msse)
# 120 -O3 -ffast-math -march=native
#
# Note that "-march=native" is essentially the same as "-march=pentium3."
#
# If the compiler is generating code for the i386 target, we can
# get much better results by telling it we have at least a Pentium 3.
# If the compiler is generating code for a 32 bit target (-m32), we can
# get much better results by telling it we have at least a Pentium 3
# which hass the SSE instructions.
CFLAGS += -march=core2 -msse4.1 -std=gnu99
#CFLAGS += -march=pentium3 -sse
#
# gcc 4.8.2 running on Ubuntu 14.04.1.
# Intel Core 2 Duo from around 2007 or 2008.
#
# 64 bit target implies that we have SSE and probably even better vector instructions.
#
# seconds options comments
# ------ ------- --------
# 245
# 75 -01
# 72 -02
# 71 -03
# 73 -O3 -march=native
# 42 -O3 -ffast-math
# 42 -Ofast (note below)
# 40 -O3 -ffast-math -march=native
#
#
# Note that "-Ofast" is a newer option roughly equivalent to "-O3 -ffast-math".
# I use the longer form because it is compatible with older compilers.
#
# Why don't I don't have "-march=native?"
# Older compilers don't recognize "native" as one of the valid options.
# One article said it was added with gcc 4.2 but I haven't verified that.
#
# Add -ffastmath in only if compiler version recognizes it.
@ -173,22 +85,6 @@ ifneq ($(useffast),)
CFLAGS += -ffast-math
endif
#
# You would expect "-march=native" to produce the fastest code.
# Why don't I use it here?
#
# 1. In my benchmarks, above, it has a negligible impact if any at all.
# 2. Some older versions of gcc don't recognize "native" as a valid choice.
# 3. Results are less portable. Not a consideration if you are
# building only for your own use but very important for anyone
# redistributing a "binary" version.
#
# If you are planning to distribute the binary version to other
# people (in some ham radio software collection, RPM, or DEB package),
# avoid # fine tuning it for your particular computer. It could
# cause compatibility issues for those with older computers.
#
#CFLAGS += -D_FORTIFY_SOURCE
# Use PortAudio Library
@ -237,7 +133,7 @@ direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25
demod.o digipeater.o cdigipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \
geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \
kiss.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \
kiss.o kissserial.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \
waypoint.o serial_port.o pfilter.o ptt.o rdq.o recv.o rrbb.o server.o \
symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xid.o xmit.o \
dwgps.o dwgpsnmea.o mheard.o
@ -295,30 +191,24 @@ direwolf.conf : generic.conf
# Where should we install it?
# My understanding, of the convention, is that something you compile
# from source, that is not a standard part of the operating system,
# should go in /usr/local/bin.
# This is a step in the right direction but not sufficient to use /usr instead.
# Macports typically installs in /opt/local so maybe you want to use that instead.
INSTALLDIR := /usr/local
#INSTALLDIR := /opt/local
# TODO: Test this better.
# Optional installation into /usr/local/...
# Optional installation into INSTALLDIR.
# Needs to be run as root or with sudo.
# TODO: Review file locations.
# Command to "install" to system directories. "install" for Linux. "ginstall" for Mac.
INSTALL=ginstall
.PHONY: install
install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png
#
# Applications, not installed with package manager, normally go in /usr/local/bin.
# /usr/bin is used instead when installing from .DEB or .RPM package.
# Applications.
#
$(INSTALL) direwolf $(INSTALLDIR)/bin
$(INSTALL) decode_aprs $(INSTALLDIR)/bin
@ -331,6 +221,7 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
$(INSTALL) gen_packets $(INSTALLDIR)/bin
$(INSTALL) atest $(INSTALLDIR)/bin
$(INSTALL) ttcalc $(INSTALLDIR)/bin
$(INSTALL) kissutil $(INSTALLDIR)/bin
$(INSTALL) dwespeak.sh $(INSTALLDIR)/bin
#
# Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory.
@ -346,11 +237,11 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
#
# Misc. data such as "tocall" to system mapping.
#
$(INSTALL) -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt
$(INSTALL) -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt
$(INSTALL) -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt
$(INSTALL) -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png
$(INSTALL) -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop
$(INSTALL) -D --mode=644 tocalls.txt $(INSTALLDIR)/share/direwolf/tocalls.txt
$(INSTALL) -D --mode=644 symbols-new.txt $(INSTALLDIR)/share/direwolf/symbols-new.txt
$(INSTALL) -D --mode=644 symbolsX.txt $(INSTALLDIR)/share/direwolf/symbolsX.txt
$(INSTALL) -D --mode=644 dw-icon.png $(INSTALLDIR)/share/direwolf/dw-icon.png
#
# Documentation. Various plain text files and PDF.
#
@ -359,15 +250,26 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
$(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
$(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt
#
$(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf
# ./README.md is an overview for the project main page.
# doc/README.md contains an overview of the PDF file contents and is more useful here.
#
$(INSTALL) -D --mode=644 doc/README.md $(INSTALLDIR)/share/doc/direwolf/README.md
$(INSTALL) -D --mode=644 doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(INSTALLDIR)/share/doc/direwolf/2400-4800-PSK-for-APRS-Packet-Radio.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
$(INSTALL) -D --mode=644 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(INSTALLDIR)/share/doc/direwolf/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf
$(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf
$(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
$(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf
$(INSTALL) -D --mode=644 doc/APRStt-Listening-Example.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Listening-Example.pdf
$(INSTALL) -D --mode=644 doc/Bluetooth-KISS-TNC.pdf $(INSTALLDIR)/share/doc/direwolf/Bluetooth-KISS-TNC.pdf
$(INSTALL) -D --mode=644 doc/Going-beyond-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/Going-beyond-9600-baud.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf
$(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf
$(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf
$(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf
$(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf
$(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf
$(INSTALL) -D --mode=644 doc/Successful-APRS-IGate-Operation.pdf $(INSTALLDIR)/share/doc/direwolf/Successful-APRS-IGate-Operation.pdf
$(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf
$(INSTALL) -D --mode=644 doc/WA8LMF-TNC-Test-CD-Results.pdf $(INSTALLDIR)/share/doc/direwolf/WA8LMF-TNC-Test-CD-Results.pdf
#
# Sample config files also go into the doc directory.
# When building from source, these can be put in home directory with "make install-conf".
@ -419,7 +321,9 @@ install-conf : direwolf.conf
# Separate application to decode raw data.
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
# First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.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.
@ -472,7 +376,7 @@ testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o
# Unit test for demodulators
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 dtest_now.o latlong.c symbols.c textcolor.c tt_text.c
fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c dtime_now.o 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_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
@ -522,6 +426,14 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c
$(CC) $(CFLAGS) -g -o $@ $^
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o
$(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o
@ -555,6 +467,7 @@ dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx ge
$(INSTALLDIR)/bin/gen_packets \
$(INSTALLDIR)/bin/atest \
$(INSTALLDIR)/bin/ttcalc \
$(INSTALLDIR)/bin/kissutil \
$(INSTALLDIR)/bin/dwespeak.sh \
$(INSTALLDIR)/share/direwolf/tocalls.txt \
$(INSTALLDIR)/share/direwolf/config/direwolf.conf \
@ -575,6 +488,7 @@ dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx ge
$(INSTALLDIR)/man/man1/decode_aprs.1 \
$(INSTALLDIR)/man/man1/direwolf.1 \
$(INSTALLDIR)/man/man1/gen_packets.1 \
$(INSTALLDIR)/man/man1/kissutil.1 \
$(INSTALLDIR)/man/man1/ll2utm.1 \
$(INSTALLDIR)/man/man1/log2gpx.1 \
$(INSTALLDIR)/man/man1/text2tt.1 \

View File

@ -16,7 +16,7 @@
#
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest kissutil
# People say we need -mthreads option for threads to work properly.
@ -30,6 +30,8 @@ CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_
AR := ar
CFLAGS += -g
# TEMP EXPERIMENT - DO NOT RELEASE
#CFLAGS += -fsanitize=undefined
# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3.
@ -98,11 +100,11 @@ demod_psk.o : fsk_demod_state.h
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 rdq.o rrbb.o dlq.o \
fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
decode_aprs.o symbols.o server.o kiss.o kissserial.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 cdigipeater.o pfilter.o dedupe.o tq.o xmit.o \
ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \
dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \
dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o \
dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o cm108.c \
dw-icon.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
@ -159,7 +161,9 @@ tocalls-symbols :
# 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.c regex.a misc.a geotranz.a
# First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^
@ -193,6 +197,12 @@ gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.
$(CC) $(CFLAGS) -o $@ $^
# Connected mode packet application server.
appserver : appserver.o textcolor.o ax25_pad.o fcs_calc.o misc.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# ------------------------------------------- Libraries --------------------------------------------
@ -267,7 +277,6 @@ strlcat.o : misc/strlcat.c
# Combine some unit tests into a single regression sanity check.
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates?
@ -548,6 +557,14 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o sock.o dtime_now.o misc.a regex.a
$(CC) $(CFLAGS) -DKISSUTIL -o $@ $^ -lwinmm -lws2_32
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
@ -591,7 +608,7 @@ z := $(notdir ${CURDIR})
.PHONY: dist-win
dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe \
aclients.exe log2gpx.exe gen_packets.exe atest.exe ttcalc.exe \
aclients.exe log2gpx.exe gen_packets.exe atest.exe ttcalc.exe kissutil.exe \
generic.conf dwespeak.bat \
README.md CHANGES.md \
doc/User-Guide.pdf \
@ -600,20 +617,25 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l
rm -f ../$z-win.zip
egrep '^C|^W' generic.conf | cut -c2-999 > direwolf.conf
unix2dos direwolf.conf
cp doc/README.md README-doc.md
zip --junk-paths ../$z-win.zip \
README.md \
CHANGES.md \
doc/User-Guide.pdf \
doc/Raspberry-Pi-APRS.pdf \
README-doc.md \
doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf \
doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf \
doc/APRS-Telemetry-Toolkit.pdf \
doc/APRStt-Implementation-Notes.pdf \
doc/APRStt-interface-for-SARTrack.pdf \
doc/APRStt-Listening-Example.pdf \
doc/Bluetooth-KISS-TNC.pdf \
doc/Going-beyond-9600-baud.pdf \
doc/Raspberry-Pi-APRS.pdf \
doc/Raspberry-Pi-APRS-Tracker.pdf \
doc/Raspberry-Pi-SDR-IGate.pdf \
doc/Successful-APRS-IGate-Operation.pdf \
doc/User-Guide.pdf \
doc/WA8LMF-TNC-Test-CD-Results.pdf \
LICENSE* \
@ -628,12 +650,34 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l
gen_packets.exe \
atest.exe \
ttcalc.exe \
kissutil.exe \
dwespeak.bat \
telemetry-toolkit/*
rm README-doc.md
# Reminders if pdf files are not up to date.
doc/User-Guide.pdf : doc/User-Guide.docx
echo "***** User-Guide.pdf is out of date *****"
doc/Raspberry-Pi-APRS.pdf : doc/Raspberry-Pi-APRS.docx
echo "***** Raspberry-Pi-APRS.pdf is out of date *****"
doc/Raspberry-Pi-APRS-Tracker.pdf : doc/Raspberry-Pi-APRS-Tracker.docx
echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****"
doc/APRStt-Implementation-Notes.pdf : doc/APRStt-Implementation-Notes.docx
echo "***** APRStt-Implementation-Notes.pdf is out of date *****"
doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf : doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.docx
echo "***** A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf is out of date *****"
doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf : doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.docx
echo "***** A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf is out of date *****"
doc/APRS-Telemetry-Toolkit.pdf : doc/APRS-Telemetry-Toolkit.docx
echo "***** APRS-Telemetry-Toolkit.pdf is out of date *****"
@ -648,3 +692,4 @@ backup :
#
# DO NOT DELETE

View File

@ -1,4 +1,4 @@

# Dire Wolf #
### Decoded Information from Radio Emissions for Windows Or Linux Fans ###
@ -20,7 +20,7 @@ Without any additional software, it can perform as:
- [APRStt](http://www.aprs.org/aprstt.html) gateway
It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [RMS Express](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), and many others.
It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), and many others.
## Features & Benefits ##
@ -36,6 +36,7 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h
Send periodic beacons to provide information to others. For tracking the location is provided by a GPS receiver.
Build your own telemetry applications with the toolkit.
- **APRStt Gateway.**
Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network. Responses can be sent by Morse Code or synthesized speech.
@ -145,6 +146,15 @@ Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose d
For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
### Macintosh OS X ###
Read the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). It is a lot more complicated than Linux.
If you have problems, post them to the [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) discussion group. I don't have a Mac and probably won't be able to help you. I rely on others, in the user community, for the Mac version.
## Join the conversation ##
Here are some good places to ask questions and share your experience:

View File

@ -549,12 +549,7 @@ static void * client_thread_net (void *arg)
mon_cmd.kind_lo = 'k';
#if __WIN32__
send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
SOCK_SEND (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
/*
* Print all of the monitored packets.
@ -563,14 +558,10 @@ static void * client_thread_net (void *arg)
while (1) {
int n;
#if __WIN32__
n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
n = SOCK_RECV (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
if (n != sizeof(mon_cmd)) {
printf ("Read error, client %d received %d command bytes.\n", my_index, n);
printf ("Read error, client %d received %d command bytes. Terminating.\n", my_index, n);
exit (1);
}
@ -581,11 +572,7 @@ static void * client_thread_net (void *arg)
assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data)));
if (mon_cmd.data_len > 0) {
#if __WIN32__
n = recv (server_sock, data, mon_cmd.data_len, 0);
#else
n = read (server_sock, data, mon_cmd.data_len);
#endif
n = SOCK_RECV (server_sock, data, mon_cmd.data_len);
if (n != mon_cmd.data_len) {
printf ("Read error, client %d received %d data bytes.\n", my_index, n);

View File

@ -312,7 +312,13 @@ int main (int argc, char *argv[])
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
if (my_audio_config.achan[0].baud < 600) {
if (my_audio_config.achan[0].baud == 100) {
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1615;
my_audio_config.achan[0].space_freq = 1785;
strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
}
else 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;

17
audio.c
View File

@ -921,11 +921,22 @@ int audio_get (int a)
/* Error */
// TODO: Needs more study and testing.
// TODO: print n. should snd_strerror use n or errno?
// Audio input device error: Unknown error
// Only expected error conditions:
// -EBADFD PCM is not in the right state (SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING)
// -EPIPE an overrun occurred
// -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
// Data overrun is displayed as "broken pipe" which seems a little misleading.
// Add our own message which says something about CPU being too slow.
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio input device %d error: %s\n", a, snd_strerror(n));
dw_printf ("Audio input device %d error code %d: %s\n", a, n, snd_strerror(n));
if (n == (-EPIPE)) {
dw_printf ("This is most likely caused by the CPU being too slow to keep up with the audio stream.\n");
dw_printf ("Use the \"top\" command, in another command window, to look at CPU usage.\n");
dw_printf ("This might be a temporary condition so we will attempt to recover a few times before giving up.\n");
}
audio_stats (a,
save_audio_config_p->adev[a].num_channels,

30
audio.h
View File

@ -30,7 +30,8 @@ enum ptt_method_e {
PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */
PTT_METHOD_GPIO, /* General purpose I/O, Linux only. */
PTT_METHOD_LPT, /* Parallel printer port, Linux only. */
PTT_METHOD_HAMLIB }; /* HAMLib, Linux only. */
PTT_METHOD_HAMLIB, /* HAMLib, Linux only. */
PTT_METHOD_CM108 }; /* GPIO pin of CM108/CM119/etc. Linux only. */
typedef enum ptt_method_e ptt_method_t;
@ -96,6 +97,10 @@ struct audio_s {
int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */
char timestamp_format[40]; /* -T option */
/* Precede received & transmitted frames with timestamp. */
/* Command line option uses "strftime" format string. */
/* Properties for each audio channel, common to receive and transmit. */
/* Can be different for each radio channel. */
@ -191,10 +196,17 @@ struct audio_s {
struct {
ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB. */
ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB, CM108. */
char ptt_device[20]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */
char ptt_device[100]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */
/* Also used for HAMLIB. Could be host:port when model is 1. */
/* For years, 20 characters was plenty then we start getting extreme names like this: */
/* /dev/serial/by-id/usb-FTDI_Navigator__CAT___2nd_PTT__00000000-if00-port0 */
/* /dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0 */
/* Issue 104, changed to 100 bytes in version 1.5. */
/* This same field is also used for CM108 GPIO PTT which will */
/* have a name like /dev/hidraw1. */
ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */
ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */
@ -202,8 +214,9 @@ struct audio_s {
int out_gpio_num; /* GPIO number. Originally this was only for PTT. */
/* It is now more general. */
/* octrl array is indexed by PTT, DCD, or CONnected indicator. */
/* For CM108, this should be in range of 1-8. */
#define MAX_GPIO_NAME_LEN 16 // 12 would cover any case I've seen so this should be safe
#define MAX_GPIO_NAME_LEN 20 // 12 would cover any case I've seen so this should be safe
char out_gpio_name[MAX_GPIO_NAME_LEN];
/* orginally, gpio number NN was assumed to simply */
@ -212,6 +225,8 @@ struct audio_s {
/* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */
/* This could probably be collapsed into ptt_device instead of being separate. */
int ptt_lpt_bit; /* Bit number for parallel printer port. */
/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
@ -270,7 +285,10 @@ struct audio_s {
/* are done sending the data. This is to avoid */
/* dropping PTT too soon and chopping off the end */
/* of the frame. Again 10 mS units. */
/* At this point, I'm thinking of 10 as the default. */
/* At this point, I'm thinking of 10 (= 100 mS) as the default */
/* because we're not quite sure when the soundcard audio stops. */
int fulldup; /* Full Duplex. */
} achan[MAX_CHANS];
@ -357,7 +375,7 @@ struct audio_s {
#define DEFAULT_PERSIST 63
#define DEFAULT_TXDELAY 30
#define DEFAULT_TXTAIL 10
#define DEFAULT_FULLDUP 0
/*
* Note that we have two versions of these in audio.c and audio_win.c.

View File

@ -313,10 +313,15 @@ int audio_open (struct audio_s *pa)
/* Does config file have a number? */
/* If so, it is an index into list of devices. */
/* Originally only a single digit was recognized. */
/* v 1.5 also recognizes two digits. (Issue 116) */
if (strlen(pa->adev[a].adevice_in) == 1 && isdigit(pa->adev[a].adevice_in[0])) {
in_dev_no[a] = atoi(pa->adev[a].adevice_in);
}
else if (strlen(pa->adev[a].adevice_in) == 2 && isdigit(pa->adev[a].adevice_in[0]) && isdigit(pa->adev[a].adevice_in[1])) {
in_dev_no[a] = atoi(pa->adev[a].adevice_in);
}
/* Otherwise, does it have search string? */
@ -344,6 +349,9 @@ int audio_open (struct audio_s *pa)
if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) {
out_dev_no[a] = atoi(pa->adev[a].adevice_out);
}
else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) {
out_dev_no[a] = atoi(pa->adev[a].adevice_out);
}
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
num_devices = waveOutGetNumDevs();
@ -842,7 +850,7 @@ int audio_get (int a)
assert (A->udp_sock > 0);
res = recv (A->udp_sock, A->stream_data, SDR_UDP_BUF_MAXLEN, 0);
res = SOCK_RECV (A->udp_sock, A->stream_data, SDR_UDP_BUF_MAXLEN);
if (res <= 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError());
@ -943,7 +951,15 @@ int audio_put (int a, int c)
timeout--;
if (timeout <= 0) {
text_color_set(DW_COLOR_ERROR);
// TODO: open issues 78 & 165. How can we avoid/improve this?
dw_printf ("Audio output failure waiting for buffer.\n");
dw_printf ("This can occur when we are producing audio output for\n");
dw_printf ("transmit and the operating system doesn't provide buffer\n");
dw_printf ("space after waiting and retrying many times.\n");
//dw_printf ("In recent years, this has been reported only when running the\n");
//dw_printf ("Windows version with VMWare on a Macintosh.\n");
ptt_term ();
return (-1);
}

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,8 @@ void ax25_link_init (struct misc_config_s *pconfig);
// These functions must be called on a single thread, one at a time.
// The Data Link Queue (DLQ) is used to serialize events from multiple sources.
// Maybe the dispatch switch should be moved to ax25_link.c so they can all
// be made static and they can't be called from the wrong place accidentally.
void dl_connect_request (dlq_item_t *E);
@ -68,6 +70,8 @@ void dl_client_cleanup (dlq_item_t *E);
void lm_data_indication (dlq_item_t *E);
void lm_seize_confirm (dlq_item_t *E);
void lm_channel_busy (dlq_item_t *E);

View File

@ -707,7 +707,7 @@ packet_t ax25_dup (packet_t copy_from)
*
* in_addr - Input such as "WB2OSZ-15*"
*
* strict - TRUE for strict checking (6 characters, no lower case,
* strict - 1 (true) for strict checking (6 characters, no lower case,
* SSID must be in range of 0 to 15).
* Strict is appropriate for packets sent
* over the radio. Communication with IGate
@ -716,6 +716,8 @@ packet_t ax25_dup (packet_t copy_from)
* We also get messages like this from a server.
* KB1POR>APU25N,TCPIP*,qAC,T2NUENGLD:...
*
* 2 (extra true) will complain if * is found at end.
*
* Outputs: out_addr - Address without any SSID.
* Must be at least AX25_MAX_ADDR_LEN bytes.
*
@ -724,6 +726,7 @@ packet_t ax25_dup (packet_t copy_from)
* out_heard - True if "*" found.
*
* Returns: True (1) if OK, false (0) if any error.
* When 0, out_addr, out_ssid, and out_heard are unpredictable.
*
*
*------------------------------------------------------------------------------*/
@ -744,35 +747,53 @@ int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, in
*out_ssid = 0;
*out_heard = 0;
if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sAddress \"%s\" is a \"q-construct\" used for communicating\n", position_name[position], in_addr);
dw_printf ("with APRS Internet Servers. It was not expected here.\n");
}
//dw_printf ("ax25_parse_addr in: %s\n", in_addr);
if (position < -1) position = -1;
if (position > AX25_REPEATER_8) position = AX25_REPEATER_8;
position++; /* Adjust for position_name above. */
if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sAddress \"%s\" is a \"q-construct\" used for communicating with\n", position_name[position], in_addr);
dw_printf ("APRS Internet Servers. It should never appear when going over the radio.\n");
}
//dw_printf ("ax25_parse_addr in: %s\n", in_addr);
maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1);
p = in_addr;
i = 0;
for (p = in_addr; isalnum(*p); p++) {
for (p = in_addr; *p != '\0' && *p != '-' && *p != '*'; p++) {
if (i >= maxlen) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sAddress is too long. \"%s\" has more than %d characters.\n", position_name[position], in_addr, maxlen);
return 0;
}
if ( ! isalnum(*p)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sAddress, \"%s\" contains character other than letter or digit in character position %d.\n", position_name[position], in_addr, (int)(long)(p-in_addr)+1);
return 0;
}
out_addr[i++] = *p;
out_addr[i] = '\0';
#if DECAMAIN // Hack when running in decode_aprs utility.
// Exempt the "qA..." case because it was already mentioned.
if (strict && islower(*p) && strncmp(in_addr, "qA", 2) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr);
}
#else
if (strict && islower(*p)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sAddress has lower case letters. \"%s\" must be all upper case.\n", position_name[position], in_addr);
return 0;
}
#endif
}
j = 0;
@ -804,6 +825,11 @@ int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, in
if (*p == '*') {
*out_heard = 1;
p++;
if (strict == 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\"*\" is not allowed at end of address \"%s\" here.\n", in_addr);
return 0;
}
}
if (*p != '\0') {
@ -1257,13 +1283,22 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station)
return;
}
memset (station, 0, 7);
for (i=0; i<6; i++) {
unsigned char ch;
// At one time this would stop at the first space, on the assumption we would have only trailing spaces.
// Then there was a forum discussion where someone encountered the address " WIDE2" with a leading space.
// In that case, we would have returned a zero length string here.
// Now we return exactly what is in the address field and trim trailing spaces.
// This will provide better information for troubleshooting.
ch = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
if (ch <= ' ') break;
station[i] = ch;
for (i=0; i<6; i++) {
station[i] = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
}
station[6] = '\0';
for (i=5; i>=0; i--) {
if (station[i] == ' ')
station[i] = '\0';
else
break;
}
ssid = ax25_get_ssid (this_p, n);
@ -1322,13 +1357,22 @@ void ax25_get_addr_no_ssid (packet_t this_p, int n, char *station)
return;
}
memset (station, 0, 7);
for (i=0; i<6; i++) {
unsigned char ch;
// At one time this would stop at the first space, on the assumption we would have only trailing spaces.
// Then there was a forum discussion where someone encountered the address " WIDE2" with a leading space.
// In that case, we would have returned a zero length string here.
// Now we return exactly what is in the address field and trim trailing spaces.
// This will provide better information for troubleshooting.
ch = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
if (ch <= ' ') break;
station[i] = ch;
for (i=0; i<6; i++) {
station[i] = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
}
station[6] = '\0';
for (i=5; i>=0; i--) {
if (station[i] == ' ')
station[i] = '\0';
else
break;
}
} /* end ax25_get_addr_no_ssid */
@ -1956,7 +2000,7 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN])
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (this_p->frame_len > 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
assert (this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
memcpy (result, this_p->frame_data, this_p->frame_len);
@ -2459,6 +2503,33 @@ int ax25_get_pid (packet_t this_p)
}
/*------------------------------------------------------------------
*
* Function: ax25_get_frame_len
*
* Purpose: Get length of frame.
*
* Inputs: this_p - pointer to packet object.
*
* Returns: Number of octets in the frame buffer.
* Does NOT include the extra 2 for FCS.
*
*------------------------------------------------------------------*/
int ax25_get_frame_len (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
return (this_p->frame_len);
} /* end ax25_get_frame_len */
/*------------------------------------------------------------------------------
*
* Name: ax25_dedupe_crc

View File

@ -427,6 +427,8 @@ extern int ax25_get_c2 (packet_t this_p);
extern int ax25_get_pid (packet_t this_p);
extern int ax25_get_frame_len (packet_t this_p);
extern unsigned short ax25_dedupe_crc (packet_t pp);
extern unsigned short ax25_m_m_crc (packet_t pp);

View File

@ -355,15 +355,19 @@ packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
*
* pf - Poll/Final flag.
*
* pinfo - Pointer to data for Info field. Allowed only for SREJ.
*
* info_len - Length for Info field.
*
*
* 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)
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, unsigned char *pinfo, int info_len, 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)
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, unsigned char *pinfo, int info_len)
#endif
{
packet_t this_p;
@ -383,7 +387,7 @@ packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
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__);
dw_printf ("Internal error in %s: Could not set addresses for S frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
@ -401,6 +405,14 @@ packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
nr &= (modulo - 1);
}
// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both.
// The underlying X.25 spec clearly says it is reponse only. Let's go with that.
if (ftype == frame_type_S_SREJ && cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: SREJ must be response.\n", __func__);
}
switch (ftype) {
case frame_type_S_RR: ctrl = 0x01; break;
@ -434,6 +446,24 @@ packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
this_p->frame_len++;
}
if (ftype == frame_type_S_SREJ) {
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: SREJ 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 RR, RNR, REJ frame.\n", __func__);
}
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
@ -813,7 +843,7 @@ int main ()
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);
pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0);
ax25_hex_dump (pp);
ax25_delete (pp);
@ -827,7 +857,7 @@ int main ()
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);
pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0);
ax25_hex_dump (pp);
ax25_delete (pp);
@ -835,6 +865,26 @@ int main ()
}
}
/* SREJ is only S frame which can have information part. */
static char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 };
ftype = frame_type_S_SREJ;
for (pf = 0; pf <= 1; pf++) {
modulo = 128;
nr = 127;
cr = cr_res;
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct Multi-SREJ 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, srej_info, (int)(sizeof(srej_info)));
ax25_hex_dump (pp);
ax25_delete (pp);
}
dw_printf ("\n----------\n\n");
/* I frame */

View File

@ -22,14 +22,14 @@
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_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, unsigned char *pinfo, int info_len, 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_s_frame(a,n,c,f,m,r,p,i,l) ax25_s_frame_debug(a,n,c,f,m,r,p,i,l,__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__)
@ -38,7 +38,7 @@ packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int
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_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, unsigned char *pinfo, int info_len);
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);

174
beacon.c
View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -141,6 +141,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo);
void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate)
{
time_t now;
struct tm tm;
int j;
int count;
#if __WIN32__
@ -270,21 +271,71 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct
}
/*
* Calculate first time for each beacon from the 'delay' value.
* Calculate first time for each beacon from the 'slot' or 'delay' value.
*/
now = time(NULL);
localtime_r (&now, &tm);
for (j=0; j<g_misc_config_p->num_beacons; j++) {
struct beacon_s *bp = & (g_misc_config_p->beacon[j]);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon[%d] chan=%d, delay=%d, every=%d\n",
dw_printf ("beacon[%d] chan=%d, delay=%d, slot=%d, every=%d\n",
j,
g_misc_config_p->beacon[j].sendto_chan,
g_misc_config_p->beacon[j].delay,
g_misc_config_p->beacon[j].every);
bp->sendto_chan,
bp->delay,
bp->slot,
bp->every);
#endif
/*
* If timeslots, there must be a full number of beacon intervals per hour.
*/
#define IS_GOOD(x) ((3600/(x))*(x) == 3600)
if (bp->slot != G_UNKNOWN) {
if ( ! IS_GOOD(bp->every)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: When using timeslots, there must be a whole number of beacon intervals per hour.\n", bp->lineno);
// Try to make it valid by adjusting up or down.
int n;
for (n=1; ; n++) {
int e;
e = bp->every + n;
if (e > 3600) {
bp->every = 3600;
break;
}
if (IS_GOOD(e)) {
bp->every = e;
break;
}
e = bp->every - n;
if (e < 1) {
bp->every = 1; // Impose a larger minimum?
break;
}
if (IS_GOOD(e)) {
bp->every = e;
break;
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Time between slotted beacons has been adjusted to %d seconds.\n", bp->lineno, bp->every);
}
/*
* Determine when next slot time will arrive.
*/
bp->delay = bp->slot - (tm.tm_min * 60 + tm.tm_sec);
while (bp->delay > bp->every) bp->delay -= bp->every;
while (bp->delay < 5) bp->delay += bp->every;
}
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay;
}
@ -491,10 +542,12 @@ static void * beacon_thread (void *arg)
*/
for (j=0; j<g_misc_config_p->num_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
struct beacon_s *bp = & (g_misc_config_p->beacon[j]);
if (bp->btype == BEACON_IGNORE)
continue;
if (g_misc_config_p->beacon[j].next <= now) {
if (bp->next <= now) {
/* Send the beacon. */
@ -503,13 +556,20 @@ static void * beacon_thread (void *arg)
/* Calculate when the next one should be sent. */
/* Easy for fixed interval. SmartBeaconing takes more effort. */
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
if (bp->btype == BEACON_TRACKER) {
if (gpsinfo.fix < DWFIX_2D) {
/* Fix not available so beacon was not sent. */
/* Try again in a couple seconds. */
g_misc_config_p->beacon[j].next = now + 2;
if (g_misc_config_p->sb_configured) {
/* Try again in a couple seconds. */
bp->next = now + 2;
}
else {
/* Stay with the schedule. */
/* Important for slotted. Might reconsider otherwise. */
bp->next += bp->every;
}
}
else if (g_misc_config_p->sb_configured) {
@ -519,18 +579,21 @@ static void * beacon_thread (void *arg)
sb_prev_time = now;
sb_prev_course = gpsinfo.track;
g_misc_config_p->beacon[j].next = sb_calculate_next_time (now,
bp->next = sb_calculate_next_time (now,
DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track,
sb_prev_time, sb_prev_course);
}
else {
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
/* Tracker beacon, fixed spacing. */
bp->next += bp->every;
}
}
else {
/* non-tracker beacons are at fixed spacing. */
/* Non-tracker beacon, fixed spacing. */
/* Increment by 'every' so slotted times come out right. */
/* i.e. Don't take relative to now in case there was some delay. */
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
bp->next += bp->every;
}
} /* if time to send it */
@ -682,6 +745,7 @@ static time_t sb_calculate_next_time (time_t now,
static void beacon_send (int j, dwgps_info_t *gpsinfo)
{
struct beacon_s *bp = & (g_misc_config_p->beacon[j]);
int strict = 1; /* Strict packet checking because they will go over air. */
char stemp[20];
@ -704,13 +768,13 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
*/
strlcpy (mycall, "NOCALL", sizeof(mycall));
assert (g_misc_config_p->beacon[j].sendto_chan >= 0);
assert (bp->sendto_chan >= 0);
strlcpy (mycall, g_modem_config_p->achan[g_misc_config_p->beacon[j].sendto_chan].mycall, sizeof(mycall));
strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall));
if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("MYCALL not set for beacon in config file line %d.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno);
return;
}
@ -723,17 +787,17 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
strlcpy (beacon_text, mycall, sizeof(beacon_text));
strlcat (beacon_text, ">", sizeof(beacon_text));
if (g_misc_config_p->beacon[j].dest != NULL) {
strlcat (beacon_text, g_misc_config_p->beacon[j].dest, sizeof(beacon_text));
if (bp->dest != NULL) {
strlcat (beacon_text, bp->dest, sizeof(beacon_text));
}
else {
snprintf (stemp, sizeof(stemp), "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
strlcat (beacon_text, stemp, sizeof(beacon_text));
}
if (g_misc_config_p->beacon[j].via != NULL) {
if (bp->via != NULL) {
strlcat (beacon_text, ",", sizeof(beacon_text));
strlcat (beacon_text, g_misc_config_p->beacon[j].via, sizeof(beacon_text));
strlcat (beacon_text, bp->via, sizeof(beacon_text));
}
strlcat (beacon_text, ":", sizeof(beacon_text));
@ -746,23 +810,23 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
// TODO: test & document.
strlcpy (super_comment, "", sizeof(super_comment));
if (g_misc_config_p->beacon[j].comment != NULL) {
strlcpy (super_comment, g_misc_config_p->beacon[j].comment, sizeof(super_comment));
if (bp->comment != NULL) {
strlcpy (super_comment, bp->comment, sizeof(super_comment));
}
if (g_misc_config_p->beacon[j].commentcmd != NULL) {
if (bp->commentcmd != NULL) {
char var_comment[AX25_MAX_INFO_LEN];
int k;
/* Run given command to get variable part of comment. */
k = dw_run_cmd (g_misc_config_p->beacon[j].commentcmd, 2, var_comment, sizeof(var_comment));
k = dw_run_cmd (bp->commentcmd, 2, var_comment, sizeof(var_comment));
if (k > 0) {
strlcat (super_comment, var_comment, sizeof(super_comment));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", bp->lineno);
}
}
@ -770,17 +834,17 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
/*
* Add the info part depending on beacon type.
*/
switch (g_misc_config_p->beacon[j].btype) {
switch (bp->btype) {
case BEACON_POSITION:
encode_position (g_misc_config_p->beacon[j].messaging, g_misc_config_p->beacon[j].compress,
g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, 0,
(int)roundf(DW_METERS_TO_FEET(g_misc_config_p->beacon[j].alt_m)),
g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol,
g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
encode_position (bp->messaging, bp->compress,
bp->lat, bp->lon, bp->ambiguity,
(int)roundf(DW_METERS_TO_FEET(bp->alt_m)),
bp->symtab, bp->symbol,
bp->power, bp->height, bp->gain, bp->dir,
G_UNKNOWN, G_UNKNOWN, /* course, speed */
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
bp->freq, bp->tone, bp->offset,
super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
@ -788,11 +852,11 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
case BEACON_OBJECT:
encode_object (g_misc_config_p->beacon[j].objname, g_misc_config_p->beacon[j].compress, 0, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, 0,
g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol,
g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
encode_object (bp->objname, bp->compress, 0, bp->lat, bp->lon, bp->ambiguity,
bp->symtab, bp->symbol,
bp->power, bp->height, bp->gain, bp->dir,
G_UNKNOWN, G_UNKNOWN, /* course, speed */
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, super_comment,
bp->freq, bp->tone, bp->offset, super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
break;
@ -809,7 +873,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
/* transmission of altitude from GPS. */
my_alt_ft = G_UNKNOWN;
if (gpsinfo->fix >= 3 && gpsinfo->altitude != G_UNKNOWN && g_misc_config_p->beacon[j].alt_m > 0) {
if (gpsinfo->fix >= 3 && gpsinfo->altitude != G_UNKNOWN && bp->alt_m > 0) {
my_alt_ft = (int)roundf(DW_METERS_TO_FEET(gpsinfo->altitude));
}
@ -818,12 +882,12 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
coarse = (int)roundf(gpsinfo->track);
}
encode_position (g_misc_config_p->beacon[j].messaging, g_misc_config_p->beacon[j].compress,
gpsinfo->dlat, gpsinfo->dlon, 0, my_alt_ft,
g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol,
g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
encode_position (bp->messaging, bp->compress,
gpsinfo->dlat, gpsinfo->dlon, bp->ambiguity, my_alt_ft,
bp->symtab, bp->symbol,
bp->power, bp->height, bp->gain, bp->dir,
coarse, (int)roundf(gpsinfo->speed_knots),
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
bp->freq, bp->tone, bp->offset,
super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
@ -845,8 +909,8 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
A.g_dcs = G_UNKNOWN;
strlcpy (A.g_src, mycall, sizeof(A.g_src));
A.g_symbol_table = g_misc_config_p->beacon[j].symtab;
A.g_symbol_code = g_misc_config_p->beacon[j].symbol;
A.g_symbol_table = bp->symtab;
A.g_symbol_code = bp->symbol;
A.g_lat = gpsinfo->dlat;
A.g_lon = gpsinfo->dlon;
A.g_speed_mph = DW_KNOTS_TO_MPH(gpsinfo->speed_knots);
@ -865,25 +929,25 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
case BEACON_CUSTOM:
if (g_misc_config_p->beacon[j].custom_info != NULL) {
if (bp->custom_info != NULL) {
/* Fixed handcrafted text. */
strlcat (beacon_text, g_misc_config_p->beacon[j].custom_info, sizeof(beacon_text));
strlcat (beacon_text, bp->custom_info, sizeof(beacon_text));
}
else if (g_misc_config_p->beacon[j].custom_infocmd != NULL) {
else if (bp->custom_infocmd != NULL) {
char info_part[AX25_MAX_INFO_LEN];
int k;
/* Run given command to obtain the info part for packet. */
k = dw_run_cmd (g_misc_config_p->beacon[j].custom_infocmd, 2, info_part, sizeof(info_part));
k = dw_run_cmd (bp->custom_infocmd, 2, info_part, sizeof(info_part));
if (k > 0) {
strlcat (beacon_text, info_part, sizeof(beacon_text));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", bp->lineno);
strlcpy (beacon_text, "", sizeof(beacon_text)); // abort!
}
}
@ -935,7 +999,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
alevel_t alevel;
switch (g_misc_config_p->beacon[j].sendto_type) {
switch (bp->sendto_type) {
case SENDTO_IGATE:
@ -949,7 +1013,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
case SENDTO_XMIT:
default:
tq_append (g_misc_config_p->beacon[j].sendto_chan, TQ_PRIO_1_LO, pp);
tq_append (bp->sendto_chan, TQ_PRIO_1_LO, pp);
break;
case SENDTO_RECV:
@ -957,13 +1021,13 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
/* Simulated reception from radio. */
memset (&alevel, 0xff, sizeof(alevel));
dlq_rec_frame (g_misc_config_p->beacon[j].sendto_chan, 0, 0, pp, alevel, 0, "");
dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, "");
break;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", bp->lineno);
dw_printf ("%s\n", beacon_text);
}

View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
// Copyright (C) 2016, 2017 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
@ -59,7 +59,7 @@
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str);
int has_alias, regex_t *alias, int to_chan, char *cfilter_str);
/*
@ -150,8 +150,9 @@ void cdigipeater (int from_chan, packet_t pp)
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
save_cdigi_config_p->has_alias[from_chan][to_chan],
&(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan,
save_cdigi_config_p->cfilter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
@ -172,8 +173,9 @@ void cdigipeater (int from_chan, packet_t pp)
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
save_cdigi_config_p->has_alias[from_chan][to_chan],
&(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan,
save_cdigi_config_p->cfilter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
@ -203,12 +205,15 @@ void cdigipeater (int from_chan, packet_t pp)
* packet is to be transmitted. Could be the same as
* mycall_rec or different.
*
* alias - Compiled pattern for my station aliases.
* Could be NULL if no aliases.
* has_alias - True if we have an alias.
*
* alias - Optional compiled pattern for my station aliases.
* Do NOT attempt to use this if 'has_alias' is false.
*
* to_chan - Channel number that we are transmitting to.
*
* filter_str - Filter expression string or NULL.
* cfilter_str - Filter expression string for the from/to channel pair or NULL.
* Note that only a subset of the APRS filters are applicable here.
*
* Returns: Packet object for transmission or NULL.
* The original packet is not modified. The caller is responsible for freeing it.
@ -227,20 +232,38 @@ void cdigipeater (int from_chan, packet_t pp)
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str)
int has_alias, regex_t *alias, int to_chan, char *cfilter_str)
{
int r;
char repeater[AX25_MAX_ADDR_LEN];
int err;
char err_msg[100];
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("cdigipeat_match (from_chan=%d, pp=%p, mycall_rec=%s, mycall_xmit=%s, has_alias=%d, alias=%p, to_chan=%d, cfilter_str=%s\n",
from_chan, pp, mycall_rec, mycall_xmit, has_alias, alias, to_chan, cfilter_str);
#endif
/*
* First check if filtering has been configured.
* Note that we have three different config file filter commands:
*
* FILTER - APRS digipeating and IGate client side.
* Originally this was the only one.
* Should we change it to AFILTER to make it clearer?
* CFILTER - Similar for connected moded digipeater.
* IGFILTER - APRS-IS (IGate) server side - completely diffeent.
* Confusing with similar name but much different idea.
* Maybe this should be renamed to SUBSCRIBE or something like that.
*
* Logically this should come later, after an address/alias match.
* But here we only have to do it once.
*/
if (filter_str != NULL) {
if (cfilter_str != NULL) {
if (pfilter(from_chan, to_chan, filter_str, pp, 0) != 1) {
if (pfilter(from_chan, to_chan, cfilter_str, pp, 0) != 1) {
return(NULL);
}
}
@ -286,7 +309,11 @@ static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, c
/*
* If we have an alias match, substitute MYCALL.
*/
if (alias != NULL) {
if (has_alias) {
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Checking %s for alias match.\n", repeater);
#endif
err = regexec(alias,repeater,0,NULL,0);
if (err == 0) {
packet_t result;
@ -304,6 +331,12 @@ static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, c
dw_printf ("%s\n", err_msg);
}
}
else {
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("No alias was specified.\n");
#endif
}
/*
* Don't repeat it if we get here.

View File

@ -23,15 +23,18 @@ struct cdigi_config_s {
/*
* Rules for each of the [from_chan][to_chan] combinations.
*/
int enabled[MAX_CHANS][MAX_CHANS]; // Is it enabled for from/to pair?
int has_alias[MAX_CHANS][MAX_CHANS]; // If there was no alias in the config file,
// the structure below will not be set up
// properly and an attempt to use it could
// result in a crash. (fixed v1.5)
// Not needed for [APRS] DIGIPEAT because
// the alias is mandatory there.
regex_t alias[MAX_CHANS][MAX_CHANS];
int enabled[MAX_CHANS][MAX_CHANS];
char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
char *cfilter_str[MAX_CHANS][MAX_CHANS];
// NULL or optional Packet Filter strings such as "t/m".
// Notice the size of arrays is one larger than normal.
// That extra position is for the IGate.
};
/*

712
cm108.c Normal file
View File

@ -0,0 +1,712 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2017 John Langner, WB2OSZ
//
// Parts of this were adapted from "hamlib" which contains the notice:
//
// * Copyright (c) 2000-2012 by Stephane Fillod
// * Copyright (c) 2011 by Andrew Errington
// * CM108 detection code Copyright (c) Thomas Sailer used with permission
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/*------------------------------------------------------------------
*
* Module: cm108.c
*
* Purpose: Use the CM108/CM119 (or compatible) GPIO pins for the Push To Talk (PTT) Control.
*
* Description:
*
* There is an incresing demand for using the GPIO pins of USB audio devices for PTT.
* We have a couple commercial products:
*
* DMK URI http://www.dmkeng.com/URI_Order_Page.htm
* RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html
*
* and homebrew projects which are all very similar.
*
* http://www.qsl.net/kb9mwr/projects/voip/usbfob-119.pdf
* http://rtpdir.weebly.com/uploads/1/6/8/7/1687703/usbfob.pdf
* http://www.repeater-builder.com/projects/fob/USB-Fob-Construction.pdf
* https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/
*
* Usually GPIO 3 is used because it is easier to tack solder a wire to a pin on the end.
*
* Soundmodem and hamlib paved the way but didn't get too far.
* Dire Wolf 1.3 added HAMLIB support (Linux only) which theoretically allows this in a
* roundabout way. This is documented in the User Guide, section called,
* "Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)"
*
* It's rather involved and the explantion doesn't cover the case of multiple
* USB-Audio adapters. It is not as straightforward as you might expect. Here we have
* an example of 3 C-Media USB adapters, a SignaLink USB, a keyboard, and a mouse.
*
*
* VID PID Product Sound ADEVICE HID [ptt]
* --- --- ------- ----- ------- ---------
* ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC1D0c plughw:1,0 /dev/hidraw0
* ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC1D0p plughw:1,0 /dev/hidraw0
* ** 0d8c 000c C-Media USB Headphone Set /dev/snd/controlC1 /dev/hidraw0
* 08bb 2904 USB Audio CODEC /dev/snd/pcmC2D0c plughw:2,0 /dev/hidraw2
* 08bb 2904 USB Audio CODEC /dev/snd/pcmC2D0p plughw:2,0 /dev/hidraw2
* 08bb 2904 USB Audio CODEC /dev/snd/controlC2 /dev/hidraw2
* ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC0D0c plughw:0,0 /dev/hidraw1
* ** 0d8c 000c C-Media USB Headphone Set /dev/snd/pcmC0D0p plughw:0,0 /dev/hidraw1
* ** 0d8c 000c C-Media USB Headphone Set /dev/snd/controlC0 /dev/hidraw1
* ** 0d8c 0008 C-Media USB Audio Device /dev/snd/pcmC4D0c plughw:4,0 /dev/hidraw6
* ** 0d8c 0008 C-Media USB Audio Device /dev/snd/pcmC4D0p plughw:4,0 /dev/hidraw6
* ** 0d8c 0008 C-Media USB Audio Device /dev/snd/controlC4 /dev/hidraw6
* 413c 2010 Dell USB Keyboard /dev/hidraw4
* 0461 4d15 USB Optical Mouse /dev/hidraw5
*
*
* The USB soundcards (/dev/snd/pcm...) have an associated Human Interface Device (HID)
* corresponding to the GPIO pins which are sometimes connected to pushbuttons.
* The mapping has no obvious pattern.
*
* Sound Card 0 HID 1
* Sound Card 1 HID 0
* Sound Card 2 HID 2
* Sound Card 4 HID 6
*
* That would be a real challenge if you had to figure that all out and configure manually.
* Dire Wolf version 1.5 makes this much more flexible and easier to use by supporting multiple
* sound devices and automatically determining the corresponding HID for the PTT signal.
*
*---------------------------------------------------------------*/
#ifndef USE_CM108
#ifdef CM108_MAIN
#include "direwolf.h"
#include "textcolor.h"
int main (void)
{
text_color_init (0); // Turn off text color.
dw_printf ("CM108 PTT support was disabled in Makefile.linux.\n");
dw_printf ("It was excluded because /usr/include/libudev.h was missing.\n");
dw_printf ("Install it with \"sudo apt-get install libudev-dev\" or\n");
dw_printf ("\"sudo yum install libudev-devel\" then rebuild.\n");
return (0);
}
#endif
#else // USE_CM108 is defined.
#include "direwolf.h"
#include <libudev.h>
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <unistd.h>
#include <string.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h> // ioctl, _IOR
#include <fcntl.h>
#include <errno.h>
#include <linux/hidraw.h> // for HIDIOCGRAWINFO
#include "textcolor.h"
#include "cm108.h"
static int cm108_write (char *name, int iomask, int iodata);
// The CM108, CM109, and CM119 datasheets all say that idProduct can be in the range
// of 0008 to 000f programmable by MSEL and MODE pin. How can we tell the difference?
// CM108B is 0012.
// CM119B is 0013.
// CM108AH is 0139 programmable by MSEL and MODE pin.
// CM119A is 013A programmable by MSEL and MODE pin.
// To make matters even more confusing, these can be overridden
// with an external EEPROM. Some have 8, rather than 4 GPIO.
#define CMEDIA_VID 0xd8c // Vendor ID
#define CMEDIA_PID1_MIN 0x0008 // range for CM108, CM109, CM119 (no following letters)
#define CMEDIA_PID1_MAX 0x000f
#define CMEDIA_PID_CM108AH 0x0139 // CM108AH
#define CMEDIA_PID_CM108B 0x0012 // CM108B
#define CMEDIA_PID_CM119A 0x013a // CM119A
#define CMEDIA_PID_CM119B 0x0013 // CM119B
#define CMEDIA_PID_HS100 0x013c // HS100
// The SSS chips seem to be pretty much compatible but they have only two GPIO.
// https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/
// Data sheet says VID/PID is from an EEPROM but mentions no default.
#define SSS_VID 0x0c76 // SSS1621, SSS1623
#define SSS_PID1 0x1605
#define SSS_PID2 0x1607
#define SSS_PID3 0x160b
// Device VID PID Number of GPIO
// ------ --- --- --------------
// CM108 0d8c 0008-000f * 4
// CM108AH 0d8c 0139 * 3 Has GPIO 1,3,4 but not 2
// CM108B 0d8c 0012 3 Has GPIO 1,3,4 but not 2
// CM109 0d8c 0008-000f * 8
// CM119 0d8c 0008-000f * 8
// CM119A 0d8c 013a * 8
// CM119B 0d8c 0013 8
// HS100 0d8c 013c 0
//
// SSS1621 0c76 1605 2 per ZL3AME, Can't find data sheet
// SSS1623 0c76 1607,160b 2 per ZL3AME, Not in data sheet.
//
// * idProduct programmable by MSEL and MODE pin.
//
// CMedia pin GPIO Notes
// ---------- ---- -----
// 43 1
// 11 2 N.C. for CM108AH, CM108B
// 13 3 Most popular for PTT because it is on the end.
// 15 4
// 16 5 CM109, CM119, CM119A, CM119B only
// 17 6 "
// 20 7 "
// 22 8 "
// Test for supported devices.
#define GOOD_DEVICE(v,p) ( (v == CMEDIA_VID && ((p >= CMEDIA_PID1_MIN && p <= CMEDIA_PID1_MAX) \
|| p == CMEDIA_PID_CM108AH \
|| p == CMEDIA_PID_CM108B \
|| p == CMEDIA_PID_CM119A \
|| p == CMEDIA_PID_CM119B )) \
|| \
(v == SSS_VID && (p == SSS_PID1 || p == SSS_PID2 || p == SSS_PID3)) )
// Look out for null source pointer, and avoid buffer overflow on destination.
#define SAFE_STRCPY(to,from) { if (from != NULL) { strncpy(to,from,sizeof(to)); to[sizeof(to)-1] = '\0'; } }
// Used to process regular expression matching results.
static void inline substr_se (char *dest, const char *src, int start, int endp1)
{
int len = endp1 - start;
if (start < 0 || endp1 < 0 || len <= 0) {
dest[0] = '\0';
return;
}
memcpy (dest, src + start, len);
dest[len] = '\0';
} /* end substr_se */
/*
* Result of taking inventory of USB soundcards and USB HIDs.
*/
struct thing_s {
int vid; // vendor id
int pid; // product id
char product[32]; // product name (e.g. manufacturer, model)
char devnode_sound[22]; // e.g. /dev/snd/pcmC0D0p
char plughw[15]; // Above in more familiar format e.g. plughw:0,0
char devnode_hidraw[17]; // e.g. /dev/hidraw3
char devnode_usb[25]; // e.g. /dev/bus/usb/001/012
};
int cm108_inventory (struct thing_s *things, int max_things);
/*-------------------------------------------------------------------
*
* Name: main
*
* Purpose: Test program to list USB audio and HID devices.
*
* sudo apt-get install libudev-dev
* gcc -DCM108_MAIN textcolor.c -l udev
*
*------------------------------------------------------------------*/
#define MAXX_THINGS 60
#ifdef CM108_MAIN
int main (void)
{
struct thing_s things[MAXX_THINGS];
int num_things;
int i;
text_color_init (0); // Turn off text color.
num_things = cm108_inventory (things, MAXX_THINGS);
dw_printf (" VID PID %-*s %-*s %-*s %-*s"
#if EXTRA
" %-*s"
#endif
"\n", (int)sizeof(things[0].product), "Product",
(int)sizeof(things[0].devnode_sound), "Sound",
(int)sizeof(things[0].plughw), "ADEVICE",
(int)sizeof(things[0].devnode_hidraw), "HID [ptt]"
#if EXTRA
, (int)sizeof(things[0].devnode_usb), "USB"
#endif
);
dw_printf (" --- --- %-*s %-*s %-*s %-*s"
#if EXTRA
" %-*s"
#endif
"\n", (int)sizeof(things[0].product), "-------",
(int)sizeof(things[0].devnode_sound), "-----",
(int)sizeof(things[0].plughw), "-------",
(int)sizeof(things[0].devnode_hidraw), "---------"
#if EXTRA
, (int)sizeof(things[0].devnode_usb), "---"
#endif
);
for (i = 0; i < num_things; i++) {
dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s"
#if EXTRA
" %-*s"
#endif
"\n",
GOOD_DEVICE(things[i].vid,things[i].pid) ? "**" : " ",
things[i].vid, things[i].pid,
(int)sizeof(things[i].product), things[i].product,
(int)sizeof(things[i].devnode_sound), things[i].devnode_sound,
(int)sizeof(things[0].plughw), things[i].plughw,
(int)sizeof(things[i].devnode_hidraw), things[i].devnode_hidraw
#if EXTRA
, (int)sizeof(things[i].devnode_usb), things[i].devnode_usb
#endif
);
}
return (0);
}
#endif // CM108_MAIN
/*-------------------------------------------------------------------
*
* Name: cm108_inventory
*
* Purpose: Take inventory of USB audio and HID.
*
* Inputs: max_things - Maximum number of items to collect.
*
* Outputs: things - Array of items collected.
* Corresponding sound device and HID are merged into one item.
*
* Returns: Number of items placed in things array.
* Should be in the range of 0 thru max_things.
* -1 for a bad unexpected error.
*
*------------------------------------------------------------------*/
int cm108_inventory (struct thing_s *things, int max_things)
{
struct udev *udev;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *dev_list_entry;
struct udev_device *dev;
struct udev_device *parentdev;
int num_things = 0;
memset (things, 0, sizeof(struct thing_s) * max_things);
/*
* First get a list of the USB audio devices.
* This is based on the example in http://www.signal11.us/oss/udev/
*/
udev = udev_new();
if (!udev) {
text_color_set(DW_COLOR_ERROR);
dw_printf("INTERNAL ERROR: Can't create udev.\n");
return (-1);
}
enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "sound");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices) {
const char *path;
path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(udev, path);
char const *devnode = udev_device_get_devnode(dev);
if (devnode != NULL) {
parentdev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device");
if (parentdev != NULL) {
char const *p;
int vid = 0;
int pid = 0;
p = udev_device_get_sysattr_value(parentdev,"idVendor");
if (p != NULL) vid = strtol(p, NULL, 16);
p = udev_device_get_sysattr_value(parentdev,"idProduct");
if (p != NULL) pid = strtol(p, NULL, 16);
if (num_things < max_things) {
things[num_things].vid = vid;
things[num_things].pid = pid;
SAFE_STRCPY (things[num_things].product, udev_device_get_sysattr_value(parentdev,"product"));
SAFE_STRCPY (things[num_things].devnode_sound, devnode);
SAFE_STRCPY (things[num_things].devnode_usb, udev_device_get_devnode(parentdev));
num_things++;
}
udev_device_unref(parentdev);
}
}
}
udev_enumerate_unref(enumerate);
udev_unref(udev);
/*
* Now merge in all of the USB HID.
*/
udev = udev_new();
if (!udev) {
text_color_set(DW_COLOR_ERROR);
dw_printf("INTERNAL ERROR: Can't create udev.\n");
return (-1);
}
enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "hidraw");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices) {
const char *path;
path = udev_list_entry_get_name(dev_list_entry);
dev = udev_device_new_from_syspath(udev, path);
char const *devnode = udev_device_get_devnode(dev);
if (devnode != NULL) {
parentdev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device");
if (parentdev != NULL) {
char const *p;
int vid = 0;
int pid = 0;
p = udev_device_get_sysattr_value(parentdev,"idVendor");
if (p != NULL) vid = strtol(p, NULL, 16);
p = udev_device_get_sysattr_value(parentdev,"idProduct");
if (p != NULL) pid = strtol(p, NULL, 16);
int j, matched = 0;
char const *usb = udev_device_get_devnode(parentdev);
// Add hidraw name to any matching existing.
for (j = 0; j < num_things; j++) {
if (things[j].vid == vid && things[j].pid == pid && usb != NULL && strcmp(things[j].devnode_usb,usb) == 0) {
matched = 1;
SAFE_STRCPY (things[j].devnode_hidraw, devnode);
}
}
// If it did not match to existing, add new entry.
if (matched == 0 && num_things < max_things) {
things[num_things].vid = vid;
things[num_things].pid = pid;
SAFE_STRCPY (things[num_things].product, udev_device_get_sysattr_value(parentdev,"product"));
SAFE_STRCPY (things[num_things].devnode_hidraw, devnode);
SAFE_STRCPY (things[num_things].devnode_usb, usb);
num_things++;
}
udev_device_unref(parentdev);
}
}
}
udev_enumerate_unref(enumerate);
udev_unref(udev);
/*
* Seeing the form /dev/snd/pcmC4D0p will be confusing to many because we
* would generally something like plughw:4,0 for in the direwolf configuration file.
* Construct the more familiar form.
*/
int i;
regex_t pcm_re;
char emsg[100];
int e = regcomp (&pcm_re, "pcmC([0-9]+)D([0-9]+)[cp]", REG_EXTENDED);
if (e) {
regerror (e, &pcm_re, emsg, sizeof(emsg));
text_color_set(DW_COLOR_ERROR);
dw_printf("INTERNAL ERROR: %s:%d: %s\n", __FILE__, __LINE__, emsg);
return (-1);
}
for (i = 0; i < num_things; i++) {
regmatch_t match[3];
if (regexec (&pcm_re, things[i].devnode_sound, 3, match, 0) == 0) {
char c[32], d[32];
substr_se (c, things[i].devnode_sound, match[1].rm_so, match[1].rm_eo);
substr_se (d, things[i].devnode_sound, match[2].rm_so, match[2].rm_eo);
snprintf (things[i].plughw, sizeof(things[i].plughw), "plughw:%s,%s", c, d);
}
}
return (num_things);
} /* end cm108_inventory */
/*-------------------------------------------------------------------
*
* Name: cm108_find_ptt
*
* Purpose: Try to find /dev/hidraw corresponding to plughw:n,n
*
* Inputs: output_audio_device
* - Device name used in the ADEVICE configuration.
* This would generally be something like plughw:1,0
*
* ptt_device_size - Size of result area to avoid buffer overflow.
*
* Outputs: ptt_device - Device name, something like /dev/hidraw2.
* Will be emptry string if no match found.
*
* Returns: none
*
*------------------------------------------------------------------*/
void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_device_size)
{
struct thing_s things[MAXX_THINGS];
int num_things;
int i;
strlcpy (ptt_device, "", ptt_device_size);
num_things = cm108_inventory (things, MAXX_THINGS);
for (i = 0; i < num_things; i++) {
if (GOOD_DEVICE(things[i].vid,things[i].pid) ) {
if (strcmp(output_audio_device, things[i].plughw) == 0) {
strlcpy (ptt_device, things[i].devnode_hidraw, ptt_device_size);
}
}
}
} /* end cm108_find_ptt */
/*-------------------------------------------------------------------
*
* Name: cm108_set_gpio_pin
*
* Purpose: Set one GPIO pin of the CM108 or similar.
*
* Inputs: name - Name of device such as /dev/hidraw2.
*
* num - GPIO number, range 1 thru 8.
*
* state - 1 for on, 0 for off.
*
* Returns: 0 for success. -1 for error.
*
* Errors: A descriptive error message will be printed for any problem.
*
* Future: For our initial implementation we are making the simplifying
* restriction of using only one GPIO pin per device and limit
* configuratin to PTT only.
* Longer term, we might want to have DCD, and maybe other
* controls thru the same chip.
* In this case, we would need to retain bit masks for each
* device so new data can be merged with old before sending it out.
*
*------------------------------------------------------------------*/
int cm108_set_gpio_pin (char *name, int num, int state)
{
int iomask;
int iodata;
if (num < 1 || num > 8) {
text_color_set(DW_COLOR_ERROR);
dw_printf("%s CM108 GPIO number %d must be in range of 1 thru 8.\n", name, num);
return (-1);
}
if (state != 0 && state != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("%s CM108 GPIO state %d must be 0 or 1.\n", name, state);
return (-1);
}
iomask = 1 << (num - 1);
iodata = state << (num - 1);
return (cm108_write (name, iomask, iodata));
} /* end cm108_set_gpio_pin */
/*-------------------------------------------------------------------
*
* Name: cm108_write
*
* Purpose: Set the GPIO pins of the CM108 or similar.
*
* Inputs: name - Name of device such as /dev/hidraw2.
*
* iomask - Bit mask for I/O direction.
* LSB is GPIO1, bit 1 is GPIO2, etc.
* 1 for output, 0 for input.
*
* iodata - Output data, same bit order as iomask.
*
* Returns: 0 for success. -1 for error.
*
* Errors: A descriptive error message will be printed for any problem.
*
* Description: This is the lowest level function.
* An application probably wants to use cm108_set_gpio_pin.
*
*------------------------------------------------------------------*/
static int cm108_write (char *name, int iomask, int iodata)
{
int fd;
struct hidraw_devinfo info;
char io[5];
int n;
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("TEMP DEBUG cm108_write: %s %d %d\n", name, iomask, iodata);
/*
* By default, the USB HID are accessible only by root:
*
* crw------- 1 root root 249, 1 ... /dev/hidraw1
*
* How should we handle this?
* Manually changing it will revert back on the next reboot or
* when the device is removed and reinserted.
*
* According to various articles on the Internet, we should be able to
* add a file to /etc/udev/rules.d. "99-direwolf-cmedia.rules" would be a
* suitable name. The leading number is the order. We want this to be
* near the end. I think the file extension must be ".rules."
*
* We could completely open it up to everyone like this:
*
* # Allow ordinary user to access CMedia GPIO for PTT.
* SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", MODE="0666"
*
* Whenever we have CMedia USB audio adapter, it should be accessible by everyone.
* This would not apply to other /dev/hidraw* corresponding to keyboard, mouse, etc.
*
* Notice the == (double =) for testing and := for setting a property.
*
* If you are concerned about security, you could restrict access to
* a particular group, something like this:
*
* SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", GROUP="audio", MODE="0660"
*
* I figure "audio" makes more sense than "gpio" because we need to be part of
* audio group to use the USB Audio adapter for sound.
*/
fd = open (name, O_WRONLY);
if (fd == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open %s for write, errno=%d\n", name, errno);
if (errno == EACCES) { // 13
dw_printf ("Type \"ls -l %s\" and verify that it has audio group rw similar to this:\n", name);
dw_printf (" crw-rw---- 1 root audio 247, 0 Oct 6 19:24 %s\n", name);
dw_printf ("rather than root-only access like this:\n");
dw_printf (" crw------- 1 root root 247, 0 Sep 24 09:40 %s\n", name);
}
return (-1);
}
// Just for fun, let's get the device information.
#if 1
n = ioctl(fd, HIDIOCGRAWINFO, &info);
if (n == 0) {
if ( ! GOOD_DEVICE(info.vendor, info.product)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ioctl HIDIOCGRAWINFO failed for %s. errno = %d.\n", name, errno);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%s is not a supported device type. Proceed at your own risk. vid=%04x pid=%04x\n", name, info.vendor, info.product);
}
#endif
// To make a long story short, I think we need 0 for the first two bytes.
io[0] = 0;
io[1] = 0;
io[2] = iomask;
io[3] = iodata;
io[4] = 0;
// Writing 4 bytes fails with errno 32, EPIPE, "broken pipe."
// Hamlib writes 5 bytes which I don't understand.
// Writing 5 bytes works.
// I have no idea why. From the CMedia datasheet it looks like we need 4.
n = write (fd, io, sizeof(io));
if (n != sizeof(io)) {
// Errors observed during development.
// as pi EACCES 13 /* Permission denied */
// as root EPIPE 32 /* Broken pipe - Happens if we send 4 bytes */
text_color_set(DW_COLOR_ERROR);
dw_printf ("Write to %s failed, n=%d, errno=%d\n", name, n, errno);
if (errno == EACCES) {
dw_printf ("Type \"ls -l %s\" and verify that it has audio group rw similar to this:\n", name);
dw_printf (" crw-rw---- 1 root audio 247, 0 Oct 6 19:24 %s\n", name);
dw_printf ("rather than root-only access like this:\n");
dw_printf (" crw------- 1 root root 247, 0 Sep 24 09:40 %s\n", name);
}
close (fd);
return (-1);
}
close (fd);
return (0);
} /* end cm108_write */
#endif // ifdef USE_CM108
/* end cm108.c */

5
cm108.h Normal file
View File

@ -0,0 +1,5 @@
/* Dire Wolf cm108.h */
extern void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_device_size);
extern int cm108_set_gpio_pin (char *name, int num, int state);

422
config.c
View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 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
@ -63,6 +63,10 @@
#include "tt_text.h"
#include "ax25_link.h"
#ifdef USE_CM108 // Linux only
#include "cm108.h"
#endif
// geotranz
#include "utm.h"
@ -515,7 +519,7 @@ static int check_via_path (char *via_path)
r = stemp;
while (( a = strsep(&r,",")) != NULL) {
int strict = 1;
int strict = 2;
int ok;
char addr[AX25_MAX_ADDR_LEN];
int ssid;
@ -590,7 +594,10 @@ static char *split (char *string, int rest_of_line)
{
static char cmd[MAXCMDLEN];
static char token[MAXCMDLEN];
static char *c; // current position in cmd.
static char shutup[] = " "; // Shut up static analysis which gets upset
// over the case where this could be called with
// string NULL and c was not yet initialized.
static char *c = shutup; // Current position in command line.
char *s, *t;
int in_quotes;
@ -790,6 +797,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;
p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;
p_audio_config->achan[channel].fulldup = DEFAULT_FULLDUP;
}
/* First channel should always be valid. */
@ -798,9 +806,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[0].valid = 1;
memset (p_digi_config, 0, sizeof(struct digi_config_s));
memset (p_digi_config, 0, sizeof(struct digi_config_s)); // APRS digipeater
p_digi_config->dedupe_time = DEFAULT_DEDUPE;
memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s));
memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s)); // Connected mode digipeater
memset (p_tt_config, 0, sizeof(struct tt_config_s));
p_tt_config->gateway_enabled = 0;
@ -864,19 +872,22 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_DEFAULT;
p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_DEFAULT;
p_igate_config->igmsp = 1;
p_igate_config->rx2ig_dedupe_time = IGATE_RX2IG_DEDUPE_TIME;
/* People find this confusing. */
/* Ideally we'd like to figure out if com0com is installed */
/* and automatically enable this. */
//strlcpy (p_misc_config->nullmodem, DEFAULT_NULLMODEM, sizeof(p_misc_config->nullmodem));
strlcpy (p_misc_config->nullmodem, "", sizeof(p_misc_config->nullmodem));
strlcpy (p_misc_config->kiss_serial_port, "", sizeof(p_misc_config->kiss_serial_port));
p_misc_config->kiss_serial_speed = 0;
p_misc_config->kiss_serial_poll = 0;
strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port));
strlcpy (p_misc_config->waypoint_port, "", sizeof(p_misc_config->waypoint_port));
strlcpy (p_misc_config->logdir, "", sizeof(p_misc_config->logdir));
p_misc_config->log_daily_names = 0;
strlcpy (p_misc_config->log_path, "", sizeof(p_misc_config->log_path));
/* connected mode. */
@ -891,6 +902,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */
p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 2; /* Max SABME before falling back to SABM. */
p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed. */
p_misc_config->v20_count = 0;
p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */
p_misc_config->noxid_count = 0;
/*
* Try to extract options from a file.
@ -971,20 +986,23 @@ void config_init (char *fname, struct audio_s *p_audio_config,
*
* ADEVICE plughw:1,0 -- same for in and out.
* ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair.
* ADEVICE1 udp:7355 default -- from Software defined radio (SDR) via UDP.
*
*/
/* Note that ALSA name can contain comma such as hw:1,0 */
if (strncasecmp(t, "ADEVICE", 7) == 0) {
/* "ADEVICE" is equivalent to "ADEVICE0". */
adevice = 0;
if (isdigit(t[7])) {
adevice = t[7] - '0';
if (strlen(t) >= 8) {
adevice = atoi(t+7);
}
if (adevice < 0 || adevice >= MAX_ADEVS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Device number %d out of range for ADEVICE command on line %d.\n", adevice, line);
dw_printf ("If you really need more than %d audio devices, increase MAX_ADEVS and recompile.\n", MAX_ADEVS);
adevice = 0;
continue;
}
@ -1192,6 +1210,24 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
else {
char *p;
int const strict = 2;
char call_no_ssid[AX25_MAX_ADDR_LEN];
int ssid, heard;
for (p = t; *p != '\0'; p++) {
if (islower(*p)) {
*p = toupper(*p); /* Silently force upper case. */
/* Might change to warning someday. */
}
}
if ( ! ax25_parse_addr (-1, t, strict, call_no_ssid, &ssid, &heard)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Invalid value for MYCALL command on line %d.\n", line);
continue;
}
// Definitely set for current channel.
// Set for other channels which have not been set yet.
@ -1204,17 +1240,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strcasecmp(p_audio_config->achan[c].mycall, "NOCALL") == 0 ||
strcasecmp(p_audio_config->achan[c].mycall, "N0CALL") == 0) {
char *p;
strlcpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall));
for (p = p_audio_config->achan[c].mycall; *p != '\0'; p++) {
if (islower(*p)) {
*p = toupper(*p); /* silently force upper case. */
}
}
// TODO: additional checks if valid.
// Should have a function to check for valid callsign[-ssid]
}
}
}
@ -1601,6 +1627,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* xxx LPT [-]bit-num
* PTT RIG model port
* PTT RIG AUTO port
* PTT CM108 [ [-]bit-num ] [ hid-device ]
*
* When model is 2, port would host:port like 127.0.0.1:4532
* Otherwise, port would be a serial port like /dev/ttyS0
@ -1629,7 +1656,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing serial port name for %s command.\n",
dw_printf ("Config file line %d: Missing output control device for %s command.\n",
line, otname);
continue;
}
@ -1725,9 +1752,97 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_HAMLIB;
#else
#if __WIN32__
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Windows version of direwolf does not support HAMLIB.\n", line);
exit (EXIT_FAILURE);
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s with RIG is only available when hamlib support is enabled.\n", line, otname);
dw_printf ("You must rebuild direwolf with hamlib support.\n");
dw_printf ("See User Guide for details.\n");
#endif
#endif
}
else if (strcasecmp(t, "CM108") == 0) {
/* CM108 - GPIO of USB sound card. case, Linux only. */
#ifdef USE_CM108
if (ot != OCTYPE_PTT) {
// Future project: Allow DCD and CON via the same device.
// This gets more complicated because we can't selectively change a single GPIO bit.
// We would need to keep track of what is currently there, change one bit, in our local
// copy of the status and then write out the byte for all of the pins.
// Let's keep it simple with just PTT for the first stab at this.
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: PTT CM108 option is only valid for PTT, not %s.\n", line, otname);
continue;
}
p_audio_config->achan[channel].octrl[ot].out_gpio_num = 3; // All known designs use GPIO 3.
// User can override for special cases.
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; // High for transmit.
strcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "");
// Try to find PTT device for audio output device.
// Simplifiying assumption is that we have one radio per USB Audio Adapter.
// Failure at this point is not an error.
// See if config file sets it explicitly before complaining.
cm108_find_ptt (p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out,
p_audio_config->achan[channel].octrl[ot].ptt_device,
(int)sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
while ((t = split(NULL,0)) != NULL) {
if (*t == '-') {
p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1);
p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
}
else if (isdigit(*t)) {
p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t);
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
}
else if (*t == '/') {
strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Found \"%s\" when expecting GPIO number or device name like /dev/hidraw1.\n", line, t);
continue;
}
}
if (p_audio_config->achan[channel].octrl[ot].out_gpio_num < 1 || p_audio_config->achan[channel].octrl[ot].out_gpio_num > 8) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: CM108 GPIO number %d is not in range of 1 thru 8.\n", line,
p_audio_config->achan[channel].octrl[ot].out_gpio_num);
continue;
}
if (strlen(p_audio_config->achan[channel].octrl[ot].ptt_device) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Could not determine USB Audio GPIO PTT device for audio output %s.\n", line,
p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out);
dw_printf ("You must explicitly mention a device name such as /dev/hidraw1.\n");
dw_printf ("See User Guide for details.\n");
continue;
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_CM108;
#else
#if __WIN32__
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: CM108 USB Audio GPIO PTT is not available for Windows.\n", line);
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s with CM108 is only available when USB Audio GPIO support is enabled.\n", line, otname);
dw_printf ("You must rebuild direwolf with CM108 Audio Adapter GPIO PTT support.\n");
dw_printf ("See User Guide for details.\n");
#endif
exit (EXIT_FAILURE);
#endif
}
else {
@ -1818,7 +1933,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
*
* TXINH - TX holdoff input
*
* xxx GPIO [-]gpio-num (only type supported yet)
* TXINH GPIO [-]gpio-num (only type supported so far)
*/
else if (strcasecmp(t, "TXINH") == 0) {
@ -1861,7 +1976,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* DWAIT - Extra delay for receiver squelch.
* DWAIT n - Extra delay for receiver squelch. n = 10 mS units.
*/
else if (strcasecmp(t, "DWAIT") == 0) {
@ -1885,7 +2000,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* SLOTTIME - For non-digipeat transmit delay timing.
* SLOTTIME n - For non-digipeat transmit delay timing. n = 10 mS units.
*/
else if (strcasecmp(t, "SLOTTIME") == 0) {
@ -1933,7 +2048,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* TXDELAY - For transmit delay timing.
* TXDELAY n - For transmit delay timing. n = 10 mS units.
*/
else if (strcasecmp(t, "TXDELAY") == 0) {
@ -1957,7 +2072,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* TXTAIL - For transmit timing.
* TXTAIL n - For transmit timing. n = 10 mS units.
*/
else if (strcasecmp(t, "TXTAIL") == 0) {
@ -1980,6 +2095,30 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
}
/*
* FULLDUP {on|off} - Full Duplex
*/
else if (strcasecmp(t, "FULLDUP") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing parameter for FULLDUP command. Expecting ON or OFF.\n", line);
continue;
}
if (strcasecmp(t, "ON") == 0) {
p_audio_config->achan[channel].fulldup = 1;
}
else if (strcasecmp(t, "OFF") == 0) {
p_audio_config->achan[channel].fulldup = 0;
}
else {
p_audio_config->achan[channel].fulldup = 0;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Expected ON or OFF for FULLDUP.\n", line);
}
}
/*
* SPEECH script
*
@ -2104,10 +2243,18 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
}
else if (strcasecmp(t, "DROP") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Preemptive digipeating DROP option is discouraged.\n", line);
dw_printf ("It can create a via path which is misleading about the actual path taken.\n");
dw_printf ("TRACE is the best choice for this feature.\n");
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP;
t = split(NULL,0);
}
else if (strcasecmp(t, "MARK") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Preemptive digipeating MARK option is discouraged.\n", line);
dw_printf ("It can create a via path which is misleading about the actual path taken.\n");
dw_printf ("TRACE is the best choice for this feature.\n");
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK;
t = split(NULL,0);
}
@ -2257,7 +2404,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
if (t != NULL) {
e = regcomp (&(p_cdigi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
if (e != 0) {
if (e == 0) {
p_cdigi_config->has_alias[from_chan][to_chan] = 1;
}
else {
regerror (e, &(p_cdigi_config->alias[from_chan][to_chan]), message, sizeof(message));
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n",
@ -2284,6 +2434,19 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* FILTER from-chan to-chan filter_specification_expression
* FILTER from-chan IG filter_specification_expression
* FILTER IG to-chan filter_specification_expression
*
*
* Note that we have three different config file filter commands:
*
* FILTER - Originally for APRS digipeating but later enhanced
* to include IGate client side. Maybe it should be
* renamed AFILTER to make it clearer after adding CFILTER.
*
* CFILTER - Similar for connected moded digipeater.
*
* IGFILTER - APRS-IS (IGate) server side - completely diffeent.
* I'm not happy with this name because IG sounds like IGate
* which is really the client side. More comments later.
*/
else if (strcasecmp(t, "FILTER") == 0) {
@ -2423,7 +2586,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = " "; /* Empty means permit nothing. */
}
p_cdigi_config->filter_str[from_chan][to_chan] = strdup(t);
p_cdigi_config->cfilter_str[from_chan][to_chan] = strdup(t);
//TODO1.2: Do a test run to see errors now instead of waiting.
@ -3919,6 +4082,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* IGFILTER - IGate Server side filters.
* Is this name too confusing. Too similar to FILTER IG 0 ...
* Maybe SSFILTER suggesting Server Side.
* SUBSCRIBE might be better because it's not a filter that limits.
*
* IGFILTER filter-spec ...
*/
@ -3927,6 +4093,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,1); /* Take rest of line as one string. */
if (p_igate_config->t2_filter != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Warning - Earlier IGFILTER value will be replaced by this one.\n", line);
continue;
}
if (t != NULL && strlen(t) > 0) {
p_igate_config->t2_filter = strdup (t);
}
@ -4005,13 +4177,13 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_igate_config->igmsp = n;
}
else {
p_igate_config->satgate_delay = 1;
p_igate_config->igmsp = 1;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable number of times for message sender position. Using default 1.\n", line);
}
}
else {
p_igate_config->satgate_delay = 1;
p_igate_config->igmsp = 1;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing number of times for message sender position. Using default 1.\n", line);
}
@ -4027,6 +4199,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
else if (strcasecmp(t, "SATGATE") == 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Line %d: SATGATE is pretty useless and will be removed in a future version.\n", line);
t = split(NULL,0);
if (t != NULL) {
@ -4102,17 +4277,58 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* NULLMODEM - Device name for our end of the virtual "null modem"
* NULLMODEM name [ speed ] - Device name for serial port or our end of the virtual "null modem"
* SERIALKISS name [ speed ]
*
* Version 1.5: Added SERIALKISS which is equivalent to NULLMODEM.
* The original name sort of made sense when it was used only for one end of a virtual
* null modem cable on Windows only. Now it is also available for Linux.
* TODO1.5: In retrospect, this doesn't seem like such a good name.
*/
else if (strcasecmp(t, "nullmodem") == 0) {
else if (strcasecmp(t, "NULLMODEM") == 0 || strcasecmp(t, "SERIALKISS") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing device name for my end of the 'null modem' on line %d.\n", line);
dw_printf ("Config file: Missing serial port name on line %d.\n", line);
continue;
}
else {
strlcpy (p_misc_config->nullmodem, t, sizeof(p_misc_config->nullmodem));
if (strlen(p_misc_config->kiss_serial_port) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Warning serial port name on line %d replaces earlier value.\n", line);
}
strlcpy (p_misc_config->kiss_serial_port, t, sizeof(p_misc_config->kiss_serial_port));
p_misc_config->kiss_serial_speed = 0;
p_misc_config->kiss_serial_poll = 0;
}
t = split(NULL,0);
if (t != NULL) {
p_misc_config->kiss_serial_speed = atoi(t);
}
}
/*
* SERIALKISSPOLL name - Poll for serial port name that might come and go.
* e.g. /dev/rfcomm0 for bluetooth.
*/
else if (strcasecmp(t, "SERIALKISSPOLL") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing serial port name on line %d.\n", line);
continue;
}
else {
if (strlen(p_misc_config->kiss_serial_port) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Warning serial port name on line %d replaces earlier value.\n", line);
}
strlcpy (p_misc_config->kiss_serial_port, t, sizeof(p_misc_config->kiss_serial_port));
p_misc_config->kiss_serial_speed = 0;
p_misc_config->kiss_serial_poll = 1; // set polling.
}
}
@ -4222,7 +4438,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* LOGDIR - Directory name for storing log files. Use "." for current working directory.
* LOGDIR - Directory name for automatically named daily log files. Use "." for current working directory.
*/
else if (strcasecmp(t, "logdir") == 0) {
t = split(NULL,0);
@ -4232,7 +4448,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue;
}
else {
strlcpy (p_misc_config->logdir, t, sizeof(p_misc_config->logdir));
if (strlen(p_misc_config->log_path) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: LOGDIR on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line);
}
p_misc_config->log_daily_names = 1;
strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path));
}
t = split(NULL,0);
if (t != NULL) {
@ -4241,6 +4462,31 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
}
/*
* LOGFILE - Log file name, including any directory part.
*/
else if (strcasecmp(t, "logfile") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing file name for LOGFILE on line %d.\n", line);
continue;
}
else {
if (strlen(p_misc_config->log_path) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: LOGFILE on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line);
}
p_misc_config->log_daily_names = 0;
strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path));
}
t = split(NULL,0);
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: LOGFILE on line %d should have file name and nothing more.\n", line);
}
}
/*
* BEACON channel delay every message
*
@ -4520,6 +4766,77 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
* V20 address [ address ... ] - Stations known to support only AX.25 v2.0.
* When connecting to these, skip SABME and go right to SABM.
* Possible to have multiple and they are cummulative.
*/
else if (strcasecmp(t, "V20") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing address(es) for V20.\n", line);
continue;
}
while (t != NULL) {
int const strict = 2;
char call_no_ssid[AX25_MAX_ADDR_LEN];
int ssid, heard;
if (ax25_parse_addr (AX25_DESTINATION, t, strict, call_no_ssid, &ssid, &heard)) {
p_misc_config->v20_addrs = (char**)realloc (p_misc_config->v20_addrs, sizeof(char*) * (p_misc_config->v20_count + 1));
p_misc_config->v20_addrs[p_misc_config->v20_count++] = strdup(t);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid station address for V20 command.\n", line);
// continue processing any others following.
}
t = split(NULL,0);
}
}
/*
* NOXID address [ address ... ] - Stations known not to understand XID.
* After connecting to these (with v2.2 obviously), don't try using XID commmand.
* AX.25 for Linux is the one known case so far.
* Possible to have multiple and they are cummulative.
*/
else if (strcasecmp(t, "NOXID") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing address(es) for NOXID.\n", line);
continue;
}
while (t != NULL) {
int const strict = 2;
char call_no_ssid[AX25_MAX_ADDR_LEN];
int ssid, heard;
if (ax25_parse_addr (AX25_DESTINATION, t, strict, call_no_ssid, &ssid, &heard)) {
p_misc_config->noxid_addrs = (char**)realloc (p_misc_config->noxid_addrs, sizeof(char*) * (p_misc_config->noxid_count + 1));
p_misc_config->noxid_addrs[p_misc_config->noxid_count++] = strdup(t);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid station address for NOXID command.\n", line);
// continue processing any others following.
}
t = split(NULL,0);
}
}
/*
* Invalid command.
*/
@ -4676,11 +4993,13 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
b->sendto_type = SENDTO_XMIT;
b->sendto_chan = 0;
b->delay = 60;
b->slot = G_UNKNOWN;
b->every = 600;
//b->delay = 6; // temp test.
//b->every = 3600;
b->lat = G_UNKNOWN;
b->lon = G_UNKNOWN;
b->ambiguity = 0;
b->alt_m = G_UNKNOWN;
b->symtab = '/';
b->symbol = '-'; /* house */
@ -4709,6 +5028,15 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
if (strcasecmp(keyword, "DELAY") == 0) {
b->delay = parse_interval(value,line);
}
else if (strcasecmp(keyword, "SLOT") == 0) {
int n = parse_interval(value,line);
if ( n < 1 || n > 3600) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Beacon time slot, %d, must be in range of 1 to 3600 seconds.\n", line, n);
continue;
}
b->slot = n;
}
else if (strcasecmp(keyword, "EVERY") == 0) {
b->every = parse_interval(value,line);
}
@ -4721,7 +5049,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
int n = atoi(value+1);
if ( n < 0 || n >= MAX_CHANS || ! p_audio_config->achan[n].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n);
continue;
}
b->sendto_type = SENDTO_RECV;
@ -4798,6 +5126,16 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
else if (strcasecmp(keyword, "LONG") == 0 || strcasecmp(keyword, "LON") == 0) {
b->lon = parse_ll (value, LON, line);
}
else if (strcasecmp(keyword, "AMBIGUITY") == 0 || strcasecmp(keyword, "AMBIG") == 0) {
int n = atoi(value);
if (n >= 0 && n <= 4) {
b->ambiguity = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Location ambiguity, on line %d, must be in range of 0 to 4.\n", line);
}
}
else if (strcasecmp(keyword, "ALT") == 0 || strcasecmp(keyword, "ALTITUDE") == 0) {
b->alt_m = atof(value);
}
@ -4865,7 +5203,13 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
if (b->custom_info != NULL && b->custom_infocmd != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time..\n", line);
dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time.\n", line);
}
if (b->compress && b->ambiguity != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Position ambiguity can't be used with compressed location format.\n", line);
b->ambiguity = 0;
}
/*
@ -4898,7 +5242,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: When any of ZONE, EASTING, NORTHING specifed, they must all be specified.\n", line);
dw_printf ("Config file, line %d: When any of ZONE, EASTING, NORTHING specified, they must all be specified.\n", line);
}
}

View File

@ -34,13 +34,24 @@ enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV };
struct misc_config_s {
int agwpe_port; /* Port number for the "AGW TCPIP Socket Interface" */
int kiss_port; /* Port number for the "KISS" protocol. */
int kiss_port; /* Port number for the "TCP KISS" protocol. */
int enable_kiss_pt; /* Enable pseudo terminal for KISS. */
/* Want this to be off by default because it hangs */
/* after a while if nothing is reading from other end. */
char nullmodem[20]; /* Serial port name for our end of the */
char kiss_serial_port[20];
/* Serial port name for our end of the */
/* virtual null modem for native Windows apps. */
/* Version 1.5 add same capability for Linux. */
int kiss_serial_speed; /* Speed, in bps, for the KISS serial port. */
/* If 0, just leave what was already there. */
int kiss_serial_poll; /* When using Bluetooth KISS, the /dev/rfcomm0 device */
/* will appear and disappear as the remote application */
/* opens and closes the virtual COM port. */
/* When this is non-zero, we will check periodically to */
/* see if the device has appeared and we will open it. */
char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */
/* e.g. COM22, /dev/ttyACM0 */
@ -66,7 +77,9 @@ struct misc_config_s {
#define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */
char logdir[80]; /* Directory for saving activity logs. */
int log_daily_names; /* True to generate new log file each day. */
char log_path[80]; /* Either directory or full file name depending on above. */
int sb_configured; /* TRUE if SmartBeaconing is configured. */
int sb_fast_speed; /* MPH */
@ -93,6 +106,17 @@ struct misc_config_s {
/* switching to SABM. This is to handle the case of an old */
/* TNC which simply ignores SABME rather than replying with FRMR. */
char **v20_addrs; /* Stations known to understand only AX.25 v2.0 so we don't */
/* waste time trying v2.2 first. */
int v20_count; /* Number of station addresses in array above. */
char **noxid_addrs; /* Stations known not to understand XID command so don't */
/* waste time sending it and eventually giving up. */
/* AX.25 for Linux is the one known case, so far, where */
/* SABME is implemented but XID is not. */
int noxid_count; /* Number of station addresses in array above. */
// Beacons.
@ -120,6 +144,9 @@ struct misc_config_s {
int delay; /* Seconds to delay before first transmission. */
int slot; /* Seconds after hour for slotted time beacons. */
/* If specified, it overrides any 'delay' value. */
int every; /* Time between transmissions, seconds. */
/* Remains fixed for PBEACON and OBEACON. */
/* Dynamically adjusted for TBEACON. */
@ -146,6 +173,7 @@ struct misc_config_s {
double lat; /* Latitude and longitude. */
double lon;
int ambiguity; /* Number of lower digits to trim from location. 0 (default), 1, 2, 3, 4. */
float alt_m; /* Altitude in meters. */
char symtab; /* Symbol table: / or \ or overlay character. */

View File

@ -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, 2017 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
@ -973,6 +973,8 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
*
* References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt
*
* This is up to date with the 24 Aug 16 version mentioning the TH-D74.
*
* Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt
*
* Examples: `b9Z!4y>/>"4N}Paul's_TH-D7
@ -1461,7 +1463,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
*
* A->g_message_number Message number if any. Required for ack/rej.
*
* Description: An APRS message is a text string with a specifed addressee.
* Description: An APRS message is a text string with a specified addressee.
*
* It's a lot more complicated with different types of addressees
* and replies with acknowledgement or rejection.
@ -3183,7 +3185,7 @@ double get_latitude_8 (char *p, int quiet)
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Error: '%c' found for latitude hemisphere. Specification requires upper case N or s.\n", plat->ns);
dw_printf("Error: '%c' found for latitude hemisphere. Specification requires upper case N or S.\n", plat->ns);
}
return (G_UNKNOWN);
}
@ -3704,8 +3706,12 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext)
*
* Windows version: File must be in current working directory.
*
* Linux version: Search order is current working directory
* then /usr/share/direwolf directory.
* Linux version: Search order is current working directory then
* /usr/local/share/direwolf
* /usr/share/direwolf/tocalls.txt
*
* Mac: Like Linux and then
* /opt/local/share/direwolf
*
*------------------------------------------------------------------*/
@ -3728,11 +3734,20 @@ static struct tocalls_s {
static int num_tocalls = 0;
// Make sure the array is null terminated.
// If search order is changed, do the same in symbols.c
static const char *search_locations[] = {
(const char *) "tocalls.txt",
#ifndef __WIN32__
(const char *) "/usr/share/direwolf/tocalls.txt",
(const char *) "/usr/local/share/direwolf/tocalls.txt",
(const char *) "/usr/share/direwolf/tocalls.txt",
#endif
#if __APPLE__
// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458
// Adding the /opt/local tree since macports typically installs there. Users might want their
// INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local
// path as well.
(const char *) "/opt/local/share/direwolf/tocalls.txt",
#endif
(const char *) NULL
};
@ -4437,6 +4452,26 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
* to stretch the numeric range to be 0 to 99.
*/
/*
* Here is an interesting case.
*
* W8SAT-1>T2UV0P:`qC<0x1f>l!Xu\'"69}WMNI EDS Response Unit #1|+/%0'n|!w:X!|3
*
* Let's break that down into pieces.
*
* W8SAT-1>T2UV0P:`qC<0x1f>l!Xu\'"69} MIC-E format
* N 42 56.0000, W 085 39.0300,
* 0 MPH, course 160, alt 709 ft
* WMNI EDS Response Unit #1 comment
* |+/%0'n| base 91 telemetry
* !w:X! DAO
* |3 Tiny Track 3
*
* Comment earlier points out that MIC-E format has resolution of 0.01 minute,
* same as non-compressed format, so the DAO does work out, after thinking
* about it for a while.
*/
/*
* The spec appears to be wrong. It says '}' is the maximum value when it should be '{'.
*/
@ -4583,6 +4618,14 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
*
* WB2OSZ-1>APN383,qAR,N1EDU-2:!4237.14NS07120.83W#PHG7130Chelmsford, MA
*
* New for 1.5:
*
* Also allow hexadecimal bytes for raw AX.25 or KISS. e.g.
*
* 00 82 a0 ae ae 62 60 e0 82 96 68 84 40 40 60 9c 68 b0 ae 86 40 e0 40 ae 92 88 8a 64 63 03 f0 3e 45 4d 36 34 6e 65 2f 23 20 45 63 68 6f 6c 69 6e 6b 20 31 34 35 2e 33 31 30 2f 31 30 30 68 7a 20 54 6f 6e 65
*
* If it begins with 00 or C0 (which would be impossible for AX.25 address) process as KISS.
* Also print these formats.
*
* Outputs: stdout
*
@ -4614,11 +4657,16 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
* TODO: To make it more useful,
* - Remove any leading timestamp.
* - Remove any "qA*" and following from the path.
* - Handle non-APRS frames properly.
*
*------------------------------------------------------------------*/
#if DECAMAIN
#include "kiss_frame.h"
/* Stub for stand-alone decoder. */
void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol,
@ -4627,11 +4675,48 @@ void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab,
return;
}
// TODO: hex_dump is currently in server.c and we don't want to drag that in.
// Someday put it in a more reasonable place, with other general utilities, and remove the private copy here.
static void hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
dw_printf (" %03x: ", offset);
for (i=0; i<n; i++) {
dw_printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
dw_printf (" ");
}
dw_printf (" ");
for (i=0; i<n; i++) {
dw_printf ("%c", isprint(p[i]) ? p[i] : '.');
}
dw_printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}
// Do we have two hexadecimal digits followed by whitespace or end of line?
#define ISHEX2(x) (isxdigit(x[0]) && isxdigit(x[1]) && (x[2] == '\0' || isspace(x[2])))
#define MAXLINE 9000
#define MAXBYTES 3000
int main (int argc, char *argv[])
{
char stuff[300];
char stuff[MAXLINE];
unsigned char bytes[MAXBYTES];
int num_bytes;
char *p;
packet_t pp;
@ -4687,36 +4772,150 @@ int main (int argc, char *argv[])
text_color_set(DW_COLOR_REC);
dw_printf("\n%s\n", stuff);
pp = ax25_from_text(stuff, 1);
if (pp != NULL)
{
// Do we have monitor format, KISS, or AX.25 frame?
p = stuff;
while (isspace(*p)) p++;
if (ISHEX2(p)) {
// Collect a bunch of hexadecimal numbers.
num_bytes = 0;
while (ISHEX2(p) && num_bytes < MAXBYTES) {
bytes[num_bytes++] = strtoul(p, NULL, 16);
p += 2;
while (isspace(*p)) p++;
}
if (num_bytes == 0 || *p != '\0') {
text_color_set(DW_COLOR_ERROR);
dw_printf("Parse error around column %d.\n", (int)(long)(p - stuff) + 1);
dw_printf("Was expecting only space separated 2 digit hexadecimal numbers.\n\n");
continue; // next line
}
// If we have 0xC0 at start, remove it and expect same at end.
if (bytes[0] == FEND) {
if (num_bytes < 2 || bytes[1] != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Was expecting to find 00 after the initial C0.\n");
continue;
}
if (bytes[num_bytes-1] == FEND) {
text_color_set(DW_COLOR_INFO);
dw_printf("Removing KISS FEND characters at beginning and end.\n");
int n;
for (n = 0; n < num_bytes-1; n++) {
bytes[n] = bytes[n+1];
}
num_bytes -= 2;
}
else {
text_color_set(DW_COLOR_INFO);
dw_printf("Removing KISS FEND character at beginning. Was expecting another at end.\n");
int n;
for (n = 0; n < num_bytes-1; n++) {
bytes[n] = bytes[n+1];
}
num_bytes -= 1;
}
}
if (bytes[0] == 0) {
// Treat as KISS. Undo any KISS encoding.
unsigned char kiss_frame[MAXBYTES];
int kiss_len = num_bytes;
memcpy (kiss_frame, bytes, num_bytes);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("--- KISS frame ---\n");
hex_dump (kiss_frame, kiss_len);
// Put FEND at end to keep kiss_unwrap happy.
// Having one at the begining is optional.
kiss_frame[kiss_len++] = FEND;
// In the more general case, we would need to include
// the command byte because it could be escaped.
// Here we know it is 0, so we take a short cut and
// remove it before, rather than after, the conversion.
num_bytes = kiss_unwrap (kiss_frame + 1, kiss_len - 1, bytes);
}
// Treat as AX.25.
alevel_t alevel;
memset (&alevel, 0, sizeof(alevel));
pp = ax25_from_frame(bytes, num_bytes, alevel);
if (pp != NULL) {
char addrs[120];
unsigned char *pinfo;
int info_len;
decode_aprs_t A;
// log directory option someday?
decode_aprs (&A, pp, 0);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("--- AX.25 frame ---\n");
ax25_hex_dump (pp);
dw_printf ("-------------------\n");
//Print it all out in human readable format.
ax25_format_addrs (pp, addrs);
text_color_set(DW_COLOR_DECODED);
dw_printf ("%s", addrs);
decode_aprs_print (&A);
info_len = ax25_get_info (pp, &pinfo);
ax25_safe_print ((char *)pinfo, info_len, 1); // Display non-ASCII to hexadecimal.
dw_printf ("\n");
/*
* Perform validity check on each address.
* This should print an error message if any issues.
*/
(void)ax25_check_addresses(pp);
decode_aprs (&A, pp, 0); // Extract information into structure.
// Send to log file?
decode_aprs_print (&A); // Now print it in human readable format.
// if (logdir != NULL && *logdir != '\0') {
// log_write (&A, pp, logdir);
// }
(void)ax25_check_addresses(pp); // Errors for invalid addresses.
ax25_delete (pp);
}
else
{
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("\n%s\n", "ERROR - Could not parse input!\n");
dw_printf("Could not construct AX.25 frame from bytes supplied!\n\n");
}
}
else {
// Normal monitoring format.
pp = ax25_from_text(stuff, 1);
if (pp != NULL) {
decode_aprs_t A;
decode_aprs (&A, pp, 0); // Extract information into structure.
decode_aprs_print (&A); // Now print it in human readable format.
// This seems to be redundant because we used strict option
// when parsing the monitoring format text.
//(void)ax25_check_addresses(pp); // Errors for invalid addresses.
// Future? Add -d option to include hex dump and maybe KISS?
ax25_delete (pp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR - Could not parse monitoring format input!\n\n");
}
}
}
}

View File

@ -639,9 +639,13 @@ int demod_init (struct audio_s *pa)
/* Not sure if it should be on for ARM too. */
/* Need to take a look at CPU usage and performance difference. */
#ifndef __arm__
/* Version 1.5: Remove special case for ARM. */
/* We want higher performance to be the default. */
/* "MODEM 9600 -" can be used on very slow CPU if necessary. */
//#ifndef __arm__
strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
#endif
//#endif
}
#ifdef TUNE_UPSAMPLE

View File

@ -491,7 +491,9 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_
*/
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
// Perform the add as unsigned to avoid signed overflow error.
D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample));
if ( D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) {

View File

@ -1122,7 +1122,9 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data,
*/
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
// Perform the add as unsigned to avoid signed overflow error.
D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample));
//text_color_set(DW_COLOR_DEBUG);
// dw_printf ("prev = %lx, new data clock pll = %lx\n" D->prev_d_c_pll, D->data_clock_pll);

View File

@ -794,7 +794,8 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits,
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
// Perform the add as unsigned to avoid signed overflow error.
D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample));
if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll >= 0) {

View File

@ -484,6 +484,16 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
break;
}
// Idea: Here is an interesting idea for a new option. REORDER?
// The preemptive digipeater could move its call after the (formerly) last used digi field
// and preserve all the unused fields after that. The list of used addresses would
// accurately record the journey taken by the packet.
// https://groups.yahoo.com/neo/groups/aprsisce/conversations/topics/31935
// > I was wishing for a non-marking preemptive digipeat so that the original packet would be left intact
// > or maybe something like WIDE1-1,WIDE2-1,KJ4OVQ-9 becoming KJ4OVQ-9*,WIDE1-1,WIDE2-1.
return (result);
}
}

View File

@ -38,8 +38,6 @@ struct digi_config_s {
enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
//char type_filter[MAX_CHANS][MAX_CHANS][20]; // TODO1.2: remove this
char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
// NULL or optional Packet Filter strings such as "t/m".
// Notice the size of arrays is one larger than normal.

View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017 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
@ -94,6 +94,7 @@
#include "server.h"
#include "kiss.h"
#include "kissnet.h"
#include "kissserial.h"
#include "kiss_frame.h"
#include "waypoint.h"
#include "gen_tone.h"
@ -116,6 +117,7 @@
#include "morse.h"
#include "mheard.h"
#include "ax25_link.h"
#include "dtime_now.h"
//static int idx_decoded = 0;
@ -162,8 +164,8 @@ static void __cpuid(int cpuinfo[4], int infotype){
static struct audio_s audio_config;
static struct tt_config_s tt_config;
//struct digi_config_s digi_config;
//struct cdigi_config_s cdigi_config;
static struct misc_config_s misc_config;
static const int audio_amplitude = 100; /* % of audio sample range. */
/* This translates to +-32k for 16 bit samples. */
@ -177,9 +179,6 @@ static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of A
static struct misc_config_s misc_config;
int main (int argc, char *argv[])
{
int err;
@ -193,8 +192,10 @@ int main (int argc, char *argv[])
struct igate_config_s igate_config;
int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */
char P_opt[16];
char l_opt[80];
char l_opt_logdir[80];
char L_opt_logfile[80];
char input_file[80];
char T_opt_timestamp[40];
int t_opt = 1; /* Text color option. */
int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */
@ -213,8 +214,10 @@ int main (int argc, char *argv[])
int E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */
int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */
strlcpy(l_opt, "", sizeof(l_opt));
strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir));
strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile));
strlcpy(P_opt, "", sizeof(P_opt));
strlcpy(T_opt_timestamp, "", sizeof(T_opt_timestamp));
#if __WIN32__
@ -259,17 +262,21 @@ 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, "H", __DATE__);
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
//dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "C", __DATE__);
dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB)
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108)
dw_printf ("Includes optional support for: ");
#if defined(ENABLE_GPSD)
dw_printf (" gpsd");
#endif
#if defined(USE_HAMLIB)
dw_printf (" hamlib");
#endif
#if defined(USE_CM108)
dw_printf (" cm108-ptt");
#endif
dw_printf ("\n");
#endif
@ -345,7 +352,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:E:",
c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:L:Sa:E:T:",
long_options, &option_index);
if (c == -1)
break;
@ -466,7 +473,7 @@ int main (int argc, char *argv[])
case 'a': server_set_debug(1); break;
case 'k': d_k_opt++; kiss_serial_set_debug (d_k_opt); break;
case 'k': d_k_opt++; kissserial_set_debug (d_k_opt); kisspt_set_debug (d_k_opt); break;
case 'n': d_n_opt++; kiss_net_set_debug (d_n_opt); break;
case 'u': d_u_opt = 1; break;
@ -521,11 +528,17 @@ int main (int argc, char *argv[])
exit (0);
break;
case 'l': /* -l for log file directory name */
case 'l': /* -l for log directory with daily files */
strlcpy (l_opt, optarg, sizeof(l_opt));
strlcpy (l_opt_logdir, optarg, sizeof(l_opt_logdir));
break;
case 'L': /* -L for log file name with full path */
strlcpy (L_opt_logfile, optarg, sizeof(L_opt_logfile));
break;
case 'S': /* Print symbol tables and exit. */
symbols_init ();
@ -554,6 +567,10 @@ int main (int argc, char *argv[])
}
break;
case 'T': /* -T for receive timestamp. */
strlcpy (T_opt_timestamp, optarg, sizeof(T_opt_timestamp));
break;
default:
/* Should not be here. */
@ -657,14 +674,27 @@ int main (int argc, char *argv[])
audio_config.achan[0].decimate = D_opt;
}
strlcpy(audio_config.timestamp_format, T_opt_timestamp, sizeof(audio_config.timestamp_format));
// temp - only xmit errors.
audio_config.xmit_error_rate = E_tx_opt;
audio_config.recv_error_rate = E_rx_opt;
if (strlen(l_opt) > 0) {
strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir));
if (strlen(l_opt_logdir) > 0 && strlen(L_opt_logfile) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Logging options -l and -L can't be used together. Pick one or the other.\n");
exit(1);
}
if (strlen(L_opt_logfile) > 0) {
misc_config.log_daily_names = 0;
strlcpy (misc_config.log_path, L_opt_logfile, sizeof(misc_config.log_path));
}
else if (strlen(l_opt_logdir) > 0) {
misc_config.log_daily_names = 1;
strlcpy (misc_config.log_path, l_opt_logdir, sizeof(misc_config.log_path));
}
misc_config.enable_kiss_pt = enable_pseudo_terminal;
@ -766,7 +796,8 @@ int main (int argc, char *argv[])
/*
* Create a pseudo terminal and KISS TNC emulator.
*/
kiss_init (&misc_config);
kisspt_init (&misc_config);
kissserial_init (&misc_config);
kiss_frame_init (&audio_config);
/*
@ -782,7 +813,7 @@ int main (int argc, char *argv[])
* log the tracker beacon transmissions with fake channel 999.
*/
log_init(misc_config.logdir);
log_init(misc_config.log_daily_names, misc_config.log_path);
mheard_init (d_m_opt);
beacon_init (&audio_config, &misc_config, &igate_config);
@ -879,6 +910,14 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
ax25_alevel_to_text (alevel, alevel_text);
// Experiment: try displaying the DC bias.
// Should be 0 for soundcard but could show mistuning with SDR.
#if 0
char bias[16];
snprintf (bias, sizeof(bias), " DC%+d", multi_modem_get_dc_average (chan));
strlcat (alevel_text, bias, sizeof(alevel_text));
#endif
/* As suggested by KJ4ERJ, if we are receiving from */
/* WIDEn-0, it is quite likely (but not guaranteed), that */
@ -926,9 +965,21 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
// -1 for APRStt DTMF decoder.
char ts[100]; // optional time stamp
if (strlen(audio_config.timestamp_format) > 0) {
char tstmp[100];
timestamp_user_format (tstmp, sizeof(tstmp), audio_config.timestamp_format);
strlcpy (ts, " ", sizeof(ts)); // space after channel.
strlcat (ts, tstmp, sizeof(ts));
}
else {
strlcpy (ts, "", sizeof(ts));
}
if (subchan == -1) {
text_color_set(DW_COLOR_REC);
dw_printf ("[%d.dtmf] ", chan);
dw_printf ("[%d.dtmf%s] ", chan, ts);
}
else {
if (ax25_is_aprs(pp)) {
@ -939,16 +990,16 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
}
if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers == 1) {
dw_printf ("[%d.%d] ", chan, subchan);
dw_printf ("[%d.%d%s] ", chan, subchan, ts);
}
else if (audio_config.achan[chan].num_subchan == 1 && audio_config.achan[chan].num_slicers > 1) {
dw_printf ("[%d.%d] ", chan, slice);
dw_printf ("[%d.%d%s] ", chan, slice, ts);
}
else if (audio_config.achan[chan].num_subchan > 1 && audio_config.achan[chan].num_slicers > 1) {
dw_printf ("[%d.%d.%d] ", chan, subchan, slice);
dw_printf ("[%d.%d.%d%s] ", chan, subchan, slice, ts);
}
else {
dw_printf ("[%d] ", chan);
dw_printf ("[%d%s] ", chan, ts);
}
}
@ -972,7 +1023,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
dw_printf ("(%s)", desc);
if (ftype == frame_type_U_XID) {
struct xid_param_s param;
char info2text[100];
char info2text[150];
xid_parse (pinfo, info_len, &param, info2text, sizeof(info2text));
dw_printf (" %s\n", info2text);
@ -1078,15 +1129,17 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
/* Send to another application if connected. */
// TODO: Put a wrapper around this so we only call one function to send by all methods.
// We see the same sequence in tt_user.c.
int flen;
unsigned char fbuf[AX25_MAX_PACKET_LEN];
flen = ax25_pack(pp, fbuf);
server_send_rec_packet (chan, pp, fbuf, flen);
kissnet_send_rec_packet (chan, fbuf, flen);
kiss_send_rec_packet (chan, fbuf, flen);
server_send_rec_packet (chan, pp, fbuf, flen); // AGW net protocol
kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS TCP
kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS serial port
kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS pseudo terminal
/*
* If it came from DTMF decoder, send it to APRStt gateway.
@ -1204,7 +1257,7 @@ static void usage (char **argv)
dw_printf (" -D n Divide audio sample rate by n for channel 0.\n");
dw_printf (" -d Debug options:\n");
dw_printf (" a a = AGWPE network protocol client.\n");
dw_printf (" k k = KISS serial port client.\n");
dw_printf (" k k = KISS serial port or pseudo terminal client.\n");
dw_printf (" n n = KISS network client.\n");
dw_printf (" u u = Display non-ASCII text in hexadecimal.\n");
dw_printf (" p p = dump Packets in hexadecimal.\n");
@ -1230,6 +1283,7 @@ static void usage (char **argv)
dw_printf (" -x Send Xmit level calibration tones.\n");
dw_printf (" -U Print UTF-8 test string and exit.\n");
dw_printf (" -S Print symbol tables and exit.\n");
dw_printf (" -T fmt Time stamp format for sent and received frames.\n");
dw_printf ("\n");
dw_printf ("After any options, there can be a single command line argument for the source of\n");

View File

@ -32,7 +32,6 @@
#endif
/*
* Previously, we could handle only a single audio device.
* This meant we could have only two radio channels.
@ -120,6 +119,43 @@
#endif
#ifdef __APPLE__
// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2072
// The original suggestion was to add this to only ptt.c.
// I thought it would make sense to put it here, so it will apply to all files,
// consistently, rather than only one file ptt.c.
// The placement of this is critical. Putting it earlier was a problem.
// https://github.com/wb2osz/direwolf/issues/113
// It needs to be after the include pthread.h because
// pthread.h pulls in <sys/cdefs.h>, which redefines __DARWIN_C_LEVEL back to ansi,
// which breaks things.
// Maybe it should just go in ptt.c as originally suggested.
// #define __DARWIN_C_LEVEL __DARWIN_C_FULL
// There is a more involved patch here:
// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458
#ifndef _DARWIN_C_SOURCE
#define _DARWIN_C_SOURCE
#endif
// Defining _DARWIN_C_SOURCE ensures that the definition for the cfmakeraw function (or similar)
// are pulled in through the include file <sys/termios.h>.
#ifdef __DARWIN_C_LEVEL
#undef __DARWIN_C_LEVEL
#endif
#define __DARWIN_C_LEVEL __DARWIN_C_FULL
#endif
/* Not sure where to put these. */
/* Prefix with DW_ because /usr/include/gps.h uses a couple of these names. */
@ -213,6 +249,18 @@ typedef pthread_mutex_t dw_mutex_t;
// Formerly used write/read on Linux, for some forgotten reason,
// but always using send/recv makes more sense.
// Need option to prevent a SIGPIPE signal on Linux. (added for 1.5 beta 2)
#if __WIN32__ || __APPLE__
#define SOCK_SEND(s,data,size) send(s,data,size,0)
#else
#define SOCK_SEND(s,data,size) send(s,data,size, MSG_NOSIGNAL)
#endif
#define SOCK_RECV(s,data,size) recv(s,data,size,0)
/* Platform differences for string functions. */

View File

@ -1,20 +1,31 @@
#%global git_commit b2548ec58f44f4b651626757a166b9f4f18d8000
%global git_commit 37179479caf0bf36adf8c9bc0fde641884edaeac
%global git_date 20171216
%global git_short_commit %(echo %{git_commit} | cut -c -8)
%global git_suffix %{git_date}git%{git_short_commit}
Name: direwolf
Version: 1.1b1
Release: 1%{?dist}
Version: 1.5Beta
Release: 1.%{git_suffix}%{?dist}
Summary: Soundcard based AX.25 TNC
Group: Applications/Communications
License: GPLv2
URL: http://home.comcast.net/~wb2osz
Source0: http://home.comcast.net/~wb2osz/Version%201.1/direwolf-%{version}.tgz
URL: https://github.com/wb2osz/direwolf
#Source0: https://github.com/wb2osz/direwolf/archive/%{name}-%{version}.tar.gz
Source: %{name}-%{version}-%{git_suffix}.tgz
Packager: David Ranch (KI6ZHD) <dranch@trinnet.net>
Distribution: RedHat Linux
Patch0: direwolf-makefile7.patch
Patch0: direwolf-1.5-makefile.patch
BuildRequires: automake
BuildRequires: alsa-lib-devel
#If the gpsd and gpsd-devel packages are installed, Direwolf will add gps support
%description
Dire Wolf is a software "soundcard" modem/TNC and APRS encoder/decoder. It can
@ -29,34 +40,64 @@ Linux AX25, SARTrack, RMS Express, and many others.
%patch0 -p0
%build
make -f Makefile.linux tocalls-symbols
make %{?_smp_mflags} -f Makefile.linux
make -f Makefile.linux
#make -f Makefile.linux tocalls-symbols
make %{?_smp_mflags}
%install
make -f Makefile.linux install DESTDIR=$RPM_BUILD_ROOT
make -f Makefile.linux install-conf DESTDIR=$RPM_BUILD_ROOT
make install INSTALLDIR=$RPM_BUILD_ROOT/usr
make install-conf INSTALLDIR=$RPM_BUILD_ROOT/usr
# Install icon
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/
cp dw-icon.png ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf/
cp dw-icon.png ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf/
mv symbols-new.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/
mv symbolsX.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/
mv tocalls.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/
desktop-file-install \
--dir=${RPM_BUILD_ROOT}%{_datadir}/applications direwolf.desktop
#temp bug
#non echo "fixing $RPM_BUILD_ROOT/%{_bindir}/bin"
#non rm -f $RPM_BUILD_ROOT/usr/bin
%files
%{_sysconfdir}/ax25/direwolf.conf
%{_sysconfdir}/ax25/sdr.conf
%{_sysconfdir}/ax25/telemetry-toolkit/telem-balloon.conf
%{_sysconfdir}/ax25/telemetry-toolkit/telem-m0xer-3.txt
%{_sysconfdir}/ax25/telemetry-toolkit/telem-volts.conf
%{_sysconfdir}/udev/rules.d/99-direwolf-cmedia.rules
%{_bindir}/*
%{_datadir}/pixmaps/dw-icon.png
%{_datadir}/pixmaps/direwolf/dw-icon.png
%{_datadir}/applications/%{name}.desktop
%{_datadir}/direwolf/*
%{_docdir}/%{name}/*
%{_docdir}/*
%{_mandir}/man1/*
%changelog
* Sat Dec 16 2017 David Ranch <dranch@trinnet.net> - 1.5-1
- New 1.5-Beta version from Git
* Sun Apr 2 2017 David Ranch <dranch@trinnet.net> - 1.4-1
- New 1.4-Beta1 version from Git
* Sun Mar 5 2017 David Ranch <dranch@trinnet.net> - 1.4-1
- New 1.4-H Alpha version from Git version
* Fri Aug 26 2016 David Ranch <dranch@trinnet.net> - 1.4-1
- New version
* Fri May 06 2016 David Ranch <dranch@trinnet.net> - 1.3-1
- New version
* Sat Sep 12 2015 David Ranch <dranch@trinnet.net> - 1.3F-1
- New version with new features
* Sun May 10 2015 David Ranch <dranch@trinnet.net> - 1.2E-1
- New version that supports a PASSALL function
- Updated the Makefile.linux patch
* Sat Mar 21 2015 David Ranch <dranch@trinnet.net> - 1.2C-1
- changed to support different make installation variable
* Sat Feb 14 2015 David Ranch <dranch@trinnet.net> - 1.2b-1
- new spec file
* Sat Dec 20 2014 David Ranch <dranch@trinnet.net> - 1.1b1-1
- new spec file

View File

@ -312,7 +312,7 @@ W# COM3 and the client application will use COM4.
W#
W# Uncomment following line to use this feature.
W
W#NULLMODEM COM3
W#SERIALKISS COM3
W
W
C#
@ -434,22 +434,26 @@ C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14
C
C
C# To relay messages from the Internet to radio, you need to add
C# one more option with the transmit channel number and a VIA path.
C# one more options with the transmit channel number and a VIA path.
C
C#IGTXVIA 0 WIDE1-1
C
C# You might want to apply a filter for what packets will be obtained from the server.
C# Read about filters here: http://www.aprs-is.net/javaprsfilter.aspx
C# The APRS Internet Server (APRS-IS) has its own idea about what you
C# should be transmitting. This includes "messages" addressed to stations
C# recently heard in your area. For special situations, you can subscribe
C# to
C# decrease what you are already subscribed to. This is known as a server
C# side filter. Read here: http://www.aprs-is.net/javaprsfilter.aspx
C# Example, positions and objects within 50 km of my location:
C
C#IGFILTER m/50
C
C# That is known as a server-side filter. It is processed by the IGate server.
C# You can also apply local filtering to limit what will be transmitted on the
C# RF side. For example, transmit only "messages" on channel 0 and weather
C# reports on channel 1.
C# Sometimes the server will send you more than you want. You can also apply
C# local filtering to limit what will be transmitted on the RF side.
C# For example, transmit only "messages" (which is the default) on channel 0
C# and weather reports on channel 1.
C
C#FILTER IG 0 t/m
C#FILTER IG 0 i/30
C#FILTER IG 1 t/wn
C
C# Finally, we don't want to flood the radio channel.

47
dlq.c
View File

@ -767,6 +767,53 @@ void dlq_channel_busy (int chan, int activity, int status)
/*-------------------------------------------------------------------
*
* Name: dlq_seize_confirm
*
* Purpose: Inform data link state machine that the transmitter is on.
* This is in reponse to lm_seize_request.
*
* Inputs: chan - Radio channel number.
*
* Outputs: Request is appended to queue for processing by
* the data link state machine.
*
* Description: When removed from the data link state machine queue, this
* becomes lm_seize_confirm.
*
*--------------------------------------------------------------------*/
void dlq_seize_confirm (int chan)
{
struct dlq_item_s *pnew;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dlq_seize_confirm (chan=%d)\n", chan);
#endif
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
s_new_count++;
pnew->type = DLQ_SEIZE_CONFIRM;
pnew->chan = chan;
/* Put it into queue. */
append_to_queue (pnew);
} /* end dlq_seize_confirm */
/*-------------------------------------------------------------------
*
* Name: dlq_client_cleanup

4
dlq.h
View File

@ -35,7 +35,7 @@ typedef struct cdata_s {
/* Types of things that can be in queue. */
typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_CLIENT_CLEANUP} dlq_type_t;
typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_SEIZE_CONFIRM, DLQ_CLIENT_CLEANUP} dlq_type_t;
/* A queue item. */
@ -116,6 +116,8 @@ void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client
void dlq_channel_busy (int chan, int activity, int status);
void dlq_seize_confirm (int chan);
void dlq_client_cleanup (int client);

Binary file not shown.

BIN
doc/Bluetooth-KISS-TNC.pdf Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Documentation for Dire Wolf #
# Documentation for Dire Wolf #
Click on the document name to view in your web browser or the link following to download the PDF file.
@ -25,6 +25,10 @@ These dive into more detail for specialized topics or typical usage scenarios.
This explains how it all works, proper configuration, and troubleshooting.
- [**Bluetooth KISS TNC**](Bluetooth-KISS-TNC.pdf) [ [*download*](../../../raw/master/doc/Bluetooth-KISS-TNC.pdf) ]
Eliminate the cable between your TNC and application. Use Bluetooth instead.
- [**APRStt Implementation Notes**](APRStt-Implementation-Notes.pdf) [ [*download*](../../../raw/master/doc/APRStt-Implementation-Notes.pdf) ]
Very few hams have portable equipment for APRS but nearly everyone has a handheld radio that can send DTMF tones. APRStt allows a user, equipped with only DTMF (commonly known as Touch Tone) generation capability, to enter information into the global APRS data network.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,8 @@
#include "direwolf.h"
#include <stdio.h>
#include "textcolor.h"
#include "dtime_now.h"
@ -17,8 +20,28 @@
#include <sys/time.h>
#endif
#include <string.h> // needed for Mac.
/*------------------------------------------------------------------
*
* Name: dtime_now
*
* Purpose: Return current time as double precision.
*
* Input: none
*
* Returns: Unix time, as double precision, so we can get resolution
* finer than one second.
*
* Description: Normal unix time is in seconds since 1/1/1970 00:00:00 UTC.
* Sometimes we want resolution finer than a second.
* Rather than having a separate variable for the fractional
* part of a second, and having extra calculations everywhere,
* simply use double precision floating point to make usage
* easier.
*
*---------------------------------------------------------------*/
double dtime_now (void)
@ -59,3 +82,148 @@ double dtime_now (void)
return (result);
}
#if __WIN32__
/*
* Windows doesn't have localtime_r.
* It should have the equivalent localtime_s, with opposite parameter
* order, but I get undefined reference when trying to use it.
*/
static struct tm *localtime_r(time_t *clock, struct tm *res)
{
struct tm *tm;
tm = localtime (clock);
memcpy (res, tm, sizeof(struct tm));
return (res);
}
#endif
/*------------------------------------------------------------------
*
* Name: timestamp_now
*
* Purpose: Convert local time to one of these formats for debug output.
*
* HH:MM:SS
* HH:MM:SS.mmm
*
* Input: result_size - Size of result location.
* Should be at least 9 or 13.
*
* show_ms - True to display milliseconds.
*
* Output: result - Result is placed here.
*
*---------------------------------------------------------------*/
void timestamp_now (char *result, int result_size, int show_ms)
{
double now = dtime_now();
time_t t = (int)now;
struct tm tm;
localtime_r (&t, &tm);
strftime (result, result_size, "%H:%M:%S", &tm);
if (show_ms) {
int ms = (now - (int)t) * 1000;
char strms[16];
if (ms == 1000) ms = 999;
sprintf (strms, ".%03d", ms);
strlcat (result, strms, result_size);
}
} /* end timestamp_now */
/*------------------------------------------------------------------
*
* Name: timestamp_user_format
*
* Purpose: Convert local time user-specified format. e.g.
*
* HH:MM:SS
* mm/dd/YYYY HH:MM:SS
* dd/mm/YYYY HH:MM:SS
*
* Input: result_size - Size of result location.
*
* user_format - See strftime documentation.
*
* https://linux.die.net/man/3/strftime
* https://msdn.microsoft.com/en-us/library/aa272978(v=vs.60).aspx
*
* Note that Windows does not support all of the Linux formats.
* For example, Linux has %T which is equivalent to %H:%M:%S
*
* Output: result - Result is placed here.
*
*---------------------------------------------------------------*/
void timestamp_user_format (char *result, int result_size, char *user_format)
{
double now = dtime_now();
time_t t = (int)now;
struct tm tm;
localtime_r (&t, &tm);
strftime (result, result_size, user_format, &tm);
} /* end timestamp_user_format */
/*------------------------------------------------------------------
*
* Name: timestamp_filename
*
* Purpose: Generate unique file name based on the current time.
* The format will be:
*
* YYYYMMDD-HHMMSS-mmm
*
* Input: result_size - Size of result location.
* Should be at least 20.
*
* Output: result - Result is placed here.
*
* Description: This is for the kissutil "-r" option which places
* each received frame in a new file. It is possible to
* have two packets arrive in less than a second so we
* need more than one second resolution.
*
* What if someone wants UTC, rather than local time?
* You can simply set an environment variable like this:
*
* TZ=UTC direwolf
*
* so it's probably not worth the effort to add another
* option.
*
*---------------------------------------------------------------*/
void timestamp_filename (char *result, int result_size)
{
double now = dtime_now();
time_t t = (int)now;
struct tm tm;
localtime_r (&t, &tm);
strftime (result, result_size, "%Y%m%d-%H%M%S", &tm);
int ms = (now - (int)t) * 1000;
char strms[16];
if (ms == 1000) ms = 999;
sprintf (strms, "-%03d", ms);
strlcat (result, strms, result_size);
} /* end timestamp_filename */

View File

@ -1,3 +1,9 @@
extern double dtime_now (void);
void timestamp_now (char *result, int result_size, int show_ms);
void timestamp_user_format (char *result, int result_size, char *user_format);
void timestamp_filename (char *result, int result_size);

View File

@ -87,6 +87,22 @@ unsigned short fcs_calc (unsigned char *data, int len)
}
/*
* CRC is also used for duplicate checking for the digipeater and IGate.
* A packet is considered a duplicate if the source, destination, and
* information parts match. In other words, we ignore the via path
* which changes along the way.
* Rather than keeping a variable length string we just keep a 16 bit
* CRC which takes less memory and processing to compare.
*
* This can result in occasional false matches. If we had a random
* 16 bit number, there is a 1/65536 ( = 0.0015 % ) chance that it will
* match and we will drop something that should be passed along.
*
* Looking at it another way, there is a 0.9999847412109375 (out of 1)
* probability of doing the right thing.
*/
/*
* This can be used when we want to calculate a single CRC over disjoint data.
*

View File

@ -84,7 +84,8 @@
static int seed = 1;
static int my_rand (void) {
seed = ((seed * 1103515245) + 12345) & MY_RAND_MAX;
// Perform the calculation as unsigned to avoid signed overflow error.
seed = (int)(((unsigned)seed * 1103515245) + 12345) & MY_RAND_MAX;
return (seed);
}
@ -254,7 +255,7 @@ int main(int argc, char **argv)
modem.achan[0].baud = atoi(optarg);
text_color_set(DW_COLOR_INFO);
dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
if (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD) {
if (modem.achan[0].baud != 100 && (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD)) {
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);
@ -263,7 +264,12 @@ int main(int argc, char **argv)
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
if (modem.achan[0].baud < 600) {
if (modem.achan[0].baud == 100) {
modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1615;
modem.achan[0].space_freq = 1785;
}
else if (modem.achan[0].baud < 600) {
modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1600; // Typical for HF SSB
modem.achan[0].space_freq = 1800;
@ -446,7 +452,7 @@ int main(int argc, char **argv)
if (strlen(output_file) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: The -o ouput file option must be specified.\n");
dw_printf ("ERROR: The -o output file option must be specified.\n");
usage (argv);
exit (1);
}
@ -619,7 +625,7 @@ int main(int argc, char **argv)
*/
for (i = 1; i <= packet_count; i++) {
char stemp[80];
char stemp[88];
if (modem.achan[0].baud < 600) {
/* e.g. 300 bps AFSK - About 2/3 should be decoded properly. */

View File

@ -868,7 +868,7 @@ long Convert_MGRS_To_Geodetic (char* MGRS,
*/
{ /* Convert_MGRS_To_Geodetic */
long zone;
char hemisphere;
char hemisphere = '?';
double easting;
double northing;
long zone_exists;
@ -1260,9 +1260,9 @@ long Convert_MGRS_To_UPS ( char *MGRS,
double false_northing; /* False northing for 3rd letter */
double grid_easting; /* easting for 100,000 meter grid square */
double grid_northing; /* northing for 100,000 meter grid square */
long zone;
long zone = 0;
long letters[MGRS_LETTERS];
long in_precision;
long in_precision = 0;
int index = 0;
long error_code = MGRS_NO_ERROR;

View File

@ -782,7 +782,7 @@ long Convert_USNG_To_Geodetic (char* USNG,
*/
{ /* Convert_USNG_To_Geodetic */
long zone;
char hemisphere;
char hemisphere = '?';
double easting;
double northing;
long zone_exists;
@ -1178,9 +1178,9 @@ long Convert_USNG_To_UPS ( char *USNG,
double false_northing; /* False northing for 3rd letter */
double grid_easting; /* easting for 100,000 meter grid square */
double grid_northing; /* northing for 100,000 meter grid square */
long zone;
long zone = 0;
long letters[USNG_LETTERS];
long in_precision;
long in_precision = 0;
int index = 0;
long error_code = USNG_NO_ERROR;

64
igate.c
View File

@ -33,6 +33,9 @@
* APRS iGate properties
* http://wiki.ham.fi/APRS_iGate_properties
*
* Notes to iGate developers
* https://github.com/hessu/aprsc/blob/master/doc/IGATE-HINTS.md#igates-dropping-duplicate-packets-unnecessarily
*
* SATgate mode.
* http://www.tapr.org/pipermail/aprssig/2016-January/045283.html
*
@ -1263,7 +1266,7 @@ static void send_msg_to_server (const char *imsg, int imsg_len)
#if __WIN32__
err = send (igate_sock, stemp, stemp_len, 0);
err = SOCK_SEND (igate_sock, stemp, stemp_len);
if (err == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
@ -1274,7 +1277,7 @@ static void send_msg_to_server (const char *imsg, int imsg_len)
WSACleanup();
}
#else
err = write (igate_sock, stemp, stemp_len);
err = SOCK_SEND (igate_sock, stemp, stemp_len);
if (err <= 0)
{
text_color_set(DW_COLOR_ERROR);
@ -1316,11 +1319,7 @@ static int get1ch (void)
// TODO: might read complete packets and unpack from own buffer
// rather than using a system call for each byte.
#if __WIN32__
n = recv (igate_sock, (char*)(&ch), 1, 0);
#else
n = read (igate_sock, &ch, 1);
#endif
n = SOCK_RECV (igate_sock, (char*)(&ch), 1);
if (n == 1) {
#if DEBUG9
@ -1959,9 +1958,37 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
* There is a 1 / 65536 chance of getting a false positive match
* which is good enough for this application.
*
*
* Original thinking:
*
* Occasionally someone will get on one of the discussion groups and say:
* I don't think my IGate is working. I look at packets, from local stations,
* on aprs.fi or findu.com, and they are always through some other IGate station,
* never mine.
* Then someone has to explain, this is not a valid strategy for analyzing
* everything going thru the network. The APRS-IS servers drop duplicate
* packets (ignoring the via path) within a 30 second period. If some
* other IGate gets the same thing there a millisecond faster than you,
* the one you send is discarded.
* In this scenario, it would make sense to perform additional duplicate
* suppression before forwarding RF packets to the Server.
* I don't recall if I saw some specific recommendation to do this or if
* it just seemed like the obvious thing to do to avoid sending useless
* stuff that would just be discarded anyhow. It seems others came to the
* same conclusion. http://www.tapr.org/pipermail/aprssig/2016-July/045907.html
*
* Version 1.5: Rethink strategy.
*
* Issue 85, https://github.com/wb2osz/direwolf/issues/85 ,
* got me thinking about this some more. Sending more information will
* allow the APRS-IS servers to perform future additional network analysis.
* To make a long story short, the RF>IS direction duplicate checking
* is now disabled. The code is still there in case I change my mind
* and want to add a configuration option to allow it. The dedupe
* time is set to 0 which means don't do the checking.
*
*--------------------------------------------------------------------*/
#define RX2IG_DEDUPE_TIME 60 /* Do not send duplicate within 60 seconds. */
#define RX2IG_HISTORY_MAX 30 /* Remember the last 30 sent to IGate server. */
static int rx2ig_insert_next;
@ -1982,6 +2009,12 @@ static void rx_to_ig_init (void)
static void rx_to_ig_remember (packet_t pp)
{
// No need to save the information if we are not doing duplicate checking.
if (save_igate_config_p->rx2ig_dedupe_time == 0) {
return;
}
rx2ig_time_stamp[rx2ig_insert_next] = time(NULL);
rx2ig_checksum[rx2ig_insert_next] = ax25_dedupe_crc(pp);
@ -2031,8 +2064,21 @@ static int rx_to_ig_allow (packet_t pp)
dw_printf ("rx_to_ig_allow? %d \"%s>%s:%s\"\n", crc, src, dest, pinfo);
}
// Do we have duplicate checking at all in the RF>IS direction?
if (save_igate_config_p->rx2ig_dedupe_time == 0) {
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("rx_to_ig_allow? YES, no dedupe checking\n");
}
return 1;
}
// Yes, check for duplicates within certain time.
for (j=0; j<RX2IG_HISTORY_MAX; j++) {
if (rx2ig_checksum[j] == crc && rx2ig_time_stamp[j] >= now - RX2IG_DEDUPE_TIME) {
if (rx2ig_checksum[j] == crc && rx2ig_time_stamp[j] >= now - save_igate_config_p->rx2ig_dedupe_time) {
if (s_debug >= 2) {
text_color_set(DW_COLOR_DEBUG);
// could be multiple entries and this might not be the most recent.

12
igate.h
View File

@ -39,6 +39,9 @@ struct igate_config_s {
char t2_passcode[8]; /* Max. 5 digits. Could be "-1". */
char *t2_filter; /* Optional filter for IS -> RF direction. */
/* This is the "server side" filter. */
/* A better name would be subscription or something */
/* like that because we can only ask for more. */
/*
* For transmitting.
@ -69,6 +72,11 @@ struct igate_config_s {
/* We allow additional flexibility of 0 to disable feature */
/* or a small number to allow more. */
/*
* Receiver to IS data options.
*/
int rx2ig_dedupe_time; /* seconds. 0 to disable. */
/*
* Special SATgate mode to delay packets heard directly.
*/
@ -82,6 +90,10 @@ struct igate_config_s {
#define IGATE_TX_LIMIT_5_DEFAULT 20
#define IGATE_TX_LIMIT_5_MAX 80
#define IGATE_RX2IG_DEDUPE_TIME 0 /* Issue 85. 0 means disable dupe checking in RF>IS direction. */
/* See comments in rx_to_ig_remember & rx_to_ig_allow. */
/* Currently there is no configuration setting to change this. */
#define DEFAULT_SATGATE_DELAY 10
#define MIN_SATGATE_DELAY 5
#define MAX_SATGATE_DELAY 30

619
kiss.c
View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014, 2016 John Langner, WB2OSZ
// Copyright (C) 2011, 2013, 2014, 2016, 2017 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@ -24,11 +24,7 @@
* Module: kiss.c
*
* Purpose: Act as a virtual KISS TNC for use by other packet radio applications.
* On Windows, it is a serial port. On Linux, a pseudo terminal.
*
* Input:
*
* Outputs:
* This file implements it with a pseudo terminal for Linux only.
*
* Description: It implements the KISS TNC protocol as described in:
* http://www.ka9q.net/papers/kiss.html
@ -49,94 +45,82 @@
*
* Commands from application recognized:
*
* 0 Data Frame AX.25 frame in raw format.
* _0 Data Frame AX.25 frame in raw format.
*
* 1 TXDELAY See explanation in xmit.c.
* _1 TXDELAY See explanation in xmit.c.
*
* 2 Persistence " "
* _2 Persistence " "
*
* 3 SlotTime " "
* _3 SlotTime " "
*
* 4 TXtail " "
* _4 TXtail " "
* Spec says it is obsolete but Xastir
* sends it and we respect it.
*
* 5 FullDuplex Ignored. Always full duplex.
* _5 FullDuplex Ignored.
*
* 6 SetHardware TNC specific. Ignored.
* _6 SetHardware TNC specific.
*
* FF Return Exit KISS mode. Ignored.
*
*
* Messages sent to client application:
*
* 0 Data Frame Received AX.25 frame in raw format.
*
* _0 Data Frame Received AX.25 frame in raw format.
*
*
* Platform differences:
*
* We can use a pseudo terminal for Linux or Cygwin applications.
* However, Microsoft Windows doesn't seem to have similar functionality.
* Native Windows applications expect to see a device named COM1,
* COM2, COM3, or COM4. Some might offer more flexibility but others
* might be limited to these four choices.
*
* The documentation instucts the user to install the com0com
* "Null-modem emulator" from http://sourceforge.net/projects/com0com/
* and configure it for COM3 & COM4.
*
* By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!)
* and the client application will use COM4 (available as /dev/ttyS or
* /dev/com4 for Cygwin applications).
*
*
* This can get confusing.
*
* If __WIN32__ is defined,
* We use the Windows interface to the specfied serial port.
* This could be a real serial port or the nullmodem driver
* connected to another application.
*
* If __CYGWIN__ is defined,
* We connect to a serial port as in the previous case but
* use the Linux I/O interface.
* We also supply a pseudo terminal for any Cygwin applications
* such as Xastir so the null modem is not needed.
*
* For the Linux case,
* We supply a pseudo terminal for use by other applications.
*
*
* Reference: http://www.robbayer.com/files/serial-win.pdf
* Version 1.5: Split serial port version off into its own file.
*
*---------------------------------------------------------------*/
#if __WIN32__ // Stub for Windows.
#include "direwolf.h"
#include "kiss.h"
void kisspt_init (struct misc_config_s *mc)
{
return;
}
void kisspt_set_debug (int n)
{
return;
}
void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client)
{
return;
}
#else // Rest of file is for Linux only.
#include "direwolf.h"
#include <stdio.h>
#include <unistd.h>
#if __WIN32__
#include <stdlib.h>
#else
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#ifdef __OpenBSD__
#include <errno.h>
#else
#include <sys/errno.h>
#endif
#endif
#include <assert.h>
#include <string.h>
#include "tq.h"
#include "ax25_pad.h"
@ -146,71 +130,38 @@
#include "xmit.h"
#if __WIN32__
typedef HANDLE MYFDTYPE;
#define MYFDERROR INVALID_HANDLE_VALUE
#else
typedef int MYFDTYPE;
#define MYFDERROR (-1)
#endif
/*
* Accumulated KISS frame and state of decoder.
*/
static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
static kiss_frame_t kf;
/*
* These are for a Linux/Cygwin pseudo terminal.
* These are for a Linux pseudo terminal.
*/
#if ! __WIN32__
static MYFDTYPE pt_master_fd = MYFDERROR; /* File descriptor for my end. */
static int pt_master_fd = -1; /* File descriptor for my end. */
static char pt_slave_name[32]; /* Pseudo terminal slave name */
/* like /dev/pts/999 */
/*
* Symlink to pseudo terminal name which changes.
*/
#define TMP_KISSTNC_SYMLINK "/tmp/kisstnc"
#endif
/*
* This is for native Windows applications and a virtual null modem.
*/
#if __CYGWIN__ || __WIN32__
static MYFDTYPE nullmodem_fd = MYFDERROR;
#endif
static void * kisspt_listen_thread (void *arg);
// TODO: define in one place, use everywhere.
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
static int kisspt_debug = 0; /* Print information flowing from and to client. */
static THREAD_F kiss_listen_thread (void *arg);
#if DEBUG9
static FILE *log_fp;
#endif
static int kiss_debug = 0; /* Print information flowing from and to client. */
void kiss_serial_set_debug (int n)
void kisspt_set_debug (int n)
{
kiss_debug = n;
kisspt_debug = n;
}
@ -224,12 +175,12 @@ void hex_dump (unsigned char *p, int len);
/*-------------------------------------------------------------------
*
* Name: kiss_init
* Name: kisspt_init
*
* Purpose: Set up a pseudo terminal acting as a virtual KISS TNC.
*
*
* Inputs: mc->nullmodem - name of device for our end of nullmodem.
* Inputs:
*
* Outputs:
*
@ -240,43 +191,28 @@ void hex_dump (unsigned char *p, int len);
*
*--------------------------------------------------------------------*/
#if __WIN32__
static MYFDTYPE kiss_open_nullmodem (char *device);
#else
static MYFDTYPE kiss_open_pt (void);
#endif
static int kisspt_open_pt (void);
void kiss_init (struct misc_config_s *mc)
void kisspt_init (struct misc_config_s *mc)
{
#if __WIN32__
HANDLE kiss_nullmodem_listen_th;
#else
pthread_t kiss_pterm_listen_tid;
//pthread_t kiss_nullmodem_listen_tid;
int e;
#endif
memset (&kf, 0, sizeof(kf));
/*
* This reads messages from client.
*/
#if ! __WIN32__
/*
* Pseudo terminal for Cygwin and Linux versions.
*/
pt_master_fd = MYFDERROR;
pt_master_fd = -1;
if (mc->enable_kiss_pt) {
pt_master_fd = kiss_open_pt ();
pt_master_fd = kisspt_open_pt ();
if (pt_master_fd != MYFDERROR) {
e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kiss_listen_thread, NULL);
if (pt_master_fd != -1) {
e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kisspt_listen_thread, NULL);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create kiss listening thread for Linux pseudo terminal");
@ -284,75 +220,25 @@ void kiss_init (struct misc_config_s *mc)
}
}
else {
text_color_set(DW_COLOR_INFO);
dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n");
//text_color_set(DW_COLOR_INFO);
//dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n");
}
#endif
#if __CYGWIN__ || __WIN32
/*
* Cygwin and native Windows versions have serial port connection.
*/
if (strlen(mc->nullmodem) > 0) {
#if ! __WIN32__
/* Translate Windows device name into Linux name. */
/* COM1 -> /dev/ttyS0, etc. */
if (strncasecmp(mc->nullmodem, "COM", 3) == 0) {
int n = atoi (mc->nullmodem + 3);
text_color_set(DW_COLOR_INFO);
dw_printf ("Converted nullmodem device '%s'", mc->nullmodem);
if (n < 1) n = 1;
snprintf (mc->nullmodem, sizeof(mc->nullmodem), "/dev/ttyS%d", n-1);
dw_printf (" to Linux equivalent '%s'\n", mc->nullmodem);
}
#endif
nullmodem_fd = kiss_open_nullmodem (mc->nullmodem);
if (nullmodem_fd != MYFDERROR) {
#if __WIN32__
kiss_nullmodem_listen_th = (HANDLE)_beginthreadex (NULL, 0, kiss_listen_thread, NULL, 0, NULL);
if (kiss_nullmodem_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create kiss nullmodem thread\n");
return;
}
#else
e = pthread_create (&kiss_nullmodem_listen_tid, NULL, kiss_listen_thread, NULL);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create kiss listening thread for Windows virtual COM port.");
}
#endif
}
}
#endif
#if DEBUG
text_color_set (DW_COLOR_DEBUG);
#if ! __WIN32__
dw_printf ("end of kiss_init: pt_master_fd = %d\n", pt_master_fd);
#endif
#if __CYGWIN__ || __WIN32__
dw_printf ("end of kiss_init: nullmodem_fd = %d\n", nullmodem_fd);
dw_printf ("end of kisspt_init: pt_master_fd = %d\n", pt_master_fd);
#endif
#endif
}
/*
* Returns fd for master side of pseudo terminal or MYFDERROR for error.
* Returns fd for master side of pseudo terminal or -1 for error.
*/
#if ! __WIN32__
static MYFDTYPE kiss_open_pt (void)
static int kisspt_open_pt (void)
{
int fd;
char *pts;
@ -362,19 +248,18 @@ static MYFDTYPE kiss_open_pt (void)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kiss_open_pt ( )\n");
dw_printf ("kisspt_open_pt ( )\n");
#endif
fd = posix_openpt(O_RDWR|O_NOCTTY);
if (fd == MYFDERROR
|| grantpt (fd) == MYFDERROR
|| unlockpt (fd) == MYFDERROR
if (fd == -1
|| grantpt (fd) == -1
|| unlockpt (fd) == -1
|| (pts = ptsname (fd)) == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n");
return (MYFDERROR);
return (-1);
}
strlcpy (pt_slave_name, pts, sizeof(pt_slave_name));
@ -430,11 +315,11 @@ static MYFDTYPE kiss_open_pt (void)
#if 1
// Sample code shows this. Why would we open it here?
// On Ubuntu, the slave side disappears after a few
// seconds if no one opens it. Same on Raspian which
// seconds if no one opens it. Same on Raspbian which
// is also based on Debian.
// Need to revisit this.
MYFDTYPE pt_slave_fd;
int pt_slave_fd;
pt_slave_fd = open(pt_slave_name, O_RDWR|O_NOCTTY);
@ -442,7 +327,7 @@ static MYFDTYPE kiss_open_pt (void)
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't open %s\n", pt_slave_name);
perror ("");
return MYFDERROR;
return -1;
}
#endif
@ -471,155 +356,20 @@ static MYFDTYPE kiss_open_pt (void)
return (fd);
}
#endif
/*
* Returns fd for our side of null modem or MYFDERROR for error.
*/
#if __CYGWIN__ || __WIN32__
static MYFDTYPE kiss_open_nullmodem (char *devicename)
{
#if __WIN32__
MYFDTYPE fd;
DCB dcb;
int ok;
char bettername[50];
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
#endif
#if DEBUG9
log_fp = fopen ("kiss-debug.txt", "w");
#endif
// Need to use FILE_FLAG_OVERLAPPED for full duplex operation.
// Without it, write blocks when waiting on read.
// Read http://support.microsoft.com/kb/156932
// Bug fix in release 1.1 - Need to munge name for COM10 and up.
// http://support.microsoft.com/kb/115831
strlcpy (bettername, devicename, sizeof(bettername));
if (strncasecmp(devicename, "COM", 3) == 0) {
int n;
n = atoi(devicename+3);
if (n >= 10) {
strlcpy (bettername, "\\\\.\\", sizeof(bettername));
strlcat (bettername, devicename, sizeof(bettername));
}
}
fd = CreateFile(bettername, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (fd == MYFDERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename);
return (MYFDERROR);
}
/* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */
memset (&dcb, 0, sizeof(dcb));
dcb.DCBlength = sizeof(DCB);
ok = GetCommState (fd, &dcb);
if (! ok) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("kiss_open_nullmodem: GetCommState failed.\n");
}
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = CBR_9600; // shouldn't matter
dcb.fBinary = 1;
dcb.fParity = 0;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fDtrControl = 0;
dcb.fDsrSensitivity = 0;
dcb.fOutX = 0;
dcb.fInX = 0;
dcb.fErrorChar = 0;
dcb.fNull = 0; /* Don't drop nul characters! */
dcb.fRtsControl = 0;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
ok = SetCommState (fd, &dcb);
if (! ok) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("kiss_open_nullmodem: SetCommState failed.\n");
}
text_color_set(DW_COLOR_INFO);
dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
#else
/* Cygwin version. */
int fd;
struct termios ts;
int e;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
#endif
fd = open (devicename, O_RDWR);
if (fd == MYFDERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename);
return (MYFDERROR);
}
e = tcgetattr (fd, &ts);
if (e != 0) { perror ("nm tcgetattr"); }
cfmakeraw (&ts);
ts.c_cc[VMIN] = 1; /* wait for at least one character */
ts.c_cc[VTIME] = 0; /* no fancy timing. */
e = tcsetattr (fd, TCSANOW, &ts);
if (e != 0) { perror ("nm tcsetattr"); }
text_color_set(DW_COLOR_INFO);
dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
#endif
return (fd);
}
#endif
/*-------------------------------------------------------------------
*
* Name: kiss_send_rec_packet
* Name: kisspt_send_rec_packet
*
* Purpose: Send a received packet or text string to the client app.
*
* Inputs: chan - Channel number where packet was received.
* 0 = first, 1 = second if any.
*
* kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have
* KISS_CMD_SET_HARDWARE when responding to a query.
*
* pp - Identifier for packet object.
*
* fbuf - Address of raw received frame buffer
@ -628,6 +378,10 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
* flen - Length of raw received frame not including the FCS
* or -1 for a text string.
*
* client - Not used for pseudo terminal.
* Here so that 3 related functions all have
* the same parameter list.
*
* Description: Send message to client.
* We really don't care if anyone is listening or not.
* I don't even know if we can find out.
@ -635,28 +389,20 @@ static MYFDTYPE kiss_open_nullmodem (char *devicename)
*--------------------------------------------------------------------*/
void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
int kiss_len;
int err;
#if ! __WIN32__
if (pt_master_fd == MYFDERROR) {
if (pt_master_fd == -1) {
return;
}
#endif
#if __CYGWIN__ || __WIN32__
if (nullmodem_fd == MYFDERROR) {
return;
}
#endif
if (flen < 0) {
flen = strlen((char*)fbuf);
if (kiss_debug) {
if (kisspt_debug) {
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
}
strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
@ -664,15 +410,18 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
}
else {
unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
assert (flen < (int)(sizeof(stemp)));
if (flen > (int)(sizeof(stemp)) - 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nPseudo Terminal KISS buffer too small. Truncated.\n\n");
flen = (int)(sizeof(stemp)) - 1;
}
stemp[0] = (chan << 4) + 0;
stemp[0] = (chan << 4) | kiss_cmd;
memcpy (stemp+1, fbuf, flen);
if (kiss_debug >= 2) {
if (kisspt_debug >= 2) {
/* AX.25 frame with the CRC removed. */
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
@ -684,16 +433,12 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
/* This has KISS framing and escapes for sending to client app. */
if (kiss_debug) {
if (kisspt_debug) {
kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
}
}
#if ! __WIN32__
/* Pseudo terminal for Cygwin and Linux. */
err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
if (err == -1 && errno == EWOULDBLOCK) {
@ -709,84 +454,18 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
perror ("pt write");
}
#endif
#if __CYGWIN__ || __WIN32__
/*
* This write can block if nothing is connected to the other end.
* The solution is found in the com0com ReadMe file:
*
* Q. My application hangs during its startup when it sends anything to one paired
* COM port. The only way to unhang it is to start HyperTerminal, which is connected
* to the other paired COM port. I didn't have this problem with physical serial
* ports.
* A. Your application can hang because receive buffer overrun is disabled by
* default. You can fix the problem by enabling receive buffer overrun for the
* receiving port. Also, to prevent some flow control issues you need to enable
* baud rate emulation for the sending port. So, if your application use port CNCA0
* and other paired port is CNCB0, then:
*
* 1. Launch the Setup Command Prompt shortcut.
* 2. Enter the change commands, for example:
*
* command> change CNCB0 EmuOverrun=yes
* command> change CNCA0 EmuBR=yes
*/
#if __WIN32__
DWORD nwritten;
/* Without this, write blocks while we are waiting on a read. */
static OVERLAPPED ov_wr;
memset (&ov_wr, 0, sizeof(ov_wr));
if ( ! WriteFile (nullmodem_fd, kiss_buff, kiss_len, &nwritten, &ov_wr))
{
err = GetLastError();
if (err != ERROR_IO_PENDING)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending KISS message to client application thru null modem. Error %d.\n\n", (int)GetLastError());
//CloseHandle (nullmodem_fd);
//nullmodem_fd = MYFDERROR;
}
}
else if ((int)nwritten != kiss_len)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len);
//CloseHandle (nullmodem_fd);
//nullmodem_fd = MYFDERROR;
}
#else
err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len);
if (err != len)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending KISS message to client application thru null modem. err=%d\n\n", err);
//close (nullmodem_fd);
//nullmodem_fd = MYFDERROR;
}
#endif
#endif
} /* kiss_send_rec_packet */
} /* kisspt_send_rec_packet */
/*-------------------------------------------------------------------
*
* Name: kiss_get
* Name: kisspt_get
*
* Purpose: Read one byte from the KISS client app.
*
* Global In: nullmodem_fd (Windows) or pt_master_fd (Linux)
* Global In: pt_master_fd
*
* Returns: one byte (value 0 - 255) or terminate thread on error.
*
@ -799,78 +478,10 @@ void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
*--------------------------------------------------------------------*/
static int kiss_get (/* MYFDTYPE fd*/ void )
static int kisspt_get (void)
{
unsigned char ch;
#if __WIN32__ /* Native Windows version. */
DWORD n;
static OVERLAPPED ov_rd;
memset (&ov_rd, 0, sizeof(ov_rd));
ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
/* Overlapped I/O makes reading rather complicated. */
/* See: http://msdn.microsoft.com/en-us/library/ms810467.aspx */
/* It seems that the read completes OK with a count */
/* of 0 every time we send a message to the serial port. */
n = 0; /* Number of characters read. */
while (n == 0) {
if ( ! ReadFile (nullmodem_fd, &ch, 1, &n, &ov_rd))
{
int err1 = GetLastError();
if (err1 == ERROR_IO_PENDING)
{
/* Wait for completion. */
if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0)
{
if ( ! GetOverlappedResult (nullmodem_fd, &ov_rd, &n, 1))
{
int err3 = GetLastError();
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nKISS GetOverlappedResult error %d.\n\n", err3);
}
else
{
/* Success! n should be 1 */
}
}
}
else
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1);
CloseHandle (nullmodem_fd);
nullmodem_fd = MYFDERROR;
//pthread_exit (NULL);
}
}
} /* end while n==0 */
CloseHandle(ov_rd.hEvent);
if (n != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nKISS failed to get one byte. n=%d.\n\n", (int)n);
#if DEBUG9
fprintf (log_fp, "n=%d\n", n);
#endif
}
#else /* Linux/Cygwin version */
int n = 0;
fd_set fd_in, fd_ex;
int rc;
@ -934,7 +545,7 @@ static int kiss_get (/* MYFDTYPE fd*/ void )
continue; // When could we get a 0?
}
if (rc == MYFDERROR
if (rc == -1
|| (n = read(pt_master_fd, &ch, (size_t)1)) != 1)
{
@ -944,70 +555,52 @@ static int kiss_get (/* MYFDTYPE fd*/ void )
close (pt_master_fd);
pt_master_fd = MYFDERROR;
pt_master_fd = -1;
unlink (TMP_KISSTNC_SYMLINK);
pthread_exit (NULL);
}
}
#endif
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kiss_get(%d) returns 0x%02x\n", fd, ch);
dw_printf ("kisspt_get(%d) returns 0x%02x\n", fd, ch);
#endif
#if DEBUG9
fprintf (log_fp, "%02x %c %c", ch,
isprint(ch) ? ch : '.' ,
(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
if (ch == FEND) fprintf (log_fp, " FEND");
if (ch == FESC) fprintf (log_fp, " FESC");
if (ch == TFEND) fprintf (log_fp, " TFEND");
if (ch == TFESC) fprintf (log_fp, " TFESC");
if (ch == '\r') fprintf (log_fp, " CR");
if (ch == '\n') fprintf (log_fp, " LF");
fprintf (log_fp, "\n");
if (ch == FEND) fflush (log_fp);
#endif
return (ch);
}
/*-------------------------------------------------------------------
*
* Name: kiss_listen_thread
* Name: kisspt_listen_thread
*
* Purpose: Read messages from serial port KISS client application.
*
* Global In: nullmodem_fd (Windows) or pt_master_fd (Linux)
* Global In:
*
* Description: Reads bytes from the KISS client app and
* sends them to kiss_rec_byte for processing.
*
*--------------------------------------------------------------------*/
static THREAD_F kiss_listen_thread (void *arg)
static void * kisspt_listen_thread (void *arg)
{
unsigned char ch;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kiss_listen_thread ( %d )\n", fd);
dw_printf ("kisspt_listen_thread ( %d )\n", fd);
#endif
while (1) {
ch = kiss_get();
kiss_rec_byte (&kf, ch, kiss_debug, kiss_send_rec_packet);
ch = kisspt_get();
kiss_rec_byte (&kf, ch, kisspt_debug, -1, kisspt_send_rec_packet);
}
#if __WIN32__
return(0);
#else
return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */
#endif
return (void *) 0; /* Unreachable but avoids compiler warning. */
}
#endif // Linux version
/* end kiss.c */

8
kiss.h
View File

@ -1,6 +1,8 @@
/*
* Name: kiss.h
*
* This is for the pseudo terminal KISS interface.
*/
@ -11,11 +13,11 @@
void kiss_init (struct misc_config_s *misc_config);
void kisspt_init (struct misc_config_s *misc_config);
void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen);
void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client);
void kiss_serial_set_debug (int n);
void kisspt_set_debug (int n);
/* end kiss.h */

View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2013, 2014 John Langner, WB2OSZ
// Copyright (C) 2013, 2014, 2017 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,7 +25,10 @@
*
* Purpose: Common code used by Serial port and network versions of KISS protocol.
*
* Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html
* Description: The KISS TNC protocol is described in http://www.ka9q.net/papers/kiss.html
*
* ( An extended form, to handle multiple TNCs on a single serial port.
* Not applicable for our situation. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf )
*
* Briefly, a frame is composed of
*
@ -37,34 +40,41 @@
*
* The first byte of the frame contains:
*
* * port number in upper nybble.
* * port number (radio channel) in upper nybble.
* * command in lower nybble.
*
*
* Commands from application recognized:
* Commands from application tp TNC:
*
* 0 Data Frame AX.25 frame in raw format.
* _0 Data Frame AX.25 frame in raw format.
*
* 1 TXDELAY See explanation in xmit.c.
* _1 TXDELAY See explanation in xmit.c.
*
* 2 Persistence " "
* _2 Persistence " "
*
* 3 SlotTime " "
* _3 SlotTime " "
*
* 4 TXtail " "
* _4 TXtail " "
* Spec says it is obsolete but Xastir
* sends it and we respect it.
*
* 5 FullDuplex Ignored. Always full duplex.
* _5 FullDuplex Full Duplex. Transmit immediately without
* waiting for channel to be clear.
*
* 6 SetHardware TNC specific. Ignored.
* _6 SetHardware TNC specific.
*
* _C XKISS extension - not supported.
* _E XKISS extention - not supported.
*
* FF Return Exit KISS mode. Ignored.
*
*
* Messages sent to client application:
*
* 0 Data Frame Received AX.25 frame in raw format.
* _0 Data Frame Received AX.25 frame in raw format.
*
* _6 SetHardware TNC specific.
* Usually a response to a query.
*
*---------------------------------------------------------------*/
@ -82,13 +92,38 @@
#include "kiss_frame.h"
#include "tq.h"
#include "xmit.h"
#include "version.h"
/* In server.c. Should probably move to some misc. function file. */
void hex_dump (unsigned char *p, int len);
#ifdef KISSUTIL
void hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
printf (" %03x: ", offset);
for (i=0; i<n; i++) {
printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
printf (" ");
}
printf (" ");
for (i=0; i<n; i++) {
printf ("%c", isprint(p[i]) ? p[i] : '.');
}
printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}
#endif
#if KISSTEST
@ -101,10 +136,18 @@ void text_color_set (dw_color_t c)
#else
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug);
#ifndef DECAMAIN
#ifndef KISSUTIL
static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
#endif
#endif
#endif
//#if KISSUTIL
//#define text_color_set(x) ;
//#define dw_printf printf
//#endif
/*-------------------------------------------------------------------
@ -134,6 +177,7 @@ void kiss_frame_init (struct audio_s *pa)
* Inputs: in - Address of input block.
* First byte is the "type indicator" with type and
* channel but we don't care about that here.
* If it happens to be FEND or FESC, it is escaped, like any other byte.
*
* This seems cumbersome and confusing to have this
* one byte offset when encapsulating an AX.25 frame.
@ -153,7 +197,7 @@ void kiss_frame_init (struct audio_s *pa)
* FEND - Magic frame separator.
*
* Returns: Number of bytes in the output.
* Absolute max length will be twice input plus 2.
* Absolute max length (extremely unlikely) will be twice input plus 2.
*
*-----------------------------------------------------------------*/
@ -205,6 +249,8 @@ int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out)
* the escapes or FEND.
* First byte is the "type indicator" with type and
* channel but we don't care about that here.
* We treat it like any other byte with special handling
* if it happens to be FESC.
* Note that this is "binary" data and can contain
* nul (0x00) values. Don't treat it like a text string!
*
@ -212,7 +258,7 @@ int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out)
*
*-----------------------------------------------------------------*/
static int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
{
int olen;
int j;
@ -278,6 +324,8 @@ static int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
} /* end kiss_unwrap */
#ifndef DECAMAIN
#ifndef KISSTEST
@ -291,6 +339,8 @@ static int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
* Inputs: kf - Current state of building a frame.
* ch - A byte from the input stream.
* debug - Activates debug output.
* client - Client app number for TCP KISS.
* Ignored for pseudo termal and serial port.
* sendfun - Function to send something to the client application.
*
* Outputs: kf - Current state is updated.
@ -326,7 +376,7 @@ static int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int))
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
//dw_printf ("kiss_frame ( %c %02x ) \n", ch, ch);
@ -364,14 +414,17 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfu
kf->noise[kf->noise_len] = '\0';
}
#ifndef KISSUTIL
/* Try to appease client app by sending something back. */
if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 ||
strcasecmp("reset\r", (char*)(kf->noise)) == 0) {
(*sendfun) (0, (unsigned char *)"\xc0\xc0", -1);
// first 2 parameters don't matter when length is -1 indicating text.
(*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, client);
}
else {
(*sendfun) (0, (unsigned char *)"\r\ncmd:", -1);
(*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, client);
}
#endif
kf->noise_len = 0;
}
return;
@ -415,7 +468,7 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfu
hex_dump (unwrapped+1, ulen-1);
}
kiss_process_msg (unwrapped, ulen, debug);
kiss_process_msg (unwrapped, ulen, debug, client, sendfun);
kf->state = KS_SEARCHING;
return;
@ -452,9 +505,22 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfu
*
* debug - Debug option is selected.
*
* client - Client app number for TCP KISS.
* Ignored for pseudo termal and serial port.
*
* sendfun - Function to send something to the client application.
* "Set Hardware" can send a response.
*
*-----------------------------------------------------------------*/
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
#ifndef KISSUTIL // All these ifdefs in here are a sign that this should be refactored.
// Should split this into multiple files.
// Some functions are only for the TNC end.
// Other functions are suitble for both TNC and client app.
// This is used only by the TNC sided.
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
int port;
int cmd;
@ -466,11 +532,21 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
switch (cmd)
{
case 0: /* Data Frame */
case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
/* Special hack - Discard apparently bad data from Linux AX25. */
if ((port == 2 || port == 8) &&
/* Note July 2017: There is a variant of of KISS, called SMACK, that assumes */
/* a TNC can never have more than 8 ports. http://symek.de/g/smack.html */
/* It uses the MSB to indicate that a checksum is added. I wonder if this */
/* is why we sometimes hear about a request to transmit on channel 8. */
/* Should we have a message that asks the user if SMACK is being used, */
/* and if so, turn it off in the application configuration? */
/* Our current default is a maximum of 6 channels but it is easily */
/* increased by changing one number and recompiling. */
if (kiss_len > 16 &&
(port == 2 || port == 8) &&
kiss_msg[1] == 'Q' << 1 &&
kiss_msg[2] == 'S' << 1 &&
kiss_msg[3] == 'T' << 1 &&
@ -519,50 +595,103 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
}
break;
case 1: /* TXDELAY */
case KISS_CMD_TXDELAY: /* 1 = TXDELAY */
if (kiss_len < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS ERROR: Missing value for TXDELAY command.\n");
return;
}
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
if (kiss_msg[1] < 4 || kiss_msg[1] > 100) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for TXDELAY?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_txdelay (port, kiss_msg[1]);
break;
case 2: /* Persistence */
case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
if (kiss_len < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS ERROR: Missing value for PERSISTENCE command.\n");
return;
}
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port);
if (kiss_msg[1] < 5 || kiss_msg[1] > 250) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for PERSIST?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_persist (port, kiss_msg[1]);
break;
case 3: /* SlotTime */
case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
if (kiss_len < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS ERROR: Missing value for SLOTTIME command.\n");
return;
}
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set SlotTime = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
if (kiss_msg[1] < 2 || kiss_msg[1] > 50) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for SLOTTIME?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_slottime (port, kiss_msg[1]);
break;
case 4: /* TXtail */
case KISS_CMD_TXTAIL: /* 4 = TXtail */
if (kiss_len < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS ERROR: Missing value for TXTAIL command.\n");
return;
}
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
if (kiss_msg[1] < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Setting TXTAIL so low is asking for trouble. You probably don't want to do this.\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_txtail (port, kiss_msg[1]);
break;
case 5: /* FullDuplex */
case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
if (kiss_len < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS ERROR: Missing value for FULLDUPLEX command.\n");
return;
}
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kiss_msg[1], port);
xmit_set_fulldup (port, kiss_msg[1]);
break;
case 6: /* TNC specific */
case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
if (kiss_len < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS ERROR: Missing value for SET HARDWARE command.\n");
return;
}
kiss_msg[kiss_len] = '\0';
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set hardware - ignored.\n");
dw_printf ("KISS protocol set hardware \"%s\", port %d\n", (char*)(kiss_msg+1), port);
kiss_set_hardware (port, (char*)(kiss_msg+1), debug, client, sendfun);
break;
case 15: /* End KISS mode, port should be 15. */
case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
/* Ignore it. */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol end KISS mode\n");
dw_printf ("KISS protocol end KISS mode - Ignored.\n");
break;
default:
@ -575,11 +704,158 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n");
dw_printf ("all communication with the client application.\n");
if (cmd == XKISS_CMD_DATA || cmd == XKISS_CMD_POLL) {
dw_printf ("\n");
dw_printf ("It looks like you are trying to use the \"XKISS\" protocol which is not supported.\n");
dw_printf ("Change your application settings to use standard \"KISS\" rather than some other variant.\n");
dw_printf ("If you are using Winlink Express, configure like this:\n");
dw_printf (" Packet TNC Type: KISS\n");
dw_printf (" Packet TNC Model: NORMAL -- Using ACKMODE will cause this error.\n");
dw_printf ("\n");
}
break;
}
} /* end kiss_process_msg */
#endif // ifndef KISSUTIL
/*-------------------------------------------------------------------
*
* Name: kiss_set_hardware
*
* Purpose: Process the "set hardware" command.
*
* Inputs: chan - channel, 0 - 15.
*
* command - All but the first byte. e.g. "TXBUF:99"
* Case sensitive.
* Will be modified so be sure caller doesn't care.
*
* debug - debug level.
*
* client - Client app number for TCP KISS.
* Needed so we can send any response to the right client app.
* Ignored for pseudo terminal and serial port.
*
* sendfun - Function to send something to the client application.
*
* This is the tricky part. We can have any combination of
* serial port, pseudo terminal, and multiple TCP clients.
* We need to send the response to same place where query came
* from. The function is different for each class of device
* and we need a client number for the TCP case because we
* can have multiple TCP KISS clients at the same time.
*
*
* Description: This is new in version 1.5. "Set hardware" was previously ignored.
*
* There are times when the client app might want to send configuration
* commands, such as modem speed, to the KISS TNC or inquire about its
* current state.
*
* The immediate motivation for adding this is that one application wants
* to know how many frames are currently in the transmit queue. This can
* be used for throttling of large transmissions and performing some action
* after the last frame has been sent.
*
* The original KISS protocol spec offers no guidance on what "Set Hardware" might look
* like. I'm aware of only two, drastically different, implementations:
*
* fldigi - http://www.w1hkj.com/FldigiHelp-3.22/kiss_command_page.html
*
* Everything is in human readable in both directions:
*
* COMMAND: [ parameter [ , parameter ... ] ]
*
* Lack of a parameter, in the client to TNC direction, is a query
* which should generate a response in the same format.
*
* Used by applications, http://www.w1hkj.com/FldigiHelp/kiss_host_prgs_page.html
* - BPQ32
* - UIChar
* - YAAC
*
* mobilinkd - https://raw.githubusercontent.com/mobilinkd/tnc1/tnc2/bertos/net/kiss.c
*
* Single byte with the command / response code, followed by
* zero or more value bytes.
*
* Used by applications:
* - APRSdroid
*
* It would be beneficial to adopt one of them rather than doing something
* completely different. It might even be possible to recognize both.
* This might allow leveraging of other existing applications.
*
* Let's start with the easy to understand human readable format.
*
* Commands: (Client to TNC, with parameter(s) to set something.)
*
* none yet
*
* Queries: (Client to TNC, no parameters, generate a response.)
*
* Query Response Comment
* ----- -------- -------
*
* TNC: TNC:DIREWOLF 9.9 9.9 represents current version.
*
* TXBUF: TXBUF:999 Number of bytes (not frames) in transmit queue.
*
*--------------------------------------------------------------------*/
#ifndef KISSUTIL
static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
char *param;
char response[100];
param = strchr (command, ':');
if (param != NULL) {
*param = '\0';
param++;
if (strcmp(command, "TNC") == 0) { /* TNC - Identify software version. */
if (strlen(param) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware TNC: Did not expect a parameter.\n");
}
snprintf (response, sizeof(response), "DIREWOLF %d.%d", MAJOR_VERSION, MINOR_VERSION);
(*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client);
}
else if (strcmp(command, "TXBUF") == 0) { /* TXBUF - Number of bytes in transmit queue. */
if (strlen(param) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware TXBUF: Did not expect a parameter.\n");
}
int n = tq_count (chan, -1, "", "", 1);
snprintf (response, sizeof(response), "TXBUF:%d", n);
(*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware unrecognized command: %s.\n", command);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware \"%s\" expected the form COMMAND:[parameter[,parameter...]]\n", command);
}
return;
} /* end kiss_set_hardware */
#endif // ifndef KISSUTIL
/*-------------------------------------------------------------------
*
@ -597,6 +873,7 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len)
{
#ifndef KISSUTIL
const char *direction [2] = { "from", "to" };
const char *prefix [2] = { "<<<", ">>>" };
const char *function[16] = {
@ -604,11 +881,14 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
"TXtail", "FullDuplex", "SetHardware", "Invalid 7",
"Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11",
"Invalid 12", "Invalid 13", "Invalid 14", "Return" };
#endif
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
#ifdef KISSUTIL
dw_printf ("From KISS TNC:\n");
#else
dw_printf ("\n");
if (special == NULL) {
unsigned char *p; /* to skip over FEND if present. */
@ -624,6 +904,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
prefix[(int)fromto], special, direction[(int)fromto],
msg_len);
}
#endif
hex_dump (pmsg, msg_len);
} /* end kiss_debug_print */
@ -631,6 +912,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
#endif
#endif /* DECAMAIN */
/* Quick unit test for encapsulate & unwrap */
@ -674,7 +956,7 @@ int main ()
exit (EXIT_SUCCESS);
}
#endif
#endif /* KISSTEST */
#endif /* WALK96 */

View File

@ -4,6 +4,25 @@
#include "audio.h" /* for struct audio_s */
/*
* The first byte of a KISS frame has:
* channel in upper nybble.
* command in lower nybble.
*/
#define KISS_CMD_DATA_FRAME 0
#define KISS_CMD_TXDELAY 1
#define KISS_CMD_PERSISTENCE 2
#define KISS_CMD_SLOTTIME 3
#define KISS_CMD_TXTAIL 4
#define KISS_CMD_FULLDUPLEX 5
#define KISS_CMD_SET_HARDWARE 6
#define XKISS_CMD_DATA 12 // Not supported. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf
#define XKISS_CMD_POLL 14 // Not supported.
#define KISS_CMD_END_KISS 15
/*
* Special characters used by SLIP protocol.
*/
@ -14,8 +33,10 @@
#define TFESC 0xDD
enum kiss_state_e {
KS_SEARCHING, /* Looking for FEND to start KISS frame. */
KS_SEARCHING = 0, /* Looking for FEND to start KISS frame. */
/* Must be 0 so we can simply zero whole structure to initialize. */
KS_COLLECTING}; /* In process of collecting KISS frame. */
@ -40,15 +61,20 @@ typedef struct kiss_frame_s {
} kiss_frame_t;
#ifndef KISSUTIL
void kiss_frame_init (struct audio_s *pa);
#endif
int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out);
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int));
int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out);
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
/* end kiss_frame.h */

312
kissnet.c
View File

@ -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, 2017 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
@ -49,28 +49,28 @@
*
* Commands from application recognized:
*
* 0 Data Frame AX.25 frame in raw format.
* _0 Data Frame AX.25 frame in raw format.
*
* 1 TXDELAY See explanation in xmit.c.
* _1 TXDELAY See explanation in xmit.c.
*
* 2 Persistence " "
* _2 Persistence " "
*
* 3 SlotTime " "
* _3 SlotTime " "
*
* 4 TXtail " "
* _4 TXtail " "
* Spec says it is obsolete but Xastir
* sends it and we respect it.
*
* 5 FullDuplex Ignored. Always full duplex.
* _5 FullDuplex Ignored.
*
* 6 SetHardware TNC specific. Ignored.
* _6 SetHardware TNC specific.
*
* FF Return Exit KISS mode. Ignored.
*
*
* Messages sent to client application:
*
* 0 Data Frame Received AX.25 frame in raw format.
* _0 Data Frame Received AX.25 frame in raw format.
*
*
*
@ -91,7 +91,6 @@
/*
* Native Windows: Use the Winsock interface.
* Linux: Use the BSD socket interface.
* Cygwin: Can use either one.
*/
@ -131,15 +130,27 @@
void hex_dump (unsigned char *p, int len); // This should be in a .h file.
static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
// TODO: multiple instances if multiple KISS network clients!
/*
* Early on we allowed one AGW connection and one KISS TCP connection at a time.
* In version 1.1, we allowed multiple concurrent client apps to attach with the AGW network protocol.
* In Version 1.5, we do essentially the same here to allow multiple concurrent KISS TCP clients.
* The default is a limit of 3 client applications at the same time.
* You can increase the limit by changing the line below.
* A larger number consumes more resources so don't go crazy by making it larger than needed.
*/
#define MAX_NET_CLIENTS 3
static int client_sock; /* File descriptor for socket for */
static int client_sock[MAX_NET_CLIENTS];
/* File descriptor for socket for */
/* communication with client application. */
/* Set to -1 if not connected. */
/* (Don't use SOCKET type because it is unsigned.) */
static kiss_frame_t kf[MAX_NET_CLIENTS];
/* Accumulated KISS frame and state of decoder. */
// TODO: define in one place, use everywhere.
#if __WIN32__
@ -187,15 +198,17 @@ void kiss_net_set_debug (int n)
void kissnet_init (struct misc_config_s *mc)
{
int client;
#if __WIN32__
HANDLE connect_listen_th;
HANDLE cmd_listen_th;
HANDLE cmd_listen_th[MAX_NET_CLIENTS];
#else
pthread_t connect_listen_tid;
pthread_t cmd_listen_tid;
pthread_t cmd_listen_tid[MAX_NET_CLIENTS];
int e;
#endif
int kiss_port = mc->kiss_port;
int kiss_port = mc->kiss_port; /* default 8001 but easily changed. */
#if DEBUG
@ -203,9 +216,11 @@ void kissnet_init (struct misc_config_s *mc)
dw_printf ("kissnet_init ( %d )\n", kiss_port);
#endif
memset (&kf, 0, sizeof(kf));
client_sock = -1;
for (client=0; client<MAX_NET_CLIENTS; client++) {
client_sock[client] = -1;
memset (&(kf[client]), 0, sizeof(kf[client]));
}
if (kiss_port == 0) {
text_color_set(DW_COLOR_INFO);
@ -214,7 +229,7 @@ void kissnet_init (struct misc_config_s *mc)
}
/*
* This waits for a client to connect and sets client_sock.
* This waits for a client to connect and sets client_sock[n].
*/
#if __WIN32__
connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL);
@ -233,23 +248,30 @@ void kissnet_init (struct misc_config_s *mc)
#endif
/*
* This reads messages from client when client_sock is valid.
* These read messages from client when client_sock[n] is valid.
* Currently we start up a separate thread for each potential connection.
* Possible later refinement. Start one now, others only as needed.
*/
for (client = 0; client < MAX_NET_CLIENTS; client++) {
#if __WIN32__
cmd_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL);
if (cmd_listen_th == NULL) {
cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, (void*)client, 0, NULL);
if (cmd_listen_th[client] == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create KISS socket command listening thread\n");
dw_printf ("Could not create KISS command listening thread for client %d\n", client);
return;
}
#else
e = pthread_create (&cmd_listen_tid, NULL, kissnet_listen_thread, NULL);
e = pthread_create (&(cmd_listen_tid[client]), NULL, kissnet_listen_thread, (void *)(long)client);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create KISS socket command listening thread");
dw_printf ("Could not create KISS command listening thread for client %d\n", client);
// Replace add perror with better message handling.
perror("");
return;
}
#endif
}
}
@ -352,13 +374,22 @@ static THREAD_F connect_listen_thread (void *arg)
while (1) {
while (client_sock > 0) {
SLEEP_SEC(1); /* Already connected. Try again later. */
int client;
int c;
client = -1;
for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
if (client_sock[c] <= 0) {
client = c;
}
}
#define QUEUE_SIZE 5
/*
* Listen for connection if we have not reached maximum.
*/
if (client >= 0) {
if(listen(listen_sock,QUEUE_SIZE) == SOCKET_ERROR)
if(listen(listen_sock, MAX_NET_CLIENTS) == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
dw_printf("Listen failed with error: %d\n", WSAGetLastError());
@ -366,11 +397,11 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
dw_printf("Ready to accept KISS client application on port %s ...\n", kiss_port_str);
dw_printf("Ready to accept KISS TCP client application %d on port %s ...\n", client, kiss_port_str);
client_sock = accept(listen_sock, NULL, NULL);
client_sock[client] = accept(listen_sock, NULL, NULL);
if (client_sock == -1) {
if (client_sock[client] == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Accept failed with error: %d\n", WSAGetLastError());
closesocket(listen_sock);
@ -379,8 +410,14 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
dw_printf("\nConnected to KISS client application ...\n\n");
dw_printf("\nAttached to KISS TCP client application %d ...\n\n", client);
// Reset the state and buffer.
memset (&(kf[client]), 0, sizeof(kf[client]));
}
else {
SLEEP_SEC(1); /* wait then check again if more clients allowed. */
}
}
@ -430,18 +467,24 @@ static THREAD_F connect_listen_thread (void *arg)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("opened KISS socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
dw_printf("opened KISS TCP socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
#endif
while (1) {
while (client_sock > 0) {
SLEEP_SEC(1); /* Already connected. Try again later. */
int client;
int c;
client = -1;
for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
if (client_sock[c] <= 0) {
client = c;
}
}
#define QUEUE_SIZE 5
if (client >= 0) {
if(listen(listen_sock,QUEUE_SIZE) == -1)
if(listen(listen_sock,MAX_NET_CLIENTS) == -1)
{
text_color_set(DW_COLOR_ERROR);
perror ("connect_listen_thread: Listen failed");
@ -449,13 +492,19 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
dw_printf("Ready to accept KISS client application on port %d ...\n", kiss_port);
dw_printf("Ready to accept KISS TCP client application %d on port %d ...\n", client, kiss_port);
client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
text_color_set(DW_COLOR_INFO);
dw_printf("\nConnected to KISS client application ...\n\n");
dw_printf("\nAttached to KISS TCP client application %d...\n\n", client);
// Reset the state and buffer.
memset (&(kf[client]), 0, sizeof(kf[client]));
}
else {
SLEEP_SEC(1); /* wait then check again if more clients allowed. */
}
}
#endif
}
@ -473,30 +522,80 @@ static THREAD_F connect_listen_thread (void *arg)
* Inputs: chan - Channel number where packet was received.
* 0 = first, 1 = second if any.
*
// TODO: add kiss_cmd
*
* fbuf - Address of raw received frame buffer
* or a text string.
*
* kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have
* KISS_CMD_SET_HARDWARE when responding to a query.
*
* flen - Number of bytes for AX.25 frame.
* or -1 for a text string.
* When called from kiss_rec_byte, flen will be -1
* indicating a text string rather than frame content.
* This is used to fake out an application that thinks
* it is using a traditional TNC and tries to put it
* into KISS mode.
*
* tcpclient - It is possible to have more than client attached
* at the same time with TCP KISS.
* When a frame is received from the radio we want it
* to go to all of the clients. In this case specify -1.
* When responding to a command from the client, we want
* to send only to that one client app. In this case
* use the value 0 .. MAX_NET_CLIENTS-1.
*
* Description: Send message to client if connected.
* Description: Send message to client(s) if connected.
* Disconnect from client, and notify user, if any error.
*
*--------------------------------------------------------------------*/
void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int tcpclient)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
int kiss_len;
int err;
int first, last, client;
// Something received over the radio would be sent to all attached clients.
// However, there are times we want to send a response only to a particular client.
// In the case of a serial port or pseudo terminal, there is only one potential client.
// so the response would be sent to only one place. A new parameter has been added for this.
if (client_sock == -1) {
if (tcpclient >= 0 && tcpclient < MAX_NET_CLIENTS) {
first = tcpclient;
last = tcpclient;
}
else if (tcpclient == -1) {
first = 0;
last = MAX_NET_CLIENTS - 1;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS TCP: Internal error, kissnet_send_rec_packet, tcpclient = %d.\n", tcpclient);
return;
}
for (client = first; client <= last; client++) {
if (client_sock[client] != -1) {
if (flen < 0) {
// A client app might think it is attached to a traditional TNC.
// It might try sending commands over and over again trying to get the TNC into KISS mode.
// We recognize this attempt and send it something to keep it happy.
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS TCP: Something unexpected from client application.\n");
dw_printf ("Is client app treating this like an old TNC with command mode?\n");
dw_printf ("This can be caused by the application sending commands to put a\n");
dw_printf ("traditional TNC into KISS mode. It is usually a harmless warning.\n");
dw_printf ("For best results, configure for a KISS-only TNC to avoid this.\n");
dw_printf ("In the case of APRSISCE/32, use \"Simply(KISS)\" rather than \"KISS.\"\n");
flen = strlen((char*)fbuf);
if (kiss_debug) {
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
@ -505,13 +604,11 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
kiss_len = strlen((char *)kiss_buff);
}
else {
unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
assert (flen < (int)(sizeof(stemp)));
stemp[0] = (chan << 4) + 0;
stemp[0] = (chan << 4) | kiss_cmd;
memcpy (stemp+1, fbuf, flen);
if (kiss_debug >= 2) {
@ -532,98 +629,41 @@ void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
}
#if __WIN32__
err = send (client_sock, (char*)kiss_buff, kiss_len, 0);
err = SOCK_SEND(client_sock[client], (char*)kiss_buff, kiss_len);
if (err == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError %d sending message to KISS client application. Closing connection.\n\n", WSAGetLastError());
closesocket (client_sock);
client_sock = -1;
dw_printf ("\nError %d sending message to KISS client %d application. Closing connection.\n\n", WSAGetLastError(), client);
closesocket (client_sock[client]);
client_sock[client] = -1;
WSACleanup();
}
#else
err = write (client_sock, kiss_buff, kiss_len);
err = SOCK_SEND (client_sock[client], kiss_buff, kiss_len);
if (err <= 0)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending message to KISS client application. Closing connection.\n\n");
close (client_sock);
client_sock = -1;
dw_printf ("\nError sending message to KISS client %d application. Closing connection.\n\n", client);
close (client_sock[client]);
client_sock[client] = -1;
}
#endif
}
}
} /* end kissnet_send_rec_packet */
/*-------------------------------------------------------------------
*
* Name: read_from_socket
*
* Purpose: Read from socket until we have desired number of bytes.
*
* Inputs: fd - file descriptor.
* ptr - address where data should be placed.
* len - desired number of bytes.
*
* Description: Just a wrapper for the "read" system call but it should
* never return fewer than the desired number of bytes.
*
* Not really needed for KISS because we are dealing with
* a stream of bytes rather than message blocks.
*
*--------------------------------------------------------------------*/
static int read_from_socket (int fd, char *ptr, int len)
{
int got_bytes = 0;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
#endif
while (got_bytes < len) {
int n;
#if __WIN32__
//TODO: any flags for send/recv?
//TODO: Would be useful to have more detailed explanation from the error code.
n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
#else
n = read (fd, ptr + got_bytes, len - got_bytes);
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("read_from_socket: n = %d\n", n);
#endif
if (n <= 0) {
return (n);
}
got_bytes += n;
}
assert (got_bytes >= 0 && got_bytes <= len);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("read_from_socket: return %d\n", got_bytes);
#endif
return (got_bytes);
}
/*-------------------------------------------------------------------
*
* Name: kissnet_listen_thread
*
* Purpose: Wait for KISS messages from an application.
*
* Inputs: arg - Not used.
* Inputs: arg - client number, 0 .. MAX_NET_CLIENTS-1
*
* Outputs: client_sock - File descriptor for communicating with client app.
* Outputs: client_sock[n] - File descriptor for communicating with client app.
*
* Description: Process messages from the client application.
* Note that the client can go away and come back again and
@ -635,20 +675,20 @@ static int read_from_socket (int fd, char *ptr, int len)
/* Return one byte (value 0 - 255) */
static int kiss_get (void)
static int kiss_get (int client)
{
unsigned char ch;
int n;
while (1) {
while (client_sock <= 0) {
while (client_sock[client] <= 0) {
SLEEP_SEC(1); /* Not connected. Try again later. */
}
/* Just get one byte at a time. */
n = read_from_socket (client_sock, (char *)(&ch), 1);
n = SOCK_RECV (client_sock[client], (char *)(&ch), 1);
if (n == 1) {
#if DEBUG9
@ -668,13 +708,13 @@ static int kiss_get (void)
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError reading KISS byte from client application. Closing connection.\n\n");
dw_printf ("\nKISS client application %d has gone away.\n\n", client);
#if __WIN32__
closesocket (client_sock);
closesocket (client_sock[client]);
#else
close (client_sock);
close (client_sock[client]);
#endif
client_sock = -1;
client_sock[client] = -1;
}
}
@ -684,14 +724,32 @@ static THREAD_F kissnet_listen_thread (void *arg)
{
unsigned char ch;
int client = (int)(long)arg;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kissnet_listen_thread ( socket = %d )\n", client_sock);
dw_printf ("kissnet_listen_thread ( client = %d, socket fd = %d )\n", client, client_sock[client]);
#endif
assert (client >= 0 && client < MAX_NET_CLIENTS);
// So why is kissnet_send_rec_packet mentioned here for incoming from the client app?
// The logic exists for the serial port case where the client might think it is
// attached to a traditional TNC. It might try sending commands over and over again
// trying to get the TNC into KISS mode. To keep it happy, we recognize this attempt
// and send it something to keep it happy.
// In the case of a serial port or pseudo terminal, there is only one potential client
// so the response would be sent to only one place.
// Starting in version 1.5, this now can have multiple attached clients. We wouldn't
// want to send the response to all of them. Actually, we should be providing only
// "Simply KISS" as some call it.
while (1) {
ch = kiss_get();
kiss_rec_byte (&kf, ch, kiss_debug, kissnet_send_rec_packet);
ch = kiss_get(client);
kiss_rec_byte (&(kf[client]), ch, kiss_debug, client, kissnet_send_rec_packet);
}
#if __WIN32__

View File

@ -13,7 +13,7 @@
void kissnet_init (struct misc_config_s *misc_config);
void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen);
void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client);
void kiss_net_set_debug (int n);

501
kissserial.c Normal file
View File

@ -0,0 +1,501 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014, 2016, 2017 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: kissserial.c
*
* Purpose: Act as a virtual KISS TNC for use by other packet radio applications.
* This file provides the service by good old fashioned serial port.
* Other files implement a pseudo terminal or TCP KISS interface.
*
* Description: This implements the KISS TNC protocol as described in:
* http://www.ka9q.net/papers/kiss.html
*
* Briefly, a frame is composed of
*
* * FEND (0xC0)
* * Contents - with special escape sequences so a 0xc0
* byte in the data is not taken as end of frame.
* as part of the data.
* * FEND
*
* The first byte of the frame contains:
*
* * port number in upper nybble.
* * command in lower nybble.
*
* Commands from application recognized:
*
* _0 Data Frame AX.25 frame in raw format.
*
* _1 TXDELAY See explanation in xmit.c.
*
* _2 Persistence " "
*
* _3 SlotTime " "
*
* _4 TXtail " "
* Spec says it is obsolete but Xastir
* sends it and we respect it.
*
* _5 FullDuplex Ignored.
*
* _6 SetHardware TNC specific.
*
* FF Return Exit KISS mode. Ignored.
*
*
* Messages sent to client application:
*
* _0 Data Frame Received AX.25 frame in raw format.
*
*
* Platform differences:
*
* This file implements KISS over a serial port.
* It should behave pretty much the same for both Windows and Linux.
*
* When running a client application on Windows, two applications
* can be connected together using a a "Null-modem emulator"
* such as com0com from http://sourceforge.net/projects/com0com/
*
* (When running a client application, on the same host, with Linux,
* a pseudo terminal can be used for old applications. More modern
* applications will generally have AGW and/or KISS over TCP.)
*
*
* version 1.5: Split out from kiss.c, simplified, consistent for Windows and Linux.
* Add polling option for use with Bluetooth.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ax25_pad.h"
#include "textcolor.h"
#include "serial_port.h"
#include "kissserial.h"
#include "kiss_frame.h"
#include "xmit.h"
/*
* Save Configuration for later use.
*/
static struct misc_config_s *g_misc_config_p;
/*
* Accumulated KISS frame and state of decoder.
*/
static kiss_frame_t kf;
/*
* The serial port device handle.
* MYFD... are defined in kissserial.h
*/
static MYFDTYPE serialport_fd = MYFDERROR;
// TODO: define in one place, use everywhere.
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
static THREAD_F kissserial_listen_thread (void *arg);
static int kissserial_debug = 0; /* Print information flowing from and to client. */
void kissserial_set_debug (int n)
{
kissserial_debug = n;
}
/* In server.c. Should probably move to some misc. function file. */
void hex_dump (unsigned char *p, int len);
/*-------------------------------------------------------------------
*
* Name: kissserial_init
*
* Purpose: Set up a serial port acting as a virtual KISS TNC.
*
* Inputs: mc->
* kiss_serial_port - Name of device for real or virtual serial port.
* kiss_serial_speed - Speed, bps, or 0 meaning leave it alone.
* kiss_serial_poll - When non-zero, poll each n seconds to see if
* device has appeared.
*
* Outputs:
*
* Description: (1) Open file descriptor for the device.
* (2) Start a new thread to listen for commands from client app
* so the main application doesn't block while we wait.
*
*--------------------------------------------------------------------*/
void kissserial_init (struct misc_config_s *mc)
{
#if __WIN32__
HANDLE kissserial_listen_th;
#else
pthread_t kissserial_listen_tid;
int e;
#endif
g_misc_config_p = mc;
memset (&kf, 0, sizeof(kf));
if (strlen(g_misc_config_p->kiss_serial_port) > 0) {
if (g_misc_config_p->kiss_serial_poll == 0) {
// Normal case, try to open the serial port at start up time.
serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed);
if (serialport_fd != MYFDERROR) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Opened %s for serial port KISS.\n", g_misc_config_p->kiss_serial_port);
}
else {
// An error message was already displayed.
}
}
else {
// Polling case. Defer until read and device not opened.
text_color_set(DW_COLOR_INFO);
dw_printf ("Will be checking periodically for %s\n", g_misc_config_p->kiss_serial_port);
}
if (g_misc_config_p->kiss_serial_poll != 0 || serialport_fd != MYFDERROR) {
#if __WIN32__
kissserial_listen_th = (HANDLE)_beginthreadex (NULL, 0, kissserial_listen_thread, NULL, 0, NULL);
if (kissserial_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create kiss serial thread\n");
return;
}
#else
e = pthread_create (&kissserial_listen_tid, NULL, kissserial_listen_thread, NULL);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create kiss serial thread.");
}
#endif
}
}
#if DEBUG
text_color_set (DW_COLOR_DEBUG);
dw_printf ("end of kiss_init: serialport_fd = %d, polling = %d\n", serialport_fd, g_misc_config_p->kiss_serial_poll);
#endif
}
/*-------------------------------------------------------------------
*
* Name: kissserial_send_rec_packet
*
* Purpose: Send a received packet or text string to the client app.
*
* Inputs: chan - Channel number where packet was received.
* 0 = first, 1 = second if any.
*
* kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have
* KISS_CMD_SET_HARDWARE when responding to a query.
*
* pp - Identifier for packet object.
*
* fbuf - Address of raw received frame buffer
* or a text string.
*
* flen - Length of raw received frame not including the FCS
* or -1 for a text string.
*
* client - Not used for serial port version.
* Here so that 3 related functions all have
* the same parameter list.
*
* Description: Send message to client.
* We really don't care if anyone is listening or not.
* I don't even know if we can find out.
*
*--------------------------------------------------------------------*/
void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
int kiss_len;
int err;
/*
* Quietly discard if we don't have open connection.
*/
if (serialport_fd == MYFDERROR) {
return;
}
if (flen < 0) {
flen = strlen((char*)fbuf);
if (kissserial_debug) {
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
}
strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
kiss_len = strlen((char *)kiss_buff);
}
else {
unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
if (flen > (int)(sizeof(stemp)) - 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nSerial Port KISS buffer too small. Truncated.\n\n");
flen = (int)(sizeof(stemp)) - 1;
}
stemp[0] = (chan << 4) | kiss_cmd;
memcpy (stemp+1, fbuf, flen);
if (kissserial_debug >= 2) {
/* AX.25 frame with the CRC removed. */
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
dw_printf ("Packet content before adding KISS framing and any escapes:\n");
hex_dump (fbuf, flen);
}
kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff);
/* This has KISS framing and escapes for sending to client app. */
if (kissserial_debug) {
kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
}
}
/*
* This write can block on Windows if using the virtual null modem
* and nothing is connected to the other end.
* The solution is found in the com0com ReadMe file:
*
* Q. My application hangs during its startup when it sends anything to one paired
* COM port. The only way to unhang it is to start HyperTerminal, which is connected
* to the other paired COM port. I didn't have this problem with physical serial
* ports.
* A. Your application can hang because receive buffer overrun is disabled by
* default. You can fix the problem by enabling receive buffer overrun for the
* receiving port. Also, to prevent some flow control issues you need to enable
* baud rate emulation for the sending port. So, if your application use port CNCA0
* and other paired port is CNCB0, then:
*
* 1. Launch the Setup Command Prompt shortcut.
* 2. Enter the change commands, for example:
*
* command> change CNCB0 EmuOverrun=yes
* command> change CNCA0 EmuBR=yes
*/
err = serial_port_write (serialport_fd, (char*)kiss_buff, kiss_len);
if (err != kiss_len)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending KISS message to client application thru serial port.\n\n");
serial_port_close (serialport_fd);
serialport_fd = MYFDERROR;
}
} /* kissserial_send_rec_packet */
/*-------------------------------------------------------------------
*
* Name: kissserial_get
*
* Purpose: Read one byte from the KISS client app.
*
* Global In: serialport_fd
*
* Returns: one byte (value 0 - 255) or terminate thread on error.
*
* Description: There is room for improvment here. Reading one byte
* at a time is inefficient. We could read a large block
* into a local buffer and return a byte from that most of the time.
* Is it worth the effort? I don't know. With GHz processors and
* the low data rate here it might not make a noticable difference.
*
*--------------------------------------------------------------------*/
static int kissserial_get (void)
{
int ch; // normally 0-255 but -1 for error.
if (g_misc_config_p->kiss_serial_poll == 0) {
/*
* Normal case, was opened at start up time.
*/
ch = serial_port_get1 (serialport_fd);
if (ch < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n");
serial_port_close (serialport_fd);
serialport_fd = MYFDERROR;
#if __WIN32__
ExitThread (0);
#else
pthread_exit (NULL);
#endif
}
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kissserial_get(%d) returns 0x%02x\n", fd, ch);
#endif
return (ch);
}
/*
* Polling case. Wait until device is present and open.
*/
while (1) {
if (serialport_fd != MYFDERROR) {
// Open, try to read.
ch = serial_port_get1 (serialport_fd);
if (ch >= 0) {
return (ch);
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nSerial Port KISS read error. Closing connection.\n\n");
serial_port_close (serialport_fd);
serialport_fd = MYFDERROR;
}
else {
// Not open. Wait for it to appear and try opening.
struct stat buf;
SLEEP_SEC (g_misc_config_p->kiss_serial_poll);
if (stat(g_misc_config_p->kiss_serial_port, &buf) == 0) {
// It's there now. Try to open.
serialport_fd = serial_port_open (g_misc_config_p->kiss_serial_port, g_misc_config_p->kiss_serial_speed);
if (serialport_fd != MYFDERROR) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nOpened %s for serial port KISS.\n\n", g_misc_config_p->kiss_serial_port);
memset (&kf, 0, sizeof(kf)); // Start with clean state.
}
else {
// An error message was already displayed.
}
}
}
}
} /* end kissserial_get */
/*-------------------------------------------------------------------
*
* Name: kissserial_listen_thread
*
* Purpose: Read messages from serial port KISS client application.
*
* Global In: serialport_fd
*
* Description: Reads bytes from the serial port KISS client app and
* sends them to kiss_rec_byte for processing.
* kiss_rec_byte is a common function used by all 3 KISS
* interfaces: serial port, pseudo terminal, and TCP.
*
*--------------------------------------------------------------------*/
static THREAD_F kissserial_listen_thread (void *arg)
{
unsigned char ch;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("kissserial_listen_thread ( %d )\n", fd);
#endif
while (1) {
ch = kissserial_get();
kiss_rec_byte (&kf, ch, kissserial_debug, -1, kissserial_send_rec_packet);
}
#if __WIN32__
return(0);
#else
return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */
#endif
}
/* end kissserial.c */

21
kissserial.h Normal file
View File

@ -0,0 +1,21 @@
/*
* Name: kissserial.h
*/
#include "ax25_pad.h" /* for packet_t */
#include "config.h"
void kissserial_init (struct misc_config_s *misc_config);
void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client);
void kissserial_set_debug (int n);
/* end kissserial.h */

948
kissutil.c Normal file
View File

@ -0,0 +1,948 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2017 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: kissutil.c
*
* Purpose: Utility for talking to a KISS TNC.
*
* Description: Convert between KISS format and usual text representation.
* This might also serve as the starting point for an application
* that uses a KISS TNC.
* The TNC can be attached by TCP or a serial port.
*
* Usage: kissutil [ options ]
*
* Default is to connect to localhost:8001.
* See the "usage" functions at the bottom for details.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include <stdlib.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <getopt.h>
#include <dirent.h>
#include <sys/stat.h>
#include "ax25_pad.h"
#include "textcolor.h"
#include "serial_port.h"
#include "kiss_frame.h"
#include "sock.h"
#include "dtime_now.h"
#include "audio.h" // for DEFAULT_TXDELAY, etc.
#include "dtime_now.h"
// TODO: define in one place, use everywhere.
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
#if __WIN32__
#define DIR_CHAR "\\"
#else
#define DIR_CHAR "/"
#endif
static THREAD_F tnc_listen_net (void *arg);
static THREAD_F tnc_listen_serial (void *arg);
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen);
static void hex_dump (unsigned char *p, int len);
static void usage(void);
static void usage2(void);
/* Obtained from the command line. */
static char hostname[50] = "localhost"; /* -h option. */
/* DNS host name or IPv4 address. */
/* Some of the code is there for IPv6 but */
/* it needs more work. */
/* Defaults to "localhost" if not specified. */
static char port[30] = "8001"; /* -p option. */
/* If it begins with a digit, it is considered */
/* a TCP port number at the hostname. */
/* Otherwise, we treat it as a serial port name. */
static int using_tcp = 1; /* Are we using TCP or serial port for TNC? */
/* Use corresponding one of the next two. */
/* This is derived from the first character of port. */
static int server_sock = -1; /* File descriptor for socket interface. */
/* Set to -1 if not used. */
/* (Don't use SOCKET type because it is unsigned.) */
static MYFDTYPE serial_fd = (MYFDTYPE)(-1); /* Serial port handle. */
static int serial_speed = 9600; /* -s option. */
/* Serial port speed, bps. */
static int verbose = 0; /* -v option. */
/* Display the KISS protocol in hexadecimal for troubleshooting. */
static char transmit_from[120] = ""; /* -f option */
/* When specified, files are read from this directory */
/* rather than using stdin. Each file is one or more */
/* lines in the standard monitoring format. */
static char receive_output[120] = ""; /* -o option */
/* When specified, each received frame is stored as a file */
/* with a unique name here. */
/* Directory must already exist; we won't create it. */
static char timestamp_format[60] = ""; /* -T option */
/* Precede received frames with timestamp. */
/* Command line option uses "strftime" format string. */
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
#if __WIN32__
static HANDLE tnc_th;
#else
static pthread_t tnc_tid;
#endif
static void process_input (char *stuff);
/* Trim any CR, LF from the end of line. */
static void trim (char *stuff)
{
char *p;
p = stuff + strlen(stuff) - 1;
while (strlen(stuff) > 0 && (*p == '\r' || *p == '\n')) {
*p = '\0';
p--;
}
} /* end trim */
/*------------------------------------------------------------------
*
* Name: main
*
* Purpose: Attach to KISS TNC and exchange information.
*
* Usage: See "usage" functions at end.
*
*---------------------------------------------------------------*/
int main (int argc, char *argv[])
{
text_color_init (0); // Turn off text color.
// It could interfere with trying to pipe stdout to some other application.
#if __WIN32__
#else
int e;
setlinebuf (stdout); // TODO: What is the Windows equivalent?
#endif
/*
* Extract command line args.
*/
while (1) {
int option_index = 0;
int c;
static struct option long_options[] = {
//{"future1", 1, 0, 0},
//{"future2", 0, 0, 0},
//{"future3", 1, 0, 'c'},
{0, 0, 0, 0}
};
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "h:p:s:vf:o:T:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h': /* -h for hostname. */
strlcpy (hostname, optarg, sizeof(hostname));
break;
case 'p': /* -p for port, either TCP or serial device. */
strlcpy (port, optarg, sizeof(port));
break;
case 's': /* -s for serial port speed. */
serial_speed = atoi(optarg);
break;
case 'v': /* -v for verbose. */
verbose++;
break;
case 'f': /* -f for transmit files directory. */
strlcpy (transmit_from, optarg, sizeof(transmit_from));
break;
case 'o': /* -o for receive output directory. */
strlcpy (receive_output, optarg, sizeof(receive_output));
break;
case 'T': /* -T for receive timestamp. */
strlcpy (timestamp_format, optarg, sizeof(timestamp_format));
break;
case '?':
/* Unknown option message was already printed. */
usage ();
break;
default:
/* Should not be here. */
text_color_set(DW_COLOR_DEBUG);
dw_printf("?? getopt returned character code 0%o ??\n", c);
usage ();
}
} /* end while(1) for options */
if (optind < argc) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Unused command line arguments are ignored.\n");
}
/*
* If receive queue directory was specified, make sure that it exists.
*/
if (strlen(receive_output) > 0) {
struct stat s;
if (stat(receive_output, &s) == 0) {
if ( ! S_ISDIR(s.st_mode)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Receive queue location, %s, is not a directory.\n", receive_output);
exit (EXIT_FAILURE);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Receive queue location, %s, does not exist.\n", receive_output);
exit (EXIT_FAILURE);
}
}
/* If port begins with digit, consider it to be TCP. */
/* Otherwise, treat as serial port name. */
using_tcp = isdigit(port[0]);
#if __WIN32__
if (using_tcp) {
tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)99, 0, NULL);
}
else {
tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_serial, (void *)99, 0, NULL);
}
if (tnc_th == NULL) {
printf ("Internal error: Could not create TNC listen thread.\n");
exit (EXIT_FAILURE);
}
#else
if (using_tcp) {
e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(long)99);
}
else {
e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(long)99);
}
if (e != 0) {
perror("Internal error: Could not create TNC listen thread.");
exit (EXIT_FAILURE);
}
#endif
/*
* Process keyboard or other input source.
*/
char stuff[1000];
if (strlen(transmit_from) > 0) {
/*
* Process and delete all files in specified directory.
* When done, sleep for a second and try again.
* This doesn't take them in any particular order.
* A future enhancement might sort by name or timestamp.
*/
while (1) {
DIR *dp;
struct dirent *ep;
//text_color_set(DW_COLOR_DEBUG);
//dw_printf("Get directory listing...\n");
dp = opendir (transmit_from);
if (dp != NULL) {
while ((ep = readdir(dp)) != NULL) {
char path [300];
FILE *fp;
if (ep->d_name[0] == '.')
continue;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Processing %s for transmit...\n", ep->d_name);
strlcpy (path, transmit_from, sizeof(path));
strlcat (path, DIR_CHAR, sizeof(path));
strlcat (path, ep->d_name, sizeof(path));
fp = fopen (path, "r");
if (fp != NULL) {
while (fgets(stuff, sizeof(stuff), fp) != NULL) {
trim (stuff);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("%s\n", stuff);
// TODO: Don't delete file if errors encountered?
process_input (stuff);
}
fclose (fp);
unlink (path);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't open for read: %s\n", path);
}
}
closedir (dp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't access transmit queue directory %s. Quitting.\n", transmit_from);
exit (EXIT_FAILURE);
}
SLEEP_SEC (1);
}
}
else {
/*
* Using stdin.
*/
while (fgets(stuff, sizeof(stuff), stdin) != NULL) {
process_input (stuff);
}
}
return (EXIT_SUCCESS);
} /* end main */
/*-------------------------------------------------------------------
*
* Name: process_input
*
* Purpose: Process frames/commands from user, either interactively or from files.
*
* Inputs: stuff - A frame is in usual format like SOURCE>DEST,DIGI:whatever.
* Commands begin with lower case letter.
* Note that it can be modified by this function.
*
* Later Enhancement: Return success/fail status. The transmit queue processing might want
* to preserve files that were not processed as expected.
*
*--------------------------------------------------------------------*/
static int parse_number (char *str, int de_fault)
{
int n;
while (isspace(*str)) {
str++;
}
if (strlen(str) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Missing number for KISS command. Using default %d.\n", de_fault);
return (de_fault);
}
n = atoi(str);
if (n < 0 || n > 255) { // must fit in a byte.
text_color_set(DW_COLOR_ERROR);
dw_printf ("Number for KISS command is out of range 0-255. Using default %d.\n", de_fault);
return (de_fault);
}
return (n);
}
static void process_input (char *stuff)
{
char *p;
int chan = 0;
/*
* Remove any end of line character(s).
*/
trim (stuff);
/*
* Optional prefix, like "[9]" or "[99]" to specify channel.
*/
p = stuff;
while (isspace(*p)) p++;
if (*p == '[') {
p++;
if (p[1] == ']') {
chan = atoi(p);
p += 2;
}
else if (p[2] == ']') {
chan = atoi(p);
p += 3;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! One or two digit channel number and ] was expected after [ at beginning of line.\n");
usage2();
return;
}
if (chan < 0 || chan > 15) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! KISS channel number must be in range of 0 thru 15.\n");
usage2();
return;
}
while (isspace(*p)) p++;
}
/*
* If it starts with upper case letter or digit, assume it is an AX.25 frame in monitor format.
* Lower case is a command (e.g. Persistence or set Hardware).
* Anything else, print explanation of what is expected.
*/
if (isupper(*p) || isdigit(*p)) {
// Parse the "TNC2 monitor format" and convert to AX.25 frame.
unsigned char frame_data[AX25_MAX_PACKET_LEN];
packet_t pp = ax25_from_text (p, 1);
if (pp != NULL) {
int frame_len = ax25_pack (pp, frame_data);
send_to_kiss_tnc (chan, KISS_CMD_DATA_FRAME, (char*)frame_data, frame_len);
ax25_delete (pp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! Could not convert to AX.25 frame: %s\n", p);
}
}
else if (islower(*p)) {
char value;
switch (*p) {
case 'd': // txDelay, 10ms units
value = parse_number(p+1, DEFAULT_TXDELAY);
send_to_kiss_tnc (chan, KISS_CMD_TXDELAY, &value, 1);
break;
case 'p': // Persistence
value = parse_number(p+1, DEFAULT_PERSIST);
send_to_kiss_tnc (chan, KISS_CMD_PERSISTENCE, &value, 1);
break;
case 's': // Slot time, 10ms units
value = parse_number(p+1, DEFAULT_SLOTTIME);
send_to_kiss_tnc (chan, KISS_CMD_SLOTTIME, &value, 1);
break;
case 't': // txTail, 10ms units
value = parse_number(p+1, DEFAULT_TXTAIL);
send_to_kiss_tnc (chan, KISS_CMD_TXTAIL, &value, 1);
break;
case 'f': // Full duplex
value = parse_number(p+1, 0);
send_to_kiss_tnc (chan, KISS_CMD_FULLDUPLEX, &value, 1);
break;
case 'h': // set Hardware
p++;
while (*p != '\0' && isspace(*p)) { p++; }
send_to_kiss_tnc (chan, KISS_CMD_SET_HARDWARE, p, strlen(p));
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid command. Must be one of d p s t f h.\n");
usage2 ();
break;
}
}
else {
usage2 ();
}
} /* end process_input */
/*-------------------------------------------------------------------
*
* Name: send_to_kiss_tnc
*
* Purpose: Encapsulate the data/command, into a KISS frame, and send to the TNC.
*
* Inputs: chan - channel number.
*
* cmd - KISS_CMD_DATA_FRAME, KISS_CMD_SET_HARDWARE, etc.
*
* data - Information for KISS frame.
*
* dlen - Number of bytes in data.
*
* Description: Encapsulate as KISS frame and send to TNC.
*
*--------------------------------------------------------------------*/
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen)
{
unsigned char temp[1000];
unsigned char kissed[2000];
int klen;
if (chan < 0 || chan > 15) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid channel %d - must be in range 0 to 15.\n", chan);
chan = 0;
}
if (cmd < 0 || cmd > 15) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid command %d - must be in range 0 to 15.\n", cmd);
cmd = 0;
}
if (dlen < 0 || dlen > (int)(sizeof(temp)-1)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid data length %d - must be in range 0 to %d.\n", dlen, (int)(sizeof(temp)-1));
dlen = sizeof(temp)-1;
}
temp[0] = (chan << 4) | cmd;
memcpy (temp+1, data, dlen);
klen = kiss_encapsulate(temp, dlen+1, kissed);
if (verbose) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Sending to KISS TNC:\n");
hex_dump (kissed, klen);
}
if (using_tcp) {
int rc = SOCK_SEND(server_sock, (char*)kissed, klen);
if (rc != klen) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR writing KISS frame to socket.\n");
}
}
else {
int rc = serial_port_write (serial_fd, (char*)kissed, klen);
if (rc != klen) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR writing KISS frame to serial port.\n");
}
}
} /* end send_to_kiss_tnc */
/*-------------------------------------------------------------------
*
* Name: tnc_listen_net
*
* Purpose: Connect to KISS TNC via TCP port.
* Print everything it sends to us.
*
* Inputs: arg - Currently not used.
*
* Global In: host
* port
*
* Global Out: server_sock - Needed to send to the TNC.
*
*--------------------------------------------------------------------*/
static THREAD_F tnc_listen_net (void *arg)
{
int err;
char ipaddr_str[SOCK_IPADDR_LEN]; // Text form of IP address.
char data[4096];
int allow_ipv6 = 0; // Maybe someday.
int debug = 0;
int client = 0; // Not used in this situation.
kiss_frame_t kstate;
memset (&kstate, 0, sizeof(kstate));
err = sock_init ();
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Network interface failure. Can't go on.\n");
exit (EXIT_FAILURE);
}
/*
* Connect to network KISS TNC.
*/
// For the IGate we would loop around and try to reconnect if the TNC
// goes away. We should probably do the same here.
server_sock = sock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str);
if (server_sock == -1) {
text_color_set(DW_COLOR_ERROR);
// Should have been a message already. What else is there to say?
exit (EXIT_FAILURE);
}
/*
* Print what we get from TNC.
*/
int len;
while ((len = SOCK_RECV (server_sock, (char*)(data), sizeof(data))) > 0) {
int j;
for (j = 0; j < len; j++) {
// Feed in one byte at a time.
// kiss_process_msg is called when a complete frame has been accumulated.
// When verbose is specified, we get debug output like this:
//
// <<< Data frame from KISS client application, port 0, total length = 46
// 000: c0 00 82 a0 88 ae 62 6a e0 ae 84 64 9e a6 b4 ff ......bj...d....
// ...
// It says "from KISS client application" because it was written
// on the assumption it was being used in only one direction.
// Not worried enough about it to do anything at this time.
kiss_rec_byte (&kstate, data[j], verbose, client, NULL);
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Read error from TCP KISS TNC. Terminating.\n");
exit (EXIT_FAILURE);
} /* end tnc_listen_net */
/*-------------------------------------------------------------------
*
* Name: tnc_listen_serial
*
* Purpose: Connect to KISS TNC via serial port.
* Print everything it sends to us.
*
* Inputs: arg - Currently not used.
*
* Global In: port
* serial_speed
*
* Global Out: serial_fd - Need for sending to the TNC.
*
*--------------------------------------------------------------------*/
static THREAD_F tnc_listen_serial (void *arg)
{
int client = 0;
kiss_frame_t kstate;
memset (&kstate, 0, sizeof(kstate));
serial_fd = serial_port_open (port, serial_speed);
if (serial_fd == MYFDERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to connect to KISS TNC serial port %s.\n", port);
#if __WIN32__
#else
// More detail such as "permission denied" or "no such device"
dw_printf("%s\n", strerror(errno));
#endif
exit (EXIT_FAILURE);
}
/*
* Read and print.
*/
while (1) {
int ch;
ch = serial_port_get1(serial_fd);
if (ch < 0) {
dw_printf("Read error from serial port KISS TNC.\n");
exit (EXIT_FAILURE);
}
// Feed in one byte at a time.
// kiss_process_msg is called when a complete frame has been accumulated.
kiss_rec_byte (&kstate, ch, verbose, client, NULL);
}
} /* end tnc_listen_serial */
/*-------------------------------------------------------------------
*
* Name: kiss_process_msg
*
* Purpose: Process a frame from the KISS TNC.
* This is called when a complete frame has been accumulated.
* In this case, we simply print it.
*
* Inputs: kiss_msg - Kiss frame with FEND and escapes removed.
* The first byte contains channel and command.
*
* kiss_len - Number of bytes including the command.
*
* debug - Debug option is selected.
*
* client - Not used in this case.
*
* sendfun - Not used in this case.
*
*-----------------------------------------------------------------*/
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
int chan;
int cmd;
packet_t pp;
alevel_t alevel;
chan = (kiss_msg[0] >> 4) & 0xf;
cmd = kiss_msg[0] & 0xf;
switch (cmd)
{
case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
memset (&alevel, 0, sizeof(alevel));
pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
printf ("ERROR - Invalid KISS data frame from TNC.\n");
}
else {
char prefix[100]; // Channel and optional timestamp.
// Like [0] or [2 12:34:56]
char addrs[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN]; // Like source>dest,digi,...,digi:
unsigned char *pinfo;
int info_len;
if (strlen(timestamp_format) > 0) {
char ts[100];
timestamp_user_format (ts, sizeof(ts), timestamp_format);
snprintf (prefix, sizeof(prefix), "[%d %s]", chan, ts);
}
else {
snprintf (prefix, sizeof(prefix), "[%d]", chan);
}
ax25_format_addrs (pp, addrs);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_REC);
dw_printf ("%s %s", prefix, addrs); // [channel] Addresses followed by :
// Safe print will replace any unprintable characters with
// hexadecimal representation.
ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n");
#if __WIN32__
fflush (stdout);
#endif
/*
* Add to receive queue directory if specified.
* File name will be based on current local time.
* If you want UTC, just set an environment variable like this:
*
* TZ=UTC kissutil ...
*/
if (strlen(receive_output) > 0) {
char fname [30];
char path [300];
FILE *fp;
timestamp_filename (fname, (int)sizeof(fname));
strlcpy (path, receive_output, sizeof(path));
strlcat (path, DIR_CHAR, sizeof(path));
strlcat (path, fname, sizeof(path));
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Save received frame to %s\n", path);
fp = fopen (path, "w");
if (fp != NULL) {
fprintf (fp, "%s %s%s\n", prefix, addrs, pinfo);
fclose (fp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Unable to open for write: %s\n", path);
}
}
ax25_delete (pp);
}
break;
case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
kiss_msg[kiss_len] = '\0';
text_color_set(DW_COLOR_REC);
// Display as "h ..." for in/out symmetry.
// Use safe print here?
dw_printf ("[%d] h %s\n", chan, (char*)(kiss_msg+1));
break;
/*
* The rest should only go TO the TNC and not come FROM it.
*/
case KISS_CMD_TXDELAY: /* 1 = TXDELAY */
case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
case KISS_CMD_TXTAIL: /* 4 = TXtail */
case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
default:
text_color_set(DW_COLOR_ERROR);
printf ("Unexpected KISS command %d, channel %d\n", cmd, chan);
break;
}
} /* end kiss_process_msg */
// TODO: We have multiple copies of this. Move to some misc file.
void hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
printf (" %03x: ", offset);
for (i=0; i<n; i++) {
printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
printf (" ");
}
printf (" ");
for (i=0; i<n; i++) {
printf ("%c", isprint(p[i]) ? p[i] : '.');
}
printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}
static void usage(void)
{
text_color_set(DW_COLOR_INFO);
dw_printf ("\n");
dw_printf ("kissutil - Utility for testing a KISS TNC.\n");
dw_printf ("\n");
dw_printf ("Convert between KISS format and usual text representation.\n");
dw_printf ("The TNC can be attached by TCP or a serial port.\n");
dw_printf ("\n");
dw_printf ("Usage: kissutil [ options ]\n");
dw_printf ("\n");
dw_printf (" -h hostname of TCP KISS TNC, default localhost.\n");
dw_printf (" -p port, default 8001.\n");
dw_printf (" If it does not start with a digit, it is\n");
dw_printf (" a serial port. e.g. /dev/ttyAMA0 or COM3.\n");
dw_printf (" -s Serial port speed, default 9600.\n");
dw_printf (" -v Verbose. Show the KISS frame contents.\n");
dw_printf (" -f Transmit files directory. Processs and delete files here.\n");
dw_printf (" -o Receive output queue directory. Store received frames here.\n");
dw_printf (" -T Precede received frames with 'strftime' format time stamp.\n");
usage2();
exit (EXIT_SUCCESS);
}
static void usage2 (void)
{
text_color_set(DW_COLOR_INFO);
dw_printf ("\n");
dw_printf ("Input, starting with upper case letter or digit, is assumed\n");
dw_printf ("to be an AX.25 frame in the usual TNC2 monitoring format.\n");
dw_printf ("\n");
dw_printf ("Input, starting with a lower case letter is a commmand.\n");
dw_printf ("Whitespace, as shown in examples, is optional.\n");
dw_printf ("\n");
dw_printf (" letter meaning example\n");
dw_printf (" ------ ------- -------\n");
dw_printf (" d txDelay, 10ms units d 30\n");
dw_printf (" p Persistence p 63\n");
dw_printf (" s Slot time, 10ms units s 10\n");
dw_printf (" t txTail, 10ms units t 5\n");
dw_printf (" f Full duplex f 0\n");
dw_printf (" h set Hardware h TNC:\n");
dw_printf ("\n");
dw_printf (" Lines may be preceded by the form \"[9]\" to indicate a\n");
dw_printf (" channel other than the default 0.\n");
dw_printf ("\n");
}
/* end kissutil.c */

147
latlong.c
View File

@ -59,6 +59,17 @@
*
* Returns: None
*
* Idea for future:
* Non zero ambiguity removes least significant digits without rounding.
* Maybe we could use -1 and -2 to add extra digits using !DAO! as
* documented in http://www.aprs.org/datum.txt
*
* For example, -1 adds one more human readable digit.
* lat minutes 12.345 would produce "12.34" and !W5 !
*
* -2 would encode almost 2 digits in base 91.
* lat minutes 10.0027 would produce "10.00" and !w: !
*
*----------------------------------------------------------------*/
void latitude_to_str (double dlat, int ambiguity, char *slat)
@ -555,6 +566,92 @@ double ll_distance_km (double lat1, double lon1, double lat2, double lon2)
}
/*------------------------------------------------------------------
*
* Function: ll_bearing_deg
*
* Purpose: Calculate bearing between two locations.
*
* Inputs: lat1, lon1 - starting location, in degrees.
* lat2, lon2 - destination location
*
* Returns: Initial Bearing, in degrees.
* The calculation produces Range +- 180 degrees.
* But I think that 0 - 360 would be more customary?
*
*------------------------------------------------------------------*/
double ll_bearing_deg (double lat1, double lon1, double lat2, double lon2)
{
double b;
lat1 *= M_PI / 180;
lon1 *= M_PI / 180;
lat2 *= M_PI / 180;
lon2 *= M_PI / 180;
b = atan2 (sin(lon2-lon1) * cos(lat2),
cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2-lon1));
b *= 180 / M_PI;
if (b < 0) b += 360;
return (b);
}
/*------------------------------------------------------------------
*
* Function: ll_dest_lat
* ll_dest_lon
*
* Purpose: Calculate the destination location given a starting point,
* distance, and bearing,
*
* Inputs: lat1, lon1 - starting location, in degrees.
* dist - distance in km.
* bearing - direction in degrees. Shouldn't matter
* if it is in +- 180 or 0 to 360 range.
*
* Returns: New latitude or longitude.
*
*------------------------------------------------------------------*/
double ll_dest_lat (double lat1, double lon1, double dist, double bearing)
{
double lat2;
lat1 *= M_PI / 180; // Everything to radians.
lon1 *= M_PI / 180;
bearing *= M_PI / 180;
lat2 = asin(sin(lat1) * cos(dist/R) + cos(lat1) * sin(dist/R) * cos(bearing));
lat2 *= 180 / M_PI; // Back to degrees.
return (lat2);
}
double ll_dest_lon (double lat1, double lon1, double dist, double bearing)
{
double lon2;
double lat2;
lat1 *= M_PI / 180; // Everything to radians.
lon1 *= M_PI / 180;
bearing *= M_PI / 180;
lat2 = asin(sin(lat1) * cos(dist/R) + cos(lat1) * sin(dist/R) * cos(bearing));
lon2 = lon1 + atan2(sin(bearing) * sin(dist/R) * cos(lat1), cos(dist/R) - sin(lat1) * sin(lat2));
lon2 *= 180 / M_PI; // Back to degrees.
return (lon2);
}
/*------------------------------------------------------------------
*
* Function: ll_from_grid_square
@ -776,6 +873,7 @@ int main (int argc, char *argv[])
int errors = 0;
int ok;
double dlat, dlon;
double d, b;
/* Latitude to APRS format. */
@ -860,6 +958,55 @@ int main (int argc, char *argv[])
// to be continued for others... NMEA...
/* Distance & bearing - Take a couple examples from other places and see if we get similar results. */
// http://www.movable-type.co.uk/scripts/latlong.html
d = ll_distance_km (35., 45., 35., 135.);
b = ll_bearing_deg (35., 45., 35., 135.);
if (d < 7862 || d > 7882) { errors++; dw_printf ("Error 5.1: Did not expect distance %.1f\n", d); }
if (b < 59.7 || b > 60.3) { errors++; dw_printf ("Error 5.2: Did not expect bearing %.1f\n", b); }
// Sydney to Kinsale. https://woodshole.er.usgs.gov/staffpages/cpolloni/manitou/ccal.htm
d = ll_distance_km (-33.8688, 151.2093, 51.7059, -8.5222);
b = ll_bearing_deg (-33.8688, 151.2093, 51.7059, -8.5222);
if (d < 17435 || d > 17455) { errors++; dw_printf ("Error 5.3: Did not expect distance %.1f\n", d); }
if (b < 327-1 || b > 327+1) { errors++; dw_printf ("Error 5.4: Did not expect bearing %.1f\n", b); }
/*
* More distance and bearing.
* Here we will start at some location1 (lat1,lon1) and go some distance (d1) at some bearing (b1).
* This results in a new location2 (lat2, lon2).
* We then calculate the distance and bearing from location1 to location2 and compare with the intention.
*/
int lat1, lon1, d1 = 10, b1;
double lat2, lon2, d2, b2;
for (lat1 = -60; lat1 <= 60; lat1 += 30) {
for (lon1 = -180; lon1 <= 180; lon1 +=30) {
for (b1 = 0; b1 < 360; b1 += 15) {
lat2 = ll_dest_lat ((double)lat1, (double)lon1, (double)d1, (double)b1);
lon2 = ll_dest_lon ((double)lat1, (double)lon1, (double)d1, (double)b1);
d2 = ll_distance_km ((double)lat1, (double)lon1, lat2, lon2);
b2 = ll_bearing_deg ((double)lat1, (double)lon1, lat2, lon2);
if (b2 > 359.9 && b2 < 360.1) b2 = 0;
// must be within 0.1% of distance and 0.1 degree.
if (d2 < 0.999 * d1 || d2 > 1.001 * d1) { errors++; dw_printf ("Error 5.8: lat1=%d, lon2=%d, d1=%d, b1=%d, d2=%.2f\n", lat1, lon1, d1, b1, d2); }
if (b2 < b1 - 0.1 || b2 > b1 + 0.1) { errors++; dw_printf ("Error 5.9: lat1=%d, lon2=%d, d1=%d, b1=%d, b2=%.2f\n", lat1, lon1, d1, b1, b2); }
}
}
}
/* Maidenhead locator to lat/long. */

129
log.c
View File

@ -28,6 +28,13 @@
* unreadable, format, write separated properties into
* CSV format for easy reading and later processing.
*
* There are two alternatives here.
*
* -L logfile Specify full file path.
*
* -l logdir Daily names will be created here.
*
* Use one or the other but not both.
*
*------------------------------------------------------------------*/
@ -91,26 +98,39 @@ static void quote_for_csv (char *out, size_t outsize, const char *in) {
*
* Purpose: Initialization at start of application.
*
* Inputs: path - Path of log file directory.
* Inputs: daily_names - True if daily names should be generated.
* In this case path is a directory.
* When false, path would be the file name.
*
* path - Log file name or just directory.
* Use "." for current directory.
* Empty string disables feature.
*
* Global Out: g_log_dir - Save directory here for later use.
* Global Out: g_daily_names - True if daily names should be generated.
*
* g_log_path - Save directory or full name here for later use.
*
* g_log_fp - File pointer for writing.
* Note that file is kept open.
* We don't open/close for every new item.
*
* g_open_fname - Name of currently open file.
* Applicable only when g_daily_names is true.
*
*------------------------------------------------------------------*/
static char g_log_dir[80];
static int g_daily_names;
static char g_log_path[80];
static FILE *g_log_fp;
static char g_open_fname[20];
void log_init (char *path)
void log_init (int daily_names, char *path)
{
struct stat st;
strlcpy (g_log_dir, "", sizeof(g_log_dir));
g_daily_names = daily_names;
strlcpy (g_log_path, "", sizeof(g_log_path));
g_log_fp = NULL;
strlcpy (g_open_fname, "", sizeof(g_open_fname));
@ -118,17 +138,21 @@ void log_init (char *path)
return;
}
if (g_daily_names) {
// Original strategy. Automatic daily file names.
if (stat(path,&st) == 0) {
// Exists, but is it a directory?
if (S_ISDIR(st.st_mode)) {
// Specified directory exists.
strlcpy (g_log_dir, path, sizeof(g_log_dir));
strlcpy (g_log_path, path, sizeof(g_log_path));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Log file location \"%s\" is not a directory.\n", path);
dw_printf ("Using current working directory \".\" instead.\n");
strlcpy (g_log_dir, ".", sizeof(g_log_dir));
strlcpy (g_log_path, ".", sizeof(g_log_path));
}
}
else {
@ -143,17 +167,28 @@ void log_init (char *path)
// Success.
text_color_set(DW_COLOR_INFO);
dw_printf ("Log file location \"%s\" has been created.\n", path);
strlcpy (g_log_dir, path, sizeof(g_log_dir));
strlcpy (g_log_path, path, sizeof(g_log_path));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create log file location \"%s\".\n", path);
dw_printf ("%s\n", strerror(errno));
dw_printf ("Using current working directory \".\" instead.\n");
strlcpy (g_log_dir, ".", sizeof(g_log_dir));
strlcpy (g_log_path, ".", sizeof(g_log_path));
}
}
}
}
else {
// Added in version 1.5. Single file.
// Typically logrotate would be used to keep size under control.
text_color_set(DW_COLOR_INFO);
dw_printf ("Log file is \"%s\"\n", path);
strlcpy (g_log_path, path, sizeof(g_log_path));
}
} /* end log_init */
@ -177,17 +212,25 @@ void log_init (char *path)
void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries)
{
time_t now; // make 'now' a parameter so we can process historical data ???
char fname[20];
time_t now;
struct tm tm;
if (strlen(g_log_dir) == 0) return;
if (strlen(g_log_path) == 0) return;
now = time(NULL); // Get current time.
(void)gmtime_r (&now, &tm);
if (g_daily_names) {
// Original strategy. Automatic daily file names.
char fname[20];
// Generate the file name from current date, UTC.
now = time(NULL);
(void)gmtime_r (&now, &tm);
// Why UTC rather than local time? I don't recall the reasoning.
// It's been there a few years and no on complained so leave it alone for now.
// Microsoft doesn't recognize %F as equivalent to %Y-%m-%d
@ -206,7 +249,7 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
struct stat st;
int already_there;
strlcpy (full_path, g_log_dir, sizeof(full_path));
strlcpy (full_path, g_log_path, sizeof(full_path));
#if __WIN32__
strlcat (full_path, "\\", sizeof(full_path));
#else
@ -214,10 +257,10 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
#endif
strlcat (full_path, fname, sizeof(full_path));
// See if it already exists.
// See if file already exists and not empty.
// This is used later to write a header if it did not exist already.
already_there = stat(full_path,&st) == 0;
already_there = (stat(full_path,&st) == 0) && (st.st_size > 0);
text_color_set(DW_COLOR_INFO);
dw_printf("Opening log file \"%s\".\n", fname);
@ -239,9 +282,49 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
// only if this will be the first line.
if ( ! already_there) {
fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,comment\n");
fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n");
}
}
}
else {
// Added in version 1.5. Single file.
// Open for append if not already open.
if (g_log_fp == NULL) {
struct stat st;
int already_there;
// See if file already exists and not empty.
// This is used later to write a header if it did not exist already.
already_there = (stat(g_log_path,&st) == 0) && (st.st_size > 0);
text_color_set(DW_COLOR_INFO);
dw_printf("Opening log file \"%s\"\n", g_log_path);
g_log_fp = fopen (g_log_path, "a");
if (g_log_fp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't open log file \"%s\" for write.\n", g_log_path);
dw_printf ("%s\n", strerror(errno));
strlcpy (g_log_path, "", sizeof(g_log_path));
return;
}
// Write a header suitable for importing into a spreadsheet
// only if this will be the first line.
if ( ! already_there) {
fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n");
}
}
}
// Add line to file if it is now open.
if (g_log_fp != NULL) {
@ -439,7 +522,13 @@ void log_term (void)
if (g_log_fp != NULL) {
text_color_set(DW_COLOR_INFO);
if (g_daily_names) {
dw_printf("Closing log file \"%s\".\n", g_open_fname);
}
else {
dw_printf("Closing log file \"%s\".\n", g_log_path);
}
fclose (g_log_fp);

2
log.h
View File

@ -10,7 +10,7 @@
void log_init (char *path);
void log_init (int daily_names, char *path);
void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries);

View File

@ -48,5 +48,5 @@ of how to set up tests and the results.
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -87,5 +87,5 @@ Try different combinations of options to find the best decoding performance.
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -9,7 +9,9 @@ decode_aprs \- Convert APRS raw data to human readable form.
[ \fItext-file\fR ]
.RS
.P
\fItext-file\fR should contain AX.25 packets in the standard monitoring format.
\fItext-file\fR should contain AX.25 packets in the standard monitoring format or
as a series two digit hexadecimal numbers.
If the first number is 00 or c0, it will be treated as a KISS frame.
If no file specified, data will be read from stdin.
.P
.RE
@ -43,12 +45,16 @@ Pipe it into decode_aprs to find out.
http://www.findu.com/cgi-bin/errors.cgi has a never-ending collection of packets
with errors. Sometimes it's not obvious what is wrong with them.
Dire Wolf will usually tell you what is wrong. First,
cut-n-paste the bad packets into a text file. Here a couple examples:
cut-n-paste the bad packets into a text file. Here a few examples:
.P
.RS
.nf
n2cma>APRS,TCPIP*,qAC,SEVENTH:@212127z43.2333n/77.1w_338/002g001t025P000h65b10208.wview_5_19_0
.P
K0YTH-10>APNU3B,NULL,qAR,K0DMF-10:!4601.5NS09255.52W#PHG6360/W2,MNn 444.575
.P
00 82 a0 ae ae 62 60 e0 82 96 68 84 40 40 60 9c 68 b0 ae 86 40 e0 40 ae 92 88 8a 64 63 03 f0 3e 45 4d 36 34 6e 65 2f 23 20 45 63 68 6f 6c 69 6e 6b 20 31 34 35 2e 33 31 30 2f 31 30 30 68 7a 20 54 6f 6e 65
+.fi
.RE
.P
If you simply fed this into decode_aprs, it would complain about the
@ -67,7 +73,7 @@ Address has lower case letters. "n2cma" must be all upper case.
.P
After changing the source address to upper case, there are other issues. Identifying them is left as an exercise for the reader.
.P
And in the second example,
In the second example,
.P
.RS
.PD 0
@ -77,6 +83,17 @@ Invalid character in longitude. Found '9' when expecting 0 or 1 for hundreds of
.PD
.RE
.P
In the third example,
.P
.RS
.PD 0
Warning: Lower case letter in Maidenhead locator. Specification requires upper case.
.P
Digi2 Address, " WIDE2-1" contains character other than letter or digit in character position 1.
.PD
.RE
.SH SEE ALSO

View File

@ -27,8 +27,12 @@ RMS Express, and many others.
Read configuration file from specified location rather than the default locations.
.TP
.BI "-l " "dir"
Generate log files in specified directory. Use "." for current directory.
.BI "-l " "logdir"
Generate daily log files in specified directory. Use "." for current directory.
.TP
.BI "-L " "logfile"
Generate single log file with fixed name.
.TP
.BI "-r " "n"
@ -135,18 +139,18 @@ gqrx (2.3 and later) has the ability to send streaming audio through a UDP socke
direwolf can listen over a UDP port with options like this:
.RS
.P
direwolf -n 1 -r 48000 -b 16 udp:7355
direwolf \-n 1 \-r 48000 \-b 16 udp:7355
.RE
.P
Other SDR applications might produce audio on stdout so it is convenient to pipe into the next application. In this example, the final "-" means read from stdin.
.RS
.P
rtl_fm -f 144.39M -o 4 - | direwolf -n 1 -r 24000 -b 16 -
rtl_fm \-f 144.39M \-o 4 \- | direwolf \-n 1 \-r 24000 \-b 16 \-
.RE
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -5,12 +5,12 @@ gen_packets \- Generate audio file for AX.25 frames.
.SH SYNOPSIS
.B gen_packets -o
.B gen_packets \-o
.I wav-file-out
[ \fIoptions\fR ] [ \fItext-file\fR | - ]
[ \fIoptions\fR ] [ \fItext-file\fR | \- ]
.RS
.P
\fIwav-file-out\fR is the result. The -o option is required.
\fIwav-file-out\fR is the result. The \-o option is required.
.P
\fItext-file\fR may contain AX.25 packets in the standard monitoring format. Use "-" to read from stdin. If not specified, a default builtin message will be used.
.RE
@ -25,7 +25,7 @@ It is very flexible allowing a wide range of audio sample rates, data speeds, an
.TP
.BI "-a " "n"
Signal amplitude in range of 0 - 200%. Default 50. Note that 100% is corresponds to signal peaks of +/- 16383 so we have plenty of headroom to avoid saturation.
Signal amplitude in range of 0-200%. Default 50. Note that 100% is corresponds to signal peaks of +/- 16383 so we have plenty of headroom to avoid saturation.
.TP
.BI "-b " "n"
@ -70,7 +70,7 @@ Send output to .wav file.
.SH EXAMPLES
.P
.B gen_packets -o x.wav
.B gen_packets \-o x.wav
.P
.RS
With all defaults, a built-in test message is generated
@ -78,27 +78,27 @@ with standard Bell 202 tones used for packet radio on ordinary
VHF FM transceivers.
.RE
.P
.B gen_packets -o x.wav -g -b 9600
.B gen_packets \-o x.wav \-g \-b 9600
.PD 0
.P
.PD
.B gen_packets -o x.wav -B 9600
.B gen_packets \-o x.wav \-B 9600
.P
.RS
Both of these are equivalent. "-B 9600" automatically selects scrambled baseband rather than AFSK.
.RE
.P
.B gen_packets -o x.wav -m 1600 -s 1800 -b 300
.B gen_packets \-o x.wav \-m 1600 \-s 1800 \-b 300
.PD 0
.P
.PD
.B gen_packets -o x.wav -B 300
.B gen_packets \-o x.wav \-B 300
.P
.RS
Both of these generate 200 Hz shift, 300 baud, suitable for HF SSB transceiver.
.RE
.P
.B echo -n 'WB2OSZ>WORLD:Hello, world!' | gen_packets -a 25 -o x.wav -
.B echo \-n 'WB2OSZ>WORLD:Hello, world!' | gen_packets \-a 25 \-o x.wav \-
.PD 0
.P
.PD

60
man1/kissutil.1 Normal file
View File

@ -0,0 +1,60 @@
.TH KISSUTIL 1
.SH NAME
kissutil \- KISS TNC troubleshooting and Application Interface.
.SH SYNOPSIS
.B kissutil
[ \fIoptions\fR ]
.SH DESCRIPTION
\fBkissutil\fR can be used interactively for troubleshooting a KISS TNC.
It is usable with direwolf and other generic KISS TNCs connected to a serial port.
It can also be used as an application interface where each side places files in a
directory for the other to process.
See User Guide for more details.
.SH OPTIONS
.TP
.BI "-h " "host"
Hostname or IP address for a TCP KISS TNC. Default is localhost.
.TP
.BI "-p " "port"
A number may be specified for a TCP port other than the default 8001.
If not a number, it is considered to be a serial port name such as /dev/ttyS0 or COM3.
.TP
.BI "-s " "speed"
Speed for serial port. e.g. 9600.
.TP
.BI "-o " "rec-directory"
For each received frame, a new file is created here.
It is expected that some other application will process files in this directory then delete them.
.TP
.BI "-T " "format"
Each received frame will be preceded by a timestamp in the specified format.
See strftime documentation for a description of the format string.
Example: %H:%M:%S for current time in hours, minutes, seconds.
.TP
.BI "-f " "xmit-directory"
Files in this directory are transmited and deleted.
Another application places a file here when it wants something to be transmitted.
.TP
.BI "-v "
Verbose - Display the KISS frames going to and from the TNC.
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -30,5 +30,5 @@ zone = 19T, easting = 307504, northing = 4721177
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -36,5 +36,5 @@ None.
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -60,5 +60,5 @@ Push buttons for two-key method:
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -62,5 +62,5 @@ Decoded text from two-key method:
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -36,5 +36,5 @@ latitude = 42.662139, longitude = -71.365553
.SH SEE ALSO
More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location.
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll
Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll

View File

@ -27,11 +27,11 @@
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include "direwolf.h"
#include "textcolor.h"

View File

@ -49,10 +49,11 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include "direwolf.h"
#include "textcolor.h"
/*

View File

@ -292,6 +292,14 @@ unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) {
*
*------------------------------------------------------------------------------*/
static float dc_average[MAX_CHANS];
int multi_modem_get_dc_average (int chan)
{
// Scale to +- 200 so it will like the deviation measurement.
return ( (int) ((float)(dc_average[chan]) * (200.0f / 32767.0f) ) );
}
__attribute__((hot))
void multi_modem_process_sample (int chan, int audio_sample)
@ -300,12 +308,29 @@ void multi_modem_process_sample (int chan, int audio_sample)
int subchan;
static int i = 0; /* for interleaving among multiple demodulators. */
// TODO: temp debug, remove this.
// Accumulate an average DC bias level.
// Shouldn't happen with a soundcard but could with mistuned SDR.
assert (save_audio_config_p->achan[chan].num_subchan > 0 && save_audio_config_p->achan[chan].num_subchan <= MAX_SUBCHANS);
assert (save_audio_config_p->achan[chan].num_slicers > 0 && save_audio_config_p->achan[chan].num_slicers <= MAX_SLICERS);
dc_average[chan] = dc_average[chan] * 0.999f + (float)audio_sample * 0.001f;
// Issue 128. Someone ran into this.
//assert (save_audio_config_p->achan[chan].num_subchan > 0 && save_audio_config_p->achan[chan].num_subchan <= MAX_SUBCHANS);
//assert (save_audio_config_p->achan[chan].num_slicers > 0 && save_audio_config_p->achan[chan].num_slicers <= MAX_SLICERS);
if (save_audio_config_p->achan[chan].num_subchan <= 0 || save_audio_config_p->achan[chan].num_subchan > MAX_SUBCHANS ||
save_audio_config_p->achan[chan].num_slicers <= 0 || save_audio_config_p->achan[chan].num_slicers > MAX_SLICERS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! Something is seriously wrong in %s %s.\n", __FILE__, __func__);
dw_printf ("chan = %d, num_subchan = %d [max %d], num_slicers = %d [max %d]\n", chan,
save_audio_config_p->achan[chan].num_subchan, MAX_SUBCHANS,
save_audio_config_p->achan[chan].num_slicers, MAX_SLICERS);
dw_printf ("Please report this message and include a copy of your configuration file.\n");
exit (EXIT_FAILURE);
}
/* Formerly one loop. */
/* 1.2: We can feed one demodulator but end up with multiple outputs. */

View File

@ -14,6 +14,8 @@ void multi_modem_init (struct audio_s *pmodem);
void multi_modem_process_sample (int c, int audio_sample);
int multi_modem_get_dc_average (int chan);
void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries);
#endif

274
pfilter.c
View File

@ -874,11 +874,15 @@ static int filt_t (pfstate_t *pf)
case 'p': /* Position */
if (*infop == '!') return (1);
if (*infop == '\'') return (1);
if (*infop == '/') return (1);
if (*infop == '=') return (1);
if (*infop == '@') return (1);
if (*infop == '`') return (1);
if (*infop == '\'') return (1); // MIC-E
if (*infop == '`') return (1); // MIC-E
// What if we have "_" symbol code for weather?
// Still consider as position.
// The same packet can match more than one type here.
break;
case 'o': /* Object */
@ -897,6 +901,11 @@ static int filt_t (pfstate_t *pf)
if (*infop == '?') return (1);
break;
case 'c': /* station Capabilities - my extension */
/* Most often used for IGate statistics. */
if (*infop == '<') return (1);
break;
case 's': /* Status */
if (*infop == '>') return (1);
break;
@ -910,16 +919,32 @@ static int filt_t (pfstate_t *pf)
if (*infop == '{') return (1);
break;
case 'h': /* third party Header - my extension */
if (*infop == '}') return (1);
break;
case 'w': /* Weather */
if (*infop == '@') return (1);
if (*infop == '*') return (1);
if (*infop == '_') return (1);
if (*infop == '*') return (1); // Peet Bros
if (*infop == '_') return (1); // Weather report, no position.
if (strncmp(infop, "!!", 2) == 0) return(1); // Ultimeter 2000.
/* '$' is normally raw GPS. Check for special case. */
if (strncmp(infop, "$ULTW", 5) == 0) return (1);
/* TODO: Positions !=/@ can be weather. */
/* Need to check for _ symbol. */
/* Positions !=/@ with symbol code _ are weather. */
if (strchr("!=/@", *infop) != NULL &&
pf->decoded.g_symbol_code == '_') return (1);
/* Object with _ symbol is also weather. APRS protocol spec page 66. */
if (*infop == ';' &&
pf->decoded.g_symbol_code == '_') return (1);
// TODO: need more test cases at end for new weather cases.
break;
case 'n': /* NWS format */
@ -1059,15 +1084,51 @@ static int filt_r (pfstate_t *pf, char *sdist)
*
* Description:
*
* s/pri
* s/pri/alt
* s/pri/alt/
* s/pri/alt/over
*
* "pri" is zero or more symbols from the primary symbol set.
* Symbol codes are any printable ASCII character other than | or ~.
* (Zero symbols here would be sensible only if later alt part is specified.)
* "alt" is one or more symbols from the alternate symbol set.
* "over" is overlay characters. Overlays apply only to the alternate symbol set.
* "over" is overlay characters for the alternate symbol set.
* Only upper case letters, digits, and \ are allowed here.
* If the last part is not specified, any overlay or lack of overlay, is ignored.
* If the last part is specified, only the listed overlays will match.
* An explicit lack of overlay is represented by the \ character.
*
* Examples:
* s/-> Allow house and car from primary symbol table.
* s//# Allow alternate table digipeater, with or without overlay.
* s//#/\ Allow alternate table digipeater, only if no overlay.
* s//#/SL1 Allow alternate table digipeater, with overlay S, L, or 1
* s/O Balloon.
* s/-> House or car from primary symbol table.
*
* s//# Alternate table digipeater, with or without overlay.
* s//#/\ Alternate table digipeater, only if no overlay.
* s//#/SL1 Alternate table digipeater, with overlay S, L, or 1.
* s//#/SL\ Alternate table digipeater, with S, L, or no overlay.
*
* s/s/s Any variation of watercraft. Either symbol table. With or without overlay.
* s/s/s/ Ship or ship sideview, only if no overlay.
* s//s/J Jet Ski.
*
* What if you want to use the / symbol when / is being used as a delimiter here? Recall that you
* can use some other special character after the initial lower case letter and this becomes the
* delimiter for the rest of the specification.
*
* Examples:
*
* s:/ Red Dot.
* s::/ Waypoint Destination, with or without overlay.
* s:/:/ Either Red Dot or Waypoint Destination.
* s:/:/: Either Red Dot or Waypoint Destination, no overlay.
*
* Bad example:
*
* Someone tried using this to include ballons: s/'/O/-/#/_
* probably following the buddy filter pattern of / between each alternative.
* There should be an error message because it has more than 3 delimiter characters.
*
*
*------------------------------------------------------------------------------*/
@ -1075,8 +1136,9 @@ static int filt_s (pfstate_t *pf)
{
char str[MAX_TOKEN_LEN];
char *cp;
char sep[2];
char *pri, *alt, *over;
char sep[2]; // Delimiter character. Typically / but it could be different.
char *pri = NULL, *alt = NULL, *over = NULL, *extra = NULL;
char *x;
strlcpy (str, pf->token_str, sizeof(str));
@ -1084,51 +1146,132 @@ static int filt_s (pfstate_t *pf)
sep[1] = '\0';
cp = str + 2;
// TODO: check here.
// First, separate the parts and do a strict syntax check.
pri = strsep (&cp, sep);
if (pri == NULL) {
if (pri != NULL) {
// Zero length is acceptable if alternate symbol(s) specified. Will check that later.
for (x = pri; *x != '\0'; x++) {
if ( ! isprint(*x) || *x == '|' || *x == '~') {
print_error (pf, "Symbol filter, primary must be printable ASCII character(s) other than | or ~.");
return (-1);
}
}
alt = strsep (&cp, sep);
if (alt != NULL) {
// Zero length after second / would be pointless.
if (strlen(alt) == 0) {
print_error (pf, "Nothing specified for alternate symbol table.");
return (-1);
}
for (x = alt; *x != '\0'; x++) {
if ( ! isprint(*x) || *x == '|' || *x == '~') {
print_error (pf, "Symbol filter, alternate must be printable ASCII character(s) other than | or ~.");
return (-1);
}
}
over = strsep (&cp, sep);
if (over != NULL) {
// Zero length is acceptable and is not the same as missing.
for (x = over; *x != '\0'; x++) {
if ( (! isupper(*x)) && (! isdigit(*x)) && *x != '\\') {
print_error (pf, "Symbol filter, overlay must be upper case letter, digit, or \\.");
return (-1);
}
}
extra = strsep (&cp, sep);
if (extra != NULL) {
print_error (pf, "More than 3 delimiter characters in Symbol filter.");
return (-1);
}
}
}
else {
// No alt part is OK if at least one primary symbol was specified.
if (strlen(pri) == 0) {
print_error (pf, "No symbols specified for Symbol filter.");
return (-1);
}
}
}
else {
print_error (pf, "Missing arguments for Symbol filter.");
return (-1);
}
if (pf->decoded.g_symbol_table == '/' && strchr(pri, pf->decoded.g_symbol_code) != NULL) {
/* Found in primary symbols. All done. */
return (1);
}
alt = strsep (&cp, sep);
if (alt == NULL) {
// This applies only for Position, Object, Item.
// decode_aprs() should set symbol code to space to mean undefined.
if (pf->decoded.g_symbol_code == ' ') {
return (0);
}
if (strlen(alt) == 0) {
/* We have s/.../ */
print_error (pf, "Missing alternate symbols for Symbol filter.");
return (-1);
// Look for Primary symbols.
if (pf->decoded.g_symbol_table == '/') {
if (pri != NULL && strlen(pri) > 0) {
return (strchr(pri, pf->decoded.g_symbol_code) != NULL);
}
}
if (alt == NULL) {
return (0);
}
//printf ("alt=\"%s\" sym='%c'\n", alt, pf->decoded.g_symbol_code);
if (strchr(alt, pf->decoded.g_symbol_code) == NULL) {
/* Not found in alternate symbols. Reject. */
return (0);
}
// Look for Alternate symbols.
over = strsep (&cp, sep);
if (over == NULL) {
/* alternate, with or without overlay. */
return (pf->decoded.g_symbol_table != '/');
}
if (strchr(alt, pf->decoded.g_symbol_code) != NULL) {
// printf ("over=\"%s\" table='%c'\n", over, pf->decoded.g_symbol_table);
// We have a match but that might not be enough.
// We must see if there was an overlay part specified.
if (strlen(over) == 0) {
return (pf->decoded.g_symbol_table == '\\');
}
if (over != NULL) {
if (strlen(over) > 0) {
// Non-zero length overlay part was specified.
// Need to match one of them.
return (strchr(over, pf->decoded.g_symbol_table) != NULL);
}
}
else {
// Zero length overlay part was specified.
// We must have no overlay, i.e. table is \.
return (pf->decoded.g_symbol_table == '\\');
}
}
else {
// No check of overlay part. Just make sure it is not primary table.
return (pf->decoded.g_symbol_table != '/');
}
}
return (0);
} /* end filt_s */
/*------------------------------------------------------------------------------
@ -1494,10 +1637,15 @@ int main ()
pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1);
pftest (127, "t/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
pftest (130, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (131, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
pftest (128, "t/c", "S0RCE>DEST:<stationcapabilities", 1);
pftest (129, "t/h", "S0RCE>DEST:<stationcapabilities", 0);
pftest (130, "t/h", "S0RCE>DEST:}thirdpartyheaderwhatever", 1);
pftest (131, "t/c", "S0RCE>DEST:}thirdpartyheaderwhatever", 0);
pftest (140, "( t/t & b/WB2OSZ ) | ( t/o & ! r/42.6/-71.3/1 )", "WB2OSZ>APDW12:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1);
pftest (140, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (141, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
pftest (145, "( t/t & b/WB2OSZ ) | ( t/o & ! r/42.6/-71.3/1 )", "WB2OSZ>APDW12:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1);
pftest (150, "s/->", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (151, "s/->", "WB2OSZ-5>APDW12:!4237.14N/07120.83W-PHG7140Chelmsford MA", 1);
@ -1515,13 +1663,28 @@ int main ()
pftest (160, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (161, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 0);
pftest (162, "s//#/LS1", "WB2OSZ-5>APDW12:!4237.14N/07120.83W#PHG7140Chelmsford MA", 0);
pftest (163, "s//#/LS\\", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W#PHG7140Chelmsford MA", 1);
pftest (170, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (171, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (172, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (173, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (174, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (175, "v/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (170, "s:/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 1);
pftest (171, "s:/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 0);
pftest (172, "s::/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 0);
pftest (173, "s::/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 1);
pftest (174, "s:/:/", "WB2OSZ-5>APDW12:!4237.14N/07120.83W/PHG7140Chelmsford MA", 1);
pftest (175, "s:/:/", "WB2OSZ-5>APDW12:!4237.14N\\07120.83W/PHG7140Chelmsford MA", 1);
pftest (176, "s:/:/", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1);
pftest (177, "s:/:/:X", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1);
// FIXME: Different on Windows and 64 bit Linux.
//pftest (178, "s:/:/:", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 1);
pftest (179, "s:/:/:\\", "WB2OSZ-5>APDW12:!4237.14NX07120.83W/PHG7140Chelmsford MA", 0);
pftest (180, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (181, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1*,DIGI2,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (182, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (183, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3*,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (184, "v/DIGI2/DIGI3", "WB2OSZ-5>APDW12,DIGI1,DIGI2,DIGI3,DIGI4*:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (185, "v/DIGI9/DIGI2", "WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
/* Test error reporting. */
@ -1540,12 +1703,23 @@ int main ()
pftest (226, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (227, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
// FIXME: behaves differently on Windows and Linux
// FIXME: behaves differently on Windows and Linux. Why?
// On Windows we have our own version of strsep because it's not in the MS library.
// It must behave differently than the Linux version when nothing follows the last separator.
//pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (229, "i/30", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (230, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
pftest (240, "s/", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (241, "s/'/O/-/#/_", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (242, "s/O/O/c", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (243, "s/O/O/1/2", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (244, "s/O/|/1", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (245, "s//", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (246, "s///", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
// TODO: to be continued...
if (error_count > 0) {

143
ptt.c
View File

@ -52,6 +52,9 @@
*
* Handle more complicated gpio node names for CubieBoard, etc.
*
* Version 1.5: Ability to use GPIO pins of CM108/CM119 for PTT signal.
*
*
* References: http://www.robbayer.com/files/serial-win.pdf
*
* https://www.kernel.org/doc/Documentation/gpio.txt
@ -59,16 +62,20 @@
*---------------------------------------------------------------*/
/*
Idea for future enhancement:
A growing number of people have been asking about support for the DMK URI
or the similar RB-USB RIM.
A growing number of people have been asking about support for the DMK URI.
This uses a C-Media CM108/CM119 with one interesting addition, a GPIO
These use a C-Media CM108/CM119 with an interesting addition, a GPIO
pin is used to drive PTT. Here is some related information.
DMK URI:
http://www.dmkeng.com/URI_Order_Page.htm
http://dmkeng.com/images/URI%20Schematic.pdf
RB-USB RIM:
http://www.repeater-builder.com/products/usb-rim-lite.html
http://www.repeater-builder.com/voip/pdf/cm119-datasheet.pdf
Homebrew versions of the same idea:
@ -83,6 +90,7 @@
http://docs.allstarlink.org/drupal/
http://soundmodem.sourcearchive.com/documentation/0.16-1/ptt_8c_source.html
https://github.com/N0NB/hamlib/blob/master/src/cm108.c#L190
http://permalink.gmane.org/gmane.linux.hams.hamlib.devel/3420
Information about the "hidraw" device:
@ -92,12 +100,31 @@
https://github.com/signal11/hidapi/blob/master/libusb/hid.c
http://stackoverflow.com/questions/899008/howto-write-to-the-gpio-pin-of-the-cm108-chip-in-linux
https://www.kernel.org/doc/Documentation/hid/hidraw.txt
https://github.com/torvalds/linux/blob/master/samples/hidraw/hid-example.c
In version 1.3, we add HAMLIB support which should be able to do this.
(Linux only & haven't verified that it actually works yet!)
Similar chips: SSS1621, SSS1623
Might want to have CM108 GPIO support built in, someday, for simpler building & configuration.
Maybe even for Windows. ;-)
https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/
Here is an attempt to add direct CM108 support.
Seems to be hardcoded for only a single USB audio adapter.
https://github.com/donothingloop/direwolf_cm108
In version 1.3, we add HAMLIB support which should be able to do this in a roundabout way.
(Linux only at this point.)
This is documented in the User Guide, section called,
"Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)"
It's rather involved and the explantion doesn't cover the case of multiple
USB-Audio adapters. It would be nice to have a little script which lists all
of the USB-Audio adapters and the corresponding /dev/hidraw device.
( We now have it. The included "cm108" application. )
In version 1.5 we have a flexible, easy to use implementation for Linux.
Windows would be a lot of extra work because USB devices are nothing like Linux.
We'd be starting from scratch to figure out how to do it.
*/
@ -126,6 +153,10 @@
#include <hamlib/rig.h>
#endif
#ifdef USE_CM108
#include "cm108.h"
#endif
/* So we can have more common code for fd. */
typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1)
@ -157,10 +188,6 @@ typedef int HANDLE;
#endif
#if TEST
#define dw_printf printf
#endif
static struct audio_s *save_audio_config_p; /* Save config information for later use. */
@ -603,10 +630,12 @@ void export_gpio(int ch, int ot, int invert, int direction)
* PTT_METHOD_GPIO - general purpose I/O.
* PTT_METHOD_LPT - Parallel printer port.
* PTT_METHOD_HAMLIB - HAMLib rig control.
* PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio.
*
* ptt_device Name of serial port device.
* e.g. COM1 or /dev/ttyS0.
* HAMLIB can also use hostaddr:port.
* Like /dev/hidraw1 for CM108.
*
* ptt_line RTS or DTR when using serial port.
*
@ -992,6 +1021,32 @@ void ptt_init (struct audio_s *audio_config_p)
#endif
/*
* Confirm what is going on with CM108 GPIO output.
* Could use some error checking for overlap.
*/
#if USE_CM108
for (ch = 0; ch < MAX_CHANS; ch++) {
if (audio_config_p->achan[ch].valid) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_CM108) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Using %s GPIO %d for channel %d %s control.\n",
audio_config_p->achan[ch].octrl[ot].ptt_device,
audio_config_p->achan[ch].octrl[ot].out_gpio_num,
ch,
otnames[ot]);
}
}
}
}
#endif
/* Why doesn't it transmit? Probably forgot to specify PTT option. */
@ -1059,7 +1114,9 @@ void ptt_set (int ot, int chan, int ptt_signal)
* This is a very convenient place to get that information.
*/
#ifndef TEST
dlq_channel_busy (chan, ot, ptt_signal);
#endif
/*
* Inverted output?
@ -1221,6 +1278,21 @@ void ptt_set (int ot, int chan, int ptt_signal)
}
#endif
/*
* Using CM108 USB Audio adapter GPIO?
*/
#ifdef USE_CM108
if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_CM108) {
if (cm108_set_gpio_pin (save_audio_config_p->achan[chan].octrl[ot].ptt_device,
save_audio_config_p->achan[chan].octrl[ot].out_gpio_num, ptt) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: %s for channel %d has failed. See User Guide for troubleshooting tips.\n", otnames[ot], chan);
}
}
#endif
} /* end ptt_set */
@ -1354,18 +1426,15 @@ void ptt_term (void)
/*
* Quick stand-alone test for above.
*
* gcc -DTEST -o ptest ptt.c ; ./ptest
* gcc -DTEST -o ptest ptt.c textcolor.o misc.a ; ./ptest
*
* TODO: Retest this, add CM108 GPIO to test.
*/
#if TEST
void text_color_set (dw_color_t c) { }
#define dw_printf printf
main ()
int main ()
{
struct audio_s my_audio_config;
int n;
@ -1375,17 +1444,18 @@ main ()
my_audio_config.adev[0].num_channels = 2;
my_audio_config.valid[0] = 1;
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
//strlcpy (my_audio_config.ptt_device, "COM1", sizeof(my_audio_config.ptt_device));
strlcpy (my_audio_config.ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.ptt_device));
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_RTS;
my_audio_config.achan[0].valid = 1;
my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
// TODO: device should be command line argument.
strlcpy (my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device, "COM3", sizeof(my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device));
//strlcpy (my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device));
my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_RTS;
my_audio_config.valid[1] = 1;
my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
//strlcpy (my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device, "COM1", sizeof(my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device));
strlcpy (my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_device));
my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_DTR;
my_audio_config.achan[1].valid = 1;
my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
strlcpy (my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device, "COM3", sizeof(my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device));
//strlcpy (my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device));
my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_DTR;
/* initialize - both off */
@ -1420,7 +1490,7 @@ main ()
/* Same thing again but invert RTS. */
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_invert = 1;
my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_invert = 1;
ptt_init (&my_audio_config);
@ -1476,6 +1546,14 @@ main ()
#endif
/* Parallel printer port. */
#if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
// TODO
#if 0
memset (&my_audio_config, 0, sizeof(my_audio_config));
my_audio_config.num_channels = 2;
my_audio_config.valid[0] = 1;
@ -1496,16 +1574,11 @@ main ()
}
ptt_term ();
/* Parallel printer port. */
#if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
// TODO
#endif
#endif
return(0);
}
#endif /* TEST */

5
recv.c
View File

@ -378,6 +378,11 @@ void recv_process (void)
lm_channel_busy (pitem);
break;
case DLQ_SEIZE_CONFIRM:
lm_seize_confirm (pitem);
break;
case DLQ_CLIENT_CLEANUP:
dl_client_cleanup (pitem);

2
search_sdks.sh Normal file → Executable file
View File

@ -43,7 +43,7 @@ if [ $valid_flag -eq "0" ]; then
prompt="Select SDK to use:"
loc1=( $(find /Applications/XCode.app -type d -name "MacOSX10.*.sdk") )
loc1=( $(find /Applications/Xcode.app -type l -name "MacOSX10.*.sdk") )
loc2=( $(find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.*.sdk") )
options=("${loc1[@]}" "${loc2[@]}")

View File

@ -1,13 +1,7 @@
// TODO: Needs more clean up and testing of error conditions.
// TODO: use this in place of other similar code.
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2014, 2015 John Langner, WB2OSZ
// Copyright (C) 2014, 2015, 2017 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
@ -73,8 +67,10 @@
* Inputs: devicename - For Windows, usually like COM5.
* For Linux, usually /dev/tty...
* "COMn" also allowed and converted to /dev/ttyS(n-1)
* Could be /dev/rfcomm0 for Bluetooth.
*
* baud - Speed. 4800, 9600, etc.
* baud - Speed. 1200, 4800, 9600 bps, etc.
* If 0, leave it alone.
*
* Returns Handle for serial port or MYFDERROR for error.
*
@ -143,6 +139,7 @@ MYFDTYPE serial_port_open (char *devicename, int baud)
switch (baud) {
case 0: /* Leave it alone. */ break;
case 1200: dcb.BaudRate = CBR_1200; break;
case 2400: dcb.BaudRate = CBR_2400; break;
case 4800: dcb.BaudRate = CBR_4800; break;
@ -233,15 +230,19 @@ MYFDTYPE serial_port_open (char *devicename, int baud)
switch (baud) {
case 0: /* Leave it alone. */ break;
case 1200: cfsetispeed (&ts, B1200); cfsetospeed (&ts, B1200); break;
case 2400: cfsetispeed (&ts, B2400); cfsetospeed (&ts, B2400); break;
case 4800: cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800); break;
case 9600: cfsetispeed (&ts, B9600); cfsetospeed (&ts, B9600); break;
case 19200: cfsetispeed (&ts, B19200); cfsetospeed (&ts, B19200); break;
case 38400: cfsetispeed (&ts, B38400); cfsetospeed (&ts, B38400); break;
#ifndef __APPLE__
// Not defined for Mac OSX.
// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2072
case 57600: cfsetispeed (&ts, B57600); cfsetospeed (&ts, B57600); break;
case 115200: cfsetispeed (&ts, B115200); cfsetospeed (&ts, B115200); break;
#endif
default: text_color_set(DW_COLOR_ERROR);
dw_printf ("serial_port_open: Unsupported speed %d. Using 4800.\n", baud);
cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800);
@ -305,8 +306,10 @@ int serial_port_write (MYFDTYPE fd, char *str, int len)
}
else if ((int)nwritten != len)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len);
// Do we want this message here?
// Or rely on caller to check and provide something more meaningful for the usage?
//text_color_set(DW_COLOR_ERROR);
//dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len);
}
return (nwritten);
@ -317,8 +320,10 @@ int serial_port_write (MYFDTYPE fd, char *str, int len)
written = write (fd, str, (size_t)len);
if (written != len)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing to serial port. err=%d\n\n", written);
// Do we want this message here?
// Or rely on caller to check and provide something more meaningful for the usage?
//text_color_set(DW_COLOR_ERROR);
//dw_printf ("Error writing to serial port. err=%d\n\n", written);
return (-1);
}
@ -402,8 +407,9 @@ int serial_port_get1 (MYFDTYPE fd)
CloseHandle(ov_rd.hEvent);
if (n != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Serial port failed to get one byte. n=%d.\n\n", (int)n);
//text_color_set(DW_COLOR_ERROR);
//dw_printf ("Serial port failed to get one byte. n=%d.\n\n", (int)n);
return (-1);
}
@ -414,8 +420,8 @@ int serial_port_get1 (MYFDTYPE fd)
n = read(fd, &ch, (size_t)1);
if (n != 1) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("serial_port_get1(%d) returns -1 for error.\n", fd);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("serial_port_get1(%d) returns -1 for error.\n", fd);
return (-1);
}
@ -447,8 +453,14 @@ int serial_port_get1 (MYFDTYPE fd)
*
*--------------------------------------------------------------------*/
// TODO:
void serial_port_close (MYFDTYPE fd)
{
#if __WIN32__
CloseHandle (fd);
#else
close (fd);
#endif
}
/* end serial_port.c */

104
server.c
View File

@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017 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
@ -59,6 +59,8 @@
*
* 'y' Ask Outstanding frames waiting on a Port (new in 1.2)
*
* 'Y' How many frames waiting for transmit for a particular station (new in 1.5)
*
* '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)
@ -92,6 +94,8 @@
*
* 'y' Outstanding frames waiting on a Port (new in 1.2)
*
* 'Y' How many frames waiting for transmit for a particular station (new in 1.5)
*
* 'C' AX.25 Connection Received (new in 1.4)
*
* 'D' Connected AX.25 Data (new in 1.4)
@ -487,14 +491,16 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc)
cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, cmd_listen_thread, (void*)client, 0, NULL);
if (cmd_listen_th[client] == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create AGW command listening thread\n");
dw_printf ("Could not create AGW command listening thread for client %d\n", client);
return;
}
#else
e = pthread_create (&cmd_listen_tid[client], NULL, cmd_listen_thread, (void *)(long)client);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create AGW command listening thread");
dw_printf ("Could not create AGW command listening thread for client %d\n", client);
// Replace add perror with better message handling.
perror("");
return;
}
#endif
@ -637,8 +643,7 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
// TODO: "attached" or some other term would be better because "connected" has a different meaning for AX.25.
dw_printf("\nConnected to AGW client application %d ...\n\n", client);
dw_printf("\nAttached to AGW client application %d ...\n\n", client);
/*
* The command to change this is actually a toggle, not explicit on or off.
@ -729,7 +734,7 @@ static THREAD_F connect_listen_thread (void *arg)
client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
text_color_set(DW_COLOR_INFO);
dw_printf("\nConnected to AGW client application %d...\n\n", client);
dw_printf("\nAttached to AGW client application %d...\n\n", client);
/*
* The command to change this is actually a toggle, not explicit on or off.
@ -813,7 +818,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
}
#if __WIN32__
err = send (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE), 0);
err = SOCK_SEND (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
if (err == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
@ -824,7 +829,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dlq_client_cleanup (client);
}
#else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
err = SOCK_SEND (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
if (err <= 0)
{
text_color_set(DW_COLOR_ERROR);
@ -918,7 +923,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
}
#if __WIN32__
err = send (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE), 0);
err = SOCK_SEND (client_sock[client], (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
if (err == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
@ -929,7 +934,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
dlq_client_cleanup (client);
}
#else
err = write (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
err = SOCK_SEND (client_sock[client], &agwpe_msg, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
if (err <= 0)
{
text_color_set(DW_COLOR_ERROR);
@ -1142,14 +1147,7 @@ static int read_from_socket (int fd, char *ptr, int len)
while (got_bytes < len) {
int n;
#if __WIN32__
//TODO: any flags for send/recv?
n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
#else
n = read (fd, ptr + got_bytes, len - got_bytes);
#endif
n = SOCK_RECV (fd, ptr + got_bytes, len - got_bytes);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
@ -1192,10 +1190,7 @@ static void send_to_client (int client, void *reply_p)
{
struct agwpe_s *ph;
int len;
#if __WIN32__
#else
int err;
#endif
ph = (struct agwpe_s *) reply_p; // Replies are often hdr + other stuff.
@ -1213,12 +1208,8 @@ static void send_to_client (int client, void *reply_p)
debug_print (TO_CLIENT, client, ph, len);
}
#if __WIN32__
send (client_sock[client], (char*)(ph), len, 0);
#else
err = write (client_sock[client], ph, len);
err = SOCK_SEND (client_sock[client], (char*)(ph), len);
(void)err;
#endif
}
@ -1260,16 +1251,20 @@ static THREAD_F cmd_listen_thread (void *arg)
}
/*
* Take some precautions to guard against bad data
* which could cause problems later.
* Take some precautions to guard against bad data which could cause problems later.
*/
if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nInvalid port number, %d, in command '%c', from AGW client application %d.\n",
cmd.hdr.portx, cmd.hdr.datakind, client);
cmd.hdr.portx = 0; // avoid subscript out of bounds, try to keep going.
}
/*
* Call to/from must not exceeed 9 characters.
* Call to/from fields are 10 bytes but contents must not exceeed 9 characters.
* It's not guaranteed that unused bytes will contain 0 so we
* don't issue error message in this case.
*/
cmd.hdr.call_from[sizeof(cmd.hdr.call_from)-1] = '\0';
cmd.hdr.call_to[sizeof(cmd.hdr.call_to)-1] = '\0';
@ -1456,6 +1451,7 @@ static THREAD_F cmd_listen_thread (void *arg)
// YAAC asks for this.
// Fake it to keep application happy.
// TODO: Supply real values instead of just faking it.
reply.on_air_baud_rate = 0;
reply.traffic_level = 1;
@ -1697,7 +1693,17 @@ static THREAD_F cmd_listen_thread (void *arg)
struct via_info {
unsigned char num_digi; /* Expect to be in range 1 to 7. Why not up to 8? */
char dcall[7][10];
} *v = (struct via_info *)cmd.data;
}
#if 1
// October 2017. gcc ??? complained:
// warning: dereferencing pointer 'v' does break strict-aliasing rules
// Try adding this attribute to get rid of the warning.
// If this upsets your compiler, take it out.
// Let me know. Maybe we could put in a compiler version check here.
__attribute__((__may_alias__))
#endif
*v = (struct via_info *)cmd.data;
char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
int num_calls = 2; /* 2 plus any digipeaters. */
@ -1843,6 +1849,7 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'y': /* Ask Outstanding frames waiting on a Port */
/* Number of frames sitting in transmit queue for specified channel. */
{
struct {
struct agwpe_s hdr;
@ -1857,7 +1864,40 @@ static THREAD_F cmd_listen_thread (void *arg)
int n = 0;
if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) {
n = tq_count (cmd.hdr.portx, TQ_PRIO_0_HI) + tq_count (cmd.hdr.portx, TQ_PRIO_1_LO);
n = tq_count (cmd.hdr.portx, -1, "", "", 0);
}
reply.data_NETLE = host2netle(n);
send_to_client (client, &reply);
}
break;
case 'Y': /* How Many Outstanding frames wait for tx for a particular station */
/* Number of frames sitting in transmit queue for given channel, */
/* source (optional) and destination addresses. */
{
char source[AX25_MAX_ADDR_LEN];
char dest[AX25_MAX_ADDR_LEN];
struct {
struct agwpe_s hdr;
int data_NETLE; // Little endian order.
} reply;
strlcpy (source, cmd.hdr.call_from, sizeof(source));
strlcpy (dest, cmd.hdr.call_to, sizeof(dest));
memset (&reply, 0, sizeof(reply));
reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number, addresses. */
reply.hdr.datakind = 'Y';
strlcpy (reply.hdr.call_from, source, sizeof(reply.hdr.call_from));
strlcpy (reply.hdr.call_to, dest, sizeof(reply.hdr.call_to));
reply.hdr.data_len_NETLE = host2netle(4);
int n = 0;
if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) {
n = tq_count (cmd.hdr.portx, -1, source, dest, 0);
}
reply.data_NETLE = host2netle(n);

438
sock.c Normal file
View File

@ -0,0 +1,438 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2017 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: sock.c
*
* Purpose: Functions for TCP sockets.
*
* Description: These are used for connecting between different applications,
* possibly on different hosts.
*
* New in version 1.5:
* Duplicate code already exists in multiple places and I was about
* to add another one. Instead, we will gather the common code here
* instead of having yet another copy.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
//#include <termios.h>
#include <sys/errno.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include "textcolor.h"
#include "sock.h"
static void shuffle (struct addrinfo *host[], int nhosts);
/*-------------------------------------------------------------------
*
* Name: sock_init
*
* Purpose: Preparation before using socket interface.
*
* Inputs: none
*
* Returns: 0 for success, -1 for error.
*
* Errors: Message is printed. I've never seen it fail.
*
* Description: Doesn't do anything for Linux.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in kissutil.c
* TODO: Use this instead of own copy in server.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
int sock_init(void)
{
#if __WIN32__
WSADATA wsadata;
int err;
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed, error: %d\n", err);
return (-1);
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return (-1);
}
#endif
return (0);
} /* end sock_init */
/*-------------------------------------------------------------------
*
* Name: sock_connect
*
* Purpose: Connect to given host / port.
*
* Inputs: hostname - Host name or IP address.
*
* port - TCP port as text string.
*
* description - Description of the remote server to be used in error message.
* e.g. "APRS-IS (Igate) Server" or "TCP KISS TNC".
*
* allow_ipv6 - True to allow IPv6. Otherwise only IPv4.
*
* debug - Print debugging information.
*
* Outputs: ipaddr_str - The IP address, in text form, is placed here in case
* the caller wants it. Should be SOCK_IPADDR_LEN bytes.
*
* Returns: Socket Handle / file descriptor or -1 for error.
*
* Errors: (1) Can't find address for given host name.
*
* Print error and return -1.
*
* (2) Can't connect to one of the address(es).
*
* Silently try the next one.
*
* (3) Can't connect to any of the address(es).
*
* Nothing is printed for success. The caller might do that
* to provide confirmation on what is happening.
*
*--------------------------------------------------------------------*/
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[SOCK_IPADDR_LEN])
{
#define MAX_HOSTS 50
struct addrinfo hints;
struct addrinfo *ai_head = NULL;
struct addrinfo *ai;
struct addrinfo *hosts[MAX_HOSTS];
int num_hosts, n;
int err;
int server_sock = -1;
strlcpy (ipaddr_str, "???", SOCK_IPADDR_LEN);
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
if ( ! allow_ipv6) {
hints.ai_family = AF_INET; /* IPv4 only. */
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
/*
* First, we need to look up the DNS name to get IP address.
* It is possible to have multiple addresses.
*/
ai_head = NULL;
err = getaddrinfo(hostname, port, &hints, &ai_head);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
#if __WIN32__
dw_printf ("Can't get address for %s, %s, err=%d\n",
description, hostname, WSAGetLastError());
#else
dw_printf ("Can't get address for %s, %s, %s\n",
description, hostname, gai_strerror(err));
#endif
freeaddrinfo(ai_head);
return (-1);
}
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("getaddrinfo returns:\n");
}
num_hosts = 0;
for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
if (debug) {
text_color_set(DW_COLOR_DEBUG);
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
shuffle (hosts, num_hosts);
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("addresses for hostname:\n");
for (n=0; n<num_hosts; n++) {
sock_ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
}
/*
* Try each address until we find one that is successful.
*/
for (n = 0; n < num_hosts; n++) {
#if __WIN32__
SOCKET is;
#else
int is;
#endif
ai = hosts[n];
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
#if __WIN32__
if (is == INVALID_SOCKET) {
printf ("Socket creation failed, err=%d", WSAGetLastError());
WSACleanup();
is = -1;
continue;
}
#else
if (err != 0) {
printf ("Socket creation failed, err=%s", gai_strerror(err));
(void) close (is);
is = -1;
continue;
}
#endif
#ifndef DEBUG_DNS
err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
#if __WIN32__
if (err == SOCKET_ERROR) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
(void) close (is);
is = -1;
continue;
}
/* IGate documentation says to use no delay. */
/* Does it really make a difference? */
int flag = 1;
err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
if (err < 0) {
printf("setsockopt TCP_NODELAY failed.\n");
}
#endif
/* Success. */
server_sock = is;
#endif
break;
}
freeaddrinfo(ai_head);
// no, caller should handle this.
// function should be generally be silent unless debug option.
if (server_sock == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to connect to %s at %s (%s), port %s\n",
description, hostname, ipaddr_str, port );
}
return (server_sock);
} /* end sock_connect */
/*-------------------------------------------------------------------
*
* Name: sock_bind
*
* Purpose: We also have a bunch of duplicate code for the server side.
*
* Inputs:
*
* TODO: Use this instead of own copy in audio.c
* TODO: Use this instead of own copy in audio_portaudio.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in server.c
*
*--------------------------------------------------------------------*/
// Not implemented yet.
/*
* Addresses don't get mixed up very well.
* IPv6 always shows up last so we'd probably never
* end up using any of them for APRS-IS server.
* Add our own shuffle.
*/
static void shuffle (struct addrinfo *host[], int nhosts)
{
int j, k;
assert (RAND_MAX >= nhosts); /* for % to work right */
if (nhosts < 2) return;
srand (time(NULL));
for (j=0; j<nhosts; j++) {
k = rand() % nhosts;
assert (k >=0 && k<nhosts);
if (j != k) {
struct addrinfo *temp;
temp = host[j]; host[j] = host[k]; host[k] = temp;
}
}
}
/*-------------------------------------------------------------------
*
* Name: sock_ia_to_text
*
* Purpose: Convert binary IP Address to text form.
*
* Inputs: Family - AF_INET or AF_INET6.
*
* pAddr - Pointer to the IP Address storage location.
*
* StringBufSize - Number of bytes in pStringBuf.
*
* Outputs: pStringBuf - Text result is placed here.
*
* Returns: pStringBuf
*
* Description: Can't use InetNtop because it is supported only on Windows Vista and later.
* At one time Dire Wolf worked on Win XP. Haven't tried it for years.
* Maybe some other dependency on a newer OS version has crept in.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
{
struct sockaddr_in *sa4;
struct sockaddr_in6 *sa6;
switch (Family) {
case AF_INET:
sa4 = (struct sockaddr_in *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
sa4->sin_addr.S_un.S_un_b.s_b2,
sa4->sin_addr.S_un.S_un_b.s_b3,
sa4->sin_addr.S_un.S_un_b.s_b4);
#else
inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
#endif
break;
case AF_INET6:
sa6 = (struct sockaddr_in6 *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
#else
inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
#endif
break;
default:
snprintf (pStringBuf, StringBufSize, "Invalid address family!");
}
return pStringBuf;
} /* end sock_ia_to_text */
/* end sock.c */

19
sock.h Normal file
View File

@ -0,0 +1,19 @@
/* sock.h - Socket helper functions. */
#ifndef SOCK_H
#define SOCK_H 1
#define SOCK_IPADDR_LEN 48 // Size of string to hold IPv4 or IPv6 address.
// I think 40 would be adequate but we'll make
// it a little larger just to be safe.
// Use INET6_ADDRSTRLEN (from netinet/in.h) instead?
int sock_init (void);
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str);
/* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */
char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
#endif

View File

@ -261,11 +261,20 @@ static const struct {
// Make sure the array is null terminated.
// If search order is changed, do the same in decode_aprs.c
static const char *search_locations[] = {
(const char *) "symbols-new.txt",
#ifndef __WIN32__
(const char *) "/usr/share/direwolf/symbols-new.txt",
(const char *) "/usr/local/share/direwolf/symbols-new.txt",
(const char *) "/usr/share/direwolf/symbols-new.txt",
#endif
#if __APPLE__
// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458
// Adding the /opt/local tree since macports typically installs there. Users might want their
// INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local
// path as well.
(const char *) "/opt/local/share/direwolf/symbols-new.txt",
#endif
(const char *) NULL
};

View File

@ -143,9 +143,10 @@ static struct t_metadata_s * t_get_metadata (char *station)
for (p = md_list_head; p != NULL; p = p->pnext) {
if (strcmp(station, p->station) == 0) {
assert (p->magic1 == MAGIC1);
assert (p->magic2 == MAGIC2);
if (p->magic1 != MAGIC1 || p->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
return (p);
}
}
@ -252,7 +253,10 @@ static int t_ndp (char *str)
* KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000
*
* Not integers. Not fixed width fields.
* We will accept these but issue a warning that others might not.
*
* Originally I printed a warning if values were not in range of 000 to 255
* but later took it out because no one pays attention to that original
* restriction anymore.
*
*--------------------------------------------------------------------*/
@ -282,8 +286,10 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output
pm = t_get_metadata(station);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
seq = 0;
for (n = 0; n < T_NUM_ANALOG; n++) {
@ -297,13 +303,13 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output
if (strncmp(info, "T#", 2) != 0) {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Error: Information part of telemetry packet must begin with \"#\"\n");
dw_printf("Error: Information part of telemetry packet must begin with \"T#\"\n");
}
return;
}
/*
* Make a copy of the input string because this will alter it.
* Make a copy of the input string (excluding T#) because this will alter it.
* Remove any trailing CR/LF.
*/
@ -315,6 +321,15 @@ void telemetry_data_original (char *station, char *info, int quiet, char *output
next = stemp;
p = strsep(&next,",");
if (p == NULL) {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Nothing after \"T#\" for telemetry data.\n");
}
return;
}
seq = atoi(p);
n = 0;
while ((p = strsep(&next,",")) != NULL) {
@ -470,8 +485,10 @@ void telemetry_data_base91 (char *station, char *cdata, char *output, size_t out
pm = t_get_metadata(station);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
seq = 0;
for (n = 0; n < T_NUM_ANALOG; n++) {
@ -576,8 +593,11 @@ void telemetry_name_message (char *station, char *msg)
}
pm = t_get_metadata(station);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
next = stemp;
@ -652,8 +672,11 @@ void telemetry_unit_label_message (char *station, char *msg)
}
pm = t_get_metadata(station);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
next = stemp;
@ -729,8 +752,11 @@ void telemetry_coefficents_message (char *station, char *msg, int quiet)
}
pm = t_get_metadata(station);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
next = stemp;
@ -809,8 +835,11 @@ void telemetry_bit_sense_message (char *station, char *msg, int quiet)
#endif
pm = t_get_metadata(station);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
if (strlen(msg) < 8) {
if ( ! quiet) {
@ -919,8 +948,11 @@ static void t_data_process (struct t_metadata_s *pm, int seq, float araw[T_NUM_A
assert (pm != NULL);
assert (pm->magic1 == MAGIC1);
assert (pm->magic2 == MAGIC2);
if (pm->magic1 != MAGIC1 || pm->magic2 != MAGIC2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error: REPORT THIS! Bad magic values %s %d\n", __func__, __LINE__);
}
strlcpy (output, "", outputsize);

View File

@ -1,6 +1,21 @@
APRS TO-CALL VERSION NUMBERS 06 Feb 2017
APRS TO-CALL VERSION NUMBERS 12 Dec 2017
-------------------------------------------------------------------
WB4APR
12 Dec 17 Added APHWxx for use in "HamWAN
11 Dec 17 Added APDVxx for OE6PLD's SSTV with APRS status exchange
20 Nov 17 added APPICO DB1NTO' PicoAPRS
18 Oct 17 Added APBMxx BrandMeister DMR Server for R3ABM
25 Sep 17 added APP6xx for APRSlib
05 Sep 17 Chged APTAxx to APTBxx for TinyAPRS by BG5HHP
17 Aug 17 Added APOCSG for POCSAG - N0AGI's APRS to Pagers
21 Jun 17 Added APCSMS for Cosmos (used for sending commands @USNA
08 Jun 17 Added APPMxx for DL1MX's RTL-SDR pytohon Igate
01 Jun 17 added APOFF digi off on PSAT,PSAT2
and APDTMF digi off mode on QIKCOM2 and DTMF ON
and APRSON digi ON for PSAT
and APDIGI digi ON for PSAT2 and QIKCOM-2
and APSAT digi ON for QIKCOM-1
20 Mar 17 added APTBxx for TinyAPRS by BG5HHP
06 Feb 17 added APIExx for W7KMV's PiAPRS system
25 Jan 17 added APSFxx F5OPV embedded devices - was APZ40
16 Dec 16 added APYSxx for W2GMD's Python APRS
@ -11,16 +26,8 @@ APRS TO-CALL VERSION NUMBERS 06 Feb 2017
29 Apr 16 added APFPRS for FreeDV by Jeroen PE1RXQ
25 Feb 16 Added APCDS0 for Leon Lessing ZS6LMG's cell tracker
21 Jan 16 added APDNOx for APRSduino by DO3SWW
18 Nov 15 Added APSTPO for N0AGI
03 Nov 15 Updated APAND1 and APDRxx for androids
26 Oct 15 added APZ247 for UPRS
09 Sep 15 added APHTxx for HMTracker by IU0AAC
06 Aug 15 added APMTxx for LZ1PPL for tracker
27 Apr 15 added APZMAJ for Martyn M1MAJ DeLorme inReach Tracker
21 Apr 15 added APB2MF & APR2MF DL2MF - MF2APRS Radiosonde
06 Apr 15 added APAVT5 SainSonic AP510 - a 1watt tracker
. . . . . ...
11 Jan 12 added APYTxx for YagTracker and updated Yaesu APY008/350
In 2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ
APB2MF,APR2MF,APAVT5
In APRS, the AX.25 Destination address is not used for packet
routing as is normally done in AX.25. So APRS uses it for two
@ -52,12 +59,14 @@ a TOCALL number series:
APBLxx BigRedBee BeeLine
APBLO MOdel Rocketry K7RKT
APBPQx John G8BPQ Digipeater/IGate
APBMxx BrandMeister DMR Server for R3ABM
APC APCxxx Cellular applications
APCBBx VE7UDP Blackberry Applications
APCDS0 Leon Lessing ZS6LMG's cell tracker
APCLEY EYTraker GPRS/GSM tracker by ZS6EY
APCLWX EYWeather GPRS/GSM WX station by ZS6EY
APCLEZ Telit EZ10 GSM application ZS6CEY
APCSMS for Cosmos (used for sending commands @USNA)
APCWP8 John GM7HHB, WinphoneAPRS
APCYxx Cybiko applications
APD APD4xx UP4DAR platform
@ -66,6 +75,8 @@ a TOCALL number series:
APDGxx D-Star Gateways by G4KLX ircDDB
APDHxx WinDV (DUTCH*Star DV Node for Windows)
APDInn DIXPRS - Bela, HA5DI
APDIGI Used by PSAT2 to indicate the digi is ON
APDIGI digi ON for PSAT2 and QIKCOM-2
APDKxx KI4LKF g2_ircddb Dstar gateway software
APDNOx APRSduino by DO3SWW
APDOxx ON8JL Standalone DStar Node
@ -73,7 +84,9 @@ a TOCALL number series:
APDRxx APRSdroid Android App http://aprsdroid.org/
APDSXX SP9UOB for dsDigi and ds-tracker
APDTxx APRStouch Tone (DTMF)
APDTMF digi off mode on QIKCOM2 and DTMF ON
APDUxx U2APRS by JA7UDE
APDVxx OE6PLD's SSTV with APRS status exchange
APDWxx DireWolf, WB2OSZ
APE APExxx Telemetry devices
APECAN Pecan Pico APRS Balloon Tracker
@ -87,6 +100,7 @@ a TOCALL number series:
APH APHKxx for LA1BR tracker/digipeater
APHAXn SM2APRS by PY2UEP
APHTxx HMTracker by IU0AAC
APHWxx for use in "HamWAN
API APICQx for ICQ
APICxx HA9MCQ's Pic IGate
APIExx W7KMV's PiAPRS system
@ -124,13 +138,18 @@ a TOCALL number series:
APNXxx TNC-X (K6DBG)
APNWxx SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/
APO APRSpoint
APOFF Used by PSAT and PSAT2 to indicate the digi is OFF
APOLUx for OSCAR satellites for AMSAT-LU by LU9DO
APOAxx OpenAPRS - Greg Carter
APOCSG For N0AGI's APRS to POCSAG project
APOTxx Open Track
APOD1w Open Track with 1 wire WX
APOU2k Open Track for Ultimeter
APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO
APP APPTxx KetaiTracker by JF6LZE, Takeki (msg capable)
APP APP6xx for APRSlib
APPICx DB1NTO' PicoAPRS
APPMxx DL1MX's RTL-SDR pytohon Igate
APPTxx KetaiTracker by JF6LZE, Takeki (msg capable)
APQ APQxxx Earthquake data
APR APR8xx APRSdos versions 800+
APR2MF DL2MF - MF2APRS Radiosonde WX reporting
@ -141,6 +160,7 @@ a TOCALL number series:
APRNOW W5GGW ipad application
APRRTx RPC electronics
APRS Generic, (obsolete. Digis should use APNxxx instead)
APRSON Used by PSAT to indicate the DIGI is ON
APRXxx >40 APRSmax
APRXxx <39 for OH2MQK's igate
APRTLM used in MIM's and Mic-lites, etc
@ -148,6 +168,7 @@ a TOCALL number series:
APRSTx APRStt (Touch tone)
APS APSxxx APRS+SA, etc
APSARx ZL4FOX's SARTRACK
APSAT digi ON for QIKCOM-1
APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK)
APSFxx F5OPV embedded devices - was APZ40
APSK63 APRS Messenger -over-PSK63
@ -158,6 +179,7 @@ a TOCALL number series:
APT APT2xx Tiny Track II
APT3xx Tiny Track III
APTAxx K4ATM's tiny track
APTBxx TinyAPRS by BG5HHP Was APTAxx till Sep 2017
APTIGR TigerTrack
APTKPT TrackPoint N0LP
APTTxx Tiny Track

71
tq.c
View File

@ -281,7 +281,7 @@ void tq_append (int chan, int prio, packet_t pp)
* Implementing the 6PACK protocol is probably the proper solution.
*/
if (ax25_is_aprs(pp) && tq_count(chan,prio) > 100) {
if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Transmit packet queue for channel %d is too long. Discarding packet.\n", chan);
dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n");
@ -458,7 +458,7 @@ void lm_data_request (int chan, int prio, packet_t pp)
* Is transmit queue out of control?
*/
if (tq_count(chan,prio) > 250) {
if (tq_count(chan,prio,"","",0) > 250) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Transmit packet queue for channel %d is extremely long.\n", chan);
dw_printf ("Perhaps the channel is so busy there is no opportunity to send.\n");
@ -912,36 +912,83 @@ static int tq_is_empty (int chan)
*
* Name: tq_count
*
* Purpose: Return count of the number of packets in the specified transmit queue.
* Purpose: Return count of the number of packets (or bytes) in the specified transmit queue.
* This is used only for queries from KISS or AWG client applications.
*
* Inputs: chan - Channel, 0 is first.
*
* prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
* Specify -1 for total of both.
*
* source - If specified, count only those with this source address.
*
* dest - If specified, count only those with this destination address.
*
* bytes - If true, return number of bytes rather than packets.
*
* Returns: Number of items in specified queue.
*
*--------------------------------------------------------------------*/
int tq_count (int chan, int prio)
int tq_count (int chan, int prio, char *source, char *dest, int bytes)
{
packet_t p;
packet_t pp;
int n;
if (prio == -1) {
return (tq_count(chan, TQ_PRIO_0_HI, source, dest, bytes)
+ tq_count(chan, TQ_PRIO_1_LO, source, dest, bytes));
}
/* Don't bother with critical section. */
/* Only used for debugging a problem. */
// Array bounds check. FIXME: TODO: should have internal error instead of dying.
if (chan < 0 || chan >= MAX_CHANS || prio < 0 || prio >= TQ_NUM_PRIO) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("INTERNAL ERROR - tq_count(%d, %d, \"%s\", \"%s\", %d)\n", chan, prio, source, dest, bytes);
return (0);
}
if (queue_head[chan][prio] == 0) {
return (0);
}
// Don't want lists being rearranged while we are traversing them.
dw_mutex_lock (&tq_mutex);
n = 0;
p = queue_head[chan][prio];
while (p != NULL) {
n++;
p = ax25_get_nextp(p);
pp = queue_head[chan][prio];
while (pp != NULL) {
int count_it = 1;
if (source != NULL && *source != '\0') {
char frame_source[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pp, AX25_SOURCE, frame_source);
if (strcmp(source,frame_source) != 0) count_it = 0;
}
if (count_it && dest != NULL && *dest != '\0') {
char frame_dest[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, frame_dest);
if (strcmp(dest,frame_dest) != 0) count_it = 0;
}
if (count_it) {
if (bytes) {
n += ax25_get_frame_len(pp);
}
else {
n++;
}
}
pp = ax25_get_nextp(pp);
}
dw_mutex_unlock (&tq_mutex);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tq_count(%d,%d) returns %d\n", chan, prio, n);
dw_printf ("tq_count(%d, %d, \"%s\", \"%s\", %d) returns %d\n", chan, prio, source, dest, bytes, n);
#endif
return (n);

2
tq.h
View File

@ -34,7 +34,7 @@ packet_t tq_remove (int chan, int prio);
packet_t tq_peek (int chan, int prio);
int tq_count (int chan, int prio);
int tq_count (int chan, int prio, char *source, char *dest, int bytes);
#endif

View File

@ -468,7 +468,7 @@ int tt_text_to_call10 (const char *text, int quiet, char *buttons)
int errors = 0;
int found;
char padded[8];
char stemp[8];
char stemp[11];
strcpy (buttons, "");

View File

@ -58,8 +58,9 @@
#include "server.h"
#include "kiss.h"
#include "kissserial.h"
#include "kissnet.h"
#include "kiss_frame.h"
/*
* Information kept about local APRStt users.
@ -891,12 +892,14 @@ static void xmit_object_report (int i, int first_time)
int flen;
// TODO1.3: Put a wrapper around this so we only call one function to send by all methods.
// We see the same sequence in direwolf.c.
flen = ax25_pack(pp, fbuf);
server_send_rec_packet (save_tt_config_p->obj_recv_chan, pp, fbuf, flen);
kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, fbuf, flen);
kiss_send_rec_packet (save_tt_config_p->obj_recv_chan, fbuf, flen);
kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
kissserial_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
kisspt_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
}
if (first_time && save_tt_config_p->obj_send_to_ig) {

View File

@ -1,4 +1,3 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
@ -108,11 +107,10 @@ int main (int argc, char *argv[])
char data[1024];
char hostname[30] = "localhost";
char port[10] = "8000";
int err;
#if __WIN32__
#else
int err;
setlinebuf (stdout);
#endif
@ -142,13 +140,8 @@ int main (int argc, char *argv[])
mon_cmd.kind_lo = 'k';
#if __WIN32__
send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
err = SOCK_SEND (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
(void)err;
#endif
/*
* Print all of the monitored packets.
@ -157,11 +150,7 @@ int main (int argc, char *argv[])
while (1) {
int n;
#if __WIN32__
n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
n = SOCK_RECV (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
if (n != sizeof(mon_cmd)) {
printf ("Read error, received %d command bytes.\n", n);
@ -171,14 +160,10 @@ int main (int argc, char *argv[])
assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data)));
if (mon_cmd.data_len > 0) {
#if __WIN32__
n = recv (server_sock, data, mon_cmd.data_len, 0);
#else
n = read (server_sock, data, mon_cmd.data_len);
#endif
n = SOCK_RECV (server_sock, data, mon_cmd.data_len);
if (n != mon_cmd.data_len) {
printf ("Read error, client received %d data bytes when %d expected.\n", n, mon_cmd.data_len);
printf ("Read error, client received %d data bytes when %d expected. Terminating.\n", n, mon_cmd.data_len);
exit (1);
}
}
@ -254,11 +239,7 @@ int main (int argc, char *argv[])
xmit_raw.hdr.kind_lo = 'K';
xmit_raw.hdr.data_len = 1 + ax25_pack (reply_pp, xmit_raw.frame);
#if __WIN32__
send (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len, 0);
#else
err = write (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len);
#endif
err = SOCK_SEND (server_sock, (char*)(&xmit_raw), sizeof(xmit_raw.hdr)+xmit_raw.hdr.data_len);
ax25_delete (reply_pp);
}

View File

@ -1,8 +1,8 @@
/* Dire Wolf version 1.4 */
/* Dire Wolf version 1.5 */
#define APP_TOCALL "APDW"
#define MAJOR_VERSION 1
#define MINOR_VERSION 4
#define MINOR_VERSION 5
//#define EXTRA_VERSION "Beta Test"

205
xid.c
View File

@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2014, 2016 John Langner, WB2OSZ
// Copyright (C) 2014, 2016, 2017 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
@ -50,6 +50,7 @@
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include "textcolor.h"
#include "xid.h"
@ -66,10 +67,36 @@
#define PI_Ack_Timer 9
#define PI_Retries 10
// Forget about the bit order at the physical layer (e.g. HDLC).
// It doesn't matter at all here. We are dealing with bytes.
// A different encoding could send the bits in the opposite order.
// The bit numbers are confusing because this one table (Fig. 4.5) starts
// with 1 for the LSB when everywhere else refers to the LSB as bit 0.
// The first byte must be of the form 0xx0 0001
// The second byte must be of the form 0000 0000
// If we process the two byte "Classes of Procedures" like
// the other multibyte numeric fields, with the more significant
// byte first, we end up with the bit masks below.
// The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9
// (This has nothing to do with the HDLC serializing order.
// I'm talking about the way we would normally write binary numbers.)
#define PV_Classes_Procedures_Balanced_ABM 0x0100
#define PV_Classes_Procedures_Half_Duplex 0x2000
#define PV_Classes_Procedures_Full_Duplex 0x4000
// The first byte must be of the form 1000 0xx0
// The second byte must be of the form 1010 xx00
// The third byte must be of the form 0000 0010
// If we process the three byte "HDLC Optional Parmeters" like
// the other multibyte numeric fields, with the most significant
// byte first, we end up with bit masks like this.
// The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 24 23 22 21 20 19 18 17
#define PV_HDLC_Optional_Functions_REJ_cmd_resp 0x020000
#define PV_HDLC_Optional_Functions_SREJ_cmd_resp 0x040000
#define PV_HDLC_Optional_Functions_Extended_Address 0x800000
@ -79,6 +106,9 @@
#define PV_HDLC_Optional_Functions_TEST_cmd_resp 0x002000
#define PV_HDLC_Optional_Functions_16_bit_FCS 0x008000
#define PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp 0x000020
#define PV_HDLC_Optional_Functions_Segmenter 0x000040
#define PV_HDLC_Optional_Functions_Synchronous_Tx 0x000002
@ -127,7 +157,7 @@ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, ch
// rej and modulo are enum so we can't use G_UNKNOWN there.
result->full_duplex = G_UNKNOWN;
result->rej = unknown_reject;
result->srej = srej_not_specified;
result->modulo = modulo_unknown;
result->i_field_length_rx = G_UNKNOWN;
result->window_size_rx = G_UNKNOWN;
@ -204,21 +234,31 @@ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, ch
case PI_HDLC_Optional_Functions:
if ((pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
result->rej = selective_reject_reject; /* Both bits set */
strlcat (desc, "SREJ-REJ ", desc_size);
}
else if ((pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && ! (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp)) {
result->rej = implicit_reject; /* Only REJ is set */
// Pick highest of those offered.
if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) {
strlcat (desc, "REJ ", desc_size);
}
else if ( ! (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) && pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
result->rej = selective_reject; /* Only SREJ is set */
if (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
strlcat (desc, "SREJ ", desc_size);
}
if (pval & PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp) {
strlcat (desc, "Multi-SREJ ", desc_size);
}
if (pval & PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp) {
result->srej = srej_multi;
}
else if (pval & PV_HDLC_Optional_Functions_SREJ_cmd_resp) {
result->srej = srej_single;
}
else if (pval & PV_HDLC_Optional_Functions_REJ_cmd_resp) {
result->srej = srej_none;
}
else {
text_color_set (DW_COLOR_ERROR);
dw_printf ("XID error: Expected one or both of REJ, SREJ to be set.\n");
dw_printf ("XID error: Expected at least one of REJ, SREJ, Multi-SREJ to be set.\n");
result->srej = srej_none;
}
if ((pval & PV_HDLC_Optional_Functions_Modulo_8) && ! (pval & PV_HDLC_Optional_Functions_Modulo_128)) {
@ -331,10 +371,10 @@ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, ch
* 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
*
* srej - Level of selective reject.
* srej_none (use REJ), srej_single, srej_multi
* As command, offer a menu of what I can handle. (i.e. perhaps multiple bits set)
* As response, take minimum of what is offered and what I can handle. (one bit set)
*
* modulo - 8 or 128.
*
@ -355,6 +395,8 @@ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, ch
* Default is 10.
* Use G_UNKNOWN to omit this.
*
* cr - Is it a command or response?
*
* Outputs: info - Information part of XID frame.
* Does not include the control byte.
* Use buffer of 40 bytes just to be safe.
@ -382,10 +424,17 @@ int xid_parse (unsigned char *info, int info_len, struct xid_param_s *result, ch
* Comment: I have a problem with "... occurs at any time." What if we were in the middle
* of transferring a large file with k=32 then along comes XID which says switch to modulo 8?
*
* Insight: Or is it Erratum?
* After reading the base standards documents, it seems that the XID command should offer
* up a menu of all the acceptable choices. e.g. REJ, SREJ, Multi-SREJ. One or more bits
* can be set. The XID response, would set a single bit which is the desired choice from
* among those offered.
* Should go back and review half/full duplex and modulo.
*
*--------------------------------------------------------------------*/
int xid_encode (struct xid_param_s *param, unsigned char *info)
int xid_encode (struct xid_param_s *param, unsigned char *info, cmdres_t cr)
{
unsigned char *p;
int len;
@ -438,11 +487,43 @@ int xid_encode (struct xid_param_s *param, unsigned char *info)
PV_HDLC_Optional_Functions_16_bit_FCS |
PV_HDLC_Optional_Functions_Synchronous_Tx;
if (param->rej == implicit_reject || param->rej == selective_reject_reject || param->rej == unknown_reject)
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
//text_color_set (DW_COLOR_ERROR);
//dw_printf ("****** XID temp hack - test no SREJ ******\n");
// param->srej = srej_none;
if (param->rej == selective_reject || param->rej == selective_reject_reject)
if (cr == cr_cmd) {
// offer a "menu" of acceptable choices. i.e. 1, 2 or 3 bits set.
switch (param->srej) {
case srej_none:
default:
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
break;
case srej_single:
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp |
PV_HDLC_Optional_Functions_SREJ_cmd_resp;
break;
case srej_multi:
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp |
PV_HDLC_Optional_Functions_SREJ_cmd_resp |
PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp;
break;
}
}
else {
// for response, set only a single bit.
switch (param->srej) {
case srej_none:
default:
x |= PV_HDLC_Optional_Functions_REJ_cmd_resp;
break;
case srej_single:
x |= PV_HDLC_Optional_Functions_SREJ_cmd_resp;
break;
case srej_multi:
x |= PV_HDLC_Optional_Functions_Multi_SREJ_cmd_resp;
break;
}
}
if (param->modulo == modulo_128)
x |= PV_HDLC_Optional_Functions_Modulo_128;
@ -531,7 +612,7 @@ static unsigned char example[27] = {
/* GL */ 0x17, /* (2 bytes) */
/* PI */ 0x02, /* Parameter Indicator - classes of procedures */
/* PL */ 0x02, /* Parameter Length */
#if 0 // Example in the protocol spec looks wrong.
#if 0 // Erratum: Example in the protocol spec looks wrong.
/* PV */ 0x00, /* Parameter Variable - Half Duplex, Async, Balanced Mode */
/* PV */ 0x20, /* */
#else // I think it should be like this instead.
@ -545,6 +626,11 @@ static unsigned char example[27] = {
/* PV */ 0x02, /* synchronous transmit */
/* PI */ 0x06, /* Parameter Indicator - Rx I field length (bits) */
/* PL */ 0x02, /* Parameter Length */
// Erratum: The text does not say anything about the byte order for multibyte
// numeric values. In the example, we have two cases where 16 bit numbers are
// sent with the more significant byte first.
/* PV */ 0x04, /* Parameter Variable - 1024 bits (128 octets) */
/* PV */ 0x00, /* */
/* PI */ 0x08, /* Parameter Indicator - Rx window size */
@ -565,7 +651,7 @@ int main (int argc, char *argv[]) {
struct xid_param_s param2;
int n;
unsigned char info[40]; // Currently max of 27 but things can change.
char desc[100]; // 80 is not adequate.
char desc[150]; // I've seen 109.
/* parse example. */
@ -573,13 +659,15 @@ int main (int argc, char *argv[]) {
n = xid_parse (example, sizeof(example), &param, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
dw_printf ("%d: %s\n", __LINE__, desc);
fflush (stdout);
SLEEP_SEC (1);
text_color_set (DW_COLOR_ERROR);
assert (n==1);
assert (param.full_duplex == 0);
assert (param.rej == selective_reject_reject);
assert (param.srej == srej_single);
assert (param.modulo == modulo_128);
assert (param.i_field_length_rx == 128);
assert (param.window_size_rx == 2);
@ -588,7 +676,7 @@ int main (int argc, char *argv[]) {
/* encode and verify it comes out the same. */
n = xid_encode (&param, info);
n = xid_encode (&param, info, cr_cmd);
assert (n == sizeof(example));
n = memcmp(info, example, 27);
@ -599,52 +687,56 @@ int main (int argc, char *argv[]) {
assert (n == 0);
/* try a couple different values. */
/* try a couple different values, no srej. */
param.full_duplex = 1;
param.rej = implicit_reject;
param.srej = srej_none;
param.modulo = modulo_8;
param.i_field_length_rx = 2048;
param.window_size_rx = 3;
param.ack_timer = 1234;
param.retries = 12;
n = xid_encode (&param, info);
n = xid_encode (&param, info, cr_cmd);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
dw_printf ("%d: %s\n", __LINE__, desc);
fflush (stdout);
SLEEP_SEC (1);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 1);
assert (param2.rej == implicit_reject);
assert (param2.srej == srej_none);
assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == 2048);
assert (param2.window_size_rx == 3);
assert (param2.ack_timer == 1234);
assert (param2.retries == 12);
/* The third possbility for rej. We don't use this. */
/* Other values, single srej. */
param.full_duplex = 0;
param.rej = selective_reject;
param.srej = srej_single;
param.modulo = modulo_8;
param.i_field_length_rx = 61;
param.window_size_rx = 4;
param.ack_timer = 5555;
param.retries = 9;
n = xid_encode (&param, info);
n = xid_encode (&param, info, cr_cmd);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
dw_printf ("%d: %s\n", __LINE__, desc);
fflush (stdout);
SLEEP_SEC (1);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 0);
assert (param2.rej == selective_reject);
assert (param2.srej == srej_single);
assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == 61);
assert (param2.window_size_rx == 4);
@ -652,26 +744,57 @@ int main (int argc, char *argv[]) {
assert (param2.retries == 9);
/* Other values, multi srej. */
param.full_duplex = 0;
param.srej = srej_multi;
param.modulo = modulo_128;
param.i_field_length_rx = 61;
param.window_size_rx = 4;
param.ack_timer = 5555;
param.retries = 9;
n = xid_encode (&param, info, cr_cmd);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%d: %s\n", __LINE__, desc);
fflush (stdout);
SLEEP_SEC (1);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 0);
assert (param2.srej == srej_multi);
assert (param2.modulo == modulo_128);
assert (param2.i_field_length_rx == 61);
assert (param2.window_size_rx == 4);
assert (param2.ack_timer == 5555);
assert (param2.retries == 9);
/* Specify some and not others. */
param.full_duplex = 0;
param.rej = selective_reject;
param.srej = srej_single;
param.modulo = modulo_8;
param.i_field_length_rx = G_UNKNOWN;
param.window_size_rx = G_UNKNOWN;
param.ack_timer = 999;
param.retries = G_UNKNOWN;
n = xid_encode (&param, info);
n = xid_encode (&param, info, cr_cmd);
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
dw_printf ("%d: %s\n", __LINE__, desc);
fflush (stdout);
SLEEP_SEC (1);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == 0);
assert (param2.rej == selective_reject);
assert (param2.srej == srej_single);
assert (param2.modulo == modulo_8);
assert (param2.i_field_length_rx == G_UNKNOWN);
assert (param2.window_size_rx == G_UNKNOWN);
@ -684,12 +807,14 @@ int main (int argc, char *argv[]) {
n = xid_parse (info, n, &param2, desc, sizeof(desc));
text_color_set (DW_COLOR_DEBUG);
dw_printf ("%s\n", desc);
dw_printf ("%d: %s\n", __LINE__, desc);
fflush (stdout);
SLEEP_SEC (1);
text_color_set (DW_COLOR_ERROR);
assert (param2.full_duplex == G_UNKNOWN);
assert (param2.rej == unknown_reject);
assert (param2.srej == srej_not_specified);
assert (param2.modulo == modulo_unknown);
assert (param2.i_field_length_rx == G_UNKNOWN);
assert (param2.window_size_rx == G_UNKNOWN);

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