direwolf-1.4/ 0000775 0000000 0000000 00000000000 13100232553 0013163 5 ustar 00root root 0000000 0000000 direwolf-1.4/.gitattributes 0000664 0000000 0000000 00000001207 13100232553 0016056 0 ustar 00root root 0000000 0000000 # Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# My rules to remove any doubt
*.c text
*.cpp text
*.h text
*.pl text
*.py text
*.sh text
*.txt text
*.desktop text
*.conf text
*.rc text
*.spec text
*.bat text
*.1 text
*.md text
COPYING text
Makefile* text
README* text
*.ico binary
*.png binary
direwolf-1.4/.gitignore 0000664 0000000 0000000 00000002074 13100232553 0015156 0 ustar 00root root 0000000 0000000 # Custom
*.docx
z*
*.log
*bak*
*~
*.xlsx
*.stackdump
*.wav
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Binaries, other build results
aclients
atest
decode_aprs
direwolf
gen_fff
gen_packets
ll2utm
log2gpx
text2tt
tt2text
ttcalc
utm2ll
direwolf.conf
fsk_fast_filter.h
direwolf.desktop
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
direwolf-1.4/CHANGES.md 0000664 0000000 0000000 00000031461 13100232553 0014562 0 ustar 00root root 0000000 0000000
# Revision History #
## Version 1.4 -- April 2017 ##
### New Features: ###
- AX.25 v2.2 connected mode. See chapter 10 of User Guide for details.
- New client side packet filter to select "messages" only to stations that have been heard nearby recently. This is now the default if no IS to RF filter is specified.
- New beacon type, IBEACON, for sending IGate statistics.
- Expanded debug options so you can understand what is going on with packet filtering.
- Added new document ***Successful-APRS-IGate-Operation.pdf*** with IGate background, configuration, and troubleshooting tips.
- 2400 & 4800 bps PSK modems. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** in the doc directory for discussion.
- The top speed of 9600 bps has been increased to 38400. You will need a sound card capable of 96k or 192k samples per second for the higher rates. Radios must also have adequate bandwidth. See ***Going-beyond-9600-baud.pdf*** in the doc directory for more details.
- Better decoder performance for 9600 and higher especially for low audio sample rate to baud ratios.
- Generate waypoint sentences for use by AvMap G5 / G6 or other mapping devices or applications. Formats include
- $GPWPL - NMEA generic with only location and name.
- $PGRMW - Garmin, adds altitude, symbol, and comment to previously named waypoint.
- $PMGNWPL - Magellan, more complete for stationary objects.
- $PKWDWPL - Kenwood with APRS style symbol but missing comment.
- DTMF tones can be sent by putting "DTMF" in the destination address, similar to the way that Morse Code is sent.
- Take advantage of new 'gpio' group and new /sys/class/gpio ownership in Raspbian Jessie.
- Handle more complicated gpio naming for CubieBoard, etc.
- More flexible dw-start.sh start up script for both GUI and CLI environments.
### Bugs Fixed: ###
- The transmitter (PTT control) was being turned off too soon when sending Morse Code.
- The -qd (quiet decode) command line option now suppresses errors about improperly formed Telemetry packets.
- Longer tocall.txt files can now be handled.
- Sometimes kissattach would have an issue with the Dire Wolf pseudo terminal. This showed up most often on Raspbian but sometimes occurred with other versions of Linux.
*kissattach: Error setting line discipline: TIOCSETD: Device or resource busy
Are you sure you have enabled MKISS support in the kernel
or, if you made it a module, that the module is loaded?*
- Sometimes writes to a pseudo terminal would block causing the received
frame processing thread to hang. The first thing you will notice is that
received frames are not being printed. After a while this message will appear:
*Received frame queue is out of control. Length=... Reader thread is probably
frozen. This can be caused by using a pseudo terminal (direwolf -p) where
another application is not reading the frames from the other side.*
- -p command line option caused segmentation fault with glibc >= 2.24.
- The Windows version 1.3 would crash when starting to transmit on Windows XP. There have also been some other reports of erratic behavior on Windows. The crashing problem was fixed in in the 1.3.1 patch release. Linux version was not affected.
- IGate did not retain nul characters in the information part of a packet. This should never happen with a valid APRS packet but there are a couple cases where it has. If we encounter these malformed packets, pass them along as-is, rather than truncating.
- Don't digipeat packets when the source is my call.
----------
## Version 1.3 -- May 2016 ##
### New Features: ###
- Support for Mac OS X.
- Many APRStt enhancements including: Morse code and speech responses to to APRStt tone sequences, new 5 digit callsign suffix abbreviation,
position ambiguity for latitude and longitude in object reports
- APRS Telemetry Toolkit.
- GPS Tracker beacons are now available for the Windows version. Previously this was only in the Linux version.
- SATgate mode for IGate. Packets heard directly are delayed before being sent
to the Internet Server. This favors digipeated packets because the original
arrives later and gets dropped if there are duplicates.
- Added support for hamlib. This provides more flexible options for PTT control.
- Implemented AGW network protocol 'M' message for sending UNPROTO information without digipeater path.
- A list of all symbols available can be obtained with the -S
command line option.
- Command line option "-a n" to print audio device statistics each n seconds. Previously this was always each 100 seconds on Linux and not available on Windows.
### Bugs Fixed: ###
- Fixed several cases where crashes were caused by unexpected packet contents:
- When receiving packet with unexpected form of GPS NMEA sentence.
- When receiving packet with comment of a few hundred characters.
- Address in path, from Internet server, more than 9 characters.
- "INTERNAL ERROR: dlq_append NULL packet pointer." when using PASSALL.
- In Mac OSX version: Assertion failed: (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768), function audio_get, file audio_portaudio.c, line 917.
- Tracker beacons were not always updating the location properly.
- AGW network protocol now works properly for big-endian processors
such as PowerPC or MIPS.
- Packet filtering treated telemetry metadata as messages rather than telemetry.
----------
## Version 1.2 -- June 2015 ##
### New Features ###
- Improved decoder performance.
Over 1000 error-free frames decoded from WA8LMF TNC Test CD.
See ***A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf*** for details.
- Up to 3 soundcards and 6 radio channels can be handled at the same time.
- New framework for applications which listen for Touch Tone commands
and respond with voice. A sample calculator application is included
as a starting point for building more interesting applications.
For example, if it hears the DTMF sequence "2*3*4#" it will respond
with the spoken words "Twenty Four."
- Reduced latency for transfers to/from soundcards.
- More accurate transmit PTT timing.
- Packet filtering for digipeater and IGate.
- New command line -q (quiet) option to suppress some types of output.
- Attempted fixing of corrupted bits now works for 9600 baud.
- Implemented AGW network protocol 'y' message so applications can
throttle generation of packets when sending a large file.
- When using serial port RTS/DTR to activate transmitter, the two
control lines can now be driven with opposite polarity as required
by some interfaces.
- Data Carrier Detect (DCD) can be sent to an output line (just
like PTT) to activate a carrier detect light.
- Linux "man" pages for on-line documentation.
- AGWPORT and KISSPORT can be set to 0 to disable the interfaces.
- APRStt gateway enhancements: MGRS/USNG coordinates, new APRStt3
format call, satellite grid squares.
### Bugs fixed ###
- Fixed "gen_packets" so it now handles user-specified messages correctly.
- Under some circumstances PTT would be held on long after the transmit
audio was finished.
### Known problems ###
- Sometimes writes to a pseudo terminal will block causing the received
frame processing thread to hang. The first thing you will notice is that
received frames are not being printed. After a while this message will appear:
Received frame queue is out of control. Length=... Reader thread is probably
frozen. This can be caused by using a pseudo terminal (direwolf -p) where
another application is not reading the frames from the other side.
-----------
## Version 1.1 -- December 2014 ##
### New Features ###
- Logging of received packets and utility to convert log file
into GPX format.
- AGW network port formerly allowed only one connection at a
time. It can now accept 3 client applications at the same time.
(Same has not yet been done for network KISS port.)
- Frequency / offset / tone standard formats are now recognized.
Non-standard attempts, in the comment, are often detected and
a message suggests the correct format.
- Telemetry is now recognized. Messages are printed for
usage that does not adhere to the published standard.
- Tracker function transmits location from GPS position.
New configuration file options: TBEACON and SMARTBEACONING.
(For Linux only. Warning - has not been well tested.)
- Experimental packet regeneration feature for HF use.
Will be documented later if proves to be useful...
- Several enhancements for trying to fix incorrect CRC:
Additional types of attempts to fix a bad CRC.
Optimized code to reduce execution time.
Improved detection of duplicate packets from different fixup attempts.
Set limit on number of packets in fix up later queue.
- Beacon positions can be specified in either latitude / longitude
or UTM coordinates.
- It is still highly recommended, but no longer mandatory, that
beaconing be enabled for digipeating to work.
* Bugs fixed:
- For Windows version, maximum serial port was COM9.
It is now possible to use COM10 and higher.
- Fixed issue with KISS protocol decoder state that showed up
only with "binary" data in packets (e.g. RMS Express).
- An extra 00 byte was being appended to packets from AGW
network protocol 'K' messages.
- Invalid data from an AGW client application could cause an
application crash.
- OSS (audio interface for non-Linux versions of Unix) should
be better now.
### Known problems ###
- Sometimes kissattach fails to connect with "direwolf -p".
The User Guide and Raspberry Pi APRS document have a couple work-arounds.
-----------
## Version 1.0a -- May 2014 ##
### Bug fixed ###
- Beacons sent directly to IGate server had incorrect source address.
-----------
## Version 1.0 -- May 2014 ##
### New Features ###
- Received audio can be obtained with a UDP socket or stdin.
This can be used to take audio from software defined radios
such as rtl_fm or gqrx.
- 9600 baud data rate.
- New PBEACON and OBEACON configuration options. Previously
it was necessary to handcraft beacons.
- Less CPU power required for 300 baud. This is important
if you want to run a bunch of decoders at the same time
to tolerate off-frequency HF SSB signals.
- Improved support for UTF-8 character set.
- Improved troubleshooting display for APRStt macros.
- In earlier versions, the DTMF decoder was always active because it
took a negligible amount of CPU time. Unfortunately this sometimes
resulted in too many false positives from some other types of digital
transmissions heard on HF. Starting in version 1.0, the DTMF decoder
is enabled only when the APRStt gateway is configured.
-----------
## Version 0.9 --November 2013 ##
### New Features ###
- Selection of non-default audio device for Linux ALSA.
- Simplified audio device set up for Raspberry Pi.
- GPIO lines can be used for PTT on suitable Linux systems.
- Improved 1200 baud decoder.
- Multiple decoders per channel to tolerate HF SSB signals off frequency.
- Command line option "-t 0" to disable text colors.
- APRStt macros which allow short numeric only touch tone
sequences to be processed as much longer predefined sequences.
### Bugs Fixed ###
- Now works on 64 bit target.
### New Restriction for Windows version ###
- Minimum processor is now Pentium 3 or equivalent or later.
It's possible to run on something older but you will need
to rebuild it from source.
-----------
## Version 0.8 -- August 2013 ##
### New Features ###
- Internet Gateway (IGate) including IPv6 support.
- Compatibility with YAAC.
- Preemptive digipeating option.
- KISS TNC should now work with connected AX.25 protocols
(e.g. AX25 for Linux), not just APRS.
----------
## Version 0.7 -- March 2013 ##
### New Features: ###
- Added APRStt gateway capability. For details, see ***APRStt-Implementation-Notes.pdf***
-----------
## Version 0.6 -- February 2013 ##
### New Features ###
- Improved performance of AFSK demodulator.
Now decodes 965 frames from Track 2 of WA8LMF's TNC Test CD.
- KISS protocol now available thru a TCP socket.
Default port is 8001.
Change it with KISSPORT option in configuration file.
- Ability to salvage frames with bad FCS.
See section mentioning "bad apple" in the user guide.
Default of fixing 1 bit works well.
Fixing more bits not recommended because there is a high
probability of occasional corrupted data getting thru.
- Added AGW "monitor" format messages.
Now compatible with APRS-TW for telemetry.
### Known Problem ###
- The Linux (but not Cygwin) version eventually hangs if nothing is
reading from the KISS pseudo terminal. Some operating system
queue fills up, the application write blocks, and decoding stops.
### Workaround ###
- If another application is not using the serial KISS interface,
run this in another window:
tail -f /tmp/kisstnc
-----------
## Version 0.5 -- March 2012 ##
- More error checking and messages for invalid APRS data.
-----------
## Version 0.4 -- September 2011 ##
- First general availability.
direwolf-1.4/LICENSE-dire-wolf.txt 0000664 0000000 0000000 00000035565 13100232553 0016712 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
direwolf-1.4/LICENSE-other.txt 0000664 0000000 0000000 00000000275 13100232553 0016131 0 ustar 00root root 0000000 0000000 The Windows version of Dire Wolf contains additional
open source covered by BSD, GPL, and other licenses.
See "regex" and "misc" subdirectories in the source
distribution for more details. direwolf-1.4/Makefile 0000664 0000000 0000000 00000000641 13100232553 0014624 0 ustar 00root root 0000000 0000000 # Select proper Makefile for operating system.
# The Windows version is built with the help of Cygwin.
# In my case, I see CYGWIN_NT-6.1-WOW so we don't check for
# equal to some value. Your mileage my vary.
win := $(shell uname | grep CYGWIN)
dar := $(shell uname | grep Darwin)
ifneq ($(win),)
include Makefile.win
else ifeq ($(dar),Darwin)
include Makefile.macosx
else
include Makefile.linux
endif
direwolf-1.4/Makefile.linux 0000664 0000000 0000000 00000072513 13100232553 0015771 0 ustar 00root root 0000000 0000000 #
# Makefile for Linux version of Dire Wolf.
#
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
all : $(APPS) direwolf.desktop direwolf.conf
@echo " "
@echo "Next step - install with:"
@echo " "
@echo " sudo make install"
@echo " "
CC := gcc
# Just for fun, let's see how clang compares to gcc. First install like this:
# sudo apt-get update
# apt-cache search clang
# sudo apt-get install clang-3.5
#
# CC := clang-3.5
# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24.
# Explanation here: https://github.com/wb2osz/direwolf/issues/62
# There are a few source files where it had been necessary to define __USE_XOPEN2KXSI,
# __USE_XOPEN, or _POSIX_C_SOURCE. Doesn't seem to be needed after adding this.
# The first assignment to CFLAGS and LDFLAGS uses +=, rather than :=, so
# we will inherit options already set in build environment.
# Explanation - https://github.com/wb2osz/direwolf/pull/38
CFLAGS += -O3 -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 -Wall
# That was fine for a recent Ubuntu and Raspbian Jessie.
# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set.
CFLAGS += -D_BSD_SOURCE
LDFLAGS += -lm -lpthread -lrt
#
# 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.
arch := $(shell echo | gcc -E -dM - | grep __i386__)
ifneq ($(arch),)
CFLAGS += -march=pentium3
endif
#
# ---------- x86_64 ----------
#
#
# 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.
#
# ---------- How does clang compare? - Ubuntu x86_64 ----------
#
# I keep hearing a lot about "clang." Let's see how it compares with gcc.
# Simply use different compiler and keep all options the same.
#
# test case: atest 02_Track_2.wav
#
# gcc 4.8.4: 988 packets decoded in 40.503 seconds. 38.3 x realtime
# 988 packets decoded in 40.403 seconds. 38.4 x realtime
#
# clang 3.8.0-2: 988 packets decoded in 77.454 seconds. 20.0 x realtime
# 988 packets decoded in 77.232 seconds. 20.1 x realtime
#
# I'm not impressed. Almost twice as long. Maybe we need to try some other compiler options.
# -march=native did not help.
# Makefile.macosx, which uses clang, has these:
# -fvectorize -fslp-vectorize -fslp-vectorize-aggressive
# Those did not help.
#
useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math)
ifneq ($(useffast),)
CFLAGS += -ffast-math
endif
#
# ---------- ARM - Raspberry Pi 1 models ----------
#
# Raspberry Pi (before RPi model 2), ARM11 (ARMv6 + VFP2)
# gcc (Debian 4.6.3-14+rpi1) 4.6.3
#
#
# seconds options comments
# ------ ------- ---------
# 892 -O3
# 887 -O3 -ffast-math
# x -O3 -ffast-math -march=native (error: bad value for -march switch)
# 887 -O3 -ffast-math -mfpu=vfp
# 890 -O3 -ffast-math -march=armv6zk -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp
#
#
# The compiler, supplied with Raspbian, is configured with these options which are
# good for the pre version 2 models.
# --with-arch=armv6 --with-fpu=vfp --with-float=hard
#
# Run "gcc --help -v 2" and look near the end.
#
#
#
# ---------- ARM - Raspberry Pi 2 ----------
#
# Besides the higher clock speed, the Raspberry Pi 2 has the NEON instruction set
# which which should make things considerably faster.
#
#
# seconds options comments
# ------ ------- ---------
# 426 -O3 -ffast-math (already more than twice as fast)
# 429 -O3 -mfpu=neon
# 419 -O3 -mfpu=neon -funsafe-math-optimizations
# 412 -O3 -ffast-math -mfpu=neon
# 413 -O3 -ffast-math -mfpu=neon-vfpv4
# 430 -O3 -ffast-math -mfpu=neon-vfpv4 -march=armv7-a
# 412 -O3 -ffast-math -mfpu=neon-vfpv4 -mtune=arm7
# 410 -O3 -ffast-math -mfpu=neon-vfpv4 -funsafe-math-optimizations
#
# gcc -march=armv7-a -mfpu=neon-vfpv4
#
# I expected the -mfpu=neon option to have a much larger impact.
# Adding -march=armv7-a makes it slower!
#
# 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.
# The best compiler options will depend on the specific type of processor
# and the compiler target defaults.
#
neon := $(shell cat /proc/cpuinfo | grep neon)
ifneq ($(neon),)
CFLAGS += -mfpu=neon
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.
#
# ---------- How does clang compare? - ARM - Raspberry Pi 2 ----------
#
# I keep hearing a lot about "clang." Let's see how it compares with gcc.
# Simply use different compiler and keep all options the same.
#
# test case: atest 02_Track_2.wav
#
# gcc 4.9.2-10: 988 packets decoded in 353.025 seconds. 4.4 x realtime
# 988 packets decoded in 352.752 seconds. 4.4 x realtime
#
# clang 3.5.0-10: 988 packets decoded in 825.879 seconds. 1.9 x realtime
# 988 packets decoded in 831.469 seconds. 1.9 x realtime
#
# There might be a different set of options which produce faster code
# but the initial test doesn't look good. About 2.3 times as slow.
# If you want to use OSS (for FreeBSD, OpenBSD) instead of
# ALSA (for Linux), comment out (or remove) the two lines below.
# TODO: Can we automate this somehow?
CFLAGS += -DUSE_ALSA
LDFLAGS += -lasound
# Enable GPS if header file is present.
# Finding libgps.so* is more difficult because it
# is in different places on different operating systems.
enable_gpsd := $(wildcard /usr/include/gps.h)
ifneq ($(enable_gpsd),)
CFLAGS += -DENABLE_GPSD
LDFLAGS += -lgps
endif
# Uncomment following lines to enable hamlib support.
# TODO: automate this too. See if hamlib has been installed.
#CFLAGS += -DUSE_HAMLIB
#LDFLAGS += -lhamlib
# Name of current directory.
# Used to generate zip file name for distribution.
z := $(notdir ${CURDIR})
# -------------------------------- Main application -----------------------------------------
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 \
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 \
misc.a geotranz.a
$(CC) -o $@ $^ $(LDFLAGS)
ifneq ($(enable_gpsd),)
@echo " "
@echo "This includes support for gpsd."
else
@echo " "
@echo "This does NOT include support for gpsd."
endif
# Optimization for slow processors.
demod.o : fsk_fast_filter.h
demod_afsk.o : fsk_fast_filter.h
fsk_fast_filter.h : gen_fff
./gen_fff > fsk_fast_filter.h
gen_fff : demod_afsk.c dsp.c textcolor.c
echo " " > tune.h
$(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS)
#
# The destination field is often used to identify the manufacturer/model.
# These are not hardcoded into Dire Wolf. Instead they are read from
# a file called tocalls.txt at application start up time.
#
# The original permanent symbols are built in but the "new" symbols,
# using overlays, are often updated. These are also read from files.
#
# You can obtain an updated copy by typing "make tocalls-symbols".
# This is not part of the normal build process. You have to do this explicitly.
#
# The locations below appear to be the most recent.
# The copy at http://www.aprs.org/tocalls.txt is out of date.
#
.PHONY: tocalls-symbols
tocalls-symbols :
cp tocalls.txt tocalls.txt~
wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
-diff -Z tocalls.txt~ tocalls.txt
cp symbols-new.txt symbols-new.txt~
wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
-diff -Z symbols-new.txt~ symbols-new.txt
cp symbolsX.txt symbolsX.txt~
wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
-diff -Z symbolsX.txt~ symbolsX.txt
# ---------------------------------------- Other utilities included ------------------------------
# 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
$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS)
# Convert between text and touch tone representation.
text2tt : tt_text.c misc.a
$(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ $(LDFLAGS)
tt2text : tt_text.c misc.a
$(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ $(LDFLAGS)
# Convert between Latitude/Longitude and UTM coordinates.
ll2utm : ll2utm.c geotranz.a textcolor.o misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
utm2ll : utm2ll.c geotranz.a textcolor.o misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Convert from log file to GPX.
log2gpx : log2gpx.c textcolor.o misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Test application to generate sound.
gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Unit test for AFSK demodulator
atest : atest.c demod.o demod_afsk.o demod_psk.o demod_9600.o \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \
dwgps.o dwgpsd.o serial_port.o telemetry.o dtime_now.o latlong.o symbols.o tt_text.o textcolor.o \
misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Multiple AGWPE network or serial port clients to test TNCs side by side.
aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a
$(CC) $(CFLAGS) -g -o $@ $^
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a
$(CC) $(CFLAGS) -g -o $@ $^
# ----------------------------------------- Libraries --------------------------------------------
# UTM, USNG, MGRS conversions.
geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o
ar -cr $@ $^
error_string.o : geotranz/error_string.c
$(CC) $(CFLAGS) -c -o $@ $^
mgrs.o : geotranz/mgrs.c
$(CC) $(CFLAGS) -c -o $@ $^
polarst.o : geotranz/polarst.c
$(CC) $(CFLAGS) -c -o $@ $^
tranmerc.o : geotranz/tranmerc.c
$(CC) $(CFLAGS) -c -o $@ $^
ups.o : geotranz/ups.c
$(CC) $(CFLAGS) -c -o $@ $^
usng.o : geotranz/usng.c
$(CC) $(CFLAGS) -c -o $@ $^
utm.o : geotranz/utm.c
$(CC) $(CFLAGS) -c -o $@ $^
# Provide our own copy of strlcpy and strlcat because they are not included with Linux.
# We don't need the others in that same directory.
misc.a : strlcpy.o strlcat.o
ar -cr $@ $^
strlcpy.o : misc/strlcpy.c
$(CC) $(CFLAGS) -I. -c -o $@ $^
strlcat.o : misc/strlcat.c
$(CC) $(CFLAGS) -I. -c -o $@ $^
# ------------------------------------- Installation ----------------------------------
# Generate apprpriate sample configuration file for this platform.
# Originally, there was one sample for all platforms. It got too cluttered
# and confusing saying, this is for windows, and this is for Linux, and this ...
# Trying to maintain 3 different versions in parallel is error prone.
# We now have a single generic version which can be used to generate
# the various platform specific versions.
# generic.conf should be checked into source control.
# direwolf.conf should NOT. It is generated when compiling on the target platform.
direwolf.conf : generic.conf
egrep '^C|^L' generic.conf | cut -c2-999 > direwolf.conf
# Where should we install it?
# My understanding, of the convention, is that something you compile
# from source, that is not a standard part of the operating system,
# should go in /usr/local/bin.
# However, if you are preparing a "binary" DEB or RPM package, the
# installation location should be /usr/bin.
# 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.
INSTALL=install
# direwolf.desktop was previously handcrafted for the Raspberry Pi.
# It was hardcoded with lxterminal, /home/pi, and so on.
# In version 1.2, try to customize this to match other situations better.
# TODO: Test this better.
direwolf.desktop :
@echo "Generating customized direwolf.desktop ..."
@echo '[Desktop Entry]' > $@
@echo 'Type=Application' >> $@
ifneq ($(wildcard /usr/bin/lxterminal),)
@echo "Exec=lxterminal -t \"Dire Wolf\" -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@
else ifneq ($(wildcard /usr/bin/lxterm),)
@echo "Exec=lxterm -hold -title \"Dire Wolf\" -bg white -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@
else
@echo "Exec=xterm -hold -title \"Dire Wolf\" -bg white -e \"$(INSTALLDIR)/bin/direwolf\"" >> $@
endif
@echo 'Name=Dire Wolf' >> $@
@echo 'Comment=APRS Soundcard TNC' >> $@
@echo 'Icon=/usr/share/direwolf/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/...
# Needs to be run as root or with sudo.
.PHONY: install
install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
#
# 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
#
# 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
#
# 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
#
# 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
#
# ./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/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
#
# 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
#
# "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
#
@echo " "
@echo "If this is your first install, not an upgrade, type this to put a copy"
@echo "of the sample configuration file (direwolf.conf) in your home directory:"
@echo " "
@echo " make install-conf"
@echo " "
# Put sample configuration files in home directory.
# These would be done as ordinary user.
# The Raspberry Pi has ~/Desktop but Ubuntu does not.
# TODO: Handle Linux variations correctly.
# Version 1.4 - Add "-n" option to avoid clobbering existing, probably customized, config files.
.PHONY: install-conf
install-conf : direwolf.conf
cp -n direwolf.conf ~
cp -n sdr.conf ~
cp -n telemetry-toolkit/telem-m0xer-3.txt ~
cp -n telemetry-toolkit/telem-*.conf ~
ifneq ($(wildcard $(HOME)/Desktop),)
@echo " "
@echo "This will add a desktop icon on some systems:"
@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
# ---------------------------------- Automated Smoke Test --------------------------------
# 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?
check-modem1200 : gen_packets atest
./gen_packets -n 100 -o /tmp/test12.wav
./atest -F0 -PE -L63 -G71 /tmp/test12.wav
./atest -F1 -PE -L70 -G75 /tmp/test12.wav
rm /tmp/test12.wav
check-modem300 : gen_packets atest
./gen_packets -B300 -n 100 -o /tmp/test3.wav
./atest -B300 -F0 -L68 -G69 /tmp/test3.wav
./atest -B300 -F1 -L73 -G75 /tmp/test3.wav
rm /tmp/test3.wav
check-modem9600 : gen_packets atest
./gen_packets -B9600 -n 100 -o /tmp/test96.wav
./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav
./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav
rm /tmp/test96.wav
check-modem19200 : gen_packets atest
./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav
./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav
./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav
rm /tmp/test19.wav
check-modem2400 : gen_packets atest
./gen_packets -B2400 -n 100 -o /tmp/test24.wav
./atest -B2400 -F0 -L70 -G78 /tmp/test24.wav
./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav
rm /tmp/test24.wav
check-modem4800 : gen_packets atest
./gen_packets -B2400 -n 100 -o /tmp/test48.wav
./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav
./atest -B2400 -F1 -L80 -G90 /tmp/test48.wav
rm /tmp/test48.wav
# Unit test for inner digipeater algorithm
.PHONY : dtest
dtest : digipeater.c dedupe.c pfilter.c \
ax25_pad.o fcs_calc.o tq.o textcolor.o \
decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a
$(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS)
./dtest
rm dtest
# Unit test for APRStt tone sequence parsing.
.PHONY : ttest
ttest : aprs_tt.c tt_text.c latlong.o textcolor.o misc.a geotranz.a misc.a
$(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ $(LDFLAGS)
./ttest
rm ttest
# Unit test for APRStt tone sequence / text conversions.
.PHONY: tttexttest
tttexttest : tt_text.c textcolor.o misc.a
$(CC) $(CFLAGS) -DTTT_TEST -o $@ $^ $(LDFLAGS)
./tttexttest
rm tttexttest
# Unit test for Packet Filtering.
.PHONY: pftest
pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a
$(CC) $(CFLAGS) -DPFTEST -o $@ $^ $(LDFLAGS)
./pftest
rm pftest
# Unit test for telemetry decoding.
.PHONY: tlmtest
tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a
$(CC) $(CFLAGS) -DTEST -o $@ $^ $(LDFLAGS)
./tlmtest
rm tlmtest
# Unit test for location coordinate conversion.
.PHONY: lltest
lltest : latlong.c textcolor.o misc.a
$(CC) $(CFLAGS) -DLLTEST -o $@ $^ $(LDFLAGS)
./lltest
rm lltest
# Unit test for encoding position & object report.
.PHONY: enctest
enctest : encode_aprs.c latlong.c textcolor.c misc.a
$(CC) $(CFLAGS) -DEN_MAIN -o $@ $^ $(LDFLAGS)
./enctest
rm enctest
# Unit test for KISS encapsulation.
.PHONY: kisstest
kisstest : kiss_frame.c
$(CC) $(CFLAGS) -DKISSTEST -o $@ $^ $(LDFLAGS)
./kisstest
rm kisstest
# Unit test for constructing frames besides UI.
.PHONY: pad2test
pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a
$(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ $(LDFLAGS)
./pad2test
rm pad2test
# Unit Test for XID frame encode/decode.
.PHONY: xidtest
xidtest : xid.c textcolor.o misc.a
$(CC) $(CFLAGS) -DXIDTEST -o $@ $^ $(LDFLAGS)
./xidtest
rm xidtest
# Unit Test for DTMF encode/decode.
.PHONY: dtmftest
dtmftest : dtmf.c textcolor.o
$(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ $(LDFLAGS)
./dtmftest
rm dtmftest
# ----------------------------- Manual tests and experiments ---------------------------
# These are not included in a normal build. Might be broken.
# Unit test for IGate
itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a
$(CC) $(CFLAGS) -DITEST -o $@ $^
./itest
# Unit test for UDP reception with AFSK demodulator.
# Temporary during development. Might not be useful anymore.
udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
./udptest
# For demodulator tweaking experiments.
# Dependencies of demod*.c, rather than .o, are intentional.
demod.o : tune.h
demod_afsk.o : tune.h
demod_9600.o : tune.h
demod_psk.o : tune.h
tune.h :
echo " " > tune.h
testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o dtime_now.o latlong.o symbols.o tune.h textcolor.o misc.a
$(CC) $(CFLAGS) -o atest $^ $(LDFLAGS)
./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a
rm -f atest96
$(CC) $(CFLAGS) -o atest96 $^ $(LDFLAGS)
./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out
#./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
# ------------------------------- Source distribution ---------------------------------
# probably obsolete and can be removed after move to github.
.PHONY: dist-src
dist-src : README.md CHANGES.md
doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \
doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \
dw-start.sh dwespeak.bat dwespeak.sh \
tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
rm -f fsk_fast_filter.h
echo " " > tune.h
rm -f ../$z-src.zip
(cd .. ; zip $z-src.zip \
$z/README.md \
$z/CHANGES.md \
$z/LICENSE* \
$z/doc/User-Guide.pdf \
$z/doc/Raspberry-Pi-APRS.pdf \
$z/doc/Raspberry-Pi-APRS-Tracker.pdf \
$z/doc/APRStt-Implementation-Notes.pdf \
$z/doc/APRS-Telemetry-Toolkit.pdf \
$z/Makefile* \
$z/*.c $z/*.h \
$z/regex/* $z/misc/* $z/geotranz/* \
$z/man1/* \
$z/generic.conf \
$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
$z/dw-start.sh $z/direwolf.spec \
$z/dwespeak.bat $z/dwespeak.sh \
$z/telemetry-toolkit/* )
# -----------------------------------------------------------------------------------------
.PHONY: clean
clean :
rm -f $(APPS) gen_fff tune.h fsk_fast_filter.h *.o *.a direwolf.desktop
depend : $(wildcard *.c)
makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
#
# The following is updated by "make depend"
#
# DO NOT DELETE
direwolf-1.4/Makefile.macosx 0000664 0000000 0000000 00000054175 13100232553 0016130 0 ustar 00root root 0000000 0000000 #
# Makefile for Macintosh 10.6+ version of Dire Wolf.
#
# TODO: This is a modified version of Makefile.linux and it
# has fallen a little behind. For example, it is missing the check target.
# It would be more maintainable if we could use a single file for both.
# The differences are not that great.
# Maybe the most of the differences could go in to platform specific include
# files rather than cluttering it up with too many if blocks.
# Changes:
#
# 16 Dec 2015
# 1. Added condition check for gps/gpsd code. Commented out due to 32/64 bit
# library issues. Macports gpsd build problem.
# 2. SDK version checks are now performed by a bash script 'search_sdks.sh'.
# This should resolve the varied locations Apple stored the SDKs on the different
# Xcode/OS versions. Executing 'make' on the first pass asks the operator
# which SDK he/she wishes to use. Executing 'make clean' resets the SDK
# selection and operator intervention is once again required. Selected SDK
# information resides in a file named './use_this_sdk' in the current working
# directory.
# 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
all : $(APPS) direwolf.desktop direwolf.conf @echo " "
@echo "Next step install with: "
@echo " "
@echo " sudo make install"
@echo " "
@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 :=
DARWIN_CC := $(shell which clang)
ifeq (${DARWIN_CC},)
DARWIN_CC := $(shell which gcc)
EXTRA_CFLAGS :=
else
EXTRA_CFLAGS := -fvectorize -fslp-vectorize -fslp-vectorize-aggressive -pthread
endif
# Change as required in support of the available libraries
#CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN)
CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN)
# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24.
# Explanation here: https://github.com/wb2osz/direwolf/issues/62
CFLAGS := -Os -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 $(EXTRA_CFLAGS)
# That was fine for a recent Ubuntu and Raspbian Jessie.
# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set.
CFLAGS += -D_BSD_SOURCE
# $(info $$CC is [${CC}])
#
# The DSP filters spend a lot of time spinning around in little
# loops multiplying and adding arrays of numbers. The Intel "SSE"
# instructions, introduced in 1999 with the Pentium III series,
# can speed this up considerably.
#
# SSE2 instructions, added in 2000, don't seem to offer any advantage.
#
#
# Let's take a look at the effect of the compile options.
#
#
# Times are elapsed time to process Track 2 of the TNC test CD.
#
# i.e. "./atest 02_Track_2.wav"
# Default demodulator type is new "E" added for version 1.2.
#
#
# ---------- x86 (32 bit) ----------
#
#
# gcc 4.6.3 running on Ubuntu 12.04.05.
# Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions.
# Probably from around 2004 or 2005.
#
# When gcc is generating code for a 32 bit x86 target, it assumes the ancient
# i386 processor. This is good for portability but bad for performance.
#
# The code can run considerably faster by taking advantage of the SSE instructions
# available in the Pentium 3 or later.
#
# seconds options comments
# ------ ------- --------
# 524
# 183 -O2
# 182 -O3
# 183 -O3 -ffast-math (should be same as -Ofast)
# 184 -Ofast
# 189 -O3 -ffast-math -march=pentium
# 122 -O3 -ffast-math -msse
# 122 -O3 -ffast-math -march=pentium -msse
# 121 -O3 -ffast-math -march=pentium3 (this implies -msse)
# 120 -O3 -ffast-math -march=native
#
# Note that "-march=native" is essentially the same as "-march=pentium3."
#
# If the compiler is generating code for the i386 target, we can
# get much better results by telling it we have at least a Pentium 3.
CFLAGS += -march=core2 -msse4.1 -std=gnu99
#CFLAGS += -march=pentium3 -sse
#
# gcc 4.8.2 running on Ubuntu 14.04.1.
# Intel Core 2 Duo from around 2007 or 2008.
#
# 64 bit target implies that we have SSE and probably even better vector instructions.
#
# seconds options comments
# ------ ------- --------
# 245
# 75 -01
# 72 -02
# 71 -03
# 73 -O3 -march=native
# 42 -O3 -ffast-math
# 42 -Ofast (note below)
# 40 -O3 -ffast-math -march=native
#
#
# Note that "-Ofast" is a newer option roughly equivalent to "-O3 -ffast-math".
# I use the longer form because it is compatible with older compilers.
#
# Why don't I don't have "-march=native?"
# Older compilers don't recognize "native" as one of the valid options.
# One article said it was added with gcc 4.2 but I haven't verified that.
#
# Add -ffastmath in only if compiler version recognizes it.
useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math)
ifneq ($(useffast),)
CFLAGS += -ffast-math
endif
#
# You would expect "-march=native" to produce the fastest code.
# Why don't I use it here?
#
# 1. In my benchmarks, above, it has a negligible impact if any at all.
# 2. Some older versions of gcc don't recognize "native" as a valid choice.
# 3. Results are less portable. Not a consideration if you are
# building only for your own use but very important for anyone
# redistributing a "binary" version.
#
# If you are planning to distribute the binary version to other
# people (in some ham radio software collection, RPM, or DEB package),
# avoid # fine tuning it for your particular computer. It could
# cause compatibility issues for those with older computers.
#
#CFLAGS += -D_FORTIFY_SOURCE
# Use PortAudio Library
# Force static linking of portaudio if the static library is available.
PA_LIB_STATIC := $(shell find /opt/local/lib -maxdepth 1 -type f -name "libportaudio.a")
#$(info $$PA_LIB_STATIC is [${PA_LIB_STATIC}])
ifeq (${PA_LIB_STATIC},)
LDLIBS += -L/opt/local/lib -lportaudio
else
LDLIBS += /opt/local/lib/libportaudio.a
endif
# Include libraries portaudio requires.
LDLIBS += -framework CoreAudio -framework AudioUnit -framework AudioToolbox
LDLIBS += -framework Foundation -framework CoreServices
CFLAGS += -DUSE_PORTAUDIO -I/opt/local/include
# Uncomment following lines to enable GPS interface & tracker function.
# Not available for MacOSX (as far as I know).
# Although MacPorts has gpsd, wonder if it's the same thing. Add the check
# just in case it works.
# Well never mind, issue with Macports with 64bit libs ;-( leave the check in
# until (if ever) Macports fixes the issue.
#GPS_HEADER := $(shell find /opt/local/include -maxdepth 1 -type f -name "gps.h")
#ifeq (${GPS_HEADER},)
#GPS_OBJS :=
#else
#CFLAGS += -DENABLE_GPSD
#LDLIBS += -L/opt/local/lib -lgps -lgpsd
#GPS_OBJS := dwgps.o dwgpsnmea.o dwgpsd.o
#endif
# Name of current directory.
# Used to generate zip file name for distribution.
z := $(notdir ${CURDIR})
# Main application.
direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25_pad.o ax25_pad2.o beacon.o \
config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o demod_psk.o \
demod.o digipeater.o cdigipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \
encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \
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 \
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
$(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm
# Optimization for slow processors.
demod.o : fsk_fast_filter.h
demod_afsk.o : fsk_fast_filter.h
fsk_fast_filter.h : gen_fff
./gen_fff > fsk_fast_filter.h
gen_fff : demod_afsk.c dsp.c textcolor.c
echo " " > tune.h
$(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS)
# UTM, USNG, MGRS conversions.
geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o
ar -cr $@ $^
error_string.o : geotranz/error_string.c
$(CC) $(CFLAGS) -c -o $@ $^
mgrs.o : geotranz/mgrs.c
$(CC) $(CFLAGS) -c -o $@ $^
polarst.o : geotranz/polarst.c
$(CC) $(CFLAGS) -c -o $@ $^
tranmerc.o : geotranz/tranmerc.c
$(CC) $(CFLAGS) -c -o $@ $^
ups.o : geotranz/ups.c
$(CC) $(CFLAGS) -c -o $@ $^
usng.o : geotranz/usng.c
$(CC) $(CFLAGS) -c -o $@ $^
utm.o : geotranz/utm.c
$(CC) $(CFLAGS) -c -o $@ $^
# Generate apprpriate sample configuration file for this platform.
direwolf.conf : generic.conf
egrep '^C|^M' generic.conf | cut -c2-999 > direwolf.conf
# Where should we install it?
# My understanding, of the convention, is that something you compile
# from source, that is not a standard part of the operating system,
# should go in /usr/local/bin.
# This is a step in the right direction but not sufficient to use /usr instead.
INSTALLDIR := /usr/local
# TODO: Test this better.
# Optional installation into /usr/local/...
# Needs to be run as root or with sudo.
# TODO: Review file locations.
# 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
#
# 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
#
# 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-unit.pl $(INSTALLDIR)/bin
$(INSTALL) telemetry-toolkit/telem-volts.py $(INSTALLDIR)/bin
#
# 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
#
# Documentation. Various plain text files and PDF.
#
$(INSTALL) -D --mode=644 README.md $(INSTALLDIR)/share/doc/direwolf/README.md
$(INSTALL) -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md
$(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt
$(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt
#
$(INSTALL) -D --mode=644 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
#
# Sample config files also go into 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.
# Someone suggested that these could go into an "examples" subdirectory under doc.
#
$(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/direwolf.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/telem-m0xer-3.txt
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/telem-balloon.conf
$(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(INSTALLDIR)/share/doc/direwolf/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
#
@echo " "
@echo "If this is your first install, not an upgrade, type this to put a copy"
@echo "of the sample configuration file (direwolf.conf) in your home directory:"
@echo " "
@echo " make install-conf"
@echo " "
# TODO: Should we put the sample direwolf.conf file somewhere like
# /usr/share/doc/direwolf/examples and add that to the
# end of the search path list?
# That would make it easy to see user customizations compared to the
# latest sample.
# These would be done as ordinary user.
.PHONY: install-conf
install-conf : direwolf.conf
cp direwolf.conf ~
cp telemetry-toolkit/telem-m0xer-3.txt ~
cp telemetry-toolkit/telem-*.conf ~
# Separate application to decode raw data.
decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o
$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm
# Convert between text and touch tone representation.
text2tt : tt_text.c
$(CC) $(CFLAGS) -DENC_MAIN -o $@ $^
tt2text : tt_text.c
$(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^
# Convert between Latitude/Longitude and UTM coordinates.
ll2utm : ll2utm.c geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lm
utm2ll : utm2ll.c geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lm
# Convert from log file to GPX.
log2gpx : log2gpx.c
$(CC) $(CFLAGS) -o $@ $^ -lm
# Test application to generate sound.
gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm
demod.o : tune.h
demod_afsk.o : tune.h
demod_9600.o : tune.h
demod_psk.o : tune.h
tune.h :
echo " " > tune.h
testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \
fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c dtime_now.o latlong.c symbols.c tune.h textcolor.c
$(CC) $(CFLAGS) -o atest $^ -lm
./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
# Unit test for 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
$(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
# $(CC) $(CFLAGS) -o $@ $^ -lm
# Unit test for inner digipeater algorithm
dtest : digipeater.c pfilter.o ax25_pad.o dedupe.o fcs_calc.o tq.o textcolor.o \
decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o
$(CC) $(CFLAGS) -DTEST -o $@ $^
./dtest
# Unit test for APRStt.
ttest : aprs_tt.c tt_text.c latlong.c geotranz.a
$(CC) $(CFLAGS) -DTT_MAIN -o $@ $^
# Unit test for IGate
itest : igate.c textcolor.c ax25_pad.c fcs_calc.c
$(CC) $(CFLAGS) -DITEST -o $@ $^
./itest
# Unit test for UDP reception with AFSK demodulator
udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c
$(CC) $(CFLAGS) -o $@ $^ -lm
./udptest
# Unit test for telemetry decoding.
tlmtest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c
$(CC) $(CFLAGS) -o $@ $^ -lm
./tlmtest
# Multiple AGWPE network or serial port clients to test TNCs side by side.
aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c
$(CC) $(CFLAGS) -g -o $@ $^
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o
$(CC) $(CFLAGS) -g -o $@ $^
depend : $(wildcard *.c)
makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
.PHONY: clean
clean :
rm -f $(APPS) gen_fff \
fsk_fast_filter.h *.o *.a use_this_sdk
echo " " > tune.h
.PHONY: dist-mac
dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \
tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png
rm -f ../direwolf_dist_bin.zip
(cd .. ; zip direwolf_dist_bin.zip \
$(INSTALLDIR)/bin/direwolf \
$(INSTALLDIR)/bin/decode_aprs \
$(INSTALLDIR)/bin/text2tt \
$(INSTALLDIR)/bin/tt2text \
$(INSTALLDIR)/bin/ll2utm \
$(INSTALLDIR)/bin/utm2ll \
$(INSTALLDIR)/bin/aclients \
$(INSTALLDIR)/bin/log2gpx \
$(INSTALLDIR)/bin/gen_packets \
$(INSTALLDIR)/bin/atest \
$(INSTALLDIR)/bin/ttcalc \
$(INSTALLDIR)/bin/dwespeak.sh \
$(INSTALLDIR)/share/direwolf/tocalls.txt \
$(INSTALLDIR)/share/direwolf/config/direwolf.conf \
$(INSTALLDIR)/share/direwolf/symbols-new.txt \
$(INSTALLDIR)/share/direwolf/symbolsX.txt \
$(INSTALLDIR)/share/direwolf/dw-icon.png \
$(INSTALLDIR)/share/doc/direwolf/README.md \
$(INSTALLDIR)/share/doc/direwolf/CHANGES.md \
$(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt \
$(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt \
$(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf \
$(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf \
$(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf \
$(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf \
$(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf \
$(INSTALLDIR)/man/man1/aclients.1 \
$(INSTALLDIR)/man/man1/atest.1 \
$(INSTALLDIR)/man/man1/decode_aprs.1 \
$(INSTALLDIR)/man/man1/direwolf.1 \
$(INSTALLDIR)/man/man1/gen_packets.1 \
$(INSTALLDIR)/man/man1/ll2utm.1 \
$(INSTALLDIR)/man/man1/log2gpx.1 \
$(INSTALLDIR)/man/man1/text2tt.1 \
$(INSTALLDIR)/man/man1/tt2text.1 \
$(INSTALLDIR)/man/man1/utm2ll.1 \
)
# Package it up for distribution.
.PHONY: dist-src
dist-src : README.md CHANGES.md \
doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \
doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \
dw-start.sh dwespeak.bat dwespeak.sh \
tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec
rm -f fsk_fast_filter.h
echo " " > tune.h
rm -f ../$z-src.zip
(cd .. ; zip $z-src.zip \
$z/README.md \
$z/CHANGES.md \
$z/LICENSE* \
$z/doc/User-Guide.pdf \
$z/doc/Raspberry-Pi-APRS.pdf \
$z/doc/Raspberry-Pi-APRS-Tracker.pdf \
$z/doc/APRStt-Implementation-Notes.pdf \
$z/Makefile* \
$z/*.c $z/*.h \
$z/regex/* $z/misc/* $z/geotranz/* \
$z/man1/* \
$z/generic.conf \
$z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
$z/dw-start.sh $z/direwolf.spec \
$z/dwespeak.bat $z/dwespeak.sh \
$z/telemetry-toolkit/* )
#
# The destination field is often used to identify the manufacturer/model.
# These are not hardcoded into Dire Wolf. Instead they are read from
# a file called tocalls.txt at application start up time.
#
# The original permanent symbols are built in but the "new" symbols,
# using overlays, are often updated. These are also read from files.
#
# You can obtain an updated copy by typing "make tocalls-symbols".
# This is not part of the normal build process. You have to do this explicitly.
#
# The locations below appear to be the most recent.
# The copy at http://www.aprs.org/tocalls.txt is out of date.
#
.PHONY: tocalls-symbols
tocalls-symbols :
cp tocalls.txt tocalls.txt~
wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
-diff -Z tocalls.txt~ tocalls.txt
cp symbols-new.txt symbols-new.txt~
wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
-diff -Z symbols-new.txt~ symbols-new.txt
cp symbolsX.txt symbolsX.txt~
wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
-diff -Z symbolsX.txt~ symbolsX.txt
direwolf-1.4/Makefile.win 0000664 0000000 0000000 00000044211 13100232553 0015421 0 ustar 00root root 0000000 0000000 #
# Makefile for native Windows version of Dire Wolf.
#
#
# This is built in the Cygwin environment but with the
# compiler from http://www.mingw.org/ so there is no
# dependency on extra DLLs.
#
# The MinGW/bin directory must be in the PATH for the
# compiler. e.g. export PATH=/cygdrive/c/MinGW/bin:$PATH
#
# Failure to have the path set correctly will result in the
# obscure message: Makefile.win:... recipe for target ... failed.
#
# Type "which gcc" to make sure you are getting the right one!
#
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest
# People say we need -mthreads option for threads to work properly.
# They also say it creates a dependency on mingwm10.dll but I'm not seeing that.
# Maybe that is for pthreads. We are using the Windows threads.
# -Ofast was added in gcc 4.6 which was the MinGW version back in 2012.
CC := gcc
CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC -Wall -Wlogical-op
AR := ar
CFLAGS += -g
# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3.
# gcc 4.8 adds these. Try them just for fun.
# No, it needs libasan which is not on Windows.
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
# Helpful for the demodulators. Overkill for non-hot spots.
#CFLAGS += -Wdouble-promotion
# Don't have the patience for this right now.
#CFLAGS += -Wextra
# Continue working on these.
CFLAGS += -Wsign-compare
CFLAGS += -Wuninitialized
CFLAGS += -Wold-style-declaration
# CFLAGS += -fdelete-null-pointer-checks -Wnull-dereference ---not recognized
#CFLAGS += -Wold-style-definition
#-Wmissing-prototypes
#
# Let's see impact of various optimization levels.
# Benchmark results with MinGW gcc version 4.6.2.
#
# seconds options, comments
# ------ -----------------
# 119.8 -O2 Used for version 0.8
# 92.1 -O3
# 88.7 -Ofast (should be same as -O3 -ffastmath)
# 87.5 -Ofast -march=pentium
# 74.1 -Ofast -msse
# 72.2 -Ofast -march=pentium -msse
# 62.0 -Ofast -march=pentium3 (this implies -msse)
# 61.9 -Ofast -march=pentium3 -msse
#
# A minimum of Windows XP is required due to some of the system
# features being used. XP requires a Pentium processor or later.
# The DSP filters can be sped up considerably with the SSE instructions.
# The SSE instructions were introduced in 1999 with the
# Pentium III series.
# SSE2 instructions, added in 2000, don't seem to offer any advantage.
#
# For version 0.9, a Pentium 3 or equivalent is now the minimum required
# for the prebuilt Windows distribution.
# If you insist on using a computer from the previous century,
# you can compile this yourself with different options.
#
# -------------------------------------- Main application --------------------------------
# Not sure why this is here.
demod.o : fsk_demod_state.h
demod_9600.o : fsk_demod_state.h
demod_afsk.o : fsk_demod_state.h
demod_psk.o : fsk_demod_state.h
direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_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 \
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 \
dw-icon.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
dw-icon.o : dw-icon.rc dw-icon.ico
windres dw-icon.rc -o $@
# Optimization for slow processors.
demod.o : fsk_fast_filter.h
demod_afsk.o : fsk_fast_filter.h
fsk_fast_filter.h : gen_fff
./gen_fff > fsk_fast_filter.h
gen_fff : demod_afsk.c dsp.c textcolor.c
echo " " > tune.h
$(CC) $(CFLAGS) -DGEN_FFF -o $@ $^
#
# The destination field is often used to identify the manufacturer/model.
# These are not hardcoded into Dire Wolf. Instead they are read from
# a file called tocalls.txt at application start up time.
#
# The original permanent symbols are built in but the "new" symbols,
# using overlays, are often updated. These are also read from files.
#
# You can obtain an updated copy by typing "make tocalls-symbols".
# This is not part of the normal build process. You have to do this explicitly.
#
# The locations below appear to be the most recent.
# The copy at http://www.aprs.org/tocalls.txt is out of date.
#
.PHONY: tocalls-symbols
tocalls-symbols :
cp tocalls.txt tocalls.txt~
wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt
-diff tocalls.txt~ tocalls.txt
cp symbols-new.txt symbols-new.txt~
wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt
-diff symbols-new.txt~ symbols-new.txt
cp symbolsX.txt symbolsX.txt~
wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt
-diff symbolsX.txt~ symbolsX.txt
# ---------------------------- Other utilities included with distribution -------------------------
# 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
$(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^
# Convert between text and touch tone representation.
text2tt : tt_text.c misc.a
$(CC) $(CFLAGS) -DENC_MAIN -o $@ $^
tt2text : tt_text.c misc.a
$(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^
# Convert between Latitude/Longitude and UTM coordinates.
ll2utm : ll2utm.c textcolor.c geotranz.a misc.a
$(CC) $(CFLAGS) -o $@ $^
utm2ll : utm2ll.c textcolor.c geotranz.a misc.a
$(CC) $(CFLAGS) -o $@ $^
# Convert from log file to GPX.
log2gpx : log2gpx.c textcolor.o misc.a
$(CC) $(CFLAGS) -o $@ $^
# Test application to generate sound.
gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o dtmf.o textcolor.o dsp.o misc.a regex.a
$(CC) $(CFLAGS) -o $@ $^
# ------------------------------------------- Libraries --------------------------------------------
# UTM, USNG, MGRS conversions.
geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o
ar -cr $@ $^
error_string.o : geotranz/error_string.c
$(CC) $(CFLAGS) -c -o $@ $^
mgrs.o : geotranz/mgrs.c
$(CC) $(CFLAGS) -c -o $@ $^
polarst.o : geotranz/polarst.c
$(CC) $(CFLAGS) -c -o $@ $^
tranmerc.o : geotranz/tranmerc.c
$(CC) $(CFLAGS) -c -o $@ $^
ups.o : geotranz/ups.c
$(CC) $(CFLAGS) -c -o $@ $^
usng.o : geotranz/usng.c
$(CC) $(CFLAGS) -c -o $@ $^
utm.o : geotranz/utm.c
$(CC) $(CFLAGS) -c -o $@ $^
#
# When building for Linux, we use regular expression
# functions supplied by the gnu C library.
# For the native WIN32 version, we need to use our own copy.
# These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm
# Consider upgrading from https://www.gnu.org/software/libc/sources.html
regex.a : regex.o
ar -cr $@ $^
regex.o : regex/regex.c
$(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^
# There are several string functions found in Linux
# but not on Windows. Need to provide our own copy.
misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o
ar -cr $@ $^
strsep.o : misc/strsep.c
$(CC) $(CFLAGS) -c -o $@ $^
strtok_r.o : misc/strtok_r.c
$(CC) $(CFLAGS) -c -o $@ $^
strcasestr.o : misc/strcasestr.c
$(CC) $(CFLAGS) -c -o $@ $^
strlcpy.o : misc/strlcpy.c
$(CC) $(CFLAGS) -I. -c -o $@ $^
strlcat.o : misc/strlcat.c
$(CC) $(CFLAGS) -I. -c -o $@ $^
# --------------------------------- Automated Smoke Test --------------------------------
# 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?
# Verify that single bit fixup increases the count.
check-modem1200 : gen_packets atest
gen_packets -n 100 -o test12.wav
atest -F0 -PE -L64 -G72 test12.wav
atest -F1 -PE -L70 -G75 test12.wav
rm test12.wav
check-modem300 : gen_packets atest
gen_packets -B300 -n 100 -o test3.wav
atest -B300 -F0 -L68 -G69 test3.wav
atest -B300 -F1 -L71 -G75 test3.wav
rm test3.wav
#FIXME: test full amplitude.
check-modem9600 : gen_packets atest
gen_packets -B9600 -a 170 -o test96.wav
sleep 1
atest -B9600 -F0 -L4 -G4 test96.wav
sleep 1
rm test96.wav
sleep 1
gen_packets -B9600 -n 100 -o test96.wav
sleep 1
atest -B9600 -F0 -L50 -G54 test96.wav
atest -B9600 -F1 -L55 -G59 test96.wav
sleep 1
rm test96.wav
check-modem19200 : gen_packets atest
gen_packets -r 96000 -B19200 -a 170 -o test19.wav
sleep 1
atest -B19200 -F0 -L4 test19.wav
sleep 1
rm test19.wav
sleep 1
gen_packets -r 96000 -B19200 -n 100 -o test19.wav
sleep 1
atest -B19200 -F0 -L55 -G59 test19.wav
atest -B19200 -F1 -L60 -G64 test19.wav
sleep 1
rm test19.wav
check-modem2400 : gen_packets atest
gen_packets -B2400 -n 100 -o test24.wav
sleep 1
atest -B2400 -F0 -L70 -G78 test24.wav
atest -B2400 -F1 -L80 -G87 test24.wav
sleep 1
rm test24.wav
check-modem4800 : gen_packets atest
gen_packets -B4800 -n 100 -o test48.wav
sleep 1
atest -B4800 -F0 -L70 -G74 test48.wav
atest -B4800 -F1 -L79 -G84 test48.wav
sleep 1
rm test48.wav
# Unit test for demodulators
atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.c \
symbols.c tt_text.c textcolor.c telemetry.c dtime_now.o \
decode_aprs.o log.o \
misc.a regex.a
echo " " > tune.h
$(CC) $(CFLAGS) -o $@ $^
#./atest ..\\direwolf-0.2\\02_Track_2.wav
#atest -B 9600 z9.wav
#atest za100.wav
atest9 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o misc.a regex.a \
fsk_fast_filter.h
echo " " > tune.h
$(CC) $(CFLAGS) -o $@ $^
./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
#./atest9 -B 9600 noise96.wav
# Unit test for inner digipeater algorithm
.PHONY: dtest
dtest : digipeater.c dedupe.c pfilter.c \
ax25_pad.o fcs_calc.o tq.o textcolor.o \
decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a
$(CC) $(CFLAGS) -DDIGITEST -o $@ $^
./dtest
rm dtest.exe
# Unit test for APRStt tone seqence parsing.
.PHONTY: ttest
ttest : aprs_tt.c tt_text.c latlong.o textcolor.o geotranz.a misc.a
$(CC) $(CFLAGS) -Igeotranz -DTT_MAIN -o $@ $^
./ttest
rm ttest.exe
# Unit test for APRStt tone sequence / text conversions.
.PHONY: tttexttest
tttexttest : tt_text.c textcolor.o misc.a
$(CC) $(CFLAGS) -DTTT_TEST -o $@ $^
./tttexttest
rm tttexttest.exe
# Unit test for Packet Filtering.
.PHONY: pftest
pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a regex.a
$(CC) $(CFLAGS) -DPFTEST -o $@ $^
./pftest
rm pftest.exe
# Unit test for telemetry decoding.
.PHONY: tlmtest
tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
$(CC) $(CFLAGS) -DTEST -o $@ $^
./tlmtest
rm tlmtest.exe
# Unit test for location coordinate conversion.
.PHONY: lltest
lltest : latlong.c textcolor.o misc.a
$(CC) $(CFLAGS) -DLLTEST -o $@ $^
./lltest
rm lltest.exe
# Unit test for encoding position & object report.
.PHONY: enctest
enctest : encode_aprs.c latlong.c textcolor.c misc.a
$(CC) $(CFLAGS) -DEN_MAIN -o $@ $^
./enctest
rm enctest.exe
# Unit test for KISS encapsulation.
.PHONY: kisstest
kisstest : kiss_frame.c
$(CC) $(CFLAGS) -DKISSTEST -o $@ $^
./kisstest
rm kisstest.exe
# Unit test for constructing frames besides UI.
.PHONY: pad2test
pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a
$(CC) $(CFLAGS) -DPAD2TEST -o $@ $^
./pad2test
rm pad2test.exe
# Unit Test for XID frame encode/decode.
.PHONY: xidtest
xidtest : xid.c textcolor.o misc.a
$(CC) $(CFLAGS) -DXIDTEST -o $@ $^
./xidtest
rm xidtest.exe
# Unit Test for DTMF encode/decode.
.PHONY: dtmftest
dtmftest : dtmf.c textcolor.o
$(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^
./dtmftest
rm dtmftest.exe
# ------------------------------ Other manual testing & experimenting -------------------------------
tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# For tweaking the demodulator.
demod.o : tune.h
demod_9600.o : tune.h
demod_afsk.o : tune.h
demod_psk.o : tune.h
testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_agc.h \
hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \
dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a
rm -f atest.exe
$(CC) $(CFLAGS) -o atest $^
./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
noisy3.wav : gen_packets
./gen_packets -B 300 -n 100 -o noisy3.wav
testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o regex.a misc.a \
tune.h
rm -f atest3.exe
$(CC) $(CFLAGS) -o atest3 $^
./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
noisy96.wav : gen_packets
./gen_packets -B 9600 -n 100 -o noisy96.wav
testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a regex.a
rm -f atest96.exe
$(CC) $(CFLAGS) -o atest96 $^
./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out
#./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out
#./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
testagc24 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a regex.a
rm -f atest24.exe
sleep 1
$(CC) $(CFLAGS) -o atest24 $^
./atest24 -B 2400 test2400.wav | grep "packets decoded in" >atest.out
echo " " > tune.h
testagc48 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \
dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \
rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \
dwgpsnmea.o dwgps.o serial_port.o latlong.o \
symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \
misc.a regex.a
rm -f atest48.exe
sleep 1
$(CC) $(CFLAGS) -o atest48 $^
./atest48 -B 4800 test4800.wav | grep "packets decoded in" >atest.out
#./atest48 -B 4800 test4800.wav
echo " " > tune.h
# Unit test for IGate
itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a
$(CC) $(CFLAGS) -DITEST -o $@ $^ -lwinmm -lws2_32
# Multiple AGWPE network or serial port clients to test TNCs side by side.
aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
$(CC) $(CFLAGS) -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
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# Send GPS location to KISS TNC each second.
walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \
latlong.o encode_aprs.o serial_port.o textcolor.o \
ax25_pad.o fcs_calc.o \
xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \
hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \
multi_modem.o demod.o demod_afsk.o demod_psk.c demod_9600.o rdq.o \
server.o morse.o dtmf.o audio_stats.o dtime_now.o dlq.o \
regex.a misc.a
$(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32
#--------------------------------------------------------------
.PHONY: depend
depend : $(wildcard *.c)
makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^
.PHONY: clean
clean :
rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav
echo " " > tune.h
# ------------------------------- Packaging for distribution ----------------------
# Name of zip file for distribution.
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 \
generic.conf dwespeak.bat \
README.md CHANGES.md \
doc/User-Guide.pdf \
doc/Raspberry-Pi-APRS.pdf \
doc/APRStt-Implementation-Notes.pdf
rm -f ../$z-win.zip
egrep '^C|^W' generic.conf | cut -c2-999 > direwolf.conf
unix2dos direwolf.conf
zip --junk-paths ../$z-win.zip \
README.md \
CHANGES.md \
doc/User-Guide.pdf \
doc/Raspberry-Pi-APRS.pdf \
doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \
doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \
doc/APRS-Telemetry-Toolkit.pdf \
doc/APRStt-Implementation-Notes.pdf \
doc/APRStt-interface-for-SARTrack.pdf \
doc/APRStt-Listening-Example.pdf \
doc/Raspberry-Pi-APRS.pdf \
doc/Raspberry-Pi-APRS-Tracker.pdf \
doc/Raspberry-Pi-SDR-IGate.pdf \
doc/User-Guide.pdf \
doc/WA8LMF-TNC-Test-CD-Results.pdf \
LICENSE* \
direwolf.conf \
direwolf.exe \
decode_aprs.exe \
tocalls.txt symbols-new.txt symbolsX.txt \
text2tt.exe tt2text.exe \
ll2utm.exe utm2ll.exe \
aclients.exe \
log2gpx.exe \
gen_packets.exe \
atest.exe \
ttcalc.exe \
dwespeak.bat \
telemetry-toolkit/*
# Reminders if pdf files are not up to date.
.PHONY: backup
backup :
mkdir /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
cp -r . /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"`
#
# The following is updated by "make depend"
#
# DO NOT DELETE
direwolf-1.4/README.md 0000664 0000000 0000000 00000013134 13100232553 0014444 0 ustar 00root root 0000000 0000000
# Dire Wolf #
### Decoded Information from Radio Emissions for Windows Or Linux Fans ###
In the early days of Amateur Packet Radio, it was necessary to use an expensive “Terminal Node Controller†(TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard†interface of a computer and using software to decode the signals.
Dire Wolf is a software "soundcard" modem/TNC and [APRS](http://www.aprs.org/) encoder/decoder. It can be used stand-alone to observe APRS traffic, as a digipeater, [APRStt](http://www.aprs.org/aprstt.html) gateway, or Internet Gateway (IGate). It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [RMS Express](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/) and many others.
## Features & Benefits ##
- Lower cost, higher performance alternative to hardware TNC.
Decodes more than 1000 error-free frames from [WA8LMF TNC Test CD](http://wa8lmf.net/TNCtest/).
- Ideal for building a Raspberry Pi digipeater & IGate.
- Data rates: 300 AFSK, 1200 AFSK, 2400 QPSK, 4800 8PSK, and 9600/19200/38400 bps K9NG/G3RUH.
- Interface with applications by
- [AGW](http://uz7.ho.ua/includes/agwpeapi.htm) network protocol
- [KISS](http://www.ax25.net/kiss.aspx) serial port
- [KISS](http://www.ax25.net/kiss.aspx) TCP network protocol
- Decoding of received information for troubleshooting.
- Conversion from APRS to waypoint sentences in popular formats: $GPWPL, $PGRMW, $PMGNWPL, $PKWDWPL.
- Logging and conversion to GPX file format.
- Beaconing for yourself or other nearby entities.
- Very flexible Digipeating with routing and filtering between up to 6 ports.
- APRStt gateway - converts touch tone sequences to APRS objects and voice responses.
- APRS Internet Gateway (IGate) with IPv6 support and special SATGate mode.
- APRS Telemetry Toolkit.
- Compatible with software defined radios (SDR) such as [gqrx](http://gqrx.dk/), [rtl_fm](http://sdr.osmocom.org/trac/wiki/rtl-sdr), and SDR#.
- Includes separate raw packet decoder, decode_aprs.
- AX.25 v2.2 connected mode. (New in version 1.4.)
- Open source so you can see how it works and make your own modifications.
- Runs in 3 different environments:
- Microsoft Windows XP or later
- Linux, regular PC/laptop or single board computer such as Raspberry Pi, BeagleBone Black, cubieboard 2, or C.H.I.P.
- Mac OS X
## Documentation ##
[Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc)
[Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc)
## Installation ##
### Windows ###
Go to the [**releases** page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window.
For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc).
### Linux - Using git clone (recommended) ###
cd ~
git clone https://www.github.com/wb2osz/direwolf
cd direwolf
make
sudo make install
make install-conf
This should give you the most recent stable release. If you want the latest (possibly unstable) development version, use "git checkout dev" before the first "make" command.
For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
### Linux - Using apt-get (Debian flavor operating systems) ###
Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging.
sudo apt-get update
apt-cache showpkg direwolf
sudo apt-get install direwolf
### Linux - Using yum (Red Hat flavor operating systems) ###
Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging.
sudo yum check-update
sudo yum list direwolf
sudo yum install direwolf
### Linux - Download source in tar or zip file ###
Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then:
cd direwolf-*
make
sudo make install
make install-conf
For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf**
## Join the conversation ##
Here are some good places to ask questions and share your experience:
- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info)
- [Raspberry Pi 4 Ham Radio](https://groups.yahoo.com/neo/groups/Raspberry_Pi_4-Ham_RADIO/info)
- [linuxham](https://groups.yahoo.com/neo/groups/linuxham/info)
- [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/)
The github "issues" section is for reporting software defects and enhancement requests. It is NOT a place to ask questions or have general discussions. Please use one of the locations above.
direwolf-1.4/aclients.c 0000664 0000000 0000000 00000050177 13100232553 0015143 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2013, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: aclients.c
*
* Purpose: Multiple concurrent APRS clients for comparing
* TNC demodulator performance.
*
* Description: Establish connection with multiple servers and
* compare results side by side.
*
* Usage: aclients port1=name1 port2=name2 ...
*
* Example: aclients 8000=AGWPE 192.168.1.64:8002=DireWolf COM1=D710A
*
* This will connect to multiple physical or virtual
* TNCs, read packets from them, and display results.
*
* Each port can have the following forms:
*
* * host-name:tcp-port
* * ip-addr:tcp-port
* * tcp-port
* * serial port name (e.g. COM1, /dev/ttyS0)
*
*---------------------------------------------------------------*/
/*
* Native Windows: Use the Winsock interface.
* Linux: Use the BSD socket interface.
*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include
#include // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include "ax25_pad.h"
#include "textcolor.h"
#include "version.h"
struct agwpe_s {
short portx; /* 0 for first, 1 for second, etc. */
short port_hi_reserved;
short kind_lo; /* message type */
short kind_hi;
char call_from[10];
char call_to[10];
int data_len; /* Number of data bytes following. */
int user_reserved;
};
#if __WIN32__
static unsigned __stdcall client_thread_net (void *arg);
static unsigned __stdcall client_thread_serial (void *arg);
#else
static void * client_thread_net (void *arg);
static void * client_thread_serial (void *arg);
#endif
/*
* Convert Internet address to text.
* Can't use InetNtop because it is supported only on Windows Vista and later.
*/
static char * 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!");
}
assert (strlen(pStringBuf) < StringBufSize);
return pStringBuf;
}
/*------------------------------------------------------------------
*
* Name: main
*
* Purpose: Start up multiple client threads listening to different
* TNCs. Print packets. Tally up statistics.
*
* Usage: Described above.
*
*---------------------------------------------------------------*/
#define MAX_CLIENTS 6
/* Obtained from the command line. */
static int num_clients;
static char hostname[MAX_CLIENTS][50]; /* DNS host name or IPv4 address. */
/* Some of the code is there for IPv6 but */
/* needs more work. */
/* Defaults to "localhost" if not specified. */
static char port[MAX_CLIENTS][30]; /* 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 char description[MAX_CLIENTS][50]; /* Name used in the output. */
#if __WIN32__
static HANDLE client_th[MAX_CLIENTS];
#else
static pthread_t client_tid[MAX_CLIENTS];
#endif
#define LINE_WIDTH 120
static int column_width;
static char packets[LINE_WIDTH+4];
static int packet_count[MAX_CLIENTS];
//#define PRINT_MINUTES 2
#define PRINT_MINUTES 30
int main (int argc, char *argv[])
{
int j;
time_t start_time, now, next_print_time;
#if __WIN32__
#else
int e;
setlinebuf (stdout);
#endif
/*
* Extract command line args.
*/
num_clients = argc - 1;
if (num_clients < 1 || num_clients > MAX_CLIENTS) {
printf ("Specify up to %d TNCs on the command line.\n", MAX_CLIENTS);
exit (1);
}
column_width = LINE_WIDTH / num_clients;
for (j=0; j= next_print_time) {
next_print_time = now + (PRINT_MINUTES) * 60;
printf ("\nTotals after %d minutes", (int)((now - start_time) / 60));
for (j=0; jai_next) {
#if DEBUG_DNS
ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
printf (" %s\n", ipaddr_str);
#endif
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
#if DEBUG_DNS
printf ("addresses for hostname:\n");
for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
printf (" %s\n", ipaddr_str);
}
#endif
// Try each address until we find one that is successful.
for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
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[my_index], hostname[my_index], ipaddr_str, port[my_index]);
#endif
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
#endif
(void) close (is);
is = -1;
continue;
}
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. */
printf("Client %d now connected to %s on %s (%s), port %s\n",
my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
server_sock = is;
#endif
break;
}
freeaddrinfo(ai_head);
if (server_sock == -1) {
printf("Client %d unable to connect to %s on %s (%s), port %s\n",
my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
exit (1);
}
/*
* Send command to toggle reception of frames in raw format.
*
* Note: Monitor format is only for UI frames.
* It also discards the via path.
*/
memset (&mon_cmd, 0, sizeof(mon_cmd));
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
/*
* Print all of the monitored packets.
*/
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
if (n != sizeof(mon_cmd)) {
printf ("Read error, client %d received %d command bytes.\n", my_index, n);
exit (1);
}
#if DEBUGx
printf ("client %d received '%c' data, data_len = %d\n",
my_index, mon_cmd.kind_lo, mon_cmd.data_len);
#endif
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
if (n != mon_cmd.data_len) {
printf ("Read error, client %d received %d data bytes.\n", my_index, n);
exit (1);
}
}
/*
* Print it and add to counter.
* The AGWPE score was coming out double the proper value because
* we were getting the same thing from ports 2 and 3.
* 'use_chan' is the first channel we hear from.
* Listen only to that one.
*/
if (mon_cmd.kind_lo == 'K' && (use_chan == -1 || use_chan == mon_cmd.portx)) {
packet_t pp;
char *pinfo;
int info_len;
char result[400];
char *p;
int col, len;
alevel_t alevel;
//printf ("server %d, portx = %d\n", my_index, mon_cmd.portx);
use_chan = mon_cmd.portx;
memset (&alevel, 0xff, sizeof(alevel));
pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
assert (pp != NULL);
ax25_format_addrs (pp, result);
info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
pinfo[info_len] = '\0';
strlcat (result, pinfo, sizeof(result));
for (p=result; *p!='\0'; p++) {
if (! isprint(*p)) *p = ' ';
}
#if DEBUGx
printf ("[%d] %s\n", my_index, result);
#endif
col = column_width * my_index;
len = strlen(result);
#define MARGIN 3
if (len > column_width - 3) {
len = column_width - 3;
}
if (packets[col] == ' ') {
memcpy (packets+col, result, (size_t)len);
}
else {
memcpy (packets+col, "OVERRUN! ", (size_t)10);
}
ax25_delete (pp);
packet_count[my_index]++;
}
}
} /* end client_thread_net */
/*-------------------------------------------------------------------
*
* Name: client_thread_serial
*
* Purpose: Establish connection with a TNC via serial port.
*
* Inputs: arg - My instance index, 0 thru MAX_CLIENTS-1.
*
* Outputs: packets - Received packets are put in the corresponding column.
*
*--------------------------------------------------------------------*/
#if __WIN32__
typedef HANDLE MYFDTYPE;
#define MYFDERROR INVALID_HANDLE_VALUE
#else
typedef int MYFDTYPE;
#define MYFDERROR (-1)
#endif
#if __WIN32__
static unsigned __stdcall client_thread_serial (void *arg)
#else
static void * client_thread_serial (void *arg)
#endif
{
int my_index = (int)(long)arg;
#if __WIN32__
MYFDTYPE fd;
DCB dcb;
int ok;
// Bug: Won't work for ports above COM9.
// http://support.microsoft.com/kb/115831
fd = CreateFile(port[my_index], GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (fd == MYFDERROR) {
printf("Client %d unable to connect to %s on %s.\n",
my_index, description[my_index], port[my_index] );
exit (1);
}
/* 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) {
printf ("GetCommState failed.\n");
}
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = 9600;
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) {
printf ("SetCommState failed.\n");
}
#else
/* Linux version. */
int fd;
struct termios ts;
int e;
fd = open (port[my_index], O_RDWR);
if (fd == MYFDERROR) {
printf("Client %d unable to connect to %s on %s.\n",
my_index, description[my_index], port[my_index] );
exit (1);
}
e = tcgetattr (fd, &ts);
if (e != 0) { perror ("nm tcgetattr"); }
cfmakeraw (&ts);
// TODO: speed?
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"); }
#endif
/* Success. */
printf("Client %d now connected to %s on %s\n",
my_index, description[my_index], port[my_index] );
/*
* Assume we are already in monitor mode.
*/
/*
* Print all of the monitored packets.
*/
while (1) {
unsigned char ch;
char result[500];
int col, len;
int done;
char *p;
len = 0;
done = 0;
while ( ! done) {
#if __WIN32__
DWORD n;
if (! ReadFile (fd, &ch, 1, &n, NULL)) {
printf ("Read error on %s.\n", description[my_index]);
CloseHandle (fd);
exit (1);
}
#else
int n;
if ( ( n = read(fd, & ch, 1)) < 0) {
printf ("Read error on %s.\n", description[my_index]);
close (fd);
exit (1);
}
#endif
if (n == 1) {
/*
* Try to build one line for each packet.
* The KPC3+ breaks a packet into two lines like this:
*
* KB1ZXL-1>T2QY5P,W1MHL*,WIDE2-1: <>:
* `c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1=
*
* N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: :
* !4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100
*
* Don't know why some are <> and some .
*
* Anyhow, ignore the return character if preceded by >:
*/
if (ch == '\r') {
if (len >= 10 && result[len-2] == '>' && result[len-1] == ':') {
continue;
}
done = 1;
continue;
}
if (ch == '\n') continue;
result[len++] = ch;
}
}
result[len] = '\0';
/*
* Print it and add to counter.
*/
if (len > 0) {
/* Blank any unprintable characters. */
for (p=result; *p!='\0'; p++) {
if (! isprint(*p)) *p = ' ';
}
#if DEBUGx
printf ("[%d] %s\n", my_index, result);
#endif
col = column_width * my_index;
if (len > column_width - 3) {
len = column_width - 3;
}
if (packets[col] == ' ') {
memcpy (packets+col, result, (size_t)len);
}
else {
memcpy (packets+col, "OVERRUN! ", (size_t)10);
}
packet_count[my_index]++;
}
}
} /* end client_thread_serial */
/* end aclients.c */
direwolf-1.4/aprs_tt.c 0000664 0000000 0000000 00000152340 13100232553 0015010 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: aprs_tt.c
*
* Purpose: First half of APRStt gateway.
*
* Description: This file contains functions to parse the tone sequences
* and extract meaning from them.
*
* tt_user.c maintains information about users and
* generates the APRS Object Reports.
*
*
* References: This is based upon APRStt (TM) documents with some
* artistic freedom.
*
* http://www.aprs.org/aprstt.html
*
*---------------------------------------------------------------*/
#define APRS_TT_C 1
#include "direwolf.h"
// TODO: clean up terminolgy.
// "Message" has a specific meaning in APRS and this is not it.
// Touch Tone sequence should be appropriate.
// What do we call the parts separated by * key? Field.
#include
#include
#include
#include
#include
#include
#include
#include
#include "version.h"
#include "ax25_pad.h"
#include "hdlc_rec2.h" /* for process_rec_frame */
#include "textcolor.h"
#include "aprs_tt.h"
#include "tt_text.h"
#include "tt_user.h"
#include "symbols.h"
#include "latlong.h"
#include "dlq.h"
#include "demod.h" /* for alevel_t & demod_get_audio_level() */
#include "tq.h"
// geotranz
#include "utm.h"
#include "mgrs.h"
#include "usng.h"
#include "error_string.h"
/* Convert between degrees and radians. */
#define D2R(d) ((d) * M_PI / 180.)
#define R2D(r) ((r) * 180. / M_PI)
/*
* Touch Tone sequences are accumulated here until # terminator found.
* Kept separate for each audio channel so the gateway CAN be listening
* on multiple channels at the same time.
*/
#define MAX_MSG_LEN 100
static char msg_str[MAX_CHANS][MAX_MSG_LEN+1];
static int msg_len[MAX_CHANS];
static int parse_fields (char *msg);
static int parse_callsign (char *e);
static int parse_object_name (char *e);
static int parse_symbol (char *e);
static int parse_aprstt3_call (char *e);
static int parse_location (char *e);
static int parse_comment (char *e);
static int expand_macro (char *e);
#ifndef TT_MAIN
static void raw_tt_data_to_app (int chan, char *msg);
#endif
static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize);
#if TT_MAIN
static void check_result (void);
#endif
/*------------------------------------------------------------------
*
* Name: aprs_tt_init
*
* Purpose: Initialize the APRStt gateway at system startup time.
*
* Inputs: Configuration options gathered by config.c.
*
* Global out: Make our own local copy of the structure here.
*
* Returns: None
*
* Description: The main program needs to call this at application
* start up time after reading the configuration file.
*
* TT_MAIN is defined for unit testing.
*
*----------------------------------------------------------------*/
static struct tt_config_s tt_config;
#if TT_MAIN
#define NUM_TEST_CONFIG (sizeof(test_config) / sizeof (struct ttloc_s))
static struct ttloc_s test_config[] = {
{ TTLOC_POINT, "B01", .point.lat = 12.25, .point.lon = 56.25 },
{ TTLOC_POINT, "B988", .point.lat = 12.50, .point.lon = 56.50 },
{ TTLOC_VECTOR, "B5bbbdddd", .vector.lat = 53., .vector.lon = -1., .vector.scale = 1000. }, /* km units */
/* Hilltop Tower http://www.aprs.org/aprs-jamboree-2013.html */
{ TTLOC_VECTOR, "B5bbbddd", .vector.lat = 37+55.37/60., .vector.lon = -(81+7.86/60.), .vector.scale = 16.09344 }, /* .01 mile units */
{ TTLOC_GRID, "B2xxyy", .grid.lat0 = 12.00, .grid.lon0 = 56.00,
.grid.lat9 = 12.99, .grid.lon9 = 56.99 },
{ TTLOC_GRID, "Byyyxxx", .grid.lat0 = 37 + 50./60.0, .grid.lon0 = 81,
.grid.lat9 = 37 + 59.99/60.0, .grid.lon9 = 81 + 9.99/60.0 },
{ TTLOC_MHEAD, "BAxxxxxx", .mhead.prefix = "326129" },
{ TTLOC_SATSQ, "BAxxxx" },
{ TTLOC_MACRO, "xxyyy", .macro.definition = "B9xx*AB166*AA2B4C5B3B0Ayyy" },
{ TTLOC_MACRO, "xxxxzzzzzzzzzz", .macro.definition = "BAxxxx*ACzzzzzzzzzz" },
};
#endif
void aprs_tt_init (struct tt_config_s *p)
{
int c;
#if TT_MAIN
/* For unit testing. */
memset (&tt_config, 0, sizeof(struct tt_config_s));
tt_config.ttloc_size = NUM_TEST_CONFIG;
tt_config.ttloc_ptr = test_config;
tt_config.ttloc_len = NUM_TEST_CONFIG;
/* Don't care about xmit timing or corral here. */
#else
// TODO: Keep ptr instead of making a copy.
memcpy (&tt_config, p, sizeof(struct tt_config_s));
#endif
for (c=0; c= 0 && chan < MAX_CHANS);
//if (button != '.') {
// dw_printf ("aprs_tt_button (%d, '%c')\n", chan, button);
//}
// TODO: Might make more sense to put timeout here rather in the dtmf decoder.
if (button == '$') {
/* Timeout reset. */
msg_len[chan] = 0;
msg_str[chan][0] = '\0';
}
else if (button != '.' && button != ' ') {
if (msg_len[chan] < MAX_MSG_LEN) {
msg_str[chan][msg_len[chan]++] = button;
msg_str[chan][msg_len[chan]] = '\0';
}
if (button == '#') {
/*
* Put into the receive queue like any other packet.
* This way they are all processed by the common receive thread
* rather than the thread associated with the particular audio device.
*/
raw_tt_data_to_app (chan, msg_str[chan]);
msg_len[chan] = 0;
msg_str[chan][0] = '\0';
}
}
else {
/*
* Idle time. Poll occasionally for processing.
* Timing would be off we we are listening to more than
* one channel so do this only for the one specified
* in the TTOBJ command.
*/
if (chan == tt_config.obj_recv_chan) {
poll_period++;
if (poll_period >= 39) {
poll_period = 0;
tt_user_background ();
}
}
}
} /* end aprs_tt_button */
#endif
/*------------------------------------------------------------------
*
* Name: aprs_tt_sequence
*
* Purpose: Process complete received touch tone sequence
* terminated by #.
*
* Inputs: chan - Audio channel it came from.
*
* msg - String of DTMF buttons.
* # should be the final character.
*
* Returns: None
*
* Description: Process a complete tone sequence.
* It should have one or more fields separated by *
* and terminated by a final # like these:
*
* callsign #
* entry1 * callsign #
* entry1 * entry * callsign #
*
* Limitation: Has one set of static data for communication among
* group of functions. This shouldn't be a problem
* when receiving on multiple channels at once
* because they get serialized thru the receive packet queue.
*
*----------------------------------------------------------------*/
static char m_callsign[20]; /* really object name */
/*
* Standard APRStt has symbol code 'A' (box) with overlay of 0-9, A-Z.
*
* Dire Wolf extension allows:
* Symbol table '/' (primary), any symbol code.
* Symbol table '\' (alternate), any symbol code.
* Alternate table symbol code, overlay of 0-9, A-Z.
*/
static char m_symtab_or_overlay;
static char m_symbol_code; // Default 'A'
static char m_loc_text[24];
static double m_longitude; // Set to G_UNKNOWN if not defined.
static double m_latitude; // Set to G_UNKNOWN if not defined.
static int m_ambiguity;
static char m_comment[200];
static char m_freq[12];
static char m_ctcss[8];
static char m_mic_e;
static char m_dao[6];
static int m_ssid; // Default 12 for APRStt user.
void aprs_tt_sequence (int chan, char *msg)
{
int err;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n\"%s\"\n", msg);
#endif
/*
* Discard empty message.
* In case # is there as optional start.
*/
if (msg[0] == '#') return;
/*
* The parse functions will fill these in.
*/
strlcpy (m_callsign, "", sizeof(m_callsign));
m_symtab_or_overlay = APRSTT_DEFAULT_SYMTAB;
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
strlcpy (m_loc_text, "", sizeof(m_loc_text));
m_longitude = G_UNKNOWN;
m_latitude = G_UNKNOWN;
m_ambiguity = 0;
strlcpy (m_comment, "", sizeof(m_comment));
strlcpy (m_freq, "", sizeof(m_freq));
strlcpy (m_ctcss, "", sizeof(m_ctcss));
m_mic_e = ' ';
strlcpy (m_dao, "!T !", sizeof(m_dao)); /* start out unknown */
m_ssid = 12;
/*
* Parse the touch tone sequence.
*/
err = parse_fields (msg);
#if defined(DEBUG)
text_color_set(DW_COLOR_DEBUG);
dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", ctcss=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n",
m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_ctcss, m_comment, m_latitude, m_longitude, m_dao);
#endif
#if TT_MAIN
(void)err; // suppress variable set but not used warning.
check_result (); // for unit testing.
#else
/*
* If digested successfully. Add to our list of users and schedule transmissions.
*/
if (err == 0) {
err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code,
m_loc_text, m_latitude, m_longitude, m_ambiguity,
m_freq, m_ctcss, m_comment, m_mic_e, m_dao);
}
/*
* If a command / script was supplied, run it now.
* This can do additional processing and provide a custom audible response.
* This is done only for the success case.
* It might be useful to run it for error cases as well but we currently
* don't pass in the success / failure code to know the difference.
*/
char script_response[1000];
strlcpy (script_response, "", sizeof(script_response));
if (err == 0 && strlen(tt_config.ttcmd) > 0) {
dw_run_cmd (tt_config.ttcmd, 1, script_response, sizeof(script_response));
}
/*
* Send response to user by constructing packet with SPEECH or MORSE as destination.
* Source shouldn't matter because it doesn't get transmitted as AX.25 frame.
* Use high priority queue for consistent timing.
*
* Anything from script, above, will override other predefined responses.
*/
char audible_response[1000];
snprintf (audible_response, sizeof(audible_response),
"APRSTT>%s:%s",
tt_config.response[err].method,
(strlen(script_response) > 0) ? script_response : tt_config.response[err].mtext);
packet_t pp;
pp = ax25_from_text (audible_response, 0);
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error. Couldn't make frame from \"%s\"\n", audible_response);
return;
}
tq_append (chan, TQ_PRIO_0_HI, pp);
#endif /* ifndef TT_MAIN */
} /* end aprs_tt_sequence */
/*------------------------------------------------------------------
*
* Name: parse_fields
*
* Purpose: Separate the complete string of touch tone characters
* into fields, delimited by *, and process each.
*
* Inputs: msg - String of DTMF buttons.
*
* Returns: None
*
* Description: It should have one or more fields separated by *.
*
* callsign #
* entry1 * callsign #
* entry1 * entry * callsign #
*
* Note that this will be used recursively when macros
* are expanded.
*
* "To iterate is human, to recurse divine."
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
*----------------------------------------------------------------*/
static int parse_fields (char *msg)
{
char stemp[MAX_MSG_LEN+1];
char *e;
char *save;
int err;
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("parse_fields (%s).\n", msg);
strlcpy (stemp, msg, sizeof(stemp));
e = strtok_r (stemp, "*#", &save);
while (e != NULL) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("parse_fields () field = %s\n", e);
switch (*e) {
case 'A':
switch (e[1]) {
case 'A': /* AA object-name */
err = parse_object_name (e);
if (err != 0) return (err);
break;
case 'B': /* AB symbol */
err = parse_symbol (e);
if (err != 0) return (err);
break;
case 'C': /* AC new-style-callsign */
err = parse_aprstt3_call (e);
if (err != 0) return (err);
break;
default: /* Traditional style call or suffix */
err = parse_callsign (e);
if (err != 0) return (err);
break;
}
break;
case 'B':
err = parse_location (e);
if (err != 0) return (err);
break;
case 'C':
err = parse_comment (e);
if (err != 0) return (err);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
err = expand_macro (e);
if (err != 0) return (err);
break;
case '\0':
/* Empty field. Just ignore it. */
/* This would happen if someone uses a leading *. */
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Field does not start with A, B, C, or digit: \"%s\"\n", msg);
return (TT_ERROR_D_MSG);
}
e = strtok_r (NULL, "*#", &save);
}
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("parse_fields () normal return\n");
return (0);
} /* end parse_fields */
/*------------------------------------------------------------------
*
* Name: expand_macro
*
* Purpose: Expand compact form "macro" to full format then process.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should contain only digits.
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
* Description: Separate out the fields, perform substitution,
* call parse_fields for processing.
*
*
* Future: Generalize this to allow any lower case letter for substitution?
*
*----------------------------------------------------------------*/
#define VALSTRSIZE 20
static int expand_macro (char *e)
{
//int len;
int ipat;
char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE];
char stemp[MAX_MSG_LEN+1];
char *d;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Macro tone sequence: '%s'\n", e);
//len = strlen(e);
ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE);
if (ipat >= 0) {
// Why did we print b & d here?
// Documentation says only x, y, z can be used with macros.
// Only those 3 are processed below.
//dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s, b=%s, d=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr, bstr, dstr);
dw_printf ("Matched pattern %3d: '%s', x=%s, y=%s, z=%s\n", ipat, tt_config.ttloc_ptr[ipat].pattern, xstr, ystr, zstr);
dw_printf ("Replace with: '%s'\n", tt_config.ttloc_ptr[ipat].macro.definition);
if (tt_config.ttloc_ptr[ipat].type != TTLOC_MACRO) {
/* Found match to a different type. Really shouldn't be here. */
/* Print internal error message... */
dw_printf ("expand_macro: type != TTLOC_MACRO\n");
return (TT_ERROR_INTERNAL);
}
/*
* We found a match for the length and any fixed digits.
* Substitute values in to the definition.
*/
strlcpy (stemp, "", sizeof(stemp));
for (d = tt_config.ttloc_ptr[ipat].macro.definition; *d != '\0'; d++) {
while (( *d == 'x' || *d == 'y' || *d == 'z') && *d == d[1]) {
/* Collapse adjacent matching substitution characters. */
d++;
}
switch (*d) {
case 'x':
strlcat (stemp, xstr, sizeof(stemp));
break;
case 'y':
strlcat (stemp, ystr, sizeof(stemp));
break;
case 'z':
strlcat (stemp, zstr, sizeof(stemp));
break;
default:
{
char c1[2];
c1[0] = *d;
c1[1] = '\0';
strlcat (stemp, c1, sizeof(stemp));
}
break;
}
}
/*
* Process as if we heard this over the air.
*/
dw_printf ("After substitution: '%s'\n", stemp);
return (parse_fields (stemp));
}
else {
/* Send reject sound. */
/* Does not match any macro definitions. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("Tone sequence did not match any pattern\n");
return (TT_ERROR_MACRO_NOMATCH);
}
/* should be unreachable */
return (0);
}
/*------------------------------------------------------------------
*
* Name: parse_callsign
*
* Purpose: Extract traditional format callsign or object name from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should start with "A".
*
* Outputs: m_callsign
*
* m_symtab_or_overlay - Set to 0-9 or A-Z if specified.
*
* m_symbol_code - Always set to 'A'.
* NO! This should be applied only if we
* have the default value at this point.
* The symbol might have been explicitly
* set already and we don't want to overwrite that.
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
* Description: We recognize 3 different formats:
*
* Annn - 3 digits are a tactical callsign. No overlay.
*
* Annnvk - Abbreviation with 3 digits, numeric overlay, checksum.
* Annnvvk - Abbreviation with 3 digits, letter overlay, checksum.
*
* Att...ttvk - Full callsign in two key method, numeric overlay, checksum.
* Att...ttvvk - Full callsign in two key method, letter overlay, checksum.
*
*
*----------------------------------------------------------------*/
static int checksum_not_ok (char *str, int len, char found)
{
int i;
int sum;
char expected;
sum = 0;
for (i=0; i= 'A' && str[i] <= 'D') {
sum += str[i] - 'A' + 10;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("aprs_tt: checksum: bad character \"%c\" in checksum calculation!\n", str[i]);
}
}
expected = '0' + (sum % 10);
if (expected != found) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Bad checksum for \"%.*s\". Expected %c but received %c.\n", len, str, expected, found);
return (TT_ERROR_BAD_CHECKSUM);
}
return (0);
}
static int parse_callsign (char *e)
{
int len;
char tttemp[40], stemp[30];
assert (*e == 'A');
len = strlen(e);
/*
* special case: 3 digit tactical call.
*/
if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) {
strlcpy (m_callsign, e+1, sizeof(m_callsign));
return (0);
}
/*
* 3 digit abbreviation: We only do the parsing here.
* Another part of application will try to find corresponding full call.
*/
if ((len == 6 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5])) ||
(len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isupper(e[5]) && isdigit(e[6]))) {
int cs_err = checksum_not_ok (e+1, len-2, e[len-1]);
if (cs_err != 0) {
return (cs_err);
}
strncpy (m_callsign, e+1, 3);
m_callsign[3] = '\0';
if (len == 7) {
tttemp[0] = e[len-3];
tttemp[1] = e[len-2];
tttemp[2] = '\0';
tt_two_key_to_text (tttemp, 0, stemp);
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = stemp[0];
}
else {
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = e[len-2];
}
return (0);
}
/*
* Callsign in two key format.
*/
if (len >= 7 && len <= 24) {
int cs_err = checksum_not_ok (e+1, len-2, e[len-1]);
if (cs_err != 0) {
return (cs_err);
}
if (isupper(e[len-2])) {
strncpy (tttemp, e+1, len-4);
tttemp[len-4] = '\0';
tt_two_key_to_text (tttemp, 0, m_callsign);
tttemp[0] = e[len-3];
tttemp[1] = e[len-2];
tttemp[2] = '\0';
tt_two_key_to_text (tttemp, 0, stemp);
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = stemp[0];
}
else {
strncpy (tttemp, e+1, len-3);
tttemp[len-3] = '\0';
tt_two_key_to_text (tttemp, 0, m_callsign);
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = e[len-2];
}
return (0);
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Touch tone callsign not valid: \"%s\"\n", e);
return (TT_ERROR_INVALID_CALL);
}
/*------------------------------------------------------------------
*
* Name: parse_object_name
*
* Purpose: Extract object name from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should start with "AA".
*
* Outputs: m_callsign
*
* m_ssid - Cleared to remove the default of 12.
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
* Description: Data format
*
* AAtt...tt - Symbol name, two key method, up to 9 characters.
*
*----------------------------------------------------------------*/
static int parse_object_name (char *e)
{
int len;
//int c_length;
//char tttemp[40];
//char stemp[30];
assert (e[0] == 'A');
assert (e[1] == 'A');
len = strlen(e);
/*
* Object name in two key format.
*/
if (len >= 2 + 1 && len <= 30) {
if (tt_two_key_to_text (e+2, 0, m_callsign) == 0) {
m_callsign[9] = '\0'; /* truncate to 9 */
m_ssid = 0; /* No ssid for object name */
return (0);
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Touch tone object name not valid: \"%s\"\n", e);
return (TT_ERROR_INVALID_OBJNAME);
} /* end parse_oject_name */
/*------------------------------------------------------------------
*
* Name: parse_symbol
*
* Purpose: Extract symbol from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should start with "AB".
*
* Outputs: m_symtab_or_overlay
*
* m_symbol_code
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
* Description: Data format
*
* AB1nn - Symbol from primary symbol table.
* Two digits nn are the same as in the GPSCnn
* generic address used as a destination.
*
* AB2nn - Symbol from alternate symbol table.
* Two digits nn are the same as in the GPSEnn
* generic address used as a destination.
*
* AB0nnvv - Symbol from alternate symbol table.
* Two digits nn are the same as in the GPSEnn
* generic address used as a destination.
* vv is an overlay digit or letter in two key method.
*
*----------------------------------------------------------------*/
static int parse_symbol (char *e)
{
int len;
char nstr[3];
int nn;
char stemp[10];
assert (e[0] == 'A');
assert (e[1] == 'B');
len = strlen(e);
if (len >= 4 && len <= 10) {
nstr[0] = e[3];
nstr[1] = e[4];
nstr[2] = '\0';
nn = atoi (nstr);
if (nn < 1) {
nn = 1;
}
else if (nn > 94) {
nn = 94;
}
switch (e[2]) {
case '1':
m_symtab_or_overlay = '/';
m_symbol_code = 32 + nn;
return (0);
break;
case '2':
m_symtab_or_overlay = '\\';
m_symbol_code = 32 + nn;
return (0);
break;
case '0':
if (len >= 6) {
if (tt_two_key_to_text (e+5, 0, stemp) == 0) {
m_symbol_code = 32 + nn;
m_symtab_or_overlay = stemp[0];
return (0);
}
}
break;
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Touch tone symbol not valid: \"%s\"\n", e);
return (TT_ERROR_INVALID_SYMBOL);
} /* end parse_oject_name */
/*------------------------------------------------------------------
*
* Name: parse_aprstt3_call
*
* Purpose: Extract QIKcom-2 / APRStt 3 ten digit call or five digit suffix.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should start with "AC".
*
* Outputs: m_callsign
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
* Description: We recognize 3 different formats:
*
* ACxxxxxxxxxx - 10 digit full callsign.
*
* ACxxxxx - 5 digit suffix. If we can find a corresponding full
* callsign, that will be substituted.
* Error condition is returned if we can't find one.
*
*----------------------------------------------------------------*/
static int parse_aprstt3_call (char *e)
{
assert (e[0] == 'A');
assert (e[1] == 'C');
if (strlen(e) == 2+10) {
char call[12];
if (tt_call10_to_text(e+2,1,call) == 0) {
strlcpy(m_callsign, call, sizeof(m_callsign));
}
else {
return (TT_ERROR_INVALID_CALL); /* Could not convert to text */
}
}
else if (strlen(e) == 2+5) {
char suffix[8];
if (tt_call5_suffix_to_text(e+2,1,suffix) == 0) {
#if TT_MAIN
/* For unit test, use suffix rather than trying lookup. */
strlcpy (m_callsign, suffix, sizeof(m_callsign));
#else
char call[12];
/* In normal operation, try to find full callsign for the suffix received. */
if (tt_3char_suffix_search (suffix, call) >= 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Suffix \"%s\" was converted to full callsign \"%s\"\n", suffix, call);
strlcpy(m_callsign, call, sizeof(m_callsign));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't find full callsign for suffix \"%s\"\n", suffix);
return (TT_ERROR_SUFFIX_NO_CALL); /* Don't know this user. */
}
#endif
}
else {
return (TT_ERROR_INVALID_CALL); /* Could not convert to text */
}
}
else {
return (TT_ERROR_INVALID_CALL); /* Invalid length, not 2+ (10 ir 5) */
}
return (0);
} /* end parse_aprstt3_call */
/*------------------------------------------------------------------
*
* Name: parse_location
*
* Purpose: Extract location from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should start with "B".
*
* Outputs: m_latitude
* m_longitude
*
* m_dao It should previously be "!T !" to mean unknown or none.
* We generally take the first two tones of the field.
* For example, "!TB5!" for the standard bearing & range.
* The point type is an exception where we use "!Tn !" for
* one of ten positions or "!Tnn" for one of a hundred.
* If this ever changes, be sure to update corresponding
* section in process_comment() in decode_aprs.c
*
* m_ambiguity
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
* Description: There are many different formats recognizable
* by total number of digits and sometimes the first digit.
*
* We handle most of them in a general way, processing
* them in 5 groups:
*
* * points
* * vector
* * grid
* * utm
* * usng / mgrs
*
* Position ambiguity is also handled here.
* Latitude, Longitude, and DAO should not be touched in this case.
* We only record a position ambiguity value.
*
*----------------------------------------------------------------*/
/* Average radius of earth in meters. */
#define R 6371000.
static int parse_location (char *e)
{
int ipat;
char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE];
double x, y, dist, bearing;
double lat0, lon0;
double lat9, lon9;
long lerr;
double easting, northing;
char mh[20];
char stemp[32];
assert (*e == 'B');
ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE);
if (ipat >= 0) {
//dw_printf ("ipat=%d, x=%s, y=%s, b=%s, d=%s\n", ipat, xstr, ystr, bstr, dstr);
switch (tt_config.ttloc_ptr[ipat].type) {
case TTLOC_POINT:
m_latitude = tt_config.ttloc_ptr[ipat].point.lat;
m_longitude = tt_config.ttloc_ptr[ipat].point.lon;
/* Is it one of ten or a hundred positions? */
/* It's not hardwired to always be B0n or B9nn. */
/* This is a pretty good approximation. */
m_dao[2] = e[0];
m_dao[3] = e[1];
if (strlen(e) == 3) { /* probably B0n --> !Tn ! */
m_dao[2] = e[2];
m_dao[3] = ' ';
}
if (strlen(e) == 4) { /* probably B9nn --> !Tnn! */
m_dao[2] = e[2];
m_dao[3] = e[3];
}
break;
case TTLOC_VECTOR:
if (strlen(bstr) != 3) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Bearing \"%s\" should be 3 digits.\n", bstr);
// return error code?
}
if (strlen(dstr) < 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Distance \"%s\" should 1 or more digits.\n", dstr);
// return error code?
}
lat0 = D2R(tt_config.ttloc_ptr[ipat].vector.lat);
lon0 = D2R(tt_config.ttloc_ptr[ipat].vector.lon);
dist = atof(dstr) * tt_config.ttloc_ptr[ipat].vector.scale;
bearing = D2R(atof(bstr));
/* Equations and caluculators found here: */
/* http://movable-type.co.uk/scripts/latlong.html */
/* This should probably be a function in latlong.c in case we have another use for it someday. */
m_latitude = R2D(asin(sin(lat0) * cos(dist/R) + cos(lat0) * sin(dist/R) * cos(bearing)));
m_longitude = R2D(lon0 + atan2(sin(bearing) * sin(dist/R) * cos(lat0),
cos(dist/R) - sin(lat0) * sin(D2R(m_latitude))));
m_dao[2] = e[0];
m_dao[3] = e[1];
break;
case TTLOC_GRID:
if (strlen(xstr) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Missing X coordinate.\n");
strlcpy (xstr, "0", sizeof(xstr));
}
if (strlen(ystr) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Missing Y coordinate.\n");
strlcpy (ystr, "0", sizeof(ystr));
}
lat0 = tt_config.ttloc_ptr[ipat].grid.lat0;
lat9 = tt_config.ttloc_ptr[ipat].grid.lat9;
y = atof(ystr);
m_latitude = lat0 + y * (lat9-lat0) / (pow(10., strlen(ystr)) - 1.);
lon0 = tt_config.ttloc_ptr[ipat].grid.lon0;
lon9 = tt_config.ttloc_ptr[ipat].grid.lon9;
x = atof(xstr);
m_longitude = lon0 + x * (lon9-lon0) / (pow(10., strlen(xstr)) - 1.);
m_dao[2] = e[0];
m_dao[3] = e[1];
break;
case TTLOC_UTM:
if (strlen(xstr) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Missing X coordinate.\n");
/* Avoid divide by zero later. Put in middle of range. */
strlcpy (xstr, "5", sizeof(xstr));
}
if (strlen(ystr) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Missing Y coordinate.\n");
/* Avoid divide by zero later. Put in middle of range. */
strlcpy (ystr, "5", sizeof(ystr));
}
x = atof(xstr);
easting = x * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.x_offset;
y = atof(ystr);
northing = y * tt_config.ttloc_ptr[ipat].utm.scale + tt_config.ttloc_ptr[ipat].utm.y_offset;
if (isalpha(tt_config.ttloc_ptr[ipat].utm.latband)) {
snprintf (m_loc_text, sizeof(m_loc_text), "%d%c %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), tt_config.ttloc_ptr[ipat].utm.latband, easting, northing);
}
else if (tt_config.ttloc_ptr[ipat].utm.latband == '-') {
snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(- tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing);
}
else {
snprintf (m_loc_text, sizeof(m_loc_text), "%d %.0f %.0f", (int)(tt_config.ttloc_ptr[ipat].utm.lzone), easting, northing);
}
lerr = Convert_UTM_To_Geodetic(tt_config.ttloc_ptr[ipat].utm.lzone,
tt_config.ttloc_ptr[ipat].utm.hemi, easting, northing, &lat0, &lon0);
if (lerr == 0) {
m_latitude = R2D(lat0);
m_longitude = R2D(lon0);
//dw_printf ("DEBUG: from UTM, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude);
}
else {
char message[300];
text_color_set(DW_COLOR_ERROR);
utm_error_string (lerr, message);
dw_printf ("Conversion from UTM failed:\n%s\n\n", message);
}
m_dao[2] = e[0];
m_dao[3] = e[1];
break;
case TTLOC_MGRS:
case TTLOC_USNG:
if (strlen(xstr) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("MGRS/USNG: Missing X (easting) coordinate.\n");
/* Should not be possible to get here. Fake it and carry on. */
strlcpy (xstr, "5", sizeof(xstr));
}
if (strlen(ystr) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("MGRS/USNG: Missing Y (northing) coordinate.\n");
/* Should not be possible to get here. Fake it and carry on. */
strlcpy (ystr, "5", sizeof(ystr));
}
char loc[40];
strlcpy (loc, tt_config.ttloc_ptr[ipat].mgrs.zone, sizeof(loc));
strlcat (loc, xstr, sizeof(loc));
strlcat (loc, ystr, sizeof(loc));
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("MGRS/USNG location debug: %s\n", loc);
strlcpy (m_loc_text, loc, sizeof(m_loc_text));
if (tt_config.ttloc_ptr[ipat].type == TTLOC_MGRS)
lerr = Convert_MGRS_To_Geodetic(loc, &lat0, &lon0);
else
lerr = Convert_USNG_To_Geodetic(loc, &lat0, &lon0);
if (lerr == 0) {
m_latitude = R2D(lat0);
m_longitude = R2D(lon0);
//dw_printf ("DEBUG: from MGRS/USNG, latitude = %.6f, longitude = %.6f\n", m_latitude, m_longitude);
}
else {
char message[300];
text_color_set(DW_COLOR_ERROR);
mgrs_error_string (lerr, message);
dw_printf ("Conversion from MGRS/USNG failed:\n%s\n\n", message);
}
m_dao[2] = e[0];
m_dao[3] = e[1];
break;
case TTLOC_MHEAD:
/* Combine prefix from configuration and digits from user. */
strlcpy (stemp, tt_config.ttloc_ptr[ipat].mhead.prefix, sizeof(stemp));
strlcat (stemp, xstr, sizeof(stemp));
if (strlen(stemp) != 4 && strlen(stemp) != 6 && strlen(stemp) != 10 && strlen(stemp) != 12) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Expected total of 4, 6, 10, or 12 digits for the Maidenhead Locator \"%s\" + \"%s\"\n",
tt_config.ttloc_ptr[ipat].mhead.prefix, xstr);
return (TT_ERROR_INVALID_MHEAD);
}
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Case MHEAD: Convert to text \"%s\".\n", stemp);
if (tt_mhead_to_text (stemp, 0, mh, sizeof(mh)) == 0) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Case MHEAD: Resulting text \"%s\".\n", mh);
strlcpy (m_loc_text, mh, sizeof(m_loc_text));
ll_from_grid_square (mh, &m_latitude, &m_longitude);
}
m_dao[2] = e[0];
m_dao[3] = e[1];
break;
case TTLOC_SATSQ:
if (strlen(xstr) != 4) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Expected 4 digits for the Satellite Square.\n");
return (TT_ERROR_INVALID_SATSQ);
}
/* Convert 4 digits to usual AA99 form, then to location. */
if (tt_satsq_to_text (xstr, 0, mh) == 0) {
strlcpy (m_loc_text, mh, sizeof(m_loc_text));
ll_from_grid_square (mh, &m_latitude, &m_longitude);
}
m_dao[2] = e[0];
m_dao[3] = e[1];
break;
case TTLOC_AMBIG:
if (strlen(xstr) != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Expected 1 digits for the position ambiguity.\n");
return (TT_ERROR_INVALID_LOC);
}
m_ambiguity = atoi(xstr);
break;
default:
assert (0);
}
return (0);
}
/* Does not match any location specification. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("Received location \"%s\" does not match any definitions.\n", e);
/* Send reject sound. */
return (TT_ERROR_INVALID_LOC);
} /* end parse_location */
/*------------------------------------------------------------------
*
* Name: find_ttloc_match
*
* Purpose: Try to match the received position report to a pattern
* defined in the configuration file.
*
* Inputs: e - An "entry" extracted from a complete
* APRStt messsage.
* In this case, it should start with "B".
*
* valstrsize - size of the outputs so we can check for buffer overflow.
*
* Outputs: xstr - All digits matching x positions in configuration.
* ystr - y
* zstr - z
* bstr - b
* dstr - d
*
* Returns: >= 0 for index into table if found.
* -1 if not found.
*
* Description:
*
*----------------------------------------------------------------*/
static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *bstr, char *dstr, size_t valstrsize)
{
int ipat; /* Index into patterns from configuration file */
int len; /* Length of pattern we are trying to match. */
int match;
char mc;
int k;
// debug dw_printf ("find_ttloc_match: e=%s\n", e);
for (ipat=0; ipat%s:t%s", src, dest, msg);
pp = ax25_from_text (raw_tt_msg, 1);
/*
* Process like a normal received frame.
* NOTE: This goes directly to application rather than
* thru the multi modem duplicate processing.
*
* Should we use a different type so it can be easily
* distinguished later?
*
* We try to capture an overall audio level here.
* Mark and space do not apply in this case.
* This currently doesn't get displayed but we might want it someday.
*/
if (pp != NULL) {
alevel = demod_get_audio_level (chan, 0);
alevel.mark = -2;
alevel.space = -2;
dlq_rec_frame (chan, -1, 0, pp, alevel, RETRY_NONE, "tt");
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not convert \"%s\" into APRS packet.\n", raw_tt_msg);
}
#endif
}
#endif
/*------------------------------------------------------------------
*
* Name: dw_run_cmd
*
* Purpose: Run a command and capture the output.
*
* Inputs: cmd - The command.
*
* oneline - 0 = Keep original line separators. Caller
* must deal with operating system differences.
* 1 = Change CR, LF, TAB to space so result
* is one line of text.
* 2 = Also remove any trailing whitespace.
*
* resultsiz - Amount of space available for result.
*
* Outputs: result - Output captured from running command.
*
* Returns: -1 for any sort of error.
* >0 for number of characters returned (= strlen(result))
*
* Description: This is currently used for running a user-specified
* script to generate a custom speech response.
*
* Future: There are potential other uses so it should probably
* be relocated to a file of other misc. utilities.
*
*----------------------------------------------------------------*/
int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz)
{
FILE *fp;
strlcpy (result, "", resultsiz);
fp = popen (cmd, "r");
if (fp != NULL) {
int remaining = (int)resultsiz;
char *pr = result;
int err;
while (remaining > 2 && fgets(pr, remaining, fp) != NULL) {
pr = result + strlen(result);
remaining = (int)resultsiz - strlen(result);
}
if ((err = pclose(fp)) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Unable to run \"%s\"\n", cmd);
// On Windows, non-existent file produces "Operation not permitted"
// Maybe we should put in a test for whether file exists.
dw_printf ("%s\n", strerror(err));
return (-1);
}
// take out any newline characters.
if (oneline) {
for (pr = result; *pr != '\0'; pr++) {
if (*pr == '\r' || *pr == '\n' || *pr == '\t') {
*pr = ' ';
}
}
if (oneline > 1) {
pr = result + strlen(result) - 1;
while (pr >= result && *pr == ' ') {
*pr = '\0';
pr--;
}
}
}
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("%s returns \"%s\"\n", cmd, result);
return (strlen(result));
}
else {
// explain_popen() would be nice but doesn't seem to be commonly available.
// We get here only if fork or pipe fails.
// The command not existing must be caught above.
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Unable to run \"%s\"\n", cmd);
dw_printf ("%s\n", strerror(errno));
return (-1);
}
} /* end dw_run_cmd */
/*------------------------------------------------------------------
*
* Name: main
*
* Purpose: Unit test for this file.
*
* Description: Run unit test like this:
*
* rm a.exe ; gcc tt_text.c -DTT_MAIN -Igeotranz aprs_tt.c latlong.o textcolor.o geotranz.a misc.a ; ./a.exe
* or
* make ttest
*
*----------------------------------------------------------------*/
#if TT_MAIN
/*
* Regression test for the parsing.
* It does not maintain any history so abbreviation will not invoke previous full call.
*/
/* Some examples are derived from http://www.aprs.org/aprstt/aprstt-coding24.txt */
static const struct {
char *toneseq; /* Tone sequence in. */
char *callsign; /* Expected results... */
char *ssid;
char *symbol;
char *freq;
char *comment;
char *lat;
char *lon;
char *dao;
} testcases[] = {
/* Callsigns & abbreviations, traditional */
{ "A9A2B42A7A7C71#", "WB4APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB4APR/7 */
{ "A27773#", "277", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */
/* Intentionally wrong - Has 6 for checksum when it should be 3. */
{ "A27776#", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Expect error message. */
/* Example in spec is wrong. checksum should be 5 in this case. */
{ "A2A7A7C71#", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Spelled suffix, overlay, checksum */
{ "A2A7A7C75#", "APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Spelled suffix, overlay, checksum */
{ "A27773#", "277", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Suffix digits, overlay, checksum */
{ "A9A2B26C7D9D71#", "WB2OSZ", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB2OSZ/7 numeric overlay */
{ "A67979#", "679", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */
{ "A9A2B26C7D9D5A9#", "WB2OSZ", "12", "JA", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB2OSZ/J letter overlay */
{ "A6795A7#", "679", "12", "JA", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */
{ "A277#", "277", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Tactical call "277" no overlay and no checksum */
/* QIKcom-2 style 10 digit call & 5 digit suffix */
{ "AC9242771558#", "WB4APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" },
{ "AC27722#", "APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" },
/* Locations */
{ "B01*A67979#", "679", "12", "7A", "", "", "12.2500", "56.2500", "!T1 !" },
{ "B988*A67979#", "679", "12", "7A", "", "", "12.5000", "56.5000", "!T88!" },
{ "B51000125*A67979#", "679", "12", "7A", "", "", "52.7907", "0.8309", "!TB5!" }, /* expect about 52.79 +0.83 */
{ "B5206070*A67979#", "679", "12", "7A", "", "", "37.9137", "-81.1366", "!TB5!" }, /* Try to get from Hilltop Tower to Archery & Target Range. */
/* Latitude comes out ok, 37.9137 -> 55.82 min. */
/* Longitude -81.1254 -> 8.20 min */
{ "B21234*A67979#", "679", "12", "7A", "", "", "12.3400", "56.1200", "!TB2!" },
{ "B533686*A67979#", "679", "12", "7A", "", "", "37.9222", "81.1143", "!TB5!" },
// TODO: should test other coordinate systems.
/* Comments */
{ "C1", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" },
{ "C2", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" },
{ "C146520", "", "12", "\\A", "146.520MHz", "", "-999999.0000", "-999999.0000", "!T !" },
{ "C7788444222550227776669660333666990122223333",
"", "12", "\\A", "", "QUICK BROWN FOX 123", "-999999.0000", "-999999.0000", "!T !" },
/* Macros */
{ "88345", "BIKE 345", "0", "/b", "", "", "12.5000", "56.5000", "!T88!" },
/* 10 digit representation for callsign & satellite grid. WB4APR near 39.5, -77 */
{ "AC9242771558*BA1819", "WB4APR", "12", "\\A", "", "", "39.5000", "-77.0000", "!TBA!" },
{ "18199242771558", "WB4APR", "12", "\\A", "", "", "39.5000", "-77.0000", "!TBA!" },
};
static int test_num;
static int error_count;
static void check_result (void)
{
char stemp[32];
text_color_set(DW_COLOR_DEBUG);
dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n",
m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_comment, m_latitude, m_longitude, m_dao);
if (strcmp(m_callsign, testcases[test_num].callsign) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for callsign.\n", testcases[test_num].callsign);
error_count++;
}
snprintf (stemp, sizeof(stemp), "%d", m_ssid);
if (strcmp(stemp, testcases[test_num].ssid) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for SSID.\n", testcases[test_num].ssid);
error_count++;
}
stemp[0] = m_symtab_or_overlay;
stemp[1] = m_symbol_code;
stemp[2] = '\0';
if (strcmp(stemp, testcases[test_num].symbol) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for Symbol.\n", testcases[test_num].symbol);
error_count++;
}
if (strcmp(m_freq, testcases[test_num].freq) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for Freq.\n", testcases[test_num].freq);
error_count++;
}
if (strcmp(m_comment, testcases[test_num].comment) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for Comment.\n", testcases[test_num].comment);
error_count++;
}
snprintf (stemp, sizeof(stemp), "%.4f", m_latitude);
if (strcmp(stemp, testcases[test_num].lat) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for Latitude.\n", testcases[test_num].lat);
error_count++;
}
snprintf (stemp, sizeof(stemp), "%.4f", m_longitude);
if (strcmp(stemp, testcases[test_num].lon) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for Longitude.\n", testcases[test_num].lon);
error_count++;
}
if (strcmp(m_dao, testcases[test_num].dao) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR: Expected \"%s\" for DAO.\n", testcases[test_num].dao);
error_count++;
}
}
int main (int argc, char *argv[])
{
aprs_tt_init (NULL);
error_count = 0;
for (test_num = 0; test_num < sizeof(testcases) / sizeof(testcases[0]); test_num++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nTest case %d: %s\n", test_num, testcases[test_num].toneseq);
aprs_tt_sequence (0, testcases[test_num].toneseq);
}
if (error_count != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n\nTEST FAILED, Total of %d errors.\n", error_count);
return (EXIT_FAILURE);
}
text_color_set(DW_COLOR_REC);
dw_printf ("\n\nAll tests passed.\n");
return (EXIT_SUCCESS);
} /* end main */
#endif
/* end aprs_tt.c */
direwolf-1.4/aprs_tt.h 0000664 0000000 0000000 00000011573 13100232553 0015017 0 ustar 00root root 0000000 0000000
/* aprs_tt.h */
#ifndef APRS_TT_H
#define APRS_TT_H 1
/*
* For holding location format specifications from config file.
* Same thing is also useful for macro definitions.
* We have exactly the same situation of looking for a pattern
* match and extracting fixed size groups of digits.
*/
struct ttloc_s {
enum { TTLOC_POINT, TTLOC_VECTOR, TTLOC_GRID, TTLOC_UTM, TTLOC_MGRS, TTLOC_USNG, TTLOC_MACRO, TTLOC_MHEAD, TTLOC_SATSQ, TTLOC_AMBIG } type;
char pattern[20]; /* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx, BAxxxx */
/* For macros, it should be all fixed digits, */
/* and the letters x, y, z. e.g. 911, xxyyyz */
union {
struct {
double lat; /* Specific locations. */
double lon;
} point;
struct {
double lat; /* For bearing/direction. */
double lon;
double scale; /* conversion to meters */
} vector;
struct {
double lat0; /* yyy all zeros. */
double lon0; /* xxx */
double lat9; /* yyy all nines. */
double lon9; /* xxx */
} grid;
struct {
double scale;
double x_offset;
double y_offset;
long lzone; /* UTM zone, should be 1-60 */
char latband; /* Latitude band if specified, otherwise space or - */
char hemi; /* UTM Hemisphere, should be 'N' or 'S'. */
} utm;
struct {
char zone[8]; /* Zone and square for USNG/MGRS */
} mgrs;
struct {
char prefix[24]; /* should be 10, 6, or 4 digits to be */
/* prepended to the received sequence. */
} mhead;
struct {
char *definition;
} macro;
};
};
/* Error codes for sending responses to user. */
#define TT_ERROR_OK 0 /* Success. */
#define TT_ERROR_D_MSG 1 /* D was first char of field. Not implemented yet. */
#define TT_ERROR_INTERNAL 2 /* Internal error. Shouldn't be here. */
#define TT_ERROR_MACRO_NOMATCH 3 /* No definition for digit sequence. */
#define TT_ERROR_BAD_CHECKSUM 4 /* Bad checksum on call. */
#define TT_ERROR_INVALID_CALL 5 /* Invalid callsign. */
#define TT_ERROR_INVALID_OBJNAME 6 /* Invalid object name. */
#define TT_ERROR_INVALID_SYMBOL 7 /* Invalid symbol specification. */
#define TT_ERROR_INVALID_LOC 8 /* Invalid location. */
#define TT_ERROR_NO_CALL 9 /* No call or object name included. */
#define TT_ERROR_INVALID_MHEAD 10 /* Invalid Maidenhead Locator. */
#define TT_ERROR_INVALID_SATSQ 11 /* Satellite square must be 4 digits. */
#define TT_ERROR_SUFFIX_NO_CALL 12 /* No known callsign for suffix. */
#define TT_ERROR_MAXP1 13 /* Number of items above. i.e. Last number plus 1. */
#if CONFIG_C /* Is this being included from config.c? */
/* Must keep in sync with above !!! */
static const char *tt_msg_id[TT_ERROR_MAXP1] = {
"OK",
"D_MSG",
"INTERNAL",
"MACRO_NOMATCH",
"BAD_CHECKSUM",
"INVALID_CALL",
"INVALID_OBJNAME",
"INVALID_SYMBOL",
"INVALID_LOC",
"NO_CALL",
"INVALID_MHEAD",
"INVALID_SATSQ",
"SUFFIX_NO_CALL"
};
#endif
/*
* Configuration options for APRStt.
*/
#define TT_MAX_XMITS 10
#define TT_MTEXT_LEN 64
struct tt_config_s {
int gateway_enabled; /* Send DTMF sequences to APRStt gateway. */
int obj_recv_chan; /* Channel to listen for tones. */
int obj_xmit_chan; /* Channel to transmit object report. */
/* -1 for none. This could happpen if we */
/* are only sending to application */
/* and/or IGate. */
int obj_send_to_app; /* send to attached application(s). */
int obj_send_to_ig; /* send to IGate. */
char obj_xmit_via[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN+1)];
/* e.g. empty or "WIDE2-1,WIDE1-1" */
int retain_time; /* Seconds to keep information about a user. */
int num_xmits; /* Number of times to transmit object report. */
int xmit_delay[TT_MAX_XMITS]; /* Delay between them. */
/* e.g. 3 seconds before first transmission then */
/* delays of 16, 32, seconds etc. in between repeats. */
struct ttloc_s *ttloc_ptr; /* Pointer to variable length array of above. */
int ttloc_size; /* Number of elements allocated. */
int ttloc_len; /* Number of elements actually used. */
double corral_lat; /* The "corral" for unknown locations. */
double corral_lon;
double corral_offset;
int corral_ambiguity;
char status[10][TT_MTEXT_LEN]; /* Up to 9 status messages. e.g. "/enroute" */
/* Position 0 means none and can't be changed. */
struct {
char method[AX25_MAX_ADDR_LEN]; /* SPEECH or MORSE[-n] */
char mtext[TT_MTEXT_LEN]; /* Message text. */
} response[TT_ERROR_MAXP1];
char ttcmd[80]; /* Command to generate custom audible response. */
};
void aprs_tt_init (struct tt_config_s *p_config);
void aprs_tt_button (int chan, char button);
#define APRSTT_LOC_DESC_LEN 32 /* Need at least 26 */
#define APRSTT_DEFAULT_SYMTAB '\\'
#define APRSTT_DEFAULT_SYMBOL 'A'
void aprs_tt_dao_to_desc (char *dao, char *str);
void aprs_tt_sequence (int chan, char *msg);
int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz);
#endif
/* end aprs_tt.h */ direwolf-1.4/atest.c 0000664 0000000 0000000 00000060714 13100232553 0014457 0 ustar 00root root 0000000 0000000
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*-------------------------------------------------------------------
*
* Name: atest.c
*
* Purpose: Test fixture for the AFSK demodulator.
*
* Inputs: Takes audio from a .WAV file insted of the audio device.
*
* Description: This can be used to test the AFSK demodulator under
* controlled and reproducable conditions for tweaking.
*
* For example
*
* (1) Download WA8LMF's TNC Test CD image file from
* http://wa8lmf.net/TNCtest/index.htm
*
* (2) Burn a physical CD.
*
* (3) "Rip" the desired tracks with Windows Media Player.
* Select .WAV file format.
*
* "Track 2" is used for most tests because that is more
* realistic for most people using the speaker output.
*
*
* Without ONE_CHAN defined:
*
* Notice that the number of packets decoded, as reported by
* this test program, will be twice the number expected because
* we are decoding the left and right audio channels separately.
*
*
* With ONE_CHAN defined:
*
* Only process one channel.
*
*--------------------------------------------------------------------*/
// #define X 1
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#define ATEST_C 1
#include "audio.h"
#include "demod.h"
#include "multi_modem.h"
#include "textcolor.h"
#include "ax25_pad.h"
#include "hdlc_rec2.h"
#include "dlq.h"
#include "ptt.h"
#include "dtime_now.h"
#if 0 /* Typical but not flexible enough. */
struct wav_header { /* .WAV file header. */
char riff[4]; /* "RIFF" */
int filesize; /* file length - 8 */
char wave[4]; /* "WAVE" */
char fmt[4]; /* "fmt " */
int fmtsize; /* 16. */
short wformattag; /* 1 for PCM. */
short nchannels; /* 1 for mono, 2 for stereo. */
int nsamplespersec; /* sampling freq, Hz. */
int navgbytespersec; /* = nblockalign*nsamplespersec. */
short nblockalign; /* = wbitspersample/8 * nchannels. */
short wbitspersample; /* 16 or 8. */
char data[4]; /* "data" */
int datasize; /* number of bytes following. */
} ;
#endif
/* 8 bit samples are unsigned bytes */
/* in range of 0 .. 255. */
/* 16 bit samples are signed short */
/* in range of -32768 .. +32767. */
static struct {
char riff[4]; /* "RIFF" */
int filesize; /* file length - 8 */
char wave[4]; /* "WAVE" */
} header;
static struct {
char id[4]; /* "LIST" or "fmt " */
int datasize;
} chunk;
static struct {
short wformattag; /* 1 for PCM. */
short nchannels; /* 1 for mono, 2 for stereo. */
int nsamplespersec; /* sampling freq, Hz. */
int navgbytespersec; /* = nblockalign*nsamplespersec. */
short nblockalign; /* = wbitspersample/8 * nchannels. */
short wbitspersample; /* 16 or 8. */
char extras[4];
} format;
static struct {
char data[4]; /* "data" */
int datasize;
} wav_data;
static FILE *fp;
static int e_o_f;
static int packets_decoded = 0;
static int decimate = 0; /* Reduce that sampling rate if set. */
/* 1 = normal, 2 = half, etc. */
static struct audio_s my_audio_config;
static int error_if_less_than = -1; /* Exit with error status if this minimum not reached. */
/* Can be used to check that performance has not decreased. */
static int error_if_greater_than = -1; /* Exit with error status if this maximum exceeded. */
/* Can be used to check that duplicate removal is not broken. */
//#define EXPERIMENT_G 1
//#define EXPERIMENT_H 1
#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
static int count[MAX_SUBCHANS];
#if EXPERIMENT_H
extern float space_gain[MAX_SUBCHANS];
#endif
#endif
static void usage (void);
static int decode_only = 0; /* Set to 0 or 1 to decode only one channel. 2 for both. */
static int sample_number = -1; /* Sample number from the file. */
/* Incremented only for channel 0. */
/* Use to print timestamp, relative to beginning */
/* of file, when frame was decoded. */
int main (int argc, char *argv[])
{
int err;
int c;
int channel;
double start_time; // Time when we started so we can measure elapsed time.
double duration; // Length of the audio file in seconds.
double elapsed; // Time it took us to process it.
#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
int j;
for (j=0; j 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);
}
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
if (my_audio_config.achan[0].baud < 600) {
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1600;
my_audio_config.achan[0].space_freq = 1800;
strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
}
else if (my_audio_config.achan[0].baud < 1800) {
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ;
my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ;
// Should default to E+ or something similar later.
}
else if (my_audio_config.achan[0].baud < 3600) {
my_audio_config.achan[0].modem_type = MODEM_QPSK;
my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
dw_printf ("Using V.26 QPSK rather than AFSK.\n");
}
else if (my_audio_config.achan[0].baud < 7200) {
my_audio_config.achan[0].modem_type = MODEM_8PSK;
my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
dw_printf ("Using V.27 8PSK rather than AFSK.\n");
}
else {
my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later.
dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
}
break;
case 'P': /* -P for modem profile. */
dw_printf ("Demodulator profile set to \"%s\"\n", optarg);
strlcpy (my_audio_config.achan[0].profiles, optarg, sizeof(my_audio_config.achan[0].profiles));
break;
case 'D': /* -D reduce sampling rate for lower CPU usage. */
decimate = atoi(optarg);
dw_printf ("Divide audio sample rate by %d\n", decimate);
if (decimate < 1 || decimate > 8) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Unreasonable value for -D.\n");
exit (EXIT_FAILURE);
}
dw_printf ("Divide audio sample rate by %d\n", decimate);
my_audio_config.achan[0].decimate = decimate;
break;
case 'F': /* -D set "fix bits" level. */
my_audio_config.achan[0].fix_bits = atoi(optarg);
if (my_audio_config.achan[0].fix_bits < RETRY_NONE || my_audio_config.achan[0].fix_bits >= RETRY_MAX) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid Fix Bits level.\n");
exit (EXIT_FAILURE);
}
break;
case 'L': /* -L error if less than this number decoded. */
error_if_less_than = atoi(optarg);
break;
case 'G': /* -G error if greater than this number decoded. */
error_if_greater_than = atoi(optarg);
break;
case '0': /* channel 0, left from stereo */
decode_only = 0;
break;
case '1': /* channel 1, right from stereo */
decode_only = 1;
break;
case '2': /* decode both from stereo */
decode_only = 2;
break;
case '?':
/* Unknown option message was already printed. */
usage ();
break;
default:
/* Should not be here. */
text_color_set(DW_COLOR_ERROR);
dw_printf("?? getopt returned character code 0%o ??\n", c);
usage ();
}
}
memcpy (&my_audio_config.achan[1], &my_audio_config.achan[0], sizeof(my_audio_config.achan[0]));
if (optind >= argc) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Specify .WAV file name on command line.\n");
usage ();
}
fp = fopen(argv[optind], "rb");
if (fp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't open file for read: %s\n", argv[optind]);
//perror ("more info?");
exit (EXIT_FAILURE);
}
start_time = dtime_now();
/*
* Read the file header.
* Doesn't handle all possible cases but good enough for our purposes.
*/
err= fread (&header, (size_t)12, (size_t)1, fp);
(void)(err);
if (strncmp(header.riff, "RIFF", 4) != 0 || strncmp(header.wave, "WAVE", 4) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("This is not a .WAV format file.\n");
exit (EXIT_FAILURE);
}
err = fread (&chunk, (size_t)8, (size_t)1, fp);
if (strncmp(chunk.id, "LIST", 4) == 0) {
err = fseek (fp, (long)chunk.datasize, SEEK_CUR);
err = fread (&chunk, (size_t)8, (size_t)1, fp);
}
if (strncmp(chunk.id, "fmt ", 4) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("WAV file error: Found \"%4.4s\" where \"fmt \" was expected.\n", chunk.id);
exit(EXIT_FAILURE);
}
if (chunk.datasize != 16 && chunk.datasize != 18) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("WAV file error: Need fmt chunk datasize of 16 or 18. Found %d.\n", chunk.datasize);
exit(EXIT_FAILURE);
}
err = fread (&format, (size_t)chunk.datasize, (size_t)1, fp);
err = fread (&wav_data, (size_t)8, (size_t)1, fp);
if (strncmp(wav_data.data, "data", 4) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("WAV file error: Found \"%4.4s\" where \"data\" was expected.\n", wav_data.data);
exit(EXIT_FAILURE);
}
if (format.wformattag != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Sorry, I only understand audio format 1 (PCM). This file has %d.\n", format.wformattag);
exit (EXIT_FAILURE);
}
if (format.nchannels != 1 && format.nchannels != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Sorry, I only understand 1 or 2 channels. This file has %d.\n", format.nchannels);
exit (EXIT_FAILURE);
}
if (format.wbitspersample != 8 && format.wbitspersample != 16) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Sorry, I only understand 8 or 16 bits per sample. This file has %d.\n", format.wbitspersample);
exit (EXIT_FAILURE);
}
my_audio_config.adev[0].samples_per_sec = format.nsamplespersec;
my_audio_config.adev[0].bits_per_sample = format.wbitspersample;
my_audio_config.adev[0].num_channels = format.nchannels;
my_audio_config.achan[0].valid = 1;
if (format.nchannels == 2) my_audio_config.achan[1].valid = 1;
text_color_set(DW_COLOR_INFO);
dw_printf ("%d samples per second. %d bits per sample. %d audio channels.\n",
my_audio_config.adev[0].samples_per_sec,
my_audio_config.adev[0].bits_per_sample,
my_audio_config.adev[0].num_channels);
duration = (double) wav_data.datasize /
((my_audio_config.adev[0].bits_per_sample / 8) * my_audio_config.adev[0].num_channels * my_audio_config.adev[0].samples_per_sec);
dw_printf ("%d audio bytes in file. Duration = %.1f seconds.\n",
(int)(wav_data.datasize),
duration);
dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits);
/*
* Initialize the AFSK demodulator and HDLC decoder.
*/
multi_modem_init (&my_audio_config);
e_o_f = 0;
while ( ! e_o_f)
{
int audio_sample;
int c;
for (c=0; c= 256 * 256) {
e_o_f = 1;
continue;
}
if (c == 0) sample_number++;
if (decode_only == 0 && c != 0) continue;
if (decode_only == 1 && c != 1) continue;
multi_modem_process_sample(c,audio_sample);
}
/* When a complete frame is accumulated, */
/* process_rec_frame, below, is called. */
}
text_color_set(DW_COLOR_INFO);
dw_printf ("\n\n");
#if EXPERIMENT_G
for (j=0; j error_if_greater_than) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than);
exit (EXIT_FAILURE);
}
exit (EXIT_SUCCESS);
}
/*
* Simulate sample from the audio device.
*/
int audio_get (int a)
{
int ch;
if (wav_data.datasize <= 0) {
e_o_f = 1;
return (-1);
}
ch = getc(fp);
wav_data.datasize--;
if (ch < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Unexpected end of file.\n");
e_o_f = 1;
}
return (ch);
}
/*
* Rather than queuing up frames with bad FCS,
* try to fix them immediately.
*/
void rdq_append (rrbb_t rrbb)
{
int chan, subchan, slice;
alevel_t alevel;
chan = rrbb_get_chan(rrbb);
subchan = rrbb_get_subchan(rrbb);
slice = rrbb_get_slice(rrbb);
alevel = rrbb_get_audio_level(rrbb);
hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, slice, alevel);
rrbb_delete (rrbb);
}
/*
* This is called when we have a good frame.
*/
void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum)
{
char stemp[500];
unsigned char *pinfo;
int info_len;
int h;
char heard[AX25_MAX_ADDR_LEN];
char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE];
packets_decoded++;
ax25_format_addrs (pp, stemp);
info_len = ax25_get_info (pp, &pinfo);
/* Print so we can see what is going on. */
//TODO: quiet option - suppress packet printing, only the count at the end.
#if 1
/* Display audio input level. */
/* Who are we hearing? Original station or digipeater? */
if (ax25_get_num_addr(pp) == 0) {
/* Not AX.25. No station to display below. */
h = -1;
strlcpy (heard, "", sizeof(heard));
}
else {
h = ax25_get_heard(pp);
ax25_get_addr_with_ssid(pp, h, heard);
}
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
dw_printf("DECODED[%d] ", packets_decoded );
/* Insert time stamp relative to start of file. */
double sec = (double)sample_number / my_audio_config.adev[0].samples_per_sec;
int min = (int)(sec / 60.);
sec -= min * 60;
dw_printf ("%d:%07.4f ", min, sec);
if (h != AX25_SOURCE) {
dw_printf ("Digipeater ");
}
ax25_alevel_to_text (alevel, alevel_text);
if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) {
dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum);
}
else {
dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], spectrum);
}
#endif
//#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
// int j;
//
// for (j=0; j 1 && my_audio_config.achan[chan].num_slicers == 1) {
dw_printf ("[%d.%d] ", chan, subchan);
}
else if (my_audio_config.achan[chan].num_subchan == 1 && my_audio_config.achan[chan].num_slicers > 1) {
dw_printf ("[%d.%d] ", chan, slice);
}
else if (my_audio_config.achan[chan].num_subchan > 1 && my_audio_config.achan[chan].num_slicers > 1) {
dw_printf ("[%d.%d.%d] ", chan, subchan, slice);
}
else {
dw_printf ("[%d] ", chan);
}
dw_printf ("%s", stemp); /* stations followed by : */
ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n");
#if 1 // temp experiment TODO: remove this.
#include "decode_aprs.h"
#include "log.h"
if (ax25_is_aprs(pp)) {
decode_aprs_t A;
decode_aprs (&A, pp, 0);
// Temp experiment to see how different systems set the RR bits in the source and destination.
// log_rr_bits (&A, pp);
}
#endif
ax25_delete (pp);
} /* end fake dlq_append */
void ptt_set (int ot, int chan, int ptt_signal)
{
return;
}
int get_input (int it, int chan)
{
return -1;
}
static void usage (void) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("atest is a test application which decodes AX.25 frames from an audio\n");
dw_printf ("recording. This provides an easy way to test Dire Wolf decoding\n");
dw_printf ("performance much quicker than normal real-time. \n");
dw_printf ("\n");
dw_printf ("usage:\n");
dw_printf ("\n");
dw_printf (" atest [ options ] wav-file-in\n");
dw_printf ("\n");
dw_printf (" -B n Bits/second for data. Proper modem automatically selected for speed.\n");
dw_printf (" 300 baud uses 1600/1800 Hz AFSK.\n");
dw_printf (" 1200 (default) baud uses 1200/2200 Hz AFSK.\n");
dw_printf (" 9600 baud uses K9NG/G2RUH standard.\n");
dw_printf ("\n");
dw_printf (" -D n Divide audio sample rate by n.\n");
dw_printf ("\n");
dw_printf (" -F n Amount of effort to try fixing frames with an invalid CRC. \n");
dw_printf (" 0 (default) = consider only correct frames. \n");
dw_printf (" 1 = Try to fix only a single bit. \n");
dw_printf (" more = Try modifying more bits to get a good CRC.\n");
dw_printf ("\n");
dw_printf (" -P m Select the demodulator type such as A, B, C, D (default for 300 baud),\n");
dw_printf (" E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+.\n");
dw_printf ("\n");
dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n");
dw_printf (" -1 use channel 1 (right) of stereo audio.\n");
dw_printf (" -2 decode both channels of stereo audio.\n");
dw_printf ("\n");
dw_printf (" wav-file-in is a WAV format audio file.\n");
dw_printf ("\n");
dw_printf ("Examples:\n");
dw_printf ("\n");
dw_printf (" gen_packets -o test1.wav\n");
dw_printf (" atest test1.wav\n");
dw_printf ("\n");
dw_printf (" gen_packets -B 300 -o test3.wav\n");
dw_printf (" atest -B 300 test3.wav\n");
dw_printf ("\n");
dw_printf (" gen_packets -B 9600 -o test9.wav\n");
dw_printf (" atest -B 9600 test9.wav\n");
dw_printf ("\n");
dw_printf (" This generates and decodes 3 test files with 1200, 300, and 9600\n");
dw_printf (" bits per second.\n");
dw_printf ("\n");
dw_printf (" atest 02_Track_2.wav\n");
dw_printf (" atest -P C+ 02_Track_2.wav\n");
dw_printf (" atest -F 1 02_Track_2.wav\n");
dw_printf (" atest -P C+ -F 1 02_Track_2.wav\n");
dw_printf ("\n");
dw_printf (" Try different combinations of options to find the best decoding\n");
dw_printf (" performance.\n");
exit (1);
}
/* end atest.c */
direwolf-1.4/audio.c 0000664 0000000 0000000 00000112004 13100232553 0014426 0 ustar 00root root 0000000 0000000
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: audio.c
*
* Purpose: Interface to audio device commonly called a "sound card" for
* historical reasons.
*
* This version is for Linux and Cygwin.
*
* Two different types of sound interfaces are supported:
*
* * OSS - For Cygwin or Linux versions with /dev/dsp.
*
* * ALSA - For Linux versions without /dev/dsp.
* In this case, define preprocessor symbol USE_ALSA.
*
* References: Some tips on on using Linux sound devices.
*
* http://www.oreilly.de/catalog/multilinux/excerpt/ch14-05.htm
* http://cygwin.com/ml/cygwin-patches/2004-q1/msg00116/devdsp.c
* http://manuals.opensound.com/developer/fulldup.c.html
*
* "Introduction to Sound Programming with ALSA"
* http://www.linuxjournal.com/article/6735?page=0,1
*
* http://www.alsa-project.org/main/index.php/Asoundrc
*
* Credits: Release 1.0: Fabrice FAURE contributed code for the SDR UDP interface.
*
* Discussion here: http://gqrx.dk/doc/streaming-audio-over-udp
*
* Release 1.1: Gabor Berczi provided fixes for the OSS code
* which had fallen into decay.
*
* Major Revisions:
*
* 1.2 - Add ability to use more than one audio device.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if USE_ALSA
#include
#else
#include
#ifdef __OpenBSD__
#include
#else
#include
#endif
#endif
#include "audio.h"
#include "audio_stats.h"
#include "textcolor.h"
#include "dtime_now.h"
#include "demod.h" /* for alevel_t & demod_get_audio_level() */
/* Audio configuration. */
static struct audio_s *save_audio_config_p;
/* Current state for each of the audio devices. */
static struct adev_s {
#if USE_ALSA
snd_pcm_t *audio_in_handle;
snd_pcm_t *audio_out_handle;
int bytes_per_frame; /* number of bytes for a sample from all channels. */
/* e.g. 4 for stereo 16 bit. */
#else
int oss_audio_device_fd; /* Single device, both directions. */
#endif
int inbuf_size_in_bytes; /* number of bytes allocated */
unsigned char *inbuf_ptr;
int inbuf_len; /* number byte of actual data available. */
int inbuf_next; /* index of next to remove. */
int outbuf_size_in_bytes;
unsigned char *outbuf_ptr;
int outbuf_len;
enum audio_in_type_e g_audio_in_type;
int udp_sock; /* UDP socket for receiving data */
} adev[MAX_ADEVS];
// Originally 40. Version 1.2, try 10 for lower latency.
#define ONE_BUF_TIME 10
#if USE_ALSA
static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *name, char *dir);
//static void alsa_select_device (char *pick_dev, int direction, char *result);
#else
static int set_oss_params (int a, int fd, struct audio_s *pa);
#endif
#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
static int calcbufsize(int rate, int chans, int bits)
{
int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000;
int size2 = roundup1k(size1);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
rate, chans, bits, size1, size2);
#endif
return (size2);
}
/*------------------------------------------------------------------
*
* Name: audio_open
*
* Purpose: Open the digital audio device.
* For "OSS", the device name is typically "/dev/dsp".
* For "ALSA", it's a lot more complicated. See User Guide.
*
* New in version 1.0, we recognize "udp:" optionally
* followed by a port number.
*
* Inputs: pa - Address of structure of type audio_s.
*
* Using a structure, rather than separate arguments
* seemed to make sense because we often pass around
* the same set of parameters various places.
*
* The fields that we care about are:
* num_channels
* samples_per_sec
* bits_per_sample
* If zero, reasonable defaults will be provided.
*
* The device names are in adevice_in and adevice_out.
* - For "OSS", the device name is typically "/dev/dsp".
* - For "ALSA", the device names are hw:c,d
* where c is the "card" (for historical purposes)
* and d is the "device" within the "card."
*
*
* Outputs: pa - The ACTUAL values are returned here.
*
* These might not be exactly the same as what was requested.
*
* Example: ask for stereo, 16 bits, 22050 per second.
* An ordinary desktop/laptop PC should be able to handle this.
* However, some other sort of smaller device might be
* more restrictive in its capabilities.
* It might say, the best I can do is mono, 8 bit, 8000/sec.
*
* The sofware modem must use this ACTUAL information
* that the device is supplying, that could be different
* than what the user specified.
*
* Returns: 0 for success, -1 for failure.
*
*
*----------------------------------------------------------------*/
int audio_open (struct audio_s *pa)
{
int err;
int chan;
int a;
char audio_in_name[30];
char audio_out_name[30];
save_audio_config_p = pa;
memset (adev, 0, sizeof(adev));
for (a=0; aadev[a].num_channels == 0)
pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS;
if (pa->adev[a].samples_per_sec == 0)
pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
if (pa->adev[a].bits_per_sample == 0)
pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
for (chan=0; chanachan[chan].mark_freq == 0)
pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ;
if (pa->achan[chan].space_freq == 0)
pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ;
if (pa->achan[chan].baud == 0)
pa->achan[chan].baud = DEFAULT_BAUD;
if (pa->achan[chan].num_subchan == 0)
pa->achan[chan].num_subchan = 1;
}
}
/*
* Open audio device(s).
*/
for (a=0; aadev[a].defined) {
adev[a].inbuf_size_in_bytes = 0;
adev[a].inbuf_ptr = NULL;
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
adev[a].outbuf_size_in_bytes = 0;
adev[a].outbuf_ptr = NULL;
adev[a].outbuf_len = 0;
/*
* Determine the type of audio input.
*/
adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN;
/* Change "-" to stdin for readability. */
strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
}
if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
/* Supply default port if none specified. */
if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
}
}
/* Let user know what is going on. */
/* If not specified, the device names should be "default". */
strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name));
strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name));
char ctemp[40];
if (pa->adev[a].num_channels == 2) {
snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
}
else {
snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a));
}
text_color_set(DW_COLOR_INFO);
if (strcmp(audio_in_name,audio_out_name) == 0) {
dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp);
}
else {
dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp);
dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp);
}
/*
* Now attempt actual opens.
*/
/*
* Input device.
*/
switch (adev[a].g_audio_in_type) {
/*
* Soundcard - ALSA.
*/
case AUDIO_IN_TYPE_SOUNDCARD:
#if USE_ALSA
err = snd_pcm_open (&(adev[a].audio_in_handle), audio_in_name, SND_PCM_STREAM_CAPTURE, 0);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device %s for input\n%s\n",
audio_in_name, snd_strerror(err));
return (-1);
}
adev[a].inbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_in_handle, pa, audio_in_name, "input");
#else // OSS
adev[a].oss_audio_device_fd = open (pa->adev[a].adevice_in, O_RDWR);
if (adev[a].oss_audio_device_fd < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%s:\n", pa->adev[a].adevice_in);
// snprintf (message, sizeof(message), "Could not open audio device %s", pa->adev[a].adevice_in);
// perror (message);
return (-1);
}
adev[a].outbuf_size_in_bytes = adev[a].inbuf_size_in_bytes = set_oss_params (a, adev[a].oss_audio_device_fd, pa);
if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
return (-1);
}
#endif
break;
/*
* UDP.
*/
case AUDIO_IN_TYPE_SDR_UDP:
//Create socket and bind socket
{
struct sockaddr_in si_me;
//int slen=sizeof(si_me);
//int data_size = 0;
//Create UDP Socket
if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't create socket, errno %d\n", errno);
return -1;
}
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons((short)atoi(audio_in_name+4));
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
//Bind to the socket
if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't bind socket, errno %d\n", errno);
return -1;
}
}
adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN;
break;
/*
* stdin.
*/
case AUDIO_IN_TYPE_STDIN:
/* Do we need to adjust any properties of stdin? */
adev[a].inbuf_size_in_bytes = 1024;
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error, invalid audio_in_type\n");
return (-1);
}
/*
* Output device. Only "soundcard" is supported at this time.
*/
#if USE_ALSA
err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device %s for output\n%s\n",
audio_out_name, snd_strerror(err));
return (-1);
}
adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output");
if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
return (-1);
}
#endif
/*
* Finally allocate buffer for each direction.
*/
adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes);
assert (adev[a].inbuf_ptr != NULL);
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes);
assert (adev[a].outbuf_ptr != NULL);
adev[a].outbuf_len = 0;
} /* end of audio device defined */
} /* end of for each audio device */
return (0);
} /* end audio_open */
#if USE_ALSA
/*
* Set parameters for sound card.
*
* See ?? for details.
*/
/*
* Terminology:
* Sample - for one channel. e.g. 2 bytes for 16 bit.
* Frame - one sample for all channels. e.g. 4 bytes for 16 bit stereo
* Period - size of one transfer.
*/
static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *devname, char *inout)
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t fpp; /* Frames per period. */
unsigned int val;
int dir;
int err;
int buf_size_in_bytes; /* result, number of bytes per transfer. */
err = snd_pcm_hw_params_malloc (&hw_params);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not alloc hw param structure.\n%s\n",
snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
err = snd_pcm_hw_params_any (handle, hw_params);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not init hw param structure.\n%s\n",
snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
/* Interleaved data: L, R, L, R, ... */
err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not set interleaved mode.\n%s\n",
snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
/* Signed 16 bit little endian or unsigned 8 bit. */
err = snd_pcm_hw_params_set_format (handle, hw_params,
pa->adev[a].bits_per_sample == 8 ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16_LE);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not set bits per sample.\n%s\n",
snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
/* Number of audio channels. */
err = snd_pcm_hw_params_set_channels (handle, hw_params, pa->adev[a].num_channels);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not set number of audio channels.\n%s\n",
snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
/* Audio sample rate. */
val = pa->adev[a].samples_per_sec;
dir = 0;
err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &val, &dir);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not set audio sample rate.\n%s\n",
snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
if (val != pa->adev[a].samples_per_sec) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Asked for %d samples/sec but got %d.\n",
pa->adev[a].samples_per_sec, val);
dw_printf ("for %s %s.\n", devname, inout);
pa->adev[a].samples_per_sec = val;
}
/* Original: */
/* Guessed around 20 reads/sec might be good. */
/* Period too long = too much latency. */
/* Period too short = more overhead of many small transfers. */
/* fpp = pa->adev[a].samples_per_sec / 20; */
/* The suggested period size was 2205 frames. */
/* I thought the later "...set_period_size_near" might adjust it to be */
/* some more optimal nearby value based hardware buffer sizes but */
/* that didn't happen. We ended up with a buffer size of 4410 bytes. */
/* In version 1.2, let's take a different approach. */
/* Reduce the latency and round up to a multiple of 1 Kbyte. */
/* For the typical case of 44100 sample rate, 1 channel, 16 bits, we calculate */
/* a buffer size of 882 and round it up to 1k. This results in 512 frames per period. */
/* A period comes out to be about 80 periods per second or about 12.5 mSec each. */
buf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
#if __arm__
/* Ugly hack for RPi. */
/* Reducing buffer size is fine for input but not so good for output. */
if (*inout == 'o') {
buf_size_in_bytes = buf_size_in_bytes * 4;
}
#endif
fpp = buf_size_in_bytes / (pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("suggest period size of %d frames\n", (int)fpp);
#endif
dir = 0;
err = snd_pcm_hw_params_set_period_size_near (handle, hw_params, &fpp, &dir);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not set period size\n%s\n", snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
err = snd_pcm_hw_params (handle, hw_params);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not set hw params\n%s\n", snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
/* Driver might not like our suggested period size */
/* and might have another idea. */
err = snd_pcm_hw_params_get_period_size (hw_params, &fpp, NULL);
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not get audio period size.\n%s\n", snd_strerror(err));
dw_printf ("for %s %s.\n", devname, inout);
return (-1);
}
snd_pcm_hw_params_free (hw_params);
/* A "frame" is one sample for all channels. */
/* The read and write use units of frames, not bytes. */
adev[a].bytes_per_frame = snd_pcm_frames_to_bytes (handle, 1);
assert (adev[a].bytes_per_frame == pa->adev[a].num_channels * pa->adev[a].bits_per_sample / 8);
buf_size_in_bytes = fpp * adev[a].bytes_per_frame;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio buffer size = %d (bytes per frame) x %d (frames per period) = %d \n", adev[a].bytes_per_frame, (int)fpp, buf_size_in_bytes);
#endif
/* Version 1.3 - after a report of this situation for Mac OSX version. */
if (buf_size_in_bytes < 256 || buf_size_in_bytes > 32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", buf_size_in_bytes);
dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("This might be caused by unusual audio device configuration values.\n");
buf_size_in_bytes = 2048;
dw_printf ("Using %d to attempt recovery.\n", buf_size_in_bytes);
}
return (buf_size_in_bytes);
} /* end alsa_set_params */
#else
/*
* Set parameters for sound card. (OSS only)
*
* See /usr/include/sys/soundcard.h for details.
*/
static int set_oss_params (int a, int fd, struct audio_s *pa)
{
int err;
int devcaps;
int asked_for;
char message[100];
int ossbuf_size_in_bytes;
err = ioctl (fd, SNDCTL_DSP_CHANNELS, &(pa->adev[a].num_channels));
if (err == -1) {
text_color_set(DW_COLOR_ERROR);
perror("Not able to set audio device number of channels");
return (-1);
}
asked_for = pa->adev[a].samples_per_sec;
err = ioctl (fd, SNDCTL_DSP_SPEED, &(pa->adev[a].samples_per_sec));
if (err == -1) {
text_color_set(DW_COLOR_ERROR);
perror("Not able to set audio device sample rate");
return (-1);
}
if (pa->adev[a].samples_per_sec != asked_for) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Asked for %d samples/sec but actually using %d.\n",
asked_for, pa->adev[a].samples_per_sec);
}
/* This is actually a bit mask but it happens that */
/* 0x8 is unsigned 8 bit samples and */
/* 0x10 is signed 16 bit little endian. */
err = ioctl (fd, SNDCTL_DSP_SETFMT, &(pa->adev[a].bits_per_sample));
if (err == -1) {
text_color_set(DW_COLOR_ERROR);
perror("Not able to set audio device sample size");
return (-1);
}
/*
* Determine capabilities.
*/
err = ioctl (fd, SNDCTL_DSP_GETCAPS, &devcaps);
if (err == -1) {
text_color_set(DW_COLOR_ERROR);
perror("Not able to get audio device capabilities");
// Is this fatal? // return (-1);
}
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_open(): devcaps = %08x\n", devcaps);
if (devcaps & DSP_CAP_DUPLEX) dw_printf ("Full duplex record/playback.\n");
if (devcaps & DSP_CAP_BATCH) dw_printf ("Device has some kind of internal buffers which may cause delays.\n");
if (devcaps & ~ (DSP_CAP_DUPLEX | DSP_CAP_BATCH)) dw_printf ("Others...\n");
#endif
if (!(devcaps & DSP_CAP_DUPLEX)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio device does not support full duplex\n");
// Do we care? // return (-1);
}
err = ioctl (fd, SNDCTL_DSP_SETDUPLEX, NULL);
if (err == -1) {
// text_color_set(DW_COLOR_ERROR);
// perror("Not able to set audio full duplex mode");
// Unfortunate but not a disaster.
}
/*
* Get preferred block size.
* Presumably this will provide the most efficient transfer.
*
* In my particular situation, this turned out to be
* 2816 for 11025 Hz 16 bit mono
* 5568 for 11025 Hz 16 bit stereo
* 11072 for 44100 Hz 16 bit mono
*
* This was long ago under different conditions.
* Should study this again some day.
*
* Your milage may vary.
*/
err = ioctl (fd, SNDCTL_DSP_GETBLKSIZE, &ossbuf_size_in_bytes);
if (err == -1) {
text_color_set(DW_COLOR_ERROR);
perror("Not able to get audio block size");
ossbuf_size_in_bytes = 2048; /* pick something reasonable */
}
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_open(): suggestd block size is %d\n", ossbuf_size_in_bytes);
#endif
/*
* That's 1/8 of a second which seems rather long if we want to
* respond quickly.
*/
ossbuf_size_in_bytes = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_open(): using block size of %d\n", ossbuf_size_in_bytes);
#endif
#if 0
/* Original - dies without good explanation. */
assert (ossbuf_size_in_bytes >= 256 && ossbuf_size_in_bytes <= 32768);
#else
/* Version 1.3 - after a report of this situation for Mac OSX version. */
if (ossbuf_size_in_bytes < 256 || ossbuf_size_in_bytes > 32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", ossbuf_size_in_bytes);
dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("This might be caused by unusual audio device configuration values.\n");
ossbuf_size_in_bytes = 2048;
dw_printf ("Using %d to attempt recovery.\n", ossbuf_size_in_bytes);
}
#endif
return (ossbuf_size_in_bytes);
} /* end set_oss_params */
#endif
/*------------------------------------------------------------------
*
* Name: audio_get
*
* Purpose: Get one byte from the audio device.
*
* Inputs: a - Our number for audio device.
*
* Returns: 0 - 255 for a valid sample.
* -1 for any type of error.
*
* Description: The caller must deal with the details of mono/stereo
* and number of bytes per sample.
*
* This will wait if no data is currently available.
*
*----------------------------------------------------------------*/
// Use hot attribute for all functions called for every audio sample.
__attribute__((hot))
int audio_get (int a)
{
int n;
int retries = 0;
#if STATISTICS
/* Gather numbers for read from audio device. */
#define duration 100 /* report every 100 seconds. */
static time_t last_time[MAX_ADEVS];
time_t this_time[MAX_ADEVS];
static int sample_count[MAX_ADEVS];
static int error_count[MAX_ADEVS];
#endif
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get():\n");
#endif
assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768);
switch (adev[a].g_audio_in_type) {
/*
* Soundcard - ALSA
*/
case AUDIO_IN_TYPE_SOUNDCARD:
#if USE_ALSA
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
assert (adev[a].audio_in_handle != NULL);
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
#endif
n = snd_pcm_readi (adev[a].audio_in_handle, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): readi asked for %d and got %d frames\n",
adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n);
#endif
if (n > 0) {
/* Success */
adev[a].inbuf_len = n * adev[a].bytes_per_frame; /* convert to number of bytes */
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
n,
save_audio_config_p->statistics_interval);
}
else if (n == 0) {
/* Didn't expect this, but it's not a problem. */
/* Wait a little while and try again. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio input got zero bytes: %s\n", snd_strerror(n));
SLEEP_MS(10);
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
}
else {
/* Error */
// TODO: Needs more study and testing.
// TODO: print n. should snd_strerror use n or errno?
// Audio input device error: Unknown error
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio input device %d error: %s\n", a, snd_strerror(n));
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
/* Try to recover a few times and eventually give up. */
if (++retries > 10) {
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
return (-1);
}
if (n == -EPIPE) {
/* EPIPE means overrun */
snd_pcm_recover (adev[a].audio_in_handle, n, 1);
}
else {
/* Could be some temporary condition. */
/* Wait a little then try again. */
/* Sometimes I get "Resource temporarily available" */
/* when the Update Manager decides to run. */
SLEEP_MS (250);
snd_pcm_recover (adev[a].audio_in_handle, n, 1);
}
}
}
#else /* end ALSA, begin OSS */
/* Fixed in 1.2. This was formerly outside of the switch */
/* so the OSS version did not process stdin or UDP. */
while (adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD && adev[a].inbuf_next >= adev[a].inbuf_len) {
assert (adev[a].oss_audio_device_fd > 0);
n = read (adev[a].oss_audio_device_fd, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes);
//text_color_set(DW_COLOR_DEBUG);
// dw_printf ("audio_get(): read %d returns %d\n", adev[a].inbuf_size_in_bytes, n);
if (n < 0) {
text_color_set(DW_COLOR_ERROR);
perror("Can't read from audio device");
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
return (-1);
}
adev[a].inbuf_len = n;
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
n / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
}
#endif /* USE_ALSA */
break;
/*
* UDP.
*/
case AUDIO_IN_TYPE_SDR_UDP:
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
int res;
assert (adev[a].udp_sock > 0);
res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
if (res < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't read from udp socket, res=%d", res);
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
return (-1);
}
adev[a].inbuf_len = res;
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
}
break;
/*
* stdin.
*/
case AUDIO_IN_TYPE_STDIN:
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
//int ch, res,i;
int res;
res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
if (res <= 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nEnd of file on stdin. Exiting.\n");
exit (0);
}
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
adev[a].inbuf_len = res;
adev[a].inbuf_next = 0;
}
break;
}
if (adev[a].inbuf_next < adev[a].inbuf_len)
n = adev[a].inbuf_ptr[adev[a].inbuf_next++];
//No data to read, avoid reading outside buffer
else
n = 0;
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): returns %d\n", n);
#endif
return (n);
} /* end audio_get */
/*------------------------------------------------------------------
*
* Name: audio_put
*
* Purpose: Send one byte to the audio device.
*
* Inputs: a
*
* c - One byte in range of 0 - 255.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
* Description: The caller must deal with the details of mono/stereo
* and number of bytes per sample.
*
* See Also: audio_flush
* audio_wait
*
*----------------------------------------------------------------*/
int audio_put (int a, int c)
{
/* Should never be full at this point. */
assert (adev[a].outbuf_len < adev[a].outbuf_size_in_bytes);
adev[a].outbuf_ptr[adev[a].outbuf_len++] = c;
if (adev[a].outbuf_len == adev[a].outbuf_size_in_bytes) {
return (audio_flush(a));
}
return (0);
} /* end audio_put */
/*------------------------------------------------------------------
*
* Name: audio_flush
*
* Purpose: Push out any partially filled output buffer.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
* See Also: audio_flush
* audio_wait
*
*----------------------------------------------------------------*/
int audio_flush (int a)
{
#if USE_ALSA
int k;
unsigned char *psound;
int retries = 10;
snd_pcm_status_t *status;
assert (adev[a].audio_out_handle != NULL);
/*
* Trying to set the automatic start threshold didn't have the desired
* effect. After the first transmitted packet, they are saved up
* for a few minutes and then all come out together.
*
* "Prepare" it if not already in the running state.
* We stop it at the end of each transmitted packet.
*/
snd_pcm_status_alloca(&status);
k = snd_pcm_status (adev[a].audio_out_handle, status);
if (k != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k));
}
if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Audio output state = %d. Try to start.\n", k);
k = snd_pcm_prepare (adev[a].audio_out_handle);
if (k != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output start error.\n%s\n", snd_strerror(k));
}
}
psound = adev[a].outbuf_ptr;
while (retries-- > 0) {
k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame);
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n",
adev[a].outbuf_len / adev[a].bytes_per_frame, k);
fflush (stdout);
#endif
if (k == -EPIPE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output data underrun.\n");
/* No problemo. Recover and go around again. */
snd_pcm_recover (adev[a].audio_out_handle, k, 1);
}
else if (k < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio write error: %s\n", snd_strerror(k));
/* Some other error condition. */
/* Try again. What do we have to lose? */
snd_pcm_recover (adev[a].audio_out_handle, k, 1);
}
else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio write took %d frames rather than %d.\n",
k, adev[a].outbuf_len / adev[a].bytes_per_frame);
/* Go around again with the rest of it. */
psound += k * adev[a].bytes_per_frame;
adev[a].outbuf_len -= k * adev[a].bytes_per_frame;
}
else {
/* Success! */
adev[a].outbuf_len = 0;
return (0);
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio write error retry count exceeded.\n");
adev[a].outbuf_len = 0;
return (-1);
#else /* OSS */
int k;
unsigned char *ptr;
int len;
ptr = adev[a].outbuf_ptr;
len = adev[a].outbuf_len;
while (len > 0) {
assert (adev[a].oss_audio_device_fd > 0);
k = write (adev[a].oss_audio_device_fd, ptr, len);
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_flush(): write %d returns %d\n", len, k);
fflush (stdout);
#endif
if (k < 0) {
text_color_set(DW_COLOR_ERROR);
perror("Can't write to audio device");
adev[a].outbuf_len = 0;
return (-1);
}
if (k < len) {
/* presumably full but didn't block. */
usleep (10000);
}
ptr += k;
len -= k;
}
adev[a].outbuf_len = 0;
return (0);
#endif
} /* end audio_flush */
/*------------------------------------------------------------------
*
* Name: audio_wait
*
* Purpose: Finish up audio output before turning PTT off.
*
* Inputs: a - Index for audio device (not channel!)
*
* Returns: None.
*
* Description: Flush out any partially filled audio output buffer.
* Wait until all the queued up audio out has been played.
* Take any other necessary actions to stop audio output.
*
* In an ideal world:
*
* We would like to ask the hardware when all the queued
* up sound has actually come out the speaker.
*
* In reality:
*
* This has been found to be less than reliable in practice.
*
* Caller does the following:
*
* (1) Make note of when PTT is turned on.
* (2) Calculate how long it will take to transmit the
* frame including TXDELAY, frame (including
* "flags", data, FCS and bit stuffing), and TXTAIL.
* (3) Call this function, which might or might not wait long enough.
* (4) Add (1) and (2) resulting in when PTT should be turned off.
* (5) Take difference between current time and desired PPT off time
* and wait for additoinal time if required.
*
*----------------------------------------------------------------*/
void audio_wait (int a)
{
audio_flush (a);
#if USE_ALSA
/* For playback, this should wait for all pending frames */
/* to be played and then stop. */
snd_pcm_drain (adev[a].audio_out_handle);
/*
* When this was first implemented, I observed:
*
* "Experimentation reveals that snd_pcm_drain doesn't
* actually wait. It returns immediately.
* However it does serve a useful purpose of stopping
* the playback after all the queued up data is used."
*
*
* Now that I take a closer look at the transmit timing, for
* version 1.2, it seems that snd_pcm_drain DOES wait until all
* all pending frames have been played.
* Either way, the caller will now compensate for it.
*/
#else
assert (adev[a].oss_audio_device_fd > 0);
// This caused a crash later on Cygwin.
// Haven't tried it on other (non-Linux) Unix yet.
// err = ioctl (adev[a].oss_audio_device_fd, SNDCTL_DSP_SYNC, NULL);
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_wait(): after sync, status=%d\n", err);
#endif
} /* end audio_wait */
/*------------------------------------------------------------------
*
* Name: audio_close
*
* Purpose: Close the audio device(s).
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
*
*----------------------------------------------------------------*/
int audio_close (void)
{
int err = 0;
int a;
for (a = 0; a < MAX_ADEVS; a++) {
#if USE_ALSA
if (adev[a].audio_in_handle != NULL && adev[a].audio_out_handle != NULL) {
audio_wait (a);
snd_pcm_close (adev[a].audio_in_handle);
snd_pcm_close (adev[a].audio_out_handle);
#else
if (adev[a].oss_audio_device_fd > 0) {
audio_wait (a);
close (adev[a].oss_audio_device_fd);
adev[a].oss_audio_device_fd = -1;
#endif
free (adev[a].inbuf_ptr);
free (adev[a].outbuf_ptr);
adev[a].inbuf_size_in_bytes = 0;
adev[a].inbuf_ptr = NULL;
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
adev[a].outbuf_size_in_bytes = 0;
adev[a].outbuf_ptr = NULL;
adev[a].outbuf_len = 0;
}
}
return (err);
} /* end audio_close */
/* end audio.c */
direwolf-1.4/audio.h 0000664 0000000 0000000 00000027740 13100232553 0014447 0 ustar 00root root 0000000 0000000
/*------------------------------------------------------------------
*
* Module: audio.h
*
* Purpose: Interface to audio device commonly called a "sound card"
* for historical reasons.
*
*---------------------------------------------------------------*/
#ifndef AUDIO_H
#define AUDIO_H 1
#ifdef USE_HAMLIB
#include
#endif
#include "direwolf.h" /* for MAX_CHANS used throughout the application. */
#include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */
/*
* PTT control.
*/
enum ptt_method_e {
PTT_METHOD_NONE, /* VOX or no transmit. */
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. */
typedef enum ptt_method_e ptt_method_t;
enum ptt_line_e { PTT_LINE_NONE = 0, PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 }; // Important: 0 for neither.
typedef enum ptt_line_e ptt_line_t;
enum audio_in_type_e {
AUDIO_IN_TYPE_SOUNDCARD,
AUDIO_IN_TYPE_SDR_UDP,
AUDIO_IN_TYPE_STDIN };
/* For option to try fixing frames with bad CRC. */
typedef enum retry_e {
RETRY_NONE=0,
RETRY_INVERT_SINGLE=1,
RETRY_INVERT_DOUBLE=2,
RETRY_INVERT_TRIPLE=3,
RETRY_INVERT_TWO_SEP=4,
RETRY_MAX = 5} retry_t;
typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t;
struct audio_s {
/* Previously we could handle only a single audio device. */
/* In version 1.2, we generalize this to handle multiple devices. */
/* This means we can now have more than 2 radio channels. */
struct adev_param_s {
/* Properites of the sound device. */
int defined; /* Was device defined? */
/* First one defaults to yes. */
char adevice_in[80]; /* Name of the audio input device (or file?). */
/* TODO: Can be "-" to read from stdin. */
char adevice_out[80]; /* Name of the audio output device (or file?). */
int num_channels; /* Should be 1 for mono or 2 for stereo. */
int samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, or 44100. */
int bits_per_sample; /* 8 (unsigned char) or 16 (signed short). */
} adev[MAX_ADEVS];
/* Common to all channels. */
char tts_script[80]; /* Script for text to speech. */
int statistics_interval; /* Number of seconds between the audio */
/* statistics reports. This is set by */
/* the "-a" option. 0 to disable feature. */
int xmit_error_rate; /* For testing purposes, we can generate frames with an invalid CRC */
/* to simulate corruption while going over the air. */
/* This is the probability, in per cent, of randomly corrupting it. */
/* Normally this is 0. 25 would mean corrupt it 25% of the time. */
int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */
/* Properties for each audio channel, common to receive and transmit. */
/* Can be different for each radio channel. */
struct achan_param_s {
int valid; /* Is this channel valid? */
char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */
/* Could all be the same or different. */
enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF } modem_type;
/* Usual AFSK. */
/* Baseband signal. Not used yet. */
/* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */
/* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */
/* No modem. Might want this for DTMF only channel. */
enum dtmf_decode_t { DTMF_DECODE_OFF, DTMF_DECODE_ON } dtmf_decode;
/* Originally the DTMF ("Touch Tone") decoder was always */
/* enabled because it took a negligible amount of CPU. */
/* There were complaints about the false positives when */
/* hearing other modulation schemes on HF SSB so now it */
/* is enabled only when needed. */
/* "On" will send special "t" packet to attached applications */
/* and process as APRStt. Someday we might want to separate */
/* these but for now, we have a single off/on. */
int decimate; /* Reduce AFSK sample rate by this factor to */
/* decrease computational requirements. */
int interleave; /* If > 1, interleave samples among multiple decoders. */
/* Quick hack for experiment. */
int mark_freq; /* Two tones for AFSK modulation, in Hz. */
int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */
int baud; /* Data bits per second. */
/* Standard rates are 1200 for VHF and 300 for HF. */
/* This should really be called bits per second. */
/* Next 3 come from config file or command line. */
char profiles[16]; /* zero or more of ABC etc, optional + */
int num_freq; /* Number of different frequency pairs for decoders. */
int offset; /* Spacing between filter frequencies. */
int num_slicers; /* Number of different threshold points to decide */
/* between mark or space. */
/* This is derived from above by demod_init. */
int num_subchan; /* Total number of modems for each channel. */
/* These are for dealing with imperfect frames. */
enum retry_e fix_bits; /* Level of effort to recover from */
/* a bad FCS on the frame. */
/* 0 = no effort */
/* 1 = try fixing a single bit */
/* 2... = more techniques... */
enum sanity_e sanity_test; /* Sanity test to apply when finding a good */
/* CRC after making a change. */
/* Must look like APRS, AX.25, or anything. */
int passall; /* Allow thru even with bad CRC. */
/* Additional properties for transmit. */
/* Originally we had control outputs only for PTT. */
/* In version 1.2, we generalize this to allow others such as DCD. */
/* In version 1.4 we add CON for connected to another station. */
/* Index following structure by one of these: */
#define OCTYPE_PTT 0
#define OCTYPE_DCD 1
#define OCTYPE_CON 2
#define NUM_OCTYPES 3 /* number of values above. i.e. last value +1. */
struct {
ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB. */
char ptt_device[20]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */
/* Also used for HAMLIB. Could be host:port when model is 1. */
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. */
int out_gpio_num; /* GPIO number. Originally this was only for PTT. */
/* It is now more general. */
/* octrl array is indexed by PTT, DCD, or CONnected indicator. */
#define MAX_GPIO_NAME_LEN 16 // 12 would cover any case I've seen so this should be safe
char out_gpio_name[MAX_GPIO_NAME_LEN];
/* orginally, gpio number NN was assumed to simply */
/* have the name gpioNN but this turned out not to be */
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */
int ptt_lpt_bit; /* Bit number for parallel printer port. */
/* Bit 0 = pin 2, ..., bit 7 = pin 9. */
int ptt_invert; /* Invert the output. */
int ptt_invert2; /* Invert the secondary output. */
#ifdef USE_HAMLIB
int ptt_model; /* HAMLIB model. -1 for AUTO. 2 for rigctld. Others are radio model. */
#endif
} octrl[NUM_OCTYPES];
/* Each channel can also have associated input lines. */
/* So far, we just have one for transmit inhibit. */
#define ICTYPE_TXINH 0
#define NUM_ICTYPES 1 /* number of values above. i.e. last value +1. */
struct {
ptt_method_t method; /* none, serial port, GPIO, LPT. */
int in_gpio_num; /* GPIO number */
char in_gpio_name[MAX_GPIO_NAME_LEN];
/* orginally, gpio number NN was assumed to simply */
/* have the name gpioNN but this turned out not to be */
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */
int invert; /* 1 = active low */
} ictrl[NUM_ICTYPES];
/* Transmit timing. */
int dwait; /* First wait extra time for receiver squelch. */
/* Default 0 units of 10 mS each . */
int slottime; /* Slot time in 10 mS units for persistance algorithm. */
/* Typical value is 10 meaning 100 milliseconds. */
int persist; /* Sets probability for transmitting after each */
/* slot time delay. Transmit if a random number */
/* in range of 0 - 255 <= persist value. */
/* Otherwise wait another slot time and try again. */
/* Default value is 63 for 25% probability. */
int txdelay; /* After turning on the transmitter, */
/* send "flags" for txdelay * 10 mS. */
/* Default value is 30 meaning 300 milliseconds. */
int txtail; /* Amount of time to keep transmitting after we */
/* are done sending the data. This is to avoid */
/* dropping PTT too soon and chopping off the end */
/* of the frame. Again 10 mS units. */
/* At this point, I'm thinking of 10 as the default. */
} achan[MAX_CHANS];
#ifdef USE_HAMLIB
int rigs; /* Total number of configured rigs */
RIG *rig[MAX_RIGS]; /* HAMLib rig instances */
#endif
};
#if __WIN32__ || __APPLE__
#define DEFAULT_ADEVICE "" /* Windows: Empty string = default audio device. */
#else
#if USE_ALSA
#define DEFAULT_ADEVICE "default" /* Use default device for ALSA. */
#else
#define DEFAULT_ADEVICE "/dev/dsp" /* First audio device for OSS. */
#endif
#endif
/*
* UDP audio receiving port. Couldn't find any standard or usage precedent.
* Got the number from this example: http://gqrx.dk/doc/streaming-audio-over-udp
* Any better suggestions?
*/
#define DEFAULT_UDP_AUDIO_PORT 7355
// Maximum size of the UDP buffer (for allowing IP routing, udp packets are often limited to 1472 bytes)
#define SDR_UDP_BUF_MAXLEN 2000
#define DEFAULT_NUM_CHANNELS 1
#define DEFAULT_SAMPLES_PER_SEC 44100 /* Very early observations. Might no longer be valid. */
/* 22050 works a lot better than 11025. */
/* 44100 works a little better than 22050. */
/* If you have a reasonable machine, use the highest rate. */
#define MIN_SAMPLES_PER_SEC 8000
//#define MAX_SAMPLES_PER_SEC 48000 /* Originally 44100. Later increased because */
/* Software Defined Radio often uses 48000. */
#define MAX_SAMPLES_PER_SEC 192000 /* The cheap USB-audio adapters (e.g. CM108) can handle 44100 and 48000. */
/* The "soundcard" in my desktop PC can do 96kHz or even 192kHz. */
/* We will probably need to increase the sample rate to go much above 9600 baud. */
#define DEFAULT_BITS_PER_SAMPLE 16
#define DEFAULT_FIX_BITS RETRY_INVERT_SINGLE
/*
* Standard for AFSK on VHF FM.
* Reversing mark and space makes no difference because
* NRZI encoding only cares about change or lack of change
* between the two tones.
*
* HF SSB uses 300 baud and 200 Hz shift.
* 1600 & 1800 Hz is a popular tone pair, sometimes
* called the KAM tones.
*/
#define DEFAULT_MARK_FREQ 1200
#define DEFAULT_SPACE_FREQ 2200
#define DEFAULT_BAUD 1200
/* Used for sanity checking in config file and command line options. */
/* 9600 baud is known to work. */
/* TODO: Is 19200 possible with a soundcard at 44100 samples/sec or do we need a higher sample rate? */
#define MIN_BAUD 100
//#define MAX_BAUD 10000
#define MAX_BAUD 40000 // Anyone want to try 38.4 k baud?
/*
* Typical transmit timings for VHF.
*/
#define DEFAULT_DWAIT 0
#define DEFAULT_SLOTTIME 10
#define DEFAULT_PERSIST 63
#define DEFAULT_TXDELAY 30
#define DEFAULT_TXTAIL 10
/*
* Note that we have two versions of these in audio.c and audio_win.c.
* Use one or the other depending on the platform.
*/
int audio_open (struct audio_s *pa);
int audio_get (int a); /* a = audio device, 0 for first */
int audio_put (int a, int c);
int audio_flush (int a);
void audio_wait (int a);
int audio_close (void);
#endif /* ifdef AUDIO_H */
/* end audio.h */
direwolf-1.4/audio_portaudio.c 0000664 0000000 0000000 00000112240 13100232553 0016516 0 ustar 00root root 0000000 0000000
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
// Copyright (C) 2015 Robert Stiles, KK5VD
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: audio_portaudio.c
*
* Purpose: Interface to audio device commonly called a "sound card" for
* historical reasons.
*
* This version is for Various OS' using Port Audio
*
* Major Revisions:
*
* 1.2 - Add ability to use more than one audio device.
* 1.3 - New file added for Port Audio for Mac and possibly others.
*
*---------------------------------------------------------------*/
#if defined(USE_PORTAUDIO)
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "audio.h"
#include "audio_stats.h"
#include "textcolor.h"
#include "dtime_now.h"
#include "demod.h" /* for alevel_t & demod_get_audio_level() */
#include "portaudio.h"
/* Audio configuration. */
static struct audio_s *save_audio_config_p;
/* Current state for each of the audio devices. */
static struct adev_s {
pthread_mutex_t input_mutex;
pthread_cond_t input_cond;
PaStream *inStream;
PaStreamParameters inputParameters;
int pa_input_device_number;
int no_of_input_channels;
int input_finished;
int input_pause;
int input_flush;
void *audio_in_handle;
int inbuf_size_in_bytes; /* number of bytes allocated */
unsigned char *inbuf_ptr;
int inbuf_len; /* number byte of actual data available. */
int inbuf_next; /* index of next to remove. */
int inbuf_bytes_per_frame; /* number of bytes for a sample from all channels. */
int inbuf_frames_per_buffer; /* number of frames in a buffer. */
pthread_mutex_t output_mutex;
pthread_cond_t output_cond;
PaStream *outStream;
PaStreamParameters outputParameters;
int pa_output_device_number;
int no_of_output_channels;
int output_pause;
int output_finished;
int output_flush;
int output_wait_flag;
void *audio_out_handle;
int outbuf_size_in_bytes;
unsigned char *outbuf_ptr;
int outbuf_len;
int outbuf_next; /* index of next to remove. */
int outbuf_bytes_per_frame; /* number of bytes for a sample from all channels. */
int outbuf_frames_per_buffer; /* number of frames in a buffer. */
enum audio_in_type_e g_audio_in_type;
int udp_sock; /* UDP socket for receiving data */
} adev[MAX_ADEVS];
// Originally 40. Version 1.2, try 10 for lower latency.
#define ONE_BUF_TIME 10
#define SAMPLE_SILENCE 0
#define PA_INPUT 1
#define PA_OUTPUT 2
#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
#undef FOR_FUTURE_USE
static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *devname, char *inout);
static void print_pa_devices(void);
static int check_pa_configure(struct adev_s *dev, int sample_rate);
static void list_supported_sample_rates(struct adev_s *dev);
static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo);
static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag);
static int calcbufsize(int rate, int chans, int bits);
static int calcbufsize(int rate, int chans, int bits)
{
int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000;
int size2 = roundup1k(size1);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
rate, chans, bits, size1, size2);
#endif
return (size2);
}
/*------------------------------------------------------------------
* Search the portaudio device tree looking for the request device.
* One of the issues with portaudio has to do with devices returning
* the same device name for more then one connected device
* (ie two SignaLinks). Appending a Portaudio device index to the
* the device name ensure we can find the correct one. And if it's not
* available return the first occurence that matches the device name.
*----------------------------------------------------------------*/
static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag)
{
int numDevices = Pa_GetDeviceCount();
const PaDeviceInfo * di = (PaDeviceInfo *)0;
int i = 0;
// First check to see if the requested index matches the device name.
if(reqDeviceNo < numDevices) {
di = Pa_GetDeviceInfo((PaDeviceIndex) reqDeviceNo);
if(strncmp(di->name, _devName, 80) == 0) {
if((io_flag == PA_INPUT) && di->maxInputChannels)
return reqDeviceNo;
if((io_flag == PA_OUTPUT) && di->maxOutputChannels)
return reqDeviceNo;
}
}
// Requested device index doesn't match device name. Search for one.
for(i = 0; i < numDevices; i++) {
di = Pa_GetDeviceInfo((PaDeviceIndex) i);
if(strncmp(di->name, _devName, 80) == 0) {
if((io_flag == PA_INPUT) && di->maxInputChannels)
return i;
if((io_flag == PA_OUTPUT) && di->maxOutputChannels)
return i;
}
}
// No Matches found
return -1;
}
/*------------------------------------------------------------------
* Extract device name and number.
*----------------------------------------------------------------*/
static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo)
{
char *cPtr = (char *)0;
char cVal = 0;
int count = 0;
char numStr[8];
if(!deviceStr || !_devName || !_devNo) {
dw_printf( "Internal Error: Func %s passed null pointer.\n", __func__);
return -1;
}
cPtr = deviceStr;
memset(_devName, 0, length);
memset(numStr, 0, sizeof(numStr));
while(*cPtr) {
cVal = *cPtr++;
if(cVal == ':') break;
if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) {
_devName[count++] = cVal;
}
}
count = 0;
while(*cPtr) {
cVal = *cPtr++;
if(isdigit(cVal) && (count < (sizeof(numStr) - 1))) {
numStr[count++] = cVal;
}
}
if(numStr[0] == 0) {
*_devNo = 0;
} else {
sscanf(numStr, "%d", _devNo);
}
return 0;
}
/*------------------------------------------------------------------
* List the supported sample rates.
*----------------------------------------------------------------*/
static void list_supported_sample_rates(struct adev_s *dev)
{
static double standardSampleRates[] = {
8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0,
44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */
};
int i, printCount;
PaError err;
printCount = 0;
for(i = 0; standardSampleRates[i] > 0; i++ ) {
err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, standardSampleRates[i] );
if( err == paFormatIsSupported ) {
if( printCount == 0 ) {
dw_printf( "\t%8.2f", standardSampleRates[i] );
printCount = 1;
}
else if( printCount == 4 ) {
dw_printf( ",\n\t%8.2f", standardSampleRates[i] );
printCount = 1;
}
else {
dw_printf( ", %8.2f", standardSampleRates[i] );
++printCount;
}
}
}
if( !printCount )
dw_printf( "None\n" );
else
dw_printf( "\n" );
}
/*------------------------------------------------------------------
* Check PA Configure parameters.
*----------------------------------------------------------------*/
static int check_pa_configure(struct adev_s *dev, int sample_rate)
{
if(!dev) {
dw_printf( "Internal Error: Func %s struct adev_s *dev null pointer.\n", __func__);
return -1;
}
PaError err = 0;
err = Pa_IsFormatSupported(&dev->inputParameters, &dev->outputParameters, sample_rate);
if(err == paFormatIsSupported) return 0;
dw_printf( "PortAudio Config Error: %s\n", Pa_GetErrorText(err));
return err;
}
/*------------------------------------------------------------------
* Print a list of device names and parameters
*----------------------------------------------------------------*/
static void print_pa_devices(void)
{
int i, numDevices, defaultDisplayed;
const PaDeviceInfo *deviceInfo;
numDevices = Pa_GetDeviceCount();
if( numDevices < 0 ) {
dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices );
return;
}
dw_printf( "Number of devices = %d\n", numDevices );
for(i = 0; i < numDevices; i++ ) {
deviceInfo = Pa_GetDeviceInfo( i );
dw_printf( "--------------------------------------- device #%d\n", i );
/* Mark global and API specific default devices */
defaultDisplayed = 0;
if( i == Pa_GetDefaultInputDevice() ) {
dw_printf( "[ Default Input" );
defaultDisplayed = 1;
}
else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultInputDevice ) {
const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
dw_printf( "[ Default %s Input", hostInfo->name );
defaultDisplayed = 1;
}
if( i == Pa_GetDefaultOutputDevice() ) {
dw_printf( (defaultDisplayed ? "," : "[") );
dw_printf( " Default Output" );
defaultDisplayed = 1;
}
else if( i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice ) {
const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
dw_printf( (defaultDisplayed ? "," : "[") );
dw_printf( " Default %s Output", hostInfo->name );
defaultDisplayed = 1;
}
if( defaultDisplayed )
dw_printf( " ]\n" );
/* print device info fields */
dw_printf( "Name = \"%s\"\n", deviceInfo->name );
dw_printf( "Host API = %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name );
dw_printf( "Max inputs = %d\n", deviceInfo->maxInputChannels );
dw_printf( "Max outputs = %d\n", deviceInfo->maxOutputChannels );
}
}
/*------------------------------------------------------------------
* Port Audio Input Callback
*----------------------------------------------------------------*/
static int paInput16CB( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
struct adev_s *data = (struct adev_s *) userData;
const int16_t *rptr = (const int16_t *) inputBuffer;
size_t framesToCalc = 0;
size_t i = 0;
int finished = 0;
int word = 0;
size_t bytes_left = data->inbuf_size_in_bytes - data->inbuf_len;
size_t framesLeft = bytes_left / data->inbuf_bytes_per_frame;
(void) outputBuffer; /* Prevent unused variable warnings. */
(void) timeInfo;
(void) statusFlags;
(void) userData;
if( framesLeft < framesPerBuffer ) {
framesToCalc = framesLeft;
finished = paComplete;
} else {
framesToCalc = framesPerBuffer;
finished = paContinue;
}
if( inputBuffer == NULL || data->input_flush) {
for(i = 0; i < framesToCalc; i++) {
data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
if(data->no_of_input_channels == 2) {
data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
data->inbuf_ptr[data->inbuf_len++] = SAMPLE_SILENCE;
}
}
} else {
for(i = 0; i < framesToCalc; i++) {
word = *rptr++; /* left */
data->inbuf_ptr[data->inbuf_len++] = word & 0xff;
data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff;
if(data->no_of_input_channels == 2) {
word = *rptr++; /* right */
data->inbuf_ptr[data->inbuf_len++] = word & 0xff;
data->inbuf_ptr[data->inbuf_len++] = (word >> 8) & 0xff;
}
}
}
if((finished == paComplete) ||
(data->inbuf_len >= data->inbuf_size_in_bytes)) {
pthread_cond_signal(&data->input_cond);
finished = data->input_finished;
}
return finished;
}
#if FOR_FUTURE_USE
/*------------------------------------------------------------------
* Port Audio Output Callback
*----------------------------------------------------------------*/
static int paOutput16CB( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
struct adev_s *data = (struct adev_s *) userData;
int16_t *wptr = (int16_t *) outputBuffer;
size_t i = 0;
int finished = 0;
size_t bytes_left = data->outbuf_size_in_bytes - data->outbuf_len;
size_t framesLeft = bytes_left / data->outbuf_bytes_per_frame;
int word = 0;
(void) inputBuffer; /* Prevent unused variable warnings. */
(void) timeInfo;
(void) statusFlags;
(void) userData;
if(framesLeft && (framesLeft < framesPerBuffer)) {
/* final buffer... */
for(i = 0; i < framesLeft; i++ ) {
word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
*wptr++ = word; /* left */
if(data->no_of_output_channels == 2 ) {
word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
*wptr++ = word; /* right */
}
}
for( ; i < framesPerBuffer; i++ ) {
*wptr++ = 0; /* left */
if(data->no_of_output_channels == 2 )
*wptr++ = 0; /* right */
}
finished = paContinue;
} else {
for(i = 0; i < framesPerBuffer; i++ ) {
word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
*wptr++ = word; /* left */
if(data->no_of_output_channels == 2) {
word = data->outbuf_ptr[data->outbuf_len++] & 0xff;
word |= (data->outbuf_ptr[data->outbuf_len++] << 8) & 0xff;
*wptr++ = word; /* right */
}
}
finished = paComplete;
}
if(data->output_flush) {
data->output_flush = 0;
finished = paComplete;
}
pthread_cond_signal(&data->output_cond);
finished = data->output_finished;
return finished;
}
#endif
/*------------------------------------------------------------------
*
* Name: audio_open
*
* Purpose: Open the digital audio device.
*
* New in version 1.0, we recognize "udp:" optionally
* followed by a port number.
*
* Inputs: pa - Address of structure of type audio_s.
*
* Using a structure, rather than separate arguments
* seemed to make sense because we often pass around
* the same set of parameters various places.
*
* The fields that we care about are:
* num_channels
* samples_per_sec
* bits_per_sample
* If zero, reasonable defaults will be provided.
*
* The device names are in adevice_in and adevice_out.
* where c is the "card" (for historical purposes)
* and d is the "device" within the "card."
*
*
* Outputs: pa - The ACTUAL values are returned here.
*
* These might not be exactly the same as what was requested.
*
* Example: ask for stereo, 16 bits, 22050 per second.
* An ordinary desktop/laptop PC should be able to handle this.
* However, some other sort of smaller device might be
* more restrictive in its capabilities.
* It might say, the best I can do is mono, 8 bit, 8000/sec.
*
* The sofware modem must use this ACTUAL information
* that the device is supplying, that could be different
* than what the user specified.
*
* Returns: 0 for success, -1 for failure.
*
*
*----------------------------------------------------------------*/
int audio_open (struct audio_s *pa)
{
int err = 0;
int chan = 0;
int a = 0;
int clear_value = 0;
char audio_in_name[80];
char audio_out_name[80];
static int initalize_flag = 0;
PaError paerr = paNoError;
if(!initalize_flag) {
paerr = Pa_Initialize();
initalize_flag = -1;
}
if(paerr != paNoError ) return -1;
save_audio_config_p = pa;
memset (adev, 0, sizeof(adev));
memset (audio_in_name, 0, sizeof(audio_in_name));
memset (audio_out_name, 0, sizeof(audio_out_name));
for (a = 0; a < MAX_ADEVS; a++) {
adev[a].udp_sock = -1;
}
/*
* Fill in defaults for any missing values.
*/
for (a = 0; a < MAX_ADEVS; a++) {
if (pa->adev[a].num_channels == 0)
pa->adev[a].num_channels = DEFAULT_NUM_CHANNELS;
if (pa->adev[a].samples_per_sec == 0)
pa->adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
if (pa->adev[a].bits_per_sample == 0)
pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
for (chan = 0; chan < MAX_CHANS; chan++) {
if (pa->achan[chan].mark_freq == 0)
pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ;
if (pa->achan[chan].space_freq == 0)
pa->achan[chan].space_freq = DEFAULT_SPACE_FREQ;
if (pa->achan[chan].baud == 0)
pa->achan[chan].baud = DEFAULT_BAUD;
if (pa->achan[chan].num_subchan == 0)
pa->achan[chan].num_subchan = 1;
}
}
/*
* Open audio device(s).
*/
for (a = 0; a < MAX_ADEVS; a++) {
if (pa->adev[a].defined) {
adev[a].inbuf_size_in_bytes = 0;
adev[a].outbuf_size_in_bytes = 0;
/*
* Determine the type of audio input.
*/
adev[a].g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
adev[a].g_audio_in_type = AUDIO_IN_TYPE_STDIN;
/* Change "-" to stdin for readability. */
strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
}
if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
adev[a].g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
/* Supply default port if none specified. */
if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
}
}
/* Let user know what is going on. */
/* If not specified, the device names should be "default". */
strlcpy (audio_in_name, pa->adev[a].adevice_in, sizeof(audio_in_name));
strlcpy (audio_out_name, pa->adev[a].adevice_out, sizeof(audio_out_name));
char ctemp[40];
if (pa->adev[a].num_channels == 2) {
snprintf (ctemp, sizeof(ctemp), " (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
} else {
snprintf (ctemp, sizeof(ctemp), " (channel %d)", ADEVFIRSTCHAN(a));
}
text_color_set(DW_COLOR_INFO);
if (strcmp(audio_in_name,audio_out_name) == 0) {
dw_printf ("Audio device for both receive and transmit: %s %s\n", audio_in_name, ctemp);
} else {
dw_printf ("Audio input device for receive: %s %s\n", audio_in_name, ctemp);
dw_printf ("Audio out device for transmit: %s %s\n", audio_out_name, ctemp);
}
/*
* Now attempt actual opens.
*/
/*
* Input device.
*/
switch (adev[a].g_audio_in_type) {
case AUDIO_IN_TYPE_SOUNDCARD:
print_pa_devices();
err = set_portaudio_params (a, &adev[a], pa, audio_in_name, audio_out_name);
if(err < 0) return -1;
pthread_mutex_init(&adev[a].input_mutex, NULL);
pthread_cond_init(&adev[a].input_cond, NULL);
pthread_mutex_init(&adev[a].output_mutex, NULL);
pthread_cond_init(&adev[a].output_cond, NULL);
if(pa->adev[a].bits_per_sample == 8)
clear_value = 128;
else
clear_value = 0;
break;
/*
* UDP.
*/
case AUDIO_IN_TYPE_SDR_UDP:
// Create socket and bind socket
{
struct sockaddr_in si_me;
//Create UDP Socket
if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't create socket, errno %d\n", errno);
return -1;
}
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons((short)atoi(audio_in_name+4));
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
//Bind to the socket
if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't bind socket, errno %d\n", errno);
return -1;
}
}
//adev[a].inbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN;
break;
/*
* stdin.
*/
case AUDIO_IN_TYPE_STDIN:
/* Do we need to adjust any properties of stdin? */
//adev[a].inbuf_size_in_bytes = 1024;
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error, invalid audio_in_type\n");
return (-1);
}
/*
* Finally allocate buffer for each direction.
*/
/* Version 1.3 - Add sanity check on buffer size. */
/* There was a reported case of assert failure on buffer size in audio_get(). */
if (adev[a].inbuf_size_in_bytes < 256 || adev[a].inbuf_size_in_bytes > 32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio input buffer has unexpected extreme size of %d bytes.\n", adev[a].inbuf_size_in_bytes);
dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("This might be caused by unusual audio device configuration values.\n");
adev[a].inbuf_size_in_bytes = 2048;
dw_printf ("Using %d to attempt recovery.\n", adev[a].inbuf_size_in_bytes);
}
if (adev[a].outbuf_size_in_bytes < 256 || adev[a].outbuf_size_in_bytes > 32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output buffer has unexpected extreme size of %d bytes.\n", adev[a].outbuf_size_in_bytes);
dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("This might be caused by unusual audio device configuration values.\n");
adev[a].outbuf_size_in_bytes = 2048;
dw_printf ("Using %d to attempt recovery.\n", adev[a].outbuf_size_in_bytes);
}
adev[a].inbuf_ptr = malloc(adev[a].inbuf_size_in_bytes);
assert (adev[a].inbuf_ptr != NULL);
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
memset(adev[a].inbuf_ptr, clear_value, adev[a].inbuf_size_in_bytes);
adev[a].outbuf_ptr = malloc(adev[a].outbuf_size_in_bytes);
assert (adev[a].outbuf_ptr != NULL);
adev[a].outbuf_len = 0;
adev[a].outbuf_next = 0;
memset(adev[a].outbuf_ptr, clear_value, adev[a].outbuf_size_in_bytes);
if(adev[a].inStream) {
err = Pa_StartStream(adev[a].inStream);
if(err != paNoError) {
dw_printf ("Input stream start Error %s\n", Pa_GetErrorText(err));
}
}
if(adev[a].outStream) {
err = Pa_StartStream(adev[a].outStream);
if(err != paNoError) {
dw_printf ("Output stream start Error %s\n", Pa_GetErrorText(err));
}
}
} /* end of audio device defined */
} /* end of for each audio device */
return (0);
} /* end audio_open */
/*
* Set parameters for sound card.
*
* See ?? for details.
*/
static int set_portaudio_params (int a, struct adev_s *dev, struct audio_s *pa, char *_audio_in_name, char *_audio_out_name)
{
int numDevices = 0;
int err = 0;
int buffer_size = 0;
int sampleFormat = 0;
int no_of_bytes_per_sample = 0;
int reqInDeviceNo = 0;
int reqOutDeviceNo = 0;
char input_devName[80];
char output_devName[80];
text_color_set(DW_COLOR_ERROR);
if(!dev || !pa || !_audio_in_name || !_audio_out_name) {
dw_printf ("Internal error, invalid function parameter pointer(s) (null)\n");
return -1;
}
if(_audio_in_name[0] == 0) {
dw_printf ("Input device name null\n");
return -1;
}
if(_audio_out_name[0] == 0) {
dw_printf ("Output device name null\n");
return -1;
}
numDevices = Pa_GetDeviceCount();
if( numDevices < 0 ) {
dw_printf( "ERROR: Pa_GetDeviceCount returned 0x%x\n", numDevices );
return -1;
}
err = pa_devNN(_audio_in_name, input_devName, sizeof(input_devName), &reqInDeviceNo);
if(err < 0) return -1;
reqInDeviceNo = searchPADevice(dev, input_devName, reqInDeviceNo, PA_INPUT);
if(reqInDeviceNo < 0) {
dw_printf ("Requested Input Audio Device not found %s.\n", input_devName);
return -1;
}
err = pa_devNN(_audio_out_name, output_devName, sizeof(output_devName), &reqOutDeviceNo);
if(err < 0) return -1;
reqOutDeviceNo = searchPADevice(dev, output_devName, reqOutDeviceNo, PA_OUTPUT);
if(reqOutDeviceNo < 0) {
dw_printf ("Requested Output Audio Device not found %s.\n", output_devName);
return -1;
}
dev->pa_input_device_number = reqInDeviceNo;
dev->pa_output_device_number = reqOutDeviceNo;
switch(pa->adev[a].bits_per_sample) {
case 8:
sampleFormat = paInt8;
no_of_bytes_per_sample = sizeof(int8_t);
assert("int8_t size not equal to 1" && sizeof(int8_t) == 1);
break;
case 16:
sampleFormat = paInt16;
no_of_bytes_per_sample = sizeof(int16_t);
assert("int16_t size not equal to 2" && sizeof(int16_t) == 2);
break;
default:
dw_printf ("Unsupported Sample Size %s.\n", output_devName);
return -1;
}
buffer_size = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
dev->inbuf_size_in_bytes = buffer_size;
dev->inbuf_bytes_per_frame = no_of_bytes_per_sample * pa->adev[a].num_channels;
dev->inbuf_frames_per_buffer = dev->inbuf_size_in_bytes / dev->inbuf_bytes_per_frame;
dev->inputParameters.device = dev->pa_input_device_number;
dev->inputParameters.channelCount = pa->adev[a].num_channels;
dev->inputParameters.sampleFormat = sampleFormat;
dev->inputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->inputParameters.device)->defaultLowInputLatency;
dev->inputParameters.hostApiSpecificStreamInfo = NULL;
dev->outbuf_size_in_bytes = buffer_size;
dev->outbuf_bytes_per_frame = no_of_bytes_per_sample * pa->adev[a].num_channels;
dev->outbuf_frames_per_buffer = dev->outbuf_size_in_bytes / dev->outbuf_bytes_per_frame;
dev->outputParameters.device = dev->pa_output_device_number;
dev->outputParameters.channelCount = pa->adev[a].num_channels;
dev->outputParameters.sampleFormat = sampleFormat;
dev->outputParameters.suggestedLatency = Pa_GetDeviceInfo(dev->outputParameters.device)->defaultHighOutputLatency;
dev->outputParameters.hostApiSpecificStreamInfo = NULL;
err = check_pa_configure(dev, pa->adev[a].samples_per_sec);
if(err) {
if(err == paInvalidSampleRate)
list_supported_sample_rates(dev);
return -1;
}
err = Pa_OpenStream(&dev->inStream, &dev->inputParameters, NULL,
pa->adev[a].samples_per_sec, dev->inbuf_frames_per_buffer, 0, paInput16CB, dev );
if( err != paNoError ) {
dw_printf( "PortAudio OpenStream (input) Error: %s\n", Pa_GetErrorText(err));
return -1;
}
err = Pa_OpenStream(&dev->outStream, NULL, &dev->outputParameters,
// pa->adev[a].samples_per_sec, framesPerBuffer, 0, paOutput16CB, dev );
pa->adev[a].samples_per_sec, dev->outbuf_frames_per_buffer, 0, NULL, dev );
if( err != paNoError ) {
dw_printf( "PortAudio OpenStream (output) Error: %s\n", Pa_GetErrorText(err));
return -1;
}
dev->input_finished = paContinue;
dev->output_finished = paContinue;
return buffer_size;
}
/*------------------------------------------------------------------
*
* Name: audio_get
*
* Purpose: Get one byte from the audio device.
*
* Inputs: a - Our number for audio device.
*
* Returns: 0 - 255 for a valid sample.
* -1 for any type of error.
*
* Description: The caller must deal with the details of mono/stereo
* and number of bytes per sample.
*
* This will wait if no data is currently available.
*
*----------------------------------------------------------------*/
// Use hot attribute for all functions called for every audio sample.
__attribute__((hot))
int audio_get (int a)
{
int n;
int retries = 0;
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get():\n");
#endif
assert (adev[a].inbuf_size_in_bytes >= 100 && adev[a].inbuf_size_in_bytes <= 32768);
switch (adev[a].g_audio_in_type) {
/*
* Soundcard - PortAudio
*/
case AUDIO_IN_TYPE_SOUNDCARD:
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
assert (adev[a].inStream != NULL);
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): readi asking for %d frames\n", adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame);
#endif
if(adev[a].inbuf_len >= adev[a].inbuf_size_in_bytes) {
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
}
pthread_mutex_lock(&adev[a].input_mutex);
pthread_cond_wait(&adev[a].input_cond, &adev[a].input_mutex);
pthread_mutex_unlock(&adev[a].input_mutex);
n = adev[a].inbuf_len / adev[a].inbuf_bytes_per_frame;
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): readi asked for %d and got %d frames\n",
adev[a].inbuf_size_in_bytes / adev[a].bytes_per_frame, n);
#endif
if (n > 0) {
/* Success */
adev[a].inbuf_len = n * adev[a].inbuf_bytes_per_frame; /* convert to number of bytes */
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
n,
save_audio_config_p->statistics_interval);
}
else if (n == 0) {
/* Didn't expect this, but it's not a problem. */
/* Wait a little while and try again. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("[%s], Audio input got zero bytes\n", __func__);
SLEEP_MS(10);
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
}
else {
/* Error */
// TODO: Needs more study and testing.
// TODO: print n. should snd_strerror use n or errno?
// Audio input device error: Unknown error
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio input device %d error\n", a);
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
/* Try to recover a few times and eventually give up. */
if (++retries > 10) {
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
return (-1);
}
if (n == -EPIPE) {
/* EPIPE means overrun */
//snd_pcm_recover (adev[a].audio_in_handle, n, 1);
}
else {
/* Could be some temporary condition. */
/* Wait a little then try again. */
/* Sometimes I get "Resource temporarily available" */
/* when the Update Manager decides to run. */
SLEEP_MS (250);
//snd_pcm_recover (adev[a].audio_in_handle, n, 1);
}
}
}
break;
/*
* UDP.
*/
case AUDIO_IN_TYPE_SDR_UDP:
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
int res;
assert (adev[a].udp_sock > 0);
res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
if (res < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't read from udp socket, res=%d", res);
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
return (-1);
}
adev[a].inbuf_len = res;
adev[a].inbuf_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
}
break;
/*
* stdin.
*/
case AUDIO_IN_TYPE_STDIN:
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
int res;
res = read(STDIN_FILENO, adev[a].inbuf_ptr, (size_t)adev[a].inbuf_size_in_bytes);
if (res <= 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nEnd of file on stdin. Exiting.\n");
exit (0);
}
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
adev[a].inbuf_len = res;
adev[a].inbuf_next = 0;
}
break;
}
if (adev[a].inbuf_next < adev[a].inbuf_len)
n = adev[a].inbuf_ptr[adev[a].inbuf_next++];
else
n = 0;
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): returns %d\n", n);
#endif
return (n);
} /* end audio_get */
/*------------------------------------------------------------------
*
* Name: audio_put
*
* Purpose: Send one byte to the audio device.
*
* Inputs: a
*
* c - One byte in range of 0 - 255.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
* Description: The caller must deal with the details of mono/stereo
* and number of bytes per sample.
*
* See Also: audio_flush
* audio_wait
*
*----------------------------------------------------------------*/
int audio_put (int a, int c)
{
int err = 0;
size_t frames = 0;
//#define __TIMED__
#ifdef __TIMED__
static int count = 0;
static double start = 0, end = 0, diff = 0;
if(adev[a].outbuf_len == 0)
start = dtime_now();
#endif
if(c >= 0) {
adev[a].outbuf_ptr[adev[a].outbuf_len++] = c;
}
if ((adev[a].outbuf_len >= adev[a].outbuf_size_in_bytes) || (c < 0)) {
frames = adev[a].outbuf_len / adev[a].outbuf_bytes_per_frame;
if(frames > 0) {
err = Pa_WriteStream(adev[a].outStream, adev[a].outbuf_ptr, frames);
}
// Getting underflow error for some reason on the first pass. Upon examination of the
// audio data revealed no discontinuity in the signal. Time measurements indicate this routine
// on this machine (2.8Ghz/Xeon E5462/2008 vintage) can handle ~6 times the current
// sample rate (44100/2 bytes per frame). For now, mask the error.
// Transfer Time:0.184750080 No of Frames:56264 Per frame:0.000003284 speed:6.905695
if ((err != paNoError) && (err != paOutputUnderflowed)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("[%s] Audio Output Error: %s\n", __func__, Pa_GetErrorText(err));
}
#ifdef __TIMED__
count += frames;
if(c < 0) { // When the Ax25 frames are flushed.
end = dtime_now();
diff = end - start;
if(count)
dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n",
diff, count, diff/(count * 1.0), (1.0/44100.0)/(diff/(count * 1.0)));
count = 0;
}
#endif
adev[a].outbuf_len = 0;
adev[a].outbuf_next = 0;
}
return (0);
}
/*------------------------------------------------------------------
*
* Name: audio_flush
*
* Purpose: Push out any partially filled output buffer.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
* See Also: audio_flush
* audio_wait
*
*----------------------------------------------------------------*/
int audio_flush (int a)
{
audio_put(a, -1);
return 0;
} /* end audio_flush */
/*------------------------------------------------------------------
*
* Name: audio_wait
*
* Purpose: Finish up audio output before turning PTT off.
*
* Inputs: a - Index for audio device (not channel!)
*
* Returns: None.
*
* Description: Flush out any partially filled audio output buffer.
* Wait until all the queued up audio out has been played.
* Take any other necessary actions to stop audio output.
*
* In an ideal world:
*
* We would like to ask the hardware when all the queued
* up sound has actually come out the speaker.
*
* In reality:
*
* This has been found to be less than reliable in practice.
*
* Caller does the following:
*
* (1) Make note of when PTT is turned on.
* (2) Calculate how long it will take to transmit the
* frame including TXDELAY, frame (including
* "flags", data, FCS and bit stuffing), and TXTAIL.
* (3) Call this function, which might or might not wait long enough.
* (4) Add (1) and (2) resulting in when PTT should be turned off.
* (5) Take difference between current time and desired PPT off time
* and wait for additoinal time if required.
*
*----------------------------------------------------------------*/
void audio_wait (int a)
{
audio_flush(a);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_wait(): after sync, status=%d\n", err);
#endif
} /* end audio_wait */
/*------------------------------------------------------------------
*
* Name: audio_close
*
* Purpose: Close the audio device(s).
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
*
*----------------------------------------------------------------*/
int audio_close (void)
{
int err = 0;
int a;
for (a = 0; a < MAX_ADEVS; a++) {
if(adev[a].g_audio_in_type == AUDIO_IN_TYPE_SOUNDCARD) {
audio_wait (a);
if (adev[a].inStream != NULL) {
pthread_mutex_destroy(&adev[a].input_mutex);
pthread_cond_destroy(&adev[a].input_cond);
err |= (int) Pa_CloseStream(adev[a].inStream);
}
if(adev[a].outStream != NULL) {
pthread_mutex_destroy(&adev[a].output_mutex);
pthread_cond_destroy(&adev[a].output_cond);
err |= (int) Pa_CloseStream(adev[a].outStream);
}
err |= (int) Pa_Terminate();
}
if(adev[a].inbuf_ptr)
free (adev[a].inbuf_ptr);
if(adev[a].outbuf_ptr)
free (adev[a].outbuf_ptr);
adev[a].inbuf_size_in_bytes = 0;
adev[a].inbuf_ptr = NULL;
adev[a].inbuf_len = 0;
adev[a].inbuf_next = 0;
adev[a].outbuf_size_in_bytes = 0;
adev[a].outbuf_ptr = NULL;
adev[a].outbuf_len = 0;
adev[a].outbuf_next = 0;
}
if(err < 0)
err = -1;
return (err);
} /* end audio_close */
/* end audio_portaudio.c */
#endif // USE_PORTAUDIO
direwolf-1.4/audio_stats.c 0000664 0000000 0000000 00000012462 13100232553 0015653 0 ustar 00root root 0000000 0000000
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: audio_stats.c
*
* Purpose: Print statistics for audio input stream.
*
* A common complaint is that there is no indication of
* audio input level until a packet is received correctly.
* That's true for the Windows version but the Linux version
* prints something like this each 100 seconds:
*
* ADEVICE0: Sample rate approx. 44.1 k, 0 errors, receive audio level CH0 73
*
* Some complain about the clutter but it has been a useful
* troubleshooting tool. In the earlier RPi days, the sample
* rate was quite low due to a device driver issue.
* Using a USB hub on the RPi also caused audio problems.
* One adapter, that I tried, produces samples at the
* right rate but all the samples are 0.
*
* Here we pull the code out of the Linux version of audio.c
* so we have a common function for all the platforms.
*
* We also add a command line option to adjust the time
* between reports or turn them off entirely.
*
* Revisions: This is new in version 1.3.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "audio_stats.h"
#include "textcolor.h"
#include "dtime_now.h"
#include "demod.h" /* for alevel_t & demod_get_audio_level() */
/*------------------------------------------------------------------
*
* Name: audio_stats
*
* Purpose: Add sample count from one buffer to the statistics.
* Print if specified amount of time has passed.
*
* Inputs: adev - Audio device number: 0, 1, ..., MAX_ADEVS-1
*
nchan - Number of channels for this device, 1 or 2.
*
* nsamp - How many audio samples were read.
*
* interval - How many seconds between reports.
* 0 to turn off.
*
* Returns: none
*
* Description: ...
*
*----------------------------------------------------------------*/
void audio_stats (int adev, int nchan, int nsamp, int interval)
{
/* Gather numbers for read from audio device. */
static time_t last_time[MAX_ADEVS] = { 0, 0, 0 };
time_t this_time[MAX_ADEVS];
static int sample_count[MAX_ADEVS];
static int error_count[MAX_ADEVS];
static int suppress_first[MAX_ADEVS];
if (interval <= 0) {
return;
}
assert (adev >= 0 && adev < MAX_ADEVS);
/*
* Print information about the sample rate as a troubleshooting aid.
* I've never seen an issue with Windows or x86 Linux but the Raspberry Pi
* has a very troublesome audio input system where many samples got lost.
*
* While we are at it we can also print the current audio level(s) providing
* more clues if nothing is being decoded.
*/
if (last_time[adev] == 0) {
last_time[adev] = time(NULL);
sample_count[adev] = 0;
error_count[adev] = 0;
suppress_first[adev] = 1;
/* suppressing the first one could mean a rather */
/* long wait for the first message. We make the */
/* first collection interval 3 seconds. */
last_time[adev] -= (interval - 3);
}
else {
if (nsamp > 0) {
sample_count[adev] += nsamp;
}
else {
error_count[adev]++;
}
this_time[adev] = time(NULL);
if (this_time[adev] >= last_time[adev] + interval) {
if (suppress_first[adev]) {
/* The issue we had is that the first time the rate */
/* would be off considerably because we didn't start */
/* on a second boundary. So we will suppress printing */
/* of the first one. */
suppress_first[adev] = 0;
}
else {
float ave_rate = (sample_count[adev] / 1000.0) / interval;
text_color_set(DW_COLOR_DEBUG);
if (nchan > 1) {
int ch0 = ADEVFIRSTCHAN(adev);
alevel_t alevel0 = demod_get_audio_level(ch0,0);
int ch1 = ADEVFIRSTCHAN(adev) + 1;
alevel_t alevel1 = demod_get_audio_level(ch1,0);
dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio levels CH%d %d, CH%d %d\n\n",
adev, ave_rate, error_count[adev], ch0, alevel0.rec, ch1, alevel1.rec);
}
else {
int ch0 = ADEVFIRSTCHAN(adev);
alevel_t alevel0 = demod_get_audio_level(ch0,0);
dw_printf ("\nADEVICE%d: Sample rate approx. %.1f k, %d errors, receive audio level CH%d %d\n\n",
adev, ave_rate, error_count[adev], ch0, alevel0.rec);
}
}
last_time[adev] = this_time[adev];
sample_count[adev] = 0;
error_count[adev] = 0;
}
}
} /* end audio_stats.c */
direwolf-1.4/audio_stats.h 0000664 0000000 0000000 00000000141 13100232553 0015647 0 ustar 00root root 0000000 0000000
/* audio_stats.h */
extern void audio_stats (int adev, int nchan, int nsamp, int interval);
direwolf-1.4/audio_win.c 0000664 0000000 0000000 00000075021 13100232553 0015312 0 ustar 00root root 0000000 0000000
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: audio_win.c
*
* Purpose: Interface to audio device commonly called a "sound card" for
* historical reasons.
*
* This version uses the native Windows sound interface.
*
* Credits: Fabrice FAURE contributed Linux code for the SDR UDP interface.
*
* Discussion here: http://gqrx.dk/doc/streaming-audio-over-udp
*
* Major revisions:
*
* 1.2 - Add ability to use more than one audio device.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
// Also includes windows.h.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef WAVE_FORMAT_96M16
#define WAVE_FORMAT_96M16 0x40000
#define WAVE_FORMAT_96S16 0x80000
#endif
#include
#include // _WIN32_WINNT must be set to 0x0501 before including this
#include "audio.h"
#include "audio_stats.h"
#include "textcolor.h"
#include "ptt.h"
#include "demod.h" /* for alevel_t & demod_get_audio_level() */
/* Audio configuration. */
static struct audio_s *save_audio_config_p;
/*
* Allocate enough buffers for 1 second each direction.
* Each buffer size is a trade off between being responsive
* to activity on the channel vs. overhead of having too
* many little transfers.
*/
/*
* Originally, we had an abitrary buf time of 40 mS.
*
* For mono, the buffer size was rounded up from 3528 to 4k so
* it was really about 50 mS per buffer or about 20 per second.
* For stereo, the buffer size was rounded up from 7056 to 7k so
* it was really about 43.7 mS per buffer or about 23 per second.
*
* In version 1.2, let's try changing it to 10 to reduce the latency.
* For mono, the buffer size was rounded up from 882 to 1k so it
* was really about 12.5 mS per buffer or about 80 per second.
*/
#define TOTAL_BUF_TIME 1000
#define ONE_BUF_TIME 10
#define NUM_IN_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME))
#define NUM_OUT_BUF ((TOTAL_BUF_TIME)/(ONE_BUF_TIME))
#define roundup1k(n) (((n) + 0x3ff) & ~0x3ff)
static int calcbufsize(int rate, int chans, int bits)
{
int size1 = (rate * chans * bits / 8 * ONE_BUF_TIME) / 1000;
int size2 = roundup1k(size1);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_open: calcbufsize (rate=%d, chans=%d, bits=%d) calc size=%d, round up to %d\n",
rate, chans, bits, size1, size2);
#endif
/* Version 1.3 - add a sanity check. */
if (size2 < 256 || size2 > 32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio buffer has unexpected extreme size of %d bytes.\n", size2);
dw_printf ("Detected at %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("This might be caused by unusual audio device configuration values.\n");
size2 = 2048;
dw_printf ("Using %d to attempt recovery.\n", size2);
}
return (size2);
}
/* Information for each audio stream (soundcard, stdin, or UDP) */
static struct adev_s {
enum audio_in_type_e g_audio_in_type;
/*
* UDP socket for receiving audio stream.
* Buffer, length, and pointer for UDP or stdin.
*/
SOCKET udp_sock;
char stream_data[SDR_UDP_BUF_MAXLEN];
int stream_len;
int stream_next;
/* For sound output. */
/* out_wavehdr.dwUser is used to keep track of output buffer state. */
#define DWU_FILLING 1 /* Ready to use or in process of being filled. */
#define DWU_PLAYING 2 /* Was given to sound system for playing. */
#define DWU_DONE 3 /* Sound system is done with it. */
HWAVEOUT audio_out_handle;
volatile WAVEHDR out_wavehdr[NUM_OUT_BUF];
int out_current; /* index to above. */
int outbuf_size;
/* For sound input. */
/* In this case dwUser is index of next available byte to remove. */
HWAVEIN audio_in_handle;
WAVEHDR in_wavehdr[NUM_IN_BUF];
volatile WAVEHDR *in_headp; /* head of queue to process. */
CRITICAL_SECTION in_cs;
} adev[MAX_ADEVS];
/*------------------------------------------------------------------
*
* Name: audio_open
*
* Purpose: Open the digital audio device.
*
* New in version 1.0, we recognize "udp:" optionally
* followed by a port number.
*
* Inputs: pa - Address of structure of type audio_s.
*
* Using a structure, rather than separate arguments
* seemed to make sense because we often pass around
* the same set of parameters various places.
*
* The fields that we care about are:
* num_channels
* samples_per_sec
* bits_per_sample
* If zero, reasonable defaults will be provided.
*
* Outputs: pa - The ACTUAL values are returned here.
*
* The Linux version adjusts strange values to the
* nearest valid value. Don't know, yet, if Windows
* does the same or just fails. Or performs some
* expensive resampling from a rate supported by
* hardware.
*
* These might not be exactly the same as what was requested.
*
* Example: ask for stereo, 16 bits, 22050 per second.
* An ordinary desktop/laptop PC should be able to handle this.
* However, some other sort of smaller device might be
* more restrictive in its capabilities.
* It might say, the best I can do is mono, 8 bit, 8000/sec.
*
* The sofware modem must use this ACTUAL information
* that the device is supplying, that could be different
* than what the user specified.
*
* Returns: 0 for success, -1 for failure.
*
* References: Multimedia Reference
*
* http://msdn.microsoft.com/en-us/library/windows/desktop/dd743606%28v=vs.85%29.aspx
*
*----------------------------------------------------------------*/
static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2);
static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2);
int audio_open (struct audio_s *pa)
{
int a;
int err;
int chan;
int n;
int in_dev_no[MAX_ADEVS];
int out_dev_no[MAX_ADEVS];
int num_devices;
WAVEINCAPS wic;
WAVEOUTCAPS woc;
save_audio_config_p = pa;
for (a=0; aadev[a].defined) {
struct adev_s *A = &(adev[a]);
assert (A->audio_in_handle == 0);
assert (A->audio_out_handle == 0);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("pa->adev[a].adevice_in = '%s'\n", pa->adev[a].adevice_in);
//dw_printf ("pa->adev[a].adevice_out = '%s'\n", pa->adev[a].adevice_out);
/*
* Fill in defaults for any missing values.
*/
if (pa -> adev[a].num_channels == 0)
pa -> adev[a].num_channels = DEFAULT_NUM_CHANNELS;
if (pa -> adev[a].samples_per_sec == 0)
pa -> adev[a].samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
if (pa -> adev[a].bits_per_sample == 0)
pa -> adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
for (chan=0; chan achan[chan].mark_freq == 0)
pa -> achan[chan].mark_freq = DEFAULT_MARK_FREQ;
if (pa -> achan[chan].space_freq == 0)
pa -> achan[chan].space_freq = DEFAULT_SPACE_FREQ;
if (pa -> achan[chan].baud == 0)
pa -> achan[chan].baud = DEFAULT_BAUD;
if (pa->achan[chan].num_subchan == 0)
pa->achan[chan].num_subchan = 1;
}
A->udp_sock = INVALID_SOCKET;
in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */
out_dev_no[a] = WAVE_MAPPER;
/*
* Determine the type of audio input and select device.
* This can be soundcard, UDP stream, or stdin.
*/
if (strcasecmp(pa->adev[a].adevice_in, "stdin") == 0 || strcmp(pa->adev[a].adevice_in, "-") == 0) {
A->g_audio_in_type = AUDIO_IN_TYPE_STDIN;
/* Change - to stdin for readability. */
strlcpy (pa->adev[a].adevice_in, "stdin", sizeof(pa->adev[a].adevice_in));
}
else if (strncasecmp(pa->adev[a].adevice_in, "udp:", 4) == 0) {
A->g_audio_in_type = AUDIO_IN_TYPE_SDR_UDP;
/* Supply default port if none specified. */
if (strcasecmp(pa->adev[a].adevice_in,"udp") == 0 ||
strcasecmp(pa->adev[a].adevice_in,"udp:") == 0) {
snprintf (pa->adev[a].adevice_in, sizeof(pa->adev[a].adevice_in), "udp:%d", DEFAULT_UDP_AUDIO_PORT);
}
}
else {
A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD;
/* Does config file have a number? */
/* If so, it is an index into list of devices. */
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);
}
/* Otherwise, does it have search string? */
if ((UINT)(in_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_in) >= 1) {
num_devices = waveInGetNumDevs();
for (n=0 ; nadev[a].adevice_in) != NULL) {
in_dev_no[a] = n;
}
}
}
if ((UINT)(in_dev_no[a]) == WAVE_MAPPER) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\"%s\" doesn't match any of the input devices.\n", pa->adev[a].adevice_in);
}
}
}
/*
* Select output device.
* Only soundcard at this point.
* Purhaps we'd like to add UDP for an SDR transmitter.
*/
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);
}
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
num_devices = waveOutGetNumDevs();
for (n=0 ; nadev[a].adevice_out) != NULL) {
out_dev_no[a] = n;
}
}
}
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out);
}
}
} /* if defined */
} /* for each device */
/*
* Display the input devices (soundcards) available and what is selected.
*/
text_color_set(DW_COLOR_INFO);
dw_printf ("Available audio input devices for receive (*=selected):\n");
num_devices = waveInGetNumDevs();
for (a=0; aadev[a].defined) {
if (in_dev_no[a] < -1 || in_dev_no[a] >= num_devices) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid input (receive) audio device number %d.\n", in_dev_no[a]);
in_dev_no[a] = WAVE_MAPPER;
}
}
}
text_color_set(DW_COLOR_INFO);
for (n=0; nadev[a].defined) {
dw_printf (" %c", n==in_dev_no[a] ? '*' : ' ');
}
}
dw_printf (" %d: %s", n, wic.szPname);
for (a=0; aadev[a].defined && n==in_dev_no[a]) {
if (pa->adev[a].num_channels == 2) {
dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
}
else {
dw_printf (" (channel %d)", ADEVFIRSTCHAN(a));
}
}
}
dw_printf ("\n");
}
}
// Add UDP or stdin to end of device list if used.
for (a=0; aadev[a].defined) {
struct adev_s *A = &(adev[a]);
/* Display stdin or udp:port if appropriate. */
if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) {
int aaa;
for (aaa=0; aaaadev[aaa].defined) {
dw_printf (" %c", a == aaa ? '*' : ' ');
}
}
dw_printf (" %s ", pa->adev[a].adevice_in); /* should be UDP:nnnn or stdin */
if (pa->adev[a].num_channels == 2) {
dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
}
else {
dw_printf (" (channel %d)", ADEVFIRSTCHAN(a));
}
dw_printf ("\n");
}
}
}
/*
* Display the output devices (soundcards) available and what is selected.
*/
dw_printf ("Available audio output devices for transmit (*=selected):\n");
/* TODO? */
/* No "*" is currently displayed when using the default device. */
/* Should we put "*" next to the default device when using it? */
/* Which is the default? The first one? */
num_devices = waveOutGetNumDevs();
for (a=0; aadev[a].defined) {
if (out_dev_no[a] < -1 || out_dev_no[a] >= num_devices) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid output (transmit) audio device number %d.\n", out_dev_no[a]);
out_dev_no[a] = WAVE_MAPPER;
}
}
}
text_color_set(DW_COLOR_INFO);
for (n=0; nadev[a].defined) {
dw_printf (" %c", n==out_dev_no[a] ? '*' : ' ');
}
}
dw_printf (" %d: %s", n, woc.szPname);
for (a=0; aadev[a].defined && n==out_dev_no[a]) {
if (pa->adev[a].num_channels == 2) {
dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
}
else {
dw_printf (" (channel %d)", ADEVFIRSTCHAN(a));
}
}
}
dw_printf ("\n");
}
}
/*
* Open for each audio device input/output pair.
*/
for (a=0; aadev[a].defined) {
struct adev_s *A = &(adev[a]);
WAVEFORMATEX wf;
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = pa -> adev[a].num_channels;
wf.nSamplesPerSec = pa -> adev[a].samples_per_sec;
wf.wBitsPerSample = pa -> adev[a].bits_per_sample;
wf.nBlockAlign = (wf.wBitsPerSample / 8) * wf.nChannels;
wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec;
wf.cbSize = 0;
A->outbuf_size = calcbufsize(wf.nSamplesPerSec,wf.nChannels,wf.wBitsPerSample);
/*
* Open the audio output device.
* Soundcard is only possibility at this time.
*/
err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION);
if (err != MMSYSERR_NOERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device for output.\n");
return (-1);
}
/*
* Set up the output buffers.
* We use dwUser to indicate it is available for filling.
*/
memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr));
for (n = 0; n < NUM_OUT_BUF; n++) {
A->out_wavehdr[n].lpData = malloc(A->outbuf_size);
A->out_wavehdr[n].dwUser = DWU_FILLING;
A->out_wavehdr[n].dwBufferLength = 0;
}
A->out_current = 0;
/*
* Open audio input device.
* More possibilities here: soundcard, UDP port, stdin.
*/
switch (A->g_audio_in_type) {
/*
* Soundcard.
*/
case AUDIO_IN_TYPE_SOUNDCARD:
InitializeCriticalSection (&(A->in_cs));
err = waveInOpen (&(A->audio_in_handle), in_dev_no[a], &wf, (DWORD_PTR)in_callback, a, CALLBACK_FUNCTION);
if (err != MMSYSERR_NOERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device for input.\n");
return (-1);
}
/*
* Set up the input buffers.
*/
memset ((void*)(A->in_wavehdr), 0, sizeof(A->in_wavehdr));
for (n = 0; n < NUM_OUT_BUF; n++) {
A->in_wavehdr[n].dwBufferLength = A->outbuf_size; /* all the same size */
A->in_wavehdr[n].lpData = malloc(A->outbuf_size);
}
A->in_headp = NULL;
/*
* Give them to the sound input system.
*/
for (n = 0; n < NUM_OUT_BUF; n++) {
waveInPrepareHeader(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR));
waveInAddBuffer(A->audio_in_handle, &(A->in_wavehdr[n]), sizeof(WAVEHDR));
}
/*
* Start it up.
* The callback function is called when one is filled.
*/
waveInStart (A->audio_in_handle);
break;
/*
* UDP.
*/
case AUDIO_IN_TYPE_SDR_UDP:
{
WSADATA wsadata;
struct sockaddr_in si_me;
//int slen=sizeof(si_me);
//int data_size = 0;
int err;
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed: %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);
}
// Create UDP Socket
A->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (A->udp_sock == INVALID_SOCKET) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError());
return -1;
}
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons((short)atoi(pa->adev[a].adevice_in + 4));
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
// Bind to the socket
if (bind(A->udp_sock, (SOCKADDR *) &si_me, sizeof(si_me)) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Couldn't bind socket, errno %d\n", WSAGetLastError());
return -1;
}
A->stream_next= 0;
A->stream_len = 0;
}
break;
/*
* stdin.
*/
case AUDIO_IN_TYPE_STDIN:
setmode (STDIN_FILENO, _O_BINARY);
A->stream_next= 0;
A->stream_len = 0;
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error, invalid audio_in_type\n");
return (-1);
}
}
}
return (0);
} /* end audio_open */
/*
* Called when input audio block is ready.
*/
static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2)
{
int a = instance;
//dw_printf ("in_callback, handle = %d, a = %d\n", (int)handle, a);
assert (a >= 0 && a < MAX_ADEVS);
struct adev_s *A = &(adev[a]);
if (msg == WIM_DATA) {
WAVEHDR *p = (WAVEHDR*)param1;
p->dwUser = -1; /* needs to be unprepared. */
p->lpNext = NULL;
EnterCriticalSection (&(A->in_cs));
if (A->in_headp == NULL) {
A->in_headp = p; /* first one in list */
}
else {
WAVEHDR *last = (WAVEHDR*)(A->in_headp);
while (last->lpNext != NULL) {
last = last->lpNext;
}
last->lpNext = p; /* append to last one */
}
LeaveCriticalSection (&(A->in_cs));
}
}
/*
* Called when output system is done with a block and it
* is again available for us to fill.
*/
static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2)
{
if (msg == WOM_DONE) {
WAVEHDR *p = (WAVEHDR*)param1;
p->dwBufferLength = 0;
p->dwUser = DWU_DONE;
}
}
/*------------------------------------------------------------------
*
* Name: audio_get
*
* Purpose: Get one byte from the audio device.
*
*
* Inputs: a - Audio soundcard number.
*
* Returns: 0 - 255 for a valid sample.
* -1 for any type of error.
*
* Description: The caller must deal with the details of mono/stereo
* and number of bytes per sample.
*
* This will wait if no data is currently available.
*
*----------------------------------------------------------------*/
// Use hot attribute for all functions called for every audio sample.
__attribute__((hot))
int audio_get (int a)
{
struct adev_s *A;
WAVEHDR *p;
int n;
int sample;
A = &(adev[a]);
switch (A->g_audio_in_type) {
/*
* Soundcard.
*/
case AUDIO_IN_TYPE_SOUNDCARD:
while (1) {
/*
* Wait if nothing available.
* Could use an event to wake up but this is adequate.
*/
int timeout = 25;
while (A->in_headp == NULL) {
//SLEEP_MS (ONE_BUF_TIME / 5);
SLEEP_MS (ONE_BUF_TIME);
timeout--;
if (timeout <= 0) {
text_color_set(DW_COLOR_ERROR);
// TODO1.2: Need more details. Can we keep going?
dw_printf ("Timeout waiting for input from audio device %d.\n", a);
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
return (-1);
}
}
p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */
if (p->dwUser == (DWORD)(-1)) {
waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
p->dwUser = 0; /* Index for next byte. */
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
p->dwBytesRecorded / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
}
if (p->dwUser < p->dwBytesRecorded) {
n = ((unsigned char*)(p->lpData))[p->dwUser++];
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_get(): returns %d\n", n);
#endif
return (n);
}
/*
* Buffer is all used up. Give it back to sound input system.
*/
EnterCriticalSection (&(A->in_cs));
A->in_headp = p->lpNext;
LeaveCriticalSection (&(A->in_cs));
p->dwFlags = 0;
waveInPrepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR));
waveInAddBuffer(A->audio_in_handle, p, sizeof(WAVEHDR));
}
break;
/*
* UDP.
*/
case AUDIO_IN_TYPE_SDR_UDP:
while (A->stream_next >= A->stream_len) {
int res;
assert (A->udp_sock > 0);
res = recv (A->udp_sock, A->stream_data, SDR_UDP_BUF_MAXLEN, 0);
if (res <= 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't read from udp socket, errno %d", WSAGetLastError());
A->stream_len = 0;
A->stream_next = 0;
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
0,
save_audio_config_p->statistics_interval);
return (-1);
}
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
A->stream_len = res;
A->stream_next = 0;
}
sample = A->stream_data[A->stream_next] & 0xff;
A->stream_next++;
return (sample);
break;
/*
* stdin.
*/
case AUDIO_IN_TYPE_STDIN:
while (A->stream_next >= A->stream_len) {
int res;
res = read(STDIN_FILENO, A->stream_data, 1024);
if (res <= 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nEnd of file on stdin. Exiting.\n");
exit (0);
}
audio_stats (a,
save_audio_config_p->adev[a].num_channels,
res / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
save_audio_config_p->statistics_interval);
A->stream_len = res;
A->stream_next = 0;
}
return (A->stream_data[A->stream_next++] & 0xff);
break;
}
return (-1);
} /* end audio_get */
/*------------------------------------------------------------------
*
* Name: audio_put
*
* Purpose: Send one byte to the audio device.
*
* Inputs: a - Index for audio device.
*
* c - One byte in range of 0 - 255.
*
*
* Global In: out_current - index of output buffer currenly being filled.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
* Description: The caller must deal with the details of mono/stereo
* and number of bytes per sample.
*
* See Also: audio_flush
* audio_wait
*
*----------------------------------------------------------------*/
int audio_put (int a, int c)
{
WAVEHDR *p;
struct adev_s *A;
A = &(adev[a]);
/*
* Wait if no buffers are available.
* Don't use p yet because compiler might might consider dwFlags a loop invariant.
*/
int timeout = 10;
while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) {
SLEEP_MS (ONE_BUF_TIME);
timeout--;
if (timeout <= 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output failure waiting for buffer.\n");
ptt_term ();
return (-1);
}
}
p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
if (p->dwUser == DWU_DONE) {
waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR));
p->dwBufferLength = 0;
p->dwUser = DWU_FILLING;
}
/* Should never be full at this point. */
assert (p->dwBufferLength >= 0);
assert (p->dwBufferLength < (DWORD)(A->outbuf_size));
p->lpData[p->dwBufferLength++] = c;
if (p->dwBufferLength == (DWORD)(A->outbuf_size)) {
return (audio_flush(a));
}
return (0);
} /* end audio_put */
/*------------------------------------------------------------------
*
* Name: audio_flush
*
* Purpose: Send current buffer to the audio output system.
*
* Inputs: a - Index for audio device.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
* See Also: audio_flush
* audio_wait
*
*----------------------------------------------------------------*/
int audio_flush (int a)
{
WAVEHDR *p;
MMRESULT e;
struct adev_s *A;
A = &(adev[a]);
p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) {
p->dwUser = DWU_PLAYING;
waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR));
e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR));
if (e != MMSYSERR_NOERROR) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("audio out write error %d\n", e);
/* I don't expect this to ever happen but if it */
/* does, make the buffer available for filling. */
p->dwUser = DWU_DONE;
return (-1);
}
A->out_current = (A->out_current + 1) % NUM_OUT_BUF;
}
return (0);
} /* end audio_flush */
/*------------------------------------------------------------------
*
* Name: audio_wait
*
* Purpose: Finish up audio output before turning PTT off.
*
* Inputs: a - Index for audio device (not channel!)
*
* Returns: None.
*
* Description: Flush out any partially filled audio output buffer.
* Wait until all the queued up audio out has been played.
* Take any other necessary actions to stop audio output.
*
* In an ideal world:
*
* We would like to ask the hardware when all the queued
* up sound has actually come out the speaker.
*
* In reality:
*
* This has been found to be less than reliable in practice.
*
* Caller does the following:
*
* (1) Make note of when PTT is turned on.
* (2) Calculate how long it will take to transmit the
* frame including TXDELAY, frame (including
* "flags", data, FCS and bit stuffing), and TXTAIL.
* (3) Call this function, which might or might not wait long enough.
* (4) Add (1) and (2) resulting in when PTT should be turned off.
* (5) Take difference between current time and desired PPT off time
* and wait for additoinal time if required.
*
*----------------------------------------------------------------*/
void audio_wait (int a)
{
audio_flush (a);
} /* end audio_wait */
/*------------------------------------------------------------------
*
* Name: audio_close
*
*
* Purpose: Close all of the audio devices.
*
* Returns: Normally non-negative.
* -1 for any type of error.
*
*
*----------------------------------------------------------------*/
int audio_close (void)
{
int err = 0;
int n;
int a;
for (a=0; aadev[a].defined) {
struct adev_s *A = &(adev[a]);
assert (A->audio_in_handle != 0);
assert (A->audio_out_handle != 0);
audio_wait (a);
/* Shutdown audio input. */
waveInReset(A->audio_in_handle);
waveInStop(A->audio_in_handle);
waveInClose(A->audio_in_handle);
A->audio_in_handle = 0;
for (n = 0; n < NUM_IN_BUF; n++) {
waveInUnprepareHeader (A->audio_in_handle, (LPWAVEHDR)(&(A->in_wavehdr[n])), sizeof(WAVEHDR));
A->in_wavehdr[n].dwFlags = 0;
free (A->in_wavehdr[n].lpData);
A->in_wavehdr[n].lpData = NULL;
}
DeleteCriticalSection (&(A->in_cs));
/* Make sure all output buffers have been played then free them. */
for (n = 0; n < NUM_OUT_BUF; n++) {
if (A->out_wavehdr[n].dwUser == DWU_PLAYING) {
int timeout = 2 * NUM_OUT_BUF;
while (A->out_wavehdr[n].dwUser == DWU_PLAYING) {
SLEEP_MS (ONE_BUF_TIME);
timeout--;
if (timeout <= 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output failure on close.\n");
}
}
waveOutUnprepareHeader (A->audio_out_handle, (LPWAVEHDR)(&(A->out_wavehdr[n])), sizeof(WAVEHDR));
A->out_wavehdr[n].dwUser = DWU_DONE;
}
free (A->out_wavehdr[n].lpData);
A->out_wavehdr[n].lpData = NULL;
}
waveOutClose (A->audio_out_handle);
A->audio_out_handle = 0;
} /* if device configured */
} /* for each device. */
/* Not right. always returns 0 but at this point, doesn't matter. */
return (err);
} /* end audio_close */
/* end audio_win.c */
direwolf-1.4/ax25_link.c 0000664 0000000 0000000 00000655472 13100232553 0015146 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Name: ax25_link
*
* Purpose: Data Link State Machine.
* Establish connections and transfer data in the proper
* order with retries.
*
* Description:
*
* Typical sequence for establishing a connection
* initiated by a client application. Try version 2.2,
* get refused, and fall back to trying version 2.0.
*
*
* State Client App State Mach. Peer
* ----- ---------- ----------- ----
*
* 0 disc
* Conn. Req --->
* SABME --->
* 5 await 2.2
* <--- FRMR or DM *note
* SABM --->
* 1 await 2.0
* <--- UA
* <--- CONN Ind.
* 3 conn
*
*
* Typical sequence when other end initiates connection.
*
*
* State Client App State Mach. Peer
* ----- ---------- ----------- ----
*
* 0 disc
* <--- SABME or SABM
* UA --->
* <--- CONN Ind.
* 3 conn
*
*
* *note:
*
* By reading the v2.2 spec, I expected a 2.0 implementation to send
* FRMR in response to SABME. In testing, I found that the KPC-3+ sent DM.
*
* After consulting the 2.0 spec, it says, that when in disconnected
* mode, it will respond to any command other than SABM or UI frame with a DM
* response with P/F set to 1. I can see where they might get that idea.
*
* I think the rule about sending FRMR for any unrecognized command should take precedence.
*
*
* References:
* * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984
*
* https://www.tapr.org/pub_ax25.html
*
* * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998
*
* https://www.tapr.org/pdf/AX25.2.2.pdf
*
* * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998
*
* http://www.ax25.net/AX25.2.2-Jul%2098-2.pdf
*
* I accidentally stumbled across this one when searching for some sort of errata
* list for the original protocol specification.
*
* "This is a new version of the 1998 standard. It has had all figures
* redone using Microsoft Visio. Errors in the SDL have been corrected."
*
* The SDL diagrams are dated 2006. I wish I knew about this version earlier
* before doing most of the implementation. :-(
*
*
* The functions here are based on the SDL diagrams but turned inside out.
* It seems more intuitive to have a function for each type of input and then decide
* what to do depending on the state. This also reduces duplicate code because we
* often see the same flow chart segments, for the same input, appearing in multiple states.
*
* Erratum: The protocol spec has many places that appear to be errors or are ambiguous so I wasn't
* sure what to do. These should be annotated with Errata comments so we can easily go
* back and revisit them.
*
* X.25: The AX.25 protocol is based on, but does not necessarily adhere to, the X.25 protocol.
* Consulting this might provide some insights where the AX.25 spec is not clear.
*
* http://www.itu.int/rec/T-REC-X.25-199610-I/en/
*
* Current Status: This is still under development.
*
* Features partially tested:
*
* Connect to/from a KPC-3+ and send I frames in both directions.
* v2.2 connect between two instances of direwolf. (Can't find another v2.2 for testing.)
* Modulo 8 & 128 sequence numbers.
* Recovery from simulated transmission errors using either REJ or SREJ.
* XID frame for parameter negotiation.
* Segments to allow data larger than max info part size.
*
* Implemented but not tested properly:
*
* Connecting thru digipeater(s).
* Acting as a digipeater.
* T3 timer.
* Compatibility with additional types of TNC.
*
*------------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include "ax25_pad.h"
#include "ax25_pad2.h"
#include "xid.h"
#include "textcolor.h"
#include "dlq.h"
#include "tq.h"
#include "ax25_link.h"
#include "dtime_now.h"
#include "server.h"
#include "ptt.h"
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
// Debug switches for different types of information.
static int s_debug_protocol_errors = 1; // Less serious Protocol errors.
// Useful for debugging but unnecessarily alarming other times.
static int s_debug_client_app = 0; // Interaction with client application.
// dl_connect_request, dl_data_request, dl_data_indication, etc.
static int s_debug_radio = 0; // Received frames and channel busy status.
// lm_data_indication, lm_channel_busy
static int s_debug_variables = 0; // Variables, state changes.
static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending.
static int s_debug_timers = 0; // Timer details.
static int s_debug_link_handle = 0; // Create data link state machine or pick existing one,
// based on my address, peer address, client app index, and radio channel.
static int s_debug_stats = 0; // Statistics when connection is closed.
static int s_debug_misc = 0; // Anything left over that might be interesting.
/*
* AX.25 data link state machine.
*
* One instance for each link identified by
* [ client, channel, owncall, peercall ]
*/
enum dlsm_state_e {
state_0_disconnected = 0,
state_1_awaiting_connection = 1,
state_2_awaiting_release = 2,
state_3_connected = 3,
state_4_timer_recovery = 4,
state_5_awaiting_v22_connection = 5 };
typedef struct ax25_dlsm_s {
int magic1; // Look out for bad pointer or corruption.
#define MAGIC1 0x11592201
struct ax25_dlsm_s *next; // Next in linked list.
int stream_id; // Unique number for each stream.
// Internally we use a pointer but this is more user-friendly.
int chan; // Radio channel being used.
int client; // We have have multiple client applications,
// each with their own links. We need to know
// which client should receive the data or
// notifications about state changes.
char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN];
// Up to 10 addresses, same order as in frame.
int num_addr; // Number of addresses. Should be in range 2 .. 10.
#define OWNCALL AX25_SOURCE
// addrs[OWNCALL] is owncall for this end of link.
// Note that we are acting on behalf of
// a client application so the APRS mycall
// might not be relevent.
#define PEERCALL AX25_DESTINATION
// addrs[PEERCALL] is call for other end.
double start_time; // Clock time when this was allocated. Used only for
// debug output for timestamps relative to start.
enum dlsm_state_e state; // Current state.
int modulo; // 8 or 128.
// Determines whether we have one or two control
// octets. 128 allows a much larger window size.
int srej_enabled; // Is other end capable of processing SREJ? (Am I allowed to send it?)
// Starts out as false for v2.0 or true for v2.2.
// Can be changed when exchanging capabilities.
// Can be used only with modulo 128.
int n1_paclen; // Maximum length of information field, in bytes.
// Starts out as 256 but can be negotiated higher.
// (Protocol Spec has this in bits. It is in bytes here.)
// "PACLEN" in configuration file.
int n2_retry; // Maximum number of retries permitted.
// Typically 10.
// "RETRY" parameter in configuration file.
int k_maxframe; // Window size. Defaults to 4 (mod 8) or 32 (mod 128).
// Maximum number of unacknowledged information
// frames that can be outstanding.
// "MAXFRAME" parameter in configuration file.
int rc; // Retry count. Give up after n2.
int vs; // 4.2.4.1. Send State Variable V(S)
// The send state variable exists within the TNC and is never sent.
// It contains the next sequential number to be assigned to the next
// transmitted I frame.
// This variable is updated with the transmission of each I frame.
int va; // 4.2.4.5. Acknowledge State Variable V(A)
// The acknowledge state variable exists within the TNC and is never sent.
// It contains the sequence number of the last frame acknowledged by
// its peer [V(A)-1 equals the N(S) of the last acknowledged I frame].
int vr; // 4.2.4.3. Receive State Variable V(R)
// The receive state variable exists within the TNC.
// It contains the sequence number of the next expected received I frame
// This variable is updated upon the reception of an error-free I frame
// whose send sequence number equals the present received state variable value.
int layer_3_initiated; // SABM(E) was sent by request of Layer 3; i.e. DL-CONNECT request primitive.
// I think this means that it is set only if we initiated the connection.
// It would not be set if we are in the middle of accepting a connection from the other station.
// Next 5 are called exception conditions.
int peer_receiver_busy; // Remote station is busy and can't receive I frames.
int reject_exception; // A REJ frame has been sent to the remote station. (boolean)
int own_receiver_busy; // Layer 3 is busy and can't receive I frames.
// We have no API to convey this information so it should always be 0.
int acknowledge_pending; // I frames have been successfully received but not yet
// acknowledged to the remote station.
// Set when receiving the next expected I frame and P=0.
// This gets cleared by sending any I, RR, RNR, REJ.
// Cleared when sending SREJ with F=1.
// Timing.
float srt; // Smoothed roundtrip time in seconds.
// This is used to dynamically adjust t1v.
// Sometimes the flow chart has SAT instead of SRT.
// I think that is a typographical error.
float t1v; // How long to wait for an acknowlegement before resending.
// Value used when starting timer T1, in seconds.
// "FRACK" parameter in some implementations.
// Typically it might be 3 seconds after frame has been
// sent. Add more for each digipeater in path.
// Here it is dynamically adjusted.
// Set initial value for T1V.
// Multiply FRACK by 2*m+1, where m is number of digipeaters.
#define INIT_T1V_SRT \
S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \
S->srt = S->t1v / 2.0;
int radio_channel_busy; // Either due to DCD or PTT.
// Timer T1.
// Timer values all use the usual unix time() value but double precision
// so we can have fractions of seconds.
// T1 is used for retries along with the retry counter, "rc."
// When timer T1 is started, the value is obtained from t1v plus the current time.
// Appropriate functions should be used rather than accessing the values directly.
// This gets a little tricky because we need to pause the timers when the radio
// channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could
// take corrective action if there is no response in a reasonable amount of time.
// What if some other station has the channel tied up for 10 seconds? We don't want
// T1 to timeout and start a retry sequence. The solution is to pause the timers while
// the channel is busy. We don't want to get a timer expiry event when t1_exp is in
// the past if it is currently paused. When it is un-paused, the expiration time is adjusted
// for the amount of time it was paused.
double t1_exp; // This is the time when T1 will expire or 0 if not running.
double t1_paused_at; // Time when it was paused or 0 if not paused.
float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped.
// This is used to fine tune t1v.
// Set to negative initially to mean invalid, don't use in calculation.
int t1_had_expired; // Set when T1 expires.
// Cleared for start & stop.
// Timer T3.
// T3 is used to terminate connection after extended inactivity.
// Similar to T1 except there is not mechanism to capture the remaining time when it is stopped
// and it is not paused when the channel is busy.
double t3_exp; // When it expires or 0 if not running.
#define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux.
// http://www.linux-ax25.org/wiki/Run_time_configurable_parameters
// D710A also defaults to 30*10 = 300 seconds.
// Should it be user-configurable?
// KPC-3+ and TM-D710A have "CHECK" command for this purpose.
// Statistics for testing purposes.
// Count how many frames of each type we received.
// This is easy to do because they all come in thru lm_data_indication.
// Counting outgoing could probably be done in lm_data_request so
// it would not have to be scattered all over the place. TBD
int count_recv_frame_type[frame_not_AX25+1];
int peak_rc_value; // Peak value of retry count (rc).
// For sending data.
cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet.
// Linked list.
cdata_t *txdata_by_ns[128]; // Data which has already been transmitted.
// Indexed by N(S) in case it gets lost and needs to be sent again.
// Cleared out when we get ACK for it.
int magic3; // Look out for out of bounds for above.
#define MAGIC3 0x03331301
cdata_t *rxdata_by_ns[128]; // "Receive buffer"
// Data which has been received out of sequence.
// Indexed by N(S).
int magic2; // Look out for out of bounds for above.
#define MAGIC2 0x02221201
// "Management Data Link" (MDL) state machine for XID exchange.
enum mdl_state_e { mdl_state_0_ready=0, mdl_state_1_negotiating=1 } mdl_state;
int mdl_rc; // Retry count, waiting to get XID response.
// The spec has provision for a separate maximum, NM201, but we
// just use the regular N2 same as other retries.
double tm201_exp; // Timer. Similar to T1.
// The spec mentions a separate timeout value but
// we will just use the same as T1.
double tm201_paused_at; // Time when it was paused or 0 if not paused.
// Segment reassembler.
cdata_t *ra_buff; // Reassembler buffer. NULL when in ready state.
int ra_following; // Most recent number following to predict next expected.
} ax25_dlsm_t;
/*
* List of current state machines for each link.
* There is potential many client apps, each with multiple links
* connected all at the same time.
*
* Everything coming thru here should be from a single thread.
* The Data Link Queue should serialize all processing.
* Therefore, we don't have to worry about critical regions.
*/
static ax25_dlsm_t *list_head = NULL;
/*
* Registered callsigns for incoming connections.
*/
#define RC_MAGIC 0x08291951
typedef struct reg_callsign_s {
char callsign[AX25_MAX_ADDR_LEN];
int chan;
int client;
struct reg_callsign_s *next;
int magic;
} reg_callsign_t;
static reg_callsign_t *reg_callsign_list = NULL;
// Use these, rather than setting variables directly, to make debug out easier.
#define SET_VS(n) { S->vs = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("V(S) = %d at %s %d\n", S->vs, __func__, __LINE__); \
} \
}
// If other guy acks reception of an I frame, we should never get an REJ or SREJ
// asking for it again. When we update V(A), we should be able to remove the saved
// transmitted data, and everything preceding it, from S->txdata_by_ns[].
#define SET_VA(n) { S->va = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("V(A) = %d at %s %d\n", S->va, __func__, __LINE__); \
} \
int x = AX25MODULO(n-1, S->modulo, __FILE__, __func__, __LINE__); \
while (S->txdata_by_ns[x] != NULL) { \
cdata_delete (S->txdata_by_ns[x]); \
S->txdata_by_ns[x] = NULL; \
x = AX25MODULO(x-1, S->modulo, __FILE__, __func__, __LINE__); \
} \
}
#define SET_VR(n) { S->vr = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("V(R) = %d at %s %d\n", S->vr, __func__, __LINE__); \
} \
}
//TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong.
static int AX25MODULO(int n, int m, const char *file, const char *func, int line)
{
if (m != 8 && m != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: %d modulo %d, %s, %s, %d\n", n, m, file, func, line);
m = 8;
}
// Use masking, rather than % operator, so negative numbers are handled properly.
return (n & (m-1));
}
// Timer macros to provide debug output with location from where they are called.
#define START_T1 start_t1(S, __func__, __LINE__)
#define IS_T1_RUNNING is_t1_running(S, __func__, __LINE__)
#define STOP_T1 stop_t1(S, __func__, __LINE__)
#define PAUSE_T1 pause_t1(S, __func__, __LINE__)
#define RESUME_T1 resume_t1(S, __func__, __LINE__)
#define START_T3 start_t3(S, __func__, __LINE__)
#define STOP_T3 stop_t3(S, __func__, __LINE__)
#define START_TM201 start_tm201(S, __func__, __LINE__)
#define STOP_TM201 stop_tm201(S, __func__, __LINE__)
#define PAUSE_TM201 pause_tm201(S, __func__, __LINE__)
#define RESUME_TM201 resume_tm201(S, __func__, __LINE__)
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len);
static void lm_seize_confirm (ax25_dlsm_t *S);
static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len);
static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len);
static int is_ns_in_window (ax25_dlsm_t *S, int ns);
static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1);
static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr);
static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr);
static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr);
static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p);
static void disc_frame (ax25_dlsm_t *S, int f);
static void dm_frame (ax25_dlsm_t *S, int f);
static void ua_frame (ax25_dlsm_t *S, int f);
static void frmr_frame (ax25_dlsm_t *S);
static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf);
static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len);
static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len);
static void t1_expiry (ax25_dlsm_t *S);
static void t3_expiry (ax25_dlsm_t *S);
static void tm201_expiry (ax25_dlsm_t *S);
static void nr_error_recovery (ax25_dlsm_t *S);
static void clear_exception_conditions (ax25_dlsm_t *S);
static void transmit_enquiry (ax25_dlsm_t *S);
static void select_t1_value (ax25_dlsm_t *S);
static void establish_data_link (ax25_dlsm_t *S);
static void set_version_2_0 (ax25_dlsm_t *S);
static void set_version_2_2 (ax25_dlsm_t *S);
static int is_good_nr (ax25_dlsm_t *S, int nr);
static void i_frame_pop_off_queue (ax25_dlsm_t *S);
static void discard_i_queue (ax25_dlsm_t *S);
static void invoke_retransmission (ax25_dlsm_t *S, int nr_input);
static void check_i_frame_ackd (ax25_dlsm_t *S, int nr);
static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf);
static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f);
static void enter_new_state(ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line);
static void mdl_negotiate_request (ax25_dlsm_t *S);
static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param);
static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param);
static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param);
// Use macros above rather than calling these directly.
static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line);
static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
/*
* Configuration settings from file or command line.
*/
static struct misc_config_s *g_misc_config_p;
/*-------------------------------------------------------------------
*
* Name: ax25_link_init
*
* Purpose: Initialize the ax25_link module.
*
* Inputs: pconfig - misc. configuration from config file or command line.
* Beacon stuff ended up here.
*
* Outputs: Remember required information for future use. That's all.
*
*--------------------------------------------------------------------*/
void ax25_link_init (struct misc_config_s *pconfig)
{
/*
* Save parameters for later use.
*/
g_misc_config_p = pconfig;
} /* end ax25_link_init */
/*------------------------------------------------------------------------------
*
* Name: get_link_handle
*
* Purpose: Find existing (or possibly create) state machine for a given link.
* It should be possible to have a large number of links active at the
* same time. They are uniquely identified by
* (owncall, peercall, client id, radio channel)
* Note that we could have multiple client applications, all sharing one
* TNC, on the same or different radio channels, completely unware of each other.
*
* Inputs: addrs - Owncall, peercall, and optional digipeaters.
* For ease of passing this around, it is an array in the
* same order as in the frame.
*
* num_addr - Number of addresses, 2 thru 10.
*
* chan - Radio channel number.
*
* client - Client app number.
* We allow multiple concurrent applications with the
* AGW network protocol. These are identified as 0, 1, ...
* We don't know this for an incoming frame from the radio
* so it is -1 at this point. At a later time will will
* associate the stream with the right client.
*
* create - True if OK to create a new one.
* Otherwise, return only one already existing.
*
* This should always be true for outgoing frames.
* For incoming frames this would be true only for SABM(e)
* with all digipeater fields marked as used.
*
* Here, we will also check to see if it is in our
* registered callsign list.
*
* Returns: Handle for data link state machine.
* NULL if not found and 'create' is false.
*
* Description: Try to find an existing entry matching owncall, peercall, channel,
* and client. If not found create a new one.
*
*------------------------------------------------------------------------------*/
static int next_stream_id = 0;
static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int create)
{
ax25_dlsm_t *p;
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle (%s>%s, chan=%d, client=%d, create=%d)\n",
addrs[AX25_SOURCE], addrs[AX25_DESTINATION], chan, client, create);
}
// Look for existing.
if (client == -1) { // from the radio.
// address order is reversed for compare.
for (p = list_head; p != NULL; p = p->next) {
if (p->chan == chan &&
strcmp(addrs[AX25_DESTINATION], p->addrs[OWNCALL]) == 0 &&
strcmp(addrs[AX25_SOURCE], p->addrs[PEERCALL]) == 0) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns existing stream id %d for incoming.\n", p->stream_id);
}
return (p);
}
}
}
else { // from client app
for (p = list_head; p != NULL; p = p->next) {
if (p->chan == chan &&
p->client == client &&
strcmp(addrs[AX25_SOURCE], p->addrs[OWNCALL]) == 0 &&
strcmp(addrs[AX25_DESTINATION], p->addrs[PEERCALL]) == 0) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns existing stream id %d for outgoing.\n", p->stream_id);
}
return (p);
}
}
}
// Could not find existing. Should we create a new one?
if ( ! create) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle: Search failed. Do not create new.\n");
}
return (NULL);
}
// If it came from the radio, search for destination our registered callsign list.
int incoming_for_client = -1; // which client app registered the callsign?
if (client == -1) { // from the radio.
reg_callsign_t *r, *found;
found = NULL;
for (r = reg_callsign_list; r != NULL && found == NULL; r = r->next) {
if (strcmp(addrs[AX25_DESTINATION], r->callsign) == 0 && chan == r->chan) {
found = r;
incoming_for_client = r->client;
}
}
if (found == NULL) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle: not for me. Ignore it.\n");
}
return (NULL);
}
}
// Create new data link state machine.
p = calloc (sizeof(ax25_dlsm_t), 1);
p->magic1 = MAGIC1;
p->start_time = dtime_now();
p->stream_id = next_stream_id++;
p->modulo = 8;
p->chan = chan;
p->num_addr = num_addr;
// If it came in over the radio, we need to swap source/destination and reverse any digi path.
if (incoming_for_client >= 0) {
strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE]));
strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION]));
int j = AX25_REPEATER_1;
int k = num_addr - 1;
while (k >= AX25_REPEATER_1) {
strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j]));
j++;
k--;
}
p->client = incoming_for_client;
}
else {
memcpy (p->addrs, addrs, sizeof(p->addrs));
p->client = client;
}
p->state = state_0_disconnected;
p->t1_remaining_when_last_stopped = -999; // Invalid, don't use.
p->magic2 = MAGIC2;
p->magic3 = MAGIC3;
// No need for critical region because this should all be in one thread.
p->next = list_head;
list_head = p;
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id);
}
return (p);
}
//###################################################################################
//###################################################################################
//
// Data Link state machine for sending data in connected mode.
//
// Incoming:
//
// Requests from the client application. Set s_debug_client_app for debugging.
//
// dl_connect_request
// dl_disconnect_request
// dl_data_request - send connected data
// dl_unit_data_request - not implemented. APRS & KISS bypass this
// dl_flow_off - not implemented. Not in AGW API.
// dl_flow_on - not implemented. Not in AGW API.
// dl_register_callsign - Register callsigns(s) for incoming connection requests.
// dl_unregister_callsign - Unregister callsigns(s) ...
// dl_client_cleanup - Clean up after client which has disappeared.
//
// Stuff from the radio channel. Set s_debug_radio for debugging.
//
// lm_data_indication - Received frame.
// lm_channel_busy - Change in PTT or DCD.
// lm_seize_confirm - We have started to transmit.
//
// Timer expiration. Set s_debug_timers for debugging.
//
// dl_timer_expiry
//
// Outgoing:
//
// To the client application:
//
// dl_data_indication - received connected data.
//
// To the transmitter:
//
// lm_data_request - Queue up a frame for transmission.
//
// lm_seize_request - Start transmitter when possible.
// lm_seize_confirm will be called when it has.
//
//
// It is important that all requests come thru the data link queue so
// everything is serialized.
// We don't have to worry about being reentrant or critical regions.
// Nothing here should consume a significant amount of time.
// i.e. There should be no sleep delay or anything that would block waiting on someone else.
//
//###################################################################################
//###################################################################################
/*------------------------------------------------------------------------------
*
* Name: dl_connect_request
*
* Purpose: Client app wants to connect to another station.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Description:
*
*------------------------------------------------------------------------------*/
void dl_connect_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
int ok_to_create = 1;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_connect_request ()\n");
}
text_color_set(DW_COLOR_INFO);
dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]);
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
switch (S->state) {
case state_0_disconnected:
INIT_T1V_SRT;
if (g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2.
set_version_2_0 (S);
establish_data_link (S);
S->layer_3_initiated = 1;
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
}
else { // Try v2.2 first, then fall back if appropriate.
set_version_2_2 (S);
establish_data_link (S);
S->layer_3_initiated = 1;
enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__);
}
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
discard_i_queue(S);
S->layer_3_initiated = 1;
// Keep current state.
break;
case state_2_awaiting_release:
// Keep current state.
break;
case state_3_connected:
case state_4_timer_recovery:
discard_i_queue(S);
establish_data_link(S);
S->layer_3_initiated = 1;
// My enhancement. Original always sent SABM and went to state 1.
// If we were using v2.2, why not reestablish with that?
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
break;
}
} /* end dl_connect_request */
/*------------------------------------------------------------------------------
*
* Name: dl_disconnect_request
*
* Purpose: Client app wants to terminate connection with another station.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Outputs:
*
* Description:
*
*------------------------------------------------------------------------------*/
void dl_disconnect_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
int ok_to_create = 1;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_disconnect_request ()\n");
}
text_color_set(DW_COLOR_INFO);
dw_printf ("Disconnect from %s ...\n", E->addrs[PEERCALL]);
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
switch (S->state) {
case state_0_disconnected:
// DL-DISCONNECT *confirm*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
// TODO: FIXME requeue. Not sure what to do here.
// If we put it back in the queue we will get it back again probably still in same state.
// Need a way to defer it until the next state change.
break;
case state_2_awaiting_release:
{
// Erratum. Flow chart simply says "DM (expedited)."
// This is the only place we have expedited. Is this correct?
cmdres_t cr = cr_res; // DM can only be response.
int p = 0;
int nopid = 0; // PID applies only to I and UI frames.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, p, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // HI means expedited.
STOP_T1;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
break;
case state_3_connected:
case state_4_timer_recovery:
discard_i_queue (S);
S->rc = 0; // I think this should be 1 but I'm not that worried about it.
cmdres_t cmd = cr_cmd;
int p = 1;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
STOP_T3;
START_T1;
enter_new_state (S, state_2_awaiting_release, __func__, __LINE__);
break;
}
} /* end dl_disconnect_request */
/*------------------------------------------------------------------------------
*
* Name: dl_data_request
*
* Purpose: Client app wants to send data to another station.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Description: Append the transmit data block to the I frame queue for later processing.
*
* We also perform the segmentation handling here.
*
* C6.1 Segmenter State Machine
* Only the following DL primitives will be candidates for modification by the segmented
* state machine:
* * DL-DATA Request. The user employs this primitive to provide information to be
* transmitted using connection-oriented procedures; i.e., using I frames. The
* segmenter state machine examines the quantity of data to be transmitted. If the
* quantity of data to be transmitted is less than or equal to the data link parameter
* N1, the segmenter state machine passes the primitive through transparently. If the
* quantity of data to be transmitted exceeds the data link parameter N1, the
* segmenter chops up the data into segments of length N1-2 octets. Each segment is
* prepended with a two octet header. (See Figures 3.1 and 3.2.) The segments are
* then turned over to the Data-link State Machine for transmission, using multiple DL
* Data Request primitives. All segments are turned over immediately; therefore the
* Data-link State Machine will transmit them consecutively on the data link.
*
* Erratum: Not sure how to interpret that. See example below for how it was implemented.
*
*------------------------------------------------------------------------------*/
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata);
void dl_data_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
int ok_to_create = 1;
int nseg_to_follow;
int orig_offset, remaining_len;
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_data_request (\"");
ax25_safe_print (E->txdata->data, E->txdata->len, 1);
dw_printf ("\") state=%d\n", S->state);
}
if (E->txdata->len <= S->n1_paclen) {
data_request_good_size (S, E->txdata);
E->txdata = NULL; // Now part of transmit I frame queue.
return;
}
// More interesting case.
// It is too large to fit in one frame so we segment it.
// As an example, suppose we had 6 bytes of data "ABCDEF".
// If N1 >= 6, it would be sent normally.
// (addresses)
// (control bytes)
// PID, typically 0xF0
// 'A' - first byte of information field
// 'B'
// 'C'
// 'D'
// 'E'
// 'F'
// Now consider the case where it would not fit.
// We would change the PID to 0x08 meaning a segment.
// The information part is the segment identifier of this format:
//
// x xxxxxxx
// | ---+---
// | |
// | +- Number of additional segments to follow.
// |
// +- '1' means it is the first segment.
// If N1 = 4, it would be split up like this:
// (addresses)
// (control bytes)
// PID = 0x08 means segment
// 0x82 - Start of info field.
// MSB set indicates FIRST segment.
// 2, in lower 7 bits, means 2 more segments to follow.
// 0xF0 - original PID, typical value.
// 'A' - For the FIRST segment, we have PID and N1-2 data bytes.
// 'B'
// (addresses)
// (control bytes)
// PID = 0x08 means segment
// 0x01 - Means 1 more segment follows.
// 'C' - For subsequent (not first) segments, we have up to N1-1 data bytes.
// 'D'
// 'E'
// (addresses)
// (control bytes)
// PID = 0x08
// 0x00 - 0 means no more to follow. i.e. This is the last.
// 'E'
// Number of segments is ceiling( (datalen + 1 ) / (N1 - 1))
// we add one to datalen for the original PID.
// We subtract one from N1 for the segment identifier header.
#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b))
// Compute number of segments.
// We will decrement this before putting it in the frame so the first
// will have one less than this number.
nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1);
if (nseg_to_follow < 2 || nseg_to_follow > 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, number of segments = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
orig_offset = 0;
remaining_len = E->txdata->len;
// First segment.
int seglen;
struct {
char header; // 0x80 + number of segments to follow.
char original_pid;
char segdata[AX25_N1_PACLEN_MAX];
} first_segment;
cdata_t *new_txdata;
nseg_to_follow--;
first_segment.header = 0x80 | nseg_to_follow;
first_segment.original_pid = E->txdata->pid;
seglen = MIN(S->n1_paclen - 2, remaining_len);
if (seglen < 1 || seglen > S->n1_paclen - 2 || seglen > remaining_len || seglen > (int)(sizeof(first_segment.segdata))) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
memcpy (first_segment.segdata, E->txdata->data + orig_offset, seglen);
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&first_segment), seglen+2);
data_request_good_size (S, new_txdata);
orig_offset += seglen;
remaining_len -= seglen;
// Subsequent segments.
do {
struct {
char header; // Number of segments to follow.
char segdata[AX25_N1_PACLEN_MAX];
} subsequent_segment;
nseg_to_follow--;
subsequent_segment.header = nseg_to_follow;
seglen = MIN(S->n1_paclen - 1, remaining_len);
if (seglen < 1 || seglen > S->n1_paclen - 1 || seglen > remaining_len || seglen > (int)(sizeof(subsequent_segment.segdata))) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, segment length = %d, number to follow = %d\n",
__LINE__, E->txdata->len, S->n1_paclen, seglen, nseg_to_follow);
cdata_delete (E->txdata);
E->txdata = NULL;
return;
}
memcpy (subsequent_segment.segdata, E->txdata->data + orig_offset, seglen);
new_txdata = cdata_new(AX25_PID_SEGMENTATION_FRAGMENT, (char*)(&subsequent_segment), seglen+1);
data_request_good_size (S, new_txdata);
orig_offset += seglen;
remaining_len -= seglen;
} while (nseg_to_follow > 0);
if (remaining_len != 0 || orig_offset != E->txdata->len) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, remaining length = %d (not 0), orig offset = %d (not %d)\n",
__LINE__, E->txdata->len, S->n1_paclen, remaining_len, orig_offset, E->txdata->len);
}
cdata_delete (E->txdata);
E->txdata = NULL;
} /* end dl_data_request */
static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata)
{
switch (S->state) {
case state_0_disconnected:
case state_2_awaiting_release:
/*
* Discard it.
*/
cdata_delete (txdata);
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
/*
* Erratum?
* The flow chart shows "push on I frame queue" if layer 3 initiated
* is NOT set. This seems backwards but I don't understand enough yet
* to make a compelling argument that it is wrong.
* Implemented as in flow chart.
* TODO: Get better understanding of what'layer_3_initiated' means.
*/
if (S->layer_3_initiated) {
cdata_delete (txdata);
break;
}
// otherwise fall thru.
case state_3_connected:
case state_4_timer_recovery:
/*
* "push on I frame queue"
* Append to the end would have been a better description because push implies a stack.
*/
if (S->i_frame_queue == NULL) {
txdata->next = NULL;
S->i_frame_queue = txdata;
}
else {
cdata_t *plast = S->i_frame_queue;
while (plast->next != NULL) {
plast = plast->next;
}
txdata->next = NULL;
plast->next = txdata;
}
break;
}
i_frame_pop_off_queue (S);
} /* end data_request_good_size */
/*------------------------------------------------------------------------------
*
* Name: dl_register_callsign
* dl_unregister_callsign
*
* Purpose: Register / Unregister callsigns that we will accept connections for.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Outputs: New item is pushed on the head of the reg_callsign_list.
* We don't bother checking for duplicates so the most recent wins.
*
* Description: The data link state machine does not use MYCALL from the APRS configuration.
* For outgoing frames, the client supplies the source callsign.
* For incoming connection requests, we need to know what address(es) to respond to.
*
* Note that one client application can register multiple callsigns for
* multiple channels.
* Different clients can register different different addresses on the same channel.
*
*------------------------------------------------------------------------------*/
void dl_register_callsign (dlq_item_t *E)
{
reg_callsign_t *r;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_register_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client);
}
r = calloc(sizeof(reg_callsign_t),1);
strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign));
r->chan = E->chan;
r->client = E->client;
r->next = reg_callsign_list;
r->magic = RC_MAGIC;
reg_callsign_list = r;
} /* end dl_register_callsign */
void dl_unregister_callsign (dlq_item_t *E)
{
reg_callsign_t *r, *prev;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_unregister_callsign (%s, chan=%d, client=%d)\n", E->addrs[0], E->chan, E->client);
}
prev = NULL;
r = reg_callsign_list;
while (r != NULL) {
assert (r->magic == RC_MAGIC);
if (strcmp(r->callsign,E->addrs[0]) == 0 && r->chan == E->chan && r->client == E->client) {
if (r == reg_callsign_list) {
reg_callsign_list = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = reg_callsign_list;
}
else {
prev->next = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = prev->next;
}
}
else {
prev = r;
r = r->next;
}
}
} /* end dl_unregister_callsign */
/*------------------------------------------------------------------------------
*
* Name: dl_client_cleanup
*
* Purpose: Client app has gone away. Clean up any data associated with it.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Description: By client application we mean something that attached with the
* AGW network protocol.
*
* Clean out anything related to the specfied client application.
* This would include state machines and registered callsigns.
*
*------------------------------------------------------------------------------*/
void dl_client_cleanup (dlq_item_t *E)
{
ax25_dlsm_t *S;
ax25_dlsm_t *dlprev;
reg_callsign_t *r, *rcprev;
if (s_debug_client_app) {
text_color_set(DW_COLOR_INFO);
dw_printf ("dl_client_cleanup (%d)\n", E->client);
}
dlprev = NULL;
S = list_head;
while (S != NULL) {
// Look for corruption or double freeing.
assert (S->magic1 == MAGIC1);
assert (S->magic2 == MAGIC2);
assert (S->magic3 == MAGIC3);
if (S->client == E->client ) {
int n;
if (s_debug_stats) {
text_color_set(DW_COLOR_INFO);
dw_printf ("%d I frames received\n", S->count_recv_frame_type[frame_type_I]);
dw_printf ("%d RR frames received\n", S->count_recv_frame_type[frame_type_S_RR]);
dw_printf ("%d RNR frames received\n", S->count_recv_frame_type[frame_type_S_RNR]);
dw_printf ("%d REJ frames received\n", S->count_recv_frame_type[frame_type_S_REJ]);
dw_printf ("%d SREJ frames received\n", S->count_recv_frame_type[frame_type_S_SREJ]);
dw_printf ("%d SABME frames received\n", S->count_recv_frame_type[frame_type_U_SABME]);
dw_printf ("%d SABM frames received\n", S->count_recv_frame_type[frame_type_U_SABM]);
dw_printf ("%d DISC frames received\n", S->count_recv_frame_type[frame_type_U_DISC]);
dw_printf ("%d DM frames received\n", S->count_recv_frame_type[frame_type_U_DM]);
dw_printf ("%d UA frames received\n", S->count_recv_frame_type[frame_type_U_UA]);
dw_printf ("%d FRMR frames received\n", S->count_recv_frame_type[frame_type_U_FRMR]);
dw_printf ("%d UI frames received\n", S->count_recv_frame_type[frame_type_U_UI]);
dw_printf ("%d XID frames received\n", S->count_recv_frame_type[frame_type_U_XID]);
dw_printf ("%d TEST frames received\n", S->count_recv_frame_type[frame_type_U_TEST]);
dw_printf ("%d peak retry count\n", S->peak_rc_value);
}
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_client_cleanup: remove %s>%s\n", S->addrs[AX25_SOURCE], S->addrs[AX25_DESTINATION]);
}
discard_i_queue (S);
for (n = 0; n < 128; n++) {
if (S->txdata_by_ns[n] != NULL) {
cdata_delete (S->txdata_by_ns[n]);
S->txdata_by_ns[n] = NULL;
}
}
for (n = 0; n < 128; n++) {
if (S->rxdata_by_ns[n] != NULL) {
cdata_delete (S->rxdata_by_ns[n]);
S->rxdata_by_ns[n] = NULL;
}
}
if (S->ra_buff != NULL) {
cdata_delete (S->ra_buff);
S->ra_buff = NULL;
}
// Put into disconnected state.
// If "connected" indicator (e.g. LED) was on, this will turn it off.
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
// Take S out of list.
S->magic1 = 0;
S->magic2 = 0;
S->magic3 = 0;
if (S == list_head) { // first one on list.
list_head = S->next;
free (S);
S = list_head;
}
else { // not the first one.
dlprev->next = S->next;
free (S);
S = dlprev->next;
}
}
else {
dlprev = S;
S = S->next;
}
}
/*
* If there are no link state machines (streams) remaining, there should be no txdata items still allocated.
*/
if (list_head == NULL) {
cdata_check_leak();
}
/*
* Remove registered callsigns for this client.
*/
rcprev = NULL;
r = reg_callsign_list;
while (r != NULL) {
assert (r->magic == RC_MAGIC);
if (r->client == E->client) {
if (r == reg_callsign_list) {
reg_callsign_list = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = reg_callsign_list;
}
else {
rcprev->next = r->next;
memset (r, 0, sizeof(reg_callsign_t));
free (r);
r = rcprev->next;
}
}
else {
rcprev = r;
r = r->next;
}
}
} /* end dl_client_cleanup */
/*------------------------------------------------------------------------------
*
* Name: dl_data_indication
*
* Purpose: send connected data to client application.
*
* Inputs: pid - Protocol ID.
*
* data - Pointer to array of bytes.
*
* len - Number of bytes in data.
*
*
* Description: TODO: We perform reassembly of segments here if necessary.
*
*------------------------------------------------------------------------------*/
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len)
{
// Now it gets more interesting. We need to combine segments before passing it along.
// See example in dl_data_request.
if (S->ra_buff == NULL) {
// Ready state.
if (pid != AX25_PID_SEGMENTATION_FRAGMENT) {
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len);
return;
}
else if (data[0] & 0x80) {
// Ready state, First segment.
S->ra_following = data[0] & 0x7f;
int total = (S->ra_following + 1) * (len - 1) - 1; // len should be other side's N1
S->ra_buff = cdata_new(data[1], NULL, total);
S->ra_buff->size = total; // max that we are expecting.
S->ra_buff->len = len - 2; // how much accumulated so far.
memcpy (S->ra_buff->data, data + 2, len - 2);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not first segment in ready state.\n", S->stream_id);
}
}
else {
// Reassembling data state
if (pid != AX25_PID_SEGMENTATION_FRAGMENT) {
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], pid, data, len);
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Not segment in reassembling state.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
else if (data[0] & 0x80) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: First segment in reassembling state.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
else if ((data[0] & 0x7f) != S->ra_following - 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments out of sequence.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
else {
// Reassembling data state, Not first segment.
S->ra_following = data[0] & 0x7f;
if (S->ra_buff->len + len - 1 <= S->ra_buff->size) {
memcpy (S->ra_buff->data + S->ra_buff->len, data + 1, len - 1);
S->ra_buff->len += len - 1;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Reassembler Protocol Error Z: Segments exceed buffer space.\n", S->stream_id);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
return;
}
if (S->ra_following == 0) {
// Last one.
server_rec_conn_data (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], S->ra_buff->pid, S->ra_buff->data, S->ra_buff->len);
cdata_delete(S->ra_buff);
S->ra_buff = NULL;
}
}
}
} /* end dl_data_indication */
/*------------------------------------------------------------------------------
*
* Name: lm_channel_busy
*
* Purpose: Change in DCD or PTT status for channel so we know when it is busy.
*
* Inputs: E - Event from the queue.
*
* E->chan - Radio channel number.
*
* E->activity - OCTYPE_PTT for my transmission start/end.
* - OCTYPE_DCD if we hear someone else.
*
* E->status - 1 for active or 0 for quiet.
*
* Outputs: S->radio_channel_busy
*
* T1 & TM201 paused/resumed if running.
*
* Description: We need to pause the timers when the channel is busy.
*
* Signal lm_seize_confirm when we have started to transmit.
*
*------------------------------------------------------------------------------*/
static int dcd_status[MAX_CHANS];
static int ptt_status[MAX_CHANS];
void lm_channel_busy (dlq_item_t *E)
{
int busy;
assert (E->chan >= 0 && E->chan < MAX_CHANS);
assert (E->activity == OCTYPE_PTT || E->activity == OCTYPE_DCD);
assert (E->status == 1 || E->status == 0);
switch (E->activity) {
case OCTYPE_DCD:
if (s_debug_radio) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_channel_busy: DCD chan %d = %d\n", E->chan, E->status);
}
dcd_status[E->chan] = E->status;
break;
case OCTYPE_PTT:
if (s_debug_radio) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_channel_busy: PTT chan %d = %d\n", E->chan, E->status);
}
ptt_status[E->chan] = E->status;
break;
default:
break;
}
busy = dcd_status[E->chan] | ptt_status[E->chan];
/*
* We know if the given radio channel is busy or not.
* This must be applied to all data link state machines associated with that radio channel.
*/
ax25_dlsm_t *S;
for (S = list_head; S != NULL; S = S->next) {
if (E->chan == S->chan) {
if (busy && ! S->radio_channel_busy) {
S->radio_channel_busy = 1;
PAUSE_T1;
PAUSE_TM201;
// Did channel become busy due to PTT turning on?
if ( E->activity == OCTYPE_PTT && E->status == 1) {
lm_seize_confirm (S); // C4.2. "This primitive indicates, to the Data-link State
// machine, that the transmission opportunity has arrived."
}
}
else if ( ! busy && S->radio_channel_busy) {
S->radio_channel_busy = 0;
RESUME_T1;
RESUME_TM201;
}
}
}
} /* end lm_channel_busy */
/*------------------------------------------------------------------------------
*
* Name: lm_seize_confirm
*
* Purpose: Notification the the channel is clear.
*
* Description: C4.2. This primitive indicates to the Data-link State Machine that
* the transmission opportunity has arrived.
*
*------------------------------------------------------------------------------*/
static void lm_seize_confirm (ax25_dlsm_t *S)
{
switch (S->state) {
case state_0_disconnected:
case state_1_awaiting_connection:
case state_2_awaiting_release:
case state_5_awaiting_v22_connection:
break;
case state_3_connected:
case state_4_timer_recovery:
if (S->acknowledge_pending) {
S->acknowledge_pending = 0;
enquiry_response (S, frame_not_AX25, 0);
}
// Implemenation difference: The flow chart for state 3 has LM-RELEASE Request here.
// I don't think I need it because the transmitter will turn off
// automatically once the queue is empty.
// Erratum: The original spec had LM-SEIZE request here, for state 4, which didn't seem right.
// The 2006 revision has LM-RELEASE Request so states 3 & 4 are the same.
break;
}
} /* lm_seize_confirm */
/*------------------------------------------------------------------------------
*
* Name: lm_data_indication
*
* Purpose: We received some sort of frame over the radio.
*
* Inputs: E - Event from the queue.
* Caller is responsible for freeing it.
*
* Description: First determine if is of interest to me. Two cases:
*
* (1) We already have a link handle for (from-addr, to-addr, channel).
* This could have been set up by an outgoing connect request.
*
* (2) It is addressed to one of the registered callsigns. This would
* catch the case of incoming connect requests. The APRS MYCALL
* is not involved at all. The attached client app might have
* much different ideas about what the station is called or
* aliases it might respond to.
*
*------------------------------------------------------------------------------*/
void lm_data_indication (dlq_item_t *E)
{
ax25_frame_type_t ftype;
char desc[80];
cmdres_t cr;
int pf;
int nr;
int ns;
ax25_dlsm_t *S;
int client_not_applicable = -1;
int n;
int any_unused_digi;
if (E->pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal Error, packet pointer is null. %s %s %d\n", __FILE__, __func__, __LINE__);
return;
}
E->num_addr = ax25_get_num_addr(E->pp);
// Digipeating is not done here so consider only those with no unused digipeater addresses.
any_unused_digi = 0;
for (n = AX25_REPEATER_1; n < E->num_addr; n++) {
if ( ! ax25_get_h(E->pp, n)) {
any_unused_digi = 1;
}
}
if (any_unused_digi) {
if (s_debug_radio) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_indication (%d, %s>%s) - ignore due to unused digi address.\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]);
}
return;
}
// Copy addresses from frame into event structure.
for (n = 0; n < E->num_addr; n++) {
ax25_get_addr_with_ssid (E->pp, n, E->addrs[n]);
}
if (s_debug_radio) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("lm_data_indication (%d, %s>%s)\n", E->chan, E->addrs[AX25_SOURCE], E->addrs[AX25_DESTINATION]);
}
// Look for existing, or possibly create new, link state matching addresses and channel.
// In most cases, we can ignore the frame if we don't have a corresponding
// data link state machine. However, we might want to create a new one for SABM or SABME.
// get_link_handle will check to see if the destination matches my address.
// TODO: This won't work right because we don't know the modulo yet.
// Maybe we should have a shorter form that only returns the frame type.
// That is all we need at this point.
ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns);
S = get_link_handle (E->addrs, E->num_addr, E->chan, client_not_applicable,
(ftype == frame_type_U_SABM) | (ftype == frame_type_U_SABME));
if (S == NULL) {
return;
}
/*
* There is not a reliable way to tell if a frame, out of context, has modulo 8 or 128
* sequence numbers. This needs to be supplied from the data link state machine.
*
* We can't do this until we get the link handle.
*/
ax25_set_modulo (E->pp, S->modulo);
/*
* Now we need to use ax25_frame_type again because the previous results, for nr and ns, might be wrong.
*/
ftype = ax25_frame_type (E->pp, &cr, desc, &pf, &nr, &ns);
// Gather statistics useful for testing.
if (ftype <= frame_not_AX25) {
S->count_recv_frame_type[ftype]++;
}
switch (ftype) {
case frame_type_I:
if (cr != cr_cmd) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error S: %s must be COMMAND.\n", S->stream_id, desc);
}
break;
case frame_type_S_RR:
case frame_type_S_RNR:
case frame_type_S_REJ:
if (cr != cr_cmd && cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc);
}
break;
case frame_type_U_SABME:
case frame_type_U_SABM:
case frame_type_U_DISC:
if (cr != cr_cmd) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND.\n", S->stream_id, desc);
}
break;
// 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.
case frame_type_S_SREJ:
case frame_type_U_DM:
case frame_type_U_UA:
case frame_type_U_FRMR:
if (cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be RESPONSE.\n", S->stream_id, desc);
}
break;
case frame_type_U_XID:
case frame_type_U_TEST:
if (cr != cr_cmd && cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error: %s must be COMMAND or RESPONSE.\n", S->stream_id, desc);
}
break;
case frame_type_U_UI:
// Don't test at this point in case an APRS frame gets thru.
// APRS doesn't specify what to put in the Source and Dest C bits.
// In practice we see all 4 possble combinations.
// I have an opinion about what would be "correct" (discussed elsewhere)
// but in practice no one seems to care.
break;
case frame_type_U:
case frame_not_AX25:
// not expected.
break;
}
switch (ftype) {
case frame_type_I: // Information
{
int pid;
unsigned char *info_ptr;
int info_len;
pid = ax25_get_pid (E->pp);
info_len = ax25_get_info (E->pp, &info_ptr);
i_frame (S, cr, pf, nr, ns, pid, (char *)info_ptr, info_len);
}
break;
case frame_type_S_RR: // Receive Ready - System Ready To Receive
rr_rnr_frame (S, 1, cr, pf, nr);
break;
case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full
rr_rnr_frame (S, 0, cr, pf, nr);
break;
case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate
rej_frame (S, cr, pf, nr);
break;
case frame_type_S_SREJ: // Selective Reject - Ask for single frame repeat
srej_frame (S, cr, pf, nr);
break;
case frame_type_U_SABME: // Set Async Balanced Mode, Extended
sabm_e_frame (S, 1, pf);
break;
case frame_type_U_SABM: // Set Async Balanced Mode
sabm_e_frame (S, 0, pf);
break;
case frame_type_U_DISC: // Disconnect
disc_frame (S, pf);
break;
case frame_type_U_DM: // Disconnect Mode
dm_frame (S, pf);
break;
case frame_type_U_UA: // Unnumbered Acknowledge
ua_frame (S, pf);
break;
case frame_type_U_FRMR: // Frame Reject
frmr_frame (S);
break;
case frame_type_U_UI: // Unnumbered Information
ui_frame (S, cr, pf);
break;
case frame_type_U_XID: // Exchange Identification
{
unsigned char *info_ptr;
int info_len;
info_len = ax25_get_info (E->pp, &info_ptr);
xid_frame (S, cr, pf, info_ptr, info_len);
}
break;
case frame_type_U_TEST: // Test
{
unsigned char *info_ptr;
int info_len;
info_len = ax25_get_info (E->pp, &info_ptr);
test_frame (S, cr, pf, info_ptr, info_len);
}
break;
case frame_type_U: // other Unnumbered, not used by AX.25.
break;
case frame_not_AX25: // Could not get control byte from frame.
break;
}
i_frame_pop_off_queue (S);
} /* end lm_data_indication */
/*------------------------------------------------------------------------------
*
* Name: i_frame
*
* Purpose: Process I Frame.
*
* Inputs: S - Data Link State Machine.
* cr - Command or Response. We have already issued an error if not command.
* p - Poll bit. Assuming we checked earlier that it was a command.
* The meaning is described below.
* nr - N(R) from the frame. Next expected seq. for other end.
* ns - N(S) from the frame. Seq. number of this incoming frame.
* pid - protocol id.
* info_ptr - pointer to information part of frame.
* info_len - Number of bytes in information part of frame.
* Should be in range of 0 thru n1_paclen.
*
* Description:
* 6.4.2. Receiving I Frames
*
* The reception of I frames that contain zero-length information fields is reported to the next layer; no information
* field will be transferred.
*
* 6.4.2.1. Not Busy
*
* If a TNC receives a valid I frame (one with a correct FCS and whose send sequence number equals the
* receiver's receive state variable) and is not in the busy condition, it accepts the received I frame, increments its
* receive state variable, and acts in one of the following manners:
*
* a) If it has an I frame to send, that I frame may be sent with the transmitted N(R) equal to its receive state
* variable V(R) (thus acknowledging the received frame). Alternately, the TNC may send an RR frame with N(R)
* equal to V(R), and then send the I frame.
*
* or b) If there are no outstanding I frames, the receiving TNC sends an RR frame with N(R) equal to V(R). The
* receiving TNC may wait a small period of time before sending the RR frame to be sure additional I frames are
* not being transmitted.
*
* 6.4.2.2. Busy
*
* If the TNC is in a busy condition, it ignores any received I frames without reporting this condition, other than
* repeating the indication of the busy condition.
* If a busy condition exists, the TNC receiving the busy condition indication polls the sending TNC periodically
* until the busy condition disappears.
* A TNC may poll the busy TNC periodically with RR or RNR frames with the P bit set to "1".
*
* 6.4.6. Receiving Acknowledgement
*
* Whenever an I or S frame is correctly received, even in a busy condition, the N(R) of the received frame is
* checked to see if it includes an acknowledgement of outstanding sent I frames. The T1 timer is canceled if the
* received frame actually acknowledges previously unacknowledged frames. If the T1 timer is canceled and there
* are still some frames that have been sent that are not acknowledged, T1 is started again. If the T1 timer expires
* before an acknowledgement is received, the TNC proceeds with the retransmission procedure outlined in Section
* 6.4.11.
*
*
* 6.2. Poll/Final (P/F) Bit Procedures
*
* The next response frame returned to an I frame with the P bit set to "1", received during the information
* transfer state, is an RR, RNR or REJ response with the F bit set to "1".
*
* The next response frame returned to a S or I command frame with the P bit set to "1", received in the
* disconnected state, is a DM response frame with the F bit set to "1".
*
*------------------------------------------------------------------------------*/
static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len)
{
switch (S->state) {
case state_0_disconnected:
// Logic from flow chart for "all other commands."
if (cr == cr_cmd) {
cmdres_t r = cr_res; // DM response with F taken from P.
int f = p;
int nopid = 0; // PID applies only for I and UI frames.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
// Ignore it. Keep same state.
break;
case state_2_awaiting_release:
// Logic from flow chart for "I, RR, RNR, REJ, SREJ commands."
if (cr == cr_cmd && p == 1) {
cmdres_t r = cr_res; // DM response with F = 1.
int f = 1;
int nopid = 0; // PID applies only for I and UI frames.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
break;
case state_3_connected:
case state_4_timer_recovery:
// Look carefully. The original had two tiny differences between the two states.
// In the 2006 version, these differences no longer exist.
if (info_len >= 0 && info_len <= S->n1_paclen) {
if (is_good_nr(S,nr)) {
// Erratum?
// I wonder if this difference is intentional or if only one place was
// was modified after a cut-n-paste of the flow chart segment.
// Erratum: Discrepancy between original and 2006 version.
// Pattern noticed: Anytime we have "is_good_nr" which tests for V(A) <= N(R) <= V(S),
// we should always call "check_i_frame_acked" or at least set V(A) from N(R).
#if 0 // Erratum: original - states 3 & 4 differ here.
if (S->state == state_3_connected) {
// This sets "S->va = nr" and also does some timer stuff.
check_i_frame_ackd (S,nr);
}
else {
SET_VA(nr);
}
#else // 2006 version - states 3 & 4 same here.
// This sets "S->va = nr" and also does some timer stuff.
check_i_frame_ackd (S,nr);
#endif
if (S->own_receiver_busy) {
// This should be unreachable because we currently don't have a way to set own_receiver_busy.
// But we might the capability someday so implement this while we are here.
if (p == 1) {
cmdres_t cr = cr_res; // Erratum: The use of "F" in the flow chart implies that RNR is a response
// in this case, but I'm not confident about that. The text says frame.
int f = 1;
int nr = S->vr;
packet_t pp;
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f);
// I wonder if this difference is intentional or if only one place was
// was modified after a cut-n-paste of the flow chart segment.
#if 0 // Erratum: Original - state 4 has expedited.
if (S->state == state_4_timer_recovery) {
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // "expedited"
}
else {
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
#else // 2006 version - states 3 & 4 the same.
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
#endif
S->acknowledge_pending = 0;
}
}
else { // N(R) out of expected range.
i_frame_continued (S, p, ns, pid, info_ptr, info_len);
}
}
else {
nr_error_recovery (S);
// my enhancement. See below.
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
}
else { // Bad information length.
// Wouldn't even get to CRC check if not octet aligned.
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error O: Information part length, %d, not in range of 0 thru %d.\n", S->stream_id, info_len, S->n1_paclen);
establish_data_link (S);
S->layer_3_initiated = 0;
// The original spec always sent SABM and went to state 1.
// I was thinking, why not use v2.2 instead of we were already connected with v2.2?
// My version of establish_data_link combined the two original functions and
// already uses SABME or SABM based on S->modulo.
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
break;
}
} /* end i_frame */
/*------------------------------------------------------------------------------
*
* Name: i_frame_continued
*
* Purpose: The I frame processing logic gets pretty complicated.
* Some of it has been split out into a separate function to make
* things more readable.
* We have already done some error checking and processed N(R).
* This is used for both states 3 & 4.
*
* Inputs: S - Data Link State Machine. We are in state 3.
* p - Poll bit.
* ns - N(S) from the frame. Seq. number of this incoming frame.
* pid - protocol id.
* info_ptr - pointer to information part of frame.
* info_len - Number of bytes in information part of frame. Already verified.
*
* Description:
*
* 4.3.2.3. Reject (REJ) Command and Response
*
* The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence
* number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the
* retransmission of the N(R) frame.
* Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the
* proper reception of I frames up to the I frame that caused the reject condition to be initiated.
* The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit
* set to one.
*
* 4.3.2.4. Selective Reject (SREJ) Command and Response
*
* The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame
* numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are
* considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ
* frame does not indicate acknowledgement of I frames.
*
* Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R)
* of the SREJ frame.
*
* A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set
* to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not
* transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so
* would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ
* frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in
*
* Section 4.5.4.
*
* I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of
* receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the
* retransmission of the specific I frame requested by the SREJ frame.
*
*
* 6.4.4. Reception of Out-of-Sequence Frames
*
* 6.4.4.1. Implicit Reject (REJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number
* equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not
* been previously established. The received state variable and poll bit of the discarded frame is checked and acted
* upon, if necessary.
* This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode
* requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This
* mode is ineffective on systems with long round-trip delays and high data rates.
*
* 6.4.4.2. Selective Reject (SREJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number
* equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously
* established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable
* and poll bit of the received frame are checked and acted upon, if necessary.
* This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can
* consume precious buffer space, especially if the user device has limited memory available and several active
* links are operational.
*
* 6.4.4.3. Selective Reject-Reject (SREJ/REJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted.
* All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is
* missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The
* received state variable and poll bit of the received frame are checked and acted upon. If another frame error
* occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame
* and discards frames received after the second errored frame until the first errored frame is recovered. Then, a
* REJ is issued to recover the second errored frame and all subsequent discarded frames.
*
*------------------------------------------------------------------------------*/
static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len)
{
if (ns == S->vr) {
// The receive sequence number, N(S), is the same as what we were expecting, V(R).
// Send it to the application and increment the next expected.
// It is possible that this was resent and we tucked away others with the following
// sequence numbers. If so, process them too.
SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__));
S->reject_exception = 0;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr);
ax25_safe_print (info_ptr, info_len, 1);
dw_printf ("\"\n");
}
dl_data_indication (S, pid, info_ptr, info_len);
if (S->rxdata_by_ns[ns] != NULL) {
// There is a possibility that we might have another received frame stashed
// away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidently
// show up at some future inopportune time.
cdata_delete (S->rxdata_by_ns[ns]);
S->rxdata_by_ns[ns] = NULL;
}
while (S->rxdata_by_ns[S->vr] != NULL) {
// dl_data_indication - send connected data to client application.
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("call dl_data_indication() at %s %d, N(S)=%d, V(R)=%d, data=\"", __func__, __LINE__, ns, S->vr);
ax25_safe_print (S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len, 1);
dw_printf ("\"\n");
}
dl_data_indication (S, S->rxdata_by_ns[S->vr]->pid, S->rxdata_by_ns[S->vr]->data, S->rxdata_by_ns[S->vr]->len);
// Don't keep around anymore after sending it to client app.
cdata_delete (S->rxdata_by_ns[S->vr]);
S->rxdata_by_ns[S->vr] = NULL;
SET_VR(AX25MODULO(S->vr + 1, S->modulo, __FILE__, __func__, __LINE__));
}
if (p) {
// Mentioned in section 6.2.
// The next response frame returned to an I frame with the P bit set to "1", received during the information
// transfer state, is an RR, RNR or REJ response with the F bit set to "1".
int f = 1;
int nr = S->vr; // Next expected sequence number.
cmdres_t cr = cr_res; // response with F set to 1.
packet_t pp;
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
else if ( ! S->acknowledge_pending) {
S->acknowledge_pending = 1; // Probably want to set this before the LM-SEIZE Request
// in case the LM-SEIZE Confirm gets processed before we
// return from it.
// Force start of transmission even if the transmit frame queue is empty.
// Notify me, with lm_seize_confirm, when transmission has started.
// When that event arrives, we check acknowledge_pending and send something
// to be determined later.
lm_seize_request (S->chan);
}
}
else if (S->reject_exception) {
// This is not the sequence we were expecting.
// We previously sent REJ, asking for a resend so don't send another.
// In this case, send RR only if the Poll bit is set.
// Again, reference section 6.2.
if (p) {
int f = 1;
int nr = S->vr; // Next expected sequence number.
cmdres_t cr = cr_res; // response with F set to 1.
packet_t pp;
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
}
else if ( ! S->srej_enabled) {
// The received sequence number is not the expected one and we can't use SREJ.
// The old v2.0 approach is to send and REJ with the number we are expecting.
// This can be very inefficient. For example if we received 1,3,4,5,6 in one transmission,
// we discard 3,4,5,6, and tell the other end to resend everything starting with 2.
// At one time, I had some doubts about when to use command or response for REJ.
// I now believe that reponse, as implied by setting F in the flow chart, is correct.
int f = p;
int nr = S->vr; // Next expected sequence number.
cmdres_t cr = cr_res; // response with F copied from P in I frame.
packet_t pp;
S->reject_exception = 1;
if (s_debug_retry) {
text_color_set(DW_COLOR_ERROR); // make it more noticable.
dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr);
}
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_REJ, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
else {
// Selective reject is enabled so we can use the more efficient method.
// This is normally enabled for v2.2 but XID can be used to change that.
// First we save the current frame so we can retrieve it later after getting the fill in.
if (S->modulo != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: Should not be sending SREJ in basic (modulo 8) mode.\n");
}
#if 1
// Erratum: AX.25 protocol spec did not handle SREJ very well.
// Based on X.25 section 2.4.6.4.
if (is_ns_in_window(S, ns)) {
// X.25 2.4.6.4 (b)
// v(R) < N(S) < V(R)+k so it is in the expected range.
// Save it in the receive buffer.
if (S->rxdata_by_ns[ns] != NULL) {
cdata_delete (S->rxdata_by_ns[ns]);
S->rxdata_by_ns[ns] = NULL;
}
S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len);
if (s_debug_misc) {
dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr);
ax25_safe_print (info_ptr, info_len, 1);
dw_printf ("\"\n");
}
if (p == 1) {
int f = 1;
enquiry_response (S, frame_type_I, f);
}
else if (S->own_receiver_busy) {
cmdres_t cr = cr_res; // send RNR response
int f = 0; // we know p=0 here.
int nr = S->vr;
packet_t pp;
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
else if (S->rxdata_by_ns[ AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__)] == NULL) {
// Ask for missing frames when we don't have N(S)-1 in the receive buffer.
// I would think that we would want to set F when sending N(R) = V(R) but it says use F=0 here.
// Don't understand why yet.
// I like my method better. It does not generate duplicate requests for gaps in the same transmission.
// This creates a cummulative list each time and would cause repeats to be sent more often than necessary.
int resend[128];
int count = 0;
int x;
int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3)
for (x = S->vr; x != ns; x = AX25MODULO(x + 1, S->modulo, __FILE__, __func__, __LINE__)) {
if (S->rxdata_by_ns[x] == NULL) {
resend[count++] = AX25MODULO(x, S->modulo, __FILE__, __func__, __LINE__);
}
}
send_srej_frames (S, resend, count, allow_f1);
}
}
else {
// X.25 2.4.6.4 a)
// N(S) is not in expected range. Discard it. Send response if P=1.
if (p == 1) {
int f = 1;
enquiry_response (S, frame_type_I, f);
}
}
#else // my earlier attempt before taking a close look at X.25 spec.
// Keeping it around for a little while because I might want to
// use earlier technique of sending only needed SREJ for any second
// and later gaps in a single multiframe transmission.
if (S->rxdata_by_ns[ns] != NULL) {
cdata_delete (S->rxdata_by_ns[ns]);
S->rxdata_by_ns[ns] = NULL;
}
S->rxdata_by_ns[ns] = cdata_new(pid, info_ptr, info_len);
S->outstanding_srej[ns] = 0; // Don't care if it was previously set or not.
// We have this one so there is no outstanding SREJ for it.
if (s_debug_misc) {
dw_printf ("%s %d, save to rxdata_by_ns N(S)=%d, V(R)=%d, \"", __func__, __LINE__, ns, S->vr);
ax25_safe_print (info_ptr, info_len, 1);
dw_printf ("\"\n");
}
if (selective_reject_exception(S) == 0) {
// Erratum: This is vastly different than the SDL in the AX.25 protocol spec.
// That would use SREJ if only one was missing and REJ instead.
// Here we do not mix the them.
// This agrees with the X.25 protocol spec that says use one or the other. Not both.
// Suppose we had incoming I frames 0, 3, 7.
// 0 was already processed and V(R)=1 meaning that is the next expected.
// At this point we area processing N(S)=3.
// In this case, we need to ask for a resend of 1 & 2.
// More generally, the range of V(R) thru N(S)-1.
int resend[128];
int count = 0;
int i;
int allow_f1 = 1;
text_color_set(DW_COLOR_ERROR);
dw_printf ("%s:%d, zero exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, S->vr, ns);
for (i = S->vr; i != ns; i = AX25MODULO(i+1, S->modulo, __FILE__, __func__, __LINE__)) {
resend[count++] = i;
}
send_srej_frames (S, resend, count, allow_f1);
}
else {
// Erratum: The SDL says ask for N(S) which is clearly wrong because that's what we just received.
// Instead we want to ask for any missing frames up to but not including N(S).
// Let's continue with the example above. I frames with N(S) of 0, 3, 7.
// selective_reject_exception is non zero meaning there are outstanding requests to resend specified I frames.
// V(R) is still 1 because 0 is the last one received with contiguous N(S) values.
// 3 has been saved into S->rxdata_by_ns.
// We now have N(S)=7. We want to ask for a resend of 4, 5, 6.
// This can be achieved by searching S->rxdata_by_ns, starting with N(S)-1, and counting
// how many empty slots we have before finding a saved frame.
int count = 0;
int first;
text_color_set(DW_COLOR_ERROR);
dw_printf ("%s:%d, %d srej exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__, selective_reject_exception(S), S->vr, ns);
first = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__);
while (S->rxdata_by_ns[first] == NULL) {
if (first == AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__)) {
// Oops! Went too far. This I frame was already processed.
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR calulating what to put in SREJ, %s line %d\n", __func__, __LINE__);
dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, count=%d\n", S->vr, ns, selective_reject_exception(S), first, count);
int k;
for (k=0; k<128; k++) {
if (S->rxdata_by_ns[k] != NULL) {
dw_printf ("rxdata_by_ns[%d] has data\n", k);
}
}
break;
}
count++;
first = AX25MODULO(first - 1, S->modulo, __FILE__, __func__, __LINE__);
}
// Go beyond the slot where we already have an I frame.
first = AX25MODULO(first + 1, S->modulo, __FILE__, __func__, __LINE__);
// The count could be 0. e.g. We got 4 rather than 7 in this example.
if (count > 0) {
int resend[128];
int n;
int allow_f1 = 1;
for (n = 0; n < count; n++) {
resend[n] = AX25MODULO(first + n, S->modulo, __FILE__, __func__, __LINE__);;
}
send_srej_frames (S, resend, count, allow_f1);
}
} /* end SREJ exception */
#endif // my earlier attempt.
// Erratum: original has following but 2006 rev does not.
// I think the 2006 version is correct.
// SREJ does not always satisfy the need for ack.
// There is a special case where F=1. We take care of that inside of send_srej_frames.
#if 0
S->acknowledge_pending = 0;
#endif
} /* end srej_enabled */
} /* end i_frame_continued */
/*------------------------------------------------------------------------------
*
* Name: is_ns_in_window
*
* Purpose: Is the N(S) value of the incoming I frame in the expected range?
*
* Inputs: ns - Sequence from I frame.
*
* Description: With selective reject, it is possible that we could receive a repeat of
* an I frame with N(S) less than V(R). In this case, we just want to
* discard it rather than getting upset and reestablishing the connection.
*
* The X.25 spec,section 2.4.6.4 (b) asks whether V(R) < N(S) < V(R)+k.
*
* The problem here is that it depends on the value of k for the other end.
* X.25 says that both sides need to agree on a common value of k ahead of time.
* We might have k=8 for our sending but the other side could have k=32 so
* this test could fail.
*
* As a hack, we could use the value 63 here. If too small we would discard
* I frames that are in the acceptable range because they would be >= V(R)+k.
* On the other hand, if this value is too big, the range < V(R) would not be
* large enough and we would accept frame we shouldn't.
* As a practical matter, using a window size that large is pretty unlikely.
* Maybe I could put a limit of 63, rather than 127 in the configuration.
*
*------------------------------------------------------------------------------*/
#define GENEROUS_K 63
static int is_ns_in_window (ax25_dlsm_t *S, int ns)
{
int adjusted_vr, adjusted_ns, adjusted_vrpk;
int result;
/* Shift all values relative to V(R) before comparing so we won't have wrap around. */
#define adjust_by_vr(x) (AX25MODULO((x) - S->vr, S->modulo, __FILE__, __func__, __LINE__))
adjusted_vr = adjust_by_vr(S->vr); // A clever compiler would know it is zero.
adjusted_ns = adjust_by_vr(ns);
adjusted_vrpk = adjust_by_vr(S->vr + GENEROUS_K);
result = adjusted_vr < adjusted_ns && adjusted_ns < adjusted_vrpk;
if (s_debug_retry) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("is_ns_in_window, V(R) %d < N(S) %d < V(R)+k %d, returns %d\n", S->vr, ns, S->vr + GENEROUS_K, result);
}
return (result);
}
/*------------------------------------------------------------------------------
*
* Name: send_srej_frames
*
* Purpose: Ask for a resend of I frames with specified sequence numbers.
*
* Inputs: resend - Array of N(S) values for missing I frames.
* count - Number of items in array.
* allow_f1 - When true, set F=1 when asking for V(R).
* X.25 section 2.4.6.4 b) 3) says F should be set to 0
* when receiving I frame out of sequence.
* X.25 sections 2.4.6.11 & 2.3.5.2.2 say set F to 1 when
* responding to command with P=1. (our enquiry_response function).
*
* Future? The X.25 protocol spec allows additional sequence numbers in one frame
* by using the INFO part.
* It would be easy but we haven't done that here to remain compatible with AX.25.
*
*------------------------------------------------------------------------------*/
static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1)
{
int f; // Set if we are ack-ing one before.
int nr;
cmdres_t cr = cr_res; // SREJ is always response.
int i;
packet_t pp;
if (count <= 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, count=%d, %s line %d\n", count, __func__, __LINE__);
return;
}
if (s_debug_retry) {
text_color_set(DW_COLOR_INFO);
dw_printf ("%s line %d\n", __func__, __LINE__);
//dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S));
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr);
dw_printf ("resend[]=");
for (i = 0; i < count; i++) {
dw_printf (" %d", resend[i]);
}
dw_printf ("\n");
dw_printf ("rxdata_by_ns[]=");
for (i = 0; i < 128; i++) {
if (S->rxdata_by_ns[i] != NULL) {
dw_printf (" %d", i);
}
}
dw_printf ("\n");
}
// Something is wrong! We ask for more than the window size.
if (count > S->k_maxframe) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR - Extreme number of SREJ, %s line %d\n", __func__, __LINE__);
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr);
dw_printf ("resend[]=");
for (i = 0; i < count; i++) {
dw_printf (" %d", resend[i]);
}
dw_printf ("\n");
dw_printf ("rxdata_by_ns[]=");
for (i = 0; i < 128; i++) {
if (S->rxdata_by_ns[i] != NULL) {
dw_printf (" %d", i);
}
}
dw_printf ("\n");
}
for (i = 0; i < count; i++) {
nr = resend[i];
f = allow_f1 && (nr == S->vr);
// Possibly set if we are asking for the next after
// the last one received in contiguous order.
if (f) {
// In this case the other end is being
// informed of my V(R) so no additional
// RR etc. is needed.
S->acknowledge_pending = 0;
}
if (nr < 0 || nr >= S->modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, nr=%d, modulo=%d, %s line %d\n", nr, S->modulo, __func__, __LINE__);
nr = AX25MODULO(nr, S->modulo, __FILE__, __func__, __LINE__);
}
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_SREJ, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
} /* end send_srej_frames */
/*------------------------------------------------------------------------------
*
* Name: rr_rnr_frame
*
* Purpose: Process RR or RNR Frame.
* Processing is the essentially the same so they are handled by a single function.
*
* Inputs: S - Data Link State Machine.
* ready - True for RR, false for RNR
* cr - Is this command or response?
* pf - Poll/Final bit.
* nr - N(R) from the frame.
*
* Description: 4.3.2.1. Receive Ready (RR) Command and Response
*
* Receive Ready accomplishes the following:
* a) indicates that the sender of the RR is now able to receive more I frames;
* b) acknowledges properly received I frames up to, and including N(R)-1;and
* c) clears a previously-set busy condition created by an RNR command having been sent.
* The status of the TNC at the other end of the link can be requested by sending an RR command frame with the
* P-bit set to one.
*
* 4.3.2.2. Receive Not Ready (RNR) Command and Response
*
* Receive Not Ready indicates to the sender of I frames that the receiving TNC is temporarily busy and cannot
* accept any more I frames. Frames up to N(R)-1 are acknowledged. Frames N(R) and above that may have been
* transmitted are discarded and must be retransmitted when the busy condition clears.
* The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame.
* The status of the TNC at the other end of the link is requested by sending an RNR command frame with the
* P bit set to one.
*
*------------------------------------------------------------------------------*/
static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr)
{
// dw_printf ("rr_rnr_frame (ready=%d, cr=%d, pf=%d, nr=%d) state=%d\n", ready, cr, pf, nr, S->state);
switch (S->state) {
case state_0_disconnected:
if (cr == cr_cmd) {
cmdres_t r = cr_res; // DM response with F taken from P.
int f = pf;
int nopid = 0; // PID only for I and UI frames.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
// do nothing.
break;
case state_2_awaiting_release:
// Logic from flow chart for "I, RR, RNR, REJ, SREJ commands."
if (cr == cr_cmd && pf == 1) {
cmdres_t r = cr_res; // DM response with F = 1.
int f = 1;
int nopid = 0; // PID applies only for I and UI frames.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
// Erratum: We have a disagreement here between original and 2006 version.
// RR, RNR, REJ, SREJ responses would fall under "all other primitives."
// In the original, we simply ignore it and stay in state 2.
// The 2006 version, page 94, says go into "1 awaiting connection" state.
// That makes no sense to me.
break;
case state_3_connected:
S->peer_receiver_busy = ! ready;
// Erratum: the flow charts have unconditional check_need_for_response here.
// I don't recall exactly why I added the extra test for command and P=1.
// It might have been because we were reporting error A for response with F=1.
// Other than avoiding that error message, this is functionally equivalent.
if (cr == cr_cmd && pf) {
check_need_for_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, cr, pf);
}
if (is_good_nr(S,nr)) {
// dw_printf ("rr_rnr_frame (), line %d, state=%d, good nr=%d, calling check_i_frame_ackd\n", __LINE__, S->state, nr);
check_i_frame_ackd (S, nr);
// keep current state.
}
else {
if (s_debug_retry) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("rr_rnr_frame (), line %d, state=%d, bad nr, calling nr_error_recovery\n", __LINE__, S->state);
}
nr_error_recovery (S);
// My enhancement. Original always sent SABM and went to state 1.
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
break;
case state_4_timer_recovery:
S->peer_receiver_busy = ! ready;
if (cr == cr_res && pf == 1) {
if (s_debug_retry) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("rr_rnr_frame (), Response, pf=1, line %d, state=%d, good nr, calling check_i_frame_ackd\n", __LINE__, S->state);
}
STOP_T1;
select_t1_value(S);
if (is_good_nr(S,nr)) {
SET_VA(nr);
if (S->vs == S->va) { // all caught up with ack from other guy.
START_T3;
S->rc =0; // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
else {
invoke_retransmission (S, nr);
// my addition
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
// We also sent N(R) so no need for extra RR at the end only for that.
STOP_T3;
START_T1;
S->acknowledge_pending = 0;
// end of my addition
}
}
else {
nr_error_recovery (S);
// Erratum: Another case of my enhancement.
// The flow charts go into state 1 after nr_error_recovery.
// I use state 5 instead if we were oprating in extended (modulo 128) mode.
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
}
else {
if (cr == cr_cmd && pf == 1) {
int f = 1;
enquiry_response (S, ready ? frame_type_S_RR : frame_type_S_RNR, f);
}
if (is_good_nr(S,nr)) {
SET_VA(nr);
// REJ state 4 is identical but has conditional invoke_retransmission here.
}
else {
nr_error_recovery (S);
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
}
break;
}
} /* end rr_rnr_frame */
/*------------------------------------------------------------------------------
*
* Name: rej_frame
*
* Purpose: Process REJ Frame.
*
* Inputs: S - Data Link State Machine.
* cr - Is this command or response?
* pf - Poll/Final bit.
* nr - N(R) from the frame.
*
* Description: 4.3.2.2. Receive Not Ready (RNR) Command and Response
*
* ... The RNR condition is cleared by the sending of a UA, RR, REJ or SABM(E) frame. ...
*
*
* 4.3.2.3. Reject (REJ) Command and Response
*
* The reject frame requests retransmission of I frames starting with N(R). Any frames sent with a sequence
* number of N(R)-1 or less are acknowledged. Additional I frames which may exist may be appended to the
* retransmission of the N(R) frame.
* Only one reject frame condition is allowed in each direction at a time. The reject condition is cleared by the
* proper reception of I frames up to the I frame that caused the reject condition to be initiated.
* The status of the TNC at the other end of the link is requested by sending a REJ command frame with the P bit
* set to one.
*
* 4.4.3. Reject (REJ) Recovery
*
* The REJ frame requests a retransmission of I frames following the detection of a N(S) sequence error. Only
* one outstanding "sent REJ" condition is allowed at a time. This condition is cleared when the requested I frame
* has been received.
* A TNC receiving the REJ command clears the condition by resending all outstanding I frames (up to the
* window size), starting with the frame indicated in N(R) of the REJ frame.
*
*
* 4.4.5.1. T1 Timer Recovery
*
* If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I
* frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently
* does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion
* of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described
* in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent
* frame(s), or by the link being reset.
*
* 6.2. Poll/Final (P/F) Bit Procedures
*
* The response frame returned by a TNC depends on the previous command received, as described in the
* following paragraphs.
* ...
*
* The next response frame returned to an I frame with the P bit set to "1", received during the information5
* transfer state, is an RR, RNR or REJ response with the F bit set to "1".
*
* The next response frame returned to a supervisory command frame with the P bit set to "1", received during
* the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1".
* ...
*
* The P bit is used in conjunction with the timeout recovery condition discussed in Section 4.5.5.
* When not used, the P/F bit is set to "0".
*
* 6.4.4.1. Implicit Reject (REJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, the frame is discarded. A REJ frame is sent with a receive sequence number
* equal to one higher than the last correctly received I frame if an uncleared N(S) sequence error condition has not
* been previously established. The received state variable and poll bit of the discarded frame is checked and acted
* upon, if necessary.
* This mode requires no frame queueing and frame resequencing at the receiver. However, because the mode
* requires transmission of frames that may not be in error, its throughput is not as high as selective reject. This
* mode is ineffective on systems with long round-trip delays and high data rates.
*
* 6.4.7. Receiving REJ
*
* After receiving a REJ frame, the transmitting TNC sets its send state variable to the same value as the REJ
* frame's received sequence number in the control field. The TNC then retransmits any I frame(s) outstanding at
* the next available opportunity in accordance with the following:
*
* a) If the TNC is not transmitting at the time and the channel is open, the TNC may begin retransmission of the
* I frame(s) immediately.
* b) If the TNC is operating on a full-duplex channel transmitting a UI or S frame when it receives a REJ frame,
* it may finish sending the UI or S frame and then retransmit the I frame(s).
* c) If the TNC is operating in a full-duplex channel transmitting another I frame when it receives a REJ frame,
* it may abort the I frame it was sending and start retransmission of the requested I frames immediately.
* d) The TNC may send just the one I frame outstanding, or it may send more than the one indicated if more I
* frames followed the first unacknowledged frame, provided that the total to be sent does not exceed the flowcontrol
* window (k frames).
* If the TNC receives a REJ frame with the poll bit set, it responds with either an RR or RNR frame with the
* final bit set before retransmitting the outstanding I frame(s).
*
*------------------------------------------------------------------------------*/
static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr)
{
switch (S->state) {
case state_0_disconnected:
// states 0 and 2 are very similar with one tiny little difference.
if (cr == cr_cmd) {
cmdres_t r = cr_res; // DM response with F taken from P.
int f = pf;
int nopid = 0; // PID is only for I and UI.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
// Do nothing.
break;
case state_2_awaiting_release:
if (cr == cr_cmd && pf == 1) {
cmdres_t r = cr_res; // DM response with F = 1.
int f = 1;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
// Erratum: We have a disagreement here between original and 2006 version.
// RR, RNR, REJ, SREJ responses would fall under "all other primitives."
// In the original, we simply ignore it and stay in state 2.
// The 2006 version, page 94, says go into "1 awaiting connection" state.
// That makes no sense to me.
break;
case state_3_connected:
S->peer_receiver_busy = 0;
// Receipt of the REJ "frame" (either command or response) causes us to
// start resending I frames at the specified number.
// I think there are 3 possibilities here:
// Response is used when incoming I frame processing detects one is missing.
// In this case, F is copied from the I frame P bit. I don't think we care here.
// Command with P=1 is used during timeout recovery.
// The rule is that we are supposed to send a response with F=1 for I, RR, RNR, or REJ with P=1.
check_need_for_response (S, frame_type_S_REJ, cr, pf);
if (is_good_nr(S,nr)) {
SET_VA(nr);
STOP_T1;
STOP_T3;
select_t1_value(S);
invoke_retransmission (S, nr);
// my addition
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
// We also sent N(R) so no need for extra RR at the end only for that.
// We ran into cases where I frame(s) would be resent but lost.
// T1 was stopped so we just waited and waited and waited instead of trying again.
// I added the following after each invoke_retransmission.
// This seems clearer than hiding the timer stuff inside of it.
// T3 is already stopped.
START_T1;
S->acknowledge_pending = 0;
}
else {
nr_error_recovery (S);
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
break;
case state_4_timer_recovery:
S->peer_receiver_busy = 0;
if (cr == cr_res && pf == 1) {
STOP_T1;
select_t1_value(S);
if (is_good_nr(S,nr)) {
SET_VA(nr);
if (S->vs == S->va) {
START_T3;
S->rc =0; // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
else {
invoke_retransmission (S, nr);
// my addition.
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
// We also sent N(R) so no need for extra RR at the end only for that.
STOP_T3;
START_T1;
S->acknowledge_pending = 0;
}
}
else {
nr_error_recovery (S);
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
}
else {
if (cr == cr_cmd && pf == 1) {
int f = 1;
enquiry_response (S, frame_type_S_REJ, f);
}
if (is_good_nr(S,nr)) {
SET_VA(nr);
if (S->vs != S->va) {
// Observation: RR/RNR state 4 is identical but it doesn't have invoke_retransmission here.
invoke_retransmission (S, nr);
// my addition.
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
// We also sent N(R) so no need for extra RR at the end only for that.
STOP_T3;
START_T1;
S->acknowledge_pending = 0;
}
}
else {
nr_error_recovery (S);
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
}
break;
}
} /* end rej_frame */
/*------------------------------------------------------------------------------
*
* Name: srej_frame
*
* Purpose: Process SREJ Response.
*
* Inputs: S - Data Link State Machine.
* cr - Is this command or response?
* f - Final bit. When set, it is ack-ing up thru N(R)-1
* nr - N(R) from the frame. Peer has asked for a resend of I frame with this N(S).
*
* Description: 4.3.2.4. Selective Reject (SREJ) Command and Response
*
* The selective reject, SREJ, frame is used by the receiving TNC to request retransmission of the single I frame
* numbered N(R). If the P/F bit in the SREJ frame is set to "1", then I frames numbered up to N(R)-1 inclusive are
* considered as acknowledged. However, if the P/F bit in the SREJ frame is set to "0", then the N(R) of the SREJ
* frame does not indicate acknowledgement of I frames.
*
* Each SREJ exception condition is cleared (reset) upon receipt of the I frame with an N(S) equal to the N(R)
* of the SREJ frame.
*
* A receiving TNC may transmit one or more SREJ frames, each containing a different N(R) with the P bit set
* to "0", before one or more earlier SREJ exception conditions have been cleared. However, a SREJ is not
* transmitted if an earlier REJ exception condition has not been cleared as indicated in Section 4.5.4. (To do so
* would request retransmission of an I frame that would be retransmitted by the REJ operation.) Likewise, a REJ
* frame is not transmitted if one or more earlier SREJ exception conditions have not been cleared as indicated in
* Section 4.5.4.
*
* I frames transmitted following the I frame indicated by the SREJ frame are not retransmitted as the result of
* receiving a SREJ frame. Additional I frames awaiting initial transmission may be transmitted following the
* retransmission of the specific I frame requested by the SREJ frame.
*
* Erratum: The section above always refers to SREJ "frames." There doesn't seem to be any clue about when
* command vs. response would be used. When we look in the flow charts, we see that we generate only
* responses but the code is there to process command and response slightly differently.
*
* Description: 4.4.4. Selective Reject (SREJ) Recovery
*
* The SREJ command/response initiates more-efficient error recovery by requesting the retransmission of a
* single I frame following the detection of a sequence error. This is an advancement over the earlier versions in
* which the requested I frame was retransmitted togther with all additional I frames subsequently transmitted and
* successfully received.
*
* When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ
* responses, each with the F bit set to "0", and the "sent SREJ" conditions are not cleared when the TNC is ready
* to issue the next response frame with the F bit set to "1", the TNC sends a SREJ response with the F bit set to "1",
* with the same N(R) as the oldest unresolved SREJ frame.
*
* Because an I or S format frame with the F bit set to "1" can cause checkpoint retransmission, a TNC does not
* send SREJ frames until it receives at least one in-sequence I frame, or it perceives by timeout that the checkpoint
* retransmission will not be initiated at the remote TNC.
*
* With respect to each direction of transmission on the data link, one or more "sent SREJ" exception conditions
* from a TNC to another TNC may be established at a time. A "sent SREJ" exception condition is cleared when
* the requested I frame is received.
*
* The SREJ frame may be repeated when a TNC perceives by timeout that a requested I frame will not be
* received, because either the requested I frame or the SREJ frame was in error or lost.
*
* When appropriate, a TNC receiving one or more SREJ frames initiates retransmission of the individual I
* frames indicated by the N(R) contained in each SREJ frame. After having retransmitted the above frames, new
* I frames are transmitted later if they become available.
*
* When a TNC receives and acts on one or more SREJ commands, each with the P bit set to "0", or an SREJ
* command with the P bit set to "1", or one or more SREJ responses each with the F bit set to "0", it disables any
* action on the next SREJ response frame if that SREJ frame has the F bit set to "1" and has the same N(R) (i.e.,
* the same value and the same numbering cycle) as a previously actioned SREJ frame, and if the resultant
* retransmission was made following the transmission of the P bit set to a "1".
* When the SREJ mechanism is used, the receiving station retains correctly-received I frames and delivers
* them to the higher layer in sequence number order.
*
*
* 6.4.4.2. Selective Reject (SREJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, the frame is retained. SREJ frames are sent with a receive sequence number
* equal to the value N(R) of the missing frame, and P=1 if an uncleared SREJ condition has not been previously
* established. If an SREJ condition is already pending, an SREJ will be sent with P=0. The received state variable
* and poll bit of the received frame are checked and acted upon, if necessary.
*
* This mode requires frame queueing and frame resequencing at the receiver. The holding of frames can
* consume precious buffer space, especially if the user device has limited memory available and several active
* links are operational.
*
*
* 6.4.4.3. Selective Reject-Reject (SREJ/REJ)
*
* When an I frame is received with a correct FCS but its send sequence number N(S) does not match the current
* receiver's receive state variable, and if N(S) indicates 2 or more frames are missing, a REJ frame is transmitted.
* All subsequently received frames are discarded until the lost frame is correctly received. If only one frame is
* missing, a SREJ frame is sent with a receive sequence number equal to the value N(R) of the missing frame. The
* received state variable and poll bit of the received frame are checked and acted upon. If another frame error
* occurs prior to recovery of the SREJ condition, the receiver saves all frames received after the first errored frame
* and discards frames received after the second errored frame until the first errored frame is recovered. Then, a
* REJ is issued to recover the second errored frame and all subsequent discarded frames.
*
* X.25: States that SREJ is only response. I'm following that and it simplifies matters.
*
* X.25 2.4.6.6.1 & 2.4.6.6.2 make a distinction between F being 0 or 1 besides copying N(R) into V(A).
* They talk about sending a poll under some conditions.
* We don't do that here. It seems to work reliably so leave well enough alone.
*
*------------------------------------------------------------------------------*/
static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int f, int nr)
{
switch (S->state) {
case state_0_disconnected:
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
// Do nothing.
// Erratum: The original spec said stay in same state.
// 2006 revision shows state 5 transitioning into 1. I think that is wrong.
// probably a cut-n-paste from state 1 to 5 and that part not updated.
break;
case state_2_awaiting_release:
// Erratum: Flow chart says send DM(F=1) for "I, RR, RNR, REJ, SREJ commands" and P=1.
// It is wrong for two reasons.
// If SREJ was a command, the P flag has a different meaning than the other Supervisory commands.
// It means ack reception of frames up thru N(R)-1; it is not a poll to get a response.
// Based on X.25, I don't think SREJ can be a command.
// It should say, "I, RR, RNR, REJ commands"
// Erratum: We have a disagreement here between original and 2006 version.
// RR, RNR, REJ, SREJ responses would fall under "all other primitives."
// In the original, we simply ignore it and stay in state 2.
// The 2006 version, page 94, says go into "1 awaiting connection" state.
// That makes no sense to me.
break;
case state_3_connected:
S->peer_receiver_busy = 0;
// Erratum: Flow chart has "check need for response here."
// check_need_for_response() does the following:
// - for command & P=1, send RR or RNR.
// - for response & F=1, error A.
// SREJ can only be a response. We don't want to produce an error when F=1.
if (is_good_nr(S,nr)) {
if (f) {
SET_VA(nr);
}
STOP_T1;
START_T3;
select_t1_value(S);
// Resend I frame with N(S) equal to the N(R) in the SREJ.
// Note: X.25 says info part can contain additional sequence numbers.
// Here we stay with Ax.25 and don't consider the info part.
cdata_t *txdata = S->txdata_by_ns[nr];
if (txdata != NULL) {
cmdres_t cr = cr_cmd;
int i_frame_ns = nr;
int i_frame_nr = S->vr;
int p = 0;
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len);
// dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// my addition
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
// We also sent N(R) so no need for extra RR at the end only for that.
// We would sometimes end up in a situation where T1 was stopped on
// both ends and everyone would wait for the other guy to timeout and do something.
// My solution was to Start T1 after every place we send an I frame if not already there.
STOP_T3;
START_T1;
S->acknowledge_pending = 0;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, nr);
}
// keep same state.
}
else {
nr_error_recovery (S);
// Erratum? Flow chart shows state 1 but that would not be appropriate if modulo is 128.
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
break;
case state_4_timer_recovery:
S->peer_receiver_busy = 0;
// Erratum: Original Flow chart has "check need for response here."
// The 2006 version correctly removed it.
// check_need_for_response() does the following:
// - for command & P=1, send RR or RNR.
// - for response & F=1, error A.
// SREJ can only be a response. We don't want to produce an error when F=1.
// The flow chart splits into two paths for command/response with a lot of duplication.
// Command path has been omitted because SREJ can only be response.
STOP_T1;
select_t1_value(S);
if (is_good_nr(S,nr)) {
if (f) { // f=1 means ack up thru previous sequence.
// Erratum: 2006 version tests "P". Original has "F."
SET_VA(nr);
}
if (S->vs == S->va) { // ACKs all caught up. Back to state 3.
START_T3;
S->rc =0; // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
else {
// Erratum: Difference between two AX.25 revisions.
#if 1 // This is from the original protocol spec.
// Resend I frame with N(S) equal to the N(R) in the SREJ.
cdata_t *txdata = S->txdata_by_ns[nr];
if (txdata != NULL) {
cmdres_t cr = cr_cmd;
int i_frame_ns = nr;
int i_frame_nr = S->vr;
int p = 0;
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, i_frame_nr, i_frame_ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len);
// dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// my addition
// Erratum: We sent I frame(s) and want to timeout if no ack comes back.
// We also sent N(R), from V(R), so no need for extra RR at the end only for that.
// We would sometimes end up in a situation where T1 was stopped on
// both ends and everyone would wait for the other guy to timeout and do something.
// My solution was to Start T1 after every place we send an I frame if not already there.
STOP_T3;
START_T1;
S->acknowledge_pending = 0;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: INTERNAL ERROR for SREJ. I frame for N(S)=%d is not available.\n", S->stream_id, nr);
}
#else // Erratum! This is from the 2006 revision.
// We should resend only the single requested I frame.
// I think there was a cut-n-paste from the REJ flow chart and this particular place did not get changed.
invoke_retransmission(S);
#endif
}
}
else {
nr_error_recovery (S);
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
}
break;
}
} /* end srej_frame */
/*------------------------------------------------------------------------------
*
* Name: sabm_e_frame
*
* Purpose: Process SABM or SABME Frame.
*
* Inputs: S - Data Link State Machine.
*
* extended - True for SABME. False for SABM.
*
* p - Poll bit. TODO: What does it mean in this case?
*
* Description: This is a request, from the other end, to establish a connection.
*
* 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command
*
* The SABM command places two Terminal Node Comtrollers (TNC) in the asynchronous balanced mode
* (modulo 8). This a balanced mode of operation in which both devices are treated as equals or peers.
*
* Information fields are not allowed in SABM commands. Any outstanding I frames left when the SABM
* command is issued remain unacknowledged.
*
* The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the
* earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if
* possible.
*
* 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command
*
* The SABME command places two TNCs in the asynchronous balanced mode extended (modulo 128). This
* is a balanced mode of operation in which both devices are treated as equals or peers.
* Information fields are not allowed in SABME commands. Any outstanding I frames left when the SABME
* command is issued remains unacknowledged.
*
* The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
*
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR. ** (see note below)
*
*
* Note: The KPC-3+, which does not appear to support v2.2, responds with a DM.
* The 2.0 spec, section 2.3.4.3.5, states, "While a DXE is in the disconnected mode, it will respond
* to any command other than a SABM or UI frame with a DM response with the P/F bit set to 1."
* I think it is a bug in the KPC but I can see how someone might implement it that way.
* However, another place says FRMR is sent for any unrecognized frame type. That would seem to take priority.
*
*------------------------------------------------------------------------------*/
static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p)
{
switch (S->state) {
case state_0_disconnected:
// Flow chart has decision: "Able to establish?"
// I think this means, are we willing to accept connection requests?
// We are always willing to accept connections.
// Of course, we wouldn't get this far if local callsigns were not "registered."
if (extended) {
set_version_2_2 (S);
}
else {
set_version_2_0 (S);
}
cmdres_t res = cr_res;
int f = p; // I don't understand the purpose of "P" in SABM/SABME
// but we dutifully copy it into "F" for the UA response.
int nopid = 0; // PID is only for I and UI.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
clear_exception_conditions (S);
SET_VS(0);
SET_VA(0);
SET_VR(0);
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], extended ? "v2.2" : "v2.0");
// dl connect indication - inform the client app.
int incoming = 1;
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
INIT_T1V_SRT;
START_T3;
S->rc =0; // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
break;
case state_1_awaiting_connection:
// Don't combine with state 5. They are slightly different.
if (extended) { // SABME - respond with DM, enter state 5.
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__);
}
else { // SABM - respond with UA.
// Erratum! 2006 version shows SAMBE twice for state 1.
// First one should be SABM in last page of Figure C4.2
// Original appears to be correct.
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// stay in state 1.
}
break;
case state_5_awaiting_v22_connection:
if (extended) { // SABME - respond with UA
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// stay in state 5
}
else { // SABM, respond with UA, enter state 1
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
}
break;
case state_2_awaiting_release:
// Erratum! Flow charts don't list SABME for state 2.
// Probably just want to treat it the same as SABM here.
{
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited
// stay in state 2.
}
break;
case state_3_connected:
case state_4_timer_recovery:
{
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// State 3 & 4 handling are the same except for this one difference.
if (S->state == state_4_timer_recovery) {
if (extended) {
set_version_2_2 (S);
}
else {
set_version_2_0 (S);
}
}
clear_exception_conditions (S);
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error F: Data Link reset; i.e. SABM(e) received in state %d.\n", S->stream_id, S->state);
}
if (S->vs != S->va) {
discard_i_queue (S);
// dl connect indication
int incoming = 1;
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
}
STOP_T1;
START_T3;
SET_VS(0);
SET_VA(0);
SET_VR(0);
S->rc =0; // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
break;
}
} /* end sabm_e_frame */
/*------------------------------------------------------------------------------
*
* Name: disc_frame
*
* Purpose: Process DISC command.
*
* Inputs: S - Data Link State Machine.
* p - Poll bit.
*
* Description: 4.3.3.3. Disconnect (DISC) Command
*
* The DISC command terminates a link session between two stations. An information field is not permitted in
* a DISC command frame.
*
* Prior to acting on the DISC frame, the receiving TNC confirms acceptance of the DISC by issuing a UA
* response frame at its earliest opportunity. The TNC sending the DISC enters the disconnected state when it
* receives the UA response.
*
* Any unacknowledged I frames left when this command is acted upon remain unacknowledged.
*
*
* 6.3.4. Link Disconnection
*
* While in the information-transfer state, either TNC may indicate a request to disconnect the link by transmitting
* a DISC command frame and starting timer T1.
*
* After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected
* state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the
* disconnected state.
*
* If a UA or DM response is not correctly received before T1 times out, the DISC frame is sent again and T1 is
* restarted. If this happens N2 times, the TNC enters the disconnected state.
*
*------------------------------------------------------------------------------*/
static void disc_frame (ax25_dlsm_t *S, int p)
{
switch (S->state) {
case state_0_disconnected:
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
{
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
// keep current state, 0, 1, or 5.
break;
case state_2_awaiting_release:
{
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_0_HI, pp); // expedited
}
// keep current state, 2.
break;
case state_3_connected:
case state_4_timer_recovery:
{
discard_i_queue (S);
cmdres_t res = cr_res;
int f = p;
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_UA, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// dl disconnect *indication*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
STOP_T1;
STOP_T3;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
break;
}
} /* end disc_frame */
/*------------------------------------------------------------------------------
*
* Name: dm_frame
*
* Purpose: Process DM Response Frame.
*
* Inputs: S - Data Link State Machine.
* f - Final bit.
*
* Description: 4.3.3.1. Set Asynchronous Balanced Mode (SABM) Command
*
* The TNC confirms reception and acceptance of a SABM command by sending a UA response frame at the
* earliest opportunity. If the TNC is not capable of accepting a SABM command, it responds with a DM frame if
* possible.
*
* The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
*
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR.
* ( I think the KPC-3+ has a bug - it replys with DM - WB2OSZ )
*
* 4.3.3.5. Disconnected Mode (DM) Response
*
* The disconnected mode response is sent whenever a TNC receives a frame other than a SABM(E) or UI
* frame while in a disconnected mode. The disconnected mode response also indicates that the TNC cannot
* accept a connection at the moment. The DM response does not have an information field.
* Whenever a SABM(E) frame is received and it is determined that a connection is not possible, a DM frame is
* sent. This indicates that the called station cannot accept a connection at that time.
* While a TNC is in the disconnected mode, it responds to any command other than a SABM(E) or UI frame
* with a DM response with the P/F bit set to "1".
*
* 4.3.3.6. Unnumbered Information (UI) Frame
*
* A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when
* in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state.
*
* 6.3.1. AX.25 Link Connection Establishment
*
* If the distant TNC receives a SABM command and cannot enter the indicated state, it sends a DM frame.
* When the originating TNC receives a DM response to its SABM(E) frame, it cancels its T1 timer and does
* not enter the information-transfer state.
*
* 6.3.4. Link Disconnection
*
* After receiving a valid DISC command, the TNC sends a UA response frame and enters the disconnected
* state. After receiving a UA or DM response to a sent DISC command, the TNC cancels timer T1 and enters the
* disconnected state.
*
* 6.5. Resetting Procedure
*
* If a DM response is received, the TNC enters the disconnected state and stops timer T1. If timer T1 expires
* before a UA or DM response frame is received, the SABM(E) is retransmitted and timer T1 restarted. If timer T1
* expires N2 times, the TNC enters the disconnected state. Any previously existing link conditions are cleared.
* Other commands or responses received by the TNC before completion of the reset procedure are discarded.
*
* Erratum: The flow chart shows the same behavior for states 1 and 5.
* For state 5, I think we should treat DM the same as FRMR.
*
*------------------------------------------------------------------------------*/
static void dm_frame (ax25_dlsm_t *S, int f)
{
switch (S->state) {
case state_0_disconnected:
// Do nothing.
break;
case state_1_awaiting_connection:
if (f == 1) {
discard_i_queue (S);
// dl disconnect *indication*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
STOP_T1;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
// keep current state.
}
break;
case state_2_awaiting_release:
if (f == 1) {
// Erratum! Original flow chart, page 91, shows DL-CONNECT confirm.
// It should clearly be DISconnect rather than Connect.
// 2006 has DISCONNECT *Indication*.
// Should it be indication or confirm? Not sure.
// dl disconnect *confirm*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
STOP_T1;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
// keep current state.
}
break;
case state_3_connected:
case state_4_timer_recovery:
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error E: DM received in state %d.\n", S->stream_id, S->state);
}
// dl disconnect *indication*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
discard_i_queue (S);
STOP_T1;
STOP_T3;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
break;
case state_5_awaiting_v22_connection:
#if 0
// Erratum: The flow chart says we should do this.
// I'm not saying it is wrong. I just found it necessary to change this
// to work around an apparent bug in a popular hardware TNC.
if (f == 1) {
discard_i_queue (S);
// dl disconnect *indication*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
STOP_T1;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
// keep current state.
}
#else
// Erratum: This is not in original spec. It's copied from the FRMR case.
// I was expecting FRMR to mean the other end did not understand v2.2.
// Experimentation, with KPC-3+, revealed that we get DM instead.
// One part of the the 2.0 spec sort of indicates this might be intentional.
// But another part more clearly states it should be FRMR.
// At first I thought it was an error in the protocol spec.
// Later, I tend to believe it was just implemented wrong in the KPC-3+.
if (f == 1) {
text_color_set(DW_COLOR_INFO);
dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]);
INIT_T1V_SRT;
// Erratum: page 105. We are in state 5 so I think that means modulo is 128,
// k is probably something > 7, and selective reject is enabled.
// At the end of this we go to state 1.
// It seems to me, that we really want to set version 2.0 in here so we have
// compatible settings.
set_version_2_0 (S);
establish_data_link (S);
S->layer_3_initiated = 1;
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
}
#endif
break;
}
} /* end dm_frame */
/*------------------------------------------------------------------------------
*
* Name: UA_frame
*
* Purpose: Process UA Response Frame.
*
* Inputs: S - Data Link State Machine.
* f - Final bit.
*
* Description: 4.3.3.4. Unnumbered Acknowledge (UA) Response
*
* The UA response frame acknowledges the reception and acceptance of a SABM(E) or DISC command
* frame. A received command is not actually processed until the UA response frame is sent. Information fields are
* not permitted in a UA frame.
*
* 4.4.1. TNC Busy Condition
*
* When a TNC is temporarily unable to receive I frames (e.g., when receive buffers are full), it sends a Receive
* Not Ready (RNR) frame. This informs the sending TNC that the receiving TNC cannot handle any more I
* frames at the moment. This receiving TNC clears this condition by the sending a UA, RR, REJ or SABM(E)
* command frame.
*
* 6.2. Poll/Final (P/F) Bit Procedures
*
* The response frame returned by a TNC depends on the previous command received, as described in the
* following paragraphs.
* The next response frame returned by the TNC to a SABM(E) or DISC command with the P bit set to "1" is a
* UA or DM response with the F bit set to "1".
*
* 6.3.1. AX.25 Link Connection Establishment
*
* To connect to a distant TNC, the originating TNC sends a SABM command frame to the distant TNC and
* starts its T1 timer. If the distant TNC exists and accepts the connect request, it responds with a UA response
* frame and resets all of its internal state variables (V(S), V(A) and V(R)). Reception of the UA response frame by
* the originating TNC causes it to cancel the T1 timer and set its internal state variables to "0".
*
* 6.5. Resetting Procedure
*
* A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of
* a FRMR frame from a TNC using an older version of the protocol.
*
*------------------------------------------------------------------------------*/
static void ua_frame (ax25_dlsm_t *S, int f)
{
switch (S->state) {
case state_0_disconnected:
// Erratum: flow chart says errors C and D. Neither one really makes sense.
// "Unexpected UA in states 3, 4, or 5." We are in state 0 here.
// "UA received without F=1 when SABM or DISC was sent P=1."
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state);
}
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
if (f == 1) {
if (S->layer_3_initiated) {
text_color_set(DW_COLOR_INFO);
// TODO: add via if apppropriate.
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0");
// There is a subtle difference here between connect confirm and indication.
// connect *confirm* means "has been made"
// The AGW API distinguishes between incoming (initiated by other station) and
// outgoing (initiated by me) connections.
int incoming = 0;
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
}
else if (S->vs != S->va) {
#if 1
// Erratum: 2006 version has this.
INIT_T1V_SRT;
START_T3; // Erratum: Rather pointless because we immediately stop it below.
// In the original flow chart, that is.
// I think there is an error as explained below.
// In my version this is still pointless because we start T3 later.
#else
// Erratum: Original version has this.
// I think this could be harmful.
// The client app might have been impatient and started sending
// information already. I don't see why we would want to discard it.
discard_i_queue (S);
#endif
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0");
// Erratum: 2006 version says DL-CONNECT *confirm* but original has *indication*.
// connect *indication* means "has been requested".
// *confirm* seems right because we got a reply from the other side.
int incoming = 0;
server_link_established (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], incoming);
}
STOP_T1;
#if 1 // My version.
START_T3;
#else // As shown in flow chart.
STOP_T3; // Erratum? I think this is wrong.
// We are about to enter state 3. When in state 3 either T1 or T3 should be
// running. In state 3, we always see start one / stop the other pairs except where
// we are about to enter a different state.
// Since there is nothing outstanding where we expect a response, T1 would
// not be started.
#endif
SET_VS(0);
SET_VA(0);
SET_VR(0);
select_t1_value (S);
// Erratum: mdl_negotiate_request does not appear in the SDL flow chart.
// It is mentioned here:
//
// C5.3 Internal Operation of the Machine
//
// The Management Data link State Machine handles the negotiation/notification of
// operational parameters. It uses a single command/response exchange to negotiate the
// final values of negotiable parameters.
//
// The station initiating the AX.25 connection will send an XID command after it receives
// the UA frame. If the other station is using a version of AX.25 earlier than 2.2, it will
// respond with an FRMR of the XID command and the default version 2.0 parameters will
// be used. If the other station is using version 2.2 or better, it will respond with an XID
// response.
if (S->state == state_5_awaiting_v22_connection) {
mdl_negotiate_request (S);
}
S->rc =0; // My enhancement. See Erratum note in select_t1_value.
enter_new_state (S, state_3_connected, __func__, __LINE__);
}
else {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id);
}
// stay in current state, either 1 or 5.
}
break;
case state_2_awaiting_release:
// Erratum: 2006 version is missing yes/no labels on this test.
// DL-ERROR Indication does not mention error D.
if (f == 1) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
STOP_T1;
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error D: UA received without F=1 when SABM or DISC was sent P=1.\n", S->stream_id);
}
// stay in same state.
}
break;
case state_3_connected:
case state_4_timer_recovery:
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error C: Unexpected UA in state %d.\n", S->stream_id, S->state);
}
establish_data_link (S);
S->layer_3_initiated = 0;
// Erratum? Flow chart goes to state 1. Wouldn't we want this to be state 5 if modulo is 128?
enter_new_state (S, S->modulo == 128 ? state_5_awaiting_v22_connection : state_1_awaiting_connection, __func__, __LINE__);
break;
}
} /* end ua_frame */
/*------------------------------------------------------------------------------
*
* Name: frmr_frame
*
* Purpose: Process FRMR Response Frame.
*
* Inputs: S - Data Link State Machine.
*
* Description: 4.3.3.2. Set Asynchronous Balanced Mode Extended (SABME) Command
* ...
* The TNC confirms reception and acceptance of a SABME command by sending a UA response frame at the
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR.
*
* 4.3.3.9. FRMR Response Frame
*
* The FRMR response is removed from the standard for the following reasons:
* a) UI frame transmission was not allowed during FRMR recovery;
* b) During FRMR recovery, the link could not be reestablished by the station that sent the FRMR;
* c) The above functions are better handled by simply resetting the link with a SABM(E) + UA exchange;
* d) An implementation that receives and process FRMRs but does not transmit them is compatible with older
* versions of the standard; and
* e) SDL is simplified and removes the need for one state.
* This version of AX.25 operates with previous versions of AX.25. It does not generate a FRMR Response
* frame, but handles error conditions by resetting the link.
*
* 6.3.2. Parameter Negotiation Phase
*
* Parameter negotiation occurs at any time. It is accomplished by sending the XID command frame and
* receiving the XID response frame. Implementations of AX.25 prior to version 2.2 respond to an XID command
* frame with a FRMR response frame. The TNC receiving the FRMR uses a default set of parameters compatible
* with previous versions of AX.25.
*
* 6.5. Resetting Procedure
*
* The link resetting procedure initializes both directions of data flow after a unrecoverable error has occurred.
* This resetting procedure is used only in the information-transfer state of an AX.25 link.
* A TNC initiates a reset procedure whenever it receives an unexpected UA response frame, or after receipt of
* a FRMR frame from a TNC using an older version of the protocol.
*
*------------------------------------------------------------------------------*/
static void frmr_frame (ax25_dlsm_t *S)
{
switch (S->state) {
case state_0_disconnected:
case state_1_awaiting_connection:
case state_2_awaiting_release:
// Ignore it. Keep current state.
break;
case state_3_connected:
case state_4_timer_recovery:
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error K: FRMR not expected in state %d.\n", S->stream_id, S->state);
}
set_version_2_0 (S); // Erratum: FRMR can only be sent by v2.0.
// Need to force v2.0. Should be added to flow chart.
establish_data_link (S);
S->layer_3_initiated = 0;
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
break;
case state_5_awaiting_v22_connection:
text_color_set(DW_COLOR_INFO);
dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]);
INIT_T1V_SRT;
set_version_2_0 (S); // Erratum: Need to force v2.0. This is not in flow chart.
establish_data_link (S);
S->layer_3_initiated = 1; // Erratum? I don't understand the difference here.
// State 1 clears it. State 5 sets it. Why not the same?
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
break;
}
// part of state machine for the XID negotiation.
// I would not expect this to happen.
// To get here:
// We sent SABME. (not SABM)
// Other side responded with UA so it understands v2.2.
// We sent XID command which puts us int the negotiating state.
// Presumably this is in response to the XID and not something else.
// Anyhow, we will fall back to v2.0 parameters.
switch (S->mdl_state) {
case mdl_state_0_ready:
break;
case mdl_state_1_negotiating:
set_version_2_0 (S);
S->mdl_state = mdl_state_0_ready;
break;
}
} /* end frmr_frame */
/*------------------------------------------------------------------------------
*
* Name: ui_frame
*
* Purpose: Process XID frame for negotiating protocol parameters.
*
* Inputs: S - Data Link State Machine.
*
* cr - Is it command or response?
*
* pf - Poll/Final bit.
*
* Description: 4.3.3.6. Unnumbered Information (UI) Frame
*
* The Unnumbered Information frame contains PID and information fields and passes information along the
* link outside the normal information controls. This allows information fields to be exchanged on the link, bypassing
* flow control.
*
* Because these frames cannot be acknowledged, if one such frame is obliterated, it cannot be recovered.
* A received UI frame with the P bit set causes a response to be transmitted. This response is a DM frame when
* in the disconnected state, or an RR (or RNR, if appropriate) frame in the information transfer state.
*
* Reality: The data link state machine was an add-on after APRS and client APIs were already done.
* UI frames don't go thru here for normal operation.
* The only reason we have this function is so that we can send a response to a UI command with P=1.
*
*------------------------------------------------------------------------------*/
static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf)
{
if (cr == cr_cmd && pf == 1) {
switch (S->state) {
case state_0_disconnected:
case state_1_awaiting_connection:
case state_2_awaiting_release:
case state_5_awaiting_v22_connection:
{
cmdres_t r = cr_res; // DM response with F taken from P.
int nopid = 0; // PID applies only for I and UI frames.
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, r, frame_type_U_DM, pf, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
break;
case state_3_connected:
case state_4_timer_recovery:
enquiry_response (S, frame_type_U_UI, pf);
break;
}
}
} /* end ui_frame */
/*------------------------------------------------------------------------------
*
* Name: xid_frame
*
* Purpose: Process XID frame for negotiating protocol parameters.
*
* Inputs: S - Data Link State Machine.
*
* cr - Is it command or response?
*
* pf - Poll/Final bit.
*
* Description: 4.3.3.7 Exchange Identification (XID) Frame
*
* The Exchange Identification frame causes the addressed station to identify itself, and
* to provide its characteristics to the sending station. An information field is optional within
* the XID frame. A station receiving an XID command returns an XID response unless a UA
* response to a mode setting command is awaiting transmission, or a FRMR condition
* exists.
*
* The XID frame complies with ISO 8885. Only those fields applicable to AX.25 are
* described. All other fields are set to an appropriate value. This implementation is
* compatible with any implementation which follows ISO 8885. Only the general-purpose
* XID information field identifier is required in this version of AX.25.
*
* The information field consists of zero or more information elements. The information
* elements start with a Format Identifier (FI) octet. The second octet is the Group Identifier
* (GI). The third and forth octets form the Group Length (GL). The rest of the information
* field contains parameter fields.
*
* The FI takes the value 82 hex for the general-purpose XID information. The GI takes
* the value 80 hex for the parameter-negotiation identifier. The GL indicates the length of
* the associated parameter field. This length is expressed as a two-octet binary number
* representing the length of the associated parameter field in octets. The high-order bits of
* length value are in the first of the two octets. A group length of zero indicates the lack of
* an associated parameter field and that all parameters assume their default values. The GL
* does not include its own length or the length of the GI.
*
* The parameter field contains a series of Parameter Identifier (PI), Parameter Length
* (PL), and Parameter Value (PV) set structures, in that order. Each PI identifies a
* parameter and is one octet in length. Each PL indicates the length of the associated PV in
* octets, and is one octet in length. Each PV contains the parameter value and is PL octets
* in length. The PL does not include its own length or the length of its associated PI. A PL
* value of zero indicates that the associated PV is absent; the parameter assumes the
* default value. A PI/PL/PV set may be omitted if it is not required to convey information, or
* if present values for the parameter are to be used. The PI/PL/PV fields are placed into the
* information field of the XID frame in ascending order. There is only one entry for each
* PI/PL/PV field used. A parameter field containing an unrecognized PI is ignored. An
* omitted parameter field assumes the currently negotiated value.
*
*------------------------------------------------------------------------------*/
static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len)
{
struct xid_param_s param;
char desc[120];
int ok;
unsigned char xinfo[40];
int xlen;
cmdres_t res = cr_res;
int f = 1;
int nopid = 0;
packet_t pp;
switch (S->mdl_state) {
case mdl_state_0_ready:
if (cr == cr_cmd) {
if (pf == 1) {
// Take parameters sent by other station.
// Generally we take minimum of what he wants and what I can do.
// Adjust my working configuration and send it back.
ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc));
if (ok) {
negotiation_response (S, ¶m);
xlen = xid_encode (¶m, xinfo);
pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_XID, f, nopid, xinfo, xlen);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error MDL-A: XID command without P=1.\n", S->stream_id);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error MDL-B: Unexpected XID response.\n", S->stream_id);
}
break;
case mdl_state_1_negotiating:
if (cr == cr_res) {
if (pf == 1) {
// Got expected response. Copy into my working parameters.
ok = xid_parse (info_ptr, info_len, ¶m, desc, sizeof(desc));
if (ok) {
complete_negotiation (S, ¶m);
}
S->mdl_state = mdl_state_0_ready;
STOP_TM201;
//#define TEST_TEST 1
#if TEST_TEST // Send TEST command to see how it responds.
// We currently have no Client API for sending this or reporting result.
{
char info[80] = "The quick brown fox jumps over the lazy dog.";
cmdres_t cmd = cr_cmd;
int p = 0;
int nopid = 0;
packet_t pp;
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_TEST, p, nopid, (unsigned char *)info, (int)strlen(info));
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
#endif
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error MDL-D: XID response without F=1.\n", S->stream_id);
}
}
else {
// Not expecting to receive a command when I sent one.
// Flow chart says requeue but I just drop it.
// The other end can retry and maybe I will be back to ready state by then.
}
break;
}
} /* end xid_frame */
/*------------------------------------------------------------------------------
*
* Name: test_frame
*
* Purpose: Process TEST command for checking link.
*
* Inputs: S - Data Link State Machine.
*
* cr - Is it command or response?
*
* pf - Poll/Final bit.
*
* Description: 4.3.3.8. Test (TEST) Frame
*
* The Test command causes the addressed station to respond with the TEST response at the first respond
* opportunity; this performs a basic test of the data-link control. An information field is optional with the TEST
* command. If present, the received information field is returned, if possible, by the addressed station, with the
* TEST response. The TEST command has no effect on the mode or sequence variables maintained by the station.
*
* A FRMR condition may be established if the received TEST command information field exceeds the maximum
* defined storage capability of the station. If a FRMR response is not returned for this condition, a TEST response
* without an information field is returned.
*
* The station considers the data-link layer test terminated on receipt of the TEST response, or when a time-out
* period has expired. The results of the TEST command/response exchange are made available for interrogation
* by a higher layer.
*
* Erratum: TEST frame is not mentioned in the SDL flow charts.
* Don't know how P/F is supposed to be used.
* Here, the response sends back what was received in the command.
*
*------------------------------------------------------------------------------*/
static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len)
{
cmdres_t res = cr_res;
int f = pf;
int nopid = 0;
packet_t pp;
if (cr == cr_cmd) {
pp = ax25_u_frame (S->addrs, S->num_addr, res, frame_type_U_TEST, f, nopid, info_ptr, info_len);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
}
} /* end test_frame */
/*------------------------------------------------------------------------------
*
* Name: dl_timer_expiry
*
* Purpose: Some timer expired. Figure out which one and act accordingly.
*
* Inputs: none.
*
*------------------------------------------------------------------------------*/
void dl_timer_expiry (void)
{
ax25_dlsm_t *p;
double now = dtime_now();
// Examine all of the data link state machines.
// Process only those where timer:
// - is running.
// - is not paused.
// - expiration time has arrived or passed.
for (p = list_head; p != NULL; p = p->next) {
if (p->t1_exp != 0 && p->t1_paused_at == 0 && p->t1_exp <= now) {
p->t1_exp = 0;
p->t1_paused_at = 0;
p->t1_had_expired = 1;
t1_expiry (p);
}
}
for (p = list_head; p != NULL; p = p->next) {
if (p->t3_exp != 0 && p->t3_exp <= now) {
p->t3_exp = 0;
t3_expiry (p);
}
}
for (p = list_head; p != NULL; p = p->next) {
if (p->tm201_exp != 0 && p->tm201_paused_at == 0 && p->tm201_exp <= now) {
p->tm201_exp = 0;
p->tm201_paused_at = 0;
tm201_expiry (p);
}
}
} /* end dl_timer_expiry */
/*------------------------------------------------------------------------------
*
* Name: t1_expiry
*
* Purpose: Handle T1 timer expiration for outstanding I frame or P-bit.
*
* Inputs: S - Data Link State Machine.
*
* Description: 4.4.5.1. T1 Timer Recovery
*
* If a transmission error causes a TNC to fail to receive (or to receive and discard) a single I frame, or the last I
* frame in a sequence of I frames, then the TNC does not detect a send-sequence-number error and consequently
* does not transmit a REJ/SREJ. The TNC that transmitted the unacknowledged I frame(s) following the completion
* of timeout period T1, takes appropriate recovery action to determine when I frame retransmission as described
* in Section 6.4.10 should begin. This condition is cleared by the reception of an acknowledgement for the sent
* frame(s), or by the link being reset.
*
* 6.7.1.1. Acknowledgment Timer T1
*
* T1, the Acknowledgement Timer, ensures that a TNC does not wait indefinitely for a response to a frame it
* sends. This timer cannot be expressed in absolute time; the time required to send frames varies greatly with the
* signaling rate used at Layer 1. T1 should take at least twice the amount of time it would take to send maximum
* length frame to the distant TNC and get the proper response frame back from the distant TNC. This allows time
* for the distant TNC to do some processing before responding.
* If Layer 2 repeaters are used, the value of T1 should be adjusted according to the number of repeaters through
* which the frame is being transferred.
*
*------------------------------------------------------------------------------*/
// Make timer start, stop, expiry a different color to stand out.
#define DW_COLOR_DEBUG_TIMER DW_COLOR_ERROR
static void t1_expiry (ax25_dlsm_t *S)
{
if (s_debug_timers) {
double now = dtime_now();
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("t1_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc);
}
switch (S->state) {
case state_0_disconnected:
// Ignore it.
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
// MAXV22 hack.
// If we already sent the maximum number of SABME, fall back to v2.0 SABM.
if (S->state == state_5_awaiting_v22_connection && S->rc == g_misc_config_p->maxv22) {
set_version_2_0 (S);
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
}
if (S->rc == S->n2_retry) {
discard_i_queue(S);
text_color_set(DW_COLOR_INFO);
dw_printf ("Failed to connect to %s after %d tries.\n", S->addrs[PEERCALL], S->n2_retry);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1);
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
cmdres_t cmd = cr_cmd;
int p = 1;
int nopid = 0;
packet_t pp;
S->rc++;
if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // Keep statistics.
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->state == state_5_awaiting_v22_connection) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
select_t1_value(S);
START_T1;
// Keep same state.
}
break;
case state_2_awaiting_release:
if (S->rc == S->n2_retry) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
cmdres_t cmd = cr_cmd;
int p = 1;
int nopid = 0;
packet_t pp;
S->rc++;
if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc;
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_DISC, p, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
select_t1_value(S);
START_T1;
// stay in same state
}
break;
case state_3_connected:
S->rc = 1;
transmit_enquiry (S);
enter_new_state (S, state_4_timer_recovery, __func__, __LINE__);
break;
case state_4_timer_recovery:
if (S->rc == S->n2_retry) {
// Erratum: 2006 version, page 103, is missing yes/no labels on decision blocks.
if (S->va != S->vr) {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error I: N2 timeouts: unacknowledged data.\n", S->stream_id);
}
}
else if (S->peer_receiver_busy) {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error U: N2 timeouts: extended peer busy condition.\n", S->stream_id);
}
}
else {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error T: N2 timeouts: no response to enquiry.\n", S->stream_id);
}
}
// Erratum: Flow chart says DL-DISCONNECT "request" in both original and 2006 revision.
// That is clearly wrong because a "request" would come FROM the higher level protocol/client app.
// I think it should be "indication" rather than "confirm" because the peer condition is unknown.
// dl disconnect *indication*
text_color_set(DW_COLOR_INFO);
dw_printf ("Stream %d: Disconnected from %s due to timeouts.\n", S->stream_id, S->addrs[PEERCALL]);
server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 1);
discard_i_queue (S);
cmdres_t cr = cr_res; // DM can only be response.
int f = 0; // Erratum: Assuming F=0 because it is not response to P=1
int nopid = 0;
packet_t pp = ax25_u_frame (S->addrs, S->num_addr, cr, frame_type_U_DM, f, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
enter_new_state (S, state_0_disconnected, __func__, __LINE__);
}
else {
S->rc++;
if (S->rc > S->peak_rc_value) S->peak_rc_value = S->rc; // gather statistics.
transmit_enquiry (S);
// Keep same state.
}
break;
}
} /* end t1_expiry */
/*------------------------------------------------------------------------------
*
* Name: t3_expiry
*
* Purpose: Handle T3 timer expiration.
*
* Inputs: S - Data Link State Machine.
*
* Description: TODO: still don't understand this.
*
* 4.4.5.2. Timer T3 Recovery
*
* Timer T3 ensures that the link is still functional during periods of low information transfer. When T1 is not
* running (no outstanding I frames), T3 periodically causes the TNC to poll the other TNC of a link. When T3
* times out, an RR or RNR frame is transmitted as a command with the P bit set, and then T1 is started. When a
* response to this command is received, T1 is stopped and T3 is started. If T1 expires before a response is
* received, then the waiting acknowledgement procedure (Section 6.4.11) is executed.
*
* 6.7.1.3. Inactive Link Timer T3
*
* T3, the Inactive Link Timer, maintains link integrity whenever T1 is not running. It is recommended that
* whenever there are no outstanding unacknowledged I frames or P-bit frames (during the information-transfer
* state), an RR or RNR frame with the P bit set to "1" be sent every T3 time units to query the status of the other
* TNC. The period of T3 is locally defined, and depends greatly on Layer 1 operation. T3 should be greater than
* T1; it may be very large on channels of high integrity.
*
*------------------------------------------------------------------------------*/
static void t3_expiry (ax25_dlsm_t *S)
{
if (s_debug_timers) {
double now = dtime_now();
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("t3_expiry (), [now=%.3f]\n", now - S->start_time);
}
switch (S->state) {
case state_0_disconnected:
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
case state_2_awaiting_release:
case state_4_timer_recovery:
break;
case state_3_connected:
// Erratum: Original sets RC to 0, 2006 revision sets RC to 1 which makes more sense.
S->rc = 1;
transmit_enquiry (S);
enter_new_state (S, state_4_timer_recovery, __func__, __LINE__);
break;
}
} /* end t3_expiry */
/*------------------------------------------------------------------------------
*
* Name: tm201_expiry
*
* Purpose: Handle TM201 timer expiration.
*
* Inputs: S - Data Link State Machine.
*
* Description: This is used when waiting for a response to an XID command.
*
*------------------------------------------------------------------------------*/
static void tm201_expiry (ax25_dlsm_t *S)
{
struct xid_param_s param;
unsigned char xinfo[40];
int xlen;
cmdres_t cmd = cr_cmd;
int p = 1;
int nopid = 0;
packet_t pp;
if (s_debug_timers) {
double now = dtime_now();
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("tm201_expiry (), [now=%.3f], state=%d, rc=%d\n", now - S->start_time, S->state, S->rc);
}
switch (S->mdl_state) {
case mdl_state_0_ready:
// Timer shouldn't be running when in this state.
break;
case mdl_state_1_negotiating:
S->mdl_rc++;
if (S->mdl_rc > S->n2_retry) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error MDL-C: Management retry limit exceeded.\n", S->stream_id);
S->mdl_state = mdl_state_0_ready;
}
else {
// No response. Ask again.
initiate_negotiation (S, ¶m);
xlen = xid_encode (¶m, xinfo);
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
START_TM201;
}
break;
}
} /* end tm201_expiry */
//###################################################################################
//###################################################################################
//
// Subroutines from protocol spec, pages 106 - 109
//
//###################################################################################
//###################################################################################
// FIXME: continue review here.
/*------------------------------------------------------------------------------
*
* Name: nr_error_recovery
*
* Purpose: Try to recover after receiving an expected N(r) value.
*
*------------------------------------------------------------------------------*/
static void nr_error_recovery (ax25_dlsm_t *S)
{
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error J: N(r) sequence error.\n", S->stream_id);
}
establish_data_link (S);
S->layer_3_initiated = 0;
} /* end nr_error_recovery */
/*------------------------------------------------------------------------------
*
* Name: establish_data_link
* (Combined with "establish extended data link")
*
* Purpose: Send SABM or SABME to other station.
*
* Inputs: S->
* addrs destination, source, and optional digi addresses.
* num_addr Number of addresses. Should be 2 .. 10.
* modulo Determines if we send SABME or SABM.
*
* Description: Original spec had two different functions that differed
* only by sending SABM or SABME. Here they are combined into one.
*
*------------------------------------------------------------------------------*/
static void establish_data_link (ax25_dlsm_t *S)
{
cmdres_t cmd = cr_cmd;
int p = 1;
packet_t pp;
int nopid = 0;
clear_exception_conditions (S);
// Erratum: We have an off-by-one error here.
// Flow chart shows setting RC to 0 and we end up sending SAMB(e) 11 times when N2 (RETRY) is 10.
// It should be 1 rather than 0.
S->rc = 1;
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, (S->modulo == 128) ? frame_type_U_SABME : frame_type_U_SABM, p, nopid, NULL, 0);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
STOP_T3;
START_T1;
} /* end establish_data_link */
/*------------------------------------------------------------------------------
*
* Name: clear_exception_conditions
*
*------------------------------------------------------------------------------*/
static void clear_exception_conditions (ax25_dlsm_t *S)
{
S->peer_receiver_busy = 0;
S->reject_exception = 0;
S->own_receiver_busy = 0;
S->acknowledge_pending = 0;
// My enhancement. If we are establishing a new connection, we should discard any saved out of sequence incoming I frames.
int n;
for (n = 0; n < 128; n++) {
if (S->rxdata_by_ns[n] != NULL) {
cdata_delete (S->rxdata_by_ns[n]);
S->rxdata_by_ns[n] = NULL;
}
}
// We retain the transmit I frame queue so we can continue after establishing a new connection.
} /* end clear_exception_conditions */
/*------------------------------------------------------------------------------
*
* Name: transmit_enquiry, page 106
*
* Purpose: This is called only when a timer expires.
*
* T1: We sent I frames and timed out waiting for the ack.
* Poke the other end to determine how much it got so far
* so we know where to continue.
*
* T3: Not activity for substantial amount of time.
* Poke the other end to see if it is still there.
*
*
* Observation: This is the only place where we send RR command with P=1.
*
* Sequence of events:
*
* We send some I frames to the other guy.
* There are outstanding sent I frames for which we did not receive ACK.
*
* Timer 1 expires when we are in state 3: send RR/RNR command P=1 (here). Enter state 4.
* Timer 1 expires when we are in state 4: same until max retry count is exceeded.
*
* Other guy gets RR/RNR command P=1.
* Same action for either state 3 or 4.
* Whether he has outstanding un-ack'ed sent I frames is irrelevent.
* He calls "enquiry response" which sends RR/RNR response F=1.
* (Read about detour 1 below and in enquiry_response.)
*
* I get back RR/RNR response F=1. Still in state 4.
* Of course, N(R) gets copied into V(A).
* Now here is the interesting part.
* If the ACKs are caught up, i.e. V(A) == V(S), stop T1 and enter state 3.
* Otherwise, "invoke retransmission" to resend everything after N(R).
*
*
* Detour 1: You were probably thinking, "Suppose SREJ is enabled and the other guy
* had a record of the SREJ frames sent which were not answered by filled in
* I frames. Why not send the SREJ again instead of backing up and resending
* stuff which already got there OK?"
*
* The code to handle incoming SREJ in state 4 is there but stop T1 is in the
* wrong place as mentioned above.
*
*------------------------------------------------------------------------------*/
static void transmit_enquiry (ax25_dlsm_t *S)
{
int p = 1;
int nr = S->vr;
cmdres_t cmd = cr_cmd;
packet_t pp;
if (s_debug_retry) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n****** TRANSMIT ENQUIRY ******\n\n");
}
// This is the ONLY place that we send RR/RNR *command* with P=1.
// Everywhere else should be response.
// I don't think we ever use RR/RNR command P=0 but need to check on that.
pp = ax25_s_frame (S->addrs, S->num_addr, cmd, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, p);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
START_T1;
} /* end transmit_enquiry */
/*------------------------------------------------------------------------------
*
* Name: enquiry_response
*
* Inputs: frame_type - Type of frame received or frame_not_AX25 for LM seize confirm.
* I think that this function is being called from too many
* different contexts where it really needs to react differently.
* So pass in more information about where we are coming from.
*
* F - Always specified as parameter in the references.
*
* Description: This is called for:
* - UI command with P=1 then F=1.
* - LM seize confirm with ack pending then F=0. (TODO: not clear on this yet.)
* TODO: I think we want to ensure that this function is called ONLY
* for RR/RNR/I command with P=1. LM Seize confirm can do its own thing and
* not get involved in this complication.
* - check_need_for_response(), command & P=1, then F=1
* - RR/RNR/REJ command & P=1, then F=1
*
* In all cases, we see that F has been specified, usually 1 because it is
* a response to a command with P=1.
* Specifying F would imply response when the flow chart says RR/RNR command.
* The documentation says:
*
* 6.2. Poll/Final (P/F) Bit Procedures
*
* The next response frame returned to an I frame with the P bit set to "1", received during the information
* transfer state, is an RR, RNR or REJ response with the F bit set to "1".
*
* The next response frame returned to a supervisory command frame with the P bit set to "1", received during
* the information transfer state, is an RR, RNR or REJ response frame with the F bit set to "1".
*
* Erattum! The flow chart says RR/RNR *command* but I'm confident should be response.
*
* Erratum: Ax.25 spec has nothing here for SREJ. See X.25 2.4.6.11 for explanation.
*
*------------------------------------------------------------------------------*/
static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f)
{
cmdres_t cr = cr_res; // Response, not command as seen in flow chart.
int nr = S->vr;
packet_t pp;
if (s_debug_retry) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n****** ENQUIRY RESPONSE F=%d ******\n\n", f);
}
#if 1 // Detour 1
// My addition, Based on X.25 2.4.6.11.
// Only for RR, RNR, I.
// See sequence of events in transmit_enquiry comments.
if (f == 1 && (frame_type == frame_type_S_RR || frame_type == frame_type_S_RNR || frame_type == frame_type_I)) {
if (S->own_receiver_busy) {
// I'm busy.
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RNR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
else if (S->srej_enabled) {
// SREJ is enabled. This is based on X.25 2.4.6.11.
if (S->modulo != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: enquiry response should not be sending SREJ for modulo 8.\n");
}
// Suppose we received I frames with N(S) of 0, 3, 7.
// V(R) is still 1 because 0 is the last one received with contiguous N(S) values.
// 3 and 7 have been saved into S->rxdata_by_ns.
// We have outstanding requests to resend 1, 2, 4, 5, 6.
// Either those requests or the replies got lost.
// The other end timed out and asked us what is happening by sending RR/RNR command P=1.
// First see if we have any out of sequence frames in the receive buffer.
int last;
last = AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__);
while (last != S->vr && S->rxdata_by_ns[last] == NULL) {
last = AX25MODULO(last - 1, S->modulo, __FILE__, __func__, __LINE__);
}
if (last != S->vr) {
// Ask for missing frames to be sent again. X.25 2.4.6.11 b) & 2.3.5.2.2
int resend[128];
int count = 0;
int j;
int allow_f1 = 1;
j = S->vr;
while (j != last) {
if (S->rxdata_by_ns[j] == NULL) {
resend[count++] = j;
}
j = AX25MODULO(j + 1, S->modulo, __FILE__, __func__, __LINE__);
}
send_srej_frames (S, resend, count, allow_f1);
}
else {
// Not waiting for fill in of missing frames. X.25 2.4.6.11 c)
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
} else {
// SREJ not enabled.
// One might get the idea that it would make sense send REJ here if the reject exception is set.
// However, I can't seem to find that buried in X.25 2.4.5.9.
// And when we look at what happens when RR response, F=1 is received in state 4, it is
// effectively REJ when N(R) is not the same as V(S).
pp = ax25_s_frame (S->addrs, S->num_addr, cr, frame_type_S_RR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
} // end of RR,RNR,I cmd with P=1
else {
// For cases other than (RR, RNR, I) command, P=1.
pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
}
#else
// As found in AX.25 spec.
// Erratum: This is woefully inadequate when SREJ is enabled.
// Erratum: Flow chart says RR/RNR command but I'm confident it should be response.
pp = ax25_s_frame (S->addrs, S->num_addr, cr, S->own_receiver_busy ? frame_type_S_RNR : frame_type_S_RR, S->modulo, nr, f);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->acknowledge_pending = 0;
# endif
} /* end enquiry_response */
/*------------------------------------------------------------------------------
*
* Name: invoke_retransmission
*
* Inputs: nr_input - Resend starting with this.
* Continue will all up to and including current V(S) value.
*
* Description: Resend one or more frames that have already been sent.
*
* This is probably the result of getting REJ asking for a resend.
*
*------------------------------------------------------------------------------*/
static void invoke_retransmission (ax25_dlsm_t *S, int nr_input)
{
// Original flow chart showed saving V(S) into temp variable x,
// using V(S) as loop control variable, and finally restoring it
// to original value before returning.
// Here we just a local variable instead of messing with it.
// This should be equivalent but safer.
int local_vs;
int sent_count = 0;
if (S->txdata_by_ns[nr_input] == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal Error, Can't resend starting with N(S) = %d. It is not available. %s %s %d\n", nr_input, __FILE__, __func__, __LINE__);
return;
}
local_vs = nr_input;
do {
if (S->txdata_by_ns[local_vs] != NULL) {
cmdres_t cr = cr_cmd;
int ns = local_vs;
int nr = S->vr;
int p = 0;
if (s_debug_misc) {
text_color_set(DW_COLOR_INFO);
dw_printf ("invoke_retransmission(): state=%d, Resending N(S) = %d, probably as result of REJ N(R) = %d\n", S->state, ns, nr_input);
}
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p,
S->txdata_by_ns[ns]->pid, (unsigned char *)(S->txdata_by_ns[ns]->data), S->txdata_by_ns[ns]->len);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// Keep it around in case we need to send again.
sent_count++;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal Error, state=%d, need to retransmit N(S) = %d for REJ but it is not available. %s %s %d\n", S->state, local_vs, __FILE__, __func__, __LINE__);
}
local_vs = AX25MODULO(local_vs + 1, S->modulo, __FILE__, __func__, __LINE__);
} while (local_vs != S->vs);
if (sent_count == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal Error, Nothing to retransmit. N(R)=%d, %s %s %d\n", nr_input, __FILE__, __func__, __LINE__);
}
} /* end invoke_retransmission */
/*------------------------------------------------------------------------------
*
* Name: check_i_frame_ackd
*
* Purpose:
*
* Inputs: nr - N(R) from I or S frame, acknowledging receipt thru N(R)-1.
* i.e. The next one expected by the peer is N(R).
*
* Outputs: S->va - updated from nr.
*
* Description: TBD... Document when this is used.
*
*------------------------------------------------------------------------------*/
static void check_i_frame_ackd (ax25_dlsm_t *S, int nr)
{
if (S->peer_receiver_busy) {
SET_VA(nr);
// Erratum? This looks odd to me.
// It doesn't seem right that we would have T3 and T1 running at the same time.
// Normally we stop one when starting the other.
// Should this be Stop T3 instead?
START_T3;
if ( ! IS_T1_RUNNING) {
START_T1;
}
}
else if (nr == S->vs) {
SET_VA(nr);
STOP_T1;
START_T3;
select_t1_value (S);
}
else if (nr != S->va) {
if (s_debug_misc) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check_i_frame_ackd n(r)=%d, v(a)=%d, Set v(a) to new value %d\n", nr, S->va, nr);
}
SET_VA(nr);
START_T1; // Erratum? Flow chart says "restart" rather than "start."
// Is this intentional, what is the difference?
}
} /* check_i_frame_ackd */
/*------------------------------------------------------------------------------
*
* Name: check_need_for_response
*
* Inputs: frame_type - frame_type_S_RR, etc.
*
* cr - Is it a command or response?
*
* pf - P/F from the frame.
*
* Description: This is called for RR, RNR, and REJ frames.
* If it is a command with P=1, we reply with RR or RNR with F=1.
*
*------------------------------------------------------------------------------*/
static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf)
{
if (cr == cr_cmd && pf == 1) {
int f = 1;
enquiry_response (S, frame_type, f);
}
else if (cr == cr_res && pf == 1) {
if (s_debug_protocol_errors) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Stream %d: AX.25 Protocol Error A: F=1 received but P=1 not outstanding.\n", S->stream_id);
}
}
} /* end check_need_for_response */
/*------------------------------------------------------------------------------
*
* Name: ui_check
*
* Description: I don't think we need this because UI frames are processed
* without going thru the data link state machine.
*
*------------------------------------------------------------------------------*/
/*------------------------------------------------------------------------------
*
* Name: select_t1_value
*
* Purpose: Dynamically adjust the T1 timeout value, commonly a fixed time known as FRACK.
*
* Inputs: S->rc Retry counter.
*
* S->srt Smoothed roundtrip time in seconds.
*
* S->t1_remaining_when_last_stopped
* Seconds left on T1 when it is stopped.
*
* Outputs: S->srt New smoothed roundtrip time.
*
* S->t1v How long to wait for an acknowlegement before resending.
* Value used when starting timer T1, in seconds.
* Here it is dynamically adjusted.
*
* Description: How long should we wait for an ACK before sending again or giving up?
* some implementations have a fixed length time. This is usually the FRACK parameter,
* typically 3 seconds (D710A) or 4 seconds (KPC-3+).
*
* This should be increased for each digipeater in the path.
* Here it is dynamically adjusted by taking the average time it takes to get a response
* and then we double it.
*
* Rambling: It seems like a good idea to adapt to channel conditions, such as digipeater delays,
* but it is fraught with peril if you are not careful.
*
* For example, if we accept an incoming connection and only receive some I frames and
* send no I frames, T1 never gets started. In my earlier attempt, 't1_remaining_when_last_stopped'
* had the initial value of 0 lacking any reason to set it differently. The calculation here
* then kept pushing t1v up up up. After receiving 20 I frames and sending none,
* t1v was over 300 seconds!!!
*
* We need some way to indicate that 't1_remaining_when_last_stopped' is not valid and
* not to use it. Rather than adding a new variable, it is set to a negative value
* initially to mean it has not been set yet. That solves one problem.
*
* T1 is paused whenever the channel is busy, either transmitting or receiving,
* so the measured time could turn out to be a tiny fraction of a second, much less than
* the frame transmission time.
* If this gets too low, an unusually long random delay, before the sender's transmission,
* could exceed this. I put in a lower limit for t1v, currently 1 second.
*
* What happens if we get multiple timeouts because we don't get a response?
* For example, when we try to connect to a station which is not there, a KPC-3+ will give
* up and report failure after 10 tries x 4 sec = 40 seconds.
*
* The algorithm in the AX.25 protocol spec shows increasing timeout values.
* It might seem like a good idea but either it was not thought out very well
* or I am not understanding it. If it is doubled each time, it gets awful large
* very quickly. If we try to connect to a station which is not there,
* we want to know within a minute, not an hour later.
*
* Keeping with the spirit of increasing the time but keeping it sane,
* I increase the time linearly by a fraction of a second.
*
*------------------------------------------------------------------------------*/
static void select_t1_value (ax25_dlsm_t *S)
{
float old_srt = S->srt;
// Erratum: I don't think this test for RC == 0 is valid.
// We would need to set RC to 0 whenever we enter state 3 and we don't do that.
// I think a more appropriate test would be to check if we are in state 3.
// When things are going smoothly, it makes sense to fine tune timeout based on smoothed round trip time.
// When in some other state, we might want to slowly increase the time to minimize collisions.
// Maybe the solution is to set RC=0 when we enter state 3.
// TODO: come back and revisit this.
if (S->rc == 0) {
if (S->t1_remaining_when_last_stopped >= 0) { // Negative means invalid, don't use it.
// This is an IIR low pass filter.
// Algebraically equivalent to version in AX.25 protocol spec but I think the
// original intent is clearer by having 1/8 appear only once.
S->srt = 7./8. * S->srt + 1./8. * ( S->t1v - S->t1_remaining_when_last_stopped );
}
// We pause T1 when the channel is busy.
// This includes both receiving someone else and us transmitting.
// This can result in the round trip time going down to almost nothing.
// My enhancement is to prevent srt from going below one second so
// t1v should never be less than 2 seconds.
// When t1v was allowed to go down to 1, we got occastional timeouts
// even under ideal conditions, probably due to random CSMA delay time.
if (S->srt < 1) {
S->srt = 1;
// Add another 2 seconds for each digipeater in path.
if (S->num_addr > 2) {
S->srt += 2 * (S->num_addr - 2);
}
}
S->t1v = S->srt * 2;
}
else {
if (S->t1_had_expired) {
// This goes up exponentially if implemented as documented!
// For example, if we were trying to connect to a station which is not there, we
// would retry after 3, the 8, 16, 32, ... and not time out for over an hour.
// That's ridiculous. Let's try increasing it by a quarter second each time.
// We now give up after about a minute.
// NO! S->t1v = powf(2, S->rc+1) * S->srt;
S->t1v = S->rc * 0.25 + S->srt * 2;
}
}
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, new t1v = %.3f\n",
S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v);
}
if (S->t1v < 0.99 || S->t1v > 30) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR? Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, Extreme new t1v = %.3f\n",
S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v);
}
} /* end select_t1_value */
/*------------------------------------------------------------------------------
*
* Name: set_version_2_0
*
* Erratum: Flow chart refers to T2 which doesn't appear anywhere else.
*
*------------------------------------------------------------------------------*/
static void set_version_2_0 (ax25_dlsm_t *S)
{
S->srej_enabled = 0;
S->modulo = 8;
S->n1_paclen = g_misc_config_p->paclen;
S->k_maxframe = g_misc_config_p->maxframe_basic;
S->n2_retry = g_misc_config_p->retry;
} /* end set_version_2_0 */
/*------------------------------------------------------------------------------
*
* Name: set_version_2_2
*
*------------------------------------------------------------------------------*/
static void set_version_2_2 (ax25_dlsm_t *S)
{
S->srej_enabled = 1;
//S->srej_enabled = 0; // temporarily disable for testing of REJ only with modulo 128
S->modulo = 128;
S->n1_paclen = g_misc_config_p->paclen;
S->k_maxframe = g_misc_config_p->maxframe_extended;
S->n2_retry = g_misc_config_p->retry;
} /* end set_version_2_2 */
/*------------------------------------------------------------------------------
*
* Name: is_good_nr
*
* Purpose: Evaluate condition "V(a) <= N(r) <= V(s)" which appears in flow charts
* for incoming I, RR, RNR, REJ, and SREJ frames.
*
* Inputs: S - state machine. Contains V(a) and V(s).
*
* nr - N(r) found in the incoming frame.
*
* Description: This determines whether the Received Sequence Number, N(R), is in
* the expected range for normal processing or if we have an error
* condition that needs recovery.
*
* This gets tricky due to the wrap around of sequence numbers.
*
* 4.2.4.4. Received Sequence Number N(R)
*
* The received sequence number exists in both I and S frames.
* Prior to sending an I or S frame, this variable is updated to equal that
* of the received state variable, thus implicitly acknowledging the proper
* reception of all frames up to and including N(R)-1.
*
* Pattern noticed: Anytime we have "is_good_nr" returning true, we should always
* - set V(A) from N(R) or
* - call "check_i_frame_acked" which does the same and some timer stuff.
*
*------------------------------------------------------------------------------*/
static int is_good_nr (ax25_dlsm_t *S, int nr)
{
int adjusted_va, adjusted_nr, adjusted_vs;
int result;
/* Adjust all values relative to V(a) before comparing so we won't have wrap around. */
#define adjust_by_va(x) (AX25MODULO((x) - S->va, S->modulo, __FILE__, __func__, __LINE__))
adjusted_va = adjust_by_va(S->va); // A clever compiler would know it is zero.
adjusted_nr = adjust_by_va(nr);
adjusted_vs = adjust_by_va(S->vs);
result = adjusted_va <= adjusted_nr && adjusted_nr <= adjusted_vs;
if (s_debug_misc) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("is_good_nr, V(a) %d <= nr %d <= V(s) %d, returns %d\n", S->va, nr, S->vs, result);
}
return (result);
} /* end is_good_nr */
/*------------------------------------------------------------------------------
*
* Name: i_frame_pop_off_queue
*
* Purpose: Transmit an I frame if we have one in the queue and conditions are right.
* This appears two slightly different ways in the flow charts:
* "frame pop off queue"
* "I frame pops off queue"
*
* Inputs: i_frame_queue - Remove items from here.
* peer_receiver_busy - If other end not busy.
* V(s) - and we haven't reached window size.
* V(a)
* k
*
* Outputs: v(s) is incremented for each processed.
* ack_pending = 0
*
*------------------------------------------------------------------------------*/
static void i_frame_pop_off_queue (ax25_dlsm_t *S)
{
if (s_debug_misc) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("i_frame_pop_off_queue () state=%d\n", S->state);
}
// TODO: Were we expecting something in the queue?
// or is empty an expected situation?
if (S->i_frame_queue == NULL) {
if (s_debug_misc) {
// TODO: add different switch for I frame queue.
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("i_frame_pop_off_queue () queue is empty get out, line %d\n", __LINE__);
}
// I Frame queue is empty.
// Nothing to see here, folks. Move along.
return;
}
switch (S->state) {
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
if (s_debug_misc) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("i_frame_pop_off_queue () line %d\n", __LINE__);
}
// This seems to say remove the I Frame from the queue and discard it if "layer 3 initiated" is set.
// For the case of removing it from the queue and putting it back in we just leave it there.
// Erratum? The flow chart seems to be backwards.
// It would seem like we want to keep it if we are further along in the connection process.
// I don't understand the intention here, and can't make a compelling argument on why it
// is backwards, so it is implemented as documented.
if (S->layer_3_initiated) {
cdata_t *txdata;
if (s_debug_misc) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("i_frame_pop_off_queue () discarding due to L3 init. line %d\n", __LINE__);
}
txdata = S->i_frame_queue; // Remove from head of list.
S->i_frame_queue = txdata->next;
cdata_delete (txdata);
}
break;
case state_3_connected:
case state_4_timer_recovery:
if (s_debug_misc) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("i_frame_pop_off_queue () state %d, line %d\n", S->state, __LINE__);
}
while ( ( ! S->peer_receiver_busy ) &&
S->i_frame_queue != NULL &&
S->vs != AX25MODULO(S->va + S->k_maxframe, S->modulo, __FILE__, __func__, __LINE__) ) {
cdata_t *txdata;
txdata = S->i_frame_queue; // Remove from head of list.
S->i_frame_queue = txdata->next;
txdata->next = NULL;
cmdres_t cr = cr_cmd;
int ns = S->vs;
int nr = S->vr;
int p = 0;
if (s_debug_misc || s_debug_radio) {
//dw_printf ("i_frame_pop_off_queue () ns=%d, queue for transmit \"", ns);
//ax25_safe_print (txdata->data, txdata->len, 1);
//dw_printf ("\"\n");
}
packet_t pp = ax25_i_frame (S->addrs, S->num_addr, cr, S->modulo, nr, ns, p, txdata->pid, (unsigned char *)(txdata->data), txdata->len);
if (s_debug_misc) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("calling lm_data_request for I frame, %s line %d\n", __func__, __LINE__);
}
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
// Stash in sent array in case it gets lost and needs to be sent again.
if (S->txdata_by_ns[ns] != NULL) {
cdata_delete (S->txdata_by_ns[ns]);
}
S->txdata_by_ns[ns] = txdata;
SET_VS(AX25MODULO(S->vs + 1, S->modulo, __FILE__, __func__, __LINE__)); // increment sequence of last sent.
S->acknowledge_pending = 0;
// Erratum: I think we always want to restart T1 when an I frame is sent.
// Otherwise we could time out too soon.
#if 1
STOP_T3;
START_T1;
#else
if ( ! IS_T1_RUNNING) {
STOP_T3;
START_T1;
}
#endif
}
break;
case state_0_disconnected:
case state_2_awaiting_release:
// Do nothing.
break;
}
} /* end i_frame_pop_off_queue */
/*------------------------------------------------------------------------------
*
* Name: discard_i_queue
*
* Purpose: Discard any data chunks waiting to be sent.
*
*------------------------------------------------------------------------------*/
static void discard_i_queue (ax25_dlsm_t *S)
{
cdata_t *t;
while (S->i_frame_queue != NULL) {
t = S->i_frame_queue;
S->i_frame_queue = S->i_frame_queue->next;
cdata_delete (t);
}
} /* end discard_i_queue */
/*------------------------------------------------------------------------------
*
* Name: enter_new_state
*
* Purpose: Switch to new state.
*
* Description: Use a function, rather than setting variable directly, so we have
* one common point for debug output and possibly other things we
* might want to do at a state change.
*
*------------------------------------------------------------------------------*/
// TODO: requeuing...
static void enter_new_state (ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line)
{
if (s_debug_variables) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf (">>> NEW STATE = %d, previously %d, called from %s %d <<<\n", new_state, S->state, from_func, from_line);
dw_printf ("\n");
}
assert (new_state >= 0 && new_state <= 5);
if (( new_state == state_3_connected || new_state == state_4_timer_recovery) &&
S->state != state_3_connected && S->state != state_4_timer_recovery ) {
ptt_set (OCTYPE_CON, S->chan, 1); // Turn on connected indicator if configured.
}
else if (( new_state != state_3_connected && new_state != state_4_timer_recovery) &&
( S->state == state_3_connected || S->state == state_4_timer_recovery ) ) {
ptt_set (OCTYPE_CON, S->chan, 0); // Turn off connected indicator if configured.
// Ideally we should look at any other link state machines
// for this channel and leave the indicator on if any
// are connected. I'm not that worried about it.
}
S->state = new_state;
} /* end enter_new_state */
/*------------------------------------------------------------------------------
*
* Name: mdl_negotiate_request
*
* Purpose: After receiving UA, in response to SABME, this starts up the XID exchange.
*
* Description: Send XID command.
* Start timer TM201 so we can retry if timeout waiting for response.
* Enter MDL negotiating state.
*
*------------------------------------------------------------------------------*/
static void mdl_negotiate_request (ax25_dlsm_t *S)
{
struct xid_param_s param;
unsigned char xinfo[40];
int xlen;
cmdres_t cmd = cr_cmd;
int p = 1;
int nopid = 0;
packet_t pp;
switch (S->mdl_state) {
case mdl_state_0_ready:
initiate_negotiation (S, ¶m);
xlen = xid_encode (¶m, xinfo);
pp = ax25_u_frame (S->addrs, S->num_addr, cmd, frame_type_U_XID, p, nopid, xinfo, xlen);
lm_data_request (S->chan, TQ_PRIO_1_LO, pp);
S->mdl_rc = 0;
START_TM201;
S->mdl_state = mdl_state_1_negotiating;
break;
case mdl_state_1_negotiating:
// SDL says "requeue" but I don't understand how it would be useful or how to do it.
break;
}
} /* end mdl_negotiate_request */
/*------------------------------------------------------------------------------
*
* Name: initiate_negotiation
*
* Purpose: Used when preparing the XID *command*.
*
* Description: Prepare set of parameters to request from the other station.
*
*------------------------------------------------------------------------------*/
static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param)
{
param->full_duplex = 0;
param->rej = S->srej_enabled ? selective_reject : implicit_reject;
param->modulo = S->modulo;
param->i_field_length_rx = S->n1_paclen; // Hmmmm. Should we ask for what the user
// specified for PACLEN or offer the maximum
// that we can handle, AX25_N1_PACLEN_MAX?
param->window_size_rx = S->k_maxframe;
param->ack_timer = (int)(g_misc_config_p->frack * 1000);
param->retries = S->n2_retry;
}
/*------------------------------------------------------------------------------
*
* Name: negotiation_response
*
* Purpose: Used when receiving the XID command and preparing the XID response.
*
* Description: Take what other station has asked for and reduce if we have lesser capabilities.
* For example if other end wants 8k information part we reduce it to 2k.
* Ack time and retries are the opposite, we take the maximum.
*
* Question: If the other send leaves anything undefined should we leave it
* undefined or fill in what we would like before sending it back?
*
* The original version of the protocol spec left this open.
* The 2006 revision, in red, says we should fill in defaults for anything
* not specified. This makes sense. We send back a complete set of parameters
* so both ends should agree.
*
*------------------------------------------------------------------------------*/
static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param)
{
// Full duplex would not be that difficult.
// Just ignore the Carrier Detect when transmitting.
// But we haven't done that yet.
param->full_duplex = 0;
// Other end might want 8.
// Seems unlikely. If it implements XID it should have modulo 128.
if (param->modulo == modulo_unknown) {
param->modulo = 8; // Not specified. Set default.
}
else {
param->modulo = MIN(param->modulo, 128);
}
// We can do REJ or SREJ but won't combine them.
// Erratum: 2006 version, section, 4.3.3.7 says default selective reject - reject.
// We can't do that.
if (param->rej == unknown_reject) {
param->rej = (param->modulo == 128) ? selective_reject : implicit_reject; // not specified, set default
}
else {
param->rej = MIN(param->rej, selective_reject);
}
// We can currently do up to 2k.
// Take minimum of that and what other guy asks for.
if (param->i_field_length_rx == G_UNKNOWN) {
param->i_field_length_rx = 256; // Not specified, take default.
}
else {
param->i_field_length_rx = MIN(param->i_field_length_rx, AX25_N1_PACLEN_MAX);
}
// In theory extended mode can have window size of 127 but
// I'm limiting it to 63 for the reason mentioned in the SREJ logic.
if (param->window_size_rx == G_UNKNOWN) {
param->window_size_rx = (param->modulo == 128) ? 32 : 4; // not specified, set default.
}
else {
if (param->modulo == 128)
param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_EXTENDED_MAX);
else
param->window_size_rx = MIN(param->window_size_rx, AX25_K_MAXFRAME_BASIC_MAX);
}
// Erratum: Unclear. Is the Acknowledgement Timer before or after compensating for digipeaters
// in the path? e.g. Typically TNCs use the FRACK parameter for this and it often defaults to 3.
// However, the actual timeout value might be something like FRACK*(2*m+1) where m is the number of
// digipeaters in the path. I'm assuming this is the FRACK value and any additional time, for
// digipeaters will be added in locally at each end on top of this exchanged value.
if (param->ack_timer == G_UNKNOWN) {
param->ack_timer = 3000; // not specified, set default.
}
else {
param->ack_timer = MAX(param->ack_timer, (int)(g_misc_config_p->frack * 1000));
}
if (param->retries == G_UNKNOWN) {
param->retries = 10; // not specified, set default.
}
else {
param->retries = MAX(param->retries, S->n2_retry);
}
// IMPORTANT: Take values we have agreed upon and put into my running configuration.
complete_negotiation(S, param);
}
/*------------------------------------------------------------------------------
*
* Name: complete_negotiation
*
* Purpose: Used when preparing or receiving the XID *response*.
*
* Description: Take set of parameters which we have agreed upon and apply
* to the running configuration.
*
* TODO: Should do some checking here in case other station
* sends something crazy.
*
*------------------------------------------------------------------------------*/
static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param)
{
if (param->rej != unknown_reject) {
S->srej_enabled = param->rej >= selective_reject;
}
if (param->modulo != modulo_unknown) {
// Disaster if aren't agreeing on this.
S->modulo = param->modulo;
}
if (param->i_field_length_rx != G_UNKNOWN) {
S->n1_paclen = param->i_field_length_rx;
}
if (param->window_size_rx != G_UNKNOWN) {
S->k_maxframe = param->window_size_rx;
}
if (param->ack_timer != G_UNKNOWN) {
S->t1v = param->ack_timer * 0.001;
}
if (param->retries != G_UNKNOWN) {
S->n2_retry = param->retries;
}
}
//###################################################################################
//###################################################################################
//
// Timers.
//
// Start.
// Stop.
// Pause (when channel busy) & resume.
// Is it running?
// Did it expire before being stopped?
// When will next one expire?
//
//###################################################################################
//###################################################################################
static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Start T1 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line);
}
S->t1_exp = now + S->t1v;
if (S->radio_channel_busy) {
S->t1_paused_at = now;
}
else {
S->t1_paused_at = 0;
}
S->t1_had_expired = 0;
} /* end start_t1 */
static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
RESUME_T1; // adjust expire time if paused.
if (S->t1_exp == 0.0) {
// Was already stopped.
}
else {
S->t1_remaining_when_last_stopped = S->t1_exp - now;
if (S->t1_remaining_when_last_stopped < 0) S->t1_remaining_when_last_stopped = 0;
}
// Normally this would be at the top but we don't know time remaining at that point.
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
if (S->t1_exp == 0.0) {
dw_printf ("Stop T1. Wasn't running, [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line);
}
else {
dw_printf ("Stop T1, %.3f remaining, [now=%.3f] from %s %d\n", S->t1_remaining_when_last_stopped, now - S->start_time, from_func, from_line);
}
}
S->t1_exp = 0.0; // now stopped.
S->t1_had_expired = 0; // remember that it did not expire.
} /* end stop_t1 */
static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line)
{
int result = S->t1_exp != 0.0;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("is_t1_running? returns %d\n", result);
}
return (result);
} /* end is_t1_running */
static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->t1_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->t1_paused_at == 0.0) {
// Running and not paused.
double now = dtime_now();
S->t1_paused_at = now;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Paused T1 with %.3f still remaining, [now=%.3f] from %s %d\n", S->t1_exp - now, now - S->start_time, from_func, from_line);
}
}
else {
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("T1 error: Didn't expect pause when already paused.\n");
}
}
} /* end pause_t1 */
static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->t1_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->t1_paused_at == 0.0) {
// Running but not paused.
}
else {
double now = dtime_now();
double paused_for_sec = now - S->t1_paused_at;
S->t1_exp += paused_for_sec;
S->t1_paused_at = 0.0;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Resumed T1 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->t1_exp - now, now - S->start_time);
}
}
} /* end resume_t1 */
// T3 is a lot simpler.
// Here we are talking about minutes of inactivity with the peer
// rather than expecting a response within seconds where timing is more critical.
// We don't need to capture remaining time when stopped.
// I don't think there is a need to pause it due to the large time frame.
static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Start T3 for %.3f sec, [now=%.3f] from %s %d\n", T3_DEFAULT, now - S->start_time, from_func, from_line);
}
S->t3_exp = now + T3_DEFAULT;
}
static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (s_debug_timers) {
double now = dtime_now();
text_color_set(DW_COLOR_DEBUG_TIMER);
if (S->t3_exp == 0.0) {
dw_printf ("Stop T3. Wasn't running.\n");
}
else {
dw_printf ("Stop T3, %.3f remaining, [now=%.3f] from %s %d\n", S->t3_exp - now, now - S->start_time, from_func, from_line);
}
}
S->t3_exp = 0.0;
}
// TM201 is similar to T1.
// It needs to be paused whent the channel is busy.
// Simpler because we don't need to keep track of time remaining when stopped.
static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Start TM201 for t1v = %.3f sec, rc = %d, [now=%.3f] from %s %d\n", S->t1v, S->rc, now - S->start_time, from_func, from_line);
}
S->tm201_exp = now + S->t1v;
if (S->radio_channel_busy) {
S->tm201_paused_at = now;
}
else {
S->tm201_paused_at = 0;
}
} /* end start_tm201 */
static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
double now = dtime_now();
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG_TIMER);
dw_printf ("Stop TM201. [now=%.3f] from %s %d\n", now - S->start_time, from_func, from_line);
}
S->tm201_exp = 0.0; // now stopped.
} /* end stop_tm201 */
static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->tm201_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->tm201_paused_at == 0.0) {
// Running and not paused.
double now = dtime_now();
S->tm201_paused_at = now;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Paused TM201 with %.3f still remaining, [now=%.3f] from %s %d\n", S->tm201_exp - now, now - S->start_time, from_func, from_line);
}
}
else {
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("TM201 error: Didn't expect pause when already paused.\n");
}
}
} /* end pause_tm201 */
static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line)
{
if (S->tm201_exp == 0.0) {
// Stopped so there is nothing to do.
}
else if (S->tm201_paused_at == 0.0) {
// Running but not paused.
}
else {
double now = dtime_now();
double paused_for_sec = now - S->tm201_paused_at;
S->tm201_exp += paused_for_sec;
S->tm201_paused_at = 0.0;
if (s_debug_timers) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Resumed TM201 after pausing for %.3f sec, %.3f still remaining, [now=%.3f]\n", paused_for_sec, S->tm201_exp - now, now - S->start_time);
}
}
} /* end resume_tm201 */
double ax25_link_get_next_timer_expiry (void)
{
double tnext = 0;
ax25_dlsm_t *p;
for (p = list_head; p != NULL; p = p->next) {
// Consider if running and not paused.
if (p->t1_exp != 0 && p->t1_paused_at == 0) {
if (tnext == 0) {
tnext = p->t1_exp;
}
else if (p->t1_exp < tnext) {
tnext = p->t1_exp;
}
}
if (p->t3_exp != 0) {
if (tnext == 0) {
tnext = p->t3_exp;
}
else if (p->t3_exp < tnext) {
tnext = p->t3_exp;
}
}
if (p->tm201_exp != 0 && p->tm201_paused_at == 0) {
if (tnext == 0) {
tnext = p->tm201_exp;
}
else if (p->tm201_exp < tnext) {
tnext = p->tm201_exp;
}
}
}
if (s_debug_timers > 1) {
text_color_set(DW_COLOR_DEBUG);
if (tnext == 0.0) {
dw_printf ("ax25_link_get_next_timer_expiry returns none.\n");
}
else {
dw_printf ("ax25_link_get_next_timer_expiry returns %.3f sec from now.\n",
tnext - dtime_now());
}
}
return (tnext);
} /* end ax25_link_get_next_timer_expiry */
/* end ax25_link.c */
direwolf-1.4/ax25_link.h 0000664 0000000 0000000 00000003470 13100232553 0015134 0 ustar 00root root 0000000 0000000
/* ax25_link.h */
#ifndef AX25_LINK_H
#define AX25_LINK_H 1
#include "ax25_pad.h" // for AX25_MAX_INFO_LEN
#include "dlq.h" // for dlq_item_t
#include "config.h" // for struct misc_config_s
// Limits and defaults for parameters.
#define AX25_N1_PACLEN_MIN 1 // Max bytes in Information part of frame.
#define AX25_N1_PACLEN_DEFAULT 256 // some v2.0 implementations have 128
#define AX25_N1_PACLEN_MAX AX25_MAX_INFO_LEN // from ax25_pad.h
#define AX25_N2_RETRY_MIN 1 // Number of times to retry before giving up.
#define AX25_N2_RETRY_DEFAULT 10
#define AX25_N2_RETRY_MAX 15
#define AX25_T1V_FRACK_MIN 1 // Number of seconds to wait before retrying.
#define AX25_T1V_FRACK_DEFAULT 3 // KPC-3+ has 4. TM-D710A has 3.
#define AX25_T1V_FRACK_MAX 15
#define AX25_K_MAXFRAME_BASIC_MIN 1 // Window size - number of I frames to send before waiting for ack.
#define AX25_K_MAXFRAME_BASIC_DEFAULT 4
#define AX25_K_MAXFRAME_BASIC_MAX 7
#define AX25_K_MAXFRAME_EXTENDED_MIN 1
#define AX25_K_MAXFRAME_EXTENDED_DEFAULT 32
#define AX25_K_MAXFRAME_EXTENDED_MAX 63 // In theory 127 but I'm restricting as explained in SREJ handling.
// Call once at startup time.
void ax25_link_init (struct misc_config_s *pconfig);
// IMPORTANT:
// These functions must be called on a single thread, one at a time.
// The Data Link Queue (DLQ) is used to serialize events from multiple sources.
void dl_connect_request (dlq_item_t *E);
void dl_disconnect_request (dlq_item_t *E);
void dl_data_request (dlq_item_t *E);
void dl_register_callsign (dlq_item_t *E);
void dl_unregister_callsign (dlq_item_t *E);
void dl_client_cleanup (dlq_item_t *E);
void lm_data_indication (dlq_item_t *E);
void lm_channel_busy (dlq_item_t *E);
void dl_timer_expiry (void);
double ax25_link_get_next_timer_expiry (void);
#endif
/* end ax25_link.h */ direwolf-1.4/ax25_pad.c 0000664 0000000 0000000 00000230104 13100232553 0014732 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011 , 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Name: ax25_pad
*
* Purpose: Packet assembler and disasembler.
*
* This was written when I was only concerned about APRS which
* uses only UI frames. ax25_pad2.c, added years later, has
* functions for dealing with other types of frames.
*
* We can obtain AX.25 packets from different sources:
*
* (a) from an HDLC frame.
* (b) from text representation.
* (c) built up piece by piece.
*
* We also want to use a packet in different ways:
*
* (a) transmit as an HDLC frame.
* (b) print in human-readable text.
* (c) take it apart piece by piece.
*
* Looking at the more general case, we also want to modify
* an existing packet. For instance an APRS repeater might
* want to change "WIDE2-2" to "WIDE2-1" and retransmit it.
*
*
* Description:
*
*
* APRS uses only UI frames.
* Each starts with 2-10 addressses (14-70 octets):
*
* * Destination Address (note: opposite order in printed format)
*
* * Source Address
*
* * 0-8 Digipeater Addresses (Could there ever be more as a result of
* digipeaters inserting their own call for
* the tracing feature?
* NO. The limit is 8 when transmitting AX.25 over the
* radio.
* Communication with an IGate server could
* have a longer VIA path but that is only in text form,
* not as an AX.25 frame.)
*
* Each address is composed of:
*
* * 6 upper case letters or digits, blank padded.
* These are shifted left one bit, leaving the LSB always 0.
*
* * a 7th octet containing the SSID and flags.
* The LSB is always 0 except for the last octet of the address field.
*
* The final octet of the Destination has the form:
*
* C R R SSID 0, where,
*
* C = command/response = 1
* R R = Reserved = 1 1
* SSID = substation ID
* 0 = zero
*
* The AX.25 spec states that the RR bits should be 11 if not used.
* There are a couple documents talking about possible uses for APRS.
* I'm ignoring them for now.
* http://www.aprs.org/aprs12/preemptive-digipeating.txt
* http://www.aprs.org/aprs12/RR-bits.txt
*
* I don't recall why I originally intended to set the source/destination C bits both to 1.
* Reviewing this 5 years later, after spending more time delving into the
* AX.25 spec, I think it should be 1 for destination and 0 for source.
* In practice you see all four combinations being used by APRS stations
* and no one really cares about these two bits.
*
* The final octet of the Source has the form:
*
* C R R SSID 0, where,
*
* C = command/response = 1 (originally, now I think it should be 0 for source.)
* (Haven't gone back to check to see what code actually does.)
* R R = Reserved = 1 1
* SSID = substation ID
* 0 = zero (or 1 if no repeaters)
*
* The final octet of each repeater has the form:
*
* H R R SSID 0, where,
*
* H = has-been-repeated = 0 initially.
* Set to 1 after this address has been used.
* R R = Reserved = 1 1
* SSID = substation ID
* 0 = zero (or 1 if last repeater in list)
*
* A digipeater would repeat this frame if it finds its address
* with the "H" bit set to 0 and all earlier repeater addresses
* have the "H" bit set to 1.
* The "H" bit would be set to 1 in the repeated frame.
*
* In standard monitoring format, an asterisk is displayed after the last
* digipeater with the "H" bit set. That indicates who you are hearing
* over the radio.
* (That is if digipeaters update the via path properly. Some don't so
* we don't know who we are hearing. This is discussed in the User Guide.)
* No asterisk means the source is being heard directly.
*
* Example, if we can hear all stations involved,
*
* SRC>DST,RPT1,RPT2,RPT3: -- we heard SRC
* SRC>DST,RPT1*,RPT2,RPT3: -- we heard RPT1
* SRC>DST,RPT1,RPT2*,RPT3: -- we heard RPT2
* SRC>DST,RPT1,RPT2,RPT3*: -- we heard RPT3
*
*
* Next we have:
*
* * One byte Control Field - APRS uses 3 for UI frame
* The more general AX.25 frame can have two.
*
* * One byte Protocol ID - APRS uses 0xf0 for no layer 3
*
* Finally the Information Field of 1-256 bytes.
*
* And, of course, the 2 byte CRC.
*
* The descriptions above, for the C, H, and RR bits, are for APRS usage.
* When operating as a KISS TNC we just pass everything along and don't
* interpret or change them.
*
*
* Constructors: ax25_init - Clear everything.
* ax25_from_text - Tear apart a text string
* ax25_from_frame - Tear apart an AX.25 frame.
* Must be called before any other function.
*
* Get methods: .... - Extract destination, source, or digipeater
* address from frame.
*
* Assumptions: CRC has already been verified to be correct.
*
*------------------------------------------------------------------*/
#define AX25_PAD_C /* this will affect behavior of ax25_pad.h */
#include "direwolf.h"
#include
#include
#include
#include
#include
#include "regex.h"
#if __WIN32__
char *strtok_r(char *str, const char *delim, char **saveptr);
#endif
#include "textcolor.h"
#include "ax25_pad.h"
#include "fcs_calc.h"
/*
* Accumulate statistics.
* If new_count gets much larger than delete_count plus the size of
* the transmit queue we have a memory leak.
*/
static volatile int new_count = 0;
static volatile int delete_count = 0;
static volatile int last_seq_num = 0;
#if AX25MEMDEBUG
int ax25memdebug = 0;
void ax25memdebug_set(void)
{
ax25memdebug = 1;
}
int ax25memdebug_get (void)
{
return (ax25memdebug);
}
int ax25memdebug_seq (packet_t this_p)
{
return (this_p->seq);
}
#endif
#define CLEAR_LAST_ADDR_FLAG this_p->frame_data[this_p->num_addr*7-1] &= ~ SSID_LAST_MASK
#define SET_LAST_ADDR_FLAG this_p->frame_data[this_p->num_addr*7-1] |= SSID_LAST_MASK
/*------------------------------------------------------------------------------
*
* Name: ax25_new
*
* Purpose: Allocate memory for a new packet object.
*
* Returns: Identifier for a new packet object.
* In the current implementation this happens to be a pointer.
*
*------------------------------------------------------------------------------*/
packet_t ax25_new (void)
{
struct packet_s *this_p;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_new(): before alloc, new=%d, delete=%d\n", new_count, delete_count);
#endif
last_seq_num++;
new_count++;
/*
* check for memory leak.
*/
// version 1.4 push up the threshold. We could have considerably more with connected mode.
//if (new_count > delete_count + 100) {
if (new_count > delete_count + 256) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Report to WB2OSZ - Memory leak for packet objects. new=%d, delete=%d\n", new_count, delete_count);
#if AX25MEMDEBUG
// Force on debug option to gather evidence.
ax25memdebug_set();
#endif
}
this_p = calloc(sizeof (struct packet_s), (size_t)1);
if (this_p == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - can't allocate memory in ax25_new.\n");
}
assert (this_p != NULL);
this_p->magic1 = MAGIC;
this_p->seq = last_seq_num;
this_p->magic2 = MAGIC;
this_p->num_addr = (-1);
return (this_p);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_delete
*
* Purpose: Destroy a packet object, freeing up memory it was using.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
void ax25_delete_debug (packet_t this_p, char *src_file, int src_line)
#else
void ax25_delete (packet_t this_p)
#endif
{
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_delete(): before free, new=%d, delete=%d\n", new_count, delete_count);
#endif
if (this_p == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - NULL pointer passed to ax25_delete.\n");
return;
}
delete_count++;
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_delete, seq=%d, called from %s %d, new_count=%d, delete_count=%d\n", this_p->seq, src_file, src_line, new_count, delete_count);
}
#endif
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
this_p->magic1 = 0;
this_p->magic1 = 0;
//memset (this_p, 0, sizeof (struct packet_s));
free (this_p);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_from_text
*
* Purpose: Parse a frame in human-readable monitoring format and change
* to internal representation.
*
* Input: monitor - "TNC-2" monitor format for packet. i.e.
* source>dest[,repeater1,repeater2,...]:information
*
* The information part can have non-printable characters
* in the form of <0xff>. This will be converted to single
* bytes. e.g. <0x0d> is carriage return.
* In version 1.4H we will allow nul characters which means
* we have to maintain a length rather than using strlen().
* I maintain that it violates the spec but want to handle it
* because it does happen and we want to preserve it when
* acting as an IGate rather than corrupting it.
*
* strict - True to enforce rules for packets sent over the air.
* False to be more lenient for packets from IGate server.
*
* Messages from an IGate server can have longer
* addresses after qAC. Up to 9 observed so far.
*
* We can just truncate the name because we will only
* end up discarding it. TODO: check on this.
*
* Returns: Pointer to new packet object in the current implementation.
*
* Outputs: Use the "get" functions to retrieve information in different ways.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line)
#else
packet_t ax25_from_text (char *monitor, int strict)
#endif
{
/*
* Tearing it apart is destructive so make our own copy first.
*/
char stuff[512];
char *pinfo;
char *pa;
char *saveptr; /* Used with strtok_r because strtok is not thread safe. */
int ssid_temp, heard_temp;
char atemp[AX25_MAX_ADDR_LEN];
char info_part[AX25_MAX_INFO_LEN+1];
int info_len;
packet_t this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_from_text, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
/* Is it possible to have a nul character (zero byte) in the */
/* information field of an AX.25 frame? */
/* At this point, we have a normal C string. */
/* It is possible that will convert <0x00> to a nul character later. */
/* There we need to maintain a separate length and not use normal C string functions. */
strlcpy (stuff, monitor, sizeof(stuff));
/*
* Initialize the packet structure with two addresses and control/pid
* for APRS.
*/
memset (this_p->frame_data + AX25_DESTINATION*7, ' ' << 1, 6);
this_p->frame_data[AX25_DESTINATION*7+6] = SSID_H_MASK | SSID_RR_MASK;
memset (this_p->frame_data + AX25_SOURCE*7, ' ' << 1, 6);
this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK;
this_p->frame_data[14] = AX25_UI_FRAME;
this_p->frame_data[15] = AX25_PID_NO_LAYER_3;
this_p->frame_len = 7 + 7 + 1 + 1;
this_p->num_addr = (-1);
assert (ax25_get_num_addr(this_p) == 2);
/*
* Separate the addresses from the rest.
*/
pinfo = strchr (stuff, ':');
if (pinfo == NULL) {
ax25_delete (this_p);
return (NULL);
}
*pinfo = '\0';
pinfo++;
/*
* Separate the addresses.
* Note that source and destination order is swappped.
*/
/*
* Source address.
* Don't use traditional strtok because it is not thread safe.
*/
pa = strtok_r (stuff, ">", &saveptr);
if (pa == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create packet from text. No source address\n");
ax25_delete (this_p);
return (NULL);
}
if ( ! ax25_parse_addr (AX25_SOURCE, pa, strict, atemp, &ssid_temp, &heard_temp)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create packet from text. Bad source address\n");
ax25_delete (this_p);
return (NULL);
}
ax25_set_addr (this_p, AX25_SOURCE, atemp);
ax25_set_h (this_p, AX25_SOURCE); // c/r in this position
ax25_set_ssid (this_p, AX25_SOURCE, ssid_temp);
/*
* Destination address.
*/
pa = strtok_r (NULL, ",", &saveptr);
if (pa == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create packet from text. No destination address\n");
ax25_delete (this_p);
return (NULL);
}
if ( ! ax25_parse_addr (AX25_DESTINATION, pa, strict, atemp, &ssid_temp, &heard_temp)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create packet from text. Bad destination address\n");
ax25_delete (this_p);
return (NULL);
}
ax25_set_addr (this_p, AX25_DESTINATION, atemp);
ax25_set_h (this_p, AX25_DESTINATION); // c/r in this position
ax25_set_ssid (this_p, AX25_DESTINATION, ssid_temp);
/*
* VIA path.
*/
while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) {
int k;
k = this_p->num_addr;
if ( ! ax25_parse_addr (k, pa, strict, atemp, &ssid_temp, &heard_temp)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create packet from text. Bad digipeater address\n");
ax25_delete (this_p);
return (NULL);
}
ax25_set_addr (this_p, k, atemp);
ax25_set_ssid (this_p, k, ssid_temp);
// Does it have an "*" at the end?
// TODO: Complain if more than one "*".
// Could also check for all has been repeated bits are adjacent.
if (heard_temp) {
for ( ; k >= AX25_REPEATER_1; k--) {
ax25_set_h (this_p, k);
}
}
}
/*
* Finally, process the information part.
*
* Translate hexadecimal values like <0xff> to single bytes.
* MIC-E format uses 5 different non-printing characters.
* We might want to manually generate UTF-8 characters such as degree.
*/
//#define DEBUG14H 1
#if DEBUG14H
text_color_set(DW_COLOR_DEBUG);
dw_printf ("BEFORE: %s\nSAFE: ", pinfo);
ax25_safe_print (pinfo, -1, 0);
dw_printf ("\n");
#endif
info_len = 0;
while (*pinfo != '\0' && info_len < AX25_MAX_INFO_LEN) {
if (strlen(pinfo) >= 6 &&
pinfo[0] == '<' &&
pinfo[1] == '0' &&
pinfo[2] == 'x' &&
isxdigit(pinfo[3]) &&
isxdigit(pinfo[4]) &&
pinfo[5] == '>') {
char *p;
info_part[info_len] = strtol (pinfo + 3, &p, 16);
info_len++;
pinfo += 6;
}
else {
info_part[info_len] = *pinfo;
info_len++;
pinfo++;
}
}
info_part[info_len] = '\0';
#if DEBUG14H
text_color_set(DW_COLOR_DEBUG);
dw_printf ("AFTER: %s\nSAFE: ", info_part);
ax25_safe_print (info_part, info_len, 0);
dw_printf ("\n");
#endif
/*
* Append the info part.
*/
memcpy ((char*)(this_p->frame_data+this_p->frame_len), info_part, info_len);
this_p->frame_len += info_len;
return (this_p);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_from_frame
*
* Purpose: Split apart an HDLC frame to components.
*
* Inputs: fbuf - Pointer to beginning of frame.
*
* flen - Length excluding the two FCS bytes.
*
* alevel - Audio level of received signal.
* Maximum range 0 - 100.
* -1 might be used when not applicable.
*
* Returns: Pointer to new packet object or NULL if error.
*
* Outputs: Use the "get" functions to retrieve information in different ways.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_from_frame_debug (unsigned char *fbuf, int flen, alevel_t alevel, char *src_file, int src_line)
#else
packet_t ax25_from_frame (unsigned char *fbuf, int flen, alevel_t alevel)
#endif
{
packet_t this_p;
/*
* First make sure we have an acceptable length:
*
* We are not concerned with the FCS (CRC) because someone else checked it.
*
* Is is possible to have zero length for info?
*
* In the original version, assuming APRS, the answer was no.
* We always had at least 3 octets after the address part:
* control, protocol, and first byte of info part for data type.
*
* In later versions, this restriction was relaxed so other
* variations of AX.25 could be used. Now the minimum length
* is 7+7 for addresses plus 1 for control.
*
*/
if (flen < AX25_MIN_PACKET_LEN || flen > AX25_MAX_PACKET_LEN)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("Frame length %d not in allowable range of %d to %d.\n", flen, AX25_MIN_PACKET_LEN, AX25_MAX_PACKET_LEN);
return (NULL);
}
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_from_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
/* Copy the whole thing intact. */
memcpy (this_p->frame_data, fbuf, flen);
this_p->frame_data[flen] = 0;
this_p->frame_len = flen;
/* Find number of addresses. */
this_p->num_addr = (-1);
(void) ax25_get_num_addr (this_p);
return (this_p);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_dup
*
* Purpose: Make a copy of given packet object.
*
* Inputs: copy_from - Existing packet object.
*
* Returns: Pointer to new packet object or NULL if error.
*
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line)
#else
packet_t ax25_dup (packet_t copy_from)
#endif
{
int save_seq;
packet_t this_p;
this_p = ax25_new ();
assert (this_p != NULL);
save_seq = this_p->seq;
memcpy (this_p, copy_from, sizeof (struct packet_s));
this_p->seq = save_seq;
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_dup, seq=%d, called from %s %d, clone of seq %d\n", this_p->seq, src_file, src_line, copy_from->seq);
}
#endif
return (this_p);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_parse_addr
*
* Purpose: Parse address with optional ssid.
*
* Inputs: position - AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER_1...
* Used for more specific error message. -1 if not used.
*
* in_addr - Input such as "WB2OSZ-15*"
*
* strict - 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
* allows lower case (e.g. "qAR") and two
* alphanumeric characters for the SSID.
* We also get messages like this from a server.
* KB1POR>APU25N,TCPIP*,qAC,T2NUENGLD:...
*
* Outputs: out_addr - Address without any SSID.
* Must be at least AX25_MAX_ADDR_LEN bytes.
*
* out_ssid - Numeric value of SSID.
*
* out_heard - True if "*" found.
*
* Returns: True (1) if OK, false (0) if any error.
*
*
*------------------------------------------------------------------------------*/
static const char *position_name[1 + AX25_MAX_ADDRS] = {
"", "Destination ", "Source ",
"Digi1 ", "Digi2 ", "Digi3 ", "Digi4 ",
"Digi5 ", "Digi6 ", "Digi7 ", "Digi8 " };
int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard)
{
char *p;
char sstr[8]; /* Should be 1 or 2 digits for SSID. */
int i, j, k;
int maxlen;
*out_addr = '\0';
*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. */
maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1);
p = in_addr;
i = 0;
for (p = in_addr; isalnum(*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;
}
out_addr[i++] = *p;
out_addr[i] = '\0';
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;
}
}
j = 0;
sstr[j] = '\0';
if (*p == '-') {
for (p++; isalnum(*p); p++) {
if (j >= 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sSSID is too long. SSID part of \"%s\" has more than 2 characters.\n", position_name[position], in_addr);
return 0;
}
sstr[j++] = *p;
sstr[j] = '\0';
if (strict && ! isdigit(*p)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sSSID must be digits. \"%s\" has letters in SSID.\n", position_name[position], in_addr);
return 0;
}
}
k = atoi(sstr);
if (k < 0 || k > 15) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("%sSSID out of range. SSID of \"%s\" not in range of 0 to 15.\n", position_name[position], in_addr);
return 0;
}
*out_ssid = k;
}
if (*p == '*') {
*out_heard = 1;
p++;
}
if (*p != '\0') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid character \"%c\" found in %saddress \"%s\".\n", *p, position_name[position], in_addr);
return 0;
}
//dw_printf ("ax25_parse_addr out: %s %d %d\n", out_addr, *out_ssid, *out_heard);
return (1);
} /* end ax25_parse_addr */
/*-------------------------------------------------------------------
*
* Name: ax25_check_addresses
*
* Purpose: Check addresses of given packet and print message if any issues.
* We call this when receiving and transmitting.
*
* Inputs: pp - packet object pointer.
*
* Errors: Print error message.
*
* Returns: 1 for all valid. 0 if not.
*
* Examples: I was surprised to get this from an APRS-IS server with
* a lower case source address.
*
* n1otx>APRS,TCPIP*,qAC,THIRD:@141335z4227.48N/07111.73W_348/005g014t044r000p000h60b10075.wview_5_20_2
*
* I haven't gotten to the bottom of this yet but it sounds
* like "q constructs" are somehow getting on to the air when
* they should only appear in conversations with IGate servers.
*
* https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/topics/678
*
* WB0VGI-7>APDW12,W0YC-5*,qAR,AE0RF-10:}N0DZQ-10>APWW10,TCPIP,WB0VGI-7*:;145.230MN*080306z4607.62N/09230.58WrKE0ACL/R 145.230- T146.2 (Pine County ARES)
*
* Typical result:
*
* Digipeater WIDE2 (probably N3LEE-4) audio level = 28(10/6) [NONE] __|||||||
* [0.5] VE2DJE-9>P_0_P?,VE2PCQ-3,K1DF-7,N3LEE-4,WIDE2*:'{S+l <0x1c>>/
* Invalid character "_" in MIC-E destination/latitude.
* Invalid character "_" in MIC-E destination/latitude.
* Invalid character "?" in MIC-E destination/latitude.
* Invalid MIC-E N/S encoding in 4th character of destination.
* Invalid MIC-E E/W encoding in 6th character of destination.
* MIC-E, normal car (side view), Unknown manufacturer, Returning
* N 00 00.0000, E 005 55.1500, 0 MPH
* Invalid character "_" found in Destination address "P_0_P?".
*
* *** The origin and journey of this packet should receive some scrutiny. ***
*
*--------------------------------------------------------------------*/
int ax25_check_addresses (packet_t pp)
{
int n;
char addr[AX25_MAX_ADDR_LEN];
char ignore1[AX25_MAX_ADDR_LEN];
int ignore2, ignore3;
int all_ok = 1;
for (n = 0; n < ax25_get_num_addr(pp); n++) {
ax25_get_addr_with_ssid (pp, n, addr);
all_ok &= ax25_parse_addr (n, addr, 1, ignore1, &ignore2, &ignore3);
}
if (! all_ok) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("*** The origin and journey of this packet should receive some scrutiny. ***\n");
dw_printf ("\n");
}
return (all_ok);
} /* end ax25_check_addresses */
/*------------------------------------------------------------------------------
*
* Name: ax25_unwrap_third_party
*
* Purpose: Unwrap a third party messge from the header.
*
* Inputs: copy_from - Existing packet object.
*
* Returns: Pointer to new packet object or NULL if error.
*
* Example: Input: A>B,C:}D>E,F:info
* Output: D>E,F:info
*
*------------------------------------------------------------------------------*/
packet_t ax25_unwrap_third_party (packet_t from_pp)
{
unsigned char *info_p;
packet_t result_pp;
if (ax25_get_dti(from_pp) != '}') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_unwrap_third_party: wrong data type.\n");
return (NULL);
}
(void) ax25_get_info (from_pp, &info_p);
// Want strict because addresses should conform to AX.25 here.
// That's not the case for something from an Internet Server.
result_pp = ax25_from_text((char *)info_p + 1, 1);
return (result_pp);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_addr
*
* Purpose: Add or change an address.
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Must be either an existing address or one greater
* than the final which causes a new one to be added.
*
* ad - Address with optional dash and substation id.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* TODO: ax25_from_text could use this.
*
* Returns: None.
*
*------------------------------------------------------------------------------*/
void ax25_set_addr (packet_t this_p, int n, char *ad)
{
int ssid_temp, heard_temp;
char atemp[AX25_MAX_ADDR_LEN];
int i;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= 0 && n < AX25_MAX_ADDRS);
//dw_printf ("ax25_set_addr (%d, %s) num_addr=%d\n", n, ad, this_p->num_addr);
if (n >= 0 && n < this_p->num_addr) {
//dw_printf ("ax25_set_addr , existing case\n");
/*
* Set existing address position.
*/
// Why aren't we setting 'strict' here?
// Messages from IGate have q-constructs.
// We use this to parse it and later remove unwanted parts.
ax25_parse_addr (n, ad, 0, atemp, &ssid_temp, &heard_temp);
memset (this_p->frame_data + n*7, ' ' << 1, 6);
for (i=0; i<6 && atemp[i] != '\0'; i++) {
this_p->frame_data[n*7+i] = atemp[i] << 1;
}
ax25_set_ssid (this_p, n, ssid_temp);
}
else if (n == this_p->num_addr) {
//dw_printf ("ax25_set_addr , appending case\n");
/*
* One beyond last position, process as insert.
*/
ax25_insert_addr (this_p, n, ad);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error, ax25_set_addr, bad position %d for '%s'\n", n, ad);
}
//dw_printf ("------\n");
//dw_printf ("dump after ax25_set_addr (%d, %s)\n", n, ad);
//ax25_hex_dump (this_p);
//dw_printf ("------\n");
}
/*------------------------------------------------------------------------------
*
* Name: ax25_insert_addr
*
* Purpose: Insert address at specified position, shifting others up one
* position.
* This is used when a digipeater wants to insert its own call
* for tracing purposes.
* For example:
* W1ABC>TEST,WIDE3-3
* Would become:
* W1ABC>TEST,WB2OSZ-1*,WIDE3-2
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* ad - Address with optional dash and substation id.
*
* Bugs: Little validity or bounds checking is performed. Be careful.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: None.
*
*
*------------------------------------------------------------------------------*/
void ax25_insert_addr (packet_t this_p, int n, char *ad)
{
int ssid_temp, heard_temp;
char atemp[AX25_MAX_ADDR_LEN];
int i;
int expect;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS);
//dw_printf ("ax25_insert_addr (%d, %s)\n", n, ad);
/* Don't do it if we already have the maximum number. */
/* Should probably return success/fail code but currently the caller doesn't care. */
if ( this_p->num_addr >= AX25_MAX_ADDRS) {
return;
}
CLEAR_LAST_ADDR_FLAG;
this_p->num_addr++;
memmove (this_p->frame_data + (n+1)*7, this_p->frame_data + n*7, this_p->frame_len - (n*7));
memset (this_p->frame_data + n*7, ' ' << 1, 6);
this_p->frame_len += 7;
this_p->frame_data[n*7+6] = SSID_RR_MASK;
SET_LAST_ADDR_FLAG;
// Why aren't we setting 'strict' here?
// Messages from IGate have q-constructs.
// We use this to parse it and later remove unwanted parts.
ax25_parse_addr (n, ad, 0, atemp, &ssid_temp, &heard_temp);
memset (this_p->frame_data + n*7, ' ' << 1, 6);
for (i=0; i<6 && atemp[i] != '\0'; i++) {
this_p->frame_data[n*7+i] = atemp[i] << 1;
}
ax25_set_ssid (this_p, n, ssid_temp);
// Sanity check after messing with number of addresses.
expect = this_p->num_addr;
this_p->num_addr = (-1);
if (expect != ax25_get_num_addr (this_p)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error ax25_remove_addr expect %d, actual %d\n", expect, this_p->num_addr);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_remove_addr
*
* Purpose: Remove address at specified position, shifting others down one position.
* This is used when we want to remove something from the digipeater list.
*
* Inputs: n - Index of address. Use the symbols
* AX25_REPEATER1, AX25_REPEATER2, etc.
*
* Bugs: Little validity or bounds checking is performed. Be careful.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: None.
*
*
*------------------------------------------------------------------------------*/
void ax25_remove_addr (packet_t this_p, int n)
{
int expect;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= AX25_REPEATER_1 && n < AX25_MAX_ADDRS);
/* Shift those beyond to fill this position. */
CLEAR_LAST_ADDR_FLAG;
this_p->num_addr--;
memmove (this_p->frame_data + n*7, this_p->frame_data + (n+1)*7, this_p->frame_len - ((n+1)*7));
this_p->frame_len -= 7;
SET_LAST_ADDR_FLAG;
// Sanity check after messing with number of addresses.
expect = this_p->num_addr;
this_p->num_addr = (-1);
if (expect != ax25_get_num_addr (this_p)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error ax25_remove_addr expect %d, actual %d\n", expect, this_p->num_addr);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_num_addr
*
* Purpose: Return number of addresses in current packet.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: Number of addresses in the current packet.
* Should be in the range of 2 .. AX25_MAX_ADDRS.
*
* Version 0.9: Could be zero for a non AX.25 frame in KISS mode.
*
*------------------------------------------------------------------------------*/
int ax25_get_num_addr (packet_t this_p)
{
//unsigned char *pf;
int a;
int addr_bytes;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
/* Use cached value if already set. */
if (this_p->num_addr >= 0) {
return (this_p->num_addr);
}
/* Otherwise, determine the number ofaddresses. */
this_p->num_addr = 0; /* Number of addresses extracted. */
addr_bytes = 0;
for (a = 0; a < this_p->frame_len && addr_bytes == 0; a++) {
if (this_p->frame_data[a] & SSID_LAST_MASK) {
addr_bytes = a + 1;
}
}
if (addr_bytes % 7 == 0) {
int addrs = addr_bytes / 7;
if (addrs >= AX25_MIN_ADDRS && addrs <= AX25_MAX_ADDRS) {
this_p->num_addr = addrs;
}
}
return (this_p->num_addr);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_num_repeaters
*
* Purpose: Return number of repeater addresses in current packet.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: Number of addresses in the current packet - 2.
* Should be in the range of 0 .. AX25_MAX_ADDRS - 2.
*
*------------------------------------------------------------------------------*/
int ax25_get_num_repeaters (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->num_addr >= 2) {
return (this_p->num_addr - 2);
}
return (0);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_addr_with_ssid
*
* Purpose: Return specified address with any SSID in current packet.
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Outputs: station - String representation of the station, including the SSID.
* e.g. "WB2OSZ-15"
* Usually variables will be AX25_MAX_ADDR_LEN bytes
* but 10 would be adequate.
*
* Bugs: No bounds checking is performed. Be careful.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: Character string in usual human readable format,
*
*
*------------------------------------------------------------------------------*/
void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station)
{
int ssid;
char sstr[8]; /* Should be 1 or 2 digits for SSID. */
int i;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (n < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("Address index, %d, is less than zero.\n", n);
strlcpy (station, "??????", 10);
return;
}
if (n >= this_p->num_addr) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error detected in ax25_get_addr_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr);
strlcpy (station, "??????", 10);
return;
}
memset (station, 0, 7);
for (i=0; i<6; i++) {
unsigned char ch;
ch = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
if (ch <= ' ') break;
station[i] = ch;
}
ssid = ax25_get_ssid (this_p, n);
if (ssid != 0) {
snprintf (sstr, sizeof(sstr), "-%d", ssid);
strlcat (station, sstr, 10);
}
} /* end ax25_get_addr_with_ssid */
/*------------------------------------------------------------------------------
*
* Name: ax25_get_addr_no_ssid
*
* Purpose: Return specified address WITHOUT any SSID.
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Outputs: station - String representation of the station, WITHOUT the SSID.
* e.g. "WB2OSZ"
* Usually variables will be AX25_MAX_ADDR_LEN bytes
* but 7 would be adequate.
*
* Bugs: No bounds checking is performed. Be careful.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: Character string in usual human readable format,
*
*
*------------------------------------------------------------------------------*/
void ax25_get_addr_no_ssid (packet_t this_p, int n, char *station)
{
int i;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (n < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error detected in ax25_get_addr_no_ssid, %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("Address index, %d, is less than zero.\n", n);
strlcpy (station, "??????", 7);
return;
}
if (n >= this_p->num_addr) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error detected in ax25_get_no_with_ssid, %s, line %d.\n", __FILE__, __LINE__);
dw_printf ("Address index, %d, is too large for number of addresses, %d.\n", n, this_p->num_addr);
strlcpy (station, "??????", 7);
return;
}
memset (station, 0, 7);
for (i=0; i<6; i++) {
unsigned char ch;
ch = (this_p->frame_data[n*7+i] >> 1) & 0x7f;
if (ch <= ' ') break;
station[i] = ch;
}
} /* end ax25_get_addr_no_ssid */
/*------------------------------------------------------------------------------
*
* Name: ax25_get_ssid
*
* Purpose: Return SSID of specified address in current packet.
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: Substation id, as integer 0 .. 15.
*
*------------------------------------------------------------------------------*/
int ax25_get_ssid (packet_t this_p, int n)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (n >= 0 && n < this_p->num_addr) {
return ((this_p->frame_data[n*7+6] & SSID_SSID_MASK) >> SSID_SSID_SHIFT);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_get_ssid(%d), num_addr=%d\n", n, this_p->num_addr);
return (0);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_ssid
*
* Purpose: Set the SSID of specified address in current packet.
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* ssid - New SSID. Must be in range of 0 to 15.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Bugs: Rewrite to keep call and SSID separate internally.
*
*------------------------------------------------------------------------------*/
void ax25_set_ssid (packet_t this_p, int n, int ssid)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (n >= 0 && n < this_p->num_addr) {
this_p->frame_data[n*7+6] = (this_p->frame_data[n*7+6] & ~ SSID_SSID_MASK) |
((ssid << SSID_SSID_SHIFT) & SSID_SSID_MASK) ;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_set_ssid(%d,%d), num_addr=%d\n", n, ssid, this_p->num_addr);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_h
*
* Purpose: Return "has been repeated" flag of specified address in current packet.
*
* Inputs: n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Bugs: No bounds checking is performed. Be careful.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: True or false.
*
*------------------------------------------------------------------------------*/
int ax25_get_h (packet_t this_p, int n)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= 0 && n < this_p->num_addr);
if (n >= 0 && n < this_p->num_addr) {
return ((this_p->frame_data[n*7+6] & SSID_H_MASK) >> SSID_H_SHIFT);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_get_h(%d), num_addr=%d\n", n, this_p->num_addr);
return (0);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_h
*
* Purpose: Set the "has been repeated" flag of specified address in current packet.
*
* Inputs: n - Index of address. Use the symbols
* Should be in range of AX25_REPEATER_1 .. AX25_REPEATER_8.
*
* Bugs: No bounds checking is performed. Be careful.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: None
*
*------------------------------------------------------------------------------*/
void ax25_set_h (packet_t this_p, int n)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (n >= 0 && n < this_p->num_addr) {
this_p->frame_data[n*7+6] |= SSID_H_MASK;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_set_hd(%d), num_addr=%d\n", n, this_p->num_addr);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_heard
*
* Purpose: Return index of the station that we heard.
*
* Inputs: none
*
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: If any of the digipeaters have the has-been-repeated bit set,
* return the index of the last one. Otherwise return index for source.
*
*------------------------------------------------------------------------------*/
int ax25_get_heard(packet_t this_p)
{
int i;
int result;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
result = AX25_SOURCE;
for (i = AX25_REPEATER_1; i < ax25_get_num_addr(this_p); i++) {
if (ax25_get_h(this_p,i)) {
result = i;
}
}
return (result);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_first_not_repeated
*
* Purpose: Return index of the first repeater that does NOT have the
* "has been repeated" flag set or -1 if none.
*
* Inputs: none
*
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: In range of X25_REPEATER_1 .. X25_REPEATER_8 or -1 if none.
*
*------------------------------------------------------------------------------*/
int ax25_get_first_not_repeated(packet_t this_p)
{
int i;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
for (i = AX25_REPEATER_1; i < ax25_get_num_addr(this_p); i++) {
if ( ! ax25_get_h(this_p,i)) {
return (i);
}
}
return (-1);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_rr
*
* Purpose: Return the two reserved "RR" bits in the specified address field.
*
* Inputs: pp - Packet object.
*
* n - Index of address. Use the symbols
* AX25_DESTINATION, AX25_SOURCE, AX25_REPEATER1, etc.
*
* Returns: 0, 1, 2, or 3.
*
*------------------------------------------------------------------------------*/
int ax25_get_rr (packet_t this_p, int n)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
assert (n >= 0 && n < this_p->num_addr);
if (n >= 0 && n < this_p->num_addr) {
return ((this_p->frame_data[n*7+6] & SSID_RR_MASK) >> SSID_RR_SHIFT);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error: ax25_get_rr(%d), num_addr=%d\n", n, this_p->num_addr);
return (0);
}
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_info
*
* Purpose: Obtain Information part of current packet.
*
* Inputs: this_p - Packet object pointer.
*
* Outputs: paddr - Starting address of information part is returned here.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: Number of octets in the Information part.
* Should be in the range of AX25_MIN_INFO_LEN .. AX25_MAX_INFO_LEN.
*
*------------------------------------------------------------------------------*/
int ax25_get_info (packet_t this_p, unsigned char **paddr)
{
unsigned char *info_ptr;
int info_len;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->num_addr >= 2) {
/* AX.25 */
info_ptr = this_p->frame_data + ax25_get_info_offset(this_p);
info_len = ax25_get_num_info(this_p);
}
else {
/* Not AX.25. Treat Whole packet as info. */
info_ptr = this_p->frame_data;
info_len = this_p->frame_len;
}
/* Add nul character in case caller treats as printable string. */
assert (info_len >= 0);
info_ptr[info_len] = '\0';
*paddr = info_ptr;
return (info_len);
} /* end ax25_get_info */
/*------------------------------------------------------------------------------
*
* Name: ax25_cut_at_crlf
*
* Purpose: Truncate the information part at the first CR or LF.
* This is used for the RF>IS IGate function.
* CR/LF is used as record separator so we must remove it
* before packaging up packet to sending to server.
*
* Inputs: this_p - Packet object pointer.
*
* Outputs: Packet is modified in place.
*
* Returns: Number of characters removed from the end.
* 0 if not changed.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
*------------------------------------------------------------------------------*/
int ax25_cut_at_crlf (packet_t this_p)
{
unsigned char *info_ptr;
int info_len;
int j;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
info_len = ax25_get_info (this_p, &info_ptr);
// Can't use strchr because there is potential of nul character.
for (j = 0; j < info_len; j++) {
if (info_ptr[j] == '\r' || info_ptr[j] == '\n') {
int chop = info_len - j;
this_p->frame_len -= chop;
return (chop);
}
}
return (0);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_dti
*
* Purpose: Get Data Type Identifier from Information part.
*
* Inputs: None.
*
* Assumption: ax25_from_text or ax25_from_frame was called first.
*
* Returns: First byte from the information part.
*
*------------------------------------------------------------------------------*/
int ax25_get_dti (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_info_offset(this_p)]);
}
return (' ');
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_nextp
*
* Purpose: Set next packet object in queue.
*
* Inputs: this_p - Current packet object.
*
* next_p - pointer to next one
*
* Description: This is used to build a linked list for a queue.
*
*------------------------------------------------------------------------------*/
void ax25_set_nextp (packet_t this_p, packet_t next_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
this_p->nextp = next_p;
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_nextp
*
* Purpose: Obtain next packet object in queue.
*
* Inputs: Packet object.
*
* Returns: Following object in queue or NULL.
*
*------------------------------------------------------------------------------*/
packet_t ax25_get_nextp (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
return (this_p->nextp);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_release_time
*
* Purpose: Set release time
*
* Inputs: this_p - Current packet object.
*
* release_time - Time as returned by dtime_now().
*
*------------------------------------------------------------------------------*/
void ax25_set_release_time (packet_t this_p, double release_time)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
this_p->release_time = release_time;
}
/*------------------------------------------------------------------------------
*
* Name: ax25_get_release_time
*
* Purpose: Get release time.
*
*------------------------------------------------------------------------------*/
double ax25_get_release_time (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
return (this_p->release_time);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_set_modulo
*
* Purpose: Set modulo value for I and S frame sequence numbers.
*
*------------------------------------------------------------------------------*/
void ax25_set_modulo (packet_t this_p, int modulo)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
this_p->modulo = modulo;
}
/*------------------------------------------------------------------
*
* Function: ax25_format_addrs
*
* Purpose: Format all the addresses suitable for printing.
*
* The AX.25 spec refers to this as "Source Path Header" - "TNC-2" Format
*
* Inputs: Current packet.
*
* Outputs: result - All addresses combined into a single string of the form:
*
* "Source > Destination [ , repeater ... ] :"
*
* An asterisk is displayed after the last digipeater
* with the "H" bit set. e.g. If we hear RPT2,
*
* SRC>DST,RPT1,RPT2*,RPT3:
*
* No asterisk means the source is being heard directly.
* Needs to be 101 characters to avoid overflowing.
* (Up to 100 characters + \0)
*
* Errors: No error checking so caller needs to be careful.
*
*
*------------------------------------------------------------------*/
// TODO: max len for result. buffer overflow?
void ax25_format_addrs (packet_t this_p, char *result)
{
int i;
int heard;
char stemp[AX25_MAX_ADDR_LEN];
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
*result = '\0';
/* New in 0.9. */
/* Don't get upset if no addresses. */
/* This will allow packets that do not comply to AX.25 format. */
if (this_p->num_addr == 0) {
return;
}
ax25_get_addr_with_ssid (this_p, AX25_SOURCE, stemp);
strcat (result, stemp);
strcat (result, ">");
ax25_get_addr_with_ssid (this_p, AX25_DESTINATION, stemp);
strcat (result, stemp);
heard = ax25_get_heard(this_p);
for (i=(int)AX25_REPEATER_1; inum_addr; i++) {
ax25_get_addr_with_ssid (this_p, i, stemp);
strcat (result, ",");
strcat (result, stemp);
if (i == heard) {
strcat (result, "*");
}
}
strcat (result, ":");
}
/*------------------------------------------------------------------
*
* Function: ax25_format_via_path
*
* Purpose: Format via path addresses suitable for printing.
*
* Inputs: Current packet.
*
* result_size - Number of bytes available for result.
* We can have up to 8 addresses x 9 characters
* plus 7 commas, possible *, and nul = 81 minimum.
*
* Outputs: result - Digipeater field addresses combined into a single string of the form:
*
* "repeater, repeater ..."
*
* An asterisk is displayed after the last digipeater
* with the "H" bit set. e.g. If we hear RPT2,
*
* RPT1,RPT2*,RPT3
*
* No asterisk means the source is being heard directly.
*
*------------------------------------------------------------------*/
void ax25_format_via_path (packet_t this_p, char *result, size_t result_size)
{
int i;
int heard;
char stemp[AX25_MAX_ADDR_LEN];
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
*result = '\0';
/* Don't get upset if no addresses. */
/* This will allow packets that do not comply to AX.25 format. */
if (this_p->num_addr == 0) {
return;
}
heard = ax25_get_heard(this_p);
for (i=(int)AX25_REPEATER_1; inum_addr; i++) {
if (i > (int)AX25_REPEATER_1) {
strlcat (result, ",", result_size);
}
ax25_get_addr_with_ssid (this_p, i, stemp);
strlcat (result, stemp, result_size);
if (i == heard) {
strlcat (result, "*", result_size);
}
}
} /* end ax25_format_via_path */
/*------------------------------------------------------------------
*
* Function: ax25_pack
*
* Purpose: Put all the pieces into format ready for transmission.
*
* Inputs: this_p - pointer to packet object.
*
* Outputs: result - Frame buffer, AX25_MAX_PACKET_LEN bytes.
* Should also have two extra for FCS to be
* added later.
*
* Returns: Number of octets in the frame buffer.
* Does NOT include the extra 2 for FCS.
*
* Errors: Returns -1.
*
*------------------------------------------------------------------*/
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);
memcpy (result, this_p->frame_data, this_p->frame_len);
return (this_p->frame_len);
}
/*------------------------------------------------------------------
*
* Function: ax25_frame_type
*
* Purpose: Extract the type of frame.
* This is derived from the control byte(s) but
* is an enumerated type for easier handling.
*
* Inputs: this_p - pointer to packet object.
*
* Outputs: desc - Text description such as "I frame" or
* "U frame SABME".
* Supply 40 bytes to be safe.
*
* cr - Command or response?
*
* pf - P/F - Poll/Final or -1 if not applicable
*
* nr - N(R) - receive sequence or -1 if not applicable.
*
* ns - N(S) - send sequence or -1 if not applicable.
*
* Returns: Frame type from enum ax25_frame_type_e.
*
*------------------------------------------------------------------*/
// TODO: need someway to ensure caller allocated enough space.
// Should pass in as parameter.
#define DESC_SIZ 40
ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns)
{
int c; // U frames are always one control byte.
int c2 = 0; // I & S frames can have second Control byte.
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
strlcpy (desc, "????", DESC_SIZ);
*cr = cr_11;
*pf = -1;
*nr = -1;
*ns = -1;
c = ax25_get_control(this_p);
if (c < 0) {
strlcpy (desc, "Not AX.25", DESC_SIZ);
return (frame_not_AX25);
}
/*
* TERRIBLE HACK :-( for display purposes.
*
* I and S frames can have 1 or 2 control bytes but there is
* no good way to determine this without dipping into the data
* link state machine. Can we guess?
*
* S frames have no protocol id or information so if there is one
* more byte beyond the control field, we could assume there are
* two control bytes.
*
* For I frames, the protocol id will usually be 0xf0. If we find
* that as the first byte of the information field, it is probably
* the pid and not part of the information. Ditto for segments 0x08.
* Not fool proof but good enough for troubleshooting text out.
*
* If we have a link to the peer station, this will be set properly
* before it needs to be used for other reasons.
*
* Setting one of the RR bits (find reference!) is sounding better and better.
* It's in common usage so I should lobby to get that in the official protocol spec.
*/
if (this_p->modulo == 0 && (c & 3) == 1 && ax25_get_c2(this_p) != -1) {
this_p->modulo = modulo_128;
}
else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0xF0) {
this_p->modulo = modulo_128;
}
else if (this_p->modulo == 0 && (c & 1) == 0 && this_p->frame_data[ax25_get_info_offset(this_p)] == 0x08) { // same for segments
this_p->modulo = modulo_128;
}
if (this_p->modulo == modulo_128) {
c2 = ax25_get_c2 (this_p);
}
int dst_c = this_p->frame_data[AX25_DESTINATION * 7 + 6] & SSID_H_MASK;
int src_c = this_p->frame_data[AX25_SOURCE * 7 + 6] & SSID_H_MASK;
char cr_text[8];
char pf_text[8];
if (dst_c) {
if (src_c) { *cr = cr_11; strcpy(cr_text,"cc=11"); strcpy(pf_text,"p/f"); }
else { *cr = cr_cmd; strcpy(cr_text,"cmd"); strcpy(pf_text,"p"); }
}
else {
if (src_c) { *cr = cr_res; strcpy(cr_text,"res"); strcpy(pf_text,"f"); }
else { *cr = cr_00; strcpy(cr_text,"cc=00"); strcpy(pf_text,"p/f"); }
}
if ((c & 1) == 0) {
// Information rrr p sss 0 or sssssss 0 rrrrrrr p
if (this_p->modulo == modulo_128) {
*ns = (c >> 1) & 0x7f;
*pf = c2 & 1;
*nr = (c2 >> 1) & 0x7f;
}
else {
*ns = (c >> 1) & 7;
*pf = (c >> 4) & 1;
*nr = (c >> 5) & 7;
}
//snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d", cr_text, *ns, *nr, pf_text, *pf);
snprintf (desc, DESC_SIZ, "I %s, n(s)=%d, n(r)=%d, %s=%d, pid=0x%02x", cr_text, *ns, *nr, pf_text, *pf, ax25_get_pid(this_p));
return (frame_type_I);
}
else if ((c & 2) == 0) {
// Supervisory rrr p/f ss 0 1 or 0000 ss 0 1 rrrrrrr p/f
if (this_p->modulo == modulo_128) {
*pf = c2 & 1;
*nr = (c2 >> 1) & 0x7f;
}
else {
*pf = (c >> 4) & 1;
*nr = (c >> 5) & 7;
}
switch ((c >> 2) & 3) {
case 0: snprintf (desc, DESC_SIZ, "RR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RR); break;
case 1: snprintf (desc, DESC_SIZ, "RNR %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_RNR); break;
case 2: snprintf (desc, DESC_SIZ, "REJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_REJ); break;
case 3: snprintf (desc, DESC_SIZ, "SREJ %s, n(r)=%d, %s=%d", cr_text, *nr, pf_text, *pf); return (frame_type_S_SREJ); break;
}
}
else {
// Unnumbered mmm p/f mm 1 1
*pf = (c >> 4) & 1;
switch (c & 0xef) {
case 0x6f: snprintf (desc, DESC_SIZ, "SABME %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABME); break;
case 0x2f: snprintf (desc, DESC_SIZ, "SABM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_SABM); break;
case 0x43: snprintf (desc, DESC_SIZ, "DISC %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DISC); break;
case 0x0f: snprintf (desc, DESC_SIZ, "DM %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_DM); break;
case 0x63: snprintf (desc, DESC_SIZ, "UA %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UA); break;
case 0x87: snprintf (desc, DESC_SIZ, "FRMR %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_FRMR); break;
case 0x03: snprintf (desc, DESC_SIZ, "UI %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_UI); break;
case 0xaf: snprintf (desc, DESC_SIZ, "XID %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_XID); break;
case 0xe3: snprintf (desc, DESC_SIZ, "TEST %s, %s=%d", cr_text, pf_text, *pf); return (frame_type_U_TEST); break;
default: snprintf (desc, DESC_SIZ, "U other???"); return (frame_type_U); break;
}
}
// Should be unreachable but compiler doesn't realize that.
// Here only to suppress "warning: control reaches end of non-void function"
return (frame_not_AX25);
} /* end ax25_frame_type */
/*------------------------------------------------------------------
*
* Function: ax25_hex_dump
*
* Purpose: Print out packet in hexadecimal for debugging.
*
* Inputs: fptr - Pointer to frame data.
*
* flen - Frame length, bytes. Does not include CRC.
*
*------------------------------------------------------------------*/
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>5)&7, (c>>4)&1, (c>>1)&7); }
else if ((c & 0xf) == 0x01) { snprintf (out, outsiz, "S frame RR: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); }
else if ((c & 0xf) == 0x05) { snprintf (out, outsiz, "S frame RNR: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); }
else if ((c & 0xf) == 0x09) { snprintf (out, outsiz, "S frame REJ: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); }
else if ((c & 0xf) == 0x0D) { snprintf (out, outsiz, "S frame sREJ: n(r)=%d, p/f=%d", (c>>5)&7, (c>>4)&1); }
else if ((c & 0xef) == 0x6f) { snprintf (out, outsiz, "U frame SABME: p=%d", (c>>4)&1); }
else if ((c & 0xef) == 0x2f) { snprintf (out, outsiz, "U frame SABM: p=%d", (c>>4)&1); }
else if ((c & 0xef) == 0x43) { snprintf (out, outsiz, "U frame DISC: p=%d", (c>>4)&1); }
else if ((c & 0xef) == 0x0f) { snprintf (out, outsiz, "U frame DM: f=%d", (c>>4)&1); }
else if ((c & 0xef) == 0x63) { snprintf (out, outsiz, "U frame UA: f=%d", (c>>4)&1); }
else if ((c & 0xef) == 0x87) { snprintf (out, outsiz, "U frame FRMR: f=%d", (c>>4)&1); }
else if ((c & 0xef) == 0x03) { snprintf (out, outsiz, "U frame UI: p/f=%d", (c>>4)&1); }
else if ((c & 0xef) == 0xAF) { snprintf (out, outsiz, "U frame XID: p/f=%d", (c>>4)&1); }
else if ((c & 0xef) == 0xe3) { snprintf (out, outsiz, "U frame TEST: p/f=%d", (c>>4)&1); }
else { snprintf (out, outsiz, "Unknown frame type for control = 0x%02x", c); }
}
/* Text description of protocol id octet. */
#define PID_TEXT_SIZE 80
static void pid_to_text (int p, char out[PID_TEXT_SIZE])
{
if ((p & 0x30) == 0x10) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); }
else if ((p & 0x30) == 0x20) { snprintf (out, PID_TEXT_SIZE, "AX.25 layer 3 implemented."); }
else if (p == 0x01) { snprintf (out, PID_TEXT_SIZE, "ISO 8208/CCITT X.25 PLP"); }
else if (p == 0x06) { snprintf (out, PID_TEXT_SIZE, "Compressed TCP/IP packet. Van Jacobson (RFC 1144)"); }
else if (p == 0x07) { snprintf (out, PID_TEXT_SIZE, "Uncompressed TCP/IP packet. Van Jacobson (RFC 1144)"); }
else if (p == 0x08) { snprintf (out, PID_TEXT_SIZE, "Segmentation fragment"); }
else if (p == 0xC3) { snprintf (out, PID_TEXT_SIZE, "TEXNET datagram protocol"); }
else if (p == 0xC4) { snprintf (out, PID_TEXT_SIZE, "Link Quality Protocol"); }
else if (p == 0xCA) { snprintf (out, PID_TEXT_SIZE, "Appletalk"); }
else if (p == 0xCB) { snprintf (out, PID_TEXT_SIZE, "Appletalk ARP"); }
else if (p == 0xCC) { snprintf (out, PID_TEXT_SIZE, "ARPA Internet Protocol"); }
else if (p == 0xCD) { snprintf (out, PID_TEXT_SIZE, "ARPA Address resolution"); }
else if (p == 0xCE) { snprintf (out, PID_TEXT_SIZE, "FlexNet"); }
else if (p == 0xCF) { snprintf (out, PID_TEXT_SIZE, "NET/ROM"); }
else if (p == 0xF0) { snprintf (out, PID_TEXT_SIZE, "No layer 3 protocol implemented."); }
else if (p == 0xFF) { snprintf (out, PID_TEXT_SIZE, "Escape character. Next octet contains more Level 3 protocol information."); }
else { snprintf (out, PID_TEXT_SIZE, "Unknown protocol id = 0x%02x", p); }
}
void ax25_hex_dump (packet_t this_p)
{
int n;
unsigned char *fptr = this_p->frame_data;
int flen = this_p->frame_len;
if (this_p->num_addr >= AX25_MIN_ADDRS && this_p->num_addr <= AX25_MAX_ADDRS) {
int c, p;
char cp_text[120];
char l_text[20];
c = fptr[this_p->num_addr*7];
p = fptr[this_p->num_addr*7+1];
ctrl_to_text (c, cp_text, sizeof(cp_text)); // TODO: use ax25_frame_type() instead.
if ( (c & 0x01) == 0 || /* I xxxx xxx0 */
c == 0x03 || c == 0x13) { /* UI 000x 0011 */
char pid_text[PID_TEXT_SIZE];
pid_to_text (p, pid_text);
strlcat (cp_text, ", ", sizeof(cp_text));
strlcat (cp_text, pid_text, sizeof(cp_text));
}
snprintf (l_text, sizeof(l_text), ", length = %d", flen);
strlcat (cp_text, l_text, sizeof(cp_text));
dw_printf ("%s\n", cp_text);
}
dw_printf (" dest %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n",
fptr[0]>>1, fptr[1]>>1, fptr[2]>>1, fptr[3]>>1, fptr[4]>>1, fptr[5]>>1,
(fptr[6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT,
(fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT,
(fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT,
fptr[6]&SSID_LAST_MASK);
dw_printf (" source %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n",
fptr[7]>>1, fptr[8]>>1, fptr[9]>>1, fptr[10]>>1, fptr[11]>>1, fptr[12]>>1,
(fptr[13]&SSID_SSID_MASK)>>SSID_SSID_SHIFT,
(fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT,
(fptr[13]&SSID_RR_MASK)>>SSID_RR_SHIFT,
fptr[13]&SSID_LAST_MASK);
for (n=2; nnum_addr; n++) {
dw_printf (" digi %d %c%c%c%c%c%c %2d h=%d res=%d last=%d\n",
n - 1,
fptr[n*7+0]>>1, fptr[n*7+1]>>1, fptr[n*7+2]>>1, fptr[n*7+3]>>1, fptr[n*7+4]>>1, fptr[n*7+5]>>1,
(fptr[n*7+6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT,
(fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT,
(fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT,
fptr[n*7+6]&SSID_LAST_MASK);
}
hex_dump (fptr, flen);
} /* end ax25_hex_dump */
/*------------------------------------------------------------------
*
* Function: ax25_is_aprs
*
* Purpose: Is this packet APRS format?
*
* Inputs: this_p - pointer to packet object.
*
* Returns: True if this frame has the proper control
* octets for an APRS packet.
* control 3 for UI frame
* protocol id 0xf0 for no layer 3
*
*
* Description: Dire Wolf should be able to act as a KISS TNC for
* any type of AX.25 activity. However, there are other
* places where we want to process only APRS.
* (e.g. digipeating and IGate.)
*
*------------------------------------------------------------------*/
int ax25_is_aprs (packet_t this_p)
{
int ctrl, pid, is_aprs;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(0);
ctrl = ax25_get_control(this_p);
pid = ax25_get_pid(this_p);
is_aprs = this_p->num_addr >= 2 && ctrl == AX25_UI_FRAME && pid == AX25_PID_NO_LAYER_3;
#if 0
text_color_set(DW_COLOR_ERROR);
dw_printf ("ax25_is_aprs(): ctrl=%02x, pid=%02x, is_aprs=%d\n", ctrl, pid, is_aprs);
#endif
return (is_aprs);
}
/*------------------------------------------------------------------
*
* Function: ax25_is_null_frame
*
* Purpose: Is this packet structure empty?
*
* Inputs: this_p - pointer to packet object.
*
* Returns: True if frame data length is 0.
*
* Description: This is used when we want to wake up the
* transmit queue processing thread but don't
* want to transmit a frame.
*
*------------------------------------------------------------------*/
int ax25_is_null_frame (packet_t this_p)
{
int is_null;
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
is_null = this_p->frame_len == 0;
#if 0
text_color_set(DW_COLOR_ERROR);
dw_printf ("ax25_is_null_frame(): is_null=%d\n", is_null);
#endif
return (is_null);
}
/*------------------------------------------------------------------
*
* Function: ax25_get_control
ax25_get_c2
*
* Purpose: Get Control field from packet.
*
* Inputs: this_p - pointer to packet object.
*
* Returns: APRS uses AX25_UI_FRAME.
* This could also be used in other situations.
*
*------------------------------------------------------------------*/
int ax25_get_control (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_control_offset(this_p)]);
}
return (-1);
}
int ax25_get_c2 (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) {
int offset2 = ax25_get_control_offset(this_p)+1;
if (offset2 < this_p->frame_len) {
return (this_p->frame_data[offset2]);
}
else {
return (-1); /* attempt to go beyond the end of frame. */
}
}
return (-1); /* not AX.25 */
}
/*------------------------------------------------------------------
*
* Function: ax25_get_pid
*
* Purpose: Get protocol ID from packet.
*
* Inputs: this_p - pointer to packet object.
*
* Returns: APRS uses 0xf0 for no layer 3.
* This could also be used in other situations.
*
* AX.25: "The Protocol Identifier (PID) field appears in information
* frames (I and UI) only. It identifies which kind of
* Layer 3 protocol, if any, is in use."
*
*------------------------------------------------------------------*/
int ax25_get_pid (packet_t this_p)
{
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
// TODO: handle 2 control byte case.
// TODO: sanity check: is it I or UI frame?
if (this_p->frame_len == 0) return(-1);
if (this_p->num_addr >= 2) {
return (this_p->frame_data[ax25_get_pid_offset(this_p)]);
}
return (-1);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_dedupe_crc
*
* Purpose: Calculate a checksum for the packet source, destination, and
* information but NOT the digipeaters.
* This is used for duplicate detection in the digipeater
* and IGate algorithms.
*
* Input: pp - Pointer to packet object.
*
* Returns: Value which will be the same for a duplicate but very unlikely
* to match a non-duplicate packet.
*
* Description: For detecting duplicates, we need to look
* + source station
* + destination
* + information field
* but NOT the changing list of digipeaters.
*
* Typically, only a checksum is kept to reduce memory
* requirements and amount of compution for comparisons.
* There is a very very small probability that two unrelated
* packets will result in the same checksum, and the
* undesired dropping of the packet.
*
* There is a 1 / 65536 chance of getting a false positive match
* which is good enough for this application.
* We could reduce that with a 32 bit CRC instead of reusing
* code from the AX.25 frame CRC calculation.
*
* Version 1.3: We exclude any trailing CR/LF at the end of the info part
* so we can detect duplicates that are received only over the
* air and those which have gone thru an IGate where the process
* removes any trailing CR/LF. Example:
*
* Original via RF only:
* W1TG-1>APU25N,N3LEE-10*,WIDE2-1:
*
* When we get the same thing via APRS-IS:
* W1TG-1>APU25N,K1FFK,WIDE2*,qAR,WB2ZII-15:= 1 && (pinfo[info_len-1] == '\r' ||
pinfo[info_len-1] == '\n' ||
pinfo[info_len-1] == ' ')) {
// Temporary for debugging!
// if (pinfo[info_len-1] == ' ') {
// text_color_set(DW_COLOR_ERROR);
// dw_printf ("DEBUG: ax25_dedupe_crc ignoring trailing space.\n");
// }
info_len--;
}
crc = 0xffff;
crc = crc16((unsigned char *)src, strlen(src), crc);
crc = crc16((unsigned char *)dest, strlen(dest), crc);
crc = crc16(pinfo, info_len, crc);
return (crc);
}
/*------------------------------------------------------------------------------
*
* Name: ax25_m_m_crc
*
* Purpose: Calculate a checksum for the packet.
* This is used for the multimodem duplicate detection.
*
* Input: pp - Pointer to packet object.
*
* Returns: Value which will be the same for a duplicate but very unlikely
* to match a non-duplicate packet.
*
* Description: For detecting duplicates, we need to look the entire packet.
*
* Typically, only a checksum is kept to reduce memory
* requirements and amount of compution for comparisons.
* There is a very very small probability that two unrelated
* packets will result in the same checksum, and the
* undesired dropping of the packet.
*------------------------------------------------------------------------------*/
unsigned short ax25_m_m_crc (packet_t pp)
{
unsigned short crc;
unsigned char fbuf[AX25_MAX_PACKET_LEN];
int flen;
flen = ax25_pack (pp, fbuf);
crc = 0xffff;
crc = crc16(fbuf, flen, crc);
return (crc);
}
/*------------------------------------------------------------------
*
* Function: ax25_safe_print
*
* Purpose: Print given string, changing non printable characters to
* hexadecimal notation. Note that character values
* , 28, 29, 30, and 31 can appear in MIC-E message.
*
* Inputs: pstr - Pointer to string.
*
* len - Number of bytes. If < 0 we use strlen().
*
* ascii_only - Restrict output to only ASCII.
* Normally we allow UTF-8.
*
* Stops after non-zero len characters or at nul.
*
* Returns: none
*
* Description: Print a string in a "safe" manner.
* Anything that is not a printable character
* will be converted to a hexadecimal representation.
* For example, a Line Feed character will appear as <0x0a>
* rather than dropping down to the next line on the screen.
*
* ax25_from_text can accept this format.
*
*
* Example: W1MED-1>T2QP0S,N1OHZ,N8VIM*,WIDE1-1:'cQBl <0x1c>-/]<0x0d>
* ------ ------
*
* Questions: What should we do about UTF-8? Should that be displayed
* as hexadecimal for troubleshooting? Maybe an option so the
* packet raw data is in hexadecimal but an extracted
* comment displays UTF-8? Or a command line option for only ASCII?
*
* Trailing space:
* I recently noticed a case where a packet has space character
* at the end. If the last character of the line is a space,
* this will be displayed in hexadecimal to make it obvious.
*
*------------------------------------------------------------------*/
#define MAXSAFE 500
void ax25_safe_print (char *pstr, int len, int ascii_only)
{
int ch;
char safe_str[MAXSAFE*6+1];
int safe_len;
safe_len = 0;
safe_str[safe_len] = '\0';
if (len < 0)
len = strlen(pstr);
if (len > MAXSAFE)
len = MAXSAFE;
while (len > 0)
{
ch = *((unsigned char *)pstr);
if (ch == ' ' && (len == 1 || pstr[1] == '\0')) {
snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch);
safe_len += 6;
}
else if (ch < ' ' || ch == 0x7f || ch == 0xfe || ch == 0xff ||
(ascii_only && ch >= 0x80) ) {
/* Control codes and delete. */
/* UTF-8 does not use fe and ff except in a possible */
/* "Byte Order Mark" (BOM) at the beginning. */
snprintf (safe_str + safe_len, sizeof(safe_str)-safe_len, "<0x%02x>", ch);
safe_len += 6;
}
else {
/* Let everything else thru so we can handle UTF-8 */
/* Maybe we should have an option to display 0x80 */
/* and above as hexadecimal. */
safe_str[safe_len++] = ch;
safe_str[safe_len] = '\0';
}
pstr++;
len--;
}
// TODO1.2: should return string rather printing to remove a race condition.
dw_printf ("%s", safe_str);
} /* end ax25_safe_print */
/*------------------------------------------------------------------
*
* Function: ax25_alevel_to_text
*
* Purpose: Convert audio level to text representation.
*
* Inputs: alevel - Audio levels collected from demodulator.
*
* Outputs: text - Text representation for presentation to user.
* Currently it will look something like this:
*
* r(m/s)
*
* With n,m,s corresponding to received, mark, and space.
* Comma is to be avoided because one place this
* ends up is in a CSV format file.
*
* size should be AX25_ALEVEL_TO_TEXT_SIZE.
*
* Returns: True if something to print. (currently if alevel.original >= 0)
* False if not.
*
* Description: Audio level used to be simple; it was a single number.
* In version 1.2, we start collecting more details.
* At the moment, it includes:
*
* - Received level from new method.
* - Levels from mark & space filters to examine the ratio.
*
* We print this in multiple places so put it into a function.
*
*------------------------------------------------------------------*/
int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE])
{
if (alevel.rec < 0) {
strlcpy (text, "", AX25_ALEVEL_TO_TEXT_SIZE);
return (0);
}
// TODO1.2: haven't thought much about non-AFSK cases yet.
// What should we do for 9600 baud?
// For DTMF omit the two extra numbers.
if (alevel.mark >= 0 && alevel.space < 0) { /* baseband */
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space);
}
else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec);
}
else if (alevel.mark == -2 && alevel.space == -2) { /* DTMF - single number. */
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec);
}
else { /* AFSK */
//snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d:%d(%d/%d=%05.3f=)", alevel.original, alevel.rec, alevel.mark, alevel.space, alevel.ms_ratio);
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%d/%d)", alevel.rec, alevel.mark, alevel.space);
}
return (1);
} /* end ax25_alevel_to_text */
/* end ax25_pad.c */
direwolf-1.4/ax25_pad.h 0000664 0000000 0000000 00000030060 13100232553 0014736 0 ustar 00root root 0000000 0000000 /*-------------------------------------------------------------------
*
* Name: ax25_pad.h
*
* Purpose: Header file for using ax25_pad.c
*
*------------------------------------------------------------------*/
#ifndef AX25_PAD_H
#define AX25_PAD_H 1
#define AX25_MAX_REPEATERS 8
#define AX25_MIN_ADDRS 2 /* Destinatin & Source. */
#define AX25_MAX_ADDRS 10 /* Destination, Source, 8 digipeaters. */
#define AX25_DESTINATION 0 /* Address positions in frame. */
#define AX25_SOURCE 1
#define AX25_REPEATER_1 2
#define AX25_REPEATER_2 3
#define AX25_REPEATER_3 4
#define AX25_REPEATER_4 5
#define AX25_REPEATER_5 6
#define AX25_REPEATER_6 7
#define AX25_REPEATER_7 8
#define AX25_REPEATER_8 9
#define AX25_MAX_ADDR_LEN 12 /* In theory, you would expect the maximum length */
/* to be 6 letters, dash, 2 digits, and nul for a */
/* total of 10. However, object labels can be 10 */
/* characters so throw in a couple extra bytes */
/* to be safe. */
#define AX25_MIN_INFO_LEN 0 /* Previously 1 when considering only APRS. */
#define AX25_MAX_INFO_LEN 2048 /* Maximum size for APRS. */
/* AX.25 starts out with 256 as the default max */
/* length but the end stations can negotiate */
/* something different. */
/* version 0.8: Change from 256 to 2028 to */
/* handle the larger paclen for Linux AX25. */
/* These don't include the 2 bytes for the */
/* HDLC frame FCS. */
/*
* Previously, for APRS only.
* #define AX25_MIN_PACKET_LEN ( 2 * 7 + 2 + AX25_MIN_INFO_LEN)
* #define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + AX25_MAX_INFO_LEN)
*/
/* The more general case. */
/* An AX.25 frame can have a control byte and no protocol. */
#define AX25_MIN_PACKET_LEN ( 2 * 7 + 1 )
#define AX25_MAX_PACKET_LEN ( AX25_MAX_ADDRS * 7 + 2 + 3 + AX25_MAX_INFO_LEN)
/*
* packet_t is a pointer to a packet object.
*
* The actual implementation is not visible outside ax25_pad.c.
*/
#define AX25_UI_FRAME 3 /* Control field value. */
#define AX25_PID_NO_LAYER_3 0xf0 /* protocol ID used for APRS */
#define AX25_PID_SEGMENTATION_FRAGMENT 0x08
#define AX25_PID_ESCAPE_CHARACTER 0xff
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
struct packet_s {
int magic1; /* for error checking. */
int seq; /* unique sequence number for debugging. */
double release_time; /* Time stamp in format returned by dtime_now(). */
/* When to release from the SATgate mode delay queue. */
#define MAGIC 0x41583235
struct packet_s *nextp; /* Pointer to next in queue. */
int num_addr; /* Number of addresses in frame. */
/* Range of AX25_MIN_ADDRS .. AX25_MAX_ADDRS for AX.25. */
/* It will be 0 if it doesn't look like AX.25. */
/* -1 is used temporarily at allocation to mean */
/* not determined yet. */
/*
* The 7th octet of each address contains:
*
* Bits: H R R SSID 0
*
* H for digipeaters set to 0 intially.
* Changed to 1 when position has been used.
*
* for source & destination it is called
* command/response. Normally both 1 for APRS.
* They should be opposites for connected mode.
*
* R R Reserved. Normally set to 1 1.
*
* SSID Substation ID. Range of 0 - 15.
*
* 0 Usually 0 but 1 for last address.
*/
#define SSID_H_MASK 0x80
#define SSID_H_SHIFT 7
#define SSID_RR_MASK 0x60
#define SSID_RR_SHIFT 5
#define SSID_SSID_MASK 0x1e
#define SSID_SSID_SHIFT 1
#define SSID_LAST_MASK 0x01
int frame_len; /* Frame length without CRC. */
int modulo; /* I & S frames have sequence numbers of either 3 bits (modulo 8) */
/* or 7 bits (modulo 128). This is conveyed by either 1 or 2 */
/* control bytes. Unfortunately, we can't determine this by looking */
/* at an isolated frame. We need to know about the context. If we */
/* are part of the conversation, we would know. But if we are */
/* just listening to others, this would be more difficult to determine. */
/* For U frames: set to 0 - not applicable */
/* For I & S frames: 8 or 128 if known. 0 if unknown. */
unsigned char frame_data[AX25_MAX_PACKET_LEN+1];
/* Raw frame contents, without the CRC. */
int magic2; /* Will get stomped on if above overflows. */
};
#else /* Public view. */
struct packet_s {
int secret;
};
#endif
typedef struct packet_s *packet_t;
typedef enum cmdres_e { cr_00 = 2, cr_cmd = 1, cr_res = 0, cr_11 = 3 } cmdres_t;
extern packet_t ax25_new (void);
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
/*
* APRS always has one control octet of 0x03 but the more
* general AX.25 case is one or two control bytes depending on
* whether "modulo 128 operation" is in effect.
*/
//#define DEBUGX 1
static inline int ax25_get_control_offset (packet_t this_p)
{
return (this_p->num_addr*7);
}
static inline int ax25_get_num_control (packet_t this_p)
{
int c;
c = this_p->frame_data[ax25_get_control_offset(this_p)];
if ( (c & 0x01) == 0 ) { /* I xxxx xxx0 */
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is I frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
#endif
return ((this_p->modulo == 128) ? 2 : 1);
}
if ( (c & 0x03) == 1 ) { /* S xxxx xx01 */
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is S frame, returns %d\n", c, (this_p->modulo == 128) ? 2 : 1);
#endif
return ((this_p->modulo == 128) ? 2 : 1);
}
#if DEBUGX
dw_printf ("ax25_get_num_control, %02x is U frame, always returns 1.\n", c);
#endif
return (1); /* U xxxx xx11 */
}
/*
* APRS always has one protocol octet of 0xF0 meaning no level 3
* protocol but the more general case is 0, 1 or 2 protocol ID octets.
*/
static inline int ax25_get_pid_offset (packet_t this_p)
{
return (ax25_get_control_offset (this_p) + ax25_get_num_control(this_p));
}
static int ax25_get_num_pid (packet_t this_p)
{
int c;
int pid;
c = this_p->frame_data[ax25_get_control_offset(this_p)];
if ( (c & 0x01) == 0 || /* I xxxx xxx0 */
c == 0x03 || c == 0x13) { /* UI 000x 0011 */
pid = this_p->frame_data[ax25_get_pid_offset(this_p)];
#if DEBUGX
dw_printf ("ax25_get_num_pid, %02x is I or UI frame, pid = %02x, returns %d\n", c, pid, (pid==AX25_PID_ESCAPE_CHARACTER) ? 2 : 1);
#endif
if (pid == AX25_PID_ESCAPE_CHARACTER) {
return (2); /* pid 1111 1111 means another follows. */
}
return (1);
}
#if DEBUGX
dw_printf ("ax25_get_num_pid, %02x is neither I nor UI frame, returns 0\n", c);
#endif
return (0);
}
/*
* AX.25 has info field for 5 frame types depending on the control field.
*
* xxxx xxx0 I
* 000x 0011 UI (which includes APRS)
* 101x 1111 XID
* 111x 0011 TEST
* 100x 0111 FRMR
*
* APRS always has an Information field with at least one octet for the Data Type Indicator.
*/
static inline int ax25_get_info_offset (packet_t this_p)
{
int offset = ax25_get_control_offset (this_p) + ax25_get_num_control(this_p) + ax25_get_num_pid(this_p);
#if DEBUGX
dw_printf ("ax25_get_info_offset, returns %d\n", offset);
#endif
return (offset);
}
static inline int ax25_get_num_info (packet_t this_p)
{
int len;
/* assuming AX.25 frame. */
len = this_p->frame_len - this_p->num_addr * 7 - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
if (len < 0) {
len = 0; /* print error? */
}
return (len);
}
#endif
typedef enum ax25_modulo_e { modulo_unknown = 0, modulo_8 = 8, modulo_128 = 128 } ax25_modulo_t;
typedef enum ax25_frame_type_e {
frame_type_I = 0, // Information
frame_type_S_RR, // Receive Ready - System Ready To Receive
frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full
frame_type_S_REJ, // Reject Frame - Out of Sequence or Duplicate
frame_type_S_SREJ, // Selective Reject - Request single frame repeat
frame_type_U_SABME, // Set Async Balanced Mode, Extended
frame_type_U_SABM, // Set Async Balanced Mode
frame_type_U_DISC, // Disconnect
frame_type_U_DM, // Disconnect Mode
frame_type_U_UA, // Unnumbered Acknowledge
frame_type_U_FRMR, // Frame Reject
frame_type_U_UI, // Unnumbered Information
frame_type_U_XID, // Exchange Identification
frame_type_U_TEST, // Test
frame_type_U, // other Unnumbered, not used by AX.25.
frame_not_AX25 // Could not get control byte from frame.
// This must be last because value plus 1 is
// for the size of an array.
} ax25_frame_type_t;
/*
* Originally this was a single number.
* Let's try something new in version 1.2.
* Also collect AGC values from the mark and space filters.
*/
typedef struct alevel_s {
int rec;
int mark;
int space;
//float ms_ratio; // TODO: take out after temporary investigation.
} alevel_t;
#ifndef AXTEST
// TODO: remove this?
#define AX25MEMDEBUG 1
#endif
#if AX25MEMDEBUG // to investigate a memory leak problem
extern void ax25memdebug_set(void);
extern int ax25memdebug_get (void);
extern int ax25memdebug_seq (packet_t this_p);
extern packet_t ax25_from_text_debug (char *monitor, int strict, char *src_file, int src_line);
#define ax25_from_text(m,s) ax25_from_text_debug(m,s,__FILE__,__LINE__)
extern packet_t ax25_from_frame_debug (unsigned char *data, int len, alevel_t alevel, char *src_file, int src_line);
#define ax25_from_frame(d,l,a) ax25_from_frame_debug(d,l,a,__FILE__,__LINE__);
extern packet_t ax25_dup_debug (packet_t copy_from, char *src_file, int src_line);
#define ax25_dup(p) ax25_dup_debug(p,__FILE__,__LINE__);
extern void ax25_delete_debug (packet_t pp, char *src_file, int src_line);
#define ax25_delete(p) ax25_delete_debug(p,__FILE__,__LINE__);
#else
extern packet_t ax25_from_text (char *monitor, int strict);
extern packet_t ax25_from_frame (unsigned char *data, int len, alevel_t alevel);
extern packet_t ax25_dup (packet_t copy_from);
extern void ax25_delete (packet_t pp);
#endif
extern int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard);
extern int ax25_check_addresses (packet_t pp);
extern packet_t ax25_unwrap_third_party (packet_t from_pp);
extern void ax25_set_addr (packet_t pp, int, char *);
extern void ax25_insert_addr (packet_t this_p, int n, char *ad);
extern void ax25_remove_addr (packet_t this_p, int n);
extern int ax25_get_num_addr (packet_t pp);
extern int ax25_get_num_repeaters (packet_t this_p);
extern void ax25_get_addr_with_ssid (packet_t pp, int n, char *station);
extern void ax25_get_addr_no_ssid (packet_t pp, int n, char *station);
extern int ax25_get_ssid (packet_t pp, int n);
extern void ax25_set_ssid (packet_t this_p, int n, int ssid);
extern int ax25_get_h (packet_t pp, int n);
extern void ax25_set_h (packet_t pp, int n);
extern int ax25_get_heard(packet_t this_p);
extern int ax25_get_first_not_repeated(packet_t pp);
extern int ax25_get_rr (packet_t this_p, int n);
extern int ax25_get_info (packet_t pp, unsigned char **paddr);
extern int ax25_cut_at_crlf (packet_t this_p);
extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
extern int ax25_get_dti (packet_t this_p);
extern packet_t ax25_get_nextp (packet_t this_p);
extern void ax25_set_release_time (packet_t this_p, double release_time);
extern double ax25_get_release_time (packet_t this_p);
extern void ax25_set_modulo (packet_t this_p, int modulo);
extern void ax25_format_addrs (packet_t pp, char *);
extern void ax25_format_via_path (packet_t this_p, char *result, size_t result_size);
extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
extern ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns);
extern void ax25_hex_dump (packet_t this_p);
extern int ax25_is_aprs (packet_t pp);
extern int ax25_is_null_frame (packet_t this_p);
extern int ax25_get_control (packet_t this_p);
extern int ax25_get_c2 (packet_t this_p);
extern int ax25_get_pid (packet_t this_p);
extern unsigned short ax25_dedupe_crc (packet_t pp);
extern unsigned short ax25_m_m_crc (packet_t pp);
extern void ax25_safe_print (char *, int, int ascii_only);
#define AX25_ALEVEL_TO_TEXT_SIZE 32 // overkill but safe.
extern int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]);
#endif /* AX25_PAD_H */
/* end ax25_pad.h */
direwolf-1.4/ax25_pad2.c 0000664 0000000 0000000 00000056413 13100232553 0015025 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Name: ax25_pad2.c
*
* Purpose: Packet assembler and disasembler, part 2.
*
* Description:
*
* The original ax25_pad.c was written with APRS in mind.
* It handles UI frames and transparency for a KISS TNC.
* Here we add new functions that can handle the
* more general cases of AX.25 frames.
*
*
* * Destination Address (note: opposite order in printed format)
*
* * Source Address
*
* * 0-8 Digipeater Addresses
* (The AX.25 v2.2 spec reduced this number to
* a maximum of 2 but I allow the original 8.)
*
* Each address is composed of:
*
* * 6 upper case letters or digits, blank padded.
* These are shifted left one bit, leaving the LSB always 0.
*
* * a 7th octet containing the SSID and flags.
* The LSB is always 0 except for the last octet of the address field.
*
* The final octet of the Destination has the form:
*
* C R R SSID 0, where,
*
* C = command/response. Set to 1 for command.
* R R = Reserved = 1 1 (See RR note, below)
* SSID = substation ID
* 0 = zero
*
* The final octet of the Source has the form:
*
* C R R SSID 0, where,
*
* C = command/response. Must be inverse of destination C bit.
* R R = Reserved = 1 1 (See RR note, below)
* SSID = substation ID
* 0 = zero (or 1 if no repeaters)
*
* The final octet of each repeater has the form:
*
* H R R SSID 0, where,
*
* H = has-been-repeated = 0 initially.
* Set to 1 after this address has been used.
* R R = Reserved = 1 1
* SSID = substation ID
* 0 = zero (or 1 if last repeater in list)
*
* A digipeater would repeat this frame if it finds its address
* with the "H" bit set to 0 and all earlier repeater addresses
* have the "H" bit set to 1.
* The "H" bit would be set to 1 in the repeated frame.
*
* In standard monitoring format, an asterisk is displayed after the last
* digipeater with the "H" bit set. That indicates who you are hearing
* over the radio.
*
*
* Next we have:
*
* * One or two byte Control Field - A U frame always has one control byte.
* When using modulo 128 sequence numbers, the
* I and S frames can have a second byte allowing
* 7 bit fields instead of 3 bit fields.
* Unfortunately, we can't tell which we have by looking
* at a frame out of context. :-(
* If we are one end of the link, we would know this
* from SABM/SABME and possible later negotiation
* with XID. But if we start monitoring two other
* stations that are already conversing, we don't know.
*
* RR note: It seems that some implementations put a hint
* in the "RR" reserved bits.
* http://www.tapr.org/pipermail/ax25-layer2/2005-October/000297.html
* The RR bits can also be used for "DAMA" which is
* some sort of channel access coordination scheme.
* http://internet.freepage.de/cgi-bin/feets/freepage_ext/41030x030A/rewrite/hennig/afu/afudoc/afudama.html
* Neither is part of the official protocol spec.
*
* * One byte Protocol ID - Only for I and UI frames.
* Normally we would use 0xf0 for no layer 3.
*
* Finally the Information Field. The initial max size is 256 but it
* can be negotiated higher if both ends agree.
*
* Only these types of frames can have an information part:
* - I
* - UI
* - XID
* - TEST
* - FRMR
*
* The 2 byte CRC is not stored here.
*
*
* Constructors:
* ax25_u_frame - Construct a U frame.
* ax25_s_frame - Construct a S frame.
* ax25_i_frame - Construct a I frame.
*
* Get methods: .... ???
*
*------------------------------------------------------------------*/
#define AX25_PAD_C /* this will affect behavior of ax25_pad.h */
#include "direwolf.h"
#include
#include
#include
#include
#include
#include "textcolor.h"
#include "ax25_pad.h"
#include "ax25_pad2.h"
extern int ax25memdebug;
static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr);
//#if AX25MEMDEBUG
//#undef AX25MEMDEBUG
//#endif
/*------------------------------------------------------------------------------
*
* Name: ax25_u_frame
*
* Purpose: Construct a U frame.
*
* Input: addrs - Array of addresses.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* ftype - One of:
* frame_type_U_SABME // Set Async Balanced Mode, Extended
* frame_type_U_SABM // Set Async Balanced Mode
* frame_type_U_DISC // Disconnect
* frame_type_U_DM // Disconnect Mode
* frame_type_U_UA // Unnumbered Acknowledge
* frame_type_U_FRMR // Frame Reject
* frame_type_U_UI // Unnumbered Information
* frame_type_U_XID // Exchange Identification
* frame_type_U_TEST // Test
*
* pf - Poll/Final flag.
*
* pid - Protocol ID. >>> Used ONLY for the UI type. <<<
* Normally 0xf0 meaning no level 3.
* Could be other values for NET/ROM, etc.
*
* pinfo - Pointer to data for Info field. Allowed only for UI, XID, TEST, FRMR.
*
* info_len - Length for Info field.
*
*
* Returns: Pointer to new packet object.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line)
#else
packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len)
#endif
{
packet_t this_p;
unsigned char *p;
int ctrl = 0;
unsigned int t = 999; // 1 = must be cmd, 0 = must be response, 2 = can be either.
int i = 0; // Is Info part allowed?
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_u_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
if (this_p == NULL) return (NULL);
this_p->modulo = 0;
if ( ! set_addrs (this_p, addrs, num_addr, cr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
switch (ftype) {
// 1 = cmd only, 0 = res only, 2 = either
case frame_type_U_SABME: ctrl = 0x6f; t = 1; break;
case frame_type_U_SABM: ctrl = 0x2f; t = 1; break;
case frame_type_U_DISC: ctrl = 0x43; t = 1; break;
case frame_type_U_DM: ctrl = 0x0f; t = 0; break;
case frame_type_U_UA: ctrl = 0x63; t = 0; break;
case frame_type_U_FRMR: ctrl = 0x87; t = 0; i = 1; break;
case frame_type_U_UI: ctrl = 0x03; t = 2; i = 1; break;
case frame_type_U_XID: ctrl = 0xaf; t = 2; i = 1; break;
case frame_type_U_TEST: ctrl = 0xe3; t = 2; i = 1; break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid ftype %d for U frame.\n", __func__, ftype);
ax25_delete (this_p);
return (NULL);
break;
}
if (pf) ctrl |= 0x10;
if (t != 2) {
if (cr != t) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, cr is %d but must be %d. ftype=%d\n", __func__, cr, t, ftype);
}
}
p = this_p->frame_data + this_p->frame_len;
*p++ = ctrl;
this_p->frame_len++;
if (ftype == frame_type_U_UI) {
// Definitely don't want pid value of 0 (not in valid list)
// or 0xff (which means more bytes follow).
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, Invalid pid value 0x%02x.\n", __func__, pid);
pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
}
if (i) {
if (pinfo != NULL && info_len > 0) {
if (info_len > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: U frame, Invalid information field length %d.\n", __func__, info_len);
info_len = AX25_MAX_INFO_LEN;
}
memcpy (p, pinfo, info_len);
p += info_len;
this_p->frame_len += info_len;
}
}
else {
if (pinfo != NULL && info_len > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Info part not allowed for U frame type.\n", __func__);
}
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d\n", check_ftype, check_desc, check_pf);
assert (check_cr == cr);
assert (check_ftype == ftype);
assert (check_pf == pf);
assert (check_nr == -1);
assert (check_ns == -1);
#endif
return (this_p);
} /* end ax25_u_frame */
/*------------------------------------------------------------------------------
*
* Name: ax25_s_frame
*
* Purpose: Construct an S frame.
*
* Input: addrs - Array of addresses.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* ftype - One of:
* frame_type_S_RR, // Receive Ready - System Ready To Receive
* frame_type_S_RNR, // Receive Not Ready - TNC Buffer Full
* frame_type_S_REJ, // Reject Frame - Out of Sequence or Duplicate
* frame_type_S_SREJ, // Selective Reject - Request single frame repeat
*
* modulo - 8 or 128. Determines if we have 1 or 2 control bytes.
*
* nr - N(R) field --- describe.
*
* pf - Poll/Final flag.
*
*
* Returns: Pointer to new packet object.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, char *src_file, int src_line)
#else
packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf)
#endif
{
packet_t this_p;
unsigned char *p;
int ctrl = 0;
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_s_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
if (this_p == NULL) return (NULL);
if ( ! set_addrs (this_p, addrs, num_addr, cr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Could not set addresses for U frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
if (modulo != 8 && modulo != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid modulo %d for S frame.\n", __func__, modulo);
modulo = 8;
}
this_p->modulo = modulo;
if (nr < 0 || nr >= modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid N(R) %d for S frame.\n", __func__, nr);
nr &= (modulo - 1);
}
switch (ftype) {
case frame_type_S_RR: ctrl = 0x01; break;
case frame_type_S_RNR: ctrl = 0x05; break;
case frame_type_S_REJ: ctrl = 0x09; break;
case frame_type_S_SREJ: ctrl = 0x0d; break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid ftype %d for S frame.\n", __func__, ftype);
ax25_delete (this_p);
return (NULL);
break;
}
p = this_p->frame_data + this_p->frame_len;
if (modulo == 8) {
if (pf) ctrl |= 0x10;
ctrl |= nr << 5;
*p++ = ctrl;
this_p->frame_len++;
}
else {
*p++ = ctrl;
this_p->frame_len++;
ctrl = pf & 1;
ctrl |= nr << 1;
*p++ = ctrl;
this_p->frame_len++;
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
// todo modulo must be input.
check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d\n", check_ftype, check_desc, check_pf, check_nr);
assert (check_cr == cr);
assert (check_ftype == ftype);
assert (check_pf == pf);
assert (check_nr == nr);
assert (check_ns == -1);
#endif
return (this_p);
} /* end ax25_s_frame */
/*------------------------------------------------------------------------------
*
* Name: ax25_i_frame
*
* Purpose: Construct an I frame.
*
* Input: addrs - Array of addresses.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* modulo - 8 or 128.
*
* nr - N(R) field --- describe.
*
* ns - N(S) field --- describe.
*
* pf - Poll/Final flag.
*
* pid - Protocol ID.
* Normally 0xf0 meaning no level 3.
* Could be other values for NET/ROM, etc.
*
* pinfo - Pointer to data for Info field.
*
* info_len - Length for Info field.
*
*
* Returns: Pointer to new packet object.
*
*------------------------------------------------------------------------------*/
#if AX25MEMDEBUG
packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line)
#else
packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len)
#endif
{
packet_t this_p;
unsigned char *p;
int ctrl = 0;
this_p = ax25_new ();
#if AX25MEMDEBUG
if (ax25memdebug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("ax25_i_frame, seq=%d, called from %s %d\n", this_p->seq, src_file, src_line);
}
#endif
if (this_p == NULL) return (NULL);
if ( ! set_addrs (this_p, addrs, num_addr, cr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Could not set addresses for I frame.\n", __func__);
ax25_delete (this_p);
return (NULL);
}
if (modulo != 8 && modulo != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid modulo %d for I frame.\n", __func__, modulo);
modulo = 8;
}
this_p->modulo = modulo;
if (nr < 0 || nr >= modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid N(R) %d for I frame.\n", __func__, nr);
nr &= (modulo - 1);
}
if (ns < 0 || ns >= modulo) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: Invalid N(S) %d for I frame.\n", __func__, ns);
ns &= (modulo - 1);
}
p = this_p->frame_data + this_p->frame_len;
if (modulo == 8) {
ctrl = (nr << 5) | (ns << 1);
if (pf) ctrl |= 0x10;
*p++ = ctrl;
this_p->frame_len++;
}
else {
ctrl = ns << 1;
*p++ = ctrl;
this_p->frame_len++;
ctrl = nr << 1;
if (pf) ctrl |= 0x01;
*p++ = ctrl;
this_p->frame_len++;
}
// Definitely don't want pid value of 0 (not in valid list)
// or 0xff (which means more bytes follow).
if (pid < 0 || pid == 0 || pid == 0xff) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Warning: Client application provided invalid PID value, 0x%02x, for I frame.\n", pid);
pid = AX25_PID_NO_LAYER_3;
}
*p++ = pid;
this_p->frame_len++;
if (pinfo != NULL && info_len > 0) {
if (info_len > AX25_MAX_INFO_LEN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error in %s: I frame, Invalid information field length %d.\n", __func__, info_len);
info_len = AX25_MAX_INFO_LEN;
}
memcpy (p, pinfo, info_len);
p += info_len;
this_p->frame_len += info_len;
}
*p = '\0';
assert (p == this_p->frame_data + this_p->frame_len);
assert (this_p->magic1 == MAGIC);
assert (this_p->magic2 == MAGIC);
#if PAD2TEST
ax25_frame_type_t check_ftype;
cmdres_t check_cr;
char check_desc[80];
int check_pf;
int check_nr;
int check_ns;
unsigned char *check_pinfo;
int check_info_len;
check_ftype = ax25_frame_type (this_p, &check_cr, check_desc, &check_pf, &check_nr, &check_ns);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check: ftype=%d, desc=\"%s\", pf=%d, nr=%d, ns=%d\n", check_ftype, check_desc, check_pf, check_nr, check_ns);
check_info_len = ax25_get_info (this_p, &check_pinfo);
assert (check_cr == cr);
assert (check_ftype == frame_type_I);
assert (check_pf == pf);
assert (check_nr == nr);
assert (check_ns == ns);
assert (check_info_len == info_len);
assert (strcmp((char*)check_pinfo,(char*)pinfo) == 0);
#endif
return (this_p);
} /* end ax25_i_frame */
/*------------------------------------------------------------------------------
*
* Name: set_addrs
*
* Purpose: Set address fields
*
* Input: pp - Packet object.
*
* addrs - Array of addresses. Same order as in frame.
*
* num_addr - Number of addresses, range 2 .. 10.
*
* cr - cr_cmd command frame, cr_res for a response frame.
*
* Output: pp->frame_data - 7 bytes for each address.
*
* pp->frame_len - num_addr * 7
*
* p->num_addr - num_addr
*
* Returns: 1 for success. 0 for failure.
*
*------------------------------------------------------------------------------*/
static int set_addrs (packet_t pp, char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr)
{
int n;
assert (pp->frame_len == 0);
assert (cr == cr_cmd || cr == cr_res);
if (num_addr < AX25_MIN_ADDRS || num_addr > AX25_MAX_ADDRS) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("INTERNAL ERROR: %s %s %d, num_addr = %d\n", __FILE__, __func__, __LINE__, num_addr);
return (0);
}
for (n = 0; n < num_addr; n++) {
unsigned char *pa = pp->frame_data + n * 7;
int ok;
int strict = 1;
char oaddr[AX25_MAX_ADDR_LEN];
int ssid;
int heard;
int j;
ok = ax25_parse_addr (n, addrs[n], strict, oaddr, &ssid, &heard);
if (! ok) return (0);
// Fill in address.
memset (pa, ' ' << 1, 6);
for (j = 0; oaddr[j]; j++) {
pa[j] = oaddr[j] << 1;
}
pa += 6;
// Fill in SSID.
*pa = 0x60 | ((ssid & 0xf) << 1);
// Command / response flag.
switch (n) {
case AX25_DESTINATION:
if (cr == cr_cmd) *pa |= 0x80;
break;
case AX25_SOURCE:
if (cr == cr_res) *pa |= 0x80;
break;
default:
break;
}
// Is this the end of address field?
if (n == num_addr - 1) {
*pa |= 1;
}
pp->frame_len += 7;
}
pp->num_addr = num_addr;
return (1);
} /* end set_addrs */
/*------------------------------------------------------------------------------
*
* Name: main
*
* Purpose: Quick unit test for this file.
*
* Description: Generate a variety of frames.
* Each function calls ax25_frame_type to verify results.
*
* $ gcc -DPAD2TEST -DUSE_REGEX_STATIC -Iregex ax25_pad.c ax25_pad2.c fcs_calc.o textcolor.o regex.a misc.a
*
*------------------------------------------------------------------------------*/
#if PAD2TEST
int main ()
{
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
int num_addr = 2;
cmdres_t cr;
ax25_frame_type_t ftype;
int pf = 0;
int pid = 0xf0;
int modulo;
int nr, ns;
unsigned char *pinfo = NULL;
int info_len = 0;
packet_t pp;
strcpy (addrs[0], "W2UB");
strcpy (addrs[1], "WB2OSZ-15");
num_addr = 2;
/* U frame */
for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) {
for (pf = 0; pf <= 1; pf++) {
int cmin, cmax;
switch (ftype) {
// 0 = response, 1 = command
case frame_type_U_SABME: cmin = 1; cmax = 1; break;
case frame_type_U_SABM: cmin = 1; cmax = 1; break;
case frame_type_U_DISC: cmin = 1; cmax = 1; break;
case frame_type_U_DM: cmin = 0; cmax = 0; break;
case frame_type_U_UA: cmin = 0; cmax = 0; break;
case frame_type_U_FRMR: cmin = 0; cmax = 0; break;
case frame_type_U_UI: cmin = 0; cmax = 1; break;
case frame_type_U_XID: cmin = 0; cmax = 1; break;
case frame_type_U_TEST: cmin = 0; cmax = 1; break;
default: break; // avoid compiler warning.
}
for (cr = cmin; cr <= cmax; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len);
ax25_hex_dump (pp);
ax25_delete (pp);
}
}
}
dw_printf ("\n----------\n\n");
/* S frame */
strcpy (addrs[2], "DIGI1-1");
num_addr = 3;
for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) {
for (pf = 0; pf <= 1; pf++) {
modulo = 8;
nr = modulo / 2 + 1;
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf);
ax25_hex_dump (pp);
ax25_delete (pp);
}
modulo = 128;
nr = modulo / 2 + 1;
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf);
ax25_hex_dump (pp);
ax25_delete (pp);
}
}
}
dw_printf ("\n----------\n\n");
/* I frame */
pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain.";
info_len = strlen((char*)pinfo);
for (pf = 0; pf <= 1; pf++) {
modulo = 8;
nr = 0x55 & (modulo - 1);
ns = 0xaa & (modulo - 1);
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len);
ax25_hex_dump (pp);
ax25_delete (pp);
}
modulo = 128;
nr = 0x55 & (modulo - 1);
ns = 0xaa & (modulo - 1);
for (cr = 0; cr <= 1; cr++) {
text_color_set(DW_COLOR_INFO);
dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len);
ax25_hex_dump (pp);
ax25_delete (pp);
}
}
text_color_set(DW_COLOR_REC);
dw_printf ("\n----------\n\n");
dw_printf ("\nSUCCESS!\n");
exit (EXIT_SUCCESS);
} /* end main */
#endif
/* end ax25_pad2.c */
direwolf-1.4/ax25_pad2.h 0000664 0000000 0000000 00000003602 13100232553 0015022 0 ustar 00root root 0000000 0000000 /*-------------------------------------------------------------------
*
* Name: ax25_pad2.h
*
* Purpose: Header file for using ax25_pad2.c
* ax25_pad dealt only with UI frames.
* This adds a facility for the other types: U, s, I.
*
*------------------------------------------------------------------*/
#ifndef AX25_PAD2_H
#define AX25_PAD2_H 1
#include "ax25_pad.h"
#if AX25MEMDEBUG // to investigate a memory leak problem
packet_t ax25_u_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line);
packet_t ax25_s_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf, char *src_file, int src_line);
packet_t ax25_i_frame_debug (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len, char *src_file, int src_line);
#define ax25_u_frame(a,n,c,f,p,q,i,l) ax25_u_frame_debug(a,n,c,f,p,q,i,l,__FILE__,__LINE__)
#define ax25_s_frame(a,n,c,f,m,r,p) ax25_s_frame_debug(a,n,c,f,m,r,p,__FILE__,__LINE__)
#define ax25_i_frame(a,n,c,m,r,s,p,q,i,l) ax25_i_frame_debug(a,n,c,m,r,s,p,q,i,l,__FILE__,__LINE__)
#else
packet_t ax25_u_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int pf, int pid, unsigned char *pinfo, int info_len);
packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, ax25_frame_type_t ftype, int modulo, int nr, int pf);
packet_t ax25_i_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, cmdres_t cr, int modulo, int nr, int ns, int pf, int pid, unsigned char *pinfo, int info_len);
#endif
#endif /* AX25_PAD2_H */
/* end ax25_pad2.h */
direwolf-1.4/beacon.c 0000664 0000000 0000000 00000067125 13100232553 0014571 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: beacon.c
*
* Purpose: Transmit messages on a fixed schedule.
*
* Description: Transmit periodic messages as specified in the config file.
*
*---------------------------------------------------------------*/
//#define DEBUG 1
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include "ax25_pad.h"
#include "textcolor.h"
#include "audio.h"
#include "tq.h"
#include "xmit.h"
#include "config.h"
#include "version.h"
#include "encode_aprs.h"
#include "beacon.h"
#include "latlong.h"
#include "dwgps.h"
#include "log.h"
#include "dlq.h"
#include "aprs_tt.h" // for dw_run_cmd - should relocate someday.
#include "mheard.h"
#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.
*/
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
/*
* Save pointers to configuration settings.
*/
static struct audio_s *g_modem_config_p;
static struct misc_config_s *g_misc_config_p;
static struct igate_config_s *g_igate_config_p;
#if __WIN32__
static unsigned __stdcall beacon_thread (void *arg);
#else
static void * beacon_thread (void *arg);
#endif
static int g_tracker_debug_level = 0; // 1 for data from gps.
// 2 + Smart Beaconing logic.
// 3 + Send transmissions to log file.
void beacon_tracker_set_debug (int level)
{
g_tracker_debug_level = level;
}
static time_t sb_calculate_next_time (time_t now,
float current_speed_mph, float current_course,
time_t last_xmit_time, float last_xmit_course);
static void beacon_send (int j, dwgps_info_t *gpsinfo);
/*-------------------------------------------------------------------
*
* Name: beacon_init
*
* Purpose: Initialize the beacon process.
*
* Inputs: pmodem - Audio device and modem configuration.
* Used only to find valid channels.
*
* pconfig - misc. configuration from config file.
* Beacon stuff ended up here.
*
* pigate - IGate configuration.
* Need this for calculating IGate statistics.
*
*
* Outputs: Remember required information for future use.
*
* Description: Do some validity checking on the beacon configuration.
*
* Start up beacon_thread to actually send the packets
* at the appropriate time.
*
*--------------------------------------------------------------------*/
void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate)
{
time_t now;
int j;
int count;
#if __WIN32__
HANDLE beacon_th;
#else
pthread_t beacon_tid;
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon_init ( ... )\n");
#endif
/*
* Save parameters for later use.
*/
g_modem_config_p = pmodem;
g_misc_config_p = pconfig;
g_igate_config_p = pigate;
/*
* Precompute the packet contents so any errors are
* Reported once at start up time rather than for each transmission.
* If a serious error is found, set type to BEACON_IGNORE and that
* table entry should be ignored later on.
*/
for (j=0; jnum_beacons; j++) {
int chan = g_misc_config_p->beacon[j].sendto_chan;
if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */
if (g_modem_config_p->achan[chan].valid) {
if (strlen(g_modem_config_p->achan[chan].mycall) > 0 &&
strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 &&
strcasecmp(g_modem_config_p->achan[chan].mycall, "NOCALL") != 0) {
switch (g_misc_config_p->beacon[j].btype) {
case BEACON_OBJECT:
/* Object name is required. */
if (strlen(g_misc_config_p->beacon[j].objname) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: OBJNAME is required for OBEACON.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
/* Fall thru. Ignore any warning about missing break. */
case BEACON_POSITION:
/* Location is required. */
if (g_misc_config_p->beacon[j].lat == G_UNKNOWN || g_misc_config_p->beacon[j].lon == G_UNKNOWN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Latitude and longitude are required.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
break;
case BEACON_TRACKER:
{
dwgps_info_t gpsinfo;
dwfix_t fix;
fix = dwgps_read (&gpsinfo);
if (fix == DWFIX_NOT_INIT) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: GPS must be configured to use TBEACON.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
}
}
break;
case BEACON_CUSTOM:
/* INFO or INFOCMD is required. */
if (g_misc_config_p->beacon[j].custom_info == NULL && g_misc_config_p->beacon[j].custom_infocmd == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: INFO or INFOCMD is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
break;
case BEACON_IGATE:
/* Doesn't make sense if IGate is not configured. */
if (strlen(g_igate_config_p->t2_server_name) == 0 ||
strlen(g_igate_config_p->t2_login) == 0 ||
strlen(g_igate_config_p->t2_passcode) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Doesn't make sense to use IBEACON without IGate Configured.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("IBEACON has been disabled.\n");
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
break;
case BEACON_IGNORE:
break;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: MYCALL must be set for beacon on channel %d. \n", g_misc_config_p->beacon[j].lineno, chan);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Invalid channel number %d for beacon. \n", g_misc_config_p->beacon[j].lineno, chan);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
}
}
/*
* Calculate first time for each beacon from the 'delay' value.
*/
now = time(NULL);
for (j=0; jnum_beacons; j++) {
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon[%d] chan=%d, delay=%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);
#endif
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay;
}
/*
* Start up thread for processing only if at least one is valid.
*/
count = 0;
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
count++;
}
}
if (count >= 1) {
#if __WIN32__
beacon_th = (HANDLE)_beginthreadex (NULL, 0, &beacon_thread, NULL, 0, NULL);
if (beacon_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create beacon thread\n");
return;
}
#else
int e;
e = pthread_create (&beacon_tid, NULL, beacon_thread, (void *)0);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create beacon thread");
return;
}
#endif
}
} /* end beacon_init */
/*-------------------------------------------------------------------
*
* Name: beacon_thread
*
* Purpose: Transmit beacons when it is time.
*
* Inputs: g_misc_config_p->beacon
*
* Outputs: g_misc_config_p->beacon[].next_time
*
* Description: Go to sleep until it is time for the next beacon.
* Transmit any beacons scheduled for now.
* Repeat.
*
*--------------------------------------------------------------------*/
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#if __WIN32__
static unsigned __stdcall beacon_thread (void *arg)
#else
static void * beacon_thread (void *arg)
#endif
{
int j; /* Index into array of beacons. */
time_t earliest;
time_t now; /* Current time. */
int number_of_tbeacons; /* Number of tracker beacons. */
/*
* SmartBeaconing state.
*/
time_t sb_prev_time = 0; /* Time of most recent transmission. */
float sb_prev_course = 0; /* Most recent course reported. */
#if DEBUG
struct tm tm;
char hms[20];
now = time(NULL);
localtime_r (&now, &tm);
strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon_thread: started %s\n", hms);
#endif
/*
* See if any tracker beacons are configured.
* No need to obtain GPS data if none.
*/
number_of_tbeacons = 0;
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
number_of_tbeacons++;
}
}
now = time(NULL);
while (1) {
dwgps_info_t gpsinfo;
/*
* Sleep until time for the earliest scheduled or
* the soonest we could transmit due to corner pegging.
*/
earliest = now + 60 * 60;
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
earliest = MIN(g_misc_config_p->beacon[j].next, earliest);
}
}
if (g_misc_config_p->sb_configured && number_of_tbeacons > 0) {
earliest = MIN(now + g_misc_config_p->sb_turn_time, earliest);
earliest = MIN(now + g_misc_config_p->sb_fast_rate, earliest);
}
if (earliest > now) {
SLEEP_SEC (earliest - now);
}
/*
* Woke up. See what needs to be done.
*/
now = time(NULL);
#if DEBUG
localtime_r (&now, &tm);
strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon_thread: woke up %s\n", hms);
#endif
/*
* Get information from GPS if being used.
* This needs to be done before the next scheduled tracker
* beacon because corner pegging make it sooner.
*/
if (number_of_tbeacons > 0) {
dwfix_t fix = dwgps_read (&gpsinfo);
float my_speed_mph = DW_KNOTS_TO_MPH(gpsinfo.speed_knots);
if (g_tracker_debug_level >= 1) {
struct tm tm;
char hms[20];
localtime_r (&now, &tm);
strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
text_color_set(DW_COLOR_DEBUG);
if (fix == 3) {
dw_printf ("%s 3D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0, %.1f m\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track, gpsinfo.altitude);
}
else if (fix == 2) {
dw_printf ("%s 2D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track);
}
else {
dw_printf ("%s No GPS fix\n", hms);
}
}
/* Don't complain here for no fix. */
/* Possibly at the point where about to transmit. */
/*
* Run SmartBeaconing calculation if configured and GPS data available.
*/
if (g_misc_config_p->sb_configured && fix >= DWFIX_2D) {
time_t tnext = sb_calculate_next_time (now,
DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track,
sb_prev_time, sb_prev_course);
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
/* Haven't thought about the consequences of SmartBeaconing */
/* and having more than one tbeacon configured. */
if (tnext < g_misc_config_p->beacon[j].next) {
g_misc_config_p->beacon[j].next = tnext;
}
}
} /* Update next time if sooner. */
} /* apply SmartBeaconing */
} /* tbeacon(s) configured. */
/*
* Send if the time has arrived.
*/
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
continue;
if (g_misc_config_p->beacon[j].next <= now) {
/* Send the beacon. */
beacon_send (j, &gpsinfo);
/* 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 (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;
}
else if (g_misc_config_p->sb_configured) {
/* Remember most recent tracker beacon. */
/* Compute next time if not turning. */
sb_prev_time = now;
sb_prev_course = gpsinfo.track;
g_misc_config_p->beacon[j].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;
}
}
else {
/* non-tracker beacons are at fixed spacing. */
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
}
} /* if time to send it */
} /* for each configured beacon */
} /* do forever */
#if __WIN32__
return(0); /* unreachable but warning if not here. */
#else
return(NULL);
#endif
} /* end beacon_thread */
/*-------------------------------------------------------------------
*
* Name: sb_calculate_next_time
*
* Purpose: Calculate next transmission time using the SmartBeaconing algorithm.
*
* Inputs: now - Current time.
*
* current_speed_mph - Current speed from GPS.
* Not expecting G_UNKNOWN but should check for it.
*
* current_course - Current direction of travel.
* Could be G_UNKNOWN if stationary.
*
* last_xmit_time - Time of most recent transmission.
*
* last_xmit_course - Direction included in most recent transmission.
*
* Global In: g_misc_config_p->
* sb_configured TRUE if SmartBeaconing is configured.
* sb_fast_speed MPH
* sb_fast_rate seconds
* sb_slow_speed MPH
* sb_slow_rate seconds
* sb_turn_time seconds
* sb_turn_angle degrees
* sb_turn_slope degrees * MPH
*
* Returns: Time of next transmission.
* Could vary from now to sb_slow_rate in the future.
*
* Caution: The algorithm is defined in MPH units. GPS uses knots.
* The caller must be careful about using the proper conversions.
*
*--------------------------------------------------------------------*/
/* Difference between two angles. */
static float heading_change (float a, float b)
{
float diff;
diff = fabs(a - b);
if (diff <= 180.)
return (diff);
else
return (360. - diff);
}
static time_t sb_calculate_next_time (time_t now,
float current_speed_mph, float current_course,
time_t last_xmit_time, float last_xmit_course)
{
int beacon_rate;
time_t next_time;
/*
* Compute time between beacons for travelling in a straight line.
*/
if (current_speed_mph == G_UNKNOWN) {
beacon_rate = (int)roundf((g_misc_config_p->sb_fast_rate + g_misc_config_p->sb_slow_rate) / 2.);
}
else if (current_speed_mph > g_misc_config_p->sb_fast_speed) {
beacon_rate = g_misc_config_p->sb_fast_rate;
}
else if (current_speed_mph < g_misc_config_p->sb_slow_speed) {
beacon_rate = g_misc_config_p->sb_slow_rate;
}
else {
/* Can't divide by 0 assuming sb_slow_speed > 0. */
beacon_rate = (int)roundf(( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / current_speed_mph);
}
if (g_tracker_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("SmartBeaconing: Beacon Rate = %d seconds for %.1f MPH\n", beacon_rate, current_speed_mph);
}
next_time = last_xmit_time + beacon_rate;
/*
* Test for "Corner Pegging" if moving.
*/
if (current_speed_mph != G_UNKNOWN && current_speed_mph >= 1.0 &&
current_course != G_UNKNOWN && last_xmit_course != G_UNKNOWN) {
float change = heading_change(current_course, last_xmit_course);
float turn_threshold = g_misc_config_p->sb_turn_angle +
g_misc_config_p->sb_turn_slope / current_speed_mph;
if (change > turn_threshold &&
now >= last_xmit_time + g_misc_config_p->sb_turn_time) {
if (g_tracker_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("SmartBeaconing: Send now for heading change of %.0f\n", change);
}
next_time = now;
}
}
return (next_time);
} /* end sb_calculate_next_time */
/*-------------------------------------------------------------------
*
* Name: beacon_send
*
* Purpose: Transmit one beacon after it was determined to be time.
*
* Inputs: j Index into beacon configuration array below.
*
* gpsinfo Information from GPS. Used only for TBEACON.
*
* Global In: g_misc_config_p->beacon Array of beacon configurations.
*
* Outputs: Destination(s) specified:
* - Transmit queue.
* - IGate.
* - Simulated reception.
*
* Description: Prepare text in monitor format.
* Convert to packet object.
* Send to desired destination(s).
*
*--------------------------------------------------------------------*/
static void beacon_send (int j, dwgps_info_t *gpsinfo)
{
int strict = 1; /* Strict packet checking because they will go over air. */
char stemp[20];
char info[AX25_MAX_INFO_LEN];
char beacon_text[AX25_MAX_PACKET_LEN];
packet_t pp = NULL;
char mycall[AX25_MAX_ADDR_LEN];
char super_comment[AX25_MAX_INFO_LEN]; // Fixed part + any dynamic part.
/*
* Obtain source call for the beacon.
* This could potentially be different on different channels.
* When sending to IGate server, use call from first radio channel.
*
* Check added in version 1.0a. Previously used index of -1.
*
* Version 1.1 - channel should now be 0 for IGate.
* Type of destination is encoded separately.
*/
strlcpy (mycall, "NOCALL", sizeof(mycall));
assert (g_misc_config_p->beacon[j].sendto_chan >= 0);
strlcpy (mycall, g_modem_config_p->achan[g_misc_config_p->beacon[j].sendto_chan].mycall, sizeof(mycall));
if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("MYCALL not set for beacon in config file line %d.\n", g_misc_config_p->beacon[j].lineno);
return;
}
/*
* Prepare the monitor format header.
*
* src > dest [ , via ]
*/
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));
}
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) {
strlcat (beacon_text, ",", sizeof(beacon_text));
strlcat (beacon_text, g_misc_config_p->beacon[j].via, sizeof(beacon_text));
}
strlcat (beacon_text, ":", sizeof(beacon_text));
/*
* If the COMMENTCMD option was specified, run specified command to get variable part.
* Result is any fixed part followed by any variable part.
*/
// TODO: test & document.
strlcpy (super_comment, "", sizeof(super_comment));
if (g_misc_config_p->beacon[j].comment != NULL) {
strlcpy (super_comment, g_misc_config_p->beacon[j].comment, sizeof(super_comment));
}
if (g_misc_config_p->beacon[j].commentcmd != NULL) {
char var_comment[AX25_MAX_INFO_LEN];
int k;
/* Run given command to get variable part of comment. */
k = dw_run_cmd (g_misc_config_p->beacon[j].commentcmd, 2, var_comment, sizeof(var_comment));
if (k > 0) {
strlcat (super_comment, var_comment, sizeof(super_comment));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", g_misc_config_p->beacon[j].lineno);
}
}
/*
* Add the info part depending on beacon type.
*/
switch (g_misc_config_p->beacon[j].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,
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,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
break;
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,
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,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
break;
case BEACON_TRACKER:
if (gpsinfo->fix >= DWFIX_2D) {
int coarse; /* Round to nearest integer. retaining unknown state. */
int my_alt_ft;
/* Transmit altitude only if user asked for it. */
/* A positive altitude in the config file enables */
/* 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) {
my_alt_ft = (int)roundf(DW_METERS_TO_FEET(gpsinfo->altitude));
}
coarse = G_UNKNOWN;
if (gpsinfo->track != G_UNKNOWN) {
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,
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,
super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
/* Write to log file for testing. */
/* The idea is to run log2gpx and map the result rather than */
/* actually transmitting and relying on someone else to receive */
/* the signals. */
if (g_tracker_debug_level >= 3) {
decode_aprs_t A;
alevel_t alevel;
memset (&A, 0, sizeof(A));
A.g_freq = G_UNKNOWN;
A.g_offset = G_UNKNOWN;
A.g_tone = G_UNKNOWN;
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_lat = gpsinfo->dlat;
A.g_lon = gpsinfo->dlon;
A.g_speed_mph = DW_KNOTS_TO_MPH(gpsinfo->speed_knots);
A.g_course = coarse;
A.g_altitude_ft = DW_METERS_TO_FEET(gpsinfo->altitude);
/* Fake channel of 999 to distinguish from real data. */
memset (&alevel, 0, sizeof(alevel));
log_write (999, &A, NULL, alevel, 0);
}
}
else {
return; /* No fix. Skip this time. */
}
break;
case BEACON_CUSTOM:
if (g_misc_config_p->beacon[j].custom_info != NULL) {
/* Fixed handcrafted text. */
strlcat (beacon_text, g_misc_config_p->beacon[j].custom_info, sizeof(beacon_text));
}
else if (g_misc_config_p->beacon[j].custom_infocmd != NULL) {
char info_part[AX25_MAX_INFO_LEN];
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));
if (k > 0) {
strlcat (beacon_text, info_part, sizeof(beacon_text));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", g_misc_config_p->beacon[j].lineno);
strlcpy (beacon_text, "", sizeof(beacon_text)); // abort!
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__);
strlcpy (beacon_text, "", sizeof(beacon_text)); // abort!
}
break;
case BEACON_IGATE:
{
int last_minutes = 30;
char stuff[256];
snprintf (stuff, sizeof(stuff), "max_digi_hops,last_minutes),
mheard_count(8,last_minutes),
igate_get_upl_cnt(),
igate_get_dnl_cnt());
strlcat (beacon_text, stuff, sizeof(beacon_text));
}
break;
case BEACON_IGNORE:
default:
break;
} /* switch beacon type. */
/*
* Parse monitor format into form for transmission.
*/
if (strlen(beacon_text) == 0) {
return;
}
pp = ax25_from_text (beacon_text, strict);
if (pp != NULL) {
/* Send to desired destination. */
alevel_t alevel;
switch (g_misc_config_p->beacon[j].sendto_type) {
case SENDTO_IGATE:
text_color_set(DW_COLOR_XMIT);
dw_printf ("[ig] %s\n", beacon_text);
igate_send_rec_packet (0, pp);
ax25_delete (pp);
break;
case SENDTO_XMIT:
default:
tq_append (g_misc_config_p->beacon[j].sendto_chan, TQ_PRIO_1_LO, pp);
break;
case SENDTO_RECV:
/* 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, "");
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 ("%s\n", beacon_text);
}
} /* end beacon_send */
/* end beacon.c */
direwolf-1.4/beacon.h 0000664 0000000 0000000 00000000246 13100232553 0014565 0 ustar 00root root 0000000 0000000
/* beacon.h */
void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate);
void beacon_tracker_set_debug (int level);
direwolf-1.4/cdigipeater.c 0000664 0000000 0000000 00000020430 13100232553 0015606 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Name: cdigipeater.c
*
* Purpose: Act as an digital repeater for connected AX.25 mode.
* Similar digipeater.c is for APRS.
*
*
* Description: Decide whether the specified packet should
* be digipeated. Put my callsign in the digipeater field used.
*
* APRS and connected mode were two split into two
* separate files. Yes, there is duplicate code but they
* are significantly different and I thought it would be
* too confusing to munge them together.
*
* References: The Ax.25 protcol barely mentions digipeaters and
* and doesn't describe how they should work.
*
*------------------------------------------------------------------*/
#define CDIGIPEATER_C
#include "direwolf.h"
#include
#include
#include
#include
#include /* for isdigit, isupper */
#include "regex.h"
#include
#include "ax25_pad.h"
#include "cdigipeater.h"
#include "textcolor.h"
#include "tq.h"
#include "pfilter.h"
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str);
/*
* Keep pointer to configuration options.
* Set by cdigipeater_init and used later.
*/
static struct audio_s *save_audio_config_p;
static struct cdigi_config_s *save_cdigi_config_p;
/*
* Maintain count of packets digipeated for each combination of from/to channel.
*/
static int cdigi_count[MAX_CHANS][MAX_CHANS];
int cdigipeater_get_count (int from_chan, int to_chan) {
return (cdigi_count[from_chan][to_chan]);
}
/*------------------------------------------------------------------------------
*
* Name: cdigipeater_init
*
* Purpose: Initialize with stuff from configuration file.
*
* Inputs: p_audio_config - Configuration for audio channels.
*
* p_cdigi_config - Connected Digipeater configuration details.
*
* Outputs: Save pointers to configuration for later use.
*
* Description: Called once at application startup time.
*
*------------------------------------------------------------------------------*/
void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config)
{
save_audio_config_p = p_audio_config;
save_cdigi_config_p = p_cdigi_config;
}
/*------------------------------------------------------------------------------
*
* Name: cdigipeater
*
* Purpose: Re-transmit packet if it matches the rules.
*
* Inputs: chan - Radio channel where it was received.
*
* pp - Packet object.
*
* Returns: None.
*
*------------------------------------------------------------------------------*/
void cdigipeater (int from_chan, packet_t pp)
{
int to_chan;
if ( from_chan < 0 || from_chan >= MAX_CHANS || ( ! save_audio_config_p->achan[from_chan].valid) ) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
return;
}
/*
* First pass: Look at packets being digipeated to same channel.
*
* There was a reason for two passes for APRS.
* Might not have a benefit here.
*/
for (to_chan=0; to_chanenabled[from_chan][to_chan]) {
if (to_chan == from_chan) {
packet_t result;
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
}
}
}
}
/*
* Second pass: Look at packets being digipeated to different channel.
*/
for (to_chan=0; to_chanenabled[from_chan][to_chan]) {
if (to_chan != from_chan) {
packet_t result;
result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall,
save_audio_config_p->achan[to_chan].mycall,
&save_cdigi_config_p->alias[from_chan][to_chan], to_chan,
save_cdigi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
tq_append (to_chan, TQ_PRIO_0_HI, result);
cdigi_count[from_chan][to_chan]++;
}
}
}
}
} /* end cdigipeater */
/*------------------------------------------------------------------------------
*
* Name: cdigipeat_match
*
* Purpose: A simple digipeater for connected mode AX.25.
*
* Input: pp - Pointer to a packet object.
*
* mycall_rec - Call of my station, with optional SSID,
* associated with the radio channel where the
* packet was received.
*
* mycall_xmit - Call of my station, with optional SSID,
* associated with the radio channel where the
* packet is to be transmitted. Could be the same as
* mycall_rec or different.
*
* alias - Compiled pattern for my station aliases.
* Could be NULL if no aliases.
*
* to_chan - Channel number that we are transmitting to.
*
* filter_str - Filter expression string or NULL.
*
* Returns: Packet object for transmission or NULL.
* The original packet is not modified. The caller is responsible for freeing it.
* We make a copy and return that modified copy!
* This is very important because we could digipeat from one channel to many.
*
* Description: The packet will be digipeated if the next unused digipeater
* field matches one of the following:
*
* - mycall_rec
* - alias list
*
* APRS digipeating drops duplicates within 30 seconds but we don't do that here.
*
*------------------------------------------------------------------------------*/
static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
regex_t *alias, int to_chan, char *filter_str)
{
int r;
char repeater[AX25_MAX_ADDR_LEN];
int err;
char err_msg[100];
/*
* First check if filtering has been configured.
*/
if (filter_str != NULL) {
if (pfilter(from_chan, to_chan, filter_str, pp, 0) != 1) {
return(NULL);
}
}
/*
* Find the first repeater station which doesn't have "has been repeated" set.
*
* r = index of the address position in the frame.
*/
r = ax25_get_first_not_repeated(pp);
if (r < AX25_REPEATER_1) {
return (NULL); // Nothing to do.
}
ax25_get_addr_with_ssid(pp, r, repeater);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("First unused digipeater is %s\n", repeater);
#endif
/*
* First check for explicit use of my call.
* Note that receive and transmit channels could have different callsigns.
*/
if (strcmp(repeater, mycall_rec) == 0) {
packet_t result;
result = ax25_dup (pp);
assert (result != NULL);
/* If using multiple radio channels, they could have different calls. */
ax25_set_addr (result, r, mycall_xmit);
ax25_set_h (result, r);
return (result);
}
/*
* If we have an alias match, substitute MYCALL.
*/
if (alias != NULL) {
err = regexec(alias,repeater,0,NULL,0);
if (err == 0) {
packet_t result;
result = ax25_dup (pp);
assert (result != NULL);
ax25_set_addr (result, r, mycall_xmit);
ax25_set_h (result, r);
return (result);
}
else if (err != REG_NOMATCH) {
regerror(err, alias, err_msg, sizeof(err_msg));
text_color_set (DW_COLOR_ERROR);
dw_printf ("%s\n", err_msg);
}
}
/*
* Don't repeat it if we get here.
*/
return (NULL);
} /* end cdigipeat_match */
/* end cdigipeater.c */
direwolf-1.4/cdigipeater.h 0000664 0000000 0000000 00000002322 13100232553 0015613 0 ustar 00root root 0000000 0000000
#ifndef CDIGIPEATER_H
#define CDIGIPEATER_H 1
#include "regex.h"
#include "direwolf.h" /* for MAX_CHANS */
#include "ax25_pad.h" /* for packet_t */
#include "audio.h" /* for radio channel properties */
/*
* Information required for Connected mode digipeating.
*
* The configuration file reader fills in this information
* and it is passed to cdigipeater_init at application start up time.
*/
struct cdigi_config_s {
/*
* Rules for each of the [from_chan][to_chan] combinations.
*/
regex_t alias[MAX_CHANS][MAX_CHANS];
int enabled[MAX_CHANS][MAX_CHANS];
char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
// NULL or optional Packet Filter strings such as "t/m".
// Notice the size of arrays is one larger than normal.
// That extra position is for the IGate.
};
/*
* Call once at application start up time.
*/
extern void cdigipeater_init (struct audio_s *p_audio_config, struct cdigi_config_s *p_cdigi_config);
/*
* Call this for each packet received.
* Suitable packets will be queued for transmission.
*/
extern void cdigipeater (int from_chan, packet_t pp);
/* Make statistics available. */
int cdigipeater_get_count (int from_chan, int to_chan);
#endif
/* end cdigipeater.h */
direwolf-1.4/config.c 0000664 0000000 0000000 00000445400 13100232553 0014603 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#define CONFIG_C 1 // influences behavior of aprs_tt.h
// #define DEBUG 1
/*------------------------------------------------------------------
*
* Module: config.c
*
* Purpose: Read configuration information from a file.
*
* Description: This started out as a simple little application with a few
* command line options. Due to creeping featurism, it's now
* time to add a configuration file to specify options.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#if ENABLE_GPSD
#include /* for DEFAULT_GPSD_PORT (2947) */
#endif
#include "ax25_pad.h"
#include "textcolor.h"
#include "audio.h"
#include "digipeater.h"
#include "cdigipeater.h"
#include "config.h"
#include "aprs_tt.h"
#include "igate.h"
#include "latlong.h"
#include "symbols.h"
#include "xmit.h"
#include "tt_text.h"
#include "ax25_link.h"
// geotranz
#include "utm.h"
#include "mgrs.h"
#include "usng.h"
#include "error_string.h"
#define D2R(d) ((d) * M_PI / 180.)
#define R2D(r) ((r) * 180. / M_PI)
//#include "tq.h"
/*
* Conversions from various units to meters.
* There is some disagreement about the exact values for some of these.
* Close enough for our purposes.
* Parsec, light year, and angstrom are probably not useful.
*/
static const struct units_s {
char *name;
float meters;
} units[] = {
{ "barleycorn", 0.008466667 },
{ "inch", 0.0254 },
{ "in", 0.0254 },
{ "hand", 0.1016 },
{ "shaku", 0.3030 },
{ "foot", 0.304801 },
{ "ft", 0.304801 },
{ "cubit", 0.4572 },
{ "megalithicyard", 0.8296 },
{ "my", 0.8296 },
{ "yard", 0.914402 },
{ "yd", 0.914402 },
{ "m", 1. },
{ "meter", 1. },
{ "metre", 1. },
{ "ell", 1.143 },
{ "ken", 1.818 },
{ "hiro", 1.818 },
{ "fathom", 1.8288 },
{ "fath", 1.8288 },
{ "toise", 1.949 },
{ "jo", 3.030 },
{ "twain", 3.6576074 },
{ "rod", 5.0292 },
{ "rd", 5.0292 },
{ "perch", 5.0292 },
{ "pole", 5.0292 },
{ "rope", 6.096 },
{ "dekameter", 10. },
{ "dekametre", 10. },
{ "dam", 10. },
{ "chain", 20.1168 },
{ "ch", 20.1168 },
{ "actus", 35.47872 },
{ "arpent", 58.471 },
{ "hectometer", 100. },
{ "hectometre", 100. },
{ "hm", 100. },
{ "cho", 109.1 },
{ "furlong", 201.168 },
{ "fur", 201.168 },
{ "kilometer", 1000. },
{ "kilometre", 1000. },
{ "km", 1000. },
{ "mile", 1609.344 },
{ "mi", 1609.344 },
{ "ri", 3927. },
{ "league", 4828.032 },
{ "lea", 4828.032 } };
#define NUM_UNITS ((int)((sizeof(units) / sizeof(struct units_s))))
static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config);
/* Do we have a string of all digits? */
static int alldigits(char *p)
{
if (p == NULL) return (0);
if (strlen(p) == 0) return (0);
while (*p != '\0') {
if ( ! isdigit(*p)) return (0);
p++;
}
return (1);
}
/* Do we have a string of all letters or + or - ? */
static int alllettersorpm(char *p)
{
if (p == NULL) return (0);
if (strlen(p) == 0) return (0);
while (*p != '\0') {
if ( ! isalpha(*p) && *p != '+' && *p != '-') return (0);
p++;
}
return (1);
}
/*------------------------------------------------------------------
*
* Name: parse_ll
*
* Purpose: Parse latitude or longitude from configuration file.
*
* Inputs: str - String like [-]deg[^min][hemisphere]
*
* which - LAT or LON for error checking and message.
*
* line - Line number for use in error message.
*
* Returns: Coordinate in signed degrees.
*
*----------------------------------------------------------------*/
/* Acceptable symbols to separate degrees & minutes. */
/* Degree symbol is not in ASCII so documentation says to use "^" instead. */
/* Some wise guy will try to use degree symbol. */
/* UTF-8 is more difficult because it is a two byte sequence, c2 b0. */
#define DEG1 '^'
#define DEG2 0xb0 /* ISO Latin1 */
#define DEG3 0xf8 /* Microsoft code page 437 */
enum parse_ll_which_e { LAT, LON };
static double parse_ll (char *str, enum parse_ll_which_e which, int line)
{
char stemp[40];
int sign;
double degrees, minutes;
char *endptr;
char hemi;
int limit;
unsigned char sep;
/*
* Remove any negative sign.
*/
strlcpy (stemp, str, sizeof(stemp));
sign = +1;
if (stemp[0] == '-') {
sign = -1;
stemp[0] = ' ';
}
/*
* Process any hemisphere on the end.
*/
if (strlen(stemp) >= 2) {
endptr = stemp + strlen(stemp) - 1;
if (isalpha(*endptr)) {
hemi = *endptr;
*endptr = '\0';
if (islower(hemi)) {
hemi = toupper(hemi);
}
if (hemi == 'W' || hemi == 'S') {
sign = -sign;
}
if (which == LAT) {
if (hemi != 'N' && hemi != 'S') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Latitude hemisphere in \"%s\" is not N or S.\n", line, str);
}
}
else {
if (hemi != 'E' && hemi != 'W') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Longitude hemisphere in \"%s\" is not E or W.\n", line, str);
}
}
}
}
/*
* Parse the degrees part.
*/
degrees = strtod (stemp, &endptr);
/*
* Is there a minutes part?
*/
sep = *endptr;
if (sep != '\0') {
if (sep == DEG1 || sep == DEG2 || sep == DEG3) {
minutes = strtod (endptr+1, &endptr);
if (*endptr != '\0') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str);
}
if (minutes >= 60.0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Number of minutes in \"%s\" is >= 60.\n", line, str);
}
degrees += minutes / 60.0;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str);
}
}
degrees = degrees * sign;
limit = which == LAT ? 90 : 180;
if (degrees < -limit || degrees > limit) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Number of degrees in \"%s\" is out of range for %s\n", line, str,
which == LAT ? "latitude" : "longitude");
}
//dw_printf ("%s = %f\n", str, degrees);
return (degrees);
}
/*------------------------------------------------------------------
*
* Name: parse_utm_zone
*
* Purpose: Parse UTM zone from configuration file.
*
* Inputs: szone - String like [-]number[letter]
*
* Outputs: latband - Latitude band if specified, otherwise space or -.
*
* hemi - Hemisphere, always one of 'N' or 'S'.
*
* Returns: Zone as number.
* Type is long because Convert_UTM_To_Geodetic expects that.
*
* Errors: Prints message and return 0.
*
* Description:
* It seems there are multiple conventions for specifying the UTM hemisphere.
*
* - MGRS latitude band. North if missing or >= 'N'.
* - Negative zone for south.
* - Separate North or South.
*
* I'm using the first alternatve.
* GEOTRANS uses the third.
* We will also recognize the second one but I'm not sure if I want to document it.
*
*----------------------------------------------------------------*/
long parse_utm_zone (char *szone, char *latband, char *hemi)
{
long lzone;
char *zlet;
*latband = ' ';
*hemi = 'N'; /* default */
lzone = strtol(szone, &zlet, 10);
if (*zlet == '\0') {
/* Number is not followed by letter something else. */
/* Allow negative number to mean south. */
if (lzone < 0) {
*latband = '-';
*hemi = 'S';
lzone = (- lzone);
}
}
else {
if (islower (*zlet)) {
*zlet = toupper(*zlet);
}
*latband = *zlet;
if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) {
if (*zlet < 'N') {
*hemi = 'S';
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Latitudinal band in \"%s\" must be one of CDEFGHJKLMNPQRSTUVWX.\n", szone);
*hemi = '?';
}
}
if (lzone < 1 || lzone > 60) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("UTM Zone number %ld must be in range of 1 to 60.\n", lzone);
}
return (lzone);
} /* end parse_utm_zone */
#if 0
main ()
{
parse_ll ("12.5", LAT);
parse_ll ("12.5N", LAT);
parse_ll ("12.5E", LAT); // error
parse_ll ("-12.5", LAT);
parse_ll ("12.5S", LAT);
parse_ll ("12.5W", LAT); // error
parse_ll ("12.5", LON);
parse_ll ("12.5E", LON);
parse_ll ("12.5N", LON); // error
parse_ll ("-12.5", LON);
parse_ll ("12.5W", LON);
parse_ll ("12.5S", LON); // error
parse_ll ("12^30", LAT);
parse_ll ("12\xb030", LAT); // ISO Latin-1 degree symbol
parse_ll ("91", LAT); // out of range
parse_ll ("91", LON);
parse_ll ("181", LON); // out of range
parse_ll ("12&5", LAT); // bad character
}
#endif
/*------------------------------------------------------------------
*
* Name: parse_interval
*
* Purpose: Parse time interval from configuration file.
*
* Inputs: str - String like 10 or 9:30
*
* line - Line number for use in error message.
*
* Returns: Number of seconds.
*
* Description: This is used by the BEACON configuration items
* for initial delay or time between beacons.
*
* The format is either minutes or minutes:seconds.
*
*----------------------------------------------------------------*/
static int parse_interval (char *str, int line)
{
char *p;
int sec;
int nc = 0;
int bad = 0;
for (p = str; *p != '\0'; p++) {
if (*p == ':') nc++;
else if ( ! isdigit(*p)) bad++;
}
if (bad > 0 || nc > 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Time interval must be of the form minutes or minutes:seconds.\n", line);
}
p = strchr (str, ':');
if (p != NULL) {
sec = atoi(str) * 60 + atoi(p+1);
}
else {
sec = atoi(str) * 60;
}
return (sec);
} /* end parse_interval */
/*------------------------------------------------------------------
*
* Name: check_via_path
*
* Purpose: Check for valid path in beacons, IGate, and APRStt configuration.
*
* Inputs: via_path - Zero or more comma separated stations.
*
* Returns: Maximum number of digipeater hops or -1 for error.
*
* Description: Beacons and IGate can use via paths such as:
*
* WIDE1-1,MA3-3
* N2GH,RARA-7
*
* Each part could be a specific station, an alias, or a path
* from the "New n-N Paradigm."
* In the first example above, the maximum number of digipeater
* hops would be 4. In the second example, 2.
*
*----------------------------------------------------------------*/
// Put something like this in the config file as a quick test.
// Not worth adding to "make check" regression tests.
//
// IBEACON via=
// IBEACON via=W2UB
// IBEACON via=W2UB-7
// IBEACON via=WIDE1-1,WIDE2-2,WIDE3-3
// IBEACON via=Lower
// IBEACON via=T00LONG
// IBEACON via=W2UB-16
// IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8
// IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8,D9
//
// Define below and visually check results.
//#define DEBUG8 1
static int check_via_path (char *via_path)
{
char stemp[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN + 1)];
int num_digi = 0;
int max_digi_hops = 0;
char *r;
char *a;
#if DEBUG8
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check_via_path %s\n", via_path);
#endif
if (strlen(via_path) == 0) {
return (0);
}
strlcpy (stemp, via_path, sizeof(stemp));
r = stemp;
while (( a = strsep(&r,",")) != NULL) {
int strict = 1;
int ok;
char addr[AX25_MAX_ADDR_LEN];
int ssid;
int heard;
num_digi++;
ok = ax25_parse_addr (AX25_REPEATER_1 - 1 + num_digi, a, strict, addr, &ssid, &heard);
if ( ! ok) {
#if DEBUG8
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check_via_path bad address\n");
#endif
return (-1);
}
/* Based on assumption that a callsign can't end with a digit. */
/* For something of the form xxx9-9, we take the ssid as max hop count. */
if (ssid > 0 && strlen(addr) >= 2 && isdigit(addr[strlen(addr)-1])) {
max_digi_hops += ssid;
}
else {
max_digi_hops++;
}
}
if (num_digi > AX25_MAX_REPEATERS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Maximum of 8 digipeaters has been exceeded.\n");
return (-1);
}
#if DEBUG8
text_color_set(DW_COLOR_DEBUG);
dw_printf ("check_via_path %d addresses, %d max digi hops\n", num_digi, max_digi_hops);
#endif
return (max_digi_hops);
} /* end check_via_path */
/*-------------------------------------------------------------------
*
* Name: split
*
* Purpose: Separate a line into command and parameters.
*
* Inputs: string - Complete command line to start process.
* NULL for subsequent calls.
*
* rest_of_line - Caller wants remainder of line, not just
* the next parameter.
*
* Returns: Pointer to next part with any quoting removed.
*
* Description: the configuration file started out very simple and strtok
* was used to split up the lines. As more complicated options
* were added, there were several different situations where
* parameter values might contain spaces. These were handled
* inconsistently in different places. In version 1.3, we now
* treat them consistently in one place.
*
*
*--------------------------------------------------------------------*/
#define MAXCMDLEN 256
static char *split (char *string, int rest_of_line)
{
static char cmd[MAXCMDLEN];
static char token[MAXCMDLEN];
static char *c; // current position in cmd.
char *s, *t;
int in_quotes;
/*
* If string is provided, make a copy.
* Drop any CRLF at the end.
* Change any tabs to spaces so we don't have to check for it later.
*/
if (string != NULL) {
// dw_printf("split in: '%s'\n", string);
c = cmd;
for (s = string; *s != '\0'; s++) {
if (*s == '\t') {
*c++ = ' ';
}
else if (*s == '\r' || *s == '\n') {
;
}
else {
*c++ = *s;
}
}
*c = '\0';
c = cmd;
}
/*
* Get next part, separated by whitespace, keeping spaces within quotes.
* Quotation marks inside need to be doubled.
*/
while (*c == ' ') {
c++;
};
t = token;
in_quotes = 0;
for ( ; *c != '\0'; c++) {
if (*c == '"') {
if (in_quotes) {
if (c[1] == '"') {
*t++ = *c++;
}
else {
in_quotes = 0;
}
}
else {
in_quotes = 1;
}
}
else if (*c == ' ') {
if (in_quotes || rest_of_line) {
*t++ = *c;
}
else {
break;
}
}
else {
*t++ = *c;
}
}
*t = '\0';
// dw_printf("split out: '%s'\n", token);
t = token;
if (*t == '\0') {
return (NULL);
}
return (t);
} /* end split */
/*-------------------------------------------------------------------
*
* Name: config_init
*
* Purpose: Read configuration file when application starts up.
*
* Inputs: fname - Name of configuration file.
*
* Outputs: p_audio_config - Radio channel parameters stored here.
*
* p_digi_config - APRS Digipeater configuration stored here.
*
* p_cdigi_config - Connected Digipeater configuration stored here.
*
* p_tt_config - APRStt stuff.
*
* p_igate_config - Internet Gateway.
*
* p_misc_config - Everything else. This wasn't thought out well.
*
* Description: Apply default values for various parameters then read the
* the configuration file which can override those values.
*
* Errors: For invalid input, display line number and message on stdout (not stderr).
* In many cases this will result in keeping the default rather than aborting.
*
* Bugs: Very simple-minded parsing.
* Not much error checking. (e.g. atoi() will return 0 for invalid string.)
* Not very forgiving about sloppy input.
*
*--------------------------------------------------------------------*/
void config_init (char *fname, struct audio_s *p_audio_config,
struct digi_config_s *p_digi_config,
struct cdigi_config_s *p_cdigi_config,
struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config,
struct misc_config_s *p_misc_config)
{
FILE *fp;
char filepath[128];
char stuff[MAXCMDLEN];
int line;
int channel;
int adevice;
int m;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("config_init ( %s )\n", fname);
#endif
/*
* First apply defaults.
*/
memset (p_audio_config, 0, sizeof(struct audio_s));
/* First audio device is always available with defaults. */
/* Others must be explicitly defined before use. */
for (adevice=0; adeviceadev[adevice].adevice_in, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_in));
strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out));
p_audio_config->adev[adevice].defined = 0;
p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */
p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */
p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */
}
p_audio_config->adev[0].defined = 1;
for (channel=0; channelachan[channel].valid = 0; /* One or both channels will be */
/* set to valid when corresponding */
/* audio device is defined. */
p_audio_config->achan[channel].modem_type = MODEM_AFSK;
p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; /* -m option */
p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; /* -s option */
p_audio_config->achan[channel].baud = DEFAULT_BAUD; /* -b option */
/* None. Will set default later based on other factors. */
strlcpy (p_audio_config->achan[channel].profiles, "", sizeof(p_audio_config->achan[channel].profiles));
p_audio_config->achan[channel].num_freq = 1;
p_audio_config->achan[channel].offset = 0;
p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
p_audio_config->achan[channel].sanity_test = SANITY_APRS;
p_audio_config->achan[channel].passall = 0;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_NONE;
strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "", sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE;
p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE;
p_audio_config->achan[channel].octrl[ot].out_gpio_num = 0;
p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = 0;
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
}
for (it = 0; it < NUM_ICTYPES; it++) {
p_audio_config->achan[channel].ictrl[it].method = PTT_METHOD_NONE;
p_audio_config->achan[channel].ictrl[it].in_gpio_num = 0;
p_audio_config->achan[channel].ictrl[it].invert = 0;
}
p_audio_config->achan[channel].dwait = DEFAULT_DWAIT;
p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;
p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;
p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;
}
/* First channel should always be valid. */
/* If there is no ADEVICE, it uses default device in mono. */
p_audio_config->achan[0].valid = 1;
memset (p_digi_config, 0, sizeof(struct digi_config_s));
p_digi_config->dedupe_time = DEFAULT_DEDUPE;
memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s));
memset (p_tt_config, 0, sizeof(struct tt_config_s));
p_tt_config->gateway_enabled = 0;
p_tt_config->ttloc_size = 2; /* Start with at least 2. */
/* When full, it will be increased by 50 %. */
p_tt_config->ttloc_ptr = malloc (sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
p_tt_config->ttloc_len = 0;
/* Retention time and decay algorithm from 13 Feb 13 version of */
/* http://www.aprs.org/aprstt/aprstt-coding24.txt */
/* Reduced by transmit count by one. An 8 minute delay in between transmissions seems awful long. */
p_tt_config->retain_time = 80 * 60;
p_tt_config->num_xmits = 6;
assert (p_tt_config->num_xmits <= TT_MAX_XMITS);
p_tt_config->xmit_delay[0] = 3; /* Before initial transmission. */
p_tt_config->xmit_delay[1] = 16;
p_tt_config->xmit_delay[2] = 32;
p_tt_config->xmit_delay[3] = 64;
p_tt_config->xmit_delay[4] = 2 * 60;
p_tt_config->xmit_delay[5] = 4 * 60;
p_tt_config->xmit_delay[6] = 8 * 60; // not currently used.
strlcpy (p_tt_config->status[0], "", sizeof(p_tt_config->status[0]));
strlcpy (p_tt_config->status[1], "/off duty", sizeof(p_tt_config->status[1]));
strlcpy (p_tt_config->status[2], "/enroute", sizeof(p_tt_config->status[2]));
strlcpy (p_tt_config->status[3], "/in service", sizeof(p_tt_config->status[3]));
strlcpy (p_tt_config->status[4], "/returning", sizeof(p_tt_config->status[4]));
strlcpy (p_tt_config->status[5], "/committed", sizeof(p_tt_config->status[5]));
strlcpy (p_tt_config->status[6], "/special", sizeof(p_tt_config->status[6]));
strlcpy (p_tt_config->status[7], "/priority", sizeof(p_tt_config->status[7]));
strlcpy (p_tt_config->status[8], "/emergency", sizeof(p_tt_config->status[8]));
strlcpy (p_tt_config->status[9], "/custom 1", sizeof(p_tt_config->status[9]));
for (m = 0; m < TT_ERROR_MAXP1; m++) {
strlcpy (p_tt_config->response[m].method, "MORSE", sizeof(p_tt_config->response[m].method));
strlcpy (p_tt_config->response[m].mtext, "?", sizeof(p_tt_config->response[m].mtext));
}
strlcpy (p_tt_config->response[TT_ERROR_OK].mtext, "R", sizeof(p_tt_config->response[TT_ERROR_OK].mtext));
memset (p_misc_config, 0, sizeof(struct misc_config_s));
p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
p_misc_config->kiss_port = DEFAULT_KISS_PORT;
p_misc_config->enable_kiss_pt = 0; /* -p option */
/* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */
p_misc_config->sb_configured = 0; /* TRUE if SmartBeaconing is configured. */
p_misc_config->sb_fast_speed = 60; /* MPH */
p_misc_config->sb_fast_rate = 180; /* seconds */
p_misc_config->sb_slow_speed = 5; /* MPH */
p_misc_config->sb_slow_rate = 1800; /* seconds */
p_misc_config->sb_turn_time = 15; /* seconds */
p_misc_config->sb_turn_angle = 30; /* degrees */
p_misc_config->sb_turn_slope = 255; /* degrees * MPH */
memset (p_igate_config, 0, sizeof(struct igate_config_s));
p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
p_igate_config->tx_chan = -1; /* IS->RF not enabled */
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;
/* 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->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));
/* connected mode. */
p_misc_config->frack = AX25_T1V_FRACK_DEFAULT; /* Number of seconds to wait for ack to transmission. */
p_misc_config->retry = AX25_N2_RETRY_DEFAULT; /* Number of times to retry before giving up. */
p_misc_config->paclen = AX25_N1_PACLEN_DEFAULT; /* Max number of bytes in information part of frame. */
p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; /* Max frames to send before ACK. mod 8 "Window" size. */
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. */
/*
* Try to extract options from a file.
*
* Windows: File must be in current working directory.
*
* Linux: Search current directory then home directory.
*
* Future possibility - Could also search home directory
* for Windows by combinting two variables:
* HOMEDRIVE=C:
* HOMEPATH=\Users\John
*
* It's not clear if this always points to same location:
* USERPROFILE=C:\Users\John
*/
channel = 0;
adevice = 0;
// TODO: Would be better to have a search list and loop thru it.
strlcpy(filepath, fname, sizeof(filepath));
fp = fopen (filepath, "r");
#ifndef __WIN32__
if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) {
/* Failed to open the default location. Try home dir. */
char *p;
strlcpy (filepath, "", sizeof(filepath));
p = getenv("HOME");
if (p != NULL) {
strlcpy (filepath, p, sizeof(filepath));
strlcat (filepath, "/direwolf.conf", sizeof(filepath));
fp = fopen (filepath, "r");
}
}
#endif
if (fp == NULL) {
// TODO: not exactly right for all situations.
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Could not open config file %s\n", filepath);
dw_printf ("Try using -c command line option for alternate location.\n");
return;
}
dw_printf ("\nReading config file %s\n", filepath);
line = 0;
while (fgets(stuff, sizeof(stuff), fp) != NULL) {
char *t;
line++;
t = split(stuff,0);
if (t == NULL) {
continue;
}
if (*t == '#' || *t == '*') {
continue;
}
/*
* ==================== Audio device parameters ====================
*/
/*
* ADEVICE[n] - Name of input sound device, and optionally output, if different.
*
* ADEVICE plughw:1,0 -- same for in and out.
* ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair.
*
*/
/* Note that ALSA name can contain comma such as hw:1,0 */
if (strncasecmp(t, "ADEVICE", 7) == 0) {
adevice = 0;
if (isdigit(t[7])) {
adevice = t[7] - '0';
}
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);
adevice = 0;
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line);
continue;
}
p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */
p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in));
strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
t = split(NULL,0);
if (t != NULL) {
strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
}
}
/*
* PAIDEVICE[n] input-device
* PAODEVICE[n] output-device
*
* This was submitted by KK5VD for the Mac OS X version. (__APPLE__)
*
* It looks like device names can contain spaces making it a little
* more difficult to put two names on the same line unless we come up with
* some other delimiter between them or a quoting scheme to handle
* embedded spaces in a name.
*
* It concerns me that we could have one defined without the other
* if we don't put in more error checking later.
*
* version 1.3 dev snapshot C:
*
* We now have a general quoting scheme so the original ADEVICE can handle this.
* These options will probably be removed before general 1.3 release.
*/
else if (strcasecmp(t, "PAIDEVICE") == 0) {
adevice = 0;
if (isdigit(t[9])) {
adevice = t[9] - '0';
}
if (adevice < 0 || adevice >= MAX_ADEVS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line);
adevice = 0;
continue;
}
t = split(NULL,1);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line);
continue;
}
p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */
p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in));
}
else if (strcasecmp(t, "PAODEVICE") == 0) {
adevice = 0;
if (isdigit(t[9])) {
adevice = t[9] - '0';
}
if (adevice < 0 || adevice >= MAX_ADEVS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line);
adevice = 0;
continue;
}
t = split(NULL,1);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line);
continue;
}
p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */
p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
}
/*
* ARATE - Audio samples per second, 11025, 22050, 44100, etc.
*/
else if (strcasecmp(t, "ARATE") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing audio sample rate for ARATE command.\n", line);
continue;
}
n = atoi(t);
if (n >= MIN_SAMPLES_PER_SEC && n <= MAX_SAMPLES_PER_SEC) {
p_audio_config->adev[adevice].samples_per_sec = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Use a more reasonable audio sample rate in range of %d - %d.\n",
line, MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC);
}
}
/*
* ACHANNELS - Number of audio channels for current device: 1 or 2
*/
else if (strcasecmp(t, "ACHANNELS") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing number of audio channels for ACHANNELS command.\n", line);
continue;
}
n = atoi(t);
if (n ==1 || n == 2) {
p_audio_config->adev[adevice].num_channels = n;
/* Set valid channels depending on mono or stereo. */
p_audio_config->achan[ADEVFIRSTCHAN(adevice)].valid = 1;
if (n == 2) {
p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].valid = 1;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Number of audio channels must be 1 or 2.\n", line);
}
}
/*
* ==================== Radio channel parameters ====================
*/
/*
* CHANNEL - Set channel for following commands.
*/
else if (strcasecmp(t, "CHANNEL") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing channel number for CHANNEL command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n < MAX_CHANS) {
channel = n;
if ( ! p_audio_config->achan[n].valid) {
if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not defined.\n",
line, n, ACHAN2ADEV(n));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not in stereo.\n",
line, n, ACHAN2ADEV(n));
}
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1);
}
}
/*
* MYCALL station
*/
else if (strcasecmp(t, "mycall") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing value for MYCALL command on line %d.\n", line);
continue;
}
else {
// Definitely set for current channel.
// Set for other channels which have not been set yet.
int c;
for (c = 0; c < MAX_CHANS; c++) {
if (c == channel ||
strlen(p_audio_config->achan[c].mycall) == 0 ||
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]
}
}
}
}
/*
* MODEM - Set modem properties for current channel.
*
*
* Old style:
* MODEM baud [ mark space [A][B][C][+] [ num-decoders spacing ] ]
*
* New style, version 1.2:
* MODEM speed [ option ] ...
*
* Options:
* mark:space - AFSK tones. Defaults based on speed.
* num@offset - Multiple decoders on different frequencies.
*
*/
else if (strcasecmp(t, "MODEM") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line);
continue;
}
n = atoi(t);
if (n >= MIN_BAUD && n <= MAX_BAUD) {
p_audio_config->achan[channel].baud = n;
if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Warning: Non-standard data rate of %d bits per second. Are you sure?\n", line, n);
}
}
else {
p_audio_config->achan[channel].baud = DEFAULT_BAUD;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable data rate. Using %d bits per second.\n",
line, p_audio_config->achan[channel].baud);
}
/* Set defaults based on speed. */
/* Should be same as -B command line option in direwolf.c. */
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
if (p_audio_config->achan[channel].baud < 600) {
p_audio_config->achan[channel].modem_type = MODEM_AFSK;
p_audio_config->achan[channel].mark_freq = 1600;
p_audio_config->achan[channel].space_freq = 1800;
}
else if (p_audio_config->achan[channel].baud < 1800) {
p_audio_config->achan[channel].modem_type = MODEM_AFSK;
p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
}
else if (p_audio_config->achan[channel].baud < 3600) {
p_audio_config->achan[channel].modem_type = MODEM_QPSK;
p_audio_config->achan[channel].mark_freq = 0;
p_audio_config->achan[channel].space_freq = 0;
}
else if (p_audio_config->achan[channel].baud < 7200) {
p_audio_config->achan[channel].modem_type = MODEM_8PSK;
p_audio_config->achan[channel].mark_freq = 0;
p_audio_config->achan[channel].space_freq = 0;
}
else {
p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
p_audio_config->achan[channel].mark_freq = 0;
p_audio_config->achan[channel].space_freq = 0;
}
/* Get mark frequency. */
t = split(NULL,0);
if (t == NULL) {
/* all done. */
continue;
}
if (alldigits(t)) {
/* old style */
n = atoi(t);
/* Originally the upper limit was 3000. */
/* Version 1.0 increased to 5000 because someone */
/* wanted to use 2400/4800 Hz AFSK. */
/* Of course the MIC and SPKR connections won't */
/* have enough bandwidth so radios must be modified. */
if (n >= 300 && n <= 5000) {
p_audio_config->achan[channel].mark_freq = n;
}
else {
p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n",
line, p_audio_config->achan[channel].mark_freq);
}
/* Get space frequency */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing tone frequency for space.\n", line);
continue;
}
n = atoi(t);
if (n >= 300 && n <= 5000) {
p_audio_config->achan[channel].space_freq = n;
}
else {
p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n",
line, p_audio_config->achan[channel].space_freq);
}
/* Gently guide users toward new format. */
if (p_audio_config->achan[channel].baud == 1200 &&
p_audio_config->achan[channel].mark_freq == 1200 &&
p_audio_config->achan[channel].space_freq == 2200) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 1200 baud default 1200:2200.\n", line);
}
if (p_audio_config->achan[channel].baud == 300 &&
p_audio_config->achan[channel].mark_freq == 1600 &&
p_audio_config->achan[channel].space_freq == 1800) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 300 baud default 1600:1800.\n", line);
}
/* New feature in 0.9 - Optional filter profile(s). */
t = split(NULL,0);
if (t != NULL) {
/* Look for some combination of letter(s) and + */
if (isalpha(t[0]) || t[0] == '+') {
char *pc;
/* Here we only catch something other than letters and + mixed in. */
/* Later, we check for valid letters and no more than one letter if + specified. */
for (pc = t; *pc != '\0'; pc++) {
if ( ! isalpha(*pc) && ! (*pc == '+')) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Demodulator type can only contain letters and + character.\n", line);
}
}
strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles));
t = split(NULL,0);
if (strlen(p_audio_config->achan[channel].profiles) > 1 && t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Can't combine multiple demodulator types and multiple frequencies.\n", line);
continue;
}
}
}
/* New feature in 0.9 - optional number of decoders and frequency offset between. */
if (t != NULL) {
n = atoi(t);
if (n < 1 || n > MAX_SUBCHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line);
n = 3;
}
p_audio_config->achan[channel].num_freq = n;
t = split(NULL,0);
if (t != NULL) {
n = atoi(t);
if (n < 5 || n > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable value for offset between modems. Using 50 Hz.\n", line);
n = 50;
}
p_audio_config->achan[channel].offset = n;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: New style for multiple demodulators is %d@%d\n", line,
p_audio_config->achan[channel].num_freq, p_audio_config->achan[channel].offset);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing frequency offset between modems. Using 50 Hz.\n", line);
p_audio_config->achan[channel].offset = 50;
}
}
}
else {
/* New style in version 1.2. */
while (t != NULL) {
char *s;
if ((s = strchr(t, ':')) != NULL) { /* mark:space */
p_audio_config->achan[channel].mark_freq = atoi(t);
p_audio_config->achan[channel].space_freq = atoi(s+1);
if (p_audio_config->achan[channel].mark_freq == 0 && p_audio_config->achan[channel].space_freq == 0) {
p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
}
else {
p_audio_config->achan[channel].modem_type = MODEM_AFSK;
if (p_audio_config->achan[channel].mark_freq < 300 || p_audio_config->achan[channel].mark_freq > 5000) {
p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d instead.\n",
line, p_audio_config->achan[channel].mark_freq);
}
if (p_audio_config->achan[channel].space_freq < 300 || p_audio_config->achan[channel].space_freq > 5000) {
p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable space tone frequency. Using %d instead.\n",
line, p_audio_config->achan[channel].space_freq);
}
}
}
else if ((s = strchr(t, '@')) != NULL) { /* num@offset */
p_audio_config->achan[channel].num_freq = atoi(t);
p_audio_config->achan[channel].offset = atoi(s+1);
if (p_audio_config->achan[channel].num_freq < 1 || p_audio_config->achan[channel].num_freq > MAX_SUBCHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line);
p_audio_config->achan[channel].num_freq = 3;
}
if (p_audio_config->achan[channel].offset < 5 ||
p_audio_config->achan[channel].offset > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Offset between demodulators is unreasonable. Using 50 Hz.\n", line);
p_audio_config->achan[channel].offset = 50;
}
}
else if (alllettersorpm(t)) { /* profile of letter(s) + - */
// Will be validated later.
strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles));
}
else if (*t == '/') { /* /div */
int n = atoi(t+1);
if (n >= 1 && n <= 8) {
p_audio_config->achan[channel].decimate = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Ignoring unreasonable sample rate division factor of %d.\n", line, n);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t);
}
t = split(NULL,0);
}
/* A later place catches disallowed combination of + and @. */
/* A later place sets /n for 300 baud if not specified by user. */
//dw_printf ("debug: div = %d\n", p_audio_config->achan[channel].decimate);
}
}
/*
* DTMF - Enable DTMF decoder.
*
* Future possibilities:
* Option to determine if it goes to APRStt gateway and/or application.
* Disable normal demodulator to reduce CPU requirements.
*/
else if (strcasecmp(t, "DTMF") == 0) {
p_audio_config->achan[channel].dtmf_decode = DTMF_DECODE_ON;
}
/*
* FIX_BITS n [ APRS | AX25 | NONE ] [ PASSALL ]
*
* - Attempt to fix frames with bad FCS.
*/
else if (strcasecmp(t, "FIX_BITS") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for FIX_BITS command.\n", line);
continue;
}
n = atoi(t);
if (n >= RETRY_NONE && n < RETRY_MAX) { // MAX is actually last valid +1
p_audio_config->achan[channel].fix_bits = (retry_t)n;
}
else {
p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid value %d for FIX_BITS. Using default of %d.\n",
line, n, p_audio_config->achan[channel].fix_bits);
}
if (p_audio_config->achan[channel].fix_bits > RETRY_INVERT_SINGLE) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Line %d: Using a FIX_BITS value greater than %d is not recommended for normal operation.\n",
line, RETRY_INVERT_SINGLE);
}
if (p_audio_config->achan[channel].fix_bits >= RETRY_INVERT_TWO_SEP) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Line %d: Using a FIX_BITS value of %d will waste a lot of CPU power and produce wrong results.\n",
line, RETRY_INVERT_TWO_SEP);
}
t = split(NULL,0);
while (t != NULL) {
// If more than one sanity test, we silently take the last one.
if (strcasecmp(t, "APRS") == 0) {
p_audio_config->achan[channel].sanity_test = SANITY_APRS;
}
else if (strcasecmp(t, "AX25") == 0 || strcasecmp(t, "AX.25") == 0) {
p_audio_config->achan[channel].sanity_test = SANITY_AX25;
}
else if (strcasecmp(t, "NONE") == 0) {
p_audio_config->achan[channel].sanity_test = SANITY_NONE;
}
else if (strcasecmp(t, "PASSALL") == 0) {
p_audio_config->achan[channel].passall = 1;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: There is an old saying, \"Be careful what you ask for because you might get it.\"\n", line);
dw_printf ("The PASSALL option means allow all frames even when they are invalid.\n");
dw_printf ("You are asking to receive random trash and you WILL get your wish.\n");
dw_printf ("Don't complain when you see all sorts of random garbage. That's what you asked for.\n");
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid option '%s' for FIX_BITS.\n", line, t);
}
t = split(NULL,0);
}
}
/*
* PTT - Push To Talk signal line.
* DCD - Data Carrier Detect indicator.
* CON - Connected to another station indicator.
*
* xxx serial-port [-]rts-or-dtr [ [-]rts-or-dtr ]
* xxx GPIO [-]gpio-num
* xxx LPT [-]bit-num
* PTT RIG model port
* PTT RIG AUTO port
*
* When model is 2, port would host:port like 127.0.0.1:4532
* Otherwise, port would be a serial port like /dev/ttyS0
*
*
* Applies to most recent CHANNEL command.
*/
else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0 || strcasecmp(t, "CON") == 0) {
int ot;
char otname[8];
if (strcasecmp(t, "PTT") == 0) {
ot = OCTYPE_PTT;
strlcpy (otname, "PTT", sizeof(otname));
}
else if (strcasecmp(t, "DCD") == 0) {
ot = OCTYPE_DCD;
strlcpy (otname, "DCD", sizeof(otname));
}
else {
ot = OCTYPE_CON;
strlcpy (otname, "CON", sizeof(otname));
}
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",
line, otname);
continue;
}
if (strcasecmp(t, "GPIO") == 0) {
/* GPIO case, Linux only. */
#if __WIN32__
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, otname);
#else
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, otname);
continue;
}
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 {
p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t);
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO;
#endif
}
else if (strcasecmp(t, "LPT") == 0) {
/* Parallel printer case, x86 Linux only. */
#if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing LPT bit number for %s.\n", line, otname);
continue;
}
if (*t == '-') {
p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t+1);
p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
}
else {
p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t);
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_LPT;
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s with LPT is only available on x86 Linux.\n", line, otname);
#endif
}
else if (strcasecmp(t, "RIG") == 0) {
#ifdef USE_HAMLIB
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing model number for hamlib.\n", line);
continue;
}
if (strcasecmp(t, "AUTO") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_model = -1;
}
else {
int n = atoi(t);
if (n < 1 || n > 9999) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Unreasonable model number %d for hamlib.\n", line, n);
continue;
}
p_audio_config->achan[channel].octrl[ot].ptt_model = n;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing port for hamlib.\n", line);
continue;
}
strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
t = split(NULL,0);
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s was not expected after model & port for hamlib.\n", line, t);
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_HAMLIB;
#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);
#endif
}
else {
/* serial port case. */
strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n",
line, otname);
continue;
}
if (strcasecmp(t, "rts") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS;
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
}
else if (strcasecmp(t, "dtr") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR;
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
}
else if (strcasecmp(t, "-rts") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS;
p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
}
else if (strcasecmp(t, "-dtr") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR;
p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Expected RTS or DTR after %s device name.\n",
line, otname);
continue;
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_SERIAL;
/* In version 1.2, we allow a second one for same serial port. */
/* Some interfaces want the two control lines driven with opposite polarity. */
/* e.g. PTT COM1 RTS -DTR */
t = split(NULL,0);
if (t != NULL) {
if (strcasecmp(t, "rts") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS;
p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
}
else if (strcasecmp(t, "dtr") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR;
p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0;
}
else if (strcasecmp(t, "-rts") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS;
p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1;
}
else if (strcasecmp(t, "-dtr") == 0) {
p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR;
p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Expected RTS or DTR after first RTS or DTR.\n",
line);
continue;
}
/* Would not make sense to specify the same one twice. */
if (p_audio_config->achan[channel].octrl[ot].ptt_line == p_audio_config->achan[channel].octrl[ot].ptt_line2) {
dw_printf ("Config file line %d: Doesn't make sense to specify the some control line twice.\n",
line);
}
} /* end of second serial port control line. */
} /* end of serial port case. */
} /* end of PTT, DCD, CON */
/*
* INPUTS
*
* TXINH - TX holdoff input
*
* xxx GPIO [-]gpio-num (only type supported yet)
*/
else if (strcasecmp(t, "TXINH") == 0) {
char itname[8];
strlcpy (itname, "TXINH", sizeof(itname));
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing input type name for %s command.\n", line, itname);
continue;
}
if (strcasecmp(t, "GPIO") == 0) {
#if __WIN32__
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, itname);
#else
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, itname);
continue;
}
if (*t == '-') {
p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t+1);
p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 1;
}
else {
p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t);
p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 0;
}
p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].method = PTT_METHOD_GPIO;
#endif
}
}
/*
* DWAIT - Extra delay for receiver squelch.
*/
else if (strcasecmp(t, "DWAIT") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing delay time for DWAIT command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= 255) {
p_audio_config->achan[channel].dwait = n;
}
else {
p_audio_config->achan[channel].dwait = DEFAULT_DWAIT;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid delay time for DWAIT. Using %d.\n",
line, p_audio_config->achan[channel].dwait);
}
}
/*
* SLOTTIME - For non-digipeat transmit delay timing.
*/
else if (strcasecmp(t, "SLOTTIME") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing delay time for SLOTTIME command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= 255) {
p_audio_config->achan[channel].slottime = n;
}
else {
p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n",
line, p_audio_config->achan[channel].slottime);
}
}
/*
* PERSIST - For non-digipeat transmit delay timing.
*/
else if (strcasecmp(t, "PERSIST") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing probability for PERSIST command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= 255) {
p_audio_config->achan[channel].persist = n;
}
else {
p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n",
line, p_audio_config->achan[channel].persist);
}
}
/*
* TXDELAY - For transmit delay timing.
*/
else if (strcasecmp(t, "TXDELAY") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing time for TXDELAY command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= 255) {
p_audio_config->achan[channel].txdelay = n;
}
else {
p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid time for transmit delay. Using %d.\n",
line, p_audio_config->achan[channel].txdelay);
}
}
/*
* TXTAIL - For transmit timing.
*/
else if (strcasecmp(t, "TXTAIL") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing time for TXTAIL command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= 255) {
p_audio_config->achan[channel].txtail = n;
}
else {
p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n",
line, p_audio_config->achan[channel].txtail);
}
}
/*
* SPEECH script
*
* Specify script for text-to-speech function.
*/
else if (strcasecmp(t, "SPEECH") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing script for Text-to-Speech function.\n", line);
continue;
}
/* See if we can run it. */
if (xmit_speak_it(t, -1, " ") == 0) {
if (strlcpy (p_audio_config->tts_script, t, sizeof(p_audio_config->tts_script)) >= sizeof(p_audio_config->tts_script)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Script for text-to-speech function is too long.\n", line);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Error trying to run Text-to-Speech function.\n", line);
continue;
}
}
/*
* ==================== APRS Digipeater parameters ====================
*/
/*
* DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE ]
*/
else if (strcasecmp(t, "DIGIPEAT") == 0 || strcasecmp(t, "DIGIPEATER") == 0) {
int from_chan, to_chan;
int e;
char message[100];
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
from_chan = atoi(t);
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
to_chan = atoi(t);
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing alias pattern on line %d.\n", line);
continue;
}
e = regcomp (&(p_digi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
if (e != 0) {
regerror (e, &(p_digi_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",
line, message);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing wide pattern on line %d.\n", line);
continue;
}
e = regcomp (&(p_digi_config->wide[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB);
if (e != 0) {
regerror (e, &(p_digi_config->wide[from_chan][to_chan]), message, sizeof(message));
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Invalid wide matching pattern on line %d:\n%s\n",
line, message);
continue;
}
p_digi_config->enabled[from_chan][to_chan] = 1;
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF;
t = split(NULL,0);
if (t != NULL) {
if (strcasecmp(t, "OFF") == 0) {
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF;
t = split(NULL,0);
}
else if (strcasecmp(t, "DROP") == 0) {
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP;
t = split(NULL,0);
}
else if (strcasecmp(t, "MARK") == 0) {
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK;
t = split(NULL,0);
}
else if (strcasecmp(t, "TRACE") == 0) {
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE;
t = split(NULL,0);
}
}
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t);
}
}
/*
* DEDUPE - Time to suppress digipeating of duplicate packets.
*/
else if (strcasecmp(t, "DEDUPE") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing time for DEDUPE command.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n < 600) {
p_digi_config->dedupe_time = n;
}
else {
p_digi_config->dedupe_time = DEFAULT_DEDUPE;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable value for dedupe time. Using %d.\n",
line, p_digi_config->dedupe_time);
}
}
/*
* REGEN - Signal regeneration.
*/
else if (strcasecmp(t, "regen") == 0) {
int from_chan, to_chan;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
from_chan = atoi(t);
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
to_chan = atoi(t);
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
p_digi_config->regen[from_chan][to_chan] = 1;
}
/*
* ==================== Connected Digipeater parameters ====================
*/
/*
* CDIGIPEAT from-chan to-chan [ alias-pattern ]
*/
else if (strcasecmp(t, "CDIGIPEAT") == 0 || strcasecmp(t, "CDIGIPEATER") == 0) {
int from_chan, to_chan;
int e;
char message[100];
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
from_chan = atoi(t);
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
to_chan = atoi(t);
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
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) {
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",
line, message);
continue;
}
t = split(NULL,0);
}
p_cdigi_config->enabled[from_chan][to_chan] = 1;
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t);
}
}
/*
* ==================== Packet Filtering for APRS digipeater or IGate ====================
*/
/*
* FILTER from-chan to-chan filter_specification_expression
* FILTER from-chan IG filter_specification_expression
* FILTER IG to-chan filter_specification_expression
*/
else if (strcasecmp(t, "FILTER") == 0) {
int from_chan, to_chan;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
if (*t == 'i' || *t == 'I') {
from_chan = MAX_CHANS;
}
else {
from_chan = isdigit(*t) ? atoi(t) : -999;
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d or \"IG\" on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
if (*t == 'i' || *t == 'I') {
to_chan = MAX_CHANS;
}
else {
to_chan = isdigit(*t) ? atoi(t) : -999;
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d or \"IG\" on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
}
t = split(NULL,1); /* Take rest of line including spaces. */
if (t == NULL) {
t = " "; /* Empty means permit nothing. */
}
if (p_digi_config->filter_str[from_chan][to_chan] != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Replacing previous filter for same from/to pair:\n %s\n",
line, p_digi_config->filter_str[from_chan][to_chan]);
free (p_digi_config->filter_str[from_chan][to_chan]);
p_digi_config->filter_str[from_chan][to_chan] = NULL;
}
p_digi_config->filter_str[from_chan][to_chan] = strdup(t);
//TODO: Do a test run to see errors now instead of waiting.
}
/*
* ==================== Packet Filtering for connected digipeater ====================
*/
/*
* CFILTER from-chan to-chan filter_specification_expression
*/
else if (strcasecmp(t, "CFILTER") == 0) {
int from_chan, to_chan;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing FROM-channel on line %d.\n", line);
continue;
}
from_chan = isdigit(*t) ? atoi(t) : -999;
if (from_chan < 0 || from_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[from_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing TO-channel on line %d.\n", line);
continue;
}
to_chan = isdigit(*t) ? atoi(t) : -999;
if (to_chan < 0 || to_chan >= MAX_CHANS) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[to_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
t = split(NULL,1); /* Take rest of line including spaces. */
if (t == NULL) {
t = " "; /* Empty means permit nothing. */
}
p_cdigi_config->filter_str[from_chan][to_chan] = strdup(t);
//TODO1.2: Do a test run to see errors now instead of waiting.
}
/*
* ==================== APRStt gateway ====================
*/
/*
* TTCORRAL - How to handle unknown positions
*
* TTCORRAL latitude longitude offset-or-ambiguity
*/
else if (strcasecmp(t, "TTCORRAL") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing latitude for TTCORRAL command.\n", line);
continue;
}
p_tt_config->corral_lat = parse_ll(t,LAT,line);
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line);
continue;
}
p_tt_config->corral_lon = parse_ll(t,LON,line);
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line);
continue;
}
p_tt_config->corral_offset = parse_ll(t,LAT,line);
if (p_tt_config->corral_offset == 1 ||
p_tt_config->corral_offset == 2 ||
p_tt_config->corral_offset == 3) {
p_tt_config->corral_ambiguity = p_tt_config->corral_offset;
p_tt_config->corral_offset = 0;
}
//dw_printf ("DEBUG: corral %f %f %f %d\n", p_tt_config->corral_lat,
// p_tt_config->corral_lon, p_tt_config->corral_offset, p_tt_config->corral_ambiguity);
}
/*
* TTPOINT - Define a point represented by touch tone sequence.
*
* TTPOINT pattern latitude longitude
*/
else if (strcasecmp(t, "TTPOINT") == 0) {
struct ttloc_s *tl;
int j;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
// Should make this a function/macro instead of repeating code.
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_POINT;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
tl->point.lat = 0;
tl->point.lon = 0;
/* Pattern: B and digits */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line);
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTPOINT pattern must begin with upper case 'B'.\n", line);
}
for (j=1; j<(int)(strlen(t)); j++) {
if ( ! isdigit(t[j])) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTPOINT pattern must be B and digits only.\n", line);
}
}
/* Latitude */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line);
continue;
}
tl->point.lat = parse_ll(t,LAT,line);
/* Longitude */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing longitude for TTPOINT command.\n", line);
continue;
}
tl->point.lon = parse_ll(t,LON,line);
/* temp debugging */
//for (j=0; jttloc_len; j++) {
// dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
// p_tt_config->ttloc_ptr[j].pattern);
//}
}
/*
* TTVECTOR - Touch tone location with bearing and distance.
*
* TTVECTOR pattern latitude longitude scale unit
*/
else if (strcasecmp(t, "TTVECTOR") == 0) {
struct ttloc_s *tl;
int j;
double scale;
double meters;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_VECTOR;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
tl->vector.lat = 0;
tl->vector.lon = 0;
tl->vector.scale = 1;
/* Pattern: B5bbbd... */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line);
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTVECTOR pattern must begin with upper case 'B'.\n", line);
}
if (strncmp(t+1, "5bbb", 4) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTVECTOR pattern would normally contain \"5bbb\".\n", line);
}
for (j=1; j<(int)(strlen(t)); j++) {
if ( ! isdigit(t[j]) && t[j] != 'b' && t[j] != 'd') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTVECTOR pattern must contain only B, digits, b, and d.\n", line);
}
}
/* Latitude */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line);
continue;
}
tl->vector.lat = parse_ll(t,LAT,line);
/* Longitude */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing longitude for TTVECTOR command.\n", line);
continue;
}
tl->vector.lon = parse_ll(t,LON,line);
/* Longitude */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing scale for TTVECTOR command.\n", line);
continue;
}
scale = atof(t);
/* Unit. */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing unit for TTVECTOR command.\n", line);
continue;
}
meters = 0;
for (j=0; jvector.scale = scale * meters;
//dw_printf ("ttvector: %f meters\n", tl->vector.scale);
/* temp debugging */
//for (j=0; jttloc_len; j++) {
// dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
// p_tt_config->ttloc_ptr[j].pattern);
//}
}
/*
* TTGRID - Define a grid for touch tone locations.
*
* TTGRID pattern min-latitude min-longitude max-latitude max-longitude
*/
else if (strcasecmp(t, "TTGRID") == 0) {
struct ttloc_s *tl;
int j;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_GRID;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
tl->grid.lat0 = 0;
tl->grid.lon0 = 0;
tl->grid.lat9 = 0;
tl->grid.lon9 = 0;
/* Pattern: B [digit] x... y... */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line);
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTGRID pattern must begin with upper case 'B'.\n", line);
}
for (j=1; j<(int)(strlen(t)); j++) {
if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTGRID pattern must be B, optional digit, xxx, yyy.\n", line);
}
}
/* Minimum Latitude - all zeros in received data */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
continue;
}
tl->grid.lat0 = parse_ll(t,LAT,line);
/* Minimum Longitude - all zeros in received data */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
continue;
}
tl->grid.lon0 = parse_ll(t,LON,line);
/* Maximum Latitude - all nines in received data */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
continue;
}
tl->grid.lat9 = parse_ll(t,LAT,line);
/* Maximum Longitude - all nines in received data */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
continue;
}
tl->grid.lon0 = parse_ll(t,LON,line);
/* temp debugging */
//for (j=0; jttloc_len; j++) {
// dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
// p_tt_config->ttloc_ptr[j].pattern);
//}
}
/*
* TTUTM - Specify UTM zone for touch tone locations.
*
* TTUTM pattern zone [ scale [ x-offset y-offset ] ]
*/
else if (strcasecmp(t, "TTUTM") == 0) {
struct ttloc_s *tl;
int j;
double dlat, dlon;
long lerr;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_UTM;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
tl->utm.lzone = 0;
tl->utm.scale = 1;
tl->utm.x_offset = 0;
tl->utm.y_offset = 0;
/* Pattern: B [digit] x... y... */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTUTM pattern must begin with upper case 'B'.\n", line);
p_tt_config->ttloc_len--;
continue;
}
for (j=1; j < (int)(strlen(t)); j++) {
if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTUTM pattern must be B, optional digit, xxx, yyy.\n", line);
// Bail out somehow. continue would match inner for.
}
}
/* Zone 1 - 60 and optional latitudinal letter. */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing zone for TTUTM command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
tl->utm.lzone = parse_utm_zone (t, &(tl->utm.latband), &(tl->utm.hemi));
/* Optional scale. */
t = split(NULL,0);
if (t != NULL) {
tl->utm.scale = atof(t);
/* Optional x offset. */
t = split(NULL,0);
if (t != NULL) {
tl->utm.x_offset = atof(t);
/* Optional y offset. */
t = split(NULL,0);
if (t != NULL) {
tl->utm.y_offset = atof(t);
}
}
}
/* Practice run to see if conversion might fail later with actual location. */
lerr = Convert_UTM_To_Geodetic(tl->utm.lzone, tl->utm.hemi,
tl->utm.x_offset + 5 * tl->utm.scale,
tl->utm.y_offset + 5 * tl->utm.scale,
&dlat, &dlon);
if (lerr != 0) {
char message [300];
utm_error_string (lerr, message);
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message);
p_tt_config->ttloc_len--;
continue;
}
}
/*
* TTUSNG, TTMGRS - Specify zone/square for touch tone locations.
*
* TTUSNG pattern zone_square
* TTMGRS pattern zone_square
*/
else if (strcasecmp(t, "TTUSNG") == 0 || strcasecmp(t, "TTMGRS") == 0) {
struct ttloc_s *tl;
int j;
int num_x, num_y;
double lat, lon;
long lerr;
char message[300];
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
// TODO1.2: in progress...
if (strcasecmp(t, "TTMGRS") == 0) {
tl->type = TTLOC_MGRS;
}
else {
tl->type = TTLOC_USNG;
}
strlcpy(tl->pattern, "", sizeof(tl->pattern));
strlcpy(tl->mgrs.zone, "", sizeof(tl->mgrs.zone));
/* Pattern: B [digit] x... y... */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTUSNG/TTMGRS command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTUSNG/TTMGRS pattern must begin with upper case 'B'.\n", line);
p_tt_config->ttloc_len--;
continue;
}
num_x = 0;
num_y = 0;
for (j=1; j<(int)(strlen(t)); j++) {
if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTUSNG/TTMGRS pattern must be B, optional digit, xxx, yyy.\n", line);
// Bail out somehow. continue would match inner for.
}
if (t[j] == 'x') num_x++;
if (t[j] == 'y') num_y++;
}
if (num_x < 1 || num_x > 5 || num_x != num_y) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTUSNG/TTMGRS must have 1 to 5 x and same number y.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* Zone 1 - 60 and optional latitudinal letter. */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing zone & square for TTUSNG/TTMGRS command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->mgrs.zone, t, sizeof(tl->mgrs.zone));
/* Try converting it rather do our own error checking. */
if (tl->type == TTLOC_MGRS) {
lerr = Convert_MGRS_To_Geodetic (tl->mgrs.zone, &lat, &lon);
}
else {
lerr = Convert_USNG_To_Geodetic (tl->mgrs.zone, &lat, &lon);
}
if (lerr != 0) {
mgrs_error_string (lerr, message);
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid USNG/MGRS zone & square: %s\n%s\n", line, tl->mgrs.zone, message);
p_tt_config->ttloc_len--;
continue;
}
/* Should be the end. */
t = split(NULL,0);
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unexpected stuff at end ignored: %s\n", line, t);
}
}
/*
* TTMHEAD - Define pattern to be used for Maidenhead Locator.
*
* TTMHEAD pattern [ prefix ]
*
* Pattern would be B[0-9A-D]xxxx...
* Optional prefix is 10, 6, or 4 digits.
*
* The total number of digts in both must be 4, 6, 10, or 12.
*/
else if (strcasecmp(t, "TTMHEAD") == 0) {
// TODO1.3: TTMHEAD needs testing.
struct ttloc_s *tl;
int j;
int k;
int count_x;
int count_other;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_MHEAD;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
strlcpy(tl->mhead.prefix, "", sizeof(tl->mhead.prefix));
/* Pattern: B, optional additional button, some number of xxxx... for matching */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTMHEAD command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMHEAD pattern must begin with upper case 'B'.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* Optionally one of 0-9ABCD */
if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
j = 2;
}
else {
j = 1;
}
count_x = 0;
count_other = 0;
for (k = j ; k < (int)(strlen(t)); k++) {
if (t[k] == 'x') count_x++;
else count_other++;
}
if (count_other != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMHEAD must have only lower case x to match received data.\n", line);
p_tt_config->ttloc_len--;
continue;
}
// optional prefix
t = split(NULL,0);
if (t != NULL) {
char mh[30];
strlcpy(tl->mhead.prefix, t, sizeof(tl->mhead.prefix));
if (!alldigits(t) || (strlen(t) != 4 && strlen(t) != 6 && strlen(t) != 10)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMHEAD prefix must be 4, 6, or 10 digits.\n", line);
p_tt_config->ttloc_len--;
continue;
}
if (tt_mhead_to_text(t, 0, mh, sizeof(mh)) != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMHEAD prefix not a valid DTMF sequence.\n", line);
p_tt_config->ttloc_len--;
continue;
}
}
k = strlen(tl->mhead.prefix) + count_x;
if (k != 4 && k != 6 && k != 10 && k != 12 ) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMHEAD prefix and user data must have a total of 4, 6, 10, or 12 digits.\n", line);
p_tt_config->ttloc_len--;
continue;
}
}
/*
* TTSATSQ - Define pattern to be used for Satellite square.
*
* TTSATSQ pattern
*
* Pattern would be B[0-9A-D]xxxx
*
* Must have exactly 4 x.
*/
else if (strcasecmp(t, "TTSATSQ") == 0) {
// TODO1.2: TTSATSQ To be continued...
struct ttloc_s *tl;
int j;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_SATSQ;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
tl->point.lat = 0;
tl->point.lon = 0;
/* Pattern: B, optional additional button, exactly xxxx for matching */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTSATSQ command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTSATSQ pattern must begin with upper case 'B'.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* Optionally one of 0-9ABCD */
if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
j = 2;
}
else {
j = 1;
}
if (strcmp(t+j, "xxxx") != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTSATSQ pattern must end with exactly xxxx in lower case.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* temp debugging */
//for (j=0; jttloc_len; j++) {
// dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
// p_tt_config->ttloc_ptr[j].pattern);
//}
}
/*
* TTAMBIG - Define pattern to be used for Object Location Ambiguity.
*
* TTAMBIG pattern
*
* Pattern would be B[0-9A-D]x
*
* Must have exactly one x.
*/
else if (strcasecmp(t, "TTAMBIG") == 0) {
// TODO1.3: TTAMBIG To be continued...
struct ttloc_s *tl;
int j;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_AMBIG;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
/* Pattern: B, optional additional button, exactly x for matching */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTAMBIG command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
if (t[0] != 'B') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTAMBIG pattern must begin with upper case 'B'.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* Optionally one of 0-9ABCD */
if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) {
j = 2;
}
else {
j = 1;
}
if (strcmp(t+j, "x") != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTAMBIG pattern must end with exactly one x in lower case.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* temp debugging */
//for (j=0; jttloc_len; j++) {
// dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
// p_tt_config->ttloc_ptr[j].pattern);
//}
}
/*
* TTMACRO - Define compact message format with full expansion
*
* TTMACRO pattern definition
*
* pattern can contain:
* 0-9 which must match exactly.
* In version 1.2, also allow A,B,C,D for exact match.
* x, y, z which are used for matching of variable fields.
*
* definition can contain:
* 0-9, A, B, C, D, *, #, x, y, z.
* Not sure why # was included in there.
*
* new for version 1.3 - in progress
*
* AA{objname}
* AB{symbol}
* AC{call}
*
* These provide automatic conversion from plain text to the TT encoding.
*
*/
else if (strcasecmp(t, "TTMACRO") == 0) {
struct ttloc_s *tl;
int j;
int p_count[3], d_count[3];
int tt_error = 0;
assert (p_tt_config->ttloc_size >= 2);
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
/* Allocate new space, but first, if already full, make larger. */
if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) {
p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2;
p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size);
}
p_tt_config->ttloc_len++;
assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size);
tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]);
tl->type = TTLOC_MACRO;
strlcpy(tl->pattern, "", sizeof(tl->pattern));
/* Pattern: Any combination of digits, x, y, and z. */
/* Also make note of which letters are used in pattern and defintition. */
/* Version 1.2: also allow A,B,C,D in the pattern. */
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line);
p_tt_config->ttloc_len--;
continue;
}
strlcpy (tl->pattern, t, sizeof(tl->pattern));
p_count[0] = p_count[1] = p_count[2] = 0;
for (j=0; j<(int)(strlen(t)); j++) {
if ( strchr ("0123456789ABCDxyz", t[j]) == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMACRO pattern can contain only digits, A, B, C, D, and lower case x, y, or z.\n", line);
p_tt_config->ttloc_len--;
continue;
}
/* Count how many x, y, z in the pattern. */
if (t[j] >= 'x' && t[j] <= 'z') {
p_count[t[j]-'x']++;
}
}
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Line %d: TTMACRO pattern \"%s\" p_count = %d %d %d.\n", line, t, p_count[0], p_count[1], p_count[2]);
/* Next we should find the definition. */
/* It can contain touch tone characters and lower case x, y, z for substitutions. */
t = split(NULL,1);;
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing definition for TTMACRO command.\n", line);
tl->macro.definition = ""; /* Don't die on null pointer later. */
p_tt_config->ttloc_len--;
continue;
}
/* Make a pass over the definition, looking for the xx{...} substitutions. */
/* These are done just once when reading the configuration file. */
char *pi;
char *ps;
char stemp[100]; // text inside of xx{...}
char ttemp[300]; // Converted to tone sequences.
char otemp[1000]; // Result after any substitutions.
char t2[2];
strlcpy (otemp, "", sizeof(otemp));
t2[1] = '\0';
pi = t;
while (*pi == ' ' || *pi == '\t') {
pi++;
}
for ( ; *pi != '\0'; pi++) {
if (strncmp(pi, "AC{", 3) == 0) {
// Convert to fixed length 10 digit callsign.
pi += 3;
ps = stemp;
while (*pi != '}' && *pi != '*' && *pi != '\0') {
*ps++ = *pi++;
}
if (*pi == '}') {
*ps = '\0';
if (tt_text_to_call10 (stemp, 0, ttemp) == 0) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("DEBUG Line %d: AC{%s} -> AC%s\n", line, stemp, ttemp);
strlcat (otemp, "AC", sizeof(otemp));
strlcat (otemp, ttemp, sizeof(otemp));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: AC{%s} could not be converted to tones for callsign.\n", line, stemp);
tt_error++;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: AC{... is missing matching } in TTMACRO definition.\n", line);
tt_error++;
}
}
else if (strncmp(pi, "AA{", 3) == 0) {
// Convert to object name.
pi += 3;
ps = stemp;
while (*pi != '}' && *pi != '*' && *pi != '\0') {
*ps++ = *pi++;
}
if (*pi == '}') {
*ps = '\0';
if (strlen(stemp) > 9) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Object name %s has been truncated to 9 characters.\n", line, stemp);
stemp[9] = '\0';
}
if (tt_text_to_two_key (stemp, 0, ttemp) == 0) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("DEBUG Line %d: AA{%s} -> AA%s\n", line, stemp, ttemp);
strlcat (otemp, "AA", sizeof(otemp));
strlcat (otemp, ttemp, sizeof(otemp));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: AA{%s} could not be converted to tones for object name.\n", line, stemp);
tt_error++;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: AA{... is missing matching } in TTMACRO definition.\n", line);
tt_error++;
}
}
else if (strncmp(pi, "AB{", 3) == 0) {
// Attempt conversion from description to symbol code.
pi += 3;
ps = stemp;
while (*pi != '}' && *pi != '*' && *pi != '\0') {
*ps++ = *pi++;
}
if (*pi == '}') {
char symtab;
char symbol;
*ps = '\0';
// First try to find something matching the description.
if (symbols_code_from_description (' ', stemp, &symtab, &symbol) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Couldn't convert \"%s\" to APRS symbol code. Using default.\n", line, stemp);
symtab = '\\'; // Alternate
symbol = 'A'; // Box
}
// Convert symtab(overlay) & symbol to tone sequence.
symbols_to_tones (symtab, symbol, ttemp, sizeof(ttemp));
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("DEBUG config file Line %d: AB{%s} -> %s\n", line, stemp, ttemp);
strlcat (otemp, ttemp, sizeof(otemp));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: AB{... is missing matching } in TTMACRO definition.\n", line);
tt_error++;
}
}
else if (strncmp(pi, "CA{", 3) == 0) {
// Convert to enhanced comment that can contain any ASCII character.
pi += 3;
ps = stemp;
while (*pi != '}' && *pi != '*' && *pi != '\0') {
*ps++ = *pi++;
}
if (*pi == '}') {
*ps = '\0';
if (tt_text_to_ascii2d (stemp, 0, ttemp) == 0) {
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("DEBUG Line %d: CA{%s} -> CA%s\n", line, stemp, ttemp);
strlcat (otemp, "CA", sizeof(otemp));
strlcat (otemp, ttemp, sizeof(otemp));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: CA{%s} could not be converted to tones for enhanced comment.\n", line, stemp);
tt_error++;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: CA{... is missing matching } in TTMACRO definition.\n", line);
tt_error++;
}
}
else if (strchr("0123456789ABCD*#xyz", *pi) != NULL) {
t2[0] = *pi;
strlcat (otemp, t2, sizeof(otemp));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line);
tt_error++;
}
}
/* Make sure that number of x, y, z, in pattern and definition match. */
d_count[0] = d_count[1] = d_count[2] = 0;
for (j=0; j<(int)(strlen(otemp)); j++) {
if (otemp[j] >= 'x' && otemp[j] <= 'z') {
d_count[otemp[j]-'x']++;
}
}
/* A little validity checking. */
for (j=0; j<3; j++) {
if (p_count[j] > 0 && d_count[j] == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: '%c' is in TTMACRO pattern but is not used in definition.\n", line, 'x'+j);
}
if (d_count[j] > 0 && p_count[j] == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: '%c' is referenced in TTMACRO definition but does not appear in the pattern.\n", line, 'x'+j);
}
}
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("DEBUG Config Line %d: %s -> %s\n", line, t, otemp);
if (tt_error == 0) {
tl->macro.definition = strdup(otemp);
}
else {
p_tt_config->ttloc_len--;
}
}
/*
* TTOBJ - TT Object Report options.
*
* TTOBJ recv-chan where-to [ via-path ]
*
* whereto is any combination of transmit channel, APP, IG.
*/
else if (strcasecmp(t, "TTOBJ") == 0) {
int r, x = -1;
int app = 0;
int ig = 0;
char *p;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing DTMF receive channel for TTOBJ command.\n", line);
continue;
}
r = atoi(t);
if (r < 0 || r > MAX_CHANS-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: DTMF receive channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
if ( ! p_audio_config->achan[r].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n",
line, r);
continue;
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line);
continue;
}
// Can have any combination of number, APP, IG.
// Would it be easier with strtok?
for (p = t; *p != '\0'; p++) {
if (isdigit(*p)) {
x = *p - '0';
if (x < 0 || x > MAX_CHANS-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line);
x = -1;
}
else if ( ! p_audio_config->achan[x].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x);
x = -1;
}
}
else if (*p == 'a' || *p == 'A') {
app = 1;
}
else if (*p == 'i' || *p == 'I') {
ig = 1;
}
else if (strchr("pPgG,", *p) != NULL) {
;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Expected comma separated list with some combination of transmit channel, APP, and IG.\n", line);
}
}
// This enables the DTMF decoder on the specified channel.
// Additional channels can be enabled with the DTMF command.
// Note that DTMF command does not enable the APRStt gateway.
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Debug TTOBJ r=%d, x=%d, app=%d, ig=%d\n", r, x, app, ig);
p_audio_config->achan[r].dtmf_decode = DTMF_DECODE_ON;
p_tt_config->gateway_enabled = 1;
p_tt_config->obj_recv_chan = r;
p_tt_config->obj_xmit_chan = x;
p_tt_config->obj_send_to_app = app;
p_tt_config->obj_send_to_ig = ig;
t = split(NULL,0);
if (t != NULL) {
if (check_via_path(t) >= 0) {
strlcpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via));
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: invalid via path.\n", line);
}
}
}
/*
* TTERR - TT responses for success or errors.
*
* TTERR msg_id method text...
*/
else if (strcasecmp(t, "TTERR") == 0) {
int n, msg_num;
char *p;
char method[AX25_MAX_ADDR_LEN];
int ssid;
int heard;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing message identifier for TTERR command.\n", line);
continue;
}
msg_num = -1;
for (n=0; n= 0 && msg_num < TT_ERROR_MAXP1);
strlcpy (p_tt_config->response[msg_num].method, method, sizeof(p_tt_config->response[msg_num].method));
// TODO1.3: Need SSID too!
strlcpy (p_tt_config->response[msg_num].mtext, t, sizeof(p_tt_config->response[msg_num].mtext));
p_tt_config->response[msg_num].mtext[TT_MTEXT_LEN-1] = '\0';
}
/*
* TTSTATUS - TT custom status messages.
*
* TTSTATUS status_id text...
*/
else if (strcasecmp(t, "TTSTATUS") == 0) {
int status_num;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing status number for TTSTATUS command.\n", line);
continue;
}
status_num = atoi(t);
if (status_num < 1 || status_num > 9) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Status number for TTSTATUS command must be in range of 1 to 9.\n", line);
continue;
}
t = split(NULL,1);;
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing status text for TTSTATUS command.\n", line);
continue;
}
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Line %d: TTSTATUS debug %d \"%s\"\n", line, status_num, t);
while (*t == ' ' || *t == '\t') t++; // remove leading white space.
strlcpy (p_tt_config->status[status_num], t, sizeof(p_tt_config->status[status_num]));
}
/*
* TTCMD - Command to run when valid sequence is received.
* Any text generated will be sent back to user.
*
* TTCMD ...
*/
else if (strcasecmp(t, "TTCMD") == 0) {
t = split(NULL,1);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing command for TTCMD command.\n", line);
continue;
}
strlcpy (p_tt_config->ttcmd, t, sizeof(p_tt_config->ttcmd));
}
/*
* ==================== Internet gateway ====================
*/
/*
* IGSERVER - Name of IGate server.
*
* IGSERVER hostname [ port ] -- original implementation.
*
* IGSERVER hostname:port -- more in line with usual conventions.
*/
else if (strcasecmp(t, "IGSERVER") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line);
continue;
}
strlcpy (p_igate_config->t2_server_name, t, sizeof(p_igate_config->t2_server_name));
/* If there is a : in the name, split it out as the port number. */
t = strchr (p_igate_config->t2_server_name, ':');
if (t != NULL) {
*t = '\0';
t++;
int n = atoi(t);
if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) {
p_igate_config->t2_server_port = n;
}
else {
p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n",
line, p_igate_config->t2_server_port);
}
}
/* Alternatively, the port number could be separated by white space. */
t = split(NULL,0);
if (t != NULL) {
int n = atoi(t);
if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) {
p_igate_config->t2_server_port = n;
}
else {
p_igate_config->t2_server_port = DEFAULT_IGATE_PORT;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n",
line, p_igate_config->t2_server_port);
}
}
//dw_printf ("DEBUG server=%s port=%d\n", p_igate_config->t2_server_name, p_igate_config->t2_server_port);
//exit (0);
}
/*
* IGLOGIN - Login callsign and passcode for IGate server
*
* IGLOGIN callsign passcode
*/
else if (strcasecmp(t, "IGLOGIN") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing login callsign for IGLOGIN command.\n", line);
continue;
}
// TODO: Wouldn't hurt to do validity checking of format.
strlcpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login));
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line);
continue;
}
strlcpy (p_igate_config->t2_passcode, t, sizeof(p_igate_config->t2_passcode));
}
/*
* IGTXVIA - Transmit channel and VIA path for messages from IGate server
*
* IGTXVIA channel [ path ]
*/
else if (strcasecmp(t, "IGTXVIA") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing transmit channel for IGTXVIA command.\n", line);
continue;
}
n = atoi(t);
if (n < 0 || n > MAX_CHANS-1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n",
MAX_CHANS-1, line);
continue;
}
p_igate_config->tx_chan = n;
t = split(NULL,0);
if (t != NULL) {
#if 1 // proper checking
n = check_via_path(t);
if (n >= 0) {
p_igate_config->max_digi_hops = n;
p_igate_config->tx_via[0] = ',';
strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: invalid via path.\n", line);
}
#else // previously
char *p;
p_igate_config->tx_via[0] = ',';
strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1);
for (p = p_igate_config->tx_via; *p != '\0'; p++) {
if (islower(*p)) {
*p = toupper(*p); /* silently force upper case. */
}
}
#endif
}
}
/*
* IGFILTER - IGate Server side filters.
*
* IGFILTER filter-spec ...
*/
else if (strcasecmp(t, "IGFILTER") == 0) {
t = split(NULL,1); /* Take rest of line as one string. */
if (t != NULL && strlen(t) > 0) {
p_igate_config->t2_filter = strdup (t);
}
}
/*
* IGTXLIMIT - Limit transmissions during 1 and 5 minute intervals.
*
* IGTXLIMIT one-minute-limit five-minute-limit
*/
else if (strcasecmp(t, "IGTXLIMIT") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line);
continue;
}
n = atoi(t);
if (n < 1) {
p_igate_config->tx_limit_1 = 1;
}
else if (n <= IGATE_TX_LIMIT_1_MAX) {
p_igate_config->tx_limit_1 = n;
}
else {
p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_MAX;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: One minute transmit limit has been reduced to %d.\n",
line, p_igate_config->tx_limit_1);
dw_printf ("You won't make friends by setting a limit this high.\n");
}
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing five minute limit for IGTXLIMIT command.\n", line);
continue;
}
n = atoi(t);
if (n < 1) {
p_igate_config->tx_limit_5 = 1;
}
else if (n <= IGATE_TX_LIMIT_5_MAX) {
p_igate_config->tx_limit_5 = n;
}
else {
p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_MAX;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Five minute transmit limit has been reduced to %d.\n",
line, p_igate_config->tx_limit_5);
dw_printf ("You won't make friends by setting a limit this high.\n");
}
}
/*
* IGMSP - Number of times to send position of message sender.
*
* IGMSP n
*/
else if (strcasecmp(t, "IGMSP") == 0) {
t = split(NULL,0);
if (t != NULL) {
int n = atoi(t);
if (n >= 0 && n <= 10) {
p_igate_config->igmsp = n;
}
else {
p_igate_config->satgate_delay = 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;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing number of times for message sender position. Using default 1.\n", line);
}
}
/*
* SATGATE - Special SATgate mode to delay packets heard directly.
*
* SATGATE [ n ]
*/
else if (strcasecmp(t, "SATGATE") == 0) {
t = split(NULL,0);
if (t != NULL) {
int n = atoi(t);
if (n >= MIN_SATGATE_DELAY && n <= MAX_SATGATE_DELAY) {
p_igate_config->satgate_delay = n;
}
else {
p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable SATgate delay. Using default.\n", line);
}
}
else {
p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY;
}
}
/*
* ==================== All the left overs ====================
*/
/*
* AGWPORT - Port number for "AGW TCPIP Socket Interface"
*
* In version 1.2 we allow 0 to disable listening.
*/
else if (strcasecmp(t, "AGWPORT") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing port number for AGWPORT command.\n", line);
continue;
}
n = atoi(t);
if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
p_misc_config->agwpe_port = n;
}
else {
p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n",
line, p_misc_config->agwpe_port);
}
}
/*
* KISSPORT - Port number for KISS over IP.
*/
else if (strcasecmp(t, "KISSPORT") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line);
continue;
}
n = atoi(t);
if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
p_misc_config->kiss_port = n;
}
else {
p_misc_config->kiss_port = DEFAULT_KISS_PORT;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid port number for KISS TCPIP Socket Interface. Using %d.\n",
line, p_misc_config->kiss_port);
}
}
/*
* NULLMODEM - Device name for our end of the virtual "null modem"
*/
else if (strcasecmp(t, "nullmodem") == 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);
continue;
}
else {
strlcpy (p_misc_config->nullmodem, t, sizeof(p_misc_config->nullmodem));
}
}
/*
* GPSNMEA - Device name for reading from GPS receiver.
*/
else if (strcasecmp(t, "gpsnmea") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Missing serial port name for GPS receiver.\n", line);
continue;
}
else {
strlcpy (p_misc_config->gpsnmea_port, t, sizeof(p_misc_config->gpsnmea_port));
}
}
/*
* GPSD - Use GPSD server.
*
* GPSD [ host [ port ] ]
*/
else if (strcasecmp(t, "gpsd") == 0) {
#if __WIN32__
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: The GPSD interface is not available for Windows.\n", line);
continue;
#elif ENABLE_GPSD
strlcpy (p_misc_config->gpsd_host, "localhost", sizeof(p_misc_config->gpsd_host));
p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT);
t = split(NULL,0);
if (t != NULL) {
strlcpy (p_misc_config->gpsd_host, t, sizeof(p_misc_config->gpsd_host));
t = split(NULL,0);
if (t != NULL) {
int n = atoi(t);
if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
p_misc_config->gpsd_port = n;
}
else {
p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT);
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid port number for GPSD Socket Interface. Using default of %d.\n",
line, p_misc_config->gpsd_port);
}
}
}
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: The GPSD interface has not been enabled.\n", line);
dw_printf ("Install gpsd and libgps-dev packages then rebuild direwolf.\n");
continue;
#endif
}
/*
* WAYPOINT - Generate WPL NMEA sentences for display on map.
*
* WAYPOINT serial-device [ formats ]
*
*/
else if (strcasecmp(t, "waypoint") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing device name for WAYPOINT on line %d.\n", line);
continue;
}
else {
strlcpy (p_misc_config->waypoint_port, t, sizeof(p_misc_config->waypoint_port));
}
t = split(NULL,1);
if (t != NULL) {
for ( ; *t != '\0' ; t++ ) {
switch (toupper(*t)) {
case 'N':
p_misc_config->waypoint_formats |= WPT_FORMAT_NMEA_GENERIC;
break;
case 'G':
p_misc_config->waypoint_formats |= WPT_FORMAT_GARMIN;
break;
case 'M':
p_misc_config->waypoint_formats |= WPT_FORMAT_MAGELLAN;
break;
case 'K':
p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD;
break;
case ' ':
case ',':
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Invalid output format '%c' for WAYPOINT on line %d.\n", *t, line);
break;
}
}
}
}
/*
* LOGDIR - Directory name for storing log files. Use "." for current working directory.
*/
else if (strcasecmp(t, "logdir") == 0) {
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing directory name for LOGDIR on line %d.\n", line);
continue;
}
else {
strlcpy (p_misc_config->logdir, t, sizeof(p_misc_config->logdir));
}
t = split(NULL,0);
if (t != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: LOGDIR on line %d should have directory path and nothing more.\n", line);
}
}
/*
* BEACON channel delay every message
*
* Original handcrafted style. Removed in version 1.0.
*/
else if (strcasecmp(t, "BEACON") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Old style 'BEACON' has been replaced with new commands.\n", line);
dw_printf ("Use PBEACON, OBEACON, or CBEACON instead.\n");
}
/*
* PBEACON keyword=value ...
* OBEACON keyword=value ...
* TBEACON keyword=value ...
* CBEACON keyword=value ...
* IBEACON keyword=value ...
*
* New style with keywords for options.
*/
else if (strcasecmp(t, "PBEACON") == 0 ||
strcasecmp(t, "OBEACON") == 0 ||
strcasecmp(t, "TBEACON") == 0 ||
strcasecmp(t, "CBEACON") == 0 ||
strcasecmp(t, "IBEACON") == 0) {
if (p_misc_config->num_beacons < MAX_BEACONS) {
memset (&(p_misc_config->beacon[p_misc_config->num_beacons]), 0, sizeof(struct beacon_s));
if (strcasecmp(t, "PBEACON") == 0) {
p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_POSITION;
}
else if (strcasecmp(t, "OBEACON") == 0) {
p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_OBJECT;
}
else if (strcasecmp(t, "TBEACON") == 0) {
p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_TRACKER;
}
else if (strcasecmp(t, "IBEACON") == 0) {
p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_IGATE;
}
else {
p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_CUSTOM;
}
/* Save line number because some errors will be reported later. */
p_misc_config->beacon[p_misc_config->num_beacons].lineno = line;
if (beacon_options(t + strlen("xBEACON") + 1, &(p_misc_config->beacon[p_misc_config->num_beacons]), line, p_audio_config)) {
p_misc_config->num_beacons++;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Maximum number of beacons exceeded on line %d.\n", line);
continue;
}
}
/*
* SMARTBEACONING [ fast_speed fast_rate slow_speed slow_rate turn_time turn_angle turn_slope ]
*
* Parameters must be all or nothing.
*/
else if (strcasecmp(t, "SMARTBEACON") == 0 ||
strcasecmp(t, "SMARTBEACONING") == 0) {
int n;
#define SB_NUM(name,sbvar,minn,maxx,unit) \
t = split(NULL,0); \
if (t == NULL) { \
if (strcmp(name, "fast speed") == 0) { \
p_misc_config->sb_configured = 1; \
continue; \
} \
text_color_set(DW_COLOR_ERROR); \
dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \
continue; \
} \
n = atoi(t); \
if (n >= minn && n <= maxx) { \
p_misc_config->sbvar = n; \
} \
else { \
text_color_set(DW_COLOR_ERROR); \
dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n", \
line, name, p_misc_config->sbvar, unit); \
}
#define SB_TIME(name,sbvar,minn,maxx,unit) \
t = split(NULL,0); \
if (t == NULL) { \
text_color_set(DW_COLOR_ERROR); \
dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \
continue; \
} \
n = parse_interval(t,line); \
if (n >= minn && n <= maxx) { \
p_misc_config->sbvar = n; \
} \
else { \
text_color_set(DW_COLOR_ERROR); \
dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n", \
line, name, p_misc_config->sbvar, unit); \
}
SB_NUM ("fast speed", sb_fast_speed, 2, 90, "MPH")
SB_TIME ("fast rate", sb_fast_rate, 10, 300, "seconds")
SB_NUM ("slow speed", sb_slow_speed, 1, 30, "MPH")
SB_TIME ("slow rate", sb_slow_rate, 30, 3600, "seconds")
SB_TIME ("turn time", sb_turn_time, 5, 180, "seconds")
SB_NUM ("turn angle", sb_turn_angle, 5, 90, "degrees")
SB_NUM ("turn slope", sb_turn_slope, 1, 255, "deg*mph")
/* If I was ambitious, I might allow optional */
/* unit at end for miles or km / hour. */
p_misc_config->sb_configured = 1;
}
/*
* ==================== AX.25 connected mode ====================
*/
/*
* FRACK n - Number of seconds to wait for ack to transmission.
*/
else if (strcasecmp(t, "FRACK") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for FRACK.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_T1V_FRACK_MIN && n <= AX25_T1V_FRACK_MAX) {
p_misc_config->frack = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid FRACK time. Using default %d.\n", line, p_misc_config->frack);
}
}
/*
* RETRY n - Number of times to retry before giving up.
*/
else if (strcasecmp(t, "RETRY") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for RETRY.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_N2_RETRY_MIN && n <= AX25_N2_RETRY_MAX) {
p_misc_config->retry = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid RETRY number. Using default %d.\n", line, p_misc_config->retry);
}
}
/*
* PACLEN n - Maximum number of bytes in information part.
*/
else if (strcasecmp(t, "PACLEN") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for PACLEN.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_N1_PACLEN_MIN && n <= AX25_N1_PACLEN_MAX) {
p_misc_config->paclen = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid PACLEN value. Using default %d.\n", line, p_misc_config->paclen);
}
}
/*
* MAXFRAME n - Max frames to send before ACK. mod 8 "Window" size.
*
* Window size would make more sense but everyone else calls it MAXFRAME.
*/
else if (strcasecmp(t, "MAXFRAME") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for MAXFRAME.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_K_MAXFRAME_BASIC_MIN && n <= AX25_K_MAXFRAME_BASIC_MAX) {
p_misc_config->maxframe_basic = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid MAXFRAME value outside range of %d to %d. Using default %d.\n",
line, AX25_K_MAXFRAME_BASIC_MIN, AX25_K_MAXFRAME_BASIC_MAX, p_misc_config->maxframe_basic);
}
}
/*
* EMAXFRAME n - Max frames to send before ACK. mod 128 "Window" size.
*/
else if (strcasecmp(t, "EMAXFRAME") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for EMAXFRAME.\n", line);
continue;
}
n = atoi(t);
if (n >= AX25_K_MAXFRAME_EXTENDED_MIN && n <= AX25_K_MAXFRAME_EXTENDED_MAX) {
p_misc_config->maxframe_extended = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid EMAXFRAME value outside of range %d to %d. Using default %d.\n",
line, AX25_K_MAXFRAME_EXTENDED_MIN, AX25_K_MAXFRAME_EXTENDED_MAX, p_misc_config->maxframe_extended);
}
}
/*
* MAXV22 n - Max number of SABME sent before trying SABM.
*/
else if (strcasecmp(t, "MAXV22") == 0) {
int n;
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Missing value for MAXV22.\n", line);
continue;
}
n = atoi(t);
if (n >= 0 && n <= AX25_N2_RETRY_MAX) {
p_misc_config->maxv22 = n;
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid MAXV22 number. Will use half of RETRY.\n", line);
}
}
/*
* Invalid command.
*/
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Unrecognized command '%s' on line %d.\n", t, line);
}
}
fclose (fp);
/*
* A little error checking for option interactions.
*/
/*
* Require that MYCALL be set when digipeating or IGating.
*
* Suggest that beaconing be enabled when digipeating.
*/
int i, j, k, b;
for (i=0; ienabled[i][j]) {
if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 ||
strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i);
p_digi_config->enabled[i][j] = 0;
}
if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 ||
strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i);
p_digi_config->enabled[i][j] = 0;
}
b = 0;
for (k=0; knum_beacons; k++) {
if (p_misc_config->beacon[k].sendto_chan == j) b++;
}
if (b == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j);
// It's a recommendation, not a requirement.
// Was there some good reason to turn it off in earlier version?
//p_digi_config->enabled[i][j] = 0;
}
}
/* Connected mode digipeating. */
if (p_cdigi_config->enabled[i][j]) {
if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 ||
strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i);
p_cdigi_config->enabled[i][j] = 0;
}
if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 ||
strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i);
p_cdigi_config->enabled[i][j] = 0;
}
b = 0;
for (k=0; knum_beacons; k++) {
if (p_misc_config->beacon[k].sendto_chan == j) b++;
}
if (b == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j);
// It's a recommendation, not a requirement.
}
}
}
if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) {
if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i);
strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login));
}
// Currently we can have only one transmit channel.
// This might be generalized someday to allow more.
if (p_igate_config->tx_chan >= 0 &&
( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 ||
strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "N0CALL") == 0)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for transmit channel %d before Tx IGate is allowed.\n", i);
p_igate_config->tx_chan = -1;
}
}
}
// Apply default IS>RF IGate filter if none specified. New in 1.4.
// This will handle eventual case of multiple transmit channels.
for (j=0; jachan[j].valid && strlen(p_igate_config->t2_login) > 0) {
if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) {
p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/30");
}
}
}
// Terrible hack. But what can we do?
if (p_misc_config->maxv22 < 0) {
p_misc_config->maxv22 = p_misc_config->retry / 3;
}
} /* end config_init */
/*
* Parse the PBEACON or OBEACON options.
* Returns 1 for success, 0 for serious error.
*/
static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config)
{
char *t;
char temp_symbol[100];
int ok;
char zone[8];
double easting = G_UNKNOWN;
double northing = G_UNKNOWN;
strlcpy (temp_symbol, "", sizeof(temp_symbol));
strlcpy (zone, "", sizeof(zone));
b->sendto_type = SENDTO_XMIT;
b->sendto_chan = 0;
b->delay = 60;
b->every = 600;
//b->delay = 6; // temp test.
//b->every = 3600;
b->lat = G_UNKNOWN;
b->lon = G_UNKNOWN;
b->alt_m = G_UNKNOWN;
b->symtab = '/';
b->symbol = '-'; /* house */
b->freq = G_UNKNOWN;
b->tone = G_UNKNOWN;
b->offset = G_UNKNOWN;
while ((t = split(NULL,0)) != NULL) {
char keyword[20];
char value[200];
char *e;
char *p;
e = strchr(t, '=');
if (e == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: No = found in, %s, on line %d.\n", t, line);
return (0);
}
*e = '\0';
strlcpy (keyword, t, sizeof(keyword));
strlcpy (value, e+1, sizeof(value));
if (strcasecmp(keyword, "DELAY") == 0) {
b->delay = parse_interval(value,line);
}
else if (strcasecmp(keyword, "EVERY") == 0) {
b->every = parse_interval(value,line);
}
else if (strcasecmp(keyword, "SENDTO") == 0) {
if (value[0] == 'i' || value[0] == 'I') {
b->sendto_type = SENDTO_IGATE;
b->sendto_chan = 0;
}
else if (value[0] == 'r' || value[0] == 'R') {
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);
continue;
}
b->sendto_type = SENDTO_RECV;
b->sendto_chan = n;
}
else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') {
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);
continue;
}
b->sendto_type = SENDTO_XMIT;
b->sendto_chan = n;
}
else {
int n = atoi(value);
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);
continue;
}
b->sendto_type = SENDTO_XMIT;
b->sendto_chan = n;
}
}
else if (strcasecmp(keyword, "DEST") == 0) {
b->dest = strdup(value);
for (p = b->dest; *p != '\0'; p++) {
if (islower(*p)) {
*p = toupper(*p); /* silently force upper case. */
}
}
if (strlen(b->dest) > 9) {
b->dest[9] = '\0';
}
}
else if (strcasecmp(keyword, "VIA") == 0) {
#if 1 // proper checking
if (check_via_path(value) >= 0) {
b->via = strdup(value);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: invalid via path.\n", line);
}
#else // previously
b->via = strdup(value);
for (p = b->via; *p != '\0'; p++) {
if (islower(*p)) {
*p = toupper(*p); /* silently force upper case. */
}
}
#endif
}
else if (strcasecmp(keyword, "INFO") == 0) {
b->custom_info = strdup(value);
}
else if (strcasecmp(keyword, "INFOCMD") == 0) {
b->custom_infocmd = strdup(value);
}
else if (strcasecmp(keyword, "OBJNAME") == 0) {
strlcpy(b->objname, value, sizeof(b->objname));
}
else if (strcasecmp(keyword, "LAT") == 0) {
b->lat = parse_ll (value, LAT, line);
}
else if (strcasecmp(keyword, "LONG") == 0 || strcasecmp(keyword, "LON") == 0) {
b->lon = parse_ll (value, LON, line);
}
else if (strcasecmp(keyword, "ALT") == 0 || strcasecmp(keyword, "ALTITUDE") == 0) {
b->alt_m = atof(value);
}
else if (strcasecmp(keyword, "ZONE") == 0) {
strlcpy(zone, value, sizeof(zone));
}
else if (strcasecmp(keyword, "EAST") == 0 || strcasecmp(keyword, "EASTING") == 0) {
easting = atof(value);
}
else if (strcasecmp(keyword, "NORTH") == 0 || strcasecmp(keyword, "NORTHING") == 0) {
northing = atof(value);
}
else if (strcasecmp(keyword, "SYMBOL") == 0) {
/* Defer processing in case overlay appears later. */
strlcpy (temp_symbol, value, sizeof(temp_symbol));
}
else if (strcasecmp(keyword, "OVERLAY") == 0) {
if (strlen(value) == 1 && (isupper(value[0]) || isdigit(value[0]))) {
b->symtab = value[0];
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Overlay must be one character in range of 0-9 or A-Z, upper case only, on line %d.\n", line);
}
}
else if (strcasecmp(keyword, "POWER") == 0) {
b->power = atoi(value);
}
else if (strcasecmp(keyword, "HEIGHT") == 0) {
b->height = atoi(value);
}
else if (strcasecmp(keyword, "GAIN") == 0) {
b->gain = atoi(value);
}
else if (strcasecmp(keyword, "DIR") == 0 || strcasecmp(keyword, "DIRECTION") == 0) {
strlcpy(b->dir, value, sizeof(b->dir));
}
else if (strcasecmp(keyword, "FREQ") == 0) {
b->freq = atof(value);
}
else if (strcasecmp(keyword, "TONE") == 0) {
b->tone = atof(value);
}
else if (strcasecmp(keyword, "OFFSET") == 0 || strcasecmp(keyword, "OFF") == 0) {
b->offset = atof(value);
}
else if (strcasecmp(keyword, "COMMENT") == 0) {
b->comment = strdup(value);
}
else if (strcasecmp(keyword, "COMMENTCMD") == 0) {
b->commentcmd = strdup(value);
}
else if (strcasecmp(keyword, "COMPRESS") == 0 || strcasecmp(keyword, "COMPRESSED") == 0) {
b->compress = atoi(value);
}
else if (strcasecmp(keyword, "MESSAGING") == 0) {
b->messaging = atoi(value);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Invalid option keyword, %s.\n", line, keyword);
return (0);
}
}
if (b->custom_info != NULL && b->custom_infocmd != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time..\n", line);
}
/*
* Convert UTM coordintes to lat / long.
*/
if (strlen(zone) > 0 || easting != G_UNKNOWN || northing != G_UNKNOWN) {
if (strlen(zone) > 0 && easting != G_UNKNOWN && northing != G_UNKNOWN) {
long lzone;
char latband, hemi;
long lerr;
double dlat, dlon;
lzone = parse_utm_zone (zone, &latband, &hemi);
lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon);
if (lerr == 0) {
b->lat = R2D(dlat);
b->lon = R2D(dlon);
}
else {
char message [300];
utm_error_string (lerr, message);
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message);
}
}
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);
}
}
/*
* Process symbol now that we have any later overlay.
*/
if (strlen(temp_symbol) > 0) {
if (strlen(temp_symbol) == 2 &&
(temp_symbol[0] == '/' || temp_symbol[0] == '\\' || isupper(temp_symbol[0]) || isdigit(temp_symbol[0])) &&
temp_symbol[1] >= '!' && temp_symbol[1] <= '~') {
/* Explicit table and symbol. */
if (isupper(b->symtab) || isdigit(b->symtab)) {
b->symbol = temp_symbol[1];
}
else {
b->symtab = temp_symbol[0];
b->symbol = temp_symbol[1];
}
}
else {
/* Try to look up by description. */
ok = symbols_code_from_description (b->symtab, temp_symbol, &(b->symtab), &(b->symbol));
if (!ok) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Could not find symbol matching %s.\n", line, temp_symbol);
}
}
}
/* Check is here because could be using default channel when SENDTO= is not specified. */
if (b->sendto_type == SENDTO_XMIT) {
if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || ! p_audio_config->achan[b->sendto_chan].valid) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan);
return (0);
}
if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 ||
strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan);
return (0);
}
}
return (1);
}
/* end config.c */
direwolf-1.4/config.h 0000664 0000000 0000000 00000013762 13100232553 0014612 0 ustar 00root root 0000000 0000000
/*----------------------------------------------------------------------------
*
* Name: config.h
*
* Purpose:
*
* Description:
*
*-----------------------------------------------------------------------------*/
#ifndef CONFIG_H
#define CONFIG_H 1
#include "audio.h" /* for struct audio_s */
#include "digipeater.h" /* for struct digi_config_s */
#include "cdigipeater.h" /* for struct cdigi_config_s */
#include "aprs_tt.h" /* for struct tt_config_s */
#include "igate.h" /* for struct igate_config_s */
/*
* All the leftovers.
* This wasn't thought out. It just happened.
*/
enum beacon_type_e { BEACON_IGNORE, BEACON_POSITION, BEACON_OBJECT, BEACON_TRACKER, BEACON_CUSTOM, BEACON_IGATE };
enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV };
#define MAX_BEACONS 30
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 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 */
/* virtual null modem for native Windows apps. */
char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */
/* e.g. COM22, /dev/ttyACM0 */
/* Currently no option for setting non-standard speed. */
char gpsd_host[20]; /* Host for gpsd server. */
/* e.g. localhost, 192.168.1.2 */
int gpsd_port; /* Port number for gpsd server. */
/* Default is 2947. */
char waypoint_port[20]; /* Serial port name for sending NMEA waypoint sentences */
/* to a GPS map display or other mapping application. */
/* e.g. COM22, /dev/ttyACM0 */
/* Currently no option for setting non-standard speed. */
int waypoint_formats; /* Which sentence formats should be generated? */
#define WPT_FORMAT_NMEA_GENERIC 0x01 /* N $GPWPT */
#define WPT_FORMAT_GARMIN 0x02 /* G $PGRMW */
#define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */
#define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */
char logdir[80]; /* Directory for saving activity logs. */
int sb_configured; /* TRUE if SmartBeaconing is configured. */
int sb_fast_speed; /* MPH */
int sb_fast_rate; /* seconds */
int sb_slow_speed; /* MPH */
int sb_slow_rate; /* seconds */
int sb_turn_time; /* seconds */
int sb_turn_angle; /* degrees */
int sb_turn_slope; /* degrees * MPH */
// AX.25 connected mode.
int frack; /* Number of seconds to wait for ack to transmission. */
int retry; /* Number of times to retry before giving up. */
int paclen; /* Max number of bytes in information part of frame. */
int maxframe_basic; /* Max frames to send before ACK. mod 8 "Window" size. */
int maxframe_extended; /* Max frames to send before ACK. mod 128 "Window" size. */
int maxv22; /* Maximum number of unanswered SABME frames sent before */
/* switching to SABM. This is to handle the case of an old */
/* TNC which simply ignores SABME rather than replying with FRMR. */
// Beacons.
int num_beacons; /* Number of beacons defined. */
struct beacon_s {
enum beacon_type_e btype; /* Position or object. */
int lineno; /* Line number from config file for later error messages. */
enum sendto_type_e sendto_type;
/* SENDTO_XMIT - Usually beacons go to a radio transmitter. */
/* chan, below is the channel number. */
/* SENDTO_IGATE - Send to IGate, probably to announce my position */
/* rather than relying on someone else to hear */
/* me on the radio and report me. */
/* SENDTO_RECV - Pretend this was heard on the specified */
/* radio channel. Mostly for testing. It is a */
/* convenient way to send packets to attached apps. */
int sendto_chan; /* Transmit or simulated receive channel for above. Should be 0 for IGate. */
int delay; /* Seconds to delay before first transmission. */
int every; /* Time between transmissions, seconds. */
/* Remains fixed for PBEACON and OBEACON. */
/* Dynamically adjusted for TBEACON. */
time_t next; /* Unix time to transmit next one. */
char *dest; /* NULL or explicit AX.25 destination to use */
/* instead of the software version such as APDW11. */
int compress; /* Use more compact form? */
char objname[10]; /* Object name. Any printable characters. */
char *via; /* Path, e.g. "WIDE1-1,WIDE2-1" or NULL. */
char *custom_info; /* Info part for handcrafted custom beacon. */
/* Ignore the rest below if this is set. */
char *custom_infocmd; /* Command to generate info part. */
/* Again, other options below are then ignored. */
int messaging; /* Set messaging attribute for position report. */
/* i.e. Data Type Indicator of '=' rather than '!' */
double lat; /* Latitude and longitude. */
double lon;
float alt_m; /* Altitude in meters. */
char symtab; /* Symbol table: / or \ or overlay character. */
char symbol; /* Symbol code. */
float power; /* For PHG. */
float height;
float gain; /* Original protocol spec was unclear. */
/* Addendum 1.1 clarifies it is dBi not dBd. */
char dir[3]; /* 1 or 2 of N,E,W,S, or empty for omni. */
float freq; /* MHz. */
float tone; /* Hz. */
float offset; /* MHz. */
char *comment; /* Comment or NULL. */
char *commentcmd; /* Command to append more to Comment or NULL. */
} beacon[MAX_BEACONS];
};
#define MIN_IP_PORT_NUMBER 1024
#define MAX_IP_PORT_NUMBER 49151
#define DEFAULT_AGWPE_PORT 8000 /* Like everyone else. */
#define DEFAULT_KISS_PORT 8001 /* Above plus 1. */
#define DEFAULT_NULLMODEM "COM3" /* should be equiv. to /dev/ttyS2 on Cygwin */
extern void config_init (char *fname, struct audio_s *p_modem,
struct digi_config_s *digi_config,
struct cdigi_config_s *cdigi_config,
struct tt_config_s *p_tt_config,
struct igate_config_s *p_igate_config,
struct misc_config_s *misc_config);
#endif /* CONFIG_H */
/* end config.h */
direwolf-1.4/decode_aprs.c 0000664 0000000 0000000 00000417230 13100232553 0015606 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 .
//
/*------------------------------------------------------------------
*
* File: decode_aprs.c
*
* Purpose: Decode the information part of APRS frame.
*
* Description: Present the packet contents in human readable format.
* This is a fairly complete implementation with error messages
* pointing out various specication violations.
*
* Assumptions: ax25_from_frame() has been called to
* separate the header and information.
*
*------------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include /* for atof */
#include /* for strtok */
#include /* for pow */
#include /* for isdigit */
#include
#include "regex.h"
#include "ax25_pad.h"
#include "textcolor.h"
#include "symbols.h"
#include "latlong.h"
#include "dwgpsnmea.h"
#include "decode_aprs.h"
#include "telemetry.h"
#define TRUE 1
#define FALSE 0
/* Position & symbol fields common to several message formats. */
typedef struct {
char lat[8];
char sym_table_id; /* / \ 0-9 A-Z */
char lon[9];
char symbol_code;
} position_t;
typedef struct {
char sym_table_id; /* / \ a-j A-Z */
/* "The presence of the leading Symbol Table Identifier */
/* instead of a digit indicates that this is a compressed */
/* Position Report and not a normal lat/long report." */
/* "a-j" is not a typographical error. */
/* The first 10 lower case letters represent the overlay */
/* characters of 0-9 in the compressed format. */
char y[4]; /* Compressed Latitude. */
char x[4]; /* Compressed Longitude. */
char symbol_code;
char c; /* Course/speed or altitude. */
char s;
char t ; /* Compression type. */
} compressed_position_t;
/* Range of digits for Base 91 representation. */
#define B91_MIN '!'
#define B91_MAX '{'
#define isdigit91(c) ((c) >= B91_MIN && (c) <= B91_MAX)
//static void print_decoded (decode_aprs_t *A);
static void aprs_ll_pos (decode_aprs_t *A, unsigned char *, int);
static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *, int);
static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *, int);
static void aprs_mic_e (decode_aprs_t *A, packet_t, unsigned char *, int);
//static void aprs_compressed_pos (decode_aprs_t *A, unsigned char *, int);
static void aprs_message (decode_aprs_t *A, unsigned char *, int, int quiet);
static void aprs_object (decode_aprs_t *A, unsigned char *, int);
static void aprs_item (decode_aprs_t *A, unsigned char *, int);
static void aprs_station_capabilities (decode_aprs_t *A, char *, int);
static void aprs_status_report (decode_aprs_t *A, char *, int);
static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet);
static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet);
static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet);
static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int);
static void aprs_morse_code (decode_aprs_t *A, char *, int);
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix);
static void aprs_ultimeter (decode_aprs_t *A, char *, int);
static void third_party_header (decode_aprs_t *A, char *, int);
static void decode_position (decode_aprs_t *A, position_t *ppos);
static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *ppos);
static double get_latitude_8 (char *p, int quiet);
static double get_longitude_9 (char *p, int quiet);
static time_t get_timestamp (decode_aprs_t *A, char *p);
static int get_maidenhead (decode_aprs_t *A, char *p);
static int data_extension_comment (decode_aprs_t *A, char *pdext);
static void decode_tocall (decode_aprs_t *A, char *dest);
//static void get_symbol (decode_aprs_t *A, char dti, char *src, char *dest);
static void process_comment (decode_aprs_t *A, char *pstart, int clen);
/*------------------------------------------------------------------
*
* Function: decode_aprs
*
* Purpose: Split APRS packet into separate properties that it contains.
*
* Inputs: pp - APRS packet object.
*
* quiet - Suppress error messages.
*
* Outputs: A-> g_symbol_table, g_symbol_code,
* g_lat, g_lon,
* g_speed_mph, g_course, g_altitude_ft,
* g_comment
* ... and many others...
*
* Major Revisions: 1.1 Reorganized so parts are returned in a structure.
* Print function is now called separately.
*
*------------------------------------------------------------------*/
void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
{
char dest[AX25_MAX_ADDR_LEN];
unsigned char *pinfo;
int info_len;
info_len = ax25_get_info (pp, &pinfo);
memset (A, 0, sizeof (*A));
A->g_quiet = quiet;
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown APRS Data Type Indicator \"%c\"", *pinfo);
A->g_symbol_table = '/'; /* Default to primary table. */
A->g_symbol_code = ' '; /* What should we have for default symbol? */
A->g_lat = G_UNKNOWN;
A->g_lon = G_UNKNOWN;
A->g_speed_mph = G_UNKNOWN;
A->g_course = G_UNKNOWN;
A->g_power = G_UNKNOWN;
A->g_height = G_UNKNOWN;
A->g_gain = G_UNKNOWN;
A->g_range = G_UNKNOWN;
A->g_altitude_ft = G_UNKNOWN;
A->g_freq = G_UNKNOWN;
A->g_tone = G_UNKNOWN;
A->g_dcs = G_UNKNOWN;
A->g_offset = G_UNKNOWN;
A->g_footprint_lat = G_UNKNOWN;
A->g_footprint_lon = G_UNKNOWN;
A->g_footprint_radius = G_UNKNOWN;
/*
* Extract source and destination including the SSID.
*/
ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
/*
* Report error if the information part contains a nul character.
* There are two known cases where this can happen.
*
* - The Kenwood TM-D710A sometimes sends packets like this:
*
* VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast=
* K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV =
*
* Notice that the data type indicator of "4" is not valid. If we remove
* 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format.
* This same thing has been observed from others and is intermittent.
*
* - AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes.
* This is wrong, it should be using UTF-8.
*/
if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) {
text_color_set(DW_COLOR_ERROR);
dw_printf("'nul' character found in Information part. This should never happen.\n");
dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src);
if (strcmp((char*)pinfo, "4P") == 0) {
dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n");
}
}
switch (*pinfo) { /* "DTI" data type identifier. */
case '!': /* Position without timestamp (no APRS messaging). */
/* or Ultimeter 2000 WX Station */
case '=': /* Position without timestamp (with APRS messaging). */
if (strncmp((char*)pinfo, "!!", 2) == 0)
{
aprs_ultimeter (A, (char*)pinfo, info_len);
}
else
{
aprs_ll_pos (A, pinfo, info_len);
}
break;
//case '#': /* Peet Bros U-II Weather station */
//case '*': /* Peet Bros U-II Weather station */
//break;
case '$': /* Raw GPS data or Ultimeter 2000 */
if (strncmp((char*)pinfo, "$ULTW", 5) == 0)
{
aprs_ultimeter (A, (char*)pinfo, info_len);
}
else
{
aprs_raw_nmea (A, pinfo, info_len);
}
break;
case '\'': /* Old Mic-E Data (but Current data for TM-D700) */
case '`': /* Current Mic-E Data (not used in TM-D700) */
aprs_mic_e (A, pp, pinfo, info_len);
break;
case ')': /* Item. */
aprs_item (A, pinfo, info_len);
break;
case '/': /* Position with timestamp (no APRS messaging) */
case '@': /* Position with timestamp (with APRS messaging) */
aprs_ll_pos_time (A, pinfo, info_len);
break;
case ':': /* Message: for one person, a group, or a bulletin. */
/* Directed Station Query */
/* Telemetry metadata. */
aprs_message (A, pinfo, info_len, quiet);
break;
case ';': /* Object */
aprs_object (A, pinfo, info_len);
break;
case '<': /* Station Capabilities */
aprs_station_capabilities (A, (char*)pinfo, info_len);
break;
case '>': /* Status Report */
aprs_status_report (A, (char*)pinfo, info_len);
break;
case '?': /* General Query */
aprs_general_query (A, (char*)pinfo, info_len, quiet);
break;
case 'T': /* Telemetry */
aprs_telemetry (A, (char*)pinfo, info_len, quiet);
break;
case '_': /* Positionless Weather Report */
aprs_positionless_weather_report (A, pinfo, info_len);
break;
case '{': /* user defined data */
/* http://www.aprs.org/aprs11/expfmts.txt */
if (strncmp((char*)pinfo, "{tt", 3) == 0) {
aprs_raw_touch_tone (A, (char*)pinfo, info_len);
}
else if (strncmp((char*)pinfo, "{mc", 3) == 0) {
aprs_morse_code (A, (char*)pinfo, info_len);
}
else {
//aprs_user_defined (A, pinfo, info_len);
}
break;
case 't': /* Raw touch tone data - NOT PART OF STANDARD */
/* Used to convey raw touch tone sequences to */
/* to an application that might want to interpret them. */
/* Might move into user defined data, above. */
aprs_raw_touch_tone (A, (char*)pinfo, info_len);
break;
case 'm': /* Morse Code data - NOT PART OF STANDARD */
/* Used by APRStt gateway to put audible responses */
/* into the transmit queue. Could potentially find */
/* other uses such as CW ID for station. */
/* Might move into user defined data, above. */
aprs_morse_code (A, (char*)pinfo, info_len);
break;
case '}': /* third party header */
third_party_header (A, (char*)pinfo, info_len);
break;
//case '\r': /* CR or LF? */
//case '\n':
//break;
default:
break;
}
/*
* Look in other locations if not found in information field.
*/
if (A->g_symbol_table == ' ' || A->g_symbol_code == ' ') {
symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
}
/*
* Application might be in the destination field for most message types.
* MIC-E format has part of location in the destination field.
*/
switch (*pinfo) { /* "DTI" data type identifier. */
case '\'': /* Old Mic-E Data */
case '`': /* Current Mic-E Data */
break;
default:
decode_tocall (A, dest);
break;
}
} /* end decode_aprs */
void decode_aprs_print (decode_aprs_t *A) {
char stemp[200];
//char tmp2[2];
double absll;
char news;
int deg;
double min;
char s_lat[30];
char s_lon[30];
int n;
char symbol_description[100];
/*
* First line has:
* - message type
* - object name
* - symbol
* - manufacturer/application
* - mic-e status
* - power/height/gain, range
*/
strlcpy (stemp, A->g_msg_type, sizeof(stemp));
if (strlen(A->g_name) > 0) {
strlcat (stemp, ", \"", sizeof(stemp));
strlcat (stemp, A->g_name, sizeof(stemp));
strlcat (stemp, "\"", sizeof(stemp));
}
if (A->g_symbol_code != ' ') {
symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description, sizeof(symbol_description));
strlcat (stemp, ", ", sizeof(stemp));
strlcat (stemp, symbol_description, sizeof(stemp));
}
if (strlen(A->g_mfr) > 0) {
strlcat (stemp, ", ", sizeof(stemp));
strlcat (stemp, A->g_mfr, sizeof(stemp));
}
if (strlen(A->g_mic_e_status) > 0) {
strlcat (stemp, ", ", sizeof(stemp));
strlcat (stemp, A->g_mic_e_status, sizeof(stemp));
}
if (A->g_power > 0) {
char phg[100];
/* Protcol spec doesn't mention whether this is dBd or dBi. */
/* Clarified later. */
/* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */
/* "The Antenna Gain in the PHG format on page 28 is in dBi." */
snprintf (phg, sizeof(phg), ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity);
strlcat (stemp, phg, sizeof(stemp));
}
if (A->g_range > 0) {
char rng[100];
snprintf (rng, sizeof(rng), ", range=%.1f", A->g_range);
strlcat (stemp, rng, sizeof(stemp));
}
text_color_set(DW_COLOR_DECODED);
dw_printf("%s\n", stemp);
/*
* Second line has:
* - Latitude
* - Longitude
* - speed
* - direction
* - altitude
* - frequency
*/
/*
* Convert Maidenhead locator to latitude and longitude.
*
* Any example was checked for each hemihemisphere using
* http://www.amsat.org/cgi-bin/gridconv
*/
if (strlen(A->g_maidenhead) > 0) {
if (A->g_lat == G_UNKNOWN && A->g_lon == G_UNKNOWN) {
ll_from_grid_square (A->g_maidenhead, &(A->g_lat), &(A->g_lon));
}
dw_printf("Grid square = %s, ", A->g_maidenhead);
}
strlcpy (stemp, "", sizeof(stemp));
if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) {
// Have location but it is posible one part is invalid.
if (A->g_lat != G_UNKNOWN) {
if (A->g_lat >= 0) {
absll = A->g_lat;
news = 'N';
}
else {
absll = - A->g_lat;
news = 'S';
}
deg = (int) absll;
min = (absll - deg) * 60.0;
snprintf (s_lat, sizeof(s_lat), "%c %02d%s%07.4f", news, deg, CH_DEGREE, min);
}
else {
strlcpy (s_lat, "Invalid Latitude", sizeof(s_lat));
}
if (A->g_lon != G_UNKNOWN) {
if (A->g_lon >= 0) {
absll = A->g_lon;
news = 'E';
}
else {
absll = - A->g_lon;
news = 'W';
}
deg = (int) absll;
min = (absll - deg) * 60.0;
snprintf (s_lon, sizeof(s_lon), "%c %03d%s%07.4f", news, deg, CH_DEGREE, min);
}
else {
strlcpy (s_lon, "Invalid Longitude", sizeof(s_lon));
}
snprintf (stemp, sizeof(stemp), "%s, %s", s_lat, s_lon);
}
if (strlen(A->g_aprstt_loc) > 0) {
if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
strlcat (stemp, A->g_aprstt_loc, sizeof(stemp));
};
if (A->g_speed_mph != G_UNKNOWN) {
char spd[20];
if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
snprintf (spd, sizeof(spd), "%.0f MPH", A->g_speed_mph);
strlcat (stemp, spd, sizeof(stemp));
};
if (A->g_course != G_UNKNOWN) {
char cse[20];
if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
snprintf (cse, sizeof(cse), "course %.0f", A->g_course);
strlcat (stemp, cse, sizeof(stemp));
};
if (A->g_altitude_ft != G_UNKNOWN) {
char alt[20];
if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
snprintf (alt, sizeof(alt), "alt %.0f ft", A->g_altitude_ft);
strlcat (stemp, alt, sizeof(stemp));
};
if (A->g_freq != G_UNKNOWN) {
char ftemp[30];
snprintf (ftemp, sizeof(ftemp), ", %.3f MHz", A->g_freq);
strlcat (stemp, ftemp, sizeof(stemp));
}
if (A->g_offset != G_UNKNOWN) {
char ftemp[30];
if (A->g_offset % 1000 == 0) {
snprintf (ftemp, sizeof(ftemp), ", %+dM", A->g_offset/1000);
}
else {
snprintf (ftemp, sizeof(ftemp), ", %+dk", A->g_offset);
}
strlcat (stemp, ftemp, sizeof(stemp));
}
if (A->g_tone != G_UNKNOWN) {
if (A->g_tone == 0) {
strlcat (stemp, ", no PL", sizeof(stemp));
}
else {
char ftemp[30];
snprintf (ftemp, sizeof(ftemp), ", PL %.1f", A->g_tone);
strlcat (stemp, ftemp, sizeof(stemp));
}
}
if (A->g_dcs != G_UNKNOWN) {
char ftemp[30];
snprintf (ftemp, sizeof(ftemp), ", DCS %03o", A->g_dcs);
strlcat (stemp, ftemp, sizeof(stemp));
}
if (strlen (stemp) > 0) {
text_color_set(DW_COLOR_DECODED);
dw_printf("%s\n", stemp);
}
/*
* Finally, any weather and/or comment.
*
* Non-printable characters are changed to safe hexadecimal representations.
* For example, carriage return is displayed as <0x0d>.
*
* Drop annoying trailing CR LF. Anyone who cares can see it in the raw datA->
*/
n = strlen(A->g_weather);
if (n >= 1 && A->g_weather[n-1] == '\n') {
A->g_weather[n-1] = '\0';
n--;
}
if (n >= 1 && A->g_weather[n-1] == '\r') {
A->g_weather[n-1] = '\0';
n--;
}
if (n > 0) {
ax25_safe_print (A->g_weather, -1, 0);
dw_printf("\n");
}
if (strlen(A->g_telemetry) > 0) {
ax25_safe_print (A->g_telemetry, -1, 0);
dw_printf("\n");
}
n = strlen(A->g_comment);
if (n >= 1 && A->g_comment[n-1] == '\n') {
A->g_comment[n-1] = '\0';
n--;
}
if (n >= 1 && A->g_comment[n-1] == '\r') {
A->g_comment[n-1] = '\0';
n--;
}
if (n > 0) {
int j;
ax25_safe_print (A->g_comment, -1, 0);
dw_printf("\n");
/*
* Point out incorrect attempts a degree symbol.
* 0xb0 is degree in ISO Latin1.
* To be part of a valid UTF-8 sequence, it would need to be preceded by 11xxxxxx or 10xxxxxx.
* 0xf8 is degree in Microsoft code page 437.
* To be part of a valid UTF-8 sequence, it would need to be followed by 10xxxxxx.
*/
if ( ! A->g_quiet) {
for (j=0; jg_comment[j]) == 0xb0 && (j == 0 || ! (A->g_comment[j-1] & 0x80))) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n");
dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
}
}
for (j=0; jg_comment[j]) == 0xf8 && (j == n-1 || (A->g_comment[j+1] & 0xc0) != 0xc0)) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n");
dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
}
}
}
}
}
/*------------------------------------------------------------------
*
* Function: aprs_ll_pos
*
* Purpose: Decode "Lat/Long Position Report - without Timestamp"
*
* Reports without a timestamp can be regarded as real-time.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
*
* Description: Type identifier '=' has APRS messaging.
* Type identifier '!' does not have APRS messaging.
*
* The location can be in either compressed or human-readable form.
*
* When the symbol code is '_' this is a weather report.
*
* Examples: !4309.95NS07307.13W#PHG3320 W2,NY2 Mt Equinox VT k2lm@arrl.net
* !4237.14NS07120.83W#
* =4246.40N/07115.15W# {UIV32}
*
* TODO: (?) Special case, DF report when sym table id = '/' and symbol code = '\'.
*
* =4903.50N/07201.75W\088/036/270/729
*
*------------------------------------------------------------------*/
static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen)
{
struct aprs_ll_pos_s {
char dti; /* ! or = */
position_t pos;
char comment[43]; /* Start of comment could be data extension(s). */
} *p;
struct aprs_compressed_pos_s {
char dti; /* ! or = */
compressed_position_t cpos;
char comment[40]; /* No data extension allowed for compressed location. */
} *q;
strlcpy (A->g_msg_type, "Position", sizeof(A->g_msg_type));
p = (struct aprs_ll_pos_s *)info;
q = (struct aprs_compressed_pos_s *)info;
if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */
{
decode_position (A, &(p->pos));
if (A->g_symbol_code == '_') {
/* Symbol code indidates it is a weather report. */
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
weather_data (A, p->comment, TRUE);
}
else {
/* Regular position report. */
data_extension_comment (A, p->comment);
}
}
else /* Compressed location. */
{
decode_compressed_position (A, &(q->cpos));
if (A->g_symbol_code == '_') {
/* Symbol code indidates it is a weather report. */
/* In this case, the wind direction and speed are in the */
/* compressed data so we don't expect a 7 byte "data */
/* extension" for them. */
strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
weather_data (A, q->comment, FALSE);
}
else {
/* Regular position report. */
process_comment (A, q->comment, -1);
}
}
}
/*------------------------------------------------------------------
*
* Function: aprs_ll_pos_time
*
* Purpose: Decode "Lat/Long Position Report - with Timestamp"
*
* Reports sent with a timestamp might contain very old information.
*
* Otherwise, same as above.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
*
* Description: Type identifier '@' has APRS messaging.
* Type identifier '/' does not have APRS messaging.
*
* The location can be in either compressed or human-readable form.
*
* When the symbol code is '_' this is a weather report.
*
* Examples: @041025z4232.32N/07058.81W_124/000g000t036r000p000P000b10229h65/wx rpt
* @281621z4237.55N/07120.20W_017/002g006t022r000p000P000h85b10195.Dvs
* /092345z4903.50N/07201.75W>Test1234
*
* I think the symbol code of "_" indicates weather report.
*
* (?) Special case, DF report when sym table id = '/' and symbol code = '\'.
*
* @092345z4903.50N/07201.75W\088/036/270/729
* /092345z4903.50N/07201.75W\000/000/270/729
*
*------------------------------------------------------------------*/
static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen)
{
struct aprs_ll_pos_time_s {
char dti; /* / or @ */
char time_stamp[7];
position_t pos;
char comment[43]; /* First 7 bytes could be data extension. */
} *p;
struct aprs_compressed_pos_time_s {
char dti; /* / or @ */
char time_stamp[7];
compressed_position_t cpos;
char comment[40]; /* No data extension in this case. */
} *q;
strlcpy (A->g_msg_type, "Position with time", sizeof(A->g_msg_type));
time_t ts = 0;
p = (struct aprs_ll_pos_time_s *)info;
q = (struct aprs_compressed_pos_time_s *)info;
if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */
{
ts = get_timestamp (A, p->time_stamp);
decode_position (A, &(p->pos));
if (A->g_symbol_code == '_') {
/* Symbol code indidates it is a weather report. */
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
weather_data (A, p->comment, TRUE);
}
else {
/* Regular position report. */
data_extension_comment (A, p->comment);
}
}
else /* Compressed location. */
{
ts = get_timestamp (A, p->time_stamp);
decode_compressed_position (A, &(q->cpos));
if (A->g_symbol_code == '_') {
/* Symbol code indidates it is a weather report. */
/* In this case, the wind direction and speed are in the */
/* compressed data so we don't expect a 7 byte "data */
/* extension" for them. */
strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
weather_data (A, q->comment, FALSE);
}
else {
/* Regular position report. */
process_comment (A, q->comment, -1);
}
}
(void)(ts); // suppress 'set but not used' warning.
}
/*------------------------------------------------------------------
*
* Function: aprs_raw_nmea
*
* Purpose: Decode "Raw NMEA Position Report"
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A-> ...
*
* Description: APRS recognizes raw ASCII data strings conforming to the NMEA 0183
* Version 2.0 specification, originating from navigation equipment such
* as GPS and LORAN receivers. It is recommended that APRS stations
* interpret at least the following NMEA Received Sentence types:
*
* GGA Global Positioning System Fix Data
* GLL Geographic Position, Latitude/Longitude Data
* RMC Recommended Minimum Specific GPS/Transit Data
* VTG Velocity and Track Data
* WPL Way Point Location
*
* We presently recognize only RMC and GGA.
*
* Examples: $GPGGA,102705,5157.9762,N,00029.3256,W,1,04,2.0,75.7,M,47.6,M,,*62
* $GPGLL,2554.459,N,08020.187,W,154027.281,A
* $GPRMC,063909,A,3349.4302,N,11700.3721,W,43.022,89.3,291099,13.6,E*52
* $GPVTG,318.7,T,,M,35.1,N,65.0,K*69
*
*------------------------------------------------------------------*/
static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
{
if (strncmp((char*)info, "$GPRMC,", 7) == 0)
{
float speed_knots = G_UNKNOWN;
(void) dwgpsnmea_gprmc ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &speed_knots, &(A->g_course));
A->g_speed_mph = DW_KNOTS_TO_MPH(speed_knots);
}
else if (strncmp((char*)info, "$GPGGA,", 7) == 0)
{
float alt_meters = G_UNKNOWN;
int num_sat = 0;
(void) dwgpsnmea_gpgga ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &alt_meters, &num_sat);
A->g_altitude_ft = DW_METERS_TO_FEET(alt_meters);
}
// TODO (low): add a few other sentence types.
} /* end aprs_raw_nmea */
/*------------------------------------------------------------------
*
* Function: aprs_mic_e
*
* Purpose: Decode MIC-E (also Kenwood D7 & D700) packet.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs:
*
* Description:
*
* Destination Address Field -
*
* The 7-byte Destination Address field contains
* the following encoded information:
*
* * The 6 latitude digits.
* * A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E
* Message Codes or one of 7 Custom Message Codes or an Emergency
* Message Code.
* * The North/South and West/East Indicators.
* * The Longitude Offset Indicator.
* * The generic APRS digipeater path code.
*
* "Although the destination address appears to be quite unconventional, it is
* still a valid AX.25 address, consisting only of printable 7-bit ASCII values."
*
* References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt
*
* Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt
*
* Examples: `b9Z!4y>/>"4N}Paul's_TH-D7
*
* TODO: Destination SSID can contain generic digipeater path.
*
* Bugs: Doesn't handle ambiguous position. "space" treated as zero.
* Invalid data results in a message but latitude is not set to unknown.
*
*------------------------------------------------------------------*/
/* a few test cases
# example from http://www.aprs.org/aprs12/mic-e-examples.txt produces 4 errors.
# TODO: Analyze all the bits someday and possibly report problem with document.
N0CALL>ABCDEF:'abc123R/text
# Let's use an actual valid location and concentrate on the manufacturers
# as listed in http://www.aprs.org/aprs12/mic-e-types.txt
N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Jeff Mobile_%
N1ZZN-9>T2SP0W:`c_Vm6hk/ "49}Originl Mic-E (leading space)
N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D7A walkie Talkie
N1ZZN-9>T2SP0W:`c_Vm6hk/>"49}TH-D72 walkie Talkie=
W6GPS>S4PT3R:`p(1oR0K\>TH-D74A^
N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D700 MObile Radio
N1ZZN-9>T2SP0W:`c_Vm6hk/]"49}TM-D710 Mobile Radio=
# Note: next line has trailing space character after _
N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu VX-8_
N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FTM-350_"
N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu VX-8G_#
N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FT1D_$
N1ZZN-9>T2SP0W:`c_Vm6hk/`"49}Yaesu FTM-400DR_%
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack3|3
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack4|4
# The next group starts with metacharacter "T" which can be any of space > ] ` '
# But space is for original Mic-E, # > and ] are for Kenwood,
# so ` ' would probably be less ambigous choices but any appear to be valid.
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Hamhud\9
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Argent/9
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}HinzTec anyfrog^9
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO*9
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}OTHER~9
# TODO: Why is manufacturer unknown? Should we explicitly say unknown?
[0] VE2VL-9>TU3V0P,VE2PCQ-3,WIDE1,W1UWS-1,UNCAN,WIDE2*:`eB?l")v/"3y}
MIC-E, VAN, En Route
[0] VE2VL-9>TU3U5Q,VE2PCQ-3,WIDE1,W1UWS-1,N1NCI-3,WIDE2*:`eBgl"$v/"42}73 de Julien, Tinytrak 3
MIC-E, VAN, En Route
[0] W1ERB-9>T1SW8P,KB1AEV-15,N1NCI-3,WIDE2*:`dI8l!#j/"3m}
MIC-E, JEEP, In Service
[0] W1ERB-9>T1SW8Q,KB1AEV-15,N1NCI-3,WIDE2*:`dI6l{^j/"4+}IntheJeep..try146.79(PVRA)
"146.79" in comment looks like a frequency in non-standard format.
For most systems to recognize it, use exactly this form "146.790MHz" at beginning of comment.
MIC-E, JEEP, In Service
*/
static int mic_e_digit (decode_aprs_t *A, char c, int mask, int *std_msg, int *cust_msg)
{
if (c >= '0' && c <= '9') {
return (c - '0');
}
if (c >= 'A' && c <= 'J') {
*cust_msg |= mask;
return (c - 'A');
}
if (c >= 'P' && c <= 'Y') {
*std_msg |= mask;
return (c - 'P');
}
/* K, L, Z should be converted to space. */
/* others are invalid. */
/* But caller expects only values 0 - 9. */
if (c == 'K') {
*cust_msg |= mask;
return (0);
}
if (c == 'L') {
return (0);
}
if (c == 'Z') {
*std_msg |= mask;
return (0);
}
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character \"%c\" in MIC-E destination/latitude.\n", c);
}
return (0);
}
static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int ilen)
{
struct aprs_mic_e_s {
char dti; /* ' or ` */
unsigned char lon[3]; /* "d+28", "m+28", "h+28" */
unsigned char speed_course[3];
char symbol_code;
char sym_table_id;
} *p;
char dest[10];
int ch;
int n;
int offset;
int std_msg = 0;
int cust_msg = 0;
const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" };
const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" };
unsigned char *pfirst, *plast;
strlcpy (A->g_msg_type, "MIC-E", sizeof(A->g_msg_type));
p = (struct aprs_mic_e_s *)info;
/* Destination is really latitude of form ddmmhh. */
/* Message codes are buried in the first 3 digits. */
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
A->g_lat = mic_e_digit(A, dest[0], 4, &std_msg, &cust_msg) * 10 +
mic_e_digit(A, dest[1], 2, &std_msg, &cust_msg) +
(mic_e_digit(A, dest[2], 1, &std_msg, &cust_msg) * 1000 +
mic_e_digit(A, dest[3], 0, &std_msg, &cust_msg) * 100 +
mic_e_digit(A, dest[4], 0, &std_msg, &cust_msg) * 10 +
mic_e_digit(A, dest[5], 0, &std_msg, &cust_msg)) / 6000.0;
/* 4th character of desination indicates north / south. */
if ((dest[3] >= '0' && dest[3] <= '9') || dest[3] == 'L') {
/* South */
A->g_lat = ( - A->g_lat);
}
else if (dest[3] >= 'P' && dest[3] <= 'Z')
{
/* North */
}
else
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid MIC-E N/S encoding in 4th character of destination.\n");
}
}
/* Longitude is mostly packed into 3 bytes of message but */
/* has a couple bits of information in the destination. */
if ((dest[4] >= '0' && dest[4] <= '9') || dest[4] == 'L')
{
offset = 0;
}
else if (dest[4] >= 'P' && dest[4] <= 'Z')
{
offset = 1;
}
else
{
offset = 0;
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid MIC-E Longitude Offset in 5th character of destination.\n");
}
}
/* First character of information field is longitude in degrees. */
/* It is possible for the unprintable DEL character to occur here. */
/* 5th character of desination indicates longitude offset of +100. */
/* Not quite that simple :-( */
ch = p->lon[0];
if (offset && ch >= 118 && ch <= 127)
{
A->g_lon = ch - 118; /* 0 - 9 degrees */
}
else if ( ! offset && ch >= 38 && ch <= 127)
{
A->g_lon = (ch - 38) + 10; /* 10 - 99 degrees */
}
else if (offset && ch >= 108 && ch <= 117)
{
A->g_lon = (ch - 108) + 100; /* 100 - 109 degrees */
}
else if (offset && ch >= 38 && ch <= 107)
{
A->g_lon = (ch - 38) + 110; /* 110 - 179 degrees */
}
else
{
A->g_lon = G_UNKNOWN;
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character 0x%02x for MIC-E Longitude Degrees.\n", ch);
}
}
/* Second character of information field is A->g_longitude minutes. */
/* These are all printable characters. */
/*
* More than once I've see the TH-D72A put <0x1a> here and flip between north and south.
*
* WB2OSZ>TRSW1R,WIDE1-1,WIDE2-2:`c0ol!O[/>=<0x0d>
* N 42 37.1200, W 071 20.8300, 0 MPH, course 151
*
* WB2OSZ>TRS7QR,WIDE1-1,WIDE2-2:`v<0x1a>n<0x1c>"P[/>=<0x0d>
* Invalid character 0x1a for MIC-E Longitude Minutes.
* S 42 37.1200, Invalid Longitude, 0 MPH, course 252
*
* This was direct over the air with no opportunity for a digipeater
* or anything else to corrupt the message.
*/
if (A->g_lon != G_UNKNOWN)
{
ch = p->lon[1];
if (ch >= 88 && ch <= 97)
{
A->g_lon += (ch - 88) / 60.0; /* 0 - 9 minutes*/
}
else if (ch >= 38 && ch <= 87)
{
A->g_lon += ((ch - 38) + 10) / 60.0; /* 10 - 59 minutes */
}
else {
A->g_lon = G_UNKNOWN;
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character 0x%02x for MIC-E Longitude Minutes.\n", ch);
}
}
/* Third character of information field is longitude hundredths of minutes. */
/* There are 100 possible values, from 0 to 99. */
/* Note that the range includes 4 unprintable control characters and DEL. */
if (A->g_lon != G_UNKNOWN)
{
ch = p->lon[2];
if (ch >= 28 && ch <= 127)
{
A->g_lon += ((ch - 28) + 0) / 6000.0; /* 0 - 99 hundredths of minutes*/
}
else {
A->g_lon = G_UNKNOWN;
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes.\n", ch);
}
}
}
}
/* 6th character of destintation indicates east / west. */
/*
* Example of apparently invalid encoding. 6th character missing.
*
* [0] KB1HOZ-9>TTRW5,KQ1L-2,WIDE1,KQ1L-8,UNCAN,WIDE2*:`aFo"]|k/]"4m}<0x0d>
* Invalid character "Invalid MIC-E E/W encoding in 6th character of destination.
* MIC-E, truck, Kenwood TM-D700, Off Duty
* N 44 27.5000, E 069 42.8300, 76 MPH, course 196, alt 282 ft
*/
if ((dest[5] >= '0' && dest[5] <= '9') || dest[5] == 'L') {
/* East */
}
else if (dest[5] >= 'P' && dest[5] <= 'Z')
{
/* West */
if (A->g_lon != G_UNKNOWN) {
A->g_lon = ( - A->g_lon);
}
}
else
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid MIC-E E/W encoding in 6th character of destination.\n");
}
}
/* Symbol table and codes like everyone else. */
A->g_symbol_table = p->sym_table_id;
A->g_symbol_code = p->symbol_code;
if (A->g_symbol_table != '/' && A->g_symbol_table != '\\'
&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid symbol table code not one of / \\ A-Z 0-9\n");
}
A->g_symbol_table = '/';
}
/* Message type from two 3-bit codes. */
if (std_msg == 0 && cust_msg == 0) {
strlcpy (A->g_mic_e_status, "Emergency", sizeof(A->g_mic_e_status));
}
else if (std_msg == 0 && cust_msg != 0) {
strlcpy (A->g_mic_e_status, cust_text[cust_msg], sizeof(A->g_mic_e_status));
}
else if (std_msg != 0 && cust_msg == 0) {
strlcpy (A->g_mic_e_status, std_text[std_msg], sizeof(A->g_mic_e_status));
}
else {
strlcpy (A->g_mic_e_status, "Unknown MIC-E Message Type", sizeof(A->g_mic_e_status));
}
/* Speed and course from next 3 bytes. */
n = ((p->speed_course[0] - 28) * 10) + ((p->speed_course[1] - 28) / 10);
if (n >= 800) n -= 800;
A->g_speed_mph = DW_KNOTS_TO_MPH(n);
n = ((p->speed_course[1] - 28) % 10) * 100 + (p->speed_course[2] - 28);
if (n >= 400) n -= 400;
/* Result is 0 for unknown and 1 - 360 where 360 is north. */
/* Convert to 0 - 360 and reserved value for unknown. */
if (n == 0)
A->g_course = G_UNKNOWN;
else if (n == 360)
A->g_course = 0;
else
A->g_course = n;
/* Now try to pick out manufacturer and other optional items. */
/* The telemetry field, in the original spec, is no longer used. */
strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr));
pfirst = info + sizeof(struct aprs_mic_e_s);
plast = info + ilen - 1;
/* Carriage return character at the end is not mentioned in spec. */
/* Remove if found because it messes up extraction of manufacturer. */
/* Don't drop trailing space because that is used for Yaesu VX-8. */
/* As I recall, the IGate function trims trailing spaces. */
/* That would be bad for this particular model. Maybe I'm mistaken? */
if (*plast == '\r') plast--;
#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
// Last updated Sept. 2016 for TH-D74A
if (isT(*pfirst)) {
if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; }
else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; }
else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; }
else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; }
else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; }
}
/*
* An optional altitude is next.
* It is three base-91 digits followed by "}".
* The TM-D710A might have encoding bug. This was observed:
*
* KJ4ETP-9>SUUP9Q,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV$n6:>/]"7&}162.475MHz clintserman@gmail=
* N 35 50.9100, W 083 58.0800, 25 MPH, course 230, alt 945 ft, 162.475MHz
*
* KJ4ETP-9>SUUP6Y,GRNTOP-3*,WIDE2-1,qAR,KI4HDU-2:`oU~nT >/]<0x9a>xt}162.475MHz clintserman@gmail=
* Invalid character in MIC-E altitude. Must be in range of '!' to '{'.
* N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 3280843 ft, 162.475MHz
*
* KJ4ETP-9>SUUP6Y,N4NEQ-3,K4EGA-1,WIDE2*,qAS,N5CWH-1:`oU~nT >/]?xt}162.475MHz clintserman@gmail=
* N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 808497 ft, 162.475MHz
*
* KJ4ETP-9>SUUP2W,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV2o"J>/]"7)}162.475MHz clintserman@gmail=
* N 35 50.2700, W 083 58.2200, 35 MPH, course 246, alt 955 ft, 162.475MHz
*
* Note the <0x9a> which is outside of the 7-bit ASCII range. Clearly very wrong.
*/
if (plast > pfirst && pfirst[3] == '}') {
A->g_altitude_ft = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000);
if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2]))
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in MIC-E altitude. Must be in range of '!' to '{'.\n");
dw_printf("Bogus altitude of %.0f changed to unknown.\n", A->g_altitude_ft);
}
A->g_altitude_ft = G_UNKNOWN;
}
pfirst += 4;
}
process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1);
}
/*------------------------------------------------------------------
*
* Function: aprs_message
*
* Purpose: Decode "Message Format."
* The word message is used loosely all over the place, but it has a very specific meaning here.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
* quiet - supress error messages.
*
* Outputs: A->g_msg_type Text description for screen display.
*
* A->g_addressee To whom is it addressed.
* Could be a specific station, alias, bulletin, etc.
* For telemetry metadata is is about this station,
* not being sent to it.
*
* A->g_message_subtype Subtype so caller might avoid replicating
* all the code to distinguish them.
*
* A->g_message_number Message number if any. Required for ack/rej.
*
* Description: An APRS message is a text string with a specifed addressee.
*
* It's a lot more complicated with different types of addressees
* and replies with acknowledgement or rejection.
*
* There is even a special case for telemetry metadata.
*
*
* Cases: :xxxxxxxxx:PARM. Telemetry metadata, parameter name
* :xxxxxxxxx:UNIT. Telemetry metadata, unit/label
* :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficents
* :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name
* :xxxxxxxxx:? Directed Station Query
* :xxxxxxxxx:ack Message acknowledged (received)
* :xxxxxxxxx:rej Message rejected (unable to accept)
*
* :xxxxxxxxx: ... Message with no message number.
* (Text may not contain the { character because
* it indicates beginning of optional message number.)
* :xxxxxxxxx: ... {num Message with message number.
*
*------------------------------------------------------------------*/
static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int quiet)
{
struct aprs_message_s {
char dti; /* : */
char addressee[9];
char colon; /* : */
char message[73]; /* 0-67 characters for message */
/* Optional { followed by 1-5 characters for message number */
/* If the first chracter is '?' it is a Directed Station Query. */
} *p;
char addressee[AX25_MAX_ADDR_LEN];
int i;
p = (struct aprs_message_s *)info;
strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type));
A->g_message_subtype = message_subtype_message; /* until found otherwise */
if (ilen < 11) {
if (! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("APRS Message must have a minimum of 11 characters for : 9 character addressee :\n");
}
A->g_message_subtype = message_subtype_invalid;
return;
}
if (p->colon != ':') {
if (! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("APRS Message must begin with : 9 character addressee :\n");
}
A->g_message_subtype = message_subtype_invalid;
return;
}
memset (addressee, 0, sizeof(addressee));
memcpy (addressee, p->addressee, sizeof(p->addressee)); // copy exactly 9 bytes.
/* Trim trailing spaces. */
i = strlen(addressee) - 1;
while (i >= 0 && addressee[i] == ' ') {
addressee[i--] = '\0';
}
strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee));
/*
* Special message formats contain telemetry metadata.
* It applies to the addressee, not the sender.
* Makes no sense to me that it would not apply to sender instead.
* Wouldn't the sender be describing his own data?
*
* I also don't understand the reasoning for putting this in a "message."
* Telemetry data always starts with "#" after the "T" data type indicator.
* Why not use other characters after the "T" for metadata?
*/
if (strncmp(p->message,"PARM.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Parameter Name Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_parm;
telemetry_name_message (addressee, p->message+5);
}
else if (strncmp(p->message,"UNIT.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_unit;
telemetry_unit_label_message (addressee, p->message+5);
}
else if (strncmp(p->message,"EQNS.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_eqns;
telemetry_coefficents_message (addressee, p->message+5, quiet);
}
else if (strncmp(p->message,"BITS.",5) == 0) {
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_bits;
telemetry_bit_sense_message (addressee, p->message+5, quiet);
}
/*
* If first character of message is "?" it is a query directed toward a specific station.
*/
else if (p->message[0] == '?') {
strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type));
A->g_message_subtype = message_subtype_directed_query;
aprs_directed_station_query (A, addressee, p->message+1, quiet);
}
/* ack or rej? Message number is required for these. */
else if (strncmp(p->message,"ack",3) == 0) {
strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "ACK message %s for \"%s\"", A->g_message_number, addressee);
A->g_message_subtype = message_subtype_ack;
}
else if (strncmp(p->message,"rej",3) == 0) {
strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "REJ message %s for \"%s\"", A->g_message_number, addressee);
A->g_message_subtype = message_subtype_ack;
}
/* message number is optional here. */
else {
char *pno = strchr(p->message, '{');
if (pno != NULL) {
strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number));
}
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message %s for \"%s\"", A->g_message_number, addressee);
A->g_message_subtype = message_subtype_message;
/* No location so don't use process_comment () */
strlcpy (A->g_comment, p->message, sizeof(A->g_comment));
}
}
/*------------------------------------------------------------------
*
* Function: aprs_object
*
* Purpose: Decode "Object Report Format"
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
*
* Description: Message has a 9 character object name which could be quite different than
* the source station.
*
* This can also be a weather report when the symbol id is '_'.
*
* Examples: ;WA2PNU *050457z4051.72N/07325.53W]BBS & FlexNet 145.070 MHz
*
* ;ActonEOC *070352z4229.20N/07125.95WoFire, EMS, Police, Heli-pad, Dial 911
*
* ;IRLPC494@*012112zI9*n*
*
*------------------------------------------------------------------*/
static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen)
{
struct aprs_object_s {
char dti; /* ; */
char name[9];
char live_killed; /* * for live or _ for killed */
char time_stamp[7];
position_t pos;
char comment[43]; /* First 7 bytes could be data extension. */
} *p;
struct aprs_compressed_object_s {
char dti; /* ; */
char name[9];
char live_killed; /* * for live or _ for killed */
char time_stamp[7];
compressed_position_t cpos;
char comment[40]; /* No data extension in this case. */
} *q;
time_t ts = 0;
int i;
p = (struct aprs_object_s *)info;
q = (struct aprs_compressed_object_s *)info;
//assert (sizeof(A->g_name) > sizeof(p->name));
memset (A->g_name, 0, sizeof(A->g_name));
memcpy (A->g_name, p->name, sizeof(p->name)); // copy exactly 9 bytes.
/* Trim trailing spaces. */
i = strlen(A->g_name) - 1;
while (i >= 0 && A->g_name[i] == ' ') {
A->g_name[i--] = '\0';
}
if (p->live_killed == '*')
strlcpy (A->g_msg_type, "Object", sizeof(A->g_msg_type));
else if (p->live_killed == '_')
strlcpy (A->g_msg_type, "Killed Object", sizeof(A->g_msg_type));
else
strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type));
ts = get_timestamp (A, p->time_stamp);
if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */
{
decode_position (A, &(p->pos));
if (A->g_symbol_code == '_') {
/* Symbol code indidates it is a weather report. */
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type));
weather_data (A, p->comment, TRUE);
}
else {
/* Regular object. */
data_extension_comment (A, p->comment);
}
}
else /* Compressed location. */
{
decode_compressed_position (A, &(q->cpos));
if (A->g_symbol_code == '_') {
/* Symbol code indidates it is a weather report. */
/* The spec doesn't explicitly mention the combination */
/* of weather report and object with compressed */
/* position. */
strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type));
weather_data (A, q->comment, FALSE);
}
else {
/* Regular position report. */
process_comment (A, q->comment, -1);
}
}
(void)(ts);
} /* end aprs_object */
/*------------------------------------------------------------------
*
* Function: aprs_item
*
* Purpose: Decode "Item Report Format"
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_object_name, A->g_lat, A->g_lon, A->g_symbol_table, A->g_symbol_code, A->g_speed_mph, A->g_course, A->g_altitude_ft.
*
* Description: An "item" is very much like an "object" except
*
* -- It doesn't have a time.
* -- Name is a VARIABLE length 3 to 9 instead of fixed 9.
* -- "live" indicator is ! rather than *
*
* Examples:
*
*------------------------------------------------------------------*/
static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen)
{
struct aprs_item_s {
char dti; /* ')' */
char name[10]; /* Actually variable length 3 - 9 bytes. */
/* DON'T refer to the rest of this structure; */
/* the offsets will be wrong! */
/* We make it 10 here so we don't get subscript out of bounds */
/* warning when looking for following '!' or '_' character. */
char live_killed__; /* ! for live or _ for killed */
position_t pos__;
char comment__[43]; /* First 7 bytes could be data extension. */
} *p;
struct aprs_compressed_item_s {
char dti; /* ')' */
char name[10]; /* Actually variable length 3 - 9 bytes. */
/* DON'T refer to the rest of this structure; */
/* the offsets will be wrong! */
char live_killed__; /* ! for live or _ for killed */
compressed_position_t cpos__;
char comment__[40]; /* No data extension in this case. */
} *q;
int i;
char *ppos;
p = (struct aprs_item_s *)info;
q = (struct aprs_compressed_item_s *)info;
(void)(q);
memset (A->g_name, 0, sizeof(A->g_name));
i = 0;
while (i < 9 && p->name[i] != '!' && p->name[i] != '_') {
A->g_name[i] = p->name[i];
i++;
A->g_name[i] = '\0';
}
if (p->name[i] == '!')
strlcpy (A->g_msg_type, "Item", sizeof(A->g_msg_type));
else if (p->name[i] == '_')
strlcpy (A->g_msg_type, "Killed Item", sizeof(A->g_msg_type));
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Item name too long or not followed by ! or _.\n");
}
strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type));
}
ppos = p->name + i + 1;
if (isdigit(*ppos)) /* Human-readable location. */
{
decode_position (A, (position_t*) ppos);
data_extension_comment (A, ppos + sizeof(position_t));
}
else /* Compressed location. */
{
decode_compressed_position (A, (compressed_position_t*)ppos);
process_comment (A, ppos + sizeof(compressed_position_t), -1);
}
}
/*------------------------------------------------------------------
*
* Function: aprs_station_capabilities
*
* Purpose: Decode "Station Capabilities"
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: ???
*
* Description: Each capability is a TOKEN or TOKEN=VALUE pair.
*
*
* Example:
*
* Bugs: Not implemented yet. Treat whole thing as comment.
*
*------------------------------------------------------------------*/
static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen)
{
strlcpy (A->g_msg_type, "Station Capabilities", sizeof(A->g_msg_type));
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy (A->g_comment, info+1, sizeof(A->g_comment));
} /* end aprs_station_capabilities */
/*------------------------------------------------------------------
*
* Function: aprs_status_report
*
* Purpose: Decode "Status Report"
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: ???
*
* Description: There are 3 different formats:
*
* (1) '>'
* 7 char - timestamp, DHM z format
* 0-55 char - status text
*
* (3) '>'
* 4 or 6 char - Maidenhead Locator
* 2 char - symbol table & code
* ' ' character
* 0-53 char - status text
*
* (2) '>'
* 0-62 char - status text
*
*
* In all cases, Beam heading and ERP can be at the
* very end by using '^' and two other characters.
*
*
* Examples from specification:
*
*
* >Net Control Center without timestamp.
* >092345zNet Control Center with timestamp.
* >IO91SX/G
* >IO91/G
* >IO91SX/- My house (Note the space at the start of the status text).
* >IO91SX/- ^B7 Meteor Scatter beam heading = 110 degrees, ERP = 490 watts.
*
*------------------------------------------------------------------*/
static void aprs_status_report (decode_aprs_t *A, char *info, int ilen)
{
struct aprs_status_time_s {
char dti; /* > */
char ztime[7]; /* Time stamp ddhhmmz */
char comment[55];
} *pt;
struct aprs_status_m4_s {
char dti; /* > */
char mhead4[4]; /* 4 character Maidenhead locator. */
char sym_table_id;
char symbol_code;
char space; /* Should be space after symbol code. */
char comment[54];
} *pm4;
struct aprs_status_m6_s {
char dti; /* > */
char mhead6[6]; /* 6 character Maidenhead locator. */
char sym_table_id;
char symbol_code;
char space; /* Should be space after symbol code. */
char comment[54];
} *pm6;
struct aprs_status_s {
char dti; /* > */
char comment[62];
} *ps;
strlcpy (A->g_msg_type, "Status Report", sizeof(A->g_msg_type));
pt = (struct aprs_status_time_s *)info;
pm4 = (struct aprs_status_m4_s *)info;
pm6 = (struct aprs_status_m6_s *)info;
ps = (struct aprs_status_s *)info;
/*
* Do we have format with time?
*/
if (isdigit(pt->ztime[0]) &&
isdigit(pt->ztime[1]) &&
isdigit(pt->ztime[2]) &&
isdigit(pt->ztime[3]) &&
isdigit(pt->ztime[4]) &&
isdigit(pt->ztime[5]) &&
pt->ztime[6] == 'z') {
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy (A->g_comment, pt->comment, sizeof(A->g_comment));
}
/*
* Do we have format with 6 character Maidenhead locator?
*/
else if (get_maidenhead (A, pm6->mhead6) == 6) {
memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead));
memcpy (A->g_maidenhead, pm6->mhead6, sizeof(pm6->mhead6));
A->g_symbol_table = pm6->sym_table_id;
A->g_symbol_code = pm6->symbol_code;
if (A->g_symbol_table != '/' && A->g_symbol_table != '\\'
&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", A->g_symbol_table);
}
A->g_symbol_table = '/';
}
if (pm6->space != ' ' && pm6->space != '\0') {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm6->space);
}
}
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy (A->g_comment, pm6->comment, sizeof(A->g_comment));
}
/*
* Do we have format with 4 character Maidenhead locator?
*/
else if (get_maidenhead (A, pm4->mhead4) == 4) {
memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead));
memcpy (A->g_maidenhead, pm4->mhead4, sizeof(pm4->mhead4));
A->g_symbol_table = pm4->sym_table_id;
A->g_symbol_code = pm4->symbol_code;
if (A->g_symbol_table != '/' && A->g_symbol_table != '\\'
&& ! isupper(A->g_symbol_table) && ! isdigit(A->g_symbol_table))
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid symbol table code '%c' not one of / \\ A-Z 0-9\n", A->g_symbol_table);
}
A->g_symbol_table = '/';
}
if (pm4->space != ' ' && pm4->space != '\0') {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Error: Found '%c' instead of space required after symbol code.\n", pm4->space);
}
}
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy (A->g_comment, pm4->comment, sizeof(A->g_comment));
}
/*
* Whole thing is status text.
*/
else {
strlcpy (A->g_comment, ps->comment, sizeof(A->g_comment));
}
/*
* Last 3 characters can represent beam heading and ERP.
*/
if (strlen(A->g_comment) >= 3) {
char *hp = A->g_comment + strlen(A->g_comment) - 3;
if (*hp == '^') {
char h = hp[1];
char p = hp[2];
int beam = -1;
int erp = -1;
if (h >= '0' && h <= '9') {
beam = (h - '0') * 10;
}
else if (h >= 'A' && h <= 'Z') {
beam = (h - 'A') * 10 + 100;
}
if (p >= '1' && p <= 'K') {
erp = (p - '0') * (p - '0') * 10;
}
// TODO (low): put result somewhere.
// could use A->g_directivity and need new variable for erp.
*hp = '\0';
(void)(beam);
(void)(erp);
}
}
} /* end aprs_status_report */
/*------------------------------------------------------------------
*
* Function: aprs_general_query
*
* Purpose: Decode "General Query" for all stations.
*
* Inputs: info - Pointer to Information field. First character should be "?".
* ilen - Information field length.
* quiet - suppress error messages.
*
* Outputs: A - Decoded packet structure
* A->g_query_type
* A->g_query_lat (optional)
* A->g_query_lon (optional)
* A->g_query_radius (optional)
*
* Description: Formats are:
*
* ?query?
* ?query?lat,long,radius
*
* 'query' is one of APRS, IGATE, WX, ...
* optional footprint, in degrees and miles radius, means only
* those in the specified circle should respond.
*
* Examples from specification, Chapter 15:
*
* ?APRS?
* ?APRS? 34.02,-117.15,0200
* ?IGATE?
*
*------------------------------------------------------------------*/
static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet)
{
char *q2;
char *p;
char *tok;
char stemp[256];
double lat, lon;
float radius;
strlcpy (A->g_msg_type, "General Query", sizeof(A->g_msg_type));
/*
* First make a copy because we will modify it while parsing it.
*/
strlcpy (stemp, info, sizeof(stemp));
/*
* There should be another "?" after the query type.
*/
q2 = strchr(stemp+1, '?');
if (q2 == NULL) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("General Query must have ? after the query type.\n");
}
return;
}
*q2 = '\0';
strlcpy (A->g_query_type, stemp+1, sizeof(A->g_query_type));
// TODO: remove debug
text_color_set(DW_COLOR_DEBUG);
dw_printf("DEBUG: General Query type = \"%s\"\n", A->g_query_type);
p = q2 + 1;
if (strlen(p) == 0) {
return;
}
/*
* Try to extract footprint.
* Spec says positive coordinate would be preceded by space
* and radius must be exactly 4 digits. We are more forgiving.
*/
tok = strsep(&p, ",");
if (tok != NULL) {
lat = atof(tok);
tok = strsep(&p, ",");
if (tok != NULL) {
lon = atof(tok);
tok = strsep(&p, ",");
if (tok != NULL) {
radius = atof(tok);
if (lat < -90 || lat > 90) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid latitude for General Query footprint.\n");
}
return;
}
if (lon < -180 || lon > 180) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid longitude for General Query footprint.\n");
}
return;
}
if (radius <= 0 || radius > 9999) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid radius for General Query footprint.\n");
}
return;
}
A->g_footprint_lat = lat;
A->g_footprint_lon = lon;
A->g_footprint_radius = radius;
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't get radius for General Query footprint.\n");
}
return;
}
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't get longitude for General Query footprint.\n");
}
return;
}
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't get latitude for General Query footprint.\n");
}
return;
}
// TODO: remove debug
text_color_set(DW_COLOR_DEBUG);
dw_printf("DEBUG: General Query footprint = %.6f %.6f %.2f\n", lat, lon, radius);
} /* end aprs_general_query */
/*------------------------------------------------------------------
*
* Function: aprs_directed_station_query
*
* Purpose: Decode "Directed Station Query" aimed at specific station.
* This is actually a special format of the more general "message."
*
* Inputs: addressee - To whom it is directed.
* Redundant because it is already in A->addressee.
*
* query - What's left over after ":addressee:?" in info part.
*
* quiet - suppress error messages.
*
* Outputs: A - Decoded packet structure
* A->g_query_type
* A->g_query_callsign (optional)
*
* Description: The caller has already removed the :addressee:? part so we are left
* with a query type of exactly 5 characters and optional "callsign
* of heard station."
*
* Examples from specification, Chapter 15. Our "query" argument.
*
* :KH2Z :?APRSD APRSD
* :KH2Z :?APRSHVN0QBF APRSHVN0QBF
* :KH2Z :?APRST APRST
* :KH2Z :?PING? PING?
*
* "PING?" contains "?" only to pad it out to exactly 5 characters.
*
*------------------------------------------------------------------*/
static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet)
{
//char query_type[20]; /* Does the query type always need to be exactly 5 characters? */
/* If not, how would we know where the extra optional information starts? */
//char callsign[AX25_MAX_ADDR_LEN];
//if (strlen(query) < 5) ...
} /* end aprs_directed_station_query */
/*------------------------------------------------------------------
*
* Function: aprs_Telemetry
*
* Purpose: Decode "Telemetry"
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
* quiet - suppress error messages.
*
* Outputs: A->g_telemetry
* A->g_comment
*
* Description: TBD.
*
* Examples from specification:
*
*
* TBD
*
*------------------------------------------------------------------*/
static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet)
{
strlcpy (A->g_msg_type, "Telemetry", sizeof(A->g_msg_type));
telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, sizeof(A->g_telemetry), A->g_comment, sizeof(A->g_comment));
} /* end aprs_telemetry */
/*------------------------------------------------------------------
*
* Function: aprs_raw_touch_tone
*
* Purpose: Decode raw touch tone datA->
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Description: Touch tone data is converted to a packet format
* so it can be conveyed to an application for processing.
*
* This is not part of the APRS standard.
*
*------------------------------------------------------------------*/
static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen)
{
strlcpy (A->g_msg_type, "Raw Touch Tone Data", sizeof(A->g_msg_type));
/* Just copy the info field without the message type. */
if (*info == '{')
strlcpy (A->g_comment, info+3, sizeof(A->g_comment));
else
strlcpy (A->g_comment, info+1, sizeof(A->g_comment));
} /* end aprs_raw_touch_tone */
/*------------------------------------------------------------------
*
* Function: aprs_morse_code
*
* Purpose: Convey message in packet format to be transmitted as
* Morse Code.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Description: This is not part of the APRS standard.
*
*------------------------------------------------------------------*/
static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen)
{
strlcpy (A->g_msg_type, "Morse Code Data", sizeof(A->g_msg_type));
/* Just copy the info field without the message type. */
if (*info == '{')
strlcpy (A->g_comment, info+3, sizeof(A->g_comment));
else
strlcpy (A->g_comment, info+1, sizeof(A->g_comment));
} /* end aprs_morse_code */
/*------------------------------------------------------------------
*
* Function: aprs_ll_pos_time
*
* Purpose: Decode weather report without a position.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_symbol_table, A->g_symbol_code.
*
* Description: Type identifier '_' is a weather report without a position.
*
*------------------------------------------------------------------*/
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *info, int ilen)
{
struct aprs_positionless_weather_s {
char dti; /* _ */
char time_stamp[8]; /* MDHM format */
char comment[99];
} *p;
strlcpy (A->g_msg_type, "Positionless Weather Report", sizeof(A->g_msg_type));
//time_t ts = 0;
p = (struct aprs_positionless_weather_s *)info;
// not yet implemented for 8 character format // ts = get_timestamp (A, p->time_stamp);
weather_data (A, p->comment, FALSE);
}
/*------------------------------------------------------------------
*
* Function: weather_data
*
* Purpose: Decode weather data in position or object report.
*
* Inputs: info - Pointer to first byte after location
* and symbol code.
*
* wind_prefix - Expecting leading wind info
* for human-readable location.
* (Currently ignored. We are very
* forgiving in what is accepted.)
* TODO: call this context instead and have 3 enumerated values.
*
* Global In: A->g_course - Wind info for compressed location.
* A->g_speed_mph
*
* Outputs: A->g_weather
*
* Description: Extract weather details and format into a comment.
*
* For human-readable locations, we expect wind direction
* and speed in a format like this: 999/999.
* For compressed location, this has already been
* processed and put in A->g_course and A->g_speed_mph.
* Otherwise, for positionless weather data, the
* wind is in the form c999s999.
*
* References: APRS Weather specification comments.
* http://aprs.org/aprs11/spec-wx.txt
*
* Weather updates to the spec.
* http://aprs.org/aprs12/weather-new.txt
*
* Examples:
*
* _10090556c220s004g005t077r000p000P000h50b09900wRSW
* !4903.50N/07201.75W_220/004g005t077r000p000P000h50b09900wRSW
* !4903.50N/07201.75W_220/004g005t077r000p000P000h50b.....wRSW
* @092345z4903.50N/07201.75W_220/004g005t-07r000p000P000h50b09900wRSW
* =/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW
* @092345z/5L!!<*e7_7P[g005t077r000p000P000h50b09900wRSW
* ;BRENDA *092345z4903.50N/07201.75W_220/004g005b0990
*
*------------------------------------------------------------------*/
static int getwdata (char **wpp, char ch, int dlen, float *val)
{
char stemp[8]; // larger than maximum dlen.
int i;
//dw_printf("debug: getwdata (wp=%p, ch=%c, dlen=%d)\n", *wpp, ch, dlen);
*val = G_UNKNOWN;
assert (dlen >= 2 && dlen <= 6);
if (**wpp != ch) {
/* Not specified element identifier. */
return (0);
}
if (strncmp((*wpp)+1, "......", dlen) == 0 || strncmp((*wpp)+1, " ", dlen) == 0) {
/* Field present, unknown value */
*wpp += 1 + dlen;
return (1);
}
/* Data field can contain digits, decimal point, leading negative. */
for (i=1; i<=dlen; i++) {
if ( ! isdigit((*wpp)[i]) && (*wpp)[i] != '.' && (*wpp)[i] != '-' ) {
return(0);
}
}
memset (stemp, 0, sizeof(stemp));
memcpy (stemp, (*wpp)+1, dlen);
*val = atof(stemp);
//dw_printf("debug: getwdata returning %f\n", *val);
*wpp += 1 + dlen;
return (1);
}
static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix)
{
int n;
float fval;
char *wp = wdata;
int keep_going;
if (wp[3] == '/')
{
if (sscanf (wp, "%3d", &n))
{
// Data Extension format.
// Fine point: Officially, should be values of 001-360.
// "000" or "..." or " " means unknown.
// In practice we see do see "000" here.
A->g_course = n;
}
if (sscanf (wp+4, "%3d", &n))
{
A->g_speed_mph = DW_KNOTS_TO_MPH(n); /* yes, in knots */
}
wp += 7;
}
else if ( A->g_speed_mph == G_UNKNOWN) {
if ( ! getwdata (&wp, 'c', 3, &A->g_course)) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Didn't find wind direction in form c999.\n");
}
}
if ( ! getwdata (&wp, 's', 3, &A->g_speed_mph)) { /* MPH here */
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Didn't find wind speed in form s999.\n");
}
}
}
// At this point, we should have the wind direction and speed
// from one of three methods.
if (A->g_speed_mph != G_UNKNOWN) {
snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph", A->g_speed_mph);
if (A->g_course != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", direction %.0f", A->g_course);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
/* We don't want this to show up on the location line. */
A->g_speed_mph = G_UNKNOWN;
A->g_course = G_UNKNOWN;
/*
* After the mandatory wind direction and speed (in 1 of 3 formats), the
* next two must be in fixed positions:
* - gust (peak in mph last 5 minutes)
* - temperature, degrees F, can be negative e.g. -01
*/
if (getwdata (&wp, 'g', 3, &fval)) {
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", gust %.0f", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Didn't find wind gust in form g999.\n");
}
}
if (getwdata (&wp, 't', 3, &fval)) {
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", temperature %.0f", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Didn't find temperature in form t999.\n");
}
}
/*
* Now pick out other optional fields in any order.
*/
keep_going = 1;
while (keep_going) {
if (getwdata (&wp, 'r', 3, &fval)) {
/* r = rainfall, 1/100 inch, last hour */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last hour", fval / 100.);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'p', 3, &fval)) {
/* p = rainfall, 1/100 inch, last 24 hours */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", rain %.2f in last 24 hours", fval / 100.);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'P', 3, &fval)) {
/* P = rainfall, 1/100 inch, since midnight */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", rain %.2f since midnight", fval / 100.);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'h', 2, &fval)) {
/* h = humidity %, 00 means 100% */
if (fval != G_UNKNOWN) {
char ctemp[30];
if (fval == 0) fval = 100;
snprintf (ctemp, sizeof(ctemp), ", humidity %.0f", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'b', 5, &fval)) {
/* b = barometric presure (tenths millibars / tenths of hPascal) */
/* Here, display as inches of mercury. */
if (fval != G_UNKNOWN) {
char ctemp[40];
fval = DW_MBAR_TO_INHG(fval * 0.1);
snprintf (ctemp, sizeof(ctemp), ", barometer %.2f", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'L', 3, &fval)) {
/* L = Luminosity, watts/ sq meter, 000-999 */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'l', 3, &fval)) {
/* l = Luminosity, watts/ sq meter, 1000-1999 */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", %.0f watts/m^2", fval + 1000);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 's', 3, &fval)) {
/* s = Snowfall in last 24 hours, inches */
/* Data can have decimal point so we don't have to worry about scaling. */
/* 's' is also used by wind speed but that must be in a fixed */
/* position in the message so there is no confusion. */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", %.1f snow in 24 hours", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 's', 3, &fval)) {
/* # = Raw rain counter */
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", raw rain counter %.f", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
else if (getwdata (&wp, 'X', 3, &fval)) {
/* X = Nuclear Radiation. */
/* Encoded as two significant digits and order of magnitude */
/* like resistor color code. */
// TODO: decode this properly
if (fval != G_UNKNOWN) {
char ctemp[40];
snprintf (ctemp, sizeof(ctemp), ", nuclear Radiation %.f", fval);
strlcat (A->g_weather, ctemp, sizeof(A->g_weather));
}
}
// TODO: add new flood level, battery voltage, etc.
else {
keep_going = 0;
}
}
/*
* We should be left over with:
* - one character for software.
* - two to four characters for weather station type.
* Examples: tU2k, wRSW
*
* But few people follow the protocol spec here. Instead more often we see things like:
* sunny/WX
* / {UIV32N}
*/
strlcat (A->g_weather, ", \"", sizeof(A->g_weather));
strlcat (A->g_weather, wp, sizeof(A->g_weather));
/*
* Drop any CR / LF character at the end.
*/
n = strlen(A->g_weather);
if (n >= 1 && A->g_weather[n-1] == '\n') {
A->g_weather[n-1] = '\0';
}
n = strlen(A->g_weather);
if (n >= 1 && A->g_weather[n-1] == '\r') {
A->g_weather[n-1] = '\0';
}
strlcat (A->g_weather, "\"", sizeof(A->g_weather));
return;
} /* end weather_data */
/*------------------------------------------------------------------
*
* Function: aprs_ultimeter
*
* Purpose: Decode Peet Brothers ULTIMETER Weather Station Info.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_weather
*
* Description: http://www.peetbros.com/shop/custom.aspx?recid=7
*
* There are two different data formats in use.
* One begins with $ULTW and is called "Packet Mode." Example:
*
* $ULTW009400DC00E21B8027730008890200010309001E02100000004C
*
* The other begins with !! and is called "logging mode." Example:
*
* !!000000A600B50000----------------001C01D500000017
*
*
* Bugs: Implementation is incomplete.
* The example shown in the APRS protocol spec has a couple "----"
* fields in the $ULTW message. This should be rewritten to handle
* each field separately to deal with missing pieces.
*
*------------------------------------------------------------------*/
static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen)
{
// Header = $ULTW
// Data Fields
short h_windpeak; // 1. Wind Speed Peak over last 5 min. (0.1 kph)
short h_wdir; // 2. Wind Direction of Wind Speed Peak (0-255)
short h_otemp; // 3. Current Outdoor Temp (0.1 deg F)
short h_totrain; // 4. Rain Long Term Total (0.01 in.)
short h_baro; // 5. Current Barometer (0.1 mbar)
short h_barodelta; // 6. Barometer Delta Value(0.1 mbar)
short h_barocorrl; // 7. Barometer Corr. Factor(LSW)
short h_barocorrm; // 8. Barometer Corr. Factor(MSW)
short h_ohumid; // 9. Current Outdoor Humidity (0.1%)
short h_date; // 10. Date (day of year)
short h_time; // 11. Time (minute of day)
short h_raintoday; // 12. Today's Rain Total (0.01 inches)*
short h_windave; // 13. 5 Minute Wind Speed Average (0.1kph)*
// Carriage Return & Line Feed
// *Some instruments may not include field 13, some may
// not include 12 or 13.
// Total size: 44, 48 or 52 characters (hex digits) +
// header, carriage return and line feed.
int n;
strlcpy (A->g_msg_type, "Ultimeter", sizeof(A->g_msg_type));
if (*info == '$')
{
n = sscanf (info+5, "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx",
&h_windpeak,
&h_wdir,
&h_otemp,
&h_totrain,
&h_baro,
&h_barodelta,
&h_barocorrl,
&h_barocorrm,
&h_ohumid,
&h_date,
&h_time,
&h_raintoday, // not on some models.
&h_windave); // not on some models.
if (n >= 11 && n <= 13) {
float windpeak, wdir, otemp, baro, ohumid;
windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
wdir = (h_wdir & 0xff) * 360. / 256.;
otemp = h_otemp * 0.1;
baro = DW_MBAR_TO_INHG(h_baro * 0.1);
ohumid = h_ohumid * 0.1;
snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f",
windpeak, wdir, otemp, baro, ohumid);
}
}
// Header = !!
// Data Fields
// 1. Wind Speed (0.1 kph)
// 2. Wind Direction (0-255)
// 3. Outdoor Temp (0.1 deg F)
// 4. Rain* Long Term Total (0.01 inches)
// 5. Barometer (0.1 mbar) [ can be ---- ]
// 6. Indoor Temp (0.1 deg F) [ can be ---- ]
// 7. Outdoor Humidity (0.1%) [ can be ---- ]
// 8. Indoor Humidity (0.1%) [ can be ---- ]
// 9. Date (day of year)
// 10. Time (minute of day)
// 11. Today's Rain Total (0.01 inches)*
// 12. 1 Minute Wind Speed Average (0.1kph)*
// Carriage Return & Line Feed
//
// *Some instruments may not include field 12, some may not include 11 or 12.
// Total size: 40, 44 or 48 characters (hex digits) + header, carriage return and line feed
if (*info == '!')
{
n = sscanf (info+2, "%4hx%4hx%4hx%4hx",
&h_windpeak,
&h_wdir,
&h_otemp,
&h_totrain);
if (n == 4) {
float windpeak, wdir, otemp;
windpeak = DW_KM_TO_MILES(h_windpeak * 0.1);
wdir = (h_wdir & 0xff) * 360. / 256.;
otemp = h_otemp * 0.1;
snprintf (A->g_weather, sizeof(A->g_weather), "wind %.1f mph, direction %.0f, temperature %.1f\n",
windpeak, wdir, otemp);
}
}
} /* end aprs_ultimeter */
/*------------------------------------------------------------------
*
* Function: third_party_header
*
* Purpose: Decode packet from a third party network.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
*
* Outputs: A->g_comment
*
* Description:
*
*------------------------------------------------------------------*/
static void third_party_header (decode_aprs_t *A, char *info, int ilen)
{
strlcpy (A->g_msg_type, "Third Party Header", sizeof(A->g_msg_type));
/* more later? */
} /* end third_party_header */
/*------------------------------------------------------------------
*
* Function: decode_position
*
* Purpose: Decode the position & symbol information common to many message formats.
*
* Inputs: ppos - Pointer to position & symbol fields.
*
* Returns: A->g_lat
* A->g_lon
* A->g_symbol_table
* A->g_symbol_code
*
* Description: This provides resolution of about 60 feet.
* This can be improved by using !DAO! in the comment.
*
*------------------------------------------------------------------*/
static void decode_position (decode_aprs_t *A, position_t *ppos)
{
A->g_lat = get_latitude_8 (ppos->lat, A->g_quiet);
A->g_lon = get_longitude_9 (ppos->lon, A->g_quiet);
A->g_symbol_table = ppos->sym_table_id;
A->g_symbol_code = ppos->symbol_code;
}
/*------------------------------------------------------------------
*
* Function: decode_compressed_position
*
* Purpose: Decode the compressed position & symbol information common to many message formats.
*
* Inputs: ppos - Pointer to compressed position & symbol fields.
*
* Returns: A->g_lat
* A->g_lon
* A->g_symbol_table
* A->g_symbol_code
*
* One of the following:
* A->g_course & A->g_speeed
* A->g_altitude_ft
* A->g_range
*
* Description: The compressed position provides resolution of around ???
* This also includes course/speed or altitude.
*
* It contains 13 bytes of the format:
*
* symbol table /, \, or overlay A-Z, a-j is mapped into 0-9
*
* yyyy Latitude, base 91.
*
* xxxx Longitude, base 91.
*
* symbol code
*
* cs Course/Speed or altitude.
*
* t Various "type" info.
*
*------------------------------------------------------------------*/
static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *pcpos)
{
if (isdigit91(pcpos->y[0]) && isdigit91(pcpos->y[1]) && isdigit91(pcpos->y[2]) && isdigit91(pcpos->y[3]))
{
A->g_lat = 90 - ((pcpos->y[0]-33)*91*91*91 + (pcpos->y[1]-33)*91*91 + (pcpos->y[2]-33)*91 + (pcpos->y[3]-33)) / 380926.0;
}
else
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in compressed latitude. Must be in range of '!' to '{'.\n");
}
A->g_lat = G_UNKNOWN;
}
if (isdigit91(pcpos->x[0]) && isdigit91(pcpos->x[1]) && isdigit91(pcpos->x[2]) && isdigit91(pcpos->x[3]))
{
A->g_lon = -180 + ((pcpos->x[0]-33)*91*91*91 + (pcpos->x[1]-33)*91*91 + (pcpos->x[2]-33)*91 + (pcpos->x[3]-33)) / 190463.0;
}
else
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in compressed longitude. Must be in range of '!' to '{'.\n");
}
A->g_lon = G_UNKNOWN;
}
if (pcpos->sym_table_id == '/' || pcpos->sym_table_id == '\\' || isupper((int)(pcpos->sym_table_id))) {
/* primary or alternate or alternate with upper case overlay. */
A->g_symbol_table = pcpos->sym_table_id;
}
else if (pcpos->sym_table_id >= 'a' && pcpos->sym_table_id <= 'j') {
/* Lower case a-j are used to represent overlay characters 0-9 */
/* because a digit here would mean normal (non-compressed) location. */
A->g_symbol_table = pcpos->sym_table_id - 'a' + '0';
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid symbol table id for compressed position.\n");
}
A->g_symbol_table = '/';
}
A->g_symbol_code = pcpos->symbol_code;
if (pcpos->c == ' ') {
; /* ignore other two bytes */
}
else if (((pcpos->t - 33) & 0x18) == 0x10) {
A->g_altitude_ft = pow(1.002, (pcpos->c - 33) * 91 + pcpos->s - 33);
}
else if (pcpos->c == '{')
{
A->g_range = 2.0 * pow(1.08, pcpos->s - 33);
}
else if (pcpos->c >= '!' && pcpos->c <= 'z')
{
/* For a weather station, this is wind information. */
A->g_course = (pcpos->c - 33) * 4;
A->g_speed_mph = DW_KNOTS_TO_MPH(pow(1.08, pcpos->s - 33) - 1.0);
}
}
/*------------------------------------------------------------------
*
* Function: get_latitude_8
*
* Purpose: Convert 8 byte latitude encoding to degrees.
*
* Inputs: plat - Pointer to first byte.
*
* Returns: Double precision value in degrees. Negative for South.
*
* Description: Latitude is expressed as a fixed 8-character field, in degrees
* and decimal minutes (to two decimal places), followed by the
* letter N for north or S for south.
* The protocol spec specifies upper case but I've seen lower
* case so this will accept either one.
* Latitude degrees are in the range 00 to 90. Latitude minutes
* are expressed as whole minutes and hundredths of a minute,
* separated by a decimal point.
* For example:
* 4903.50N is 49 degrees 3 minutes 30 seconds north.
* In generic format examples, the latitude is shown as the 8-character
* string ddmm.hhN (i.e. degrees, minutes and hundredths of a minute north).
*
* Bug: We don't properly deal with position ambiguity where trailing
* digits might be replaced by spaces. We simply treat them like zeros.
*
* Errors: Return G_UNKNOWN for any type of error.
*
* Should probably print an error message.
*
*------------------------------------------------------------------*/
double get_latitude_8 (char *p, int quiet)
{
struct lat_s {
unsigned char deg[2];
unsigned char minn[2];
char dot;
unsigned char hmin[2];
char ns;
} *plat;
double result = 0;
plat = (void *)p;
if (isdigit(plat->deg[0]))
result += ((plat->deg[0]) - '0') * 10;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for tens of degrees.\n", plat->deg[0]);
}
return (G_UNKNOWN);
}
if (isdigit(plat->deg[1]))
result += ((plat->deg[1]) - '0') * 1;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for degrees.\n", plat->deg[1]);
}
return (G_UNKNOWN);
}
if (plat->minn[0] >= '0' && plat->minn[0] <= '5')
result += ((plat->minn[0]) - '0') * (10. / 60.);
else if (plat->minn[0] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in latitude. Found '%c' when expecting 0-5 for tens of minutes.\n", plat->minn[0]);
}
return (G_UNKNOWN);
}
if (isdigit(plat->minn[1]))
result += ((plat->minn[1]) - '0') * (1. / 60.);
else if (plat->minn[1] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for minutes.\n", plat->minn[1]);
}
return (G_UNKNOWN);
}
if (plat->dot != '.') {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unexpected character \"%c\" found where period expected in latitude.\n", plat->dot);
}
return (G_UNKNOWN);
}
if (isdigit(plat->hmin[0]))
result += ((plat->hmin[0]) - '0') * (0.1 / 60.);
else if (plat->hmin[0] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for tenths of minutes.\n", plat->hmin[0]);
}
return (G_UNKNOWN);
}
if (isdigit(plat->hmin[1]))
result += ((plat->hmin[1]) - '0') * (0.01 / 60.);
else if (plat->hmin[1] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in latitude. Found '%c' when expecting 0-9 for hundredths of minutes.\n", plat->hmin[1]);
}
return (G_UNKNOWN);
}
// The spec requires upper case for hemisphere. Accept lower case but warn.
if (plat->ns == 'N') {
return (result);
}
else if (plat->ns == 'n') {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case n found for latitude hemisphere. Specification requires upper case N or S.\n");
}
return (result);
}
else if (plat->ns == 'S') {
return ( - result);
}
else if (plat->ns == 's') {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case s found for latitude hemisphere. Specification requires upper case N or S.\n");
}
return ( - result);
}
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);
}
return (G_UNKNOWN);
}
}
/*------------------------------------------------------------------
*
* Function: get_longitude_9
*
* Purpose: Convert 9 byte longitude encoding to degrees.
*
* Inputs: plat - Pointer to first byte.
*
* Returns: Double precision value in degrees. Negative for West.
*
* Description: Longitude is expressed as a fixed 9-character field, in degrees and
* decimal minutes (to two decimal places), followed by the letter E
* for east or W for west.
* Longitude degrees are in the range 000 to 180. Longitude minutes are
* expressed as whole minutes and hundredths of a minute, separated by a
* decimal point.
* For example:
* 07201.75W is 72 degrees 1 minute 45 seconds west.
* In generic format examples, the longitude is shown as the 9-character
* string dddmm.hhW (i.e. degrees, minutes and hundredths of a minute west).
*
* Bug: We don't properly deal with position ambiguity where trailing
* digits might be replaced by spaces. We simply treat them like zeros.
*
* Errors: Return G_UNKNOWN for any type of error.
*
* Example:
*
*------------------------------------------------------------------*/
double get_longitude_9 (char *p, int quiet)
{
struct lat_s {
unsigned char deg[3];
unsigned char minn[2];
char dot;
unsigned char hmin[2];
char ew;
} *plon;
double result = 0;
plon = (void *)p;
if (plon->deg[0] == '0' || plon->deg[0] == '1')
result += ((plon->deg[0]) - '0') * 100;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0 or 1 for hundreds of degrees.\n", plon->deg[0]);
}
return (G_UNKNOWN);
}
if (isdigit(plon->deg[1]))
result += ((plon->deg[1]) - '0') * 10;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for tens of degrees.\n", plon->deg[1]);
}
return (G_UNKNOWN);
}
if (isdigit(plon->deg[2]))
result += ((plon->deg[2]) - '0') * 1;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for degrees.\n", plon->deg[2]);
}
return (G_UNKNOWN);
}
if (plon->minn[0] >= '0' && plon->minn[0] <= '5')
result += ((plon->minn[0]) - '0') * (10. / 60.);
else if (plon->minn[0] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0-5 for tens of minutes.\n", plon->minn[0]);
}
return (G_UNKNOWN);
}
if (isdigit(plon->minn[1]))
result += ((plon->minn[1]) - '0') * (1. / 60.);
else if (plon->minn[1] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for minutes.\n", plon->minn[1]);
}
return (G_UNKNOWN);
}
if (plon->dot != '.') {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unexpected character \"%c\" found where period expected in longitude.\n", plon->dot);
}
return (G_UNKNOWN);
}
if (isdigit(plon->hmin[0]))
result += ((plon->hmin[0]) - '0') * (0.1 / 60.);
else if (plon->hmin[0] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for tenths of minutes.\n", plon->hmin[0]);
}
return (G_UNKNOWN);
}
if (isdigit(plon->hmin[1]))
result += ((plon->hmin[1]) - '0') * (0.01 / 60.);
else if (plon->hmin[1] == ' ')
;
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Invalid character in longitude. Found '%c' when expecting 0-9 for hundredths of minutes.\n", plon->hmin[1]);
}
return (G_UNKNOWN);
}
// The spec requires upper case for hemisphere. Accept lower case but warn.
if (plon->ew == 'E') {
return (result);
}
else if (plon->ew == 'e') {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case e found for longitude hemisphere. Specification requires upper case E or W.\n");
}
return (result);
}
else if (plon->ew == 'W') {
return ( - result);
}
else if (plon->ew == 'w') {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case w found for longitude hemisphere. Specification requires upper case E or W.\n");
}
return ( - result);
}
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Error: '%c' found for longitude hemisphere. Specification requires upper case E or W.\n", plon->ew);
}
return (G_UNKNOWN);
}
}
/*------------------------------------------------------------------
*
* Function: get_timestamp
*
* Purpose: Convert 7 byte timestamp to unix time value.
*
* Inputs: p - Pointer to first byte.
*
* Returns: time_t data type. (UTC)
*
* Description:
*
* Day/Hours/Minutes (DHM) format is a fixed 7-character field, consisting of
* a 6-digit day/time group followed by a single time indicator character (z or
* /). The day/time group consists of a two-digit day-of-the-month (01-31) and
* a four-digit time in hours and minutes.
* Times can be expressed in zulu (UTC/GMT) or local time. For example:
*
* 092345z is 2345 hours zulu time on the 9th day of the month.
* 092345/ is 2345 hours local time on the 9th day of the month.
*
* It is recommended that future APRS implementations only transmit zulu
* format on the air.
*
* Note: The time in Status Reports may only be in zulu format.
*
* Hours/Minutes/Seconds (HMS) format is a fixed 7-character field,
* consisting of a 6-digit time in hours, minutes and seconds, followed by the h
* time-indicator character. For example:
*
* 234517h is 23 hours 45 minutes and 17 seconds zulu.
*
* Note: This format may not be used in Status Reports.
*
* Month/Day/Hours/Minutes (MDHM) format is a fixed 8-character field,
* consisting of the month (01-12) and day-of-the-month (01-31), followed by
* the time in hours and minutes zulu. For example:
*
* 10092345 is 23 hours 45 minutes zulu on October 9th.
*
* This format is only used in reports from stand-alone "positionless" weather
* stations (i.e. reports that do not contain station position information).
*
*
* Bugs: Local time not implemented yet.
* 8 character form not implemented yet.
*
* Boundary conditions are not handled properly.
* For example, suppose it is 00:00:03 on January 1.
* We receive a timestamp of 23:59:58 (which was December 31).
* If we simply replace the time, and leave the current date alone,
* the result is about a day into the future.
*
*
* Example:
*
*------------------------------------------------------------------*/
time_t get_timestamp (decode_aprs_t *A, char *p)
{
struct dhm_s {
char day[2];
char hours[2];
char minutes[2];
char tic; /* Time indicator character. */
/* z = UTC. */
/* / = local - not implemented yet. */
} *pdhm;
struct hms_s {
char hours[2];
char minutes[2];
char seconds[2];
char tic; /* Time indicator character. */
/* h = UTC. */
} *phms;
struct tm *ptm;
time_t ts;
ts = time(NULL);
ptm = gmtime(&ts);
pdhm = (void *)p;
phms = (void *)p;
if (pdhm->tic == 'z' || pdhm->tic == '/') /* Wrong! */
{
int j;
j = (pdhm->day[0] - '0') * 10 + pdhm->day[1] - '0';
//text_color_set(DW_COLOR_DECODED);
//dw_printf("Changing day from %d to %d\n", ptm->tm_mday, j);
ptm->tm_mday = j;
j = (pdhm->hours[0] - '0') * 10 + pdhm->hours[1] - '0';
//dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
ptm->tm_hour = j;
j = (pdhm->minutes[0] - '0') * 10 + pdhm->minutes[1] - '0';
//dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
ptm->tm_min = j;
}
else if (phms->tic == 'h')
{
int j;
j = (phms->hours[0] - '0') * 10 + phms->hours[1] - '0';
//text_color_set(DW_COLOR_DECODED);
//dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
ptm->tm_hour = j;
j = (phms->minutes[0] - '0') * 10 + phms->minutes[1] - '0';
//dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
ptm->tm_min = j;
j = (phms->seconds[0] - '0') * 10 + phms->seconds[1] - '0';
//dw_printf("%sChanging seconds from %d to %d\n", ptm->tm_sec, j);
ptm->tm_sec = j;
}
return (mktime(ptm));
}
/*------------------------------------------------------------------
*
* Function: get_maidenhead
*
* Purpose: See if we have a maidenhead locator.
*
* Inputs: p - Pointer to first byte.
*
* Returns: 0 = not found.
* 4 = possible 4 character locator found.
* 6 = possible 6 character locator found.
*
* It is not stored anywhere or processed.
*
* Description:
*
* The maidenhead locator system is sometimes used as a more compact,
* and less precise, alternative to numeric latitude and longitude.
*
* It is composed of:
* a pair of letters in range A to R.
* a pair of digits in range of 0 to 9.
* a pair of letters in range of A to X.
*
* The APRS spec says that all letters must be transmitted in upper case.
*
*
* Examples from APRS spec:
*
* IO91SX
* IO91
*
*
*------------------------------------------------------------------*/
int get_maidenhead (decode_aprs_t *A, char *p)
{
if (toupper(p[0]) >= 'A' && toupper(p[0]) <= 'R' &&
toupper(p[1]) >= 'A' && toupper(p[1]) <= 'R' &&
isdigit(p[2]) && isdigit(p[3])) {
/* We have 4 characters matching the rule. */
if (islower(p[0]) || islower(p[1])) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n");
}
}
if (toupper(p[4]) >= 'A' && toupper(p[4]) <= 'X' &&
toupper(p[5]) >= 'A' && toupper(p[5]) <= 'X') {
/* We have 6 characters matching the rule. */
if (islower(p[4]) || islower(p[5])) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n");
}
}
return 6;
}
return 4;
}
return 0;
}
/*------------------------------------------------------------------
*
* Function: data_extension_comment
*
* Purpose: A fixed length 7-byte field may follow APRS position datA->
*
* Inputs: pdext - Pointer to optional data extension and comment.
*
* Returns: true if a data extension was found.
*
* Outputs: One or more of the following, depending the data found:
*
* A->g_course
* A->g_speed_mph
* A->g_power
* A->g_height
* A->g_gain
* A->g_directivity
* A->g_range
*
* Anything left over will be put in
*
* A->g_comment
*
* Description:
*
*
*
*------------------------------------------------------------------*/
const char *dir[9] = { "omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N" };
static int data_extension_comment (decode_aprs_t *A, char *pdext)
{
int n;
if (strlen(pdext) < 7) {
strlcpy (A->g_comment, pdext, sizeof(A->g_comment));
return 0;
}
/* Tyy/Cxx - Area object descriptor. */
if (pdext[0] == 'T' &&
pdext[3] == '/' &&
pdext[4] == 'C')
{
/* not decoded at this time */
process_comment (A, pdext+7, -1);
return 1;
}
/* CSE/SPD */
/* For a weather station (symbol code _) this is wind. */
/* For others, it would be course and speed. */
if (pdext[3] == '/')
{
if (sscanf (pdext, "%3d", &n))
{
A->g_course = n;
}
if (sscanf (pdext+4, "%3d", &n))
{
A->g_speed_mph = DW_KNOTS_TO_MPH(n);
}
/* Bearing and Number/Range/Quality? */
if (pdext[7] == '/' && pdext[11] == '/')
{
process_comment (A, pdext + 7 + 8, -1);
}
else {
process_comment (A, pdext+7, -1);
}
return 1;
}
/* check for Station power, height, gain. */
if (strncmp(pdext, "PHG", 3) == 0)
{
A->g_power = (pdext[3] - '0') * (pdext[3] - '0');
A->g_height = (1 << (pdext[4] - '0')) * 10;
A->g_gain = pdext[5] - '0';
if (pdext[6] >= '0' && pdext[6] <= '8') {
strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity));
}
process_comment (A, pdext+7, -1);
return 1;
}
/* check for precalculated radio range. */
if (strncmp(pdext, "RNG", 3) == 0)
{
if (sscanf (pdext+3, "%4d", &n))
{
A->g_range = n;
}
process_comment (A, pdext+7, -1);
return 1;
}
/* DF signal strength, */
if (strncmp(pdext, "DFS", 3) == 0)
{
//A->g_strength = pdext[3] - '0';
A->g_height = (1 << (pdext[4] - '0')) * 10;
A->g_gain = pdext[5] - '0';
if (pdext[6] >= '0' && pdext[6] <= '8') {
strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity));
}
process_comment (A, pdext+7, -1);
return 1;
}
process_comment (A, pdext, -1);
return 0;
}
/*------------------------------------------------------------------
*
* Function: decode_tocall
*
* Purpose: Extract application from the destination.
*
* Inputs: dest - Destination address.
* Don't care if SSID is present or not.
*
* Outputs: A->g_mfr
*
* Description: For maximum flexibility, we will read the
* data file at run time rather than compiling it in.
*
* For the most recent version, download from:
*
* http://www.aprs.org/aprs11/tocalls.txt
*
* Windows version: File must be in current working directory.
*
* Linux version: Search order is current working directory
* then /usr/share/direwolf directory.
*
*------------------------------------------------------------------*/
// If I was more ambitious, this would dynamically allocate enough
// storage based on the file contents. Just stick in a constant for
// now. This takes an insignificant amount of space and
// I don't anticipate tocalls.txt growing that quickly.
// Version 1.4 - add message if too small instead of silently ignoring the rest.
// Dec. 2016 tocalls.txt has 153 destination addresses.
#define MAX_TOCALLS 200
static struct tocalls_s {
unsigned char len;
char prefix[7];
char *description;
} tocalls[MAX_TOCALLS];
static int num_tocalls = 0;
// Make sure the array is null terminated.
static const char *search_locations[] = {
(const char *) "tocalls.txt",
#ifndef __WIN32__
(const char *) "/usr/share/direwolf/tocalls.txt",
(const char *) "/usr/local/share/direwolf/tocalls.txt",
#endif
(const char *) NULL
};
static int tocall_cmp (const void *px, const void *py)
{
const struct tocalls_s *x = (struct tocalls_s *)px;
const struct tocalls_s *y = (struct tocalls_s *)py;
if (x->len != y->len) return (y->len - x->len);
return (strcmp(x->prefix, y->prefix));
}
static void decode_tocall (decode_aprs_t *A, char *dest)
{
FILE *fp = 0;
int n = 0;
static int first_time = 1;
char stuff[100];
char *p = NULL;
char *r = NULL;
//dw_printf("debug: decode_tocall(\"%s\")\n", dest);
/*
* Extract the calls and descriptions from the file.
*
* Use only lines with exactly these formats:
*
* APN Network nodes, digis, etc
* APWWxx APRSISCE win32 version
* | | |
* 00000000001111111111
* 01234567890123456789...
*
* Matching will be with only leading upper case and digits.
*/
// TODO: Look for this in multiple locations.
// For example, if application was installed in /usr/local/bin,
// we might want to put this in /usr/local/share/aprs
// If search strategy changes, be sure to keep symbols_init in sync.
if (first_time) {
n = 0;
fp = NULL;
do {
if(search_locations[n] == NULL) break;
fp = fopen(search_locations[n++], "r");
} while (fp == NULL);
if (fp != NULL) {
while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) {
p = stuff + strlen(stuff) - 1;
while (p >= stuff && (*p == '\r' || *p == '\n')) {
*p-- = '\0';
}
// dw_printf("debug: %s\n", stuff);
if (stuff[0] == ' ' &&
stuff[4] == ' ' &&
stuff[5] == ' ' &&
stuff[6] == 'A' &&
stuff[7] == 'P' &&
stuff[12] == ' ' &&
stuff[13] == ' ' ) {
p = stuff + 6;
r = tocalls[num_tocalls].prefix;
while (isupper((int)(*p)) || isdigit((int)(*p))) {
*r++ = *p++;
}
*r = '\0';
if (strlen(tocalls[num_tocalls].prefix) > 2) {
tocalls[num_tocalls].description = strdup(stuff+14);
tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
// dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
num_tocalls++;
}
}
else if (stuff[0] == ' ' &&
stuff[1] == 'A' &&
stuff[2] == 'P' &&
isupper((int)(stuff[3])) &&
stuff[4] == ' ' &&
stuff[5] == ' ' &&
stuff[6] == ' ' &&
stuff[12] == ' ' &&
stuff[13] == ' ' ) {
p = stuff + 1;
r = tocalls[num_tocalls].prefix;
while (isupper((int)(*p)) || isdigit((int)(*p))) {
*r++ = *p++;
}
*r = '\0';
if (strlen(tocalls[num_tocalls].prefix) > 2) {
tocalls[num_tocalls].description = strdup(stuff+14);
tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
// dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
num_tocalls++;
}
}
if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some.
text_color_set(DW_COLOR_ERROR);
dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS);
}
}
fclose(fp);
/*
* Sort by decreasing length so the search will go
* from most specific to least specific.
* Example: APY350 or APY008 would match those specific
* models before getting to the more generic APY.
*/
#if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp);
#else
qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), (__compar_fn_t)tocall_cmp);
#endif
}
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Could not open 'tocalls.txt'.\n");
dw_printf("System types in the destination field will not be decoded.\n");
}
}
first_time = 0;
//for (n=0; n '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description);
//}
}
for (n=0; ng_mfr, tocalls[n].description, sizeof(A->g_mfr));
return;
}
}
} /* end decode_tocall */
/*------------------------------------------------------------------
*
* Function: substr_se
*
* Purpose: Extract substring given start and end+1 offset.
*
* Inputs: src - Source string
*
* start - Start offset.
*
* endp1 - End offset+1 for ease of use with regexec result.
*
* Outputs: dest - Destination for substring.
*
*------------------------------------------------------------------*/
// TODO: potential for buffer overflow here.
static void substr_se (char *dest, const char *src, int start, int endp1)
{
int len = endp1 - start;
if (start < 0 || endp1 < 0 || len <= 0) {
dest[0] = '\0';
return;
}
memcpy (dest, src + start, len);
dest[len] = '\0';
} /* end substr_se */
/*------------------------------------------------------------------
*
* Function: process_comment
*
* Purpose: Extract optional items from the comment.
*
* Inputs: pstart - Pointer to start of left over information field.
*
* clen - Length of comment or -1 to take it all.
*
* Outputs: A->g_telemetry - Base 91 telemetry |ss1122|
* A->g_altitude_ft - from /A=123456
* A->g_lat - Might be adjusted from !DAO!
* A->g_lon - Might be adjusted from !DAO!
* A->g_aprstt_loc - Private extension to !DAO!
* A->g_freq
* A->g_tone
* A->g_offset
* A->g_comment - Anything left over after extracting above.
*
* Description: After processing fixed and possible optional parts
* of the message, everything left over is a comment.
*
* Except!!!
*
* There are could be some other pieces of data, with
* particular formats, buried in there.
* Pull out those special items and put everything
* else into A->g_comment.
*
* References: http://www.aprs.org/info/freqspec.txt
*
* 999.999MHz T100 +060 Voice frequency.
*
* http://www.aprs.org/datum.txt
*
* !DAO! APRS precision and Datum option.
*
* Protocol reference, end of chaper 6.
*
* /A=123456 Altitude
*
* What can appear in a comment?
*
* Chapter 5 of the APRS spec ( http://www.aprs.org/doc/APRS101.PDF ) says:
*
* "The comment may contain any printable ASCII characters (except | and ~,
* which are reserved for TNC channel switching)."
*
* "Printable" would exclude character values less than space (00100000), e.g.
* tab, carriage return, line feed, nul. Sometimes we see carriage return
* (00001010) at the end of APRS packets. This would be in violation of the
* specification.
*
* The base 91 telemetry format (http://he.fi/doc/aprs-base91-comment-telemetry.txt ),
* which is not part of the APRS spec, uses the | character in the comment to delimit encoded
* telemetry data. This would be in violation of the original spec.
*
* The APRS Spec Addendum 1.2 Proposals ( http://www.aprs.org/aprs12/datum.txt)
* adds use of UTF-8 (https://en.wikipedia.org/wiki/UTF-8 )for the free form text in
* messages and comments. It can't be used in the fixed width fields.
*
* Non-ASCII characters are represented by multi-byte sequences. All bytes in these
* multi-byte sequences have the most significant bit set to 1. Using UTF-8 would not
* add any nul (00000000) bytes to the stream.
*
* There are two known cases where we can have a nul character value.
*
* * The Kenwood TM-D710A sometimes sends packets like this:
*
* VA3AJ-9>T2QU6X,VE3WRC,WIDE1,K8UNS,WIDE2*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`nW<0x1f>oS8>/]"6M}driving fast=
* K4JH-9>S5UQ6X,WR4AGC-3*,WIDE1*:4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00>`jP}l"&>/]"47}QRV from the EV =
*
* Notice that the data type indicator of "4" is not valid. If we remove
* 4P<0x00><0x0f>4T<0x00><0x0f>4X<0x00><0x0f>4\<0x00> we are left with a good MIC-E format.
* This same thing has been observed from others and is intermittent.
*
* * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes.
* This is wrong. It should be using UTF-8 and I'm not going to accomodate it here.
*
*
* The digipeater and IGate functions should pass along anything exactly the
* we received it, even if it is invalid. If different implementations try to fix it up
* somehow, like changing unprintable characters to spaces, we will only make things
* worse and thwart the duplicate detection.
*
*------------------------------------------------------------------*/
/* CTCSS tones in various formats to avoid conversions every time. */
#define NUM_CTCSS 50
static const int i_ctcss[NUM_CTCSS] = {
67, 69, 71, 74, 77, 79, 82, 85, 88, 91,
94, 97, 100, 103, 107, 110, 114, 118, 123, 127,
131, 136, 141, 146, 151, 156, 159, 162, 165, 167,
171, 173, 177, 179, 183, 186, 189, 192, 196, 199,
203, 206, 210, 218, 225, 229, 233, 241, 250, 254 };
static const float f_ctcss[NUM_CTCSS] = {
67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5,
94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3,
131.8, 136.5, 141.3, 146.2, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9,
171.3, 173.8, 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1 };
static const char * s_ctcss[NUM_CTCSS] = {
"67.0", "69.3", "71.9", "74.4", "77.0", "79.7", "82.5", "85.4", "88.5", "91.5",
"94.8", "97.4", "100.0", "103.5", "107.2", "110.9", "114.8", "118.8", "123.0", "127.3",
"131.8", "136.5", "141.3", "146.2", "151.4", "156.7", "159.8", "162.2", "165.5", "167.9",
"171.3", "173.8", "177.3", "179.9", "183.5", "186.2", "189.9", "192.8", "196.6", "199.5",
"203.5", "206.5", "210.7", "218.1", "225.7", "229.1", "233.6", "241.8", "250.3", "254.1" };
#define sign(x) (((x)>=0)?1:(-1))
static void process_comment (decode_aprs_t *A, char *pstart, int clen)
{
static int first_time = 1;
static regex_t std_freq_re; /* Frequency in standard format. */
static regex_t std_tone_re; /* Tone in standard format. */
static regex_t std_toff_re; /* Explicitly no tone. */
static regex_t std_dcs_re; /* Digital codes squelch in standard format. */
static regex_t std_offset_re; /* Xmit freq offset in standard format. */
static regex_t std_range_re; /* Range in standard format. */
static regex_t dao_re; /* DAO */
static regex_t alt_re; /* /A= altitude */
static regex_t bad_freq_re; /* Likely frequency, not standard format */
static regex_t bad_tone_re; /* Likely tone, not standard format */
static regex_t base91_tel_re; /* Base 91 compressed telemetry data. */
int e;
char emsg[100];
#define MAXMATCH 4
regmatch_t match[MAXMATCH];
char temp[sizeof(A->g_comment)];
int keep_going;
/*
* No sense in recompiling the patterns and freeing every time.
*/
if (first_time)
{
/*
* Frequency must be at the at the beginning.
* Others can be anywhere in the comment.
*/
//e = regcomp (&freq_re, "^[0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ]MHz( [TCDtcd][0-9][0-9][0-9]| Toff)?( [+-][0-9][0-9][0-9])?", REG_EXTENDED);
// Freq optionally preceded by space or /.
// Third fractional digit can be space instead.
// "MHz" should be exactly that capitalization.
// Print warning later it not.
e = regcomp (&std_freq_re, "^[/ ]?([0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ])([Mm][Hh][Zz])", REG_EXTENDED);
if (e) {
regerror (e, &std_freq_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
// If no tone, we might gobble up / after any data extension,
// We could also have a space but it's not required.
// I don't understand the difference between T and C so treat the same for now.
// We can also have "off" instead of number to explicitly mean none.
e = regcomp (&std_tone_re, "^[/ ]?([TtCc][012][0-9][0-9])", REG_EXTENDED);
if (e) {
regerror (e, &std_tone_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&std_toff_re, "^[/ ]?[TtCc][Oo][Ff][Ff]", REG_EXTENDED);
if (e) {
regerror (e, &std_toff_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&std_dcs_re, "^[/ ]?[Dd]([0-7][0-7][0-7])", REG_EXTENDED);
if (e) {
regerror (e, &std_dcs_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&std_offset_re, "^[/ ]?([+-][0-9][0-9][0-9])", REG_EXTENDED);
if (e) {
regerror (e, &std_offset_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&std_range_re, "^[/ ]?[Rr]([0-9][0-9])([mk])", REG_EXTENDED);
if (e) {
regerror (e, &std_range_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&dao_re, "!([A-Z][0-9 ][0-9 ]|[a-z][!-{ ][!-{ ]|T[0-9 B][0-9 ])!", REG_EXTENDED);
if (e) {
regerror (e, &dao_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&alt_re, "/A=[0-9][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED);
if (e) {
regerror (e, &alt_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&bad_freq_re, "[0-9][0-9][0-9]\\.[0-9][0-9][0-9]?", REG_EXTENDED);
if (e) {
regerror (e, &bad_freq_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
e = regcomp (&bad_tone_re, "(^|[^0-9.])([6789][0-9]\\.[0-9]|[12][0-9][0-9]\\.[0-9]|67|77|100|123)($|[^0-9.])", REG_EXTENDED);
if (e) {
regerror (e, &bad_tone_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
// TODO: Would like to restrict to even length something like this: ([!-{][!-{]){2,7}
e = regcomp (&base91_tel_re, "\\|([!-{]{4,14})\\|", REG_EXTENDED);
if (e) {
regerror (e, &base91_tel_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
first_time = 0;
}
/*
* If clen is >= 0, take only specified number of characters.
* Otherwise, take it all.
*/
if (clen < 0) {
clen = strlen(pstart);
}
/*
* Watch out for buffer overflow.
* KG6AZZ reports that there is a local digipeater that seems to
* malfunction ocassionally. It corrupts the packet, as it is
* digipeated, causing the comment to be hundreds of characters long.
*/
if (clen > (int)(sizeof(A->g_comment) - 1)) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Comment is extremely long, %d characters.\n", clen);
dw_printf("Please report this, along with surrounding lines, so we can find the cause.\n");
}
clen = sizeof(A->g_comment) - 1;
}
if (clen > 0) {
memcpy (A->g_comment, pstart, (size_t)clen);
A->g_comment[clen] = '\0';
}
else {
A->g_comment[0] = '\0';
}
/*
* Look for frequency in the standard format at start of comment.
* If that fails, try to obtain from object name.
*/
if (regexec (&std_freq_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
char sftemp[30];
char smtemp[10];
//dw_printf("matches= %d - %d, %d - %d, %d - %d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo),
// (int)(match[1].rm_so), (int)(match[1].rm_eo),
// (int)(match[2].rm_so), (int)(match[2].rm_eo) );
substr_se (sftemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
substr_se (smtemp, A->g_comment, match[2].rm_so, match[2].rm_eo);
switch (sftemp[0]) {
case 'A': A->g_freq = 1200 + atof(sftemp+1); break;
case 'B': A->g_freq = 2300 + atof(sftemp+1); break;
case 'C': A->g_freq = 2400 + atof(sftemp+1); break;
case 'D': A->g_freq = 3400 + atof(sftemp+1); break;
case 'E': A->g_freq = 5600 + atof(sftemp+1); break;
case 'F': A->g_freq = 5700 + atof(sftemp+1); break;
case 'G': A->g_freq = 5800 + atof(sftemp+1); break;
case 'H': A->g_freq = 10100 + atof(sftemp+1); break;
case 'I': A->g_freq = 10200 + atof(sftemp+1); break;
case 'J': A->g_freq = 10300 + atof(sftemp+1); break;
case 'K': A->g_freq = 10400 + atof(sftemp+1); break;
case 'L': A->g_freq = 10500 + atof(sftemp+1); break;
case 'M': A->g_freq = 24000 + atof(sftemp+1); break;
case 'N': A->g_freq = 24100 + atof(sftemp+1); break;
case 'O': A->g_freq = 24200 + atof(sftemp+1); break;
default: A->g_freq = atof(sftemp); break;
}
if (strncmp(smtemp, "MHz", 3) != 0) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: \"%s\" has non-standard capitalization and might not be recognized by some systems.\n", smtemp);
dw_printf("For best compatibility, it should be exactly like this: \"MHz\" (upper,upper,lower case)\n");
}
}
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment));
}
else if (strlen(A->g_name) > 0) {
// Try to extract sensible number from object/item name.
double x = atof (A->g_name);
if ((x >= 144 && x <= 148) ||
(x >= 222 && x <= 225) ||
(x >= 420 && x <= 450) ||
(x >= 902 && x <= 928)) {
A->g_freq = x;
}
}
/*
* Next, look for tone, DCS code, and range.
* Examples always have them in same order but it's not clear
* whether any order is allowed after possible frequency.
*
* TODO: Convert integer tone to original value for display.
* TODO: samples in zfreq-test3.txt
*/
keep_going = 1;
while (keep_going) {
if (regexec (&std_tone_re, A->g_comment, MAXMATCH, match, 0) == 0) {
char sttemp[10]; /* includes leading letter */
int f;
int i;
substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
// Try to convert from integer to proper value.
f = atoi(sttemp+1);
for (i = 0; i < NUM_CTCSS; i++) {
if (f == i_ctcss[i]) {
A->g_tone = f_ctcss[i];
break;
}
}
if (A->g_tone == G_UNKNOWN) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Bad CTCSS/PL specification: \"%s\"\n", sttemp);
dw_printf("Integer does not correspond to standard tone.\n");
}
}
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment));
}
else if (regexec (&std_toff_re, A->g_comment, MAXMATCH, match, 0) == 0) {
dw_printf ("NO tone\n");
A->g_tone = 0;
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment));
}
else if (regexec (&std_dcs_re, A->g_comment, MAXMATCH, match, 0) == 0) {
char sttemp[10]; /* three octal digits */
substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
A->g_dcs = strtoul (sttemp, NULL, 8);
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
}
else if (regexec (&std_offset_re, A->g_comment, MAXMATCH, match, 0) == 0) {
char sttemp[10]; /* includes leading sign */
substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
A->g_offset = 10 * atoi(sttemp);
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
}
else if (regexec (&std_range_re, A->g_comment, MAXMATCH, match, 0) == 0) {
char sttemp[10]; /* should be two digits */
char sutemp[10]; /* m for miles or k for km */
substr_se (sttemp, A->g_comment, match[1].rm_so, match[1].rm_eo);
substr_se (sutemp, A->g_comment, match[2].rm_so, match[2].rm_eo);
if (strcmp(sutemp, "m") == 0) {
A->g_range = atoi(sttemp);
}
else {
A->g_range = DW_KM_TO_MILES(atoi(sttemp));
}
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
}
else {
keep_going = 0;
}
}
/*
* Telemetry data, in base 91 compressed format appears as 2 to 7 pairs
* of base 91 digits, surrounded by | at start and end.
*/
if (regexec (&base91_tel_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
char tdata[30]; /* Should be 4 to 14 characters. */
//dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
substr_se (tdata, A->g_comment, match[1].rm_so, match[1].rm_eo);
//dw_printf("compressed telemetry data = \"%s\"\n", tdata);
telemetry_data_base91 (A->g_src, tdata, A->g_telemetry, sizeof(A->g_telemetry));
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
}
/*
* Latitude and Longitude in the form DD MM.HH has a resolution of about 60 feet.
* The !DAO! option allows another digit or almost two for greater resolution.
*
* This would not make sense to use this with a compressed location which
* already has much greater resolution.
*
* It surprized me to see this in a MIC-E message.
* MIC-E has resolution of .01 minute so it would make sense to have it as an option.
*/
if (regexec (&dao_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
int d = A->g_comment[match[0].rm_so+1];
int a = A->g_comment[match[0].rm_so+2];
int o = A->g_comment[match[0].rm_so+3];
//dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
/*
* Private extension for APRStt
*/
if (d == 'T') {
if (a == ' ' && o == ' ') {
snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt corral location");
}
else if (isdigit(a) && o == ' ') {
snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c of 10", a);
}
else if (isdigit(a) && isdigit(o)) {
snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c of 100", a, o);
}
else if (a == 'B' && isdigit(o)) {
snprintf (A->g_aprstt_loc, sizeof(A->g_aprstt_loc), "APRStt location %c%c...", a, o);
}
}
else if (isupper(d))
{
/*
* This adds one extra digit to each. Dao adds extra digit like:
*
* Lat: DD MM.HHa
* Lon: DDD HH.HHo
*/
if (isdigit(a)) {
A->g_lat += (a - '0') / 60000.0 * sign(A->g_lat);
}
if (isdigit(o)) {
A->g_lon += (o - '0') / 60000.0 * sign(A->g_lon);
}
}
else if (islower(d))
{
/*
* This adds almost two extra digits to each like this:
*
* Lat: DD MM.HHxx
* Lon: DDD HH.HHxx
*
* The original character range '!' to '{' is first converted
* to an integer in range of 0 to 90. It is multiplied by 1.1
* to stretch the numeric range to be 0 to 99.
*/
/*
* The spec appears to be wrong. It says '}' is the maximum value when it should be '{'.
*/
if (isdigit91(a)) {
A->g_lat += (a - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lat);
}
if (isdigit91(o)) {
A->g_lon += (o - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lon);
}
}
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
}
/*
* Altitude in feet. /A=123456
*/
if (regexec (&alt_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
//dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
strlcpy (temp, A->g_comment + match[0].rm_eo, sizeof(temp));
A->g_comment[match[0].rm_eo] = '\0';
A->g_altitude_ft = atoi(A->g_comment + match[0].rm_so + 3);
strlcpy (A->g_comment + match[0].rm_so, temp, sizeof(A->g_comment)-match[0].rm_so);
}
//dw_printf("Final comment='%s'\n", A->g_comment);
/*
* Finally look for something that looks like frequency or CTCSS tone
* in the remaining comment. Point this out and suggest the
* standardized format.
* Don't complain if we have already found a valid value.
*/
if (A->g_freq == G_UNKNOWN && regexec (&bad_freq_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
char bad[30];
char good[30];
double x;
substr_se (bad, A->g_comment, match[0].rm_so, match[0].rm_eo);
x = atof(bad);
if ((x >= 144 && x <= 148) ||
(x >= 222 && x <= 225) ||
(x >= 420 && x <= 450) ||
(x >= 902 && x <= 928)) {
if ( ! A->g_quiet) {
snprintf (good, sizeof(good), "%07.3fMHz", x);
text_color_set(DW_COLOR_ERROR);
dw_printf("\"%s\" in comment looks like a frequency in non-standard format.\n", bad);
dw_printf("For most systems to recognize it, use exactly this form \"%s\" at beginning of comment.\n", good);
}
if (A->g_freq == G_UNKNOWN) {
A->g_freq = x;
}
}
}
if (A->g_tone == G_UNKNOWN && regexec (&bad_tone_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
char bad1[30]; /* original 99.9 or 999.9 format or one of 67 77 100 123 */
char bad2[30]; /* 99.9 or 999.9 format. ".0" appended for special cases. */
char good[30];
int i;
substr_se (bad1, A->g_comment, match[2].rm_so, match[2].rm_eo);
strlcpy (bad2, bad1, sizeof(bad2));
if (strcmp(bad2, "67") == 0 || strcmp(bad2, "77") == 0 || strcmp(bad2, "100") == 0 || strcmp(bad2, "123") == 0) {
strlcat (bad2, ".0", sizeof(bad2));
}
// TODO: Why wasn't freq/PL recognized here?
// Should we recognize some cases of single decimal place as frequency?
//DECODED[194] N8VIM audio level = 27 [NONE]
//[0] N8VIM>BEACON,WIDE2-2:!4240.85N/07133.99W_PHG72604/ Pepperell, MA-> WX. 442.9+ PL100<0x0d>
//Didn't find wind direction in form c999.
//Didn't find wind speed in form s999.
//Didn't find wind gust in form g999.
//Didn't find temperature in form t999.
//Weather Report, WEATHER Station (blue)
//N 42 40.8500, W 071 33.9900
//, "PHG72604/ Pepperell, MA-> WX. 442.9+ PL100"
for (i = 0; i < NUM_CTCSS; i++) {
if (strcmp (s_ctcss[i], bad2) == 0) {
if ( ! A->g_quiet) {
snprintf (good, sizeof(good), "T%03d", i_ctcss[i]);
text_color_set(DW_COLOR_ERROR);
dw_printf("\"%s\" in comment looks like it might be a CTCSS tone in non-standard format.\n", bad1);
dw_printf("For most systems to recognize it, use exactly this form \"%s\" at near beginning of comment, after any frequency.\n", good);
}
if (A->g_tone == G_UNKNOWN) {
A->g_tone = atof(bad2);
}
break;
}
}
}
if ((A->g_offset == 6000 || A->g_offset == -6000) && A->g_freq >= 144 && A->g_freq <= 148) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("A transmit offset of 6 MHz on the 2 meter band doesn't seem right.\n");
dw_printf("Each unit is 10 kHz so you should probably be using \"-060\" or \"+060\"\n");
}
}
/*
* TODO: samples in zfreq-test4.txt
*/
}
/* end process_comment */
/*------------------------------------------------------------------
*
* Function: main
*
* Purpose: Main program for standalone test program.
*
* Inputs: stdin for raw data to decode.
* This is in the usual display format either from
* a TNC, findu.com, aprs.fi, etc. e.g.
*
* N1EDF-9>T2QT8Y,W1CLA-1,WIDE1*,WIDE2-2,00000:`bSbl!Mv/`"4%}_ <0x0d>
*
* WB2OSZ-1>APN383,qAR,N1EDU-2:!4237.14NS07120.83W#PHG7130Chelmsford, MA
*
*
* Outputs: stdout
*
* Description: Compile like this to make a standalone test program.
*
* gcc -o decode_aprs -DDECAMAIN decode_aprs.c ax25_pad.c ...
*
* ./decode_aprs < decode_aprs.txt
*
* aprs.fi precedes raw data with a time stamp which you
* would need to remove first.
*
* cut -c26-999 tmp/kj4etp-9.txt | decode_aprs.exe
*
*
* Restriction: MIC-E message type can be problematic because it
* it can use unprintable characters in the information field.
*
* Dire Wolf and aprs.fi print it in hexadecimal. Example:
*
* KB1KTR-8>TR3U6T,KB1KTR-9*,WB2OSZ-1*,WIDE2*,qAR,W1XM:`c1<0x1f>l!t>/>"4^}
* ^^^^^^
* ||||||
* What does findu.com do in this case?
*
* ax25_from_text recognizes this representation so it can be used
* to decode raw data later.
*
* TODO: To make it more useful,
* - Remove any leading timestamp.
* - Remove any "qA*" and following from the path.
*
*------------------------------------------------------------------*/
#if DECAMAIN
/* Stub for stand-alone decoder. */
void nmea_send_waypoint (char *wname_in, double dlat, double dlong, char symtab, char symbol,
float alt, float course, float speed, char *comment)
{
return;
}
int main (int argc, char *argv[])
{
char stuff[300];
char *p;
packet_t pp;
#if __WIN32__
// Select UTF-8 code page for console output.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx
// This is the default I see for windows terminal:
// >chcp
// Active code page: 437
//Restore on exit? oldcp = GetConsoleOutputCP();
SetConsoleOutputCP(CP_UTF8);
#else
/*
* Default on Raspian & Ubuntu Linux is fine. Don't know about others.
*
* Should we look at LANG environment variable and issue a warning
* if it doesn't look something like en_US.UTF-8 ?
*/
#endif
if (argc >= 2) {
if (freopen (argv[1], "r", stdin) == NULL) {
fprintf(stderr, "Can't open %s for read.\n", argv[1]);
exit(1);
}
}
text_color_init(1);
text_color_set(DW_COLOR_INFO);
while (fgets(stuff, sizeof(stuff), stdin) != NULL)
{
p = stuff + strlen(stuff) - 1;
while (p >= stuff && (*p == '\r' || *p == '\n')) {
*p-- = '\0';
}
if (strlen(stuff) == 0 || stuff[0] == '#')
{
/* comment or blank line */
text_color_set(DW_COLOR_INFO);
dw_printf("%s\n", stuff);
continue;
}
else
{
/* Try to process it. */
text_color_set(DW_COLOR_REC);
dw_printf("\n%s\n", stuff);
pp = ax25_from_text(stuff, 1);
if (pp != NULL)
{
decode_aprs_t A;
// log directory option someday?
decode_aprs (&A, pp, 0);
//Print it all out in human readable format.
decode_aprs_print (&A);
/*
* Perform validity check on each address.
* This should print an error message if any issues.
*/
(void)ax25_check_addresses(pp);
// Send to log file?
// if (logdir != NULL && *logdir != '\0') {
// log_write (&A, pp, logdir);
// }
ax25_delete (pp);
}
else
{
text_color_set(DW_COLOR_ERROR);
dw_printf("\n%s\n", "ERROR - Could not parse input!\n");
}
}
}
return (0);
}
#endif /* DECAMAIN */
/* end decode_aprs.c */
direwolf-1.4/decode_aprs.h 0000664 0000000 0000000 00000010307 13100232553 0015605 0 ustar 00root root 0000000 0000000
/* decode_aprs.h */
#ifndef DECODE_APRS_H
#define DECODE_APRS_H 1
#ifndef G_UNKNOWN
#include "latlong.h"
#endif
#ifndef AX25_MAX_ADDR_LEN
#include "ax25_pad.h"
#endif
#ifndef APRSTT_LOC_DESC_LEN
#include "aprs_tt.h"
#endif
typedef struct decode_aprs_s {
int g_quiet; /* Suppress error messages when decoding. */
char g_src[AX25_MAX_ADDR_LEN];
char g_msg_type[60]; /* APRS data type. Telemetry descriptions get pretty long. */
/* Putting msg in the name was a poor choice because */
/* "message" has a specific meaning. Rename it someday. */
char g_symbol_table; /* The Symbol Table Identifier character selects one */
/* of the two Symbol Tables, or it may be used as */
/* single-character (alpha or numeric) overlay, as follows: */
/* / Primary Symbol Table (mostly stations) */
/* \ Alternate Symbol Table (mostly Objects) */
/* 0-9 Numeric overlay. Symbol from Alternate Symbol */
/* Table (uncompressed lat/long data format) */
/* a-j Numeric overlay. Symbol from Alternate */
/* Symbol Table (compressed lat/long data */
/* format only). i.e. a-j maps to 0-9 */
/* A-Z Alpha overlay. Symbol from Alternate Symbol Table */
char g_symbol_code; /* Where the Symbol Table Identifier is 0-9 or A-Z (or a-j */
/* with compressed position data only), the symbol comes from */
/* the Alternate Symbol Table, and is overlaid with the */
/* identifier (as a single digit or a capital letter). */
char g_aprstt_loc[APRSTT_LOC_DESC_LEN]; /* APRStt location from !DAO! */
double g_lat, g_lon; /* Location, degrees. Negative for South or West. */
/* Set to G_UNKNOWN if missing or error. */
char g_maidenhead[12]; /* 4 or 6 (or 8?) character maidenhead locator. */
char g_name[12]; /* Object or item name. Max. 9 characters. */
char g_addressee[12]; /* Addressee for a "message." Max. 9 characters. */
/* Also for Directed Station Query which is a */
/* special case of message. */
enum message_subtype_e { message_subtype_invalid = 0,
message_subtype_message,
message_subtype_ack,
message_subtype_rej,
message_subtype_telem_parm,
message_subtype_telem_unit,
message_subtype_telem_eqns,
message_subtype_telem_bits,
message_subtype_directed_query
} g_message_subtype; /* Various cases of the overloaded "message." */
char g_message_number[8]; /* Message number. Should be 1 - 5 characters if used. */
float g_speed_mph; /* Speed in MPH. */
float g_course; /* 0 = North, 90 = East, etc. */
int g_power; /* Transmitter power in watts. */
int g_height; /* Antenna height above average terrain, feet. */
int g_gain; /* Antenna gain in dB. */
char g_directivity[12]; /* Direction of max signal strength */
float g_range; /* Precomputed radio range in miles. */
float g_altitude_ft; /* Feet above median sea level. */
char g_mfr[80]; /* Manufacturer or application. */
char g_mic_e_status[32]; /* MIC-E message. */
double g_freq; /* Frequency, MHz */
float g_tone; /* CTCSS tone, Hz, one fractional digit */
int g_dcs; /* Digital coded squelch, print as 3 octal digits. */
int g_offset; /* Transmit offset, kHz */
char g_query_type[12]; /* General Query: APRS, IGATE, WX, ... */
/* Addressee is NOT set. */
/* Directed Station Query: exactly 5 characters. */
/* APRSD, APRST, PING?, ... */
/* Addressee is set. */
double g_footprint_lat; /* A general query may contain a foot print. */
double g_footprint_lon; /* Set all to G_UNKNOWN if not used. */
float g_footprint_radius; /* Radius in miles. */
char g_query_callsign[12]; /* Directed query may contain callsign. */
/* e.g. tell me all objects from that callsign. */
char g_weather[500]; /* Weather. Can get quite long. Rethink max size. */
char g_telemetry[256]; /* Telemetry data. Rethink max size. */
char g_comment[256]; /* Comment. */
} decode_aprs_t;
extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet);
extern void decode_aprs_print (decode_aprs_t *A);
#endif direwolf-1.4/dedupe.c 0000664 0000000 0000000 00000015630 13100232553 0014602 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Name: dedupe.c
*
* Purpose: Avoid transmitting duplicate packets which are too
* close together.
*
*
* Description: We want to avoid digipeating duplicate packets to
* to help reduce radio channel congestion with
* redundant information.
* Duplicate packets can occur in several ways:
*
* (1) A digipeated packet can loop between 2 or more
* digipeaters. For example:
*
* W1ABC>APRS,WIDE3-3
* W1ABC>APRS,mycall*,WIDE3-2
* W1ABC>APRS,mycall,RPT1*,WIDE3-1
* W1ABC>APRS,mycall,RPT1,mycall*
*
* (2) We could hear our own original transmission
* repeated by someone else. Example:
*
* mycall>APRS,WIDE3-3
* mycall>APRS,RPT1*,WIDE3-2
* mycall>APRS,RPT1*,mycall*,WIDE3-1
*
* (3) We could hear the same packet from multiple
* digipeaters (with or without the original).
*
* W1ABC>APRS,WIDE3-2
* W1ABC>APRS,RPT1*,WIDE3-2
* W1ABC>APRS,RPT2*,WIDE3-2
* W1ABC>APRS,RPT3*,WIDE3-2
*
* (4) Someone could be sending the same thing over and
* over with very little delay in between.
*
* W1ABC>APRS,WIDE3-3
* W1ABC>APRS,WIDE3-3
* W1ABC>APRS,WIDE3-3
*
* We can catch the first two by looking for 'mycall' in
* the source or digipeater fields.
*
* The other two cases require us to keep a record of what
* we transmitted recently and test for duplicates that
* should be dropped.
*
* Once we have the solution to catch cases (3) and (4)
* there is no reason for the special case of looking for
* mycall. The same technique catches all four situations.
*
* For detecting duplicates, we need to look
* + source station
* + destination
* + information field
* but NOT the changing list of digipeaters.
*
* Typically, only a checksum is kept to reduce memory
* requirements and amount of compution for comparisons.
* There is a very very small probability that two unrelated
* packets will result in the same checksum, and the
* undesired dropping of the packet.
*
* References: Original APRS specification:
*
* TBD...
*
* "The New n-N Paradigm"
*
* http://www.aprs.org/fix14439.html
*
*------------------------------------------------------------------*/
#define DEDUPE_C
#include "direwolf.h"
#include
#include
#include
#include
#include
#include "ax25_pad.h"
#include "dedupe.h"
#include "fcs_calc.h"
#include "textcolor.h"
#ifndef DIGITEST
#include "igate.h"
#endif
/*------------------------------------------------------------------------------
*
* Name: dedupe_init
*
* Purpose: Initialize the duplicate detection subsystem.
*
* Input: ttl - Number of seconds to retain information
* about recent transmissions.
*
*
* Returns: None
*
* Description: This should be called at application startup.
*
*
*------------------------------------------------------------------------------*/
static int history_time = 30; /* Number of seconds to keep information */
/* about recent transmissions. */
#define HISTORY_MAX 25 /* Maximum number of transmission */
/* records to keep. If we run out of */
/* room the oldest ones are overwritten */
/* before they expire. */
static int insert_next; /* Index, in array below, where next */
/* item should be stored. */
static struct {
time_t time_stamp; /* When the packet was transmitted. */
unsigned short checksum; /* Some sort of checksum for the */
/* source, destination, and information. */
/* is is not used anywhere else. */
short xmit_channel; /* Radio channel number. */
} history[HISTORY_MAX];
void dedupe_init (int ttl)
{
history_time = ttl;
insert_next = 0;
memset (history, 0, sizeof(history));
}
/*------------------------------------------------------------------------------
*
* Name: dedupe_remember
*
* Purpose: Save information about a packet being transmitted so we
* can detect, and avoid, duplicates later.
*
* Input: pp - Pointer to packet object.
*
* chan - Radio channel for transmission.
*
* Returns: None
*
* Rambling: At one time, my thinking is that we want to keep track of
* ALL transmitted packets regardless of origin or type.
*
* + my beacons
* + anything from a connected application
* + anything digipeated
*
* The easiest way to catch all cases is to call dedup_remember()
* from inside tq_append().
*
* But I don't think that is the right approach.
* When acting as a KISS TNC, we should just shovel everything
* through and not question what the application is doing.
* If the connected application has a digipeating function,
* it's responsible for those decisions.
*
* My current thinking is that dedupe_remember() should be
* called BEFORE tq_append() in the digipeater case.
*
* We should also capture our own beacon transmissions.
*
*------------------------------------------------------------------------------*/
void dedupe_remember (packet_t pp, int chan)
{
history[insert_next].time_stamp = time(NULL);
history[insert_next].checksum = ax25_dedupe_crc(pp);
history[insert_next].xmit_channel = chan;
insert_next++;
if (insert_next >= HISTORY_MAX) {
insert_next = 0;
}
/* If we send something by digipeater, we don't */
/* want to do it again if it comes from APRS-IS. */
/* Not sure about the other way around. */
#ifndef DIGITEST
ig_to_tx_remember (pp, chan, 1);
#endif
}
/*------------------------------------------------------------------------------
*
* Name: dedupe_check
*
* Purpose: Check whether this is a duplicate of another sent recently.
*
* Input: pp - Pointer to packet object.
*
* chan - Radio channel for transmission.
*
* Returns: True if it is a duplicate.
*
*
*------------------------------------------------------------------------------*/
int dedupe_check (packet_t pp, int chan)
{
unsigned short crc = ax25_dedupe_crc(pp);
time_t now = time(NULL);
int j;
for (j=0; j= now - history_time &&
history[j].checksum == crc &&
history[j].xmit_channel == chan) {
return 1;
}
}
return 0;
}
/* end dedupe.c */
direwolf-1.4/dedupe.h 0000664 0000000 0000000 00000000215 13100232553 0014600 0 ustar 00root root 0000000 0000000
void dedupe_init (int ttl);
void dedupe_remember (packet_t pp, int chan);
int dedupe_check (packet_t pp, int chan);
/* end dedupe.h */
direwolf-1.4/demod.c 0000664 0000000 0000000 00000104205 13100232553 0014421 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* Module: demod.c
*
* Purpose: Common entry point for multiple types of demodulators.
*
* Input: Audio samples from either a file or the "sound card."
*
* Outputs: Calls hdlc_rec_bit() for each bit demodulated.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "audio.h"
#include "demod.h"
#include "tune.h"
#include "fsk_demod_state.h"
#include "fsk_gen_filter.h"
#include "fsk_fast_filter.h"
#include "hdlc_rec.h"
#include "textcolor.h"
#include "demod_9600.h"
#include "demod_afsk.h"
#include "demod_psk.h"
// Properties of the radio channels.
static struct audio_s *save_audio_config_p;
// TODO: temp experiment.
static int upsample = 2; // temp experiment.
static int zerostuff = 1; // temp experiment.
// Current state of all the decoders.
static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS];
static int sample_sum[MAX_CHANS][MAX_SUBCHANS];
static int sample_count[MAX_CHANS][MAX_SUBCHANS];
/*------------------------------------------------------------------
*
* Name: demod_init
*
* Purpose: Initialize the demodulator(s) used for reception.
*
* Inputs: pa - Pointer to audio_s structure with
* various parameters for the modem(s).
*
* Returns: 0 for success, -1 for failure.
*
*
* Bugs: This doesn't do much error checking so don't give it
* anything crazy.
*
*----------------------------------------------------------------*/
int demod_init (struct audio_s *pa)
{
//int j;
int chan; /* Loop index over number of radio channels. */
char profile;
/*
* Save audio configuration for later use.
*/
save_audio_config_p = pa;
for (chan = 0; chan < MAX_CHANS; chan++) {
if (save_audio_config_p->achan[chan].valid) {
char *p;
char just_letters[16];
int num_letters;
int have_plus;
/*
* These are derived from config file parameters.
*
* num_subchan is number of demodulators.
* This can be increased by:
* Multiple frequencies.
* Multiple letters (not sure if I will continue this).
* New interleaved decoders.
*
* num_slicers is set to max by the "+" option.
*/
save_audio_config_p->achan[chan].num_subchan = 1;
save_audio_config_p->achan[chan].num_slicers = 1;
switch (save_audio_config_p->achan[chan].modem_type) {
case MODEM_OFF:
break;
case MODEM_AFSK:
/*
* Tear apart the profile and put it back together in a normalized form:
* - At least one letter, supply suitable default if necessary.
* - Upper case only.
* - Any plus will be at the end.
*/
num_letters = 0;
just_letters[num_letters] = '\0';
have_plus = 0;
for (p = save_audio_config_p->achan[chan].profiles; *p != '\0'; p++) {
if (islower(*p)) {
just_letters[num_letters] = toupper(*p);
num_letters++;
just_letters[num_letters] = '\0';
}
else if (isupper(*p)) {
just_letters[num_letters] = *p;
num_letters++;
just_letters[num_letters] = '\0';
}
else if (*p == '+') {
have_plus = 1;
if (p[1] != '\0') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Channel %d: + option must appear at end of demodulator types \"%s\" \n",
chan, save_audio_config_p->achan[chan].profiles);
}
}
else if (*p == '-') {
have_plus = -1;
if (p[1] != '\0') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Channel %d: - option must appear at end of demodulator types \"%s\" \n",
chan, save_audio_config_p->achan[chan].profiles);
}
} else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Channel %d: Demodulator types \"%s\" can contain only letters and + - characters.\n",
chan, save_audio_config_p->achan[chan].profiles);
}
}
assert (num_letters == (int)(strlen(just_letters)));
/*
* Pick a good default demodulator if none specified.
*/
if (num_letters == 0) {
if (save_audio_config_p->achan[chan].baud < 600) {
/* This has been optimized for 300 baud. */
strlcpy (just_letters, "D", sizeof(just_letters));
}
else {
#if __arm__
/* We probably don't have a lot of CPU power available. */
/* Previously we would use F if possible otherwise fall back to A. */
/* In version 1.2, new default is E+ /3. */
strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 now E.
if (have_plus != -1) have_plus = 1; // Add as default for version 1.2
// If not explicitly turned off.
if (save_audio_config_p->achan[chan].decimate == 0) {
if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
save_audio_config_p->achan[chan].decimate = 3;
}
}
#else
strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 changed C to E.
if (have_plus != -1) have_plus = 1; // Add as default for version 1.2
// If not explicitly turned off.
#endif
}
num_letters = 1;
}
assert (num_letters == (int)(strlen(just_letters)));
/*
* Put it back together again.
*/
/* At this point, have_plus can have 3 values: */
/* 1 = turned on, either explicitly or by applied default */
/* -1 = explicitly turned off. change to 0 here so it is false. */
/* 0 = off by default. */
if (have_plus == -1) have_plus = 0;
strlcpy (save_audio_config_p->achan[chan].profiles, just_letters, sizeof(save_audio_config_p->achan[chan].profiles));
assert (strlen(save_audio_config_p->achan[chan].profiles) >= 1);
if (have_plus) {
strlcat (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
}
/* These can be increased later for the multi-frequency case. */
save_audio_config_p->achan[chan].num_subchan = num_letters;
save_audio_config_p->achan[chan].num_slicers = 1;
/*
* Some error checking - Can use only one of these:
*
* - Multiple letters.
* - New + multi-slicer.
* - Multiple frequencies.
*/
if (have_plus && save_audio_config_p->achan[chan].num_freq > 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Channel %d: Demodulator + option can't be combined with multiple frequencies.\n", chan);
save_audio_config_p->achan[chan].num_subchan = 1; // Will be set higher later.
save_audio_config_p->achan[chan].num_freq = 1;
}
if (num_letters > 1 && save_audio_config_p->achan[chan].num_freq > 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Channel %d: Multiple demodulator types can't be combined with multiple frequencies.\n", chan);
save_audio_config_p->achan[chan].profiles[1] = '\0';
num_letters = 1;
}
if (save_audio_config_p->achan[chan].decimate == 0) {
save_audio_config_p->achan[chan].decimate = 1;
if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
save_audio_config_p->achan[chan].decimate = 3;
}
}
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate",
chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].mark_freq, save_audio_config_p->achan[chan].space_freq,
save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
if (save_audio_config_p->achan[chan].decimate != 1)
dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled");
dw_printf (".\n");
/*
* Initialize the demodulator(s).
*
* We have 3 cases to consider.
*/
// TODO1.3: revisit this logic now that it is less restrictive.
if (num_letters > 1) {
int d;
/*
* Multiple letters, usually for 1200 baud.
* Each one corresponds to a demodulator and subchannel.
*
* An interesting experiment but probably not too useful.
* Can't have multiple frequency pairs.
* In version 1.3 this can be combined with the + option.
*/
save_audio_config_p->achan[chan].num_subchan = num_letters;
/*
* Quick hack with special case for another experiment.
* Do this in a more general way if it turns out to be useful.
*/
save_audio_config_p->achan[chan].interleave = 1;
if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EE") == 0) {
save_audio_config_p->achan[chan].interleave = 2;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEE") == 0) {
save_audio_config_p->achan[chan].interleave = 3;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEE") == 0) {
save_audio_config_p->achan[chan].interleave = 4;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEEE") == 0) {
save_audio_config_p->achan[chan].interleave = 5;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GG") == 0) {
save_audio_config_p->achan[chan].interleave = 2;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG") == 0) {
save_audio_config_p->achan[chan].interleave = 3;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG+") == 0) {
save_audio_config_p->achan[chan].interleave = 3;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGG") == 0) {
save_audio_config_p->achan[chan].interleave = 4;
save_audio_config_p->achan[chan].decimate = 1;
}
else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGGG") == 0) {
save_audio_config_p->achan[chan].interleave = 5;
save_audio_config_p->achan[chan].decimate = 1;
}
if (save_audio_config_p->achan[chan].num_subchan != num_letters) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_subchan(%d) != strlen(\"%s\")\n",
__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_subchan, save_audio_config_p->achan[chan].profiles);
}
if (save_audio_config_p->achan[chan].num_freq != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
}
for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
int mark, space;
assert (d >= 0 && d < MAX_SUBCHANS);
struct demodulator_state_s *D;
D = &demodulator_state[chan][d];
profile = save_audio_config_p->achan[chan].profiles[d];
mark = save_audio_config_p->achan[chan].mark_freq;
space = save_audio_config_p->achan[chan].space_freq;
if (save_audio_config_p->achan[chan].num_subchan != 1) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
}
demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / (save_audio_config_p->achan[chan].decimate * save_audio_config_p->achan[chan].interleave),
save_audio_config_p->achan[chan].baud,
mark,
space,
profile,
D);
if (have_plus) {
/* I'm not happy about putting this hack here. */
/* should pass in as a parameter rather than adding on later. */
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
D->num_slicers = MAX_SLICERS;
}
/* For signal level reporting, we want a longer term view. */
// TODO: Should probably move this into the init functions.
D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2f;
}
}
else if (have_plus) {
/*
* PLUS - which (formerly) implies we have only one letter and one frequency pair.
*
* One demodulator feeds multiple slicers, each a subchannel.
*/
if (num_letters != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
__FILE__, __LINE__, chan, just_letters);
}
if (save_audio_config_p->achan[chan].num_freq != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
}
if (save_audio_config_p->achan[chan].num_freq != save_audio_config_p->achan[chan].num_subchan) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_subchan(%d)\n",
__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_subchan);
}
struct demodulator_state_s *D;
D = &demodulator_state[chan][0];
/* I'm not happy about putting this hack here. */
/* This belongs in demod_afsk_init but it doesn't have access to the audio config. */
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].mark_freq,
save_audio_config_p->achan[chan].space_freq,
save_audio_config_p->achan[chan].profiles[0],
D);
if (have_plus) {
/* I'm not happy about putting this hack here. */
/* should pass in as a parameter rather than adding on later. */
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
D->num_slicers = MAX_SLICERS;
}
/* For signal level reporting, we want a longer term view. */
D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2f;
}
else {
int d;
/*
* One letter.
* Can be combined with multiple frequencies.
*/
if (num_letters != 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].profiles);
}
save_audio_config_p->achan[chan].num_subchan = save_audio_config_p->achan[chan].num_freq;
for (d = 0; d < save_audio_config_p->achan[chan].num_freq; d++) {
int mark, space, k;
assert (d >= 0 && d < MAX_SUBCHANS);
struct demodulator_state_s *D;
D = &demodulator_state[chan][d];
profile = save_audio_config_p->achan[chan].profiles[0];
k = d * save_audio_config_p->achan[chan].offset - ((save_audio_config_p->achan[chan].num_freq - 1) * save_audio_config_p->achan[chan].offset) / 2;
mark = save_audio_config_p->achan[chan].mark_freq + k;
space = save_audio_config_p->achan[chan].space_freq + k;
if (save_audio_config_p->achan[chan].num_freq != 1) {
text_color_set(DW_COLOR_DEBUG);
dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
}
demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
save_audio_config_p->achan[chan].baud,
mark, space,
profile,
D);
if (have_plus) {
/* I'm not happy about putting this hack here. */
/* should pass in as a parameter rather than adding on later. */
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
D->num_slicers = MAX_SLICERS;
}
/* For signal level reporting, we want a longer term view. */
D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2f;
} /* for each freq pair */
}
break;
case MODEM_QPSK: // New for 1.4
// TODO: See how much CPU this takes on ARM and decide if we should have different defaults.
if (strlen(save_audio_config_p->achan[chan].profiles) == 0) {
//#if __arm__
// strlcpy (save_audio_config_p->achan[chan].profiles, "R", sizeof(save_audio_config_p->achan[chan].profiles));
//#else
strlcpy (save_audio_config_p->achan[chan].profiles, "PQRS", sizeof(save_audio_config_p->achan[chan].profiles));
//#endif
}
save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles);
save_audio_config_p->achan[chan].decimate = 1; // think about this later.
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d bps, QPSK, %s, %d sample rate",
chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
if (save_audio_config_p->achan[chan].decimate != 1)
dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled");
dw_printf (".\n");
int d;
for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
assert (d >= 0 && d < MAX_SUBCHANS);
struct demodulator_state_s *D;
D = &demodulator_state[chan][d];
profile = save_audio_config_p->achan[chan].profiles[d];
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("About to call demod_psk_init for Q-PSK case, modem_type=%d, profile='%c'\n",
// save_audio_config_p->achan[chan].modem_type, profile);
demod_psk_init (save_audio_config_p->achan[chan].modem_type,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
save_audio_config_p->achan[chan].baud,
profile,
D);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Returned from demod_psk_init\n");
/* For signal level reporting, we want a longer term view. */
/* Guesses based on 9600. Maybe revisit someday. */
D->quick_attack = 0.080 * 0.2;
D->sluggish_decay = 0.00012 * 0.2;
}
break;
case MODEM_8PSK: // New for 1.4
// TODO: See how much CPU this takes on ARM and decide if we should have different defaults.
if (strlen(save_audio_config_p->achan[chan].profiles) == 0) {
//#if __arm__
// strlcpy (save_audio_config_p->achan[chan].profiles, "V", sizeof(save_audio_config_p->achan[chan].profiles));
//#else
strlcpy (save_audio_config_p->achan[chan].profiles, "TUVW", sizeof(save_audio_config_p->achan[chan].profiles));
//#endif
}
save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles);
save_audio_config_p->achan[chan].decimate = 1; // think about this later
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d bps, 8PSK, %s, %d sample rate",
chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
if (save_audio_config_p->achan[chan].decimate != 1)
dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled");
dw_printf (".\n");
//int d;
for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
assert (d >= 0 && d < MAX_SUBCHANS);
struct demodulator_state_s *D;
D = &demodulator_state[chan][d];
profile = save_audio_config_p->achan[chan].profiles[d];
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("About to call demod_psk_init for 8-PSK case, modem_type=%d, profile='%c'\n",
// save_audio_config_p->achan[chan].modem_type, profile);
demod_psk_init (save_audio_config_p->achan[chan].modem_type,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
save_audio_config_p->achan[chan].baud,
profile,
D);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Returned from demod_psk_init\n");
/* For signal level reporting, we want a longer term view. */
/* Guesses based on 9600. Maybe revisit someday. */
D->quick_attack = 0.080 * 0.2;
D->sluggish_decay = 0.00012 * 0.2;
}
break;
//TODO: how about MODEM_OFF case?
case MODEM_BASEBAND:
case MODEM_SCRAMBLE:
default: /* Not AFSK */
{
if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) {
/* Apply default if not set earlier. */
/* Not sure if it should be on for ARM too. */
/* Need to take a look at CPU usage and performance difference. */
#ifndef __arm__
strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
#endif
}
#ifdef TUNE_UPSAMPLE
upsample = TUNE_UPSAMPLE;
#endif
#ifdef TUNE_ZEROSTUFF
zerostuff = TUNE_ZEROSTUFF;
#endif
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d",
chan, save_audio_config_p->achan[chan].baud,
save_audio_config_p->achan[chan].profiles,
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, upsample);
if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
dw_printf (", DTMF decoder enabled");
dw_printf (".\n");
struct demodulator_state_s *D;
D = &demodulator_state[chan][0]; // first subchannel
save_audio_config_p->achan[chan].num_subchan = 1;
save_audio_config_p->achan[chan].num_slicers = 1;
if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
/* I'm not happy about putting this hack here. */
/* This belongs in demod_9600_init but it doesn't have access to the audio config. */
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
}
/* We need a minimum number of audio samples per bit time for good performance. */
/* Easier to check here because demod_9600_init might have an adjusted sample rate. */
float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec)
/ (float)(save_audio_config_p->achan[chan].baud);
text_color_set(DW_COLOR_INFO);
dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n",
save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
save_audio_config_p->achan[chan].baud,
(double)ratio);
if (ratio < 3) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("There is little hope of success with such a low ratio. Use a higher sample rate.\n");
}
else if (ratio < 5) {
dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n");
}
else if (ratio < 6) {
dw_printf ("Increasing the sample rate should improve decoder performance.\n");
}
else if (ratio > 15) {
dw_printf ("Sample rate is more than adequate. You might lower it if CPU load is a concern.\n");
}
else {
dw_printf ("This is a suitable ratio for good performance.\n");
}
demod_9600_init (upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D);
if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
/* I'm not happy about putting this hack here. */
/* should pass in as a parameter rather than adding on later. */
save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
D->num_slicers = MAX_SLICERS;
}
/* For signal level reporting, we want a longer term view. */
D->quick_attack = D->agc_fast_attack * 0.2f;
D->sluggish_decay = D->agc_slow_decay * 0.2f;
}
break;
} /* switch on modulation type. */
} /* if channel number is valid */
} /* for chan ... */
return (0);
} /* end demod_init */
/*------------------------------------------------------------------
*
* Name: demod_get_sample
*
* Purpose: Get one audio sample fromt the specified sound input source.
*
* Inputs: a - Index for audio device. 0 = first.
*
* Returns: -32768 .. 32767 for a valid audio sample.
* 256*256 for end of file or other error.
*
* Global In: save_audio_config_p->adev[ACHAN2ADEV(chan)].bits_per_sample - So we know whether to
* read 1 or 2 bytes from audio stream.
*
* Description: Grab 1 or two btyes depending on data source.
*
* When processing stereo, the caller will call this
* at twice the normal rate to obtain alternating left
* and right samples.
*
*----------------------------------------------------------------*/
#define FSK_READ_ERR (256*256)
__attribute__((hot))
int demod_get_sample (int a)
{
int x1, x2;
signed short sam; /* short to force sign extention. */
assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16);
if (save_audio_config_p->adev[a].bits_per_sample == 8) {
x1 = audio_get(a);
if (x1 < 0) return(FSK_READ_ERR);
assert (x1 >= 0 && x1 <= 255);
/* Scale 0..255 into -32k..+32k */
sam = (x1 - 128) * 256;
}
else {
x1 = audio_get(a); /* lower byte first */
if (x1 < 0) return(FSK_READ_ERR);
x2 = audio_get(a);
if (x2 < 0) return(FSK_READ_ERR);
assert (x1 >= 0 && x1 <= 255);
assert (x2 >= 0 && x2 <= 255);
sam = ( x2 << 8 ) | x1;
}
return (sam);
}
/*-------------------------------------------------------------------
*
* Name: demod_process_sample
*
* Purpose: (1) Demodulate the AFSK signal.
* (2) Recover clock and data.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
* subchan - modem of the channel.
* sam - One sample of audio.
* Should be in range of -32768 .. 32767.
*
* Returns: None
*
* Descripion: We start off with two bandpass filters tuned to
* the given frequencies. In the case of VHF packet
* radio, this would be 1200 and 2200 Hz.
*
* The bandpass filter amplitudes are compared to
* obtain the demodulated signal.
*
* We also have a digital phase locked loop (PLL)
* to recover the clock and pick out data bits at
* the proper rate.
*
* For each recovered data bit, we call:
*
* hdlc_rec (channel, demodulated_bit);
*
* to decode HDLC frames from the stream of bits.
*
* Future: This could be generalized by passing in the name
* of the function to be called for each bit recovered
* from the demodulator. For now, it's simply hard-coded.
*
*--------------------------------------------------------------------*/
__attribute__((hot))
void demod_process_sample (int chan, int subchan, int sam)
{
float fsam;
int k;
struct demodulator_state_s *D;
assert (chan >= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
D = &demodulator_state[chan][subchan];
/* Scale to nice number, actually -2.0 to +2.0 for extra headroom */
fsam = sam / 16384.0f;
/*
* Accumulate measure of the input signal level.
*/
/*
* Version 1.2: Try new approach to capturing the amplitude.
* This is same as the later AGC without the normalization step.
* We want decay to be substantially slower to get a longer
* range idea of the received audio.
*/
if (fsam >= D->alevel_rec_peak) {
D->alevel_rec_peak = fsam * D->quick_attack + D->alevel_rec_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_rec_peak = fsam * D->sluggish_decay + D->alevel_rec_peak * (1.0f - D->sluggish_decay);
}
if (fsam <= D->alevel_rec_valley) {
D->alevel_rec_valley = fsam * D->quick_attack + D->alevel_rec_valley * (1.0f - D->quick_attack);
}
else {
D->alevel_rec_valley = fsam * D->sluggish_decay + D->alevel_rec_valley * (1.0f - D->sluggish_decay);
}
/*
* Select decoder based on modulation type.
*/
switch (save_audio_config_p->achan[chan].modem_type) {
case MODEM_OFF:
// Might have channel only listening to DTMF for APRStt gateway.
// Don't waste CPU time running a demodulator here.
break;
case MODEM_AFSK:
if (save_audio_config_p->achan[chan].decimate > 1) {
sample_sum[chan][subchan] += sam;
sample_count[chan][subchan]++;
if (sample_count[chan][subchan] >= save_audio_config_p->achan[chan].decimate) {
demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / save_audio_config_p->achan[chan].decimate, D);
sample_sum[chan][subchan] = 0;
sample_count[chan][subchan] = 0;
}
}
else {
demod_afsk_process_sample (chan, subchan, sam, D);
}
break;
case MODEM_QPSK:
case MODEM_8PSK:
if (save_audio_config_p->achan[chan].decimate > 1) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid combination of options. Exiting.\n");
// Would probably work but haven't thought about it or tested yet.
exit (1);
}
else {
demod_psk_process_sample (chan, subchan, sam, D);
}
break;
case MODEM_BASEBAND:
case MODEM_SCRAMBLE:
default:
if (zerostuff) {
/* Literature says this is better if followed */
/* by appropriate low pass filter. */
/* So far, both are same in tests with different */
/* optimal low pass filter parameters. */
for (k=1; k= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
/* We have to consider two different cases here. */
/* N demodulators, each with own slicer and HDLC decoder. */
/* Single demodulator, multiple slicers each with own HDLC decoder. */
if (demodulator_state[chan][0].num_slicers > 1) {
subchan = 0;
}
D = &demodulator_state[chan][subchan];
// Take half of peak-to-peak for received audio level.
alevel.rec = (int) (( D->alevel_rec_peak - D->alevel_rec_valley ) * 50.0f + 0.5f);
if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) {
/* For AFSK, we have mark and space amplitudes. */
alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f);
alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f);
}
else if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK ||
save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) {
alevel.mark = -1;
alevel.space = -1;
}
else {
#if 1
/* Display the + and - peaks. */
/* Normally we'd expect them to be about the same. */
/* However, with SDR, or other DC coupling, we could have an offset. */
alevel.mark = (int) ((D->alevel_mark_peak) * 200.0f + 0.5f);
alevel.space = (int) ((D->alevel_space_peak) * 200.0f - 0.5f);
#else
/* Here we have + and - peaks after filtering. */
/* Take half of the peak to peak. */
/* The "5/6" factor worked out right for the current low pass filter. */
/* Will it need to be different if the filter is tweaked? */
alevel.mark = (int) ((D->alevel_mark_peak - D->alevel_space_peak) * 100.0f * 5.0f/6.0f + 0.5f);
alevel.space = -1; /* to print one number inside of ( ) */
#endif
}
return (alevel);
}
/* end demod.c */
direwolf-1.4/demod.h 0000664 0000000 0000000 00000000521 13100232553 0014422 0 ustar 00root root 0000000 0000000
/* demod.h */
#include "audio.h" /* for struct audio_s */
#include "ax25_pad.h" /* for alevel_t */
int demod_init (struct audio_s *pa);
int demod_get_sample (int a);
void demod_process_sample (int chan, int subchan, int sam);
void demod_print_agc (int chan, int subchan);
alevel_t demod_get_audio_level (int chan, int subchan); direwolf-1.4/demod_9600.c 0000664 0000000 0000000 00000037776 13100232553 0015121 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
//#define DEBUG4 1 /* capture 9600 output to log files */
/*------------------------------------------------------------------
*
* Module: demod_9600.c
*
* Purpose: Demodulator for scrambled baseband encoding.
*
* Input: Audio samples from either a file or the "sound card."
*
* Outputs: Calls hdlc_rec_bit() for each bit demodulated.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "tune.h"
#include "fsk_demod_state.h"
#include "hdlc_rec.h"
#include "demod_9600.h"
#include "textcolor.h"
#include "dsp.h"
static float slice_point[MAX_SUBCHANS];
/* Add sample to buffer and shift the rest down. */
__attribute__((hot)) __attribute__((always_inline))
static inline void push_sample (float val, float *buff, int size)
{
memmove(buff+1,buff,(size-1)*sizeof(float));
buff[0] = val;
}
/* FIR filter kernel. */
__attribute__((hot)) __attribute__((always_inline))
static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
{
float sum = 0.0f;
int j;
//#pragma GCC ivdep // ignored until gcc 4.9
for (j=0; j= *ppeak) {
*ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
}
else {
*ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
}
if (in <= *pvalley) {
*pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
}
else {
*pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
}
if (*ppeak > *pvalley) {
return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
}
return (0.0);
}
/*------------------------------------------------------------------
*
* Name: demod_9600_init
*
* Purpose: Initialize the 9600 (or higher) baud demodulator.
*
* Inputs: samples_per_sec - Number of samples per second.
* Might be upsampled in hopes of
* reducing the PLL jitter.
*
* baud - Data rate in bits per second.
*
* D - Address of demodulator state.
*
* Returns: None
*
*----------------------------------------------------------------*/
void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D)
{
float fc;
int j;
memset (D, 0, sizeof(struct demodulator_state_s));
D->num_slicers = 1;
// Multiple profiles in future?
// switch (profile) {
// case 'J': // upsample x2 with filtering.
// case 'K': // upsample x3 with filtering.
// case 'L': // upsample x4 with filtering.
D->lp_filter_len_bits = 76 * 9600.0 / (44100.0 * 2.0);
// Works best with odd number in some tests. Even is better in others.
//D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ))) * 2 + 1;
D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f);
D->lp_window = BP_WINDOW_HAMMING;
D->lpf_baud = 0.62;
D->agc_fast_attack = 0.080;
D->agc_slow_decay = 0.00012;
D->pll_locked_inertia = 0.89;
D->pll_searching_inertia = 0.67;
// break;
// }
D->pll_step_per_sample =
(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
#ifdef TUNE_LP_WINDOW
D->lp_window = TUNE_LP_WINDOW;
#endif
#if TUNE_LP_FILTER_SIZE
D->lp_filter_size = TUNE_LP_FILTER_SIZE;
#endif
#ifdef TUNE_LPF_BAUD
D->lpf_baud = TUNE_LPF_BAUD;
#endif
#ifdef TUNE_AGC_FAST
D->agc_fast_attack = TUNE_AGC_FAST;
#endif
#ifdef TUNE_AGC_SLOW
D->agc_slow_decay = TUNE_AGC_SLOW;
#endif
#if defined(TUNE_PLL_LOCKED)
D->pll_locked_inertia = TUNE_PLL_LOCKED;
#endif
#if defined(TUNE_PLL_SEARCHING)
D->pll_searching_inertia = TUNE_PLL_SEARCHING;
#endif
fc = (float)baud * D->lpf_baud / (float)samples_per_sec;
//dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size);
gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
/* Version 1.2: Experiment with different slicing levels. */
for (j = 0; j < MAX_SUBCHANS; j++) {
slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS-1));
//dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]);
}
} /* end fsk_demod_init */
/*-------------------------------------------------------------------
*
* Name: demod_9600_process_sample
*
* Purpose: (1) Filter & slice the signal.
* (2) Descramble it.
* (2) Recover clock and data.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
*
* sam - One sample of audio.
* Should be in range of -32768 .. 32767.
*
* Returns: None
*
* Descripion: "9600 baud" packet is FSK for an FM voice transceiver.
* By the time it gets here, it's really a baseband signal.
* At one extreme, we could have a 4800 Hz square wave.
* A the other extreme, we could go a considerable number
* of bit times without any transitions.
*
* The trick is to extract the digital data which has
* been distorted by going thru voice transceivers not
* intended to pass this sort of "audio" signal.
*
* Data is "scrambled" to reduce the amount of DC bias.
* The data stream must be unscrambled at the receiving end.
*
* We also have a digital phase locked loop (PLL)
* to recover the clock and pick out data bits at
* the proper rate.
*
* For each recovered data bit, we call:
*
* hdlc_rec (channel, demodulated_bit);
*
* to decode HDLC frames from the stream of bits.
*
* Future: This could be generalized by passing in the name
* of the function to be called for each bit recovered
* from the demodulator. For now, it's simply hard-coded.
*
* References: 9600 Baud Packet Radio Modem Design
* http://www.amsat.org/amsat/articles/g3ruh/109.html
*
* The KD2BD 9600 Baud Modem
* http://www.amsat.org/amsat/articles/kd2bd/9k6modem/
*
* 9600 Baud Packet Handbook
* ftp://ftp.tapr.org/general/9600baud/96man2x0.txt
*
*
*--------------------------------------------------------------------*/
inline static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D);
__attribute__((hot))
void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D)
{
float fsam;
float amp;
float demod_out;
#if DEBUG4
static FILE *demod_log_fp = NULL;
static int log_file_seq = 0; /* Part of log file name */
#endif
int subchan = 0;
int demod_data; /* Still scrambled. */
assert (chan >= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
/*
* Filters use last 'filter_size' samples.
*
* First push the older samples down.
*
* Finally, put the most recent at the beginning.
*
* Future project? Rather than shifting the samples,
* it might be faster to add another variable to keep
* track of the most recent sample and change the
* indexing in the later loops that multipy and add.
*/
/* Scale to nice number for convenience. */
/* Consistent with the AFSK demodulator, we'd like to use */
/* only half of the dynamic range to have some headroom. */
/* i.e. input range +-16k becomes +-1 here and is */
/* displayed in the heard line as audio level 100. */
fsam = sam / 16384.0;
#if defined(TUNE_ZEROSTUFF) && TUNE_ZEROSTUFF == 0
// experiment - no filtering.
amp = fsam;
#else
push_sample (fsam, D->raw_cb, D->lp_filter_size);
/*
* Low pass filter to reduce noise yet pass the data.
*/
amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size);
#endif
/*
* Version 1.2: Capture the post-filtering amplitude for display.
* This is similar to the AGC without the normalization step.
* We want decay to be substantially slower to get a longer
* range idea of the received audio.
* For AFSK, we keep mark and space amplitudes.
* Here we keep + and - peaks because there could be a DC bias.
*/
// TODO: probably no need for this. Just use D->m_peak, D->m_valley
if (amp >= D->alevel_mark_peak) {
D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
}
if (amp <= D->alevel_space_peak) {
D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
}
/*
* The input level can vary greatly.
* More importantly, there could be a DC bias which we need to remove.
*
* Normalize the signal with automatic gain control (AGC).
* This works by looking at the minimum and maximum signal peaks
* and scaling the results to be roughly in the -1.0 to +1.0 range.
*/
demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
// TODO: There is potential for multiple decoders with one filter.
//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm);
if (D->num_slicers <= 1) {
/* Normal case of one demodulator to one HDLC decoder. */
/* Demodulator output is difference between response from two filters. */
/* AGC should generally keep this around -1 to +1 range. */
demod_data = demod_out > 0;
nudge_pll (chan, subchan, 0, demod_out, D);
}
else {
int slice;
/* Multiple slicers each feeding its own HDLC decoder. */
for (slice=0; slicenum_slicers; slice++) {
demod_data = demod_out - slice_point[slice] > 0;
nudge_pll (chan, subchan, slice, demod_out - slice_point[slice], D);
}
}
// demod_data is used only for debug out.
// suppress compiler warning about it not being used.
(void) demod_data;
#if DEBUG4
if (chan == 0) {
if (1) {
//if (hdlc_rec_gathering (chan, subchan, slice)) {
char fname[30];
int slice = 0;
if (demod_log_fp == NULL) {
log_file_seq++;
snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq);
//if (log_file_seq == 1) mkdir ("demod", 0777);
if (log_file_seq == 1) mkdir ("demod");
demod_log_fp = fopen (fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Starting demodulator log file %s\n", fname);
fprintf (demod_log_fp, "Audio, Filtered, Max, Min, Normalized, Sliced, Clock\n");
}
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n",
fsam + 6,
amp + 4,
D->m_peak + 4,
D->m_valley + 4,
demod_out + 2,
demod_data + 2,
(D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0);
fflush (demod_log_fp);
}
else {
if (demod_log_fp != NULL) {
fclose (demod_log_fp);
demod_log_fp = NULL;
}
}
}
#endif
} /* end demod_9600_process_sample */
/*-------------------------------------------------------------------
*
* Name: nudge_pll
*
* Purpose: Update the PLL state for each audio sample.
*
* (2) Descramble it.
* (2) Recover clock and data.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
*
* subchan - Which demodulator. We could have several running in parallel.
*
* slice - Determines which Slicing level & HDLC decoder to use.
*
* demod_out_f - Demodulator output, possibly shifted by slicing level
* It will be compared with 0.0 to bit binary value out.
*
* D - Demodulator state for this channel / subchannel.
*
* Returns: None
*
* Descripton: A PLL is used to sample near the centers of the data bits.
*
* D->data_clock_pll is a SIGNED 32 bit variable.
* When it overflows from a large positive value to a negative value, we
* sample a data bit from the demodulated signal.
*
* Ideally, the the demodulated signal transitions should be near
* zero we we sample mid way between the transitions.
*
* Nudge the PLL by removing some small fraction from the value of
* data_clock_pll, pushing it closer to zero.
*
* This adjustment will never change the sign so it won't cause
* any erratic data bit sampling.
*
* If we adjust it too quickly, the clock will have too much jitter.
* If we adjust it too slowly, it will take too long to lock on to a new signal.
*
* I don't think the optimal value will depend on the audio sample rate
* because this happens for each transition from the demodulator.
*
* Version 1.4: Previously, we would always pull the PLL phase toward 0 after
* after a zero crossing was detetected. This adds extra jitter,
* especially when the ratio of audio sample rate to baud is low.
* Now, we interpolate between the two samples to get an estimate
* on when the zero crossing happened. The PLL is pulled toward
* this point.
*
* Results??? TBD
*
*--------------------------------------------------------------------*/
__attribute__((hot))
inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D)
{
/*
*/
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
if ( D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) {
/* Overflow. Was large positive, wrapped around, now large negative. */
hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr);
}
/*
* Zero crossing?
*/
if ((D->slicer[slice].prev_demod_out_f < 0 && demod_out_f > 0) ||
(D->slicer[slice].prev_demod_out_f > 0 && demod_out_f < 0)) {
// Note: Test for this demodulator, not overall for channel.
float target = 0;
target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f);
if (hdlc_rec_gathering (chan, subchan, slice)) {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia) );
}
else {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia) );
}
}
#if DEBUG5
//if (chan == 0) {
if (hdlc_rec_gathering (chan,subchan,slice)) {
char fname[30];
if (demod_log_fp == NULL) {
seq++;
snprintf (fname, sizeof(fname), "demod96/%04d.csv", seq);
if (seq == 1) mkdir ("demod96"
#ifndef __WIN32__
, 0777
#endif
);
demod_log_fp = fopen (fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Starting 9600 decoder log file %s\n", fname);
fprintf (demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n");
}
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n",
0.5f * fsam + 3.5,
0.5f * D->m_peak + 3.5,
0.5f * D->m_valley + 3.5,
0.5f * demod_out + 2.0,
demod_data ? 1.35 : 1.0,
descram ? .9 : .55,
(D->data_clock_pll & 0x80000000) ? .1 : .45);
}
else {
if (demod_log_fp != NULL) {
fclose (demod_log_fp);
demod_log_fp = NULL;
}
}
//}
#endif
/*
* Remember demodulator output (pre-descrambling) so we can compare next time
* for the DPLL sync.
*/
D->slicer[slice].prev_demod_out_f = demod_out_f;
} /* end nudge_pll */
/* end demod_9600.c */
direwolf-1.4/demod_9600.h 0000664 0000000 0000000 00000000665 13100232553 0015111 0 ustar 00root root 0000000 0000000
/* demod_9600.h */
#include "fsk_demod_state.h"
void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D);
void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D);
/* Undo data scrambling for 9600 baud. */
static inline int descramble (int in, int *state)
{
int out;
out = (in ^ (*state >> 16) ^ (*state >> 11)) & 1;
*state = (*state << 1) | (in & 1);
return (out);
}
direwolf-1.4/demod_afsk.c 0000664 0000000 0000000 00000077077 13100232553 0015445 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
// #define DEBUG1 1 /* display debugging info */
// #define DEBUG3 1 /* print carrier detect changes. */
// #define DEBUG4 1 /* capture AFSK demodulator output to log files */
// #define DEBUG5 1 /* capture 9600 output to log files */
/*------------------------------------------------------------------
*
* Module: demod_afsk.c
*
* Purpose: Demodulator for Audio Frequency Shift Keying (AFSK).
*
* Input: Audio samples from either a file or the "sound card."
*
* Outputs: Calls hdlc_rec_bit() for each bit demodulated.
*
*---------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "audio.h"
#include "tune.h"
#include "fsk_demod_state.h"
#include "fsk_gen_filter.h"
#include "hdlc_rec.h"
#include "textcolor.h"
#include "demod_afsk.h"
#include "dsp.h"
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
#ifndef GEN_FFF
/* Quick approximation to sqrt(x*x+y*y) */
/* No benefit for regular PC. */
/* Should help with microcomputer platform. */
__attribute__((hot)) __attribute__((always_inline))
static inline float z (float x, float y)
{
x = fabsf(x);
y = fabsf(y);
if (x > y) {
return (x * .941246f + y * .41f);
}
else {
return (y * .941246f + x * .41f);
}
}
/* Add sample to buffer and shift the rest down. */
__attribute__((hot)) __attribute__((always_inline))
static inline void push_sample (float val, float *buff, int size)
{
memmove(buff+1,buff,(size-1)*sizeof(float));
buff[0] = val;
}
/* FIR filter kernel. */
__attribute__((hot)) __attribute__((always_inline))
static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
{
float sum = 0.0f;
int j;
//#pragma GCC ivdep // ignored until gcc 4.9
for (j=0; j= *ppeak) {
*ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
}
else {
*ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
}
if (in <= *pvalley) {
*pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
}
else {
*pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
}
if (*ppeak > *pvalley) {
return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
}
return (0.0f);
}
#endif // ifndef GEN_FFF
/*
* for multi-slicer experiment.
*/
#define MIN_G 0.5f
#define MAX_G 4.0f
/* TODO: static */ float space_gain[MAX_SUBCHANS];
/*------------------------------------------------------------------
*
* Name: demod_afsk_init
*
* Purpose: Initialization for an AFSK demodulator.
* Select appropriate parameters and set up filters.
*
* Inputs: samples_per_sec
* baud
* mark_freq
* space_freq
*
* D - Pointer to demodulator state for given channel.
*
* Outputs: D->ms_filter_size
* D->m_sin_table[]
* D->m_cos_table[]
* D->s_sin_table[]
* D->s_cos_table[]
*
* Returns: None.
*
* Bugs: This doesn't do much error checking so don't give it
* anything crazy.
*
*----------------------------------------------------------------*/
void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
int space_freq, char profile, struct demodulator_state_s *D)
{
int j;
memset (D, 0, sizeof(struct demodulator_state_s));
D->num_slicers = 1;
#if DEBUG1
dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n",
samples_per_sec, baud, mark_freq, space_freq, profile);
#endif
#ifdef TUNE_PROFILE
profile = TUNE_PROFILE;
#endif
if (profile == 'F') {
if (baud != DEFAULT_BAUD ||
mark_freq != DEFAULT_MARK_FREQ ||
space_freq!= DEFAULT_SPACE_FREQ ||
samples_per_sec != DEFAULT_SAMPLES_PER_SEC) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Note: Decoder 'F' works only for %d baud, %d/%d tones, %d samples/sec.\n",
DEFAULT_BAUD, DEFAULT_MARK_FREQ, DEFAULT_SPACE_FREQ, DEFAULT_SAMPLES_PER_SEC);
dw_printf ("Using Decoder 'A' instead.\n");
profile = 'A';
}
}
D->profile = profile; // so we know whether to take fast path later.
switch (profile) {
case 'A':
case 'F':
/* Original. 52 taps, truncated bandpass, IIR lowpass */
/* 'F' is the fast version for low end processors. */
/* It is a special case that works only for a particular */
/* baud rate, tone pair, and sampling rate. */
D->use_prefilter = 0;
D->ms_filter_len_bits = 1.415; /* 52 @ 44100, 1200 */
D->ms_window = BP_WINDOW_TRUNCATED;
//D->bp_window = BP_WINDOW_TRUNCATED;
D->lpf_use_fir = 0;
D->lpf_iir = 0.195;
D->agc_fast_attack = 0.250;
D->agc_slow_decay = 0.00012;
D->hysteresis = 0.005;
D->pll_locked_inertia = 0.700;
D->pll_searching_inertia = 0.580;
break;
case 'B':
/* Original bandpass. Use FIR lowpass instead. */
D->use_prefilter = 0;
D->ms_filter_len_bits = 1.415; /* 52 @ 44100, 1200 */
D->ms_window = BP_WINDOW_TRUNCATED;
//D->bp_window = BP_WINDOW_TRUNCATED;
D->lpf_use_fir = 1;
D->lpf_baud = 1.09;
D->lp_filter_len_bits = D->ms_filter_len_bits;
D->lp_window = BP_WINDOW_TRUNCATED;
D->agc_fast_attack = 0.370;
D->agc_slow_decay = 0.00014;
D->hysteresis = 0.003;
D->pll_locked_inertia = 0.620;
D->pll_searching_inertia = 0.350;
break;
case 'C':
/* Cosine window, 76 taps for bandpass, FIR lowpass. */
D->use_prefilter = 0;
D->ms_filter_len_bits = 2.068; /* 76 @ 44100, 1200 */
D->ms_window = BP_WINDOW_COSINE;
//D->bp_window = BP_WINDOW_COSINE;
D->lpf_use_fir = 1;
D->lpf_baud = 1.09;
D->lp_filter_len_bits = D->ms_filter_len_bits;
D->lp_window = BP_WINDOW_TRUNCATED;
D->agc_fast_attack = 0.495;
D->agc_slow_decay = 0.00022;
D->hysteresis = 0.005;
D->pll_locked_inertia = 0.620;
D->pll_searching_inertia = 0.350;
break;
case 'D':
/* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */
D->use_prefilter = 1; /* first, a bandpass filter. */
D->prefilter_baud = 0.87;
D->pre_filter_len_bits = 1.857;
D->pre_window = BP_WINDOW_COSINE;
D->ms_filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */
D->ms_window = BP_WINDOW_COSINE;
//D->bp_window = BP_WINDOW_COSINE;
D->lpf_use_fir = 1;
D->lpf_baud = 1.10;
D->lp_filter_len_bits = D->ms_filter_len_bits;
D->lp_window = BP_WINDOW_TRUNCATED;
D->agc_fast_attack = 0.495;
D->agc_slow_decay = 0.00022;
D->hysteresis = 0.027;
D->pll_locked_inertia = 0.620;
D->pll_searching_inertia = 0.350;
break;
case 'E':
/* 1200 baud - Started out similar to C but add prefilter. */
/* Version 1.2 */
/* Enhancements: */
/* + Add prefilter. Previously used for 300 baud D, but not 1200. */
/* + Prefilter length now independent of M/S filters. */
/* + Lowpass filter length now independent of M/S filters. */
/* + Allow mixed window types. */
//D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */
D->use_prefilter = 1; /* first, a bandpass filter. */
D->prefilter_baud = 0.23;
D->pre_filter_len_bits = 156 * 1200. / 44100.;
D->pre_window = BP_WINDOW_TRUNCATED;
D->ms_filter_len_bits = 74 * 1200. / 44100.;
D->ms_window = BP_WINDOW_COSINE;
D->lpf_use_fir = 1;
D->lpf_baud = 1.18;
D->lp_filter_len_bits = 63 * 1200. / 44100.;
D->lp_window = BP_WINDOW_TRUNCATED;
//D->agc_fast_attack = 0.300;
//D->agc_slow_decay = 0.000185;
D->agc_fast_attack = 0.820;
D->agc_slow_decay = 0.000214;
D->hysteresis = 0.01;
//D->pll_locked_inertia = 0.57;
//D->pll_searching_inertia = 0.33;
D->pll_locked_inertia = 0.74;
D->pll_searching_inertia = 0.50;
break;
case 'G':
/* 1200 baud - Started out same as E but add 3 way interleave. */
/* Version 1.3 - EXPERIMENTAL - Needs more fine tuning. */
//D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */
D->use_prefilter = 1; /* first, a bandpass filter. */
D->prefilter_baud = 0.15;
D->pre_filter_len_bits = 128 * 1200. / (44100. / 3.);
D->pre_window = BP_WINDOW_TRUNCATED;
D->ms_filter_len_bits = 25 * 1200. / (44100. / 3.);
D->ms_window = BP_WINDOW_COSINE;
D->lpf_use_fir = 1;
D->lpf_baud = 1.16;
D->lp_filter_len_bits = 21 * 1200. / (44100. / 3.);
D->lp_window = BP_WINDOW_TRUNCATED;
D->agc_fast_attack = 0.130;
D->agc_slow_decay = 0.00013;
D->hysteresis = 0.01;
D->pll_locked_inertia = 0.73;
D->pll_searching_inertia = 0.64;
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid filter profile = %c\n", profile);
exit (1);
}
#ifdef TUNE_PRE_WINDOW
D->pre_window = TUNE_PRE_WINDOW;
#endif
#ifdef TUNE_MS_WINDOW
D->ms_window = TUNE_MS_WINDOW;
#endif
#ifdef TUNE_LP_WINDOW
D->lp_window = TUNE_LP_WINDOW;
#endif
#if defined(TUNE_AGC_FAST) && defined(TUNE_AGC_SLOW)
D->agc_fast_attack = TUNE_AGC_FAST;
D->agc_slow_decay = TUNE_AGC_SLOW;
#endif
#ifdef TUNE_HYST
D->hysteresis = TUNE_HYST;
#endif
#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
D->pll_locked_inertia = TUNE_PLL_LOCKED;
D->pll_searching_inertia = TUNE_PLL_SEARCHING;
#endif
#ifdef TUNE_LPF_BAUD
D->lpf_baud = TUNE_LPF_BAUD;
#endif
#ifdef TUNE_PRE_BAUD
D->prefilter_baud = TUNE_PRE_BAUD;
#endif
/*
* Calculate constants used for timing.
* The audio sample rate must be at least a few times the data rate.
*/
D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec));
/*
* Convert number of bit times to number of taps.
*/
D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud );
D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud );
D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud );
/* Experiment with other sizes. */
#ifdef TUNE_PRE_FILTER_SIZE
D->pre_filter_size = TUNE_PRE_FILTER_SIZE;
#endif
#ifdef TUNE_MS_FILTER_SIZE
D->ms_filter_size = TUNE_MS_FILTER_SIZE;
#endif
#ifdef TUNE_LP_FILTER_SIZE
D->lp_filter_size = TUNE_LP_FILTER_SIZE;
#endif
//assert (D->pre_filter_size >= 4);
assert (D->ms_filter_size >= 4);
//assert (D->lp_filter_size >= 4);
if (D->pre_filter_size > MAX_FILTER_SIZE)
{
text_color_set (DW_COLOR_ERROR);
dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
MAX_FILTER_SIZE);
exit (1);
}
if (D->ms_filter_size > MAX_FILTER_SIZE)
{
text_color_set (DW_COLOR_ERROR);
dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size);
dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
MAX_FILTER_SIZE);
exit (1);
}
if (D->lp_filter_size > MAX_FILTER_SIZE)
{
text_color_set (DW_COLOR_ERROR);
dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
MAX_FILTER_SIZE);
exit (1);
}
/*
* Optionally apply a bandpass ("pre") filter to attenuate
* frequencies outside the range of interest.
* This was first used for the "D" profile for 300 baud
* which uses narrow shift. We expect it to have significant
* benefit for a narrow shift.
* In version 1.2, we will also try it with 1200 baud "E" as
* an experiment to see how much it actually helps.
*/
if (D->use_prefilter) {
float f1, f2;
f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud;
f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud;
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2);
#endif
f1 = f1 / (float)samples_per_sec;
f2 = f2 / (float)samples_per_sec;
//gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_HAMMING);
//gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_BLACKMAN);
//gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_COSINE);
//gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->bp_window);
gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window);
}
/*
* Filters for detecting mark and space tones.
*/
#if DEBUG1
text_color_set(DW_COLOR_DEBUG);
dw_printf ("%s: \n", __FILE__);
dw_printf ("%d baud, %d samples_per_sec\n", baud, samples_per_sec);
dw_printf ("AFSK %d & %d Hz\n", mark_freq, space_freq);
dw_printf ("spll_step_per_sample = %d = 0x%08x\n", D->pll_step_per_sample, D->pll_step_per_sample);
dw_printf ("D->ms_filter_size = %d = 0x%08x\n", D->ms_filter_size, D->ms_filter_size);
dw_printf ("\n");
dw_printf ("Mark\n");
dw_printf (" j shape M sin M cos \n");
#endif
float Gs = 0, Gc = 0;
for (j=0; jms_filter_size; j++) {
float am;
float center;
float shape = 1.0f; /* Shape is an attempt to smooth out the */
/* abrupt edges in hopes of reducing */
/* overshoot and ringing. */
/* My first thought was to use a cosine shape. */
/* Should investigate Hamming and Blackman */
/* windows mentioned in the literature. */
/* http://en.wikipedia.org/wiki/Window_function */
center = 0.5f * (D->ms_filter_size - 1);
am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2.0f * (float)M_PI);
shape = window (D->ms_window, D->ms_filter_size, j);
D->m_sin_table[j] = sinf(am) * shape;
D->m_cos_table[j] = cosf(am) * shape;
Gs += D->m_sin_table[j] * sinf(am);
Gc += D->m_cos_table[j] * cosf(am);
#if DEBUG1
dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ;
#endif
}
/* Normalize for unity gain */
#if DEBUG1
dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ;
#endif
for (j=0; jms_filter_size; j++) {
D->m_sin_table[j] = D->m_sin_table[j] / Gs;
D->m_cos_table[j] = D->m_cos_table[j] / Gc;
}
#if DEBUG1
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Space\n");
dw_printf (" j shape S sin S cos\n");
#endif
Gs = 0;
Gc = 0;
for (j=0; jms_filter_size; j++) {
float as;
float center;
float shape = 1.0f;
center = 0.5 * (D->ms_filter_size - 1);
as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI);
shape = window (D->ms_window, D->ms_filter_size, j);
D->s_sin_table[j] = sinf(as) * shape;
D->s_cos_table[j] = cosf(as) * shape;
Gs += D->s_sin_table[j] * sinf(as);
Gc += D->s_cos_table[j] * cosf(as);
#if DEBUG1
dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ;
#endif
}
/* Normalize for unity gain */
#if DEBUG1
dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ;
#endif
for (j=0; jms_filter_size; j++) {
D->s_sin_table[j] = D->s_sin_table[j] / Gs;
D->s_cos_table[j] = D->s_cos_table[j] / Gc;
}
/*
* Now the lowpass filter.
* I thought we'd want a cutoff of about 0.5 the baud rate
* but it turns out about 1.1x is better. Still investigating...
*/
if (D->lpf_use_fir) {
float fc;
fc = baud * D->lpf_baud / (float)samples_per_sec;
gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window);
}
/*
* A non-whole number of cycles results in a DC bias.
* Let's see if it helps to take it out.
* Actually makes things worse: 20 fewer decoded.
* Might want to try again after EXPERIMENTC.
*/
#if 0
#ifndef AVOID_FLOATING_POINT
failed experiment
dc_bias = 0;
for (j=0; jms_filter_size; j++) {
dc_bias += D->m_sin_table[j];
}
for (j=0; jms_filter_size; j++) {
D->m_sin_table[j] -= dc_bias / D->ms_filter_size;
}
dc_bias = 0;
for (j=0; jms_filter_size; j++) {
dc_bias += D->m_cos_table[j];
}
for (j=0; jms_filter_size; j++) {
D->m_cos_table[j] -= dc_bias / D->ms_filter_size;
}
dc_bias = 0;
for (j=0; jms_filter_size; j++) {
dc_bias += D->s_sin_table[j];
}
for (j=0; jms_filter_size; j++) {
D->s_sin_table[j] -= dc_bias / D->ms_filter_size;
}
dc_bias = 0;
for (j=0; jms_filter_size; j++) {
dc_bias += D->s_cos_table[j];
}
for (j=0; jms_filter_size; j++) {
D->s_cos_table[j] -= dc_bias / D->ms_filter_size;
}
#endif
#endif
/*
* In version 1.2 we try another experiment.
* Try using multiple slicing points instead of the traditional AGC.
*/
space_gain[0] = MIN_G;
float step = powf(10.0, log10f(MAX_G/MIN_G) / (MAX_SUBCHANS-1));
for (j=1; j= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
/*
* Filters use last 'filter_size' samples.
*
* First push the older samples down.
*
* Finally, put the most recent at the beginning.
*
* Future project? Can we do better than shifting each time?
*/
/* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */
fsam = sam / 16384.0f;
//abs_fsam = fsam >= 0.0f ? fsam : -fsam;
/*
* Optional bandpass filter before the mark/space discriminator.
*/
if (D->use_prefilter) {
float cleaner;
push_sample (fsam, D->raw_cb, D->pre_filter_size);
cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size);
push_sample (cleaner, D->ms_in_cb, D->ms_filter_size);
}
else {
push_sample (fsam, D->ms_in_cb, D->ms_filter_size);
}
/*
* Next we have bandpass filters for the mark and space tones.
*
* This takes a lot of computation.
* It's not a problem on a typical (Intel x86 based) PC.
* Dire Wolf takes only about 2 or 3% of the CPU time.
*
* It might be too much for a little microcomputer to handle.
*
* Here we have an optimized case for the default values.
*/
// TODO1.2: is this right or do we need to store profile in the modulator info?
if (D->profile == toupper(FFF_PROFILE)) {
/* ========== Faster for default values on slower processors. ========== */
m_sum1 = CALC_M_SUM1(D->ms_in_cb);
m_sum2 = CALC_M_SUM2(D->ms_in_cb);
m_amp = z(m_sum1,m_sum2);
s_sum1 = CALC_S_SUM1(D->ms_in_cb);
s_sum2 = CALC_S_SUM2(D->ms_in_cb);
s_amp = z(s_sum1,s_sum2);
}
else {
/* ========== General case to handle all situations. ========== */
/*
* find amplitude of "Mark" tone.
*/
m_sum1 = convolve (D->ms_in_cb, D->m_sin_table, D->ms_filter_size);
m_sum2 = convolve (D->ms_in_cb, D->m_cos_table, D->ms_filter_size);
m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2);
/*
* Find amplitude of "Space" tone.
*/
s_sum1 = convolve (D->ms_in_cb, D->s_sin_table, D->ms_filter_size);
s_sum2 = convolve (D->ms_in_cb, D->s_cos_table, D->ms_filter_size);
s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2);
/* ========== End of general case. ========== */
}
/*
* Apply some low pass filtering BEFORE the AGC to remove
* overshoot, ringing, and other bad stuff.
*
* A simple IIR filter is faster but FIR produces better results.
*
* It is a balancing act between removing high frequency components
* from the tone dectection while letting the data thru.
*/
if (D->lpf_use_fir) {
push_sample (m_amp, D->m_amp_cb, D->lp_filter_size);
m_amp = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size);
push_sample (s_amp, D->s_amp_cb, D->lp_filter_size);
s_amp = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size);
}
else {
/* Original, but faster, IIR. */
m_amp = D->lpf_iir * m_amp + (1.0f - D->lpf_iir) * D->m_amp_prev;
D->m_amp_prev = m_amp;
s_amp = D->lpf_iir * s_amp + (1.0f - D->lpf_iir) * D->s_amp_prev;
D->s_amp_prev = s_amp;
}
/*
* Version 1.2: Try new approach to capturing the amplitude for display.
* This is same as the AGC above without the normalization step.
* We want decay to be substantially slower to get a longer
* range idea of the received audio.
*/
if (m_amp >= D->alevel_mark_peak) {
D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
}
if (s_amp >= D->alevel_space_peak) {
D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
}
/*
* Which tone is stronger?
*
* In an ideal world, simply compare. In my first naive attempt, that
* worked perfectly with perfect signals. In the real world, we don't
* have too many perfect signals.
*
* Here is an excellent explanation:
* http://www.febo.com/packet/layer-one/transmit.html
*
* Under real conditions, we find that the higher tone has a
* considerably smaller amplitude due to the passband characteristics
* of the transmitter and receiver. To make matters worse, it
* varies considerably from one station to another.
*
* The two filters also have different amounts of DC bias.
*
* My solution was to apply automatic gain control (AGC) to the mark and space
* levels. This works by looking at the minimum and maximum outputs
* for each filter and scaling the results to be roughly in the -0.5 to +0.5 range.
* Results were excellent after tweaking the attack and decay times.
*
* 4X6IZ took a different approach. See QEX Jul-Aug 2012.
*
* He ran two different demodulators in parallel. One of them boosted the higher
* frequency tone by 6 dB. Any duplicates were removed. This produced similar results.
* He also used a bandpass filter before the mark/space filters.
* I haven't tried this combination yet for 1200 baud.
*
* First, let's take a look at Track 1 of the TNC test CD. Here the receiver
* has a flat response. We find the mark/space strength ratios very from 0.53 to 1.38
* with a median of 0.81. This in in line with expections because most
* transmitters add pre-emphasis to boost the higher audio frequencies.
* Track 2 should more closely resemble what comes out of the speaker on a typical
* transceiver. Here we see a ratio from 1.73 to 3.81 with a median of 2.48.
*
* This is similar to my observations of local signals, from the speaker.
* The amplitude ratio varies from 1.48 to 3.41 with a median of 2.70.
*
* Rather than only two filters, let's try slicing the data in more places.
*/
/* Fast attack and slow decay. */
/* Numbers were obtained by trial and error from actual */
/* recorded less-than-optimal signals. */
/* See fsk_demod_agc.h for more information. */
m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley));
if (D->num_slicers <= 1) {
/* Normal case of one demodulator to one HDLC decoder. */
/* Demodulator output is difference between response from two filters. */
/* AGC should generally keep this around -1 to +1 range. */
demod_out = m_norm - s_norm;
/* Try adding some Hysteresis. */
/* (Not to be confused with Hysteria.) */
if (demod_out > D->hysteresis) {
demod_data = 1;
}
else if (demod_out < (- (D->hysteresis))) {
demod_data = 0;
}
else {
demod_data = D->slicer[subchan].prev_demod_data;
}
nudge_pll (chan, subchan, 0, demod_data, D);
}
else {
int slice;
for (slice=0; slicenum_slicers; slice++) {
demod_data = m_amp > s_amp * space_gain[slice];
nudge_pll (chan, subchan, slice, demod_data, D);
}
}
#if DEBUG4
if (chan == 0) {
if (hdlc_rec_gathering (chan, subchan)) {
char fname[30];
if (demod_log_fp == NULL) {
seq++;
snprintf (fname, sizeof(fname), "demod/%04d.csv", seq);
if (seq == 1) mkdir ("demod", 0777);
demod_log_fp = fopen (fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Starting demodulator log file %s\n", fname);
fprintf (demod_log_fp, "Audio, Mark, Space, Demod, Data, Clock\n");
}
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f\n", fsam + 3.5, m_norm + 2, s_norm + 2,
(m_norm - s_norm) / 2 + 1.5,
demod_data ? .9 : .55,
(D->data_clock_pll & 0x80000000) ? .1 : .45);
}
else {
if (demod_log_fp != NULL) {
fclose (demod_log_fp);
demod_log_fp = NULL;
}
}
}
#endif
} /* end demod_afsk_process_sample */
__attribute__((hot))
inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D)
{
/*
* Finally, a PLL is used to sample near the centers of the data bits.
*
* D points to a demodulator for a channel/subchannel pair so we don't
* have to keep recalculating it.
*
* D->data_clock_pll is a SIGNED 32 bit variable.
* When it overflows from a large positive value to a negative value, we
* sample a data bit from the demodulated signal.
*
* Ideally, the the demodulated signal transitions should be near
* zero we we sample mid way between the transitions.
*
* Nudge the PLL by removing some small fraction from the value of
* data_clock_pll, pushing it closer to zero.
*
* This adjustment will never change the sign so it won't cause
* any erratic data bit sampling.
*
* If we adjust it too quickly, the clock will have too much jitter.
* If we adjust it too slowly, it will take too long to lock on to a new signal.
*
* Be a little more agressive about adjusting the PLL
* phase when searching for a signal. Don't change it as much when
* locked on to a signal.
*
* I don't think the optimal value will depend on the audio sample rate
* because this happens for each transition from the demodulator.
*/
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
D->slicer[slice].data_clock_pll += D->pll_step_per_sample;
//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);
if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) {
/* Overflow. */
hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1);
}
if (demod_data != D->slicer[slice].prev_demod_data) {
if (hdlc_rec_gathering (chan, subchan, slice)) {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia);
}
else {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia);
}
}
/*
* Remember demodulator output so we can compare next time.
*/
D->slicer[slice].prev_demod_data = demod_data;
} /* end nudge_pll */
#endif /* GEN_FFF */
/* end demod_afsk.c */
direwolf-1.4/demod_afsk.h 0000664 0000000 0000000 00000000374 13100232553 0015434 0 ustar 00root root 0000000 0000000
/* demod_afsk.h */
void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
int space_freq, char profile, struct demodulator_state_s *D);
void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D);
direwolf-1.4/demod_psk.c 0000664 0000000 0000000 00000061427 13100232553 0015306 0 ustar 00root root 0000000 0000000 //
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see